# @mastra/core ## 1.83.0-alpha.1 ### Minor Changes - Added resumable streams to every `Agent`, not just durable agents. An agent registered on `Mastra` now publishes each run on its stream topic and keeps a replay cache, so a client can reconnect after a disconnect, another reader can follow the run with `agent.observe(runId)`, and any reader can stop it with `observe().abort()` or `agent.abortRunStream(runId)`. ([#28424](https://github.com/mastra-ai/mastra/pull/28424)) Configure it with the new `streams` option. Streams are on by default for agents registered on `Mastra` and off for standalone agents. Pass `streams: false` to turn them off, or an object to set `pubsub`, `cache`, `shouldCache` and `cleanupTimeoutMs`: ```ts const agent = new Agent({ id: 'batch-agent', name: 'Batch agent', instructions: 'You classify support tickets.', model: 'openai/gpt-5-mini', streams: false, }); ``` For durable agents, `streams` keys override the matching `durable` keys. `durable: true` with `streams: false` keeps publishing but turns off the replay cache. A durable agent on the default engine now publishes on `mastra.pubsub` once registered, instead of a private in-process bus, so other `Mastra` instances sharing that PubSub and cache can observe its runs. An agent-level `pubsub` still takes precedence. Observing a run follows it across tool-approval suspensions until it finishes, errors or is aborted, for plain and durable agents alike. Reconnecting is not crash recovery: if the process running a plain agent stops, the run stops. Behaviour change: with streams on, closing the HTTP stream no longer aborts a plain agent run on the server. The server detaches the reader and the run keeps generating tokens, calling tools and running `onFinish`, so the client can reconnect. Stop a run with `POST /agents/:agentId/runs/:runId/abort`, which now also works for plain agents with resumable streams, or the thread abort route. Set `streams: false` to keep the old disconnect-aborts behaviour. Behaviour change: observing a run that can't be resumed now fails instead of returning an empty stream. An unknown run, or one whose ownership record expired or was evicted, answers 404, so `client.agents(id).observe({ runId })` rejects with a `MastraClientError` carrying that status. This includes durable agents, which used to resolve to an empty, closed stream. Servers that share a PubSub and cache (for example Redis) can observe and abort a plain agent run started on any of them. Each run writes an ownership record (agent, run, resource, thread) to the cache before its first event, and the observe and abort routes check the caller against it, including Studio auth mode. Active runs retry a failed initial ownership write after a temporary cache outage. Unknown, expired or evicted runs return 404, and an unreadable cache returns 503. The abort route returns `{ aborted: true }` only when the executing process confirms the stop. Finished runs, runs parked on an approval, and unanswered requests return `{ aborted: false }`. Behaviour change: a `runId` now belongs to the run that first claimed it. Starting a run with a `runId` that another resource already owns is refused with `AGENT_RUN_ID_ALREADY_CLAIMED` (409 from the server) instead of taking over that run's stream topic and ownership record. A caller with no resource id may not claim a run id that carries one. Runs without a resource id scope nothing to protect, and a run's owner can still reuse its id for a resume or a retry after it finished. A `runId` whose record says a run is still executing is refused even to its owner, because two publishers on one topic would interleave their chunks and abort requests; the one exception is a resume, which has already claimed the suspended run and may still be looking at the previous `running` record while a slow suspend write lands. Reusing a finished id starts on an empty log, so a reader does not replay the previous run's `finish` and stop early, and the previous run's cleanup timer no longer clears the record or history of the run that reused the id. Run logs and ownership records ask the cache for a 24-hour TTL. `cleanupTimeoutMs: 0` turns off only the post-finish cleanup timer. Cache TTL and eviction still apply, and the in-memory cache's default limit of 1,000 counts cache entries, not runs. Cleanup timers no longer keep an otherwise finished Node.js process alive. - Chat SDK logs from channels now use your Mastra logger, so they follow its format, level, and redact settings. These logs no longer print with the `[chat-sdk]` console prefix. To filter them, use `component: 'chat-sdk'`. Use the new `toChatLogger` export to send adapter logs to the same logger. ([#29392](https://github.com/mastra-ai/mastra/pull/29392)) ```ts import { toChatLogger } from '@mastra/core/channels'; const logger = new PinoLogger({ name: 'app', level: 'info' }); createDiscordAdapter({ logger: toChatLogger(logger, { component: 'discord', minLevel: 'warn' }) }); ``` Chat SDK warnings and errors now also reach observability log export, like other Mastra logs. `redact` doesn't apply to these exports. Channel logs from `PinoLogger` now have a `component` field in place of stray `0`, `1`, `2` fields. - Added direct feedback lookup to observability storage. `await storage.getFeedbackById(feedbackId)` returns the record or `null` when it is missing. ([#29529](https://github.com/mastra-ai/mastra/pull/29529)) - BREAKING: On a plain `Agent`, `durable` now controls resumable streams only, not checkpoint-backed execution. Omit it for streams on registered agents and off standalone agents; use `true` to enable streams everywhere, `false` to disable them, or an object containing `pubsub`, `cache`, `shouldCache`, and `cleanupTimeoutMs`. ([#29503](https://github.com/mastra-ai/mastra/pull/29503)) Remove implicit durable execution wrapping, both on Mastra registration and standalone calls. `maxSteps`, `shouldPersistSnapshot`, `id`, and `name` are no longer plain-agent durable options; pass execution options to `createDurableAgent` instead. JavaScript callers and persisted agents carrying those keys receive a migration warning. Export `AgentStreamsConfig` alongside `AgentDurableOption` so callers can name the stream configuration object type. The published `@mastra/core@1.82.0` API already uses `durable` and exports neither `AgentStreamsConfig` nor `AgentStreamsOption`. The `streams` to `durable` rename applies only to unreleased development builds; the released breaking change is the meaning of `durable`. Before: ```ts const agent = new Agent({ ...config, durable: { maxSteps: 50 } }); ``` After, for durable execution: ```ts import { createDurableAgent } from '@mastra/core/agent/durable'; const agent = createDurableAgent({ agent: new Agent(config), maxSteps: 50, cache: false, }); ``` For resumable streams without checkpoint execution: ```ts const agent = new Agent({ ...config, durable: { cache: false } }); ``` Existing stored agents with `durable: true` or a durable options object now load as plain agents with resumable streams. They no longer write checkpoints or recover execution, and an existing stored `maxSteps` value no longer configures execution. Migrate agents that need durable execution to an explicit code-defined `createDurableAgent({ agent })` wrapper. With `durable: false`, HTTP disconnects abort plain-agent runs; explicit durable wrappers continue running. ### Patch Changes - Fixed client tools and AI SDK `jsonSchema()` tools losing unions (`anyOf`) in their input schema. The model was offered an empty object instead of every branch, so it could only send empty arguments. Union schemas now reach the model intact. ([#29440](https://github.com/mastra-ai/mastra/pull/29440)) Tools with a plain JSON Schema input (including client tools sent by `@mastra/client-js`) now validate the model's arguments against that schema. Previously any arguments were accepted; arguments that don't match the schema now return a validation error to the model instead of reaching the tool. AI SDK `jsonSchema()` tools are still validated only by their own `validate` function, if they provide one. - Fixed background tasks that finish after their run ended going unreported or reported twice: ([#29442](https://github.com/mastra-ai/mastra/pull/29442)) - The agent woken to report a finished task now uses the request context of the run that dispatched it, so a model, instructions, or memory resolved from that context no longer fail. - A finished task is now reported once another agent's run on the same thread ends, for example after it waits for a tool approval, instead of being dropped. - A stored version of a durable agent no longer reports a finished task twice while an `untilIdle` stream is open. - A task is still reported when a newer `untilIdle` stream on the thread takes over and can't track it, instead of being treated as stopped by the caller. - Fixed streaming supervisors that delegate to a durable or evented sub-agent: ([#29592](https://github.com/mastra-ai/mastra/pull/29592)) - The supervisor now receives the sub-agent's answer instead of a "Failed agent tool execution" error. - Tool approval requests from the sub-agent now reach the supervisor instead of stalling its stream. - After an approval or decline, the delegation result points to the sub-agent's own thread and conversation. - A declined tool call is no longer reported as a tool that never ran. - Redacted tool-result fields stay redacted when the sub-agent's conversation is saved. `generate()` was not affected. - Fixed embedding models for providers whose base URL comes from an environment variable, such as Neon. These models previously failed with `Invalid URL` because the provider URL template was not resolved. ([#29631](https://github.com/mastra-ai/mastra/pull/29631)) - Fixed a nested workflow overwriting parent state that another step changed while it ran. When a nested workflow ran inside `.parallel()` or `.foreach()` and its `stateSchema` filled in a default or transformed a value, that value replaced whatever the other step had written to the same key. The nested workflow now hands back only the values its own steps changed. Fixes [#29347](https://github.com/mastra-ai/mastra/issues/29347). ([#29489](https://github.com/mastra-ai/mastra/pull/29489)) - Fixed evented durable agent runs that hung forever after the API and worker restarted together mid-tool. The recovered run now finishes and releases its thread, so the next run on that thread is no longer rejected with `AgentThreadLeaseConflictError`. ([#29431](https://github.com/mastra-ai/mastra/pull/29431)) - Agent stream chunks now carry `index`, their run-log position, so a client can resume `observe()` after a dropped connection without repeated or missing chunks. Chunks from `observe()` carry it on every agent, and chunks from `stream()` carry it on durable and evented agents, in core and over HTTP. ([#29520](https://github.com/mastra-ai/mastra/pull/29520)) ```ts let offset = 0; const response = await agent.stream(messages, { runId }); await response.processDataStream({ onChunk: async chunk => { if (chunk.index === undefined) return; offset = chunk.index + 1; }, }); // After a dropped connection: const resumed = await agent.observe({ runId, offset }); await resumed.processDataStream({ onChunk: async chunk => console.log(chunk) }); ``` Callers that pass a run-log position, or replay from `offset: 0`, need no change. Callers that passed a count of received chunks as `offset` should switch to the last `index + 1`. - Resuming a finished plain (non-durable) agent run past its last chunk now closes the stream right away. Over HTTP `/observe` it used to wait for the idle timeout, and `agent.observe()` in core used to stay open until the caller detached. ([#29520](https://github.com/mastra-ai/mastra/pull/29520)) - Fixed duplicate assistant messages after resuming a thread with memory. When a sealed assistant message ended in whitespace, such as a trailing newline, contained working memory, or contained a tool call that was still streaming when it was saved, the copy loaded back from memory was added again under a new id. OpenAI Responses requests then failed with `Duplicate item found with id rs_...`. The stored copy is now recognized as the same message. Fixes #29380. ([#29537](https://github.com/mastra-ai/mastra/pull/29537)) - Workflow runs with the experimental `storageVersion` option now keep the version they were stored with: resuming a run after changing the option continues it in that version instead of throwing. A nested workflow's runs now use their parent run's version. ([#28708](https://github.com/mastra-ai/mastra/pull/28708)) - A durable sub-agent's run-log `index` is not copied onto the parent agent's stream, where it would read as a position in the parent's run. Streams numbered from a run log carry the parent run's own `index`; a plain in-process `agent.stream()` carries none. ([#29520](https://github.com/mastra-ai/mastra/pull/29520)) - Fixed client tool results being saved as a second copy of the tool call. When a browser answered a client tool, memory kept the original pending call and stored the call again with its result, so tool cards showed up twice after reloading a thread. The result now completes the stored call, including when the model called a server tool and a client tool in the same step. ([#29440](https://github.com/mastra-ai/mastra/pull/29440)) - Durable agent runs that finish on another worker now record their real span outcome (success, limit/max_steps, guardrail with its processor id, delegation failure) instead of unknown. The loop state carries the termination cause between steps, so the result no longer depends on the process that started the run. ([#29261](https://github.com/mastra-ai/mastra/pull/29261)) ## 1.83.0-alpha.0 ### Minor Changes - The experimental workflow `storageVersion: 2` option now works with `@mastra/pg` and `@mastra/libsql` as well as in-memory storage. ([#28299](https://github.com/mastra-ai/mastra/pull/28299)) - Added an experimental `storageVersion` workflow option. With `storageVersion: 2`, a run saves only what changed at each step instead of rewriting the whole run. This cuts storage writes for long workflows and loops, including loops whose state grows every iteration. ([#27144](https://github.com/mastra-ai/mastra/pull/27144)) ```ts const workflow = createWorkflow({ id: 'my-workflow', inputSchema, outputSchema, options: { storageVersion: 2 }, }); ``` **Limitations** - Version 2 requires the default engine and a storage adapter that supports it: in-memory, `@mastra/pg`, or `@mastra/libsql`. Creating a run on another adapter throws. - Inside version 2 runs, use `await getStepResultAsync(step)` instead of `getStepResult(step)`. - A `shouldPersistSnapshot` predicate on a version 2 run sees earlier successful steps without their `output`. - Workflow state is stored as JSON, so values such as `Date` are read back as strings, as with database storage. - Runs keep the version they were created with, so resuming a run after changing this option throws. - Add optional context mode, timestamp, and response identity fields to HTTP feedback/score types. Preserve an explicit null feedback author in storage without substituting the span user; omitted legacy storage attribution remains supported. ([#29289](https://github.com/mastra-ai/mastra/pull/29289)) ```ts await observability.createFeedback({ feedback: { feedbackId: 'feedback-1', timestamp: new Date(), traceId: 'trace-123', feedbackType: 'thumbs', value: 1, userId: 'span-user', feedbackUserId: null, // Anonymous author; the span user stays separate. }, }); ``` - Required a nonblank `traceId` in the feedback and score HTTP body schemas. Missing, null, empty, and whitespace-only IDs are rejected. Storage creation, batch APIs, and historical nullable records are unchanged. ([#29287](https://github.com/mastra-ai/mastra/pull/29287)) Before, HTTP callers could omit the trace: ```ts await client.createFeedback({ feedback: { feedbackType: 'thumbs', value: 1 } }); ``` Now, supply its ID. `spanId` remains optional: ```ts await client.createFeedback({ feedback: { traceId: 'trace-123', feedbackType: 'thumbs', value: 1 } }); await client.createScore({ score: { traceId: 'trace-123', scorerId: 'accuracy', score: 0.9 } }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`1a078af`](https://github.com/mastra-ai/mastra/commit/1a078afbb1cb4ac73eca047e80d366c57e83e5e8)) - The agent controller's `command_exit` event now says how a command ended, so clients can show more than a failure code: `killed` when the process was killed, `timedOut` when it ran past its timeout, and `error` when the sandbox could not run it (its `exitCode` is then -1). The `data-sandbox-exit` stream part carries the same `error`. `kill_process` no longer reports an exit code of -1 for a process it could not find. `kill_process` and `get_process_output` also send `command_exit`, for the process they stopped or waited on, so a non-zero exit only means the call itself failed when it comes from `execute_command`. ([#29314](https://github.com/mastra-ai/mastra/pull/29314)) ```ts session.subscribe(event => { if (event.type !== 'command_exit') return; if (event.error) showError(event.toolCallId, event.error); else if (event.timedOut) showTimedOut(event.toolCallId); else if (event.killed) showKilled(event.toolCallId); else if (!event.success) showExitCode(event.toolCallId, event.exitCode); }); ``` - Fixed evented agent runs staying in `running` forever when the lease backend restarted while a tool was finishing. The execution that still owns the run now takes back a thread lease that expired during the restart, and the run no longer aborts as if it lost its lease while the backend is refusing every client. ([#29430](https://github.com/mastra-ai/mastra/pull/29430)) Custom `LeaseProvider` implementations can add the optional `isUnavailableToAll(error)` method to report errors where the backend refuses every client, so no other owner can be granted the lease meanwhile. Return `false` for any other error, including connection failures that may affect only this caller. ```ts import type { LeaseProvider } from '@mastra/core/events'; const leaseProvider: LeaseProvider = { // ...acquireLease, getLeaseOwner, releaseLease, renewLease, transferLease isUnavailableToAll: error => error instanceof Error && error.message.startsWith('LOADING'), }; ``` - Fixed nested workflows losing their state changes after a crash. When the process stopped after a nested workflow had finished but before its parent saved that, restarting the parent used the nested workflow's output but dropped the changes it made to the workflow state. ([#29308](https://github.com/mastra-ai/mastra/pull/29308)) - Fixed the `SOURCE_MAP.json` file shipped with the package docs. It previously listed minified names such as `t` and `n`, so tools that look up public exports could not find them. The map now lists public exports by name. Each entry points to the type definition file that declares the export and the line where it is implemented, when those can be resolved; otherwise it falls back to the module's main type definition file. Default exports and names the package re-exports from another package are left out. ([#29328](https://github.com/mastra-ai/mastra/pull/29328)) - Fixed restarting a `.foreach()` over a nested workflow failing with "This workflow run was not active" when an item was running at the moment the process stopped. That item now starts over, and the restarted run finishes. ([#29308](https://github.com/mastra-ai/mastra/pull/29308)) - Fixed `.foreach()` applying a finished item's state changes twice after a restart. If the process stopped at the wrong moment, the saved run could hold an item's state changes without marking the item as finished, so restarting the run ran that item again on top of its own changes. An item is now saved as finished together with its state changes. ([#29308](https://github.com/mastra-ai/mastra/pull/29308)) - Fixed `writer` being undefined in `processOutputResult` for agents wrapped with `createDurableAgent` or `createEventedAgent`. Chunks written with `writer.custom()` now stream before `finish`, and non-transient chunks are saved with the reply, as with a regular `Agent`. Fixes [#29213](https://github.com/mastra-ai/mastra/issues/29213). ([#29286](https://github.com/mastra-ai/mastra/pull/29286)) Fixed output processor data being saved on the rejected answer when an `isTaskComplete` check failed and the agent answered again. The data is now saved with the answer the user sees. - Fixed a type error when calling a tool directly without a context. `tool.execute(input)` now compiles, so you no longer need casts like `tool.execute(input, {} as ToolExecutionContext)` in tests, scripts, or other tools. Without a context the tool still gets an empty request context but no Mastra instance, writer, memory, or thread IDs, so pass a context when the tool needs those. Fixes #28651. ([#29419](https://github.com/mastra-ai/mastra/pull/29419)) If your code assigns its own function to `tool.execute`, declare that function's context parameter as optional (`context?`). Helpers that infer from the function type, such as `vi.fn(tool.execute!)`, still see the signature that requires a context, as before. - Storage version 2 workflow results no longer load every step result into memory. With `storageVersion: 2`, `start()`, `resume()`, `restart()` and `timeTravel()` return `status`, `result`, `error`, `suspended` and the other result fields right away, and `steps` is an async function that reads the full step history from storage when you call it. Version 1 results are unchanged, and `steps` is still a record. ([#29362](https://github.com/mastra-ai/mastra/pull/29362)) ```ts const run = await workflow.createRun(); const result = await run.start({ inputData }); console.log(result.status, result.result); // available right away const steps = await result.steps(); // reads the step results from storage ``` Calling `steps()` more than once, or at the same time, reads storage only once. `onFinish` and `onError` callbacks receive the same function and can await it. If the run moved on after the result was returned, for example because it was resumed or deleted, `steps()` throws `WORKFLOW_STORAGE_V2_STEPS_STALE` instead of returning newer results. The workflow HTTP routes and MCP workflow tools still return `steps` as an object. - Fixed unbounded shell output retention in the controller display state and the Mastra Code terminal UI. Live shell output now keeps the latest 1 MB per command. ([#28808](https://github.com/mastra-ai/mastra/pull/28808)) `execute_command` no longer rewrites a trailing `| tail -N`; the pipeline runs as written. Pipelines that end in `tail` now run with `pipefail` where the shell supports it, so a failing upstream command reports its non-zero exit code instead of `tail`'s success. The `tail` option still limits output returned to the model. - Fixed `mastra.restartAllActiveWorkflowRuns()` and `workflow.restartAllActiveWorkflowRuns()` restarting a nested workflow run on its own. A nested run is collected by its parent's step, so a standalone restart either ran the same steps twice while the parent was driving it, or ran an item a second time after the parent had already finished. ([#29308](https://github.com/mastra-ai/mastra/pull/29308)) Both sweeps now read the parent's stored status before restarting a nested run: - Parent suspended, or parent run missing: the nested run restarts on its own. - Parent finished: the nested run is marked canceled. - Parent running, waiting, pending, or paused: the nested run is left for its parent to drive. A run left behind by a parent that restarted its item under a new run id stays as it was until a sweep sees the parent has finished, which then marks it canceled instead of restarting it. Marking a nested run canceled is a compare-and-set taken behind the run's ownership lease. A run another process is driving, or one that finished or suspended since the sweep listed it, keeps its own status and is left as it is, so a lease that only expires later delays the cancellation to the next sweep rather than guaranteeing it in the current one. - Fixed a nested workflow run's stored snapshot dropping its parent workflow while the run executed a step inside `.parallel()` or `.branch()`. Reading the run at that point, or restarting it after a crash there, now keeps the link to its parent. ([#29308](https://github.com/mastra-ai/mastra/pull/29308)) - Fixed state changes being lost when a nested workflow ran at the same time as other steps, in `.parallel()` or in `.foreach()` with a `concurrency` above 1. When the nested workflow finished or resumed, it replaced the workflow state with its own copy, dropping changes the other steps had made in the meantime. Now only the nested workflow's own changes are applied, so every step's changes are kept. ([#29308](https://github.com/mastra-ai/mastra/pull/29308)) - The `submit_plan` tool no longer names `.mastracode/plans/` in its path example, so agents follow the plans directory their instructions give instead of a hardcoded one. ([#29132](https://github.com/mastra-ai/mastra/pull/29132)) - The workspace `read_file` tool now reads NDJSON and JSONL files as paginated text when the filesystem reports an NDJSON MIME type (such as `application/x-ndjson`) or adds a UTF-8 `charset` parameter, instead of refusing them as binary. Files whose MIME type declares a non-UTF-8 charset still return file metadata rather than garbled text. ([#28796](https://github.com/mastra-ai/mastra/pull/28796)) - Tool validation failures no longer write submitted values to logs. Logs now record the tool and the failing field paths, such as `issuePaths: ["email"]`. Errors returned to the model or MCP caller are unchanged, so it can still correct its call. Fixes #29318. ([#29331](https://github.com/mastra-ai/mastra/pull/29331)) `@mastra/core/tools` now exports `getValidationIssuePaths`, which returns the failing field paths of a validation error and masks dynamic keys such as record keys as `*`. `@mastra/mcp` now requires `@mastra/core` 1.82.0 or later. - Fixed restarting a parent workflow reverting its state to a nested workflow run's copy. When a nested run finished before its parent saved the result, the restart merged the nested run's whole state snapshot over the parent's current state. The parent now records the state the nested run had handed back when it parks on it, and the re-drive merges only the nested run's own changes, so a key another step changed while the nested run was in flight keeps its newer value instead of reverting to the nested run's copy. ([#29332](https://github.com/mastra-ai/mastra/pull/29332)) The recorded state is internal: it is saved with the run but kept out of the step results that `start()`, `resume()`, and a run lookup return. Cancelling a leftover nested run whose parent finished now reports what happened: a run another process is driving, or one whose status moved since the sweep listed it, is left as it is and logged as such instead of reporting a cancel that did not happen. The next sweep decides again. ## 1.82.0 ### Minor Changes - An agent's `scopes` function can now narrow or replace a run's scopes. It receives the run's incoming scopes (from the call, `defaultOptions` and request context) and returns the run's final set. A static `scopes` list is still added to the incoming scopes. The function must keep any `resource:` or `thread:` scope it was given; dropping one throws. ([#28353](https://github.com/mastra-ai/mastra/pull/28353)) ```ts const agent = new Agent({ id: 'support', name: 'Support', instructions: 'Answer support questions.', model, // Keep identity scopes, drop anything else the caller sent, and add the tenant's org. scopes: ({ scopes, requestContext }) => [ ...scopes.filter(scope => typeof scope === 'string' && /^(resource|thread):/.test(scope)), `org:${requestContext.get('orgId')}`, ], }); ``` - Added `requestId`, `serviceName`, and `experimentId` filters to advanced trace queries. Use them at trace scope or inside `spans.some` / `spans.none` with `eq`, `ne`, `in`, `notIn`, `exists`, and `notExists`. ([#29194](https://github.com/mastra-ai/mastra/pull/29194)) ```ts const result = await mastraClient.queryTraces({ timeRange: { from: '2026-11-01T00:00:00.000Z', to: '2026-11-02T00:00:00.000Z' }, where: { op: 'and', args: [ { op: 'eq', left: { path: 'requestId' }, right: { literal: 'req-42' } }, { spans: { some: { op: 'eq', left: { path: 'serviceName' }, right: { literal: 'payments' } } } }, ], }, }); ``` - Added `onDecision` to `ClassifierProcessor` for writing guardrail decisions to your own audit log (#28985). It runs after each classification, including failed classifier calls, and receives the outcome (`allowed`, `filtered` or `aborted`), the answers or the error, the call duration, and the ids of the message, thread, resource or run. ([#29070](https://github.com/mastra-ai/mastra/pull/29070)) ```ts new ClassifierProcessor({ classifier: safety, onResult, onDecision: async decision => { await auditLog.insert({ threadId: decision.ids.threadId, outcome: decision.outcome, answers: decision.result ? decision.result.answers : null, error: decision.result ? null : String(decision.error), }); }, }); ``` `abort` in `onResult` now accepts tripwire metadata, so the caller can tell which policy blocked the request: ```ts onResult: (answers, { abort }) => { if ((answers.unsafe.probability ?? 1) > 0.8) { abort('Blocked', { metadata: { policy: 'unsafe' } }); } }; ``` A failed classifier call under `errorStrategy: 'strict'` now sets tripwire metadata `{ processorId, classifierId, code: 'CLASSIFIER_FAILED' }`. On agents that aren't durable, stopping a streaming reply now cancels a classifier call that is still running. - Fixed evented durable agents stopping when the API process shuts down. Workers now keep running the agent, finish cleanup, and free the thread, so the next message starts right away. ([#28951](https://github.com/mastra-ai/mastra/pull/28951)) Each run keeps its own ownership. Nested durable agents and runs that share a `RequestContext` no longer pick up each other's ownership. Canceling a suspended run still notifies listeners, and a late cancel can no longer stop a newer run of the same agent. This needs storage that supports run fencing. Other shared setups keep their current behavior and log a warning once per agent: API shutdown can still stop remote workers. Single-process setups are unchanged. To enable worker continuity across API loss, use a shared fencing-capable adapter, such as `PostgresStore` from `@mastra/pg`, and a shared pubsub transport: ```ts new Mastra({ storage: new PostgresStore({ id: 'shared-storage', connectionString }), pubsub, agents: { agent: createEventedAgent({ agent, pubsub }) }, }); ``` - Added `getStepResultAsync` to workflow step, condition, sleep, and `.map()` contexts, and deprecated `getStepResult`. ([#26999](https://github.com/mastra-ai/mastra/pull/26999)) `getStepResultAsync` returns the same value as `getStepResult`, wrapped in a promise. Switching now prepares workflows for upcoming storage changes that load step results on demand instead of keeping every result in memory. `getStepResult` keeps working and will be removed in a future major release. ```ts // Before const step2 = createStep({ execute: async ({ getStepResult }) => { const step1Result = getStepResult(step1); }, }); // After const step2 = createStep({ execute: async ({ getStepResultAsync }) => { const step1Result = await getStepResultAsync(step1); }, }); ``` If your tests build step execute params by hand, add a `getStepResultAsync` function to them. - Changes to an experimental, upcoming memory feature. ([#28353](https://github.com/mastra-ai/mastra/pull/28353)) `experimental_subconscious` and the Memory `knowledge` option are removed: setting either now throws, saying to attach Knowledge to the agent instead (`knowledge: false` is still accepted). `Subconscious` and `KnowledgeSemanticIndexCoordinator` stay exported from `@mastra/memory` so older code still loads, and throw when constructed. `@mastra/core/knowledge` exports `Knowledge` with its option and result types and the Knowledge storage domain; the tool, pin and semantic-index helpers are internal. Agent `scopes` are plain `":"` strings that say who the run is; what the run may read or write comes from whoever consumes them, such as Knowledge's scope declarations. A message can carry its own scopes: `session.sendMessage({ content, scopes })`. A run's scopes are fixed when it starts, so a message whose scopes differ from the running run's waits for it to end, then starts its own run in the same conversation. `sendSignal` reports this as `{ action: 'blocked', reason: 'scope-mismatch', runId }`. `queueMessage`, `queueSignal`, `sendNotificationSignal` and `steer` take `scopes` the same way: a queued or steering message, or a notification, whose scopes differ from the running run's starts its own run with its scopes after that run instead of joining it. A deferred notification keeps the scopes it was sent with. Scopes are set in process only: remote callers (the agent controller HTTP routes and `@mastra/client-js`) can't send them. To derive scopes from a request, give the agent a `scopes` function that reads trusted request context. When the thread's claimed owner runs in another process, a message with scopes isn't queued: `sendMessage` rejects with an error saying scopes don't cross processes. Messages without scopes still join that owner's run. Attach Knowledge with `new Agent({ knowledge, scopes })`. Knowledge needs `scopes`, the scope types its runs may hold and who gets which role on them: `new Knowledge({ storage, scopes: { ...knowledgeMemoryScopes() } })` declares Memory's `thread:`/`resource:` scopes and an `org:` scope; `{}` declares none. A run holding a scope that isn't declared fails. Tools are off by default: `tools: true` gives the read and write tools, `tools: { read: true }` the read tools only, and `pinnedContext: true` (or `{ maxPins, maxTokens }`) delivers pinned pages every turn and adds `knowledge_pin`. `knowledge.readScopeFor(principals)` returns what holders of those scopes can read, for host code reading the store directly. `knowledge.explainAccess(runScopes, { scope } | { page })` says why a run can or can't reach a scope or page; it's host-only, so never expose it through an agent tool or an unauthenticated route. `knowledge.onChange(listener)` reports each committed write made through `knowledge.getStorage()` (tools included) by id, for live UIs; re-read each page under the viewer's read scope before showing it. A scope declaration can carry a `displayLabel` for UIs, and `knowledge.scopeDeclaration(key)` returns a declaration's display fields. Knowledge is a tree of pages. The tools are five: `knowledge_search`, `knowledge_read` (a page with its notes, or a scope's pages), `knowledge_page` (`op: 'write' | 'move' | 'merge'`), `knowledge_note` (`op: 'add' | 'restamp' | 'remove'`) and `knowledge_pin` (`op: 'pin' | 'unpin'`). Notes are at most about 250 tokens and page descriptions about 100; long text goes in a page body. Search is keyword search by default. Give Knowledge a `vector` store and an `embedder` and `knowledge_search` blends keyword and semantic results; pages and notes written through the tools are embedded as they're written. Every semantic hit is re-checked against what the run can read before it's ranked, so hidden content never fills result slots. Content written straight to the store isn't embedded. - Added `knowledge_list`, a Knowledge read tool that lists the page tree under a starting point, so an agent can see how a scope or section is organized before searching. Each entry has the ref `knowledge_read` accepts, its name and child count; pass `descriptions: true` to add page descriptions. It goes up to 3 levels deep, takes a `limit` and a `cursor` for wide levels, and says when deeper levels were cut off. A result also stops at the same token cap as a `knowledge_read` body; `more` says how many entries were left out and gives the `cursor` that continues exactly where it stopped, at any depth. Scopes the run can't read are left out. `tools: { read: true }` includes it. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) ```ts // Agent tool call knowledge_list({ from: 'docs:acme#guides', depth: 2 }); // → { from: { ref: 'docs:acme#guides', name: 'Guides' }, // entries: [{ ref: 'docs:acme#guides/observability', name: 'Observability', depth: 1, children: 2 }, …] } ``` Knowledge storage adapters gained `countChildPages({ parentIds, read })`, which counts the readable child pages under each parent in one query. - Agents can pin a listing of the Knowledge page tree with `knowledge_pin`. Pass `kind: 'list'` and an optional `depth` (1 to 3, default 1) to pin the pages under a page instead of the page itself: ([#29263](https://github.com/mastra-ai/mastra/pull/29263)) ```json { "op": "pin", "kind": "list", "depth": 2, "page": "docs:acme#guides", "audience": ["org:acme"] } ``` A page can hold a page pin and a listing pin at once; each is pinned and unpinned on its own, and pinning a listing again sets its new depth. Stored listings follow the same rules as config listings: only pages the run can read, one pin slot, shared `pinnedContext.maxTokens`. A stored listing that repeats a config listing is shown once. Knowledge storage adapters store a pin `kind` (`page` or `list`) and a list pin's `depth`. Adapters report this with the `supportsPinKinds` capability. With a storage adapter from before this release, page pins keep working and pinning a listing is refused before anything is stored, with a message to upgrade the adapter. - Pin a listing of the Knowledge page tree into an agent's context. A scope's `pinnedContext` now also takes `{ list, depth? }` entries, so every run holding the scope sees the pages under a scope or page (ref, name and child count each) without calling `knowledge_list`: ([#29263](https://github.com/mastra-ai/mastra/pull/29263)) ```ts scopes: { 'org:$orgId': { pinnedContext: [{ list: 'docs:$orgId#guides', depth: 1 }] }, } ``` A listing shows only pages the run can read, updates when a listed page is added, renamed or removed, takes one pin slot and shares `pinnedContext.maxTokens` with pinned pages. A listing cut short ends with a `knowledge_list` pointer to the rest. - `knowledge_read` reads long pages by line. A body that fits in one read (1,000 tokens) comes back whole as `body`. A longer one comes back as numbered `lines` (`12│text`) with the `range` they cover and the page's `lineCount`, ending at the last whole line that fits, and `more` says where to read on with `from: { line }`. A line too long for one read, such as a wide table row, is cut by column (`from: { line, col }`). An agent can also ask for the body's `outline` (headings with their line numbers and sizes), read one `section`, or `match` one or more terms like grep: only the lines that contain them, with `context` lines before and after (default 2). ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) ```ts knowledge_read({ page: 'docs:acme#models/anthropic', outline: true }); knowledge_read({ page: 'docs:acme#models/anthropic', section: 'contextManagement' }); knowledge_read({ page: 'docs:acme#models/anthropic', match: ['excludeTools', 'cacheControl'], context: 3 }); knowledge_read({ page: 'docs:acme#models/anthropic', from: { line: 120 } }); ``` Every Knowledge read result now lists anything it cut short in `more`, with how to get the rest: the body, notes, child pages (continue with `knowledge_list`), notes that mention the page (new `mentionedInCursor`), and search results at their limit. A whole-page read keeps a page's notes, and the notes on other pages that mention it, only while each list stays under 1,000 tokens. A longer list is left out and counted in `more`; read it on its own with `notes: true` or `mentionedIn: true`. A read with `cursor` or `mentionedInCursor` now returns only those notes, without repeating the body. Every part of a body a read returns comes with its `range`. `knowledge_page` takes a `range` with `expectedVersion` and replaces only those lines with `body`; an empty `body` removes them. A range may be the one a read returned or a smaller one inside it, but not wider than one read from its first line shows, so text the writer never saw can't be overwritten. A body write without a range is refused when the stored body is longer than one read returns, and so is a body that still carries the read's line numbers. ```ts const { page } = await knowledge_read({ page: 'docs:acme#models/anthropic', section: 'Setup' }); knowledge_page({ op: 'write', page: page.ref, body: editedSetup, range: page.range, expectedVersion: page.version }); ``` The per-result cap is configurable: `tools: { read: { maxTokens: 3000 }, write: true }` sets how many estimated tokens one `knowledge_read` body window or match result, and one `knowledge_list` result, returns (default 1,000, at least 100). The write guard for line ranges follows the same cap. ```ts const knowledge = new Knowledge({ scopes, tools: { read: { maxTokens: 3000 }, write: true } }); ``` - Added vector and hybrid workspace search for vector stores that generate embeddings themselves, so no `embedder` is needed. ([#27022](https://github.com/mastra-ai/mastra/pull/27022)) With MongoDB Automated Embedding, MongoDB embeds each file when it is indexed and the search string when you query: ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), vectorStore: new MongoDBVector({ id: 'search', uri, dbName, autoEmbed: { model: 'voyage-4' } }), }); await workspace.rebuildSearchIndex(['docs']); await workspace.search('how do I reset a password?'); ``` The index is named `_search_selfembed` unless you set `searchIndexName`. Workspaces that pass an `embedder` keep working unchanged. - Added opt-in Editor ownership and versioned persistence for static model fallback lists. ([#27287](https://github.com/mastra-ai/mastra/pull/27287)) ```ts new Agent({ id: 'support-agent', name: 'Support agent', instructions: 'Help users resolve support requests.', model: [ { id: 'primary', model: 'openai/gpt-4o' }, { id: 'fallback', model: 'openai/gpt-4o-mini' }, ], editor: { model: true }, }); ``` - Added time to first token and time to first chunk measures to trace and span aggregates, so dashboards can chart streaming latency next to duration. ([#29055](https://github.com/mastra-ai/mastra/pull/29055)) The new measures are `timeToFirstToken.avg`, `.min`, `.max`, `.p50`, `.p90`, `.p95`, `.p99`, and `.count`, plus the same set for `timeToFirstChunk`. Traces and spans without a recorded value are skipped instead of counted as 0, and `.count` reports how many had one. Stores opt in through the `aggregate-time-to-first-measures` feature. - Suspended background tool calls can now be answered after a reload. ([#28998](https://github.com/mastra-ai/mastra/pull/28998)) **Open request saved on the tool call.** While a deferred background tool is suspended, the assistant message that holds the call keeps the open request in `metadata.suspendedTools[toolCallId]` (with `suspendPayload`, `taskId`, and `suspendedAt`). The entry is removed when the task resumes or is cancelled, so a chat that reloads the thread shows only requests that are still open. **Resumed tools see what they asked for.** A resumed background tool now receives the payload it suspended with in `context.agent.suspendPayload`, the same as a foreground tool: ```typescript execute: async (input, context) => { const { resumeData, suspendPayload, suspend } = context.agent; if (!resumeData) return suspend?.({ question: 'Approve the refund?' }); // suspendPayload is { question: 'Approve the refund?' } }; ``` **Resume only the suspension you read.** `BackgroundTaskManager.resume()` takes an optional `suspendedAt`, and refuses the resume when the task has resumed or suspended again since: ```typescript await mastra.backgroundTaskManager.resume(taskId, { approved: true }, { suspendedAt: task.suspendedAt }); ``` **The agent reports a deferred task's result after its stream ends.** When a deferred background task finishes and no `untilIdle` stream is open for its thread, the agent now gets a `background-task` notification signal on the thread. An idle thread wakes and the agent reports the outcome; a run already active on the thread gets the signal on its next model request. Before, the result was only written to memory and waited for the next user message. This covers `stream()` calls without `untilIdle` and tasks resumed after they suspended, for example an MCP task answered from the chat. Streams using `untilIdle` are unchanged: they still run their own follow-up turn, and no signal is sent. Cancelling or aborting an `untilIdle` stream still means no reply for the tasks it was waiting for. **Opt in to ending woken runs after their turn.** A run that a signal starts on an idle thread still stays open until the background tasks it dispatched finish. Set the temporary `wokenRunsEndAfterTurn` option to end it after its own turn instead; each task then wakes the thread when it finishes. A signal's own `ifIdle.streamOptions.untilIdle` takes precedence either way. ```typescript new Mastra({ backgroundTasks: { enabled: true, wokenRunsEndAfterTurn: true } }); ``` In the next major release, ending after the turn becomes the only behavior and `wokenRunsEndAfterTurn` is removed. Pass `untilIdle: true` in `ifIdle.streamOptions` to keep a woken run open; channels and agent controller sessions already do. **Turn off the completion signal.** Set `notifyAgentOnFinish: false` in the `backgroundTasks` config when your app sends its own completion signal, for example from `onTaskComplete`. With `wokenRunsEndAfterTurn` also on, your app must report the tasks of woken runs itself. - Added `knowledge.upsertPage()`, `knowledge.removePage()`, `knowledge.addNote()`, `knowledge.updateNote()` and `knowledge.removeNote()` so apps can sync outside content, such as docs, into Knowledge from server code. A page is identified by its scope and key path, so running a sync again updates the same pages and skips unchanged ones. Notes on synced pages are kept, pages are embedded when Knowledge has a vector store, and change listeners are told. ([#29159](https://github.com/mastra-ai/mastra/pull/29159)) ```ts await knowledge.upsertPage({ scope: 'docs:acme', path: 'guides/install', name: 'Install', body: markdown }); await knowledge.removePage({ scope: 'docs:acme', path: 'guides/old' }); const note = await knowledge.addNote({ scope: 'docs:acme', path: 'guides/install', text: 'Needs Node 20.' }); await knowledge.updateNote({ id: note.id, text: 'Needs Node 22.' }); // returns a note with a new id ``` - Fixed a recovered thread run losing its thread lease when the execution it replaced finished. Previously, when a run was recovered in another process, the original execution's exit released the shared lease while the recovered execution was still running, so the thread looked free and a new run could start on it. Each execution now holds the lease under its own generation, so only the live execution can renew or release it. ([#28947](https://github.com/mastra-ai/mastra/pull/28947)) Also fixed run IDs that contain the internal handover marker, or look like an encoded lease owner, being misread as a different run. Any caller-provided run ID now round-trips unchanged. **Upgrading shared deployments** Drain active thread runs and restart API and worker processes together. Older processes do not recognize the new lease owner format: they cannot see a run as live, even while it is renewing its lease. Follow-ups that an older process forwards to a run on this release are still delivered. ### Patch Changes - dependencies updates: ([#29242](https://github.com/mastra-ai/mastra/pull/29242)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.63` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.63) (from `npm:@ai-sdk/provider-utils@4.0.62`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.72` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.72) (from `npm:@ai-sdk/provider-utils@5.0.69`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.35` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.35) (from `npm:@ai-sdk/provider@4.0.34`, in `dependencies`) - Moved the agent stream layer (stream topics, event publishing, replay cache setup and abort requests) out of `DurableAgent` into a shared internal module. No behavior change; existing `@mastra/core/agent/durable` exports are unchanged. ([#28416](https://github.com/mastra-ai/mastra/pull/28416)) - Fixed resuming suspended agent runs across server instances when a process retains an earlier run segment. ([#29065](https://github.com/mastra-ai/mastra/pull/29065)) - Update provider registry and model documentation with latest models and providers ([`4d3ebbd`](https://github.com/mastra-ai/mastra/commit/4d3ebbd304509acb118f319e8467fc932079e715)) - Fixed thread aborts for durable agents on multi-server deployments. Aborting a thread on a server that didn't start the run now stops the run on whichever server executes it, as aborting by run id already did. A run parked on a suspension, such as a tool waiting for approval, is released too. ([#28345](https://github.com/mastra-ai/mastra/pull/28345)) ```ts agent.abortThreadStream({ resourceId, threadId }); // or POST /agents/:agentId/threads/abort ``` The server finds the run through the thread lease, so this needs a pubsub with a shared lease backend such as Redis or Valkey. **Return values** - The HTTP route returns `{ aborted: true }` once the abort request is published, and an error if the lease backend can't be reached or doesn't answer in time. - The synchronous `abortThreadStream` still returns `false` for a run on another server, because it can't wait for the lookup. - An `expectedRunId` that doesn't match the running run leaves it alone and returns `false`. **Queued input** With `clearPendingSignals: true`, the server running the run clears its queued input before stopping it, as long as that run still holds the thread. Input queued for a run that has since taken over the thread is kept. - Updated `chat` and the Chat SDK adapters to ^4.44.0, so all Mastra channel packages resolve the same `chat` version. ([#29285](https://github.com/mastra-ai/mastra/pull/29285)) - Fixed "Tenant model credential policy could not be resolved: the request has no authenticated user" errors that recurred on every message in a session with a pending tool approval. When a new user message declines a pending approval, the decline now runs with the sender's request context instead of the reattached run's empty one. ([#29231](https://github.com/mastra-ai/mastra/pull/29231)) - Fixed durable agents (`durable: true`) ending the `model_generation` trace span without `responseId` and `responseModel`. Durable runs now record the provider's response id and model on the span, the same as non-durable agents, so a trace can be linked back to the provider's own response log. Fixes #29053. ([#29180](https://github.com/mastra-ai/mastra/pull/29180)) - Improved `validateStepRequestContext` typing. It now accepts any object with `id` and `requestContextSchema`, not only a full `Step`. ([#29080](https://github.com/mastra-ai/mastra/pull/29080)) - Fixed skills not loading on Windows when the workspace `basePath` uses a short folder name such as `C:\Users\ABCDEF~1\...`, which `os.tmpdir()` can return. Optional skill folders that don't exist, like `references`, no longer fail with `Permission denied`. Fixes [#28957](https://github.com/mastra-ai/mastra/issues/28957). ([#29078](https://github.com/mastra-ai/mastra/pull/29078)) - Fixed a signal sent to a thread while an `agent.generate()` turn was running starting a second run that could not see that turn. `generate()` now registers its run with the thread like `stream()` does, so `sendSignal`, `sendMessage` and notifications with the default `ifActive: 'deliver'` join the running turn. ([#28953](https://github.com/mastra-ai/mastra/pull/28953)) As with `stream()`, a thread-bound `generate()` now waits for a run already going on the same thread, so concurrent `generate()` calls on one thread run one after another instead of in parallel. A tool that calls `generate()` or `stream()` on its own thread waits for the run it is part of, so it deadlocks; a tool should use `sendSignal` to send a message to its own thread. - Fixed metadata filters in the in-memory store returning records whose nested metadata had different keys from the filter when one side contained an `undefined` value. ([#29101](https://github.com/mastra-ai/mastra/pull/29101)) - Improved how Knowledge reads fenced code and headings in page bodies, to match CommonMark: ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) - Fenced code ends only on a fence of the same character, at least as long, with nothing after it. A ` ` `block can show` ```js ```` lines without breaking a page's sections, outline or embedding chunks. A fence indented four or more spaces is indented code and doesn't open or close a block. - A heading keeps a `#` that's part of its title: `## Use C#` is the section "Use C#". - Reading the last part of a page whose body ends in a newline returns a range that `knowledge_page` accepts back for an edit. Embedding a long page now runs up to 4 chunk embeddings at once (one at a time when the embedding model doesn't support parallel calls), and one semantic search looks at no more than 1,000 distinct pages and notes. - Knowledge notes returned by `knowledge_note`, `knowledge_read` and `knowledge_search` now carry `mentions`, mapping each `[[ref]]` in the note to the page id it resolves to, so a chat can link them. Refs are resolved the way the note's write resolved them; a name that no longer resolves is left out. ([#28942](https://github.com/mastra-ai/mastra/pull/28942)) ```json { "id": "n1", "text": "Escalate per [[Escalation Process]].", "mentions": { "Escalation Process": "page-esc" } } ``` - `knowledge_list` now asks the store for only the child scopes the agent can read, and counts them in one call per level, instead of reading every child scope and dropping the unreadable ones. Listing under a scope with many private child scopes no longer loads all of them. ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) - Knowledge pages too long for the embedder are now searchable by meaning. A long page is embedded as overlapping chunks of about 2,000 tokens, cut between paragraphs and before headings, and semantic search still returns the page once, ranked by its best chunk. Before, the embedder refused these pages and they could only be found by keyword. Rewriting a page replaces its chunks, and deleting it removes them all. Pages embedded before this change keep working; a page that was too long is chunked the next time it changes. ([#29218](https://github.com/mastra-ai/mastra/pull/29218)) A page is embedded as at most 100 chunks (about 200,000 tokens of body); past that, the rest of the body is found by keyword only. A page's name and description lead every chunk, up to 500 tokens. One semantic search looks at up to 1,000 distinct pages and notes (up to 10,000 vectors). A rewrite drops the page's old chunks by id after the new ones are stored; on a vector store that can't delete by id, old chunks stay but only ever point back to the same page. - Knowledge page and note writes now retry a failed embed or vector write a few times over about 10 seconds before giving up. That gives a cold scale-to-zero database (Neon, Lakebase) time to wake, so its pages aren't silently left out of semantic search. If every try fails, the write still succeeds and the warning names the page, for example `Knowledge couldn't embed page "docs:acme#guides/cli" after retrying; it is findable by keyword search only until it is rewritten`. Errors the embedding provider marks as not retryable, such as an input over its limit, are not retried. ([#29288](https://github.com/mastra-ai/mastra/pull/29288)) - Added read-only page-store readers to `@mastra/core/knowledge` for browsing UIs: `listKnowledgeTreeChildren` (one level of the page tree, child scopes first), `readKnowledgePage` (a page with its readable ancestors, notes, pin, referenced pages and notes that mention it), `readKnowledgePageGraph` (a bounded page graph with parent and mention edges, limits in `DEFAULT_KNOWLEDGE_PAGE_GRAPH_LIMITS`; mentions come from every note on a walked page until a graph-wide `maxNotes` budget runs out, which sets `truncated`), `searchKnowledgeExplorerPages` and `readKnowledgePageActivity`. Each takes a read scope (`'all'` for an owner view) and passes it to every store call, so pages and notes outside it never appear, mention edges included. They never write. Added `readKnowledgeScopeGrants`, which lists the declared grants onto the scope pages in a graph (principal scope, its page id when it has one, target and role), keeping only principals the read scope can read, and `knowledge.scopeGrants(scope)`, which gives each principal the declared rules name on one scope with its strongest role. ([#28362](https://github.com/mastra-ai/mastra/pull/28362)) - Knowledge tool results now name each page by the ref `knowledge_read` accepts. Search, read and page results carry `ref`: the scope address for a scope page, the full key path for a nested page (`docs:acme#guides/observability/logging`), or the page id when a page on the path has no key. The old `key` field held only the last path segment, so agents that copied it got "not found" for nested pages; it is gone. A read that misses now lists up to three close matches by ref, and an ambiguous page name lists its candidates by ref. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) - Fixed pinned Knowledge listings stopping at 100 pages per level. A listing now reads every child of a page while its token budget has room, and only ends with "…and more" when the budget runs out. ([#29298](https://github.com/mastra-ai/mastra/pull/29298)) - `GET /knowledge/:knowledgeKey/pages/:pageId` also opens a page by the reference tools name it with: a scope address (`site:mastra`) or `scope#key/path` (`site:mastra#topics/observability`). It reads only what the caller can read, and an unreadable or unknown reference still returns 404. ([#29215](https://github.com/mastra-ai/mastra/pull/29215)) - Sped up `readKnowledgePageGraph`, which loads the Studio Knowledge graph, on large page trees. It now reads the tree a level at a time: one child-page count for the whole level, a listing only for pages that have children, one note count across the walked pages, and one lookup for linked pages outside the walk. A 300-page tree went from 1,221 sequential storage calls to 37, which matters most on remote databases such as Turso. The returned graph is unchanged. ([#29288](https://github.com/mastra-ai/mastra/pull/29288)) - Added an index of the `[[links]]` in Knowledge page bodies, so a host can count and list which pages link where without loading every page. `countPageLinks` and `listPageLinks` on the Knowledge store return in and out links for a set of pages, gated by the reader's scopes like every other read. Note `[[mentions]]` show up there as `note` links. ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) **How links resolve** - Links resolve when a page is written through the Knowledge tools, `upsertPage` or `removePage`. - A link matches by page id, `scope#key/key` path, scope address or page name: the page's own scope first, then the whole store. - A link to a page that doesn't exist yet resolves once that page is created. It follows renames and moves, and goes back to unresolved when its target is deleted or merged away. - One write re-resolves at most 100 pages that link to what it changed. Past that it logs a warning, and the rest catch up when they are next written or when `knowledge.reindexLinks()` runs. Pages written before this release, or straight to the store, aren't indexed until they are written again. Run `knowledge.reindexLinks()` once to index them all; it needs no vector store. ```ts await knowledge.reindexLinks(); const store = await mastra.getStorage()!.getStore('knowledge'); const counts = await store!.countPageLinks({ pageIds: [page.id], read: 'all' }); // { [page.id]: { in: 3, out: 1 } } ``` - Added `readKnowledgePageLinkCounts` and `readKnowledgePageLinks` to `@mastra/core/knowledge`: exact link counts for any set of pages and one page's links with the pages at their other ends, read from the store's link index and filtered by the caller's read scope. Both return `null` when the store has no link index. ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) ```ts import { readKnowledgePageLinkCounts, readKnowledgePageLinks } from '@mastra/core/knowledge'; const counts = await readKnowledgePageLinkCounts(store, { pageIds: [pageId], read }); if (counts === null) { // this store has no link index } const links = await readKnowledgePageLinks(store, { pageId, read, direction: 'out' }); ``` - Knowledge page paths ignore leading, trailing and doubled slashes, so `docs:acme#models/gateways/` names the same page as `docs:acme#models/gateways`. This holds for `upsertPage`, `removePage`, the note methods, `[[links]]` in notes, page refs given to the Knowledge tools, and `pinnedContext` refs. Previously `upsertPage` refused such paths and reads reported the page as not found. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) - `pinnedContext.maxTokens` no longer has a 2,000-token ceiling. It budgets pinned pages only, so set it below your model's context limit to leave room for instructions, conversation history and model output, for example `pinnedContext: { maxTokens: 8000 }`. The default is still 500. ([#29257](https://github.com/mastra-ai/mastra/pull/29257)) - Fill pinned Knowledge listings level by level within their token budget. A listing with `depth` 2 or 3 now shows every child that fits first, then grandchildren, and so on, instead of stopping at a fixed 50 entries in sibling order. Each entry still prints under its parent, and a listing that is cut still ends with `…and more; knowledge_list from: ""`. ([#29263](https://github.com/mastra-ai/mastra/pull/29263)) - The `knowledge_read` tool now tells the model that a read from a line runs until the page ends or the read's token cap is reached, and that `limit` caps notes, children and mentioning notes, not lines. Models had been passing `limit` expecting a set number of lines back. ([#29258](https://github.com/mastra-ai/mastra/pull/29258)) - Removed `knowledge.reindex()`. It re-embedded every page and note in the store, which is slow and costly on a large store. Pages and notes are embedded when they're written through the Knowledge tools, `upsertPage` or the note methods. If an embed fails, the write still succeeds and the content stays findable by keyword search; it's embedded again the next time it changes. `upsertPage` skips writes whose content is unchanged, so sending identical content again doesn't retry a failed embed; the Knowledge tools and `updateNote` embed on every write. ([#29256](https://github.com/mastra-ai/mastra/pull/29256)) If you called `reindex()` after loading content, delete the call and load the content through `upsertPage` instead, which embeds each new or changed page as it's written. There is no bulk re-embed for content that's already stored: ```ts // Before await knowledge.reindex(); // After await knowledge.upsertPage({ scope: 'org:acme', path: 'refunds', name: 'Refund policy', body }); ``` - Fixed read-only Knowledge runs filling storage with empty scopes. A run that only searches, lists or reads Knowledge, and pinned-context delivery each turn, no longer creates the run's `resource:`, `thread:` or other held scopes. A held scope is created, with its template pages and its `belongsTo` placement, the first time a run writes Knowledge (`knowledge_page`, `knowledge_note`, `knowledge_pin`). ([#29288](https://github.com/mastra-ai/mastra/pull/29288)) Before, a Slack agent that only answered questions with `knowledge_search` created an empty `resource:slack:` and `thread:` scope for every user and conversation, which filled the Studio tree. A scope with no pages yet reads as not found until the first write creates it. - `knowledge_search` now finds pages that match any word of the query, not only pages that match every word. Pages with more of the query's words rank higher, so a long question still finds the page that holds most of its words. A page whose name contains the whole query still comes first. When several pages match the same number of words, a word in the name counts more than one in the description, and one in the description more than one in the body. A search uses the first 16 distinct words of the query; repeated words count once. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) - `knowledge_search` handles queries agents commonly write. Quoted words (`"api" "responses"`) are searched as plain words; before, the quote marks were matched literally and nothing was found. A search that finds nothing now says so and suggests one to three key words or `knowledge_list`. Passing a page ref such as `docs:acme#models` as `scope` now explains that `scope` takes scope addresses, instead of reporting the page as not found. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) A first `knowledge_read` of a long page that stops early now also returns the `outline` of the headings it did not reach (up to about 500 tokens of them, with their line numbers and sizes), so an agent can read the section it needs next instead of paging toward it. Search results at their limit no longer tell the agent to search again; the default Knowledge instructions now say to follow `more` only when what the agent needs is not already in what it has read, and to answer once it has it. The first `knowledge_read` of a page too long to return whole now shows the whole page at a glance: every heading with its line number and the first lines under it, with `⋯ lines a–b` where lines are left out, within the read cap. Read the part you need with `section` or `from`. A page with more headings than fit falls back to its top lines and the headings below them. - Knowledge tree child counts now say when they were capped. If a scope has more child scopes than the 1,000 the tree looks through for each level, the page gets `childCountCapped: true` and its `childCount` is a lower bound. The Knowledge explorer then shows the count as "1000+" in the tree and in the graph's "pages under" line, instead of a number that's too low. ([#29216](https://github.com/mastra-ai/mastra/pull/29216)) - Each page in a Knowledge tree level (`GET /api/knowledge/:knowledgeKey/tree`, `listKnowledgeTreeChildren`) now carries `childCount`: how many readable pages, and for a scope how many readable child scopes, sit directly under it. A browsing UI can show the count and offer to open only pages that have children. The count is left out when the storage adapter can't count children yet. ([#29216](https://github.com/mastra-ai/mastra/pull/29216)) ```ts const level = await client.getKnowledge('main').tree({ parentId: scopeId }); level.pages.map(page => `${page.name} (${page.childCount ?? '?'})`); ``` Storage adapters gain `countChildPages({ parentIds, read })`, which counts a batch of parents' child pages in one query (in-memory, LibSQL, PostgreSQL, MySQL, MongoDB). - Stored spans now use the service name from the Observability config when the span metadata does not set one. ([#29201](https://github.com/mastra-ai/mastra/pull/29201)) - Fixed `getStepResult` and `getStepResultAsync` returning `null` for the loop body inside `.dowhile()` and `.dountil()` conditions on the evented workflow engine. Loop conditions now see the body's latest result on every iteration, matching the default engine. ([#26999](https://github.com/mastra-ai/mastra/pull/26999)) - Fixed approving a tool loaded by `ToolSearchProcessor` after a server restart. The approved call failed with `Tool "" not found` when memory no longer returned the search result. It now runs with the tools it had when the model made the call. Fixes [#29196](https://github.com/mastra-ai/mastra/issues/29196). ([#29251](https://github.com/mastra-ai/mastra/pull/29251)) - Fixed `run.cancel()` leaving suspended nested workflow runs suspended after the parent was canceled. Nested runs, including foreach iterations, are now canceled with their parent on both the default and evented engines. Calling `cancel()` again on a run that is already canceled also cancels nested runs it left suspended. If storage fails while canceling nested runs, `cancel()` rejects and can be retried. (#24780) ([#29170](https://github.com/mastra-ai/mastra/pull/29170)) - Fixed `Property '#private' is missing` when passing a voice provider such as `new OpenAIRealtimeVoice()` to an agent's `voice` option or to `CompositeVoice` ([#29016](https://github.com/mastra-ai/mastra/issues/29016)). ([#29074](https://github.com/mastra-ai/mastra/pull/29074)) These options now accept the `IMastraVoice` interface, exported from `@mastra/core/voice`. `agent.voice` and `agent.getVoice()` return `IMastraVoice`. If you annotate a variable with a voice type, use `IMastraVoice` instead of `MastraVoice`. ```typescript import { Agent } from '@mastra/core/agent'; import { OpenAIRealtimeVoice } from '@mastra/voice-openai-realtime'; export const voiceAgent = new Agent({ id: 'voice-agent', name: 'Voice Agent', instructions: 'You are a voice assistant.', model: 'openai/gpt-6.1-sol', voice: new OpenAIRealtimeVoice(), // failed with TS2322 before this fix }); ``` - Fixed signal-started runs to use the `ifIdle.streamOptions.runId` you provide. This applies when `sendSignal`, `sendMessage`, `queueSignal` or `queueMessage` wakes an idle thread or queues a run behind the active one. The id appears in `accepted.runId`, `output.runId` and thread events. If the id already names a known run, `accepted` rejects. Calls without an id still get a random one. ([#29152](https://github.com/mastra-ai/mastra/pull/29152)) ```ts const { accepted } = agent.sendSignal( { type: 'user-message', contents: 'hello' }, { resourceId, threadId, ifIdle: { streamOptions: { runId: 'my-run-id' } } }, ); await accepted; // { action: 'wake', runId: 'my-run-id', output } ``` - Fixed progress from tools wrapped with `createStep` from `@mastra/core/workflows/evented` never reaching the workflow stream. The tool now receives the step `writer`, the run `actor` (including the `actor` step option) and `suspendPayload`, the same as with `createStep` from `@mastra/core/workflows`. Fixes #29164. ([#29165](https://github.com/mastra-ai/mastra/pull/29165)) - Fixed controller tool status for validation failures and explicit error results, including delegated tool results. Successful results containing business error fields retain their successful status. ([#28812](https://github.com/mastra-ai/mastra/pull/28812)) - Fixed directly resumed nested workflows in the evented engine so they continue suspended parent runs. ([#25979](https://github.com/mastra-ai/mastra/pull/25979)) - Fixed type declarations for bundled AI SDK packages. When `@mastra/core` bundled two versions of the same AI SDK package, one version's types overwrote the other's, so types such as `LanguageModelV3` used by the bundled OpenAI, Google and Anthropic providers resolved to `any`. Each version now keeps its own copy, so these types are checked again. Fixes [#28732](https://github.com/mastra-ai/mastra/issues/28732). ([#29171](https://github.com/mastra-ai/mastra/pull/29171)) - Fixed retained workflow handles and abort listeners after durable agent runs settle (#29009) ([#29065](https://github.com/mastra-ai/mastra/pull/29065)) - Fixed dataset experiments that target a workflow so their traces carry the experiment ID. Workflow runs and every auto-resume now include `experimentId` in tracing metadata, so Studio's experiment page shows total cost, tokens and run duration for workflow experiments, the same as it does for agent experiments. ([#29245](https://github.com/mastra-ai/mastra/pull/29245)) ## 1.82.0-alpha.3 ### Minor Changes - Fixed evented durable agents stopping when the API process shuts down. Workers now keep running the agent, finish cleanup, and free the thread, so the next message starts right away. ([#28951](https://github.com/mastra-ai/mastra/pull/28951)) Each run keeps its own ownership. Nested durable agents and runs that share a `RequestContext` no longer pick up each other's ownership. Canceling a suspended run still notifies listeners, and a late cancel can no longer stop a newer run of the same agent. This needs storage that supports run fencing. Other shared setups keep their current behavior and log a warning once per agent: API shutdown can still stop remote workers. Single-process setups are unchanged. To enable worker continuity across API loss, use a shared fencing-capable adapter, such as `PostgresStore` from `@mastra/pg`, and a shared pubsub transport: ```ts new Mastra({ storage: new PostgresStore({ id: 'shared-storage', connectionString }), pubsub, agents: { agent: createEventedAgent({ agent, pubsub }) }, }); ``` - Agents can pin a listing of the Knowledge page tree with `knowledge_pin`. Pass `kind: 'list'` and an optional `depth` (1 to 3, default 1) to pin the pages under a page instead of the page itself: ([#29263](https://github.com/mastra-ai/mastra/pull/29263)) ```json { "op": "pin", "kind": "list", "depth": 2, "page": "docs:acme#guides", "audience": ["org:acme"] } ``` A page can hold a page pin and a listing pin at once; each is pinned and unpinned on its own, and pinning a listing again sets its new depth. Stored listings follow the same rules as config listings: only pages the run can read, one pin slot, shared `pinnedContext.maxTokens`. A stored listing that repeats a config listing is shown once. Knowledge storage adapters store a pin `kind` (`page` or `list`) and a list pin's `depth`. Adapters report this with the `supportsPinKinds` capability. With a storage adapter from before this release, page pins keep working and pinning a listing is refused before anything is stored, with a message to upgrade the adapter. - Pin a listing of the Knowledge page tree into an agent's context. A scope's `pinnedContext` now also takes `{ list, depth? }` entries, so every run holding the scope sees the pages under a scope or page (ref, name and child count each) without calling `knowledge_list`: ([#29263](https://github.com/mastra-ai/mastra/pull/29263)) ```ts scopes: { 'org:$orgId': { pinnedContext: [{ list: 'docs:$orgId#guides', depth: 1 }] }, } ``` A listing shows only pages the run can read, updates when a listed page is added, renamed or removed, takes one pin slot and shares `pinnedContext.maxTokens` with pinned pages. A listing cut short ends with a `knowledge_list` pointer to the rest. - Added vector and hybrid workspace search for vector stores that generate embeddings themselves, so no `embedder` is needed. ([#27022](https://github.com/mastra-ai/mastra/pull/27022)) With MongoDB Automated Embedding, MongoDB embeds each file when it is indexed and the search string when you query: ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), vectorStore: new MongoDBVector({ id: 'search', uri, dbName, autoEmbed: { model: 'voyage-4' } }), }); await workspace.rebuildSearchIndex(['docs']); await workspace.search('how do I reset a password?'); ``` The index is named `_search_selfembed` unless you set `searchIndexName`. Workspaces that pass an `embedder` keep working unchanged. ### Patch Changes - dependencies updates: ([#29242](https://github.com/mastra-ai/mastra/pull/29242)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.63` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.63) (from `npm:@ai-sdk/provider-utils@4.0.62`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.72` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.72) (from `npm:@ai-sdk/provider-utils@5.0.69`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.35` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.35) (from `npm:@ai-sdk/provider@4.0.34`, in `dependencies`) - Fixed thread aborts for durable agents on multi-server deployments. Aborting a thread on a server that didn't start the run now stops the run on whichever server executes it, as aborting by run id already did. A run parked on a suspension, such as a tool waiting for approval, is released too. ([#28345](https://github.com/mastra-ai/mastra/pull/28345)) ```ts agent.abortThreadStream({ resourceId, threadId }); // or POST /agents/:agentId/threads/abort ``` The server finds the run through the thread lease, so this needs a pubsub with a shared lease backend such as Redis or Valkey. **Return values** - The HTTP route returns `{ aborted: true }` once the abort request is published, and an error if the lease backend can't be reached or doesn't answer in time. - The synchronous `abortThreadStream` still returns `false` for a run on another server, because it can't wait for the lookup. - An `expectedRunId` that doesn't match the running run leaves it alone and returns `false`. **Queued input** With `clearPendingSignals: true`, the server running the run clears its queued input before stopping it, as long as that run still holds the thread. Input queued for a run that has since taken over the thread is kept. - Updated `chat` and the Chat SDK adapters to ^4.44.0, so all Mastra channel packages resolve the same `chat` version. ([#29285](https://github.com/mastra-ai/mastra/pull/29285)) - Improved how Knowledge reads fenced code and headings in page bodies, to match CommonMark: ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) - Fenced code ends only on a fence of the same character, at least as long, with nothing after it. A ` ` `block can show` ```js ```` lines without breaking a page's sections, outline or embedding chunks. A fence indented four or more spaces is indented code and doesn't open or close a block. - A heading keeps a `#` that's part of its title: `## Use C#` is the section "Use C#". - Reading the last part of a page whose body ends in a newline returns a range that `knowledge_page` accepts back for an edit. Embedding a long page now runs up to 4 chunk embeddings at once (one at a time when the embedding model doesn't support parallel calls), and one semantic search looks at no more than 1,000 distinct pages and notes. - `knowledge_list` now asks the store for only the child scopes the agent can read, and counts them in one call per level, instead of reading every child scope and dropping the unreadable ones. Listing under a scope with many private child scopes no longer loads all of them. ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) - Knowledge pages too long for the embedder are now searchable by meaning. A long page is embedded as overlapping chunks of about 2,000 tokens, cut between paragraphs and before headings, and semantic search still returns the page once, ranked by its best chunk. Before, the embedder refused these pages and they could only be found by keyword. Rewriting a page replaces its chunks, and deleting it removes them all. Pages embedded before this change keep working; a page that was too long is chunked the next time it changes. ([#29218](https://github.com/mastra-ai/mastra/pull/29218)) A page is embedded as at most 100 chunks (about 200,000 tokens of body); past that, the rest of the body is found by keyword only. A page's name and description lead every chunk, up to 500 tokens. One semantic search looks at up to 1,000 distinct pages and notes (up to 10,000 vectors). A rewrite drops the page's old chunks by id after the new ones are stored; on a vector store that can't delete by id, old chunks stay but only ever point back to the same page. - Knowledge page and note writes now retry a failed embed or vector write a few times over about 10 seconds before giving up. That gives a cold scale-to-zero database (Neon, Lakebase) time to wake, so its pages aren't silently left out of semantic search. If every try fails, the write still succeeds and the warning names the page, for example `Knowledge couldn't embed page "docs:acme#guides/cli" after retrying; it is findable by keyword search only until it is rewritten`. Errors the embedding provider marks as not retryable, such as an input over its limit, are not retried. ([#29288](https://github.com/mastra-ai/mastra/pull/29288)) - Fixed pinned Knowledge listings stopping at 100 pages per level. A listing now reads every child of a page while its token budget has room, and only ends with "…and more" when the budget runs out. ([#29298](https://github.com/mastra-ai/mastra/pull/29298)) - `GET /knowledge/:knowledgeKey/pages/:pageId` also opens a page by the reference tools name it with: a scope address (`site:mastra`) or `scope#key/path` (`site:mastra#topics/observability`). It reads only what the caller can read, and an unreadable or unknown reference still returns 404. ([#29215](https://github.com/mastra-ai/mastra/pull/29215)) - Sped up `readKnowledgePageGraph`, which loads the Studio Knowledge graph, on large page trees. It now reads the tree a level at a time: one child-page count for the whole level, a listing only for pages that have children, one note count across the walked pages, and one lookup for linked pages outside the walk. A 300-page tree went from 1,221 sequential storage calls to 37, which matters most on remote databases such as Turso. The returned graph is unchanged. ([#29288](https://github.com/mastra-ai/mastra/pull/29288)) - Added an index of the `[[links]]` in Knowledge page bodies, so a host can count and list which pages link where without loading every page. `countPageLinks` and `listPageLinks` on the Knowledge store return in and out links for a set of pages, gated by the reader's scopes like every other read. Note `[[mentions]]` show up there as `note` links. ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) **How links resolve** - Links resolve when a page is written through the Knowledge tools, `upsertPage` or `removePage`. - A link matches by page id, `scope#key/key` path, scope address or page name: the page's own scope first, then the whole store. - A link to a page that doesn't exist yet resolves once that page is created. It follows renames and moves, and goes back to unresolved when its target is deleted or merged away. - One write re-resolves at most 100 pages that link to what it changed. Past that it logs a warning, and the rest catch up when they are next written or when `knowledge.reindexLinks()` runs. Pages written before this release, or straight to the store, aren't indexed until they are written again. Run `knowledge.reindexLinks()` once to index them all; it needs no vector store. ```ts await knowledge.reindexLinks(); const store = await mastra.getStorage()!.getStore('knowledge'); const counts = await store!.countPageLinks({ pageIds: [page.id], read: 'all' }); // { [page.id]: { in: 3, out: 1 } } ``` - Added `readKnowledgePageLinkCounts` and `readKnowledgePageLinks` to `@mastra/core/knowledge`: exact link counts for any set of pages and one page's links with the pages at their other ends, read from the store's link index and filtered by the caller's read scope. Both return `null` when the store has no link index. ([#29260](https://github.com/mastra-ai/mastra/pull/29260)) ```ts import { readKnowledgePageLinkCounts, readKnowledgePageLinks } from '@mastra/core/knowledge'; const counts = await readKnowledgePageLinkCounts(store, { pageIds: [pageId], read }); if (counts === null) { // this store has no link index } const links = await readKnowledgePageLinks(store, { pageId, read, direction: 'out' }); ``` - `pinnedContext.maxTokens` no longer has a 2,000-token ceiling. It budgets pinned pages only, so set it below your model's context limit to leave room for instructions, conversation history and model output, for example `pinnedContext: { maxTokens: 8000 }`. The default is still 500. ([#29257](https://github.com/mastra-ai/mastra/pull/29257)) - Fill pinned Knowledge listings level by level within their token budget. A listing with `depth` 2 or 3 now shows every child that fits first, then grandchildren, and so on, instead of stopping at a fixed 50 entries in sibling order. Each entry still prints under its parent, and a listing that is cut still ends with `…and more; knowledge_list from: ""`. ([#29263](https://github.com/mastra-ai/mastra/pull/29263)) - The `knowledge_read` tool now tells the model that a read from a line runs until the page ends or the read's token cap is reached, and that `limit` caps notes, children and mentioning notes, not lines. Models had been passing `limit` expecting a set number of lines back. ([#29258](https://github.com/mastra-ai/mastra/pull/29258)) - Removed `knowledge.reindex()`. It re-embedded every page and note in the store, which is slow and costly on a large store. Pages and notes are embedded when they're written through the Knowledge tools, `upsertPage` or the note methods. If an embed fails, the write still succeeds and the content stays findable by keyword search; it's embedded again the next time it changes. `upsertPage` skips writes whose content is unchanged, so sending identical content again doesn't retry a failed embed; the Knowledge tools and `updateNote` embed on every write. ([#29256](https://github.com/mastra-ai/mastra/pull/29256)) If you called `reindex()` after loading content, delete the call and load the content through `upsertPage` instead, which embeds each new or changed page as it's written. There is no bulk re-embed for content that's already stored: ```ts // Before await knowledge.reindex(); // After await knowledge.upsertPage({ scope: 'org:acme', path: 'refunds', name: 'Refund policy', body }); ``` - Fixed read-only Knowledge runs filling storage with empty scopes. A run that only searches, lists or reads Knowledge, and pinned-context delivery each turn, no longer creates the run's `resource:`, `thread:` or other held scopes. A held scope is created, with its template pages and its `belongsTo` placement, the first time a run writes Knowledge (`knowledge_page`, `knowledge_note`, `knowledge_pin`). ([#29288](https://github.com/mastra-ai/mastra/pull/29288)) Before, a Slack agent that only answered questions with `knowledge_search` created an empty `resource:slack:` and `thread:` scope for every user and conversation, which filled the Studio tree. A scope with no pages yet reads as not found until the first write creates it. - Knowledge tree child counts now say when they were capped. If a scope has more child scopes than the 1,000 the tree looks through for each level, the page gets `childCountCapped: true` and its `childCount` is a lower bound. The Knowledge explorer then shows the count as "1000+" in the tree and in the graph's "pages under" line, instead of a number that's too low. ([#29216](https://github.com/mastra-ai/mastra/pull/29216)) - Each page in a Knowledge tree level (`GET /api/knowledge/:knowledgeKey/tree`, `listKnowledgeTreeChildren`) now carries `childCount`: how many readable pages, and for a scope how many readable child scopes, sit directly under it. A browsing UI can show the count and offer to open only pages that have children. The count is left out when the storage adapter can't count children yet. ([#29216](https://github.com/mastra-ai/mastra/pull/29216)) ```ts const level = await client.getKnowledge('main').tree({ parentId: scopeId }); level.pages.map(page => `${page.name} (${page.childCount ?? '?'})`); ``` Storage adapters gain `countChildPages({ parentIds, read })`, which counts a batch of parents' child pages in one query (in-memory, LibSQL, PostgreSQL, MySQL, MongoDB). - Fixed approving a tool loaded by `ToolSearchProcessor` after a server restart. The approved call failed with `Tool "" not found` when memory no longer returned the search result. It now runs with the tools it had when the model made the call. Fixes [#29196](https://github.com/mastra-ai/mastra/issues/29196). ([#29251](https://github.com/mastra-ai/mastra/pull/29251)) - Fixed controller tool status for validation failures and explicit error results, including delegated tool results. Successful results containing business error fields retain their successful status. ([#28812](https://github.com/mastra-ai/mastra/pull/28812)) - Fixed dataset experiments that target a workflow so their traces carry the experiment ID. Workflow runs and every auto-resume now include `experimentId` in tracing metadata, so Studio's experiment page shows total cost, tokens and run duration for workflow experiments, the same as it does for agent experiments. ([#29245](https://github.com/mastra-ai/mastra/pull/29245)) ## 1.82.0-alpha.2 ### Minor Changes - Added `requestId`, `serviceName`, and `experimentId` filters to advanced trace queries. Use them at trace scope or inside `spans.some` / `spans.none` with `eq`, `ne`, `in`, `notIn`, `exists`, and `notExists`. ([#29194](https://github.com/mastra-ai/mastra/pull/29194)) ```ts const result = await mastraClient.queryTraces({ timeRange: { from: '2026-11-01T00:00:00.000Z', to: '2026-11-02T00:00:00.000Z' }, where: { op: 'and', args: [ { op: 'eq', left: { path: 'requestId' }, right: { literal: 'req-42' } }, { spans: { some: { op: 'eq', left: { path: 'serviceName' }, right: { literal: 'payments' } } } }, ], }, }); ``` - Added `getStepResultAsync` to workflow step, condition, sleep, and `.map()` contexts, and deprecated `getStepResult`. ([#26999](https://github.com/mastra-ai/mastra/pull/26999)) `getStepResultAsync` returns the same value as `getStepResult`, wrapped in a promise. Switching now prepares workflows for upcoming storage changes that load step results on demand instead of keeping every result in memory. `getStepResult` keeps working and will be removed in a future major release. ```ts // Before const step2 = createStep({ execute: async ({ getStepResult }) => { const step1Result = getStepResult(step1); }, }); // After const step2 = createStep({ execute: async ({ getStepResultAsync }) => { const step1Result = await getStepResultAsync(step1); }, }); ``` If your tests build step execute params by hand, add a `getStepResultAsync` function to them. - Added `knowledge_list`, a Knowledge read tool that lists the page tree under a starting point, so an agent can see how a scope or section is organized before searching. Each entry has the ref `knowledge_read` accepts, its name and child count; pass `descriptions: true` to add page descriptions. It goes up to 3 levels deep, takes a `limit` and a `cursor` for wide levels, and says when deeper levels were cut off. A result also stops at the same token cap as a `knowledge_read` body; `more` says how many entries were left out and gives the `cursor` that continues exactly where it stopped, at any depth. Scopes the run can't read are left out. `tools: { read: true }` includes it. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) ```ts // Agent tool call knowledge_list({ from: 'docs:acme#guides', depth: 2 }); // → { from: { ref: 'docs:acme#guides', name: 'Guides' }, // entries: [{ ref: 'docs:acme#guides/observability', name: 'Observability', depth: 1, children: 2 }, …] } ``` Knowledge storage adapters gained `countChildPages({ parentIds, read })`, which counts the readable child pages under each parent in one query. - `knowledge_read` reads long pages by line. A body that fits in one read (1,000 tokens) comes back whole as `body`. A longer one comes back as numbered `lines` (`12│text`) with the `range` they cover and the page's `lineCount`, ending at the last whole line that fits, and `more` says where to read on with `from: { line }`. A line too long for one read, such as a wide table row, is cut by column (`from: { line, col }`). An agent can also ask for the body's `outline` (headings with their line numbers and sizes), read one `section`, or `match` one or more terms like grep: only the lines that contain them, with `context` lines before and after (default 2). ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) ```ts knowledge_read({ page: 'docs:acme#models/anthropic', outline: true }); knowledge_read({ page: 'docs:acme#models/anthropic', section: 'contextManagement' }); knowledge_read({ page: 'docs:acme#models/anthropic', match: ['excludeTools', 'cacheControl'], context: 3 }); knowledge_read({ page: 'docs:acme#models/anthropic', from: { line: 120 } }); ``` Every Knowledge read result now lists anything it cut short in `more`, with how to get the rest: the body, notes, child pages (continue with `knowledge_list`), notes that mention the page (new `mentionedInCursor`), and search results at their limit. A whole-page read keeps a page's notes, and the notes on other pages that mention it, only while each list stays under 1,000 tokens. A longer list is left out and counted in `more`; read it on its own with `notes: true` or `mentionedIn: true`. A read with `cursor` or `mentionedInCursor` now returns only those notes, without repeating the body. Every part of a body a read returns comes with its `range`. `knowledge_page` takes a `range` with `expectedVersion` and replaces only those lines with `body`; an empty `body` removes them. A range may be the one a read returned or a smaller one inside it, but not wider than one read from its first line shows, so text the writer never saw can't be overwritten. A body write without a range is refused when the stored body is longer than one read returns, and so is a body that still carries the read's line numbers. ```ts const { page } = await knowledge_read({ page: 'docs:acme#models/anthropic', section: 'Setup' }); knowledge_page({ op: 'write', page: page.ref, body: editedSetup, range: page.range, expectedVersion: page.version }); ``` The per-result cap is configurable: `tools: { read: { maxTokens: 3000 }, write: true }` sets how many estimated tokens one `knowledge_read` body window or match result, and one `knowledge_list` result, returns (default 1,000, at least 100). The write guard for line ranges follows the same cap. ```ts const knowledge = new Knowledge({ scopes, tools: { read: { maxTokens: 3000 }, write: true } }); ``` - Added opt-in Editor ownership and versioned persistence for static model fallback lists. ([#27287](https://github.com/mastra-ai/mastra/pull/27287)) ```ts new Agent({ id: 'support-agent', name: 'Support agent', instructions: 'Help users resolve support requests.', model: [ { id: 'primary', model: 'openai/gpt-4o' }, { id: 'fallback', model: 'openai/gpt-4o-mini' }, ], editor: { model: true }, }); ``` - Suspended background tool calls can now be answered after a reload. ([#28998](https://github.com/mastra-ai/mastra/pull/28998)) **Open request saved on the tool call.** While a deferred background tool is suspended, the assistant message that holds the call keeps the open request in `metadata.suspendedTools[toolCallId]` (with `suspendPayload`, `taskId`, and `suspendedAt`). The entry is removed when the task resumes or is cancelled, so a chat that reloads the thread shows only requests that are still open. **Resumed tools see what they asked for.** A resumed background tool now receives the payload it suspended with in `context.agent.suspendPayload`, the same as a foreground tool: ```typescript execute: async (input, context) => { const { resumeData, suspendPayload, suspend } = context.agent; if (!resumeData) return suspend?.({ question: 'Approve the refund?' }); // suspendPayload is { question: 'Approve the refund?' } }; ``` **Resume only the suspension you read.** `BackgroundTaskManager.resume()` takes an optional `suspendedAt`, and refuses the resume when the task has resumed or suspended again since: ```typescript await mastra.backgroundTaskManager.resume(taskId, { approved: true }, { suspendedAt: task.suspendedAt }); ``` **The agent reports a deferred task's result after its stream ends.** When a deferred background task finishes and no `untilIdle` stream is open for its thread, the agent now gets a `background-task` notification signal on the thread. An idle thread wakes and the agent reports the outcome; a run already active on the thread gets the signal on its next model request. Before, the result was only written to memory and waited for the next user message. This covers `stream()` calls without `untilIdle` and tasks resumed after they suspended, for example an MCP task answered from the chat. Streams using `untilIdle` are unchanged: they still run their own follow-up turn, and no signal is sent. Cancelling or aborting an `untilIdle` stream still means no reply for the tasks it was waiting for. **Opt in to ending woken runs after their turn.** A run that a signal starts on an idle thread still stays open until the background tasks it dispatched finish. Set the temporary `wokenRunsEndAfterTurn` option to end it after its own turn instead; each task then wakes the thread when it finishes. A signal's own `ifIdle.streamOptions.untilIdle` takes precedence either way. ```typescript new Mastra({ backgroundTasks: { enabled: true, wokenRunsEndAfterTurn: true } }); ``` In the next major release, ending after the turn becomes the only behavior and `wokenRunsEndAfterTurn` is removed. Pass `untilIdle: true` in `ifIdle.streamOptions` to keep a woken run open; channels and agent controller sessions already do. **Turn off the completion signal.** Set `notifyAgentOnFinish: false` in the `backgroundTasks` config when your app sends its own completion signal, for example from `onTaskComplete`. With `wokenRunsEndAfterTurn` also on, your app must report the tasks of woken runs itself. - Added `knowledge.upsertPage()`, `knowledge.removePage()`, `knowledge.addNote()`, `knowledge.updateNote()` and `knowledge.removeNote()` so apps can sync outside content, such as docs, into Knowledge from server code. A page is identified by its scope and key path, so running a sync again updates the same pages and skips unchanged ones. Notes on synced pages are kept, pages are embedded when Knowledge has a vector store, and change listeners are told. ([#29159](https://github.com/mastra-ai/mastra/pull/29159)) ```ts await knowledge.upsertPage({ scope: 'docs:acme', path: 'guides/install', name: 'Install', body: markdown }); await knowledge.removePage({ scope: 'docs:acme', path: 'guides/old' }); const note = await knowledge.addNote({ scope: 'docs:acme', path: 'guides/install', text: 'Needs Node 20.' }); await knowledge.updateNote({ id: note.id, text: 'Needs Node 22.' }); // returns a note with a new id ``` ### Patch Changes - Fixed "Tenant model credential policy could not be resolved: the request has no authenticated user" errors that recurred on every message in a session with a pending tool approval. When a new user message declines a pending approval, the decline now runs with the sender's request context instead of the reattached run's empty one. ([#29231](https://github.com/mastra-ai/mastra/pull/29231)) - Knowledge tool results now name each page by the ref `knowledge_read` accepts. Search, read and page results carry `ref`: the scope address for a scope page, the full key path for a nested page (`docs:acme#guides/observability/logging`), or the page id when a page on the path has no key. The old `key` field held only the last path segment, so agents that copied it got "not found" for nested pages; it is gone. A read that misses now lists up to three close matches by ref, and an ambiguous page name lists its candidates by ref. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) - Knowledge page paths ignore leading, trailing and doubled slashes, so `docs:acme#models/gateways/` names the same page as `docs:acme#models/gateways`. This holds for `upsertPage`, `removePage`, the note methods, `[[links]]` in notes, page refs given to the Knowledge tools, and `pinnedContext` refs. Previously `upsertPage` refused such paths and reads reported the page as not found. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) - `knowledge_search` finds pages that match any word of the query and ranks them by how many of the words they contain, so a long question still surfaces the page that holds most of its words. Among pages with the same number of words, a word in a page's name counts for more than one in its description, and one in its description for more than one in its body, then the newest page wins. A page whose name contains the whole query still comes first. Ranking happens inside each store (in-memory, LibSQL, PostgreSQL, MySQL and MongoDB) before the result limit, and access checks run before ranking as before. Quoted phrases are still searched as plain words. When nothing matches any word, the "no results" hint now says so. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) - `knowledge_search` handles queries agents commonly write. Quoted words (`"api" "responses"`) are searched as plain words; before, the quote marks were matched literally and nothing was found. A search that finds nothing now says so and suggests one to three key words or `knowledge_list`. Passing a page ref such as `docs:acme#models` as `scope` now explains that `scope` takes scope addresses, instead of reporting the page as not found. ([#29207](https://github.com/mastra-ai/mastra/pull/29207)) A first `knowledge_read` of a long page that stops early now also returns the `outline` of the headings it did not reach (up to about 500 tokens of them, with their line numbers and sizes), so an agent can read the section it needs next instead of paging toward it. Search results at their limit no longer tell the agent to search again; the default Knowledge instructions now say to follow `more` only when what the agent needs is not already in what it has read, and to answer once it has it. The first `knowledge_read` of a page too long to return whole now shows the whole page at a glance: every heading with its line number and the first lines under it, with `⋯ lines a–b` where lines are left out, within the read cap. Read the part you need with `section` or `from`. A page with more headings than fit falls back to its top lines and the headings below them. - Stored spans now use the service name from the Observability config when the span metadata does not set one. ([#29201](https://github.com/mastra-ai/mastra/pull/29201)) - Fixed `getStepResult` and `getStepResultAsync` returning `null` for the loop body inside `.dowhile()` and `.dountil()` conditions on the evented workflow engine. Loop conditions now see the body's latest result on every iteration, matching the default engine. ([#26999](https://github.com/mastra-ai/mastra/pull/26999)) - Fixed `run.cancel()` leaving suspended nested workflow runs suspended after the parent was canceled. Nested runs, including foreach iterations, are now canceled with their parent on both the default and evented engines. Calling `cancel()` again on a run that is already canceled also cancels nested runs it left suspended. If storage fails while canceling nested runs, `cancel()` rejects and can be retried. (#24780) ([#29170](https://github.com/mastra-ai/mastra/pull/29170)) - Fixed signal-started runs to use the `ifIdle.streamOptions.runId` you provide. This applies when `sendSignal`, `sendMessage`, `queueSignal` or `queueMessage` wakes an idle thread or queues a run behind the active one. The id appears in `accepted.runId`, `output.runId` and thread events. If the id already names a known run, `accepted` rejects. Calls without an id still get a random one. ([#29152](https://github.com/mastra-ai/mastra/pull/29152)) ```ts const { accepted } = agent.sendSignal( { type: 'user-message', contents: 'hello' }, { resourceId, threadId, ifIdle: { streamOptions: { runId: 'my-run-id' } } }, ); await accepted; // { action: 'wake', runId: 'my-run-id', output } ``` - Fixed progress from tools wrapped with `createStep` from `@mastra/core/workflows/evented` never reaching the workflow stream. The tool now receives the step `writer`, the run `actor` (including the `actor` step option) and `suspendPayload`, the same as with `createStep` from `@mastra/core/workflows`. Fixes #29164. ([#29165](https://github.com/mastra-ai/mastra/pull/29165)) - Fixed type declarations for bundled AI SDK packages. When `@mastra/core` bundled two versions of the same AI SDK package, one version's types overwrote the other's, so types such as `LanguageModelV3` used by the bundled OpenAI, Google and Anthropic providers resolved to `any`. Each version now keeps its own copy, so these types are checked again. Fixes [#28732](https://github.com/mastra-ai/mastra/issues/28732). ([#29171](https://github.com/mastra-ai/mastra/pull/29171)) ## 1.82.0-alpha.1 ### Minor Changes - Added time to first token and time to first chunk measures to trace and span aggregates, so dashboards can chart streaming latency next to duration. ([#29055](https://github.com/mastra-ai/mastra/pull/29055)) The new measures are `timeToFirstToken.avg`, `.min`, `.max`, `.p50`, `.p90`, `.p95`, `.p99`, and `.count`, plus the same set for `timeToFirstChunk`. Traces and spans without a recorded value are skipped instead of counted as 0, and `.count` reports how many had one. Stores opt in through the `aggregate-time-to-first-measures` feature. ### Patch Changes - Fixed durable agents (`durable: true`) ending the `model_generation` trace span without `responseId` and `responseModel`. Durable runs now record the provider's response id and model on the span, the same as non-durable agents, so a trace can be linked back to the provider's own response log. Fixes #29053. ([#29180](https://github.com/mastra-ai/mastra/pull/29180)) - Fixed metadata filters in the in-memory store returning records whose nested metadata had different keys from the filter when one side contained an `undefined` value. ([#29101](https://github.com/mastra-ai/mastra/pull/29101)) - Fixed directly resumed nested workflows in the evented engine so they continue suspended parent runs. ([#25979](https://github.com/mastra-ai/mastra/pull/25979)) ## 1.82.0-alpha.0 ### Minor Changes - An agent's `scopes` function can now narrow or replace a run's scopes. It receives the run's incoming scopes (from the call, `defaultOptions` and request context) and returns the run's final set. A static `scopes` list is still added to the incoming scopes. The function must keep any `resource:` or `thread:` scope it was given; dropping one throws. ([#28353](https://github.com/mastra-ai/mastra/pull/28353)) ```ts const agent = new Agent({ id: 'support', name: 'Support', instructions: 'Answer support questions.', model, // Keep identity scopes, drop anything else the caller sent, and add the tenant's org. scopes: ({ scopes, requestContext }) => [ ...scopes.filter(scope => typeof scope === 'string' && /^(resource|thread):/.test(scope)), `org:${requestContext.get('orgId')}`, ], }); ``` - Added `onDecision` to `ClassifierProcessor` for writing guardrail decisions to your own audit log (#28985). It runs after each classification, including failed classifier calls, and receives the outcome (`allowed`, `filtered` or `aborted`), the answers or the error, the call duration, and the ids of the message, thread, resource or run. ([#29070](https://github.com/mastra-ai/mastra/pull/29070)) ```ts new ClassifierProcessor({ classifier: safety, onResult, onDecision: async decision => { await auditLog.insert({ threadId: decision.ids.threadId, outcome: decision.outcome, answers: decision.result ? decision.result.answers : null, error: decision.result ? null : String(decision.error), }); }, }); ``` `abort` in `onResult` now accepts tripwire metadata, so the caller can tell which policy blocked the request: ```ts onResult: (answers, { abort }) => { if ((answers.unsafe.probability ?? 1) > 0.8) { abort('Blocked', { metadata: { policy: 'unsafe' } }); } }; ``` A failed classifier call under `errorStrategy: 'strict'` now sets tripwire metadata `{ processorId, classifierId, code: 'CLASSIFIER_FAILED' }`. On agents that aren't durable, stopping a streaming reply now cancels a classifier call that is still running. - Changes to an experimental, upcoming memory feature. ([#28353](https://github.com/mastra-ai/mastra/pull/28353)) `experimental_subconscious` and the Memory `knowledge` option are removed: setting either now throws, saying to attach Knowledge to the agent instead (`knowledge: false` is still accepted). `Subconscious` and `KnowledgeSemanticIndexCoordinator` stay exported from `@mastra/memory` so older code still loads, and throw when constructed. `@mastra/core/knowledge` exports `Knowledge` with its option and result types and the Knowledge storage domain; the tool, pin and semantic-index helpers are internal. Agent `scopes` are plain `":"` strings that say who the run is; what the run may read or write comes from whoever consumes them, such as Knowledge's scope declarations. A message can carry its own scopes: `session.sendMessage({ content, scopes })`. A run's scopes are fixed when it starts, so a message whose scopes differ from the running run's waits for it to end, then starts its own run in the same conversation. `sendSignal` reports this as `{ action: 'blocked', reason: 'scope-mismatch', runId }`. `queueMessage`, `queueSignal`, `sendNotificationSignal` and `steer` take `scopes` the same way: a queued or steering message, or a notification, whose scopes differ from the running run's starts its own run with its scopes after that run instead of joining it. A deferred notification keeps the scopes it was sent with. Scopes are set in process only: remote callers (the agent controller HTTP routes and `@mastra/client-js`) can't send them. To derive scopes from a request, give the agent a `scopes` function that reads trusted request context. When the thread's claimed owner runs in another process, a message with scopes isn't queued: `sendMessage` rejects with an error saying scopes don't cross processes. Messages without scopes still join that owner's run. Attach Knowledge with `new Agent({ knowledge, scopes })`. Knowledge needs `scopes`, the scope types its runs may hold and who gets which role on them: `new Knowledge({ storage, scopes: { ...knowledgeMemoryScopes() } })` declares Memory's `thread:`/`resource:` scopes and an `org:` scope; `{}` declares none. A run holding a scope that isn't declared fails. Tools are off by default: `tools: true` gives the read and write tools, `tools: { read: true }` the read tools only, and `pinnedContext: true` (or `{ maxPins, maxTokens }`) delivers pinned pages every turn and adds `knowledge_pin`. `knowledge.readScopeFor(principals)` returns what holders of those scopes can read, for host code reading the store directly. `knowledge.explainAccess(runScopes, { scope } | { page })` says why a run can or can't reach a scope or page; it's host-only, so never expose it through an agent tool or an unauthenticated route. `knowledge.onChange(listener)` reports each committed write made through `knowledge.getStorage()` (tools included) by id, for live UIs; re-read each page under the viewer's read scope before showing it. A scope declaration can carry a `displayLabel` for UIs, and `knowledge.scopeDeclaration(key)` returns a declaration's display fields. Knowledge is a tree of pages. The tools are five: `knowledge_search`, `knowledge_read` (a page with its notes, or a scope's pages), `knowledge_page` (`op: 'write' | 'move' | 'merge'`), `knowledge_note` (`op: 'add' | 'restamp' | 'remove'`) and `knowledge_pin` (`op: 'pin' | 'unpin'`). Notes are at most about 250 tokens and page descriptions about 100; long text goes in a page body. Search is keyword search by default. Give Knowledge a `vector` store and an `embedder` and `knowledge_search` blends keyword and semantic results; pages and notes written through the tools are embedded as they're written. Every semantic hit is re-checked against what the run can read before it's ranked, so hidden content never fills result slots. `knowledge.reindex()` re-embeds everything, for content written straight to the store or after an embedding failure. - Fixed a recovered thread run losing its thread lease when the execution it replaced finished. Previously, when a run was recovered in another process, the original execution's exit released the shared lease while the recovered execution was still running, so the thread looked free and a new run could start on it. Each execution now holds the lease under its own generation, so only the live execution can renew or release it. ([#28947](https://github.com/mastra-ai/mastra/pull/28947)) Also fixed run IDs that contain the internal handover marker, or look like an encoded lease owner, being misread as a different run. Any caller-provided run ID now round-trips unchanged. **Upgrading shared deployments** Drain active thread runs and restart API and worker processes together. Older processes do not recognize the new lease owner format: they cannot see a run as live, even while it is renewing its lease. Follow-ups that an older process forwards to a run on this release are still delivered. ### Patch Changes - Fixed resuming suspended agent runs across server instances when a process retains an earlier run segment. ([#29065](https://github.com/mastra-ai/mastra/pull/29065)) - Update provider registry and model documentation with latest models and providers ([`4d3ebbd`](https://github.com/mastra-ai/mastra/commit/4d3ebbd304509acb118f319e8467fc932079e715)) - Improved `validateStepRequestContext` typing. It now accepts any object with `id` and `requestContextSchema`, not only a full `Step`. ([#29080](https://github.com/mastra-ai/mastra/pull/29080)) - Fixed skills not loading on Windows when the workspace `basePath` uses a short folder name such as `C:\Users\ABCDEF~1\...`, which `os.tmpdir()` can return. Optional skill folders that don't exist, like `references`, no longer fail with `Permission denied`. Fixes [#28957](https://github.com/mastra-ai/mastra/issues/28957). ([#29078](https://github.com/mastra-ai/mastra/pull/29078)) - Fixed a signal sent to a thread while an `agent.generate()` turn was running starting a second run that could not see that turn. `generate()` now registers its run with the thread like `stream()` does, so `sendSignal`, `sendMessage` and notifications with the default `ifActive: 'deliver'` join the running turn. ([#28953](https://github.com/mastra-ai/mastra/pull/28953)) As with `stream()`, a thread-bound `generate()` now waits for a run already going on the same thread, so concurrent `generate()` calls on one thread run one after another instead of in parallel. A tool that calls `generate()` or `stream()` on its own thread waits for the run it is part of, so it deadlocks; a tool should use `sendSignal` to send a message to its own thread. - Knowledge notes returned by `knowledge_note`, `knowledge_read` and `knowledge_search` now carry `mentions`, mapping each `[[ref]]` in the note to the page id it resolves to, so a chat can link them. Refs are resolved the way the note's write resolved them; a name that no longer resolves is left out. ([#28942](https://github.com/mastra-ai/mastra/pull/28942)) ```json { "id": "n1", "text": "Escalate per [[Escalation Process]].", "mentions": { "Escalation Process": "page-esc" } } ``` - Added read-only page-store readers to `@mastra/core/knowledge` for browsing UIs: `listKnowledgeTreeChildren` (one level of the page tree, child scopes first), `readKnowledgePage` (a page with its readable ancestors, notes, pin, referenced pages and notes that mention it), `readKnowledgePageGraph` (a bounded page graph with parent and mention edges, limits in `DEFAULT_KNOWLEDGE_PAGE_GRAPH_LIMITS`; mentions come from every note on a walked page until a graph-wide `maxNotes` budget runs out, which sets `truncated`), `searchKnowledgeExplorerPages` and `readKnowledgePageActivity`. Each takes a read scope (`'all'` for an owner view) and passes it to every store call, so pages and notes outside it never appear, mention edges included. They never write. Added `readKnowledgeScopeGrants`, which lists the declared grants onto the scope pages in a graph (principal scope, its page id when it has one, target and role), keeping only principals the read scope can read, and `knowledge.scopeGrants(scope)`, which gives each principal the declared rules name on one scope with its strongest role. ([#28362](https://github.com/mastra-ai/mastra/pull/28362)) - Fixed `Property '#private' is missing` when passing a voice provider such as `new OpenAIRealtimeVoice()` to an agent's `voice` option or to `CompositeVoice` ([#29016](https://github.com/mastra-ai/mastra/issues/29016)). ([#29074](https://github.com/mastra-ai/mastra/pull/29074)) These options now accept the `IMastraVoice` interface, exported from `@mastra/core/voice`. `agent.voice` and `agent.getVoice()` return `IMastraVoice`. If you annotate a variable with a voice type, use `IMastraVoice` instead of `MastraVoice`. ```typescript import { Agent } from '@mastra/core/agent'; import { OpenAIRealtimeVoice } from '@mastra/voice-openai-realtime'; export const voiceAgent = new Agent({ id: 'voice-agent', name: 'Voice Agent', instructions: 'You are a voice assistant.', model: 'openai/gpt-6.1-sol', voice: new OpenAIRealtimeVoice(), // failed with TS2322 before this fix }); ``` - Fixed retained workflow handles and abort listeners after durable agent runs settle (#29009) ([#29065](https://github.com/mastra-ai/mastra/pull/29065)) ## 1.81.0 ### Minor Changes - Added schemas for saved observability list views. Use `savedViewStateSchema` to validate a saved traces, spans, threads or logs view: its filters, time range, sort, columns and page options. A service that only stores views can use `savedViewStateEnvelopeSchema` instead. It checks the version, the page and the 16 KiB size limit. Dashboards can reuse the filter shape through `filterNodeSchema`. It accepts any operator id, so new operators don't need a schema change. ([#28715](https://github.com/mastra-ai/mastra/pull/28715)) ```ts import { savedViewStateSchema } from '@mastra/core/storage'; savedViewStateSchema.parse({ schemaVersion: 1, page: 'traces', filters: [{ fieldId: 'status', value: 'error' }], dateRange: { preset: 'last-24h' }, sort: { field: 'startedAt', direction: 'desc' }, }); ``` - Channel agents with a workspace can now open binary file attachments, such as ZIP and DOCX files, that need an authenticated download (for example Slack files). The file is downloaded through the adapter and saved under `channel-attachments/` in the agent's workspace. The agent gets the workspace path instead of a private URL it can't open. ([#28913](https://github.com/mastra-ai/mastra/pull/28913)) This covers attachments that `inlineMedia` doesn't send to the model as file parts. PDFs are sent inline by default. When the workspace uses `mounts`, the file is saved in the first writable mount other than `/`. Saving follows the workspace's write-file tool policy: if `mastra_workspace_write_file` is disabled or needs approval, the file isn't saved. Adapters whose links don't need a token also get the workspace path. For example, a Discord agent with a writable workspace now gets the workspace path instead of the CDN URL, which expires. The agent still gets the URL for files over 10 MiB, failed downloads, read-only workspaces, workspaces where writes are disabled or need approval, and agents without a workspace. `channel-attachments/` is capped at 100 MiB per workspace so channel participants can't fill the disk. Once a file would go over the cap, the agent gets the URL instead and nothing is written. Change the cap with `maxAttachmentStorageBytes`: ```ts new Agent({ // ... channels: { adapters: { slack }, maxAttachmentStorageBytes: 500 * 1024 * 1024 }, }); ``` - Added `setCookies()` to `MastraBrowser`, so your app can sign a thread's browser in to a site the agent can't sign in to itself, such as a preview behind your own login. The cookie value never passes through the model. Cookies set before the browser opens are added when it does. ([#28631](https://github.com/mastra-ai/mastra/pull/28631)) ```ts await browser.setCookies([{ name: 'my_app_session', value: token, domain: 'app.example.com', path: '/' }], threadId); ``` Browser providers that don't support cookies throw. Custom providers can add support by overriding `addCookiesToOpenBrowser()` and calling `addPendingCookies(threadId)` once a thread's browser opens, before it loads a page. - Added factory-scoped sandbox environment. A deployment that hosts several factories can now give a credential to one factory's sessions only: every Factory sandbox provider accepts `env` as a function of the session's sandbox context, which carries the new `factoryId` and `factoryName` fields. Scope secrets by `factoryId`, because names can be edited and aren't unique. ([#28767](https://github.com/mastra-ai/mastra/pull/28767)) ```ts new PlatformFactorySandbox({ env: ctx => (ctx.factoryId === PLATFORM_FACTORY_ID ? { PLATFORM_TOKEN } : {}), }); ``` Platform and Docker sandboxes keep the environment they started with when they resume. When you remove a credential from a factory, recreate its running sandboxes. - Added execution outcomes and typed stop-reason details to agent, model, tool, and workflow traces. Traces distinguish failures, cancellations, limits, guardrail blocks, and observer interruptions; TripWire errors now use the name `TripWire` instead of `Error`, including in string representations, stacks, and serialized errors, so they can be identified across workers. ([#28703](https://github.com/mastra-ai/mastra/pull/28703)) Custom spans can record an outcome with a compact stop-reason context: ```ts import { SpanType, type ObservabilityInstance } from '@mastra/core/observability'; function recordGuardrailBlock(observability: ObservabilityInstance, processorId: string) { const span = observability.startSpan({ name: 'policy check', type: SpanType.GENERIC }); span.end({ outcome: 'blocked', stopReason: 'guardrail', stopReasonContext: { processorId } }); } ``` The public `outcome`, `stopReason`, and `stopReasonContext` fields now also allow `null`. Exporter and other consumer types must handle `null` alongside an absent value; arbitrary custom outcome strings and context objects remain supported. Workflow events and durable snapshots can now include tripwire details, including processor-supplied metadata. Agent and generation spans also close when the internal workflow fails without a finish callback, while errors followed by a finish retain their final usage and output. - Added an optional `inputAccepted` field to `OutputResult`, the `result` argument of `processOutputResult`. It is set only when `finishReason` is `'error'`, and is `true` when the run accepted the turn's input before it failed. `MessageHistory` uses it to keep the user message of a failed turn that produced no output, so an accepted turn is not lost from history. Failed turns without the flag are still dropped, as before. ([#28988](https://github.com/mastra-ai/mastra/pull/28988)) ```ts import type { Processor } from '@mastra/core/processors'; const processor: Processor = { id: 'accepted-turns', async processOutputResult({ messages, result }) { if (result.finishReason === 'error' && result.inputAccepted) { // The run failed after accepting this turn's input } return messages; }, }; ``` - Added an optional `getRoutes()` method to `SignalProvider`. Routes it returns are added to `server.apiRoutes` automatically when the agent is registered, so webhook and OAuth routes for a signal provider no longer need manual server config. ([#28575](https://github.com/mastra-ai/mastra/pull/28575)) ```ts class AcmeSignals extends SignalProvider<'acme'> { readonly id = 'acme' as const; getRoutes() { return [registerApiRoute('/acme/webhook', { method: 'POST', handler: async c => c.json({ ok: true }) })]; } } // Before: server: { apiRoutes: acme.getRoutes() } // After: just add the provider to the agent const agent = new Agent({ /* ... */ signals: [new AcmeSignals()] }); export const mastra = new Mastra({ agents: { agent } }); ``` - Fixed resumed runs losing the link to the run they continue. When you resume an agent, durable agent or workflow run, its span now sets `resumedFromSpanId` to the span id of the first run. This stays true after several resumes and after a durable agent recovers. A resumed run also keeps the external parent span of the run it continues. ([#28849](https://github.com/mastra-ai/mastra/pull/28849)) A resume that passes only `tracingOptions.parentSpanId` now stays in the original trace under the first run, and uses that id as its external parent. Before, it started a new trace. ```ts await agent.resumeGenerate({ approved: true }, { runId, toolCallId, tracingOptions: { parentSpanId: callerSpanId } }); // The resumed run stays in the first run's trace. // resumedFromSpanId is the first run's span id, externalParentSpanId is callerSpanId. ``` The `resumedFromSpanId` option of `getOrCreateSpan` is deprecated. Use `resumeFrom` with the saved tracing context instead. - Span aggregates can now group by `model` and `provider` and count their distinct values with `countDistinct.model` and `countDistinct.provider`. Stores opt in with the new `span-aggregate-model-dimensions` feature. ([#28908](https://github.com/mastra-ai/mastra/pull/28908)) ```typescript const { rows } = await mastraClient.aggregateSpans({ timeRange, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'model_generation' } }, groupBy: ['provider', 'model'], measures: ['count', 'tokens.total.sum', 'cost.sum'], }); ``` - Span queries and span aggregates now accept `model` and `provider` filters. Stores opt in with the new `span-query-model-fields` feature. ([#28859](https://github.com/mastra-ai/mastra/pull/28859)) ```typescript const { spans } = await mastraClient.querySpans({ timeRange, where: { op: 'eq', left: { path: 'provider' }, right: { literal: 'anthropic' } }, }); ``` - Fixed the browser failing with `ECONNREFUSED` when launched again on a profile it had used before. ([#28675](https://github.com/mastra-ai/mastra/pull/28675)) Before launching Chrome on a local profile, Mastra now clears the launcher logs left by earlier launches (`chrome-err.log`, `chrome-out.log`), which made the next launch connect to the old, dead DevTools port. It also removes a Chrome profile lock left by a browser that is no longer running. Stagehand does this for every thread-scoped browser too, not only on the first launch. Remote browsers (`cdpUrl`, Browserbase) are not affected. If a browser that is still running holds the profile, launch now fails with a `BrowserProfileInUseError` that names the profile and the holding process: ```ts import { BrowserProfileInUseError } from '@mastra/core/browser'; try { await browser.launch(); } catch (error) { if (error instanceof BrowserProfileInUseError) { // Another Chrome is using the profile: close it, or use a different profile. console.error(error.message); } throw error; } ``` Added `browser.retire()` and `browser.killProcesses()` for code that replaces one browser with another. `close()` lets the browser relaunch the next time something uses it; `retire()` closes it for good, so a tool call still holding the old browser gets an error instead of starting an untracked Chrome. `killProcesses()` force-kills the browser's processes when `close()` fails or hangs. It works on macOS and Linux; it cannot force-kill Chrome on Windows. ```ts // Replace oldBrowser with newBrowser without leaving its Chrome running. const retired = oldBrowser.retire(); const timedOut = await Promise.race([ retired.then(() => false), new Promise(resolve => setTimeout(() => resolve(true), 5000)), ]).catch(() => true); if (timedOut) oldBrowser.killProcesses(); ``` A browser that finishes closing after another browser took its profile no longer deletes the new browser's lock. A lock whose holder is a live local process that has not started listening yet (Chrome creates the lock first) is no longer removed as stale; Mastra waits up to 3 seconds for it. - Added schemas for custom dashboard definitions. These are schemas only: `@mastra/core` adds no storage methods for dashboards, and no store persists them. ([#28714](https://github.com/mastra-ai/mastra/pull/28714)) **Layout** - A definition lays out rows of widgets on a 12-column grid. - Each widget stores its own trace aggregate, span aggregate, metrics, or scores queries. Queries have no time range; the page supplies it. - A view can set its own `description`, `scope`, and `drilldown`, overriding the widget's. A drilldown has an `action` target and a `click` target. **Queries** - A query can have an optional `id`. It starts with a letter, uses only letters, digits, `_` and `-`, and is unique within its view. Column ids are also unique within a view. - Set `compare: 'previousWindow'` on any query to compare with the previous window. - A scores query without `scorerId` covers every scorer, with one series per scorer. **Filters** - `defaults.filters` and drilldown `filters` use the same filter node schema as saved views (`filterNodeSchema`). - Filter groups can nest at most 12 levels deep (`DASHBOARD_MAX_FILTER_DEPTH`). - The page's filters are combined with each query's own. `where` clauses are ANDed. If both set the same metrics or scores filter field, the values must agree (arrays intersect). A contradiction returns no data. **Validation** - `dashboardDefinitionSchema` validates a whole definition. - `dashboardDefinitionEnvelopeSchema` checks only what a service storing dashboards needs: a known `schemaVersion`, at most 20 widgets, 1 to 8 queries per view, at most 64 KiB serialized, unique widget ids, and `kind` and `views` on every widget. New widget kinds can be stored without upgrading that service. **New widget kinds** - Widget `kind` accepts any non-empty string. View `options` are passed through for each kind to validate, so new widget kinds need no `@mastra/core` upgrade. - `DASHBOARD_WIDGET_KINDS` lists the built-in kinds: `kpis`, `table`, `timeseries` and `scores`. **Versions** - Adding an optional field keeps `schemaVersion` the same. - A breaking change bumps `schemaVersion` and adds an upgrade step to `migrateDashboardDefinition`. That function brings any stored version up to the current one before parsing. **Browser apps** - Import the dashboard schemas from `@mastra/core/storage/dashboards`. It loads no Node built-ins and uses no Node globals such as `process` or `Buffer`. - `@mastra/core/storage` reaches agent code that reads `process` when it loads, so it fails in the browser. ```ts import { dashboardDefinitionSchema } from '@mastra/core/storage/dashboards'; const result = dashboardDefinitionSchema.safeParse({ schemaVersion: 1, rows: [ { widgets: [ { id: 'agents', span: 6, kind: 'table', views: [ { queries: [ { source: 'traceAggregate', measures: ['count', 'errorRate'], groupBy: ['rootEntityName'], }, ], }, ], }, ], }, ], }); ``` - Added `outcome` and `stopReason` to advanced span and trace queries. You can now filter, group, and discover values by how a span ended and why, separately from `status`. Span and trace results also return `stopReasonContext`. ([#28884](https://github.com/mastra-ai/mastra/pull/28884)) At trace scope the fields read the root span. Use `spans.some` to find traces where any span hit a limit: ```ts const { traces } = await client.queryTraces({ timeRange, where: { spans: { some: { op: 'eq', left: { path: 'outcome' }, right: { literal: 'limit' } } } }, }); ``` A missing outcome means "not recorded", never success. Stores opt in through the new `span-query-outcome-fields` and `trace-query-outcome-fields` features. - Trace, span and thread queries can now return and filter by `timeToFirstTokenMs`, the time from the start of the model call to its first streamed chunk. Trace and span queries can also sort by it. Trace rows use the root span's value, and thread rows use the first trace's root agent. Missing values sort last. Stores that support it declare the `trace-query-time-to-first-token` and `trace-query-time-to-first-token-sort` features. ([#28699](https://github.com/mastra-ai/mastra/pull/28699)) ```typescript const page = await storage.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, orderBy: [{ field: 'timeToFirstTokenMs', direction: 'desc' }] }), ), ); ``` Durable agents now carry the time to first token of the model call that streamed the run's first chunk across steps, so their generation and agent run spans report it too. A model call that fails before a fallback model, or a durable agent's retry, takes over now ends its model inference span with that error. - Added support for background tasks owned by code other than agents, such as MCP servers. ([#28425](https://github.com/mastra-ai/mastra/pull/28425)) **Per-task retention.** Tasks accept optional `expiresAt` and `metadata` fields. When the `cleanup` option is set, cleanup deletes a finished task once its own `expiresAt` passes instead of using `completedTtlMs` or `failedTtlMs`. Pending, running, and suspended tasks are never deleted. `metadata` is stored with the task and not read by the manager. ```ts await mastra.backgroundTaskManager.enqueue({ agentId: 'mcp-server:ops', toolName: 'mcp-server:ops:deploy', toolCallId, runId, args, expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), metadata: { ttlMs: 86_400_000 }, }); ``` **Per-owner concurrency.** `perOwnerConcurrency` overrides `perAgentConcurrency` for specific owners, keyed by `agentId`. Each value must be a number of at least 1. These tasks still count toward `globalConcurrency`. ```ts new Mastra({ backgroundTasks: { enabled: true, globalConcurrency: 60, perOwnerConcurrency: { 'mcp-server:ops': 50 } }, }); ``` **Queued resume.** `resume()` still throws when no concurrency slot is free. Pass `{ onNoSlot: 'queue' }` to keep the task suspended and resume it once a slot frees up. The queue is held in memory by the calling process, so a queued resume is lost if that process exits; the task stays suspended and can be resumed again. A queued resume is dropped if the task is cancelled or resumed elsewhere first. ```ts await mastra.backgroundTaskManager.resume(taskId, resumeData, { onNoSlot: 'queue' }); ``` Storage adapters need a matching release to store `expiresAt` and `metadata`. With an older adapter, cleanup detects that expiry is unsupported and keeps using only the global retention settings, as before. - Added the experimental `getTasksInfo()` and `getTasksInspector()` methods to MCP servers, so tools like Studio can see which tools run as MCP tasks and list, cancel or answer a server's tasks. ([#28629](https://github.com/mastra-ai/mastra/pull/28629)) ```ts const inspector = server.getTasksInspector(); const { tasks } = (await inspector?.list({ status: 'input_required' })) ?? { tasks: [] }; if (tasks[0]) await inspector?.cancel(tasks[0].taskKey); ``` ### Patch Changes - dependencies updates: ([#28223](https://github.com/mastra-ai/mastra/pull/28223)) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.63` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.63) (from `npm:@ai-sdk/provider-utils@5.0.62`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.30` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.30) (from `npm:@ai-sdk/provider@4.0.29`, in `dependencies`) - dependencies updates: ([#28928](https://github.com/mastra-ai/mastra/pull/28928)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.62` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.62) (from `npm:@ai-sdk/provider-utils@4.0.61`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.69` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.69) (from `npm:@ai-sdk/provider-utils@5.0.63`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.34` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.34) (from `npm:@ai-sdk/provider@4.0.30`, in `dependencies`) - Fixed resuming suspended agent runs across server instances when a process retains an earlier run segment. ([#29061](https://github.com/mastra-ai/mastra/pull/29061)) - Update provider registry and model documentation with latest models and providers ([`9f03685`](https://github.com/mastra-ai/mastra/commit/9f0368570677b9c36285e4512d0fc3f136594f74)) - Fixed recovered durable agents skipping configured completion checks ([#29037](https://github.com/mastra-ai/mastra/pull/29037)) - Fixed background tasks staying locked to a worker after it shut down gracefully. A worker that stops with `mastra.shutdown()` now releases the tasks it was running right away, so another worker can pick them up immediately instead of waiting for the lease to expire. ([#28719](https://github.com/mastra-ai/mastra/pull/28719)) - Fixed provider-reported cost on model generation spans when an agent run pauses on a tool approval or `suspend()` and then resumes. The paused part of the run now records its own cost, and the resumed part no longer repeats the cost of the steps that ran before the pause. ([#28955](https://github.com/mastra-ai/mastra/pull/28955)) - Fixed `@mastra/core/loop/server` failing to load with a module-not-found error. The `createRunCommandTool` export is now importable at runtime. Also removed the `@mastra/core/network/vNext` and `@mastra/core/telemetry/otel-vendor` subpaths, which pointed to files that no longer ship. Fixes [#29014](https://github.com/mastra-ai/mastra/issues/29014). ([#29056](https://github.com/mastra-ai/mastra/pull/29056)) - `PROCESSOR_WORKFLOW_FAILED` now keeps the underlying error as its `cause`. Previously the failure text included the error message but dropped the error object, so callers could not inspect the original stack, error type or provider fields; the failed workflow's error (or the failed step's error) is now attached. ([#28831](https://github.com/mastra-ai/mastra/pull/28831)) - Fixed `POST /agents/:agentId/runs/:runId/abort` answering `{ aborted: false }` when it reached a server that does not own the run. The run stopped, but the client kept retrying for 10 seconds and then reported that cancellation was not acknowledged. The route now waits up to 2 seconds for the server executing the run to confirm the abort, and answers `{ aborted: true }` once it does. A run whose owner has gone still answers `{ aborted: false }` after that wait. ([#28522](https://github.com/mastra-ai/mastra/pull/28522)) - Flush observability exporters before closing storage on Mastra shutdown, so final spans persist when using DuckDB. Release DuckDB file locks on shutdown, including composed observability storage, concurrent operations, and public connection cleanup. Prevent closed DuckDB connections from reopening, and avoid leaving a connection open while checking Mastra Code's local observability store. ([#28656](https://github.com/mastra-ai/mastra/pull/28656)) - Fixed `output.request` and `onFinish.request` for durable and evented agents. Fresh, uninterrupted `stream()` and `generate()` runs that finish normally in the same process now expose the final provider request body. If a local run fails on a provider error, `output.request` reports the failing attempt's request, when available, rather than an earlier step's request. Provider request bodies are kept out of workflow events and snapshots. ([#28674](https://github.com/mastra-ai/mastra/pull/28674)) **Unchanged cases** - Remote workers - Resumed, recovered, or observed runs - Suspended, aborted, or tripwire runs - Historical request metadata in `output.steps` - Fixed agents treating `observationalMemory: { enabled: false }` as if observational memory were on. This now behaves the same as having no observational memory configured, on every engine: ([#28922](https://github.com/mastra-ai/mastra/pull/28922)) - `agent.stream()` and `agent.generate()` with `savePerStep` save each step before the next one runs. - Durable and evented agents title the thread, save each step with `savePerStep`, and save the turn when the run finishes. - Fixed `resumeStream()` on evented workflows rejecting its `result` with "promise 'result' was not resolved or rejected when stream finished". The stream now stays open until the resumed run's result is set. ([#28871](https://github.com/mastra-ai/mastra/pull/28871)) - Added an `expectedSuspendedAt` condition to background task updates. A write fenced on a suspension time is only applied while the task is still in that same suspension, so an answer meant for an earlier suspension can no longer overwrite a later one. ([#28590](https://github.com/mastra-ai/mastra/pull/28590)) ```ts const task = await storage.getTask(taskId); const applied = await storage.updateTask( taskId, { status: 'running', suspendPayload: undefined }, { expectedStatus: 'suspended', expectedSuspendedAt: task?.suspendedAt ?? null }, ); // `applied` is false when the task suspended again in the meantime. ``` Fixed a delayed background task resume answering the wrong suspension. If a task was resumed another way and suspended again before a resume request was processed, that request used to resume the new suspension with the old data. It is now dropped. - Added a `pinToWorker` option to `backgroundTaskManager.enqueue()` for tasks whose work is already running in the current process. Other processes no longer pick up the queued task and run it a second time while that process is alive. If the process dies, the task uses a retry or fails, so a task with `maxRetries: 0` never runs twice. Running processes check for these tasks every `leaseDurationMs`, so they're recovered without a restart. Pinned work should be idempotent: a lapsed lease doesn't prove the original process stopped. ([#28507](https://github.com/mastra-ai/mastra/pull/28507)) ```ts await mastra.backgroundTaskManager?.enqueue(payload, { executor }, { pinToWorker: true }); ``` - Fixed nested evented workflows running without the caller's `workflows:execute` permission on the child workflow. When a fine-grained authorization (FGA) provider is configured, non-internal nested workflows now require an authenticated user with that permission, or a trusted actor with tenant scope subject to the provider's actor policy. Internal child workflows remain exempt from this check. ([#28669](https://github.com/mastra-ai/mastra/pull/28669)) Schedule-triggered runs carry no user identity or trusted actor by default. Scheduled workflows containing non-internal nested children therefore now fail closed when FGA is configured, matching the default engine's behavior. - Added the internal support `@mastra/memory` needs to delete a thread's event stream when the thread is deleted. ([#28968](https://github.com/mastra-ai/mastra/pull/28968)) - Fixed durable runs stopping when a lease renewal queued behind other storage writes still belongs to the same execution. The run now waits up to one lease TTL past its deadline for the renewal result, then stops as before if the renewal has still not returned. ([#28936](https://github.com/mastra-ai/mastra/pull/28936)) - Fixed trace trees for agents whose model call is rejected and handled by an error processor (for example a retry on a rate limit). The "request error processor" spans now sit inside the model step whose call failed, next to that step's other processor spans, instead of showing up as a flat list under the agent run. ([#28745](https://github.com/mastra-ai/mastra/pull/28745)) - Inngest durable agents no longer block memory writes after `modelSettings.timeout.totalMs` expires. When a tool is still running at the deadline, its step is now saved to memory with the tool call. A tool that outlives the abort grace period is recorded as aborted rather than with its late return value. The run still stops with one `MastraTimeoutError` at the deadline in the process that started it; steps already running on a separate Inngest worker are not signalled. The user's message is still saved before the run starts. Durable and evented agents are unchanged: they release their execution claim at the deadline, so a late step, or a user message whose save is still in flight, is not saved. ([#28926](https://github.com/mastra-ai/mastra/pull/28926)) Errors with the id `DURABLE_AGENT_EXECUTION_TIMED_OUT` are no longer thrown, because Inngest durable agents no longer refuse memory writes after the deadline. Remove any checks for that id. The deadline is still reported as a `MastraTimeoutError`. ```ts // Before if (error.id === 'DURABLE_AGENT_EXECUTION_TIMED_OUT') { // handle the deadline } // After import { isMastraTimeoutError } from '@mastra/core/loop'; if (isMastraTimeoutError(error)) { // handle the deadline } ``` - Fixed thread-message listeners accumulating while durable runs wait for approval. ([#29046](https://github.com/mastra-ai/mastra/pull/29046)) - Fixed tracing for runs that continue in another process. A durable agent run resumed after a restart or on another server now stays on its original trace instead of opening a second trace that never ends. Evented workflow runs restarted after a crash, or time-travelled, now trace their steps, so scores of a recovered agent run link to the run's trace. ([#28832](https://github.com/mastra-ai/mastra/pull/28832)) - Fixed supervisor agents losing track of which subagent tool paused. When a subagent's tool suspends or needs approval, the supervisor's `tool-call-suspended` and `tool-call-approval` chunks now include `delegatedToolCallId` and `delegatedToolName`. UIs can label each pause with the subagent tool that caused it and tell apart several pauses from the same delegation, including after reloading the conversation from memory. With nested delegation, the fields name the deepest tool call that paused. Keep using `toolCallId` to approve, decline, or resume. Fixes #28583. ([#28991](https://github.com/mastra-ai/mastra/pull/28991)) ```ts for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-suspended' || chunk.type === 'tool-call-approval') { chunk.payload.toolCallId; // the supervisor's call to the subagent, use it to resume chunk.payload.delegatedToolCallId; // the subagent's own tool call that paused chunk.payload.delegatedToolName; // e.g. "ask" } } ``` - Improved `Mastra` deployer compatibility: the `deployer` option now accepts any object that implements the `IDeployer` interface (exported from `@mastra/core/deployer`), not only subclasses of `MastraDeployer`. `getDeployer()` now returns `IDeployer | undefined`. ([#28428](https://github.com/mastra-ai/mastra/pull/28428)) This keeps deployers from `@mastra/deployer` (such as `VercelDeployer`) assignable to `Mastra` even when they are built on a separate copy of the base classes. ```ts // Before: deployer had to be typed as MastraDeployer const deployer: MastraDeployer | undefined = mastra.getDeployer(); // After import type { IDeployer } from '@mastra/core/deployer'; const deployer: IDeployer | undefined = mastra.getDeployer(); ``` - Retry policies that match bad-request errors no longer retry a 400 that rejects the model or a parameter, such as `model_not_found`. These requests fail the same way on every retry. ([#28786](https://github.com/mastra-ai/mastra/pull/28786)) - Fixed thread stream topics using far more memory than needed for agents without memory, such as the observational memory observer and reflector. The output of their finished runs, successful or failed, stayed on the topic until the stream length cap pushed it out. It is now removed 30 seconds after the run ends. Each run's final outcome stays on the topic, so subscribers that reconnect can still see how it ended. ([#28746](https://github.com/mastra-ai/mastra/pull/28746)) - Fixed channel tool approval actions on tool calls whose ID contains a colon. `tool_approve:` and `tool_deny:` now take everything after the first colon as the tool call ID, so a click no longer resolves to the wrong pending approval. ([#28728](https://github.com/mastra-ai/mastra/pull/28728)) - Inngest durable agents now save the accepted user message before the run starts, as the other durable engines do. A run that times out or fails keeps the user's turn in history, even when the deadline fires before that save finishes. ([#28747](https://github.com/mastra-ai/mastra/pull/28747)) - Clarify that `FileStat.path` is a path within the filesystem namespace. ([#28819](https://github.com/mastra-ai/mastra/pull/28819)) - Fixed `subscribeToThread({ withInitialHistory: true })` hanging or rejecting when the lease backend (for example Redis) is slow or unavailable, which also kept `AgentController` sessions from opening. The subscription now opens anyway. If the lease read doesn't answer in time, `activeRunId` in the `thread-history` chunk comes from this process's own view of the thread, and a warning is logged. A run that is still live in another process keeps streaming to the subscriber once the backend answers again. Fixes #28750. ([#28924](https://github.com/mastra-ai/mastra/pull/28924)) - Fixed provider-executed tool results that stream partial values, such as OpenAI `image_generation` partial images with AI SDK v6. Partial results now keep `preliminary: true` on `tool-result` chunks and on the `tool-output-available` UI chunks sent to `useChat`, so they stay visible in the stream. Tool-result processing, `toolResults`, saved messages and tracing spans now use only the final result. ([#28903](https://github.com/mastra-ai/mastra/pull/28903)) - Fixed signals and notifications that arrive while a run is finishing. If the run ended before the signal or notification was routed, the agent woke the thread without the session's request context, so a model resolved from that context failed with "No model available". Both now start their run with the session's full stream options. In Mastra Code's Factory, this failure repeated on every retry of a rule-driven run until the process restarted. ([#29015](https://github.com/mastra-ai/mastra/pull/29015)) - Fixed AgentController sessions getting stuck as "running" after a failed tool answer. If answering a suspended tool call or a tool approval fails before the run restarts, the session now goes back to idle. The tool call stays waiting, and you can retry it. Fixes #26870. ([#29005](https://github.com/mastra-ai/mastra/pull/29005)) - Fixed workspace sandbox tools (`execute_command`, `get_process_output`, `kill_process`) sending an empty tool result to the model when a `beforeToolCall` hook blocked the call with `{ error: reason }`. The model now receives the reason as an error result, so it no longer assumes a blocked command ran. Mastra Code's `PreToolUse` hooks block tools this way. ([#28919](https://github.com/mastra-ai/mastra/pull/28919)) - Fixed channel agents being unable to read text attachments such as `.txt`, CSV, JSON, YAML or log files shared in Slack. Previously the agent only received the file's private Slack URL, which can't be opened without Slack credentials. The file is now downloaded through the adapter's authenticated fetch and its contents (up to 128 KB) are given to the agent as text. Fixes #28854. ([#28901](https://github.com/mastra-ai/mastra/pull/28901)) - Studio's model provider picker now lists Mastra Gateway even when `MASTRA_GATEWAY_API_KEY` isn't set. Without the key it shows as disconnected, the same way other providers without an API key do. Existing provider policies such as `AUTO_BLOCK_EXTERNAL_PROVIDERS` and model policy still decide what Studio shows. ([#28824](https://github.com/mastra-ai/mastra/pull/28824)) - Publish tool suspension and approval events only after the parked tool call is saved to memory, so a client that reacts to the event — resuming it, or aborting the run — finds the tool call already persisted instead of racing the save still in flight. When memory is writable and that save fails, the suspension or approval event is withheld, the run remains suspended, and the stream attempts to report the save failure, scoped to the tool call that failed so other tool calls already suspended in the same run stay resumable; read-only memory, and runs without a save queue or thread, behave as before. An approval or error chunk that an output processor rejects is now also written straight to the stream as a fallback, instead of only being logged. ([#28839](https://github.com/mastra-ai/mastra/pull/28839)) - Limit memory used when publishing stalls. Durable agent history keeps less data for each tool result. Tool calls and results are still kept. ([#28652](https://github.com/mastra-ai/mastra/pull/28652)) - Added the saved view schemas (`savedViewStateSchema`, `savedViewStateEnvelopeSchema`, `SAVED_VIEW_STATE_MAX_BYTES` and related types) to the browser-safe `@mastra/core/storage/dashboards` entry, so browser apps can validate a saved view before sending it. ([#29001](https://github.com/mastra-ai/mastra/pull/29001)) ```ts import { savedViewStateSchema } from '@mastra/core/storage/dashboards'; const result = savedViewStateSchema.safeParse({ schemaVersion: 1, page: 'traces', filters: [{ fieldId: 'status', value: 'error' }], }); if (!result.success) console.error(result.error.issues); ``` - Fixed agent runs that hit `modelSettings.timeout.totalMs` losing the user's message from memory. The default and filesystem engines now save the accepted user message when the deadline is reached, as the durable and evented engines already did. Completed steps are still saved only when `savePerStep` is on, unfinished steps are never saved, and read-only memory is respected. ([#28668](https://github.com/mastra-ai/mastra/pull/28668)) - Updated the AI SDK v5 compatibility dependencies so they resolve @ai-sdk/provider-utils 3.0.42, which blocks `.internal` hostnames in download URL validation and encodes provider-returned identifiers in request paths. This patch-level update replaces 3.0.41 (Aikido findings AIKIDO-2026-10990 and AIKIDO-2026-10981): ai 5.0.282, @ai-sdk/gateway 2.0.173, anthropic 2.0.110, google 2.0.106, groq 2.0.59, openai 2.0.137, openai-compatible 1.0.59, and xai 2.0.100. ([#28716](https://github.com/mastra-ai/mastra/pull/28716)) - Fixed `LocalSandbox.executeCommand(command, args)` on native Windows. Arguments are quoted for a POSIX shell, but commands ran through `cmd.exe`, so paths like `C:\Users\First Last\repo` were split or kept their quote characters, and tools like `mkdir -p` and `find` resolved to Windows builtins. Argv commands, including `sh -c` scripts, now run through Git Bash, found via `MASTRA_WINDOWS_SHELL`, next to `git.exe` on `PATH`, or at `%ProgramFiles%\Git\bin\bash.exe`. Fixes #28866. ([#28900](https://github.com/mastra-ai/mastra/pull/28900)) - Workspace tools now save their `data-workspace-metadata` chunks to the current response message. Previously, after a task signal rotated the response message id, the chunk was saved under the previous id and replaced that assistant message, losing its reasoning, text, and tool calls from stored history. ([#28853](https://github.com/mastra-ai/mastra/pull/28853)) - Workspace file-modification protection is now off by default and only applies when `requireReadBeforeWrite` is enabled. Previously `write_file`, `edit_file` and `apply_patch` always rejected a file that changed on disk after it was read, with `StaleFileError`, even when `requireReadBeforeWrite` was unset or `false`. Now, with the option unset or `false`, a write succeeds even if the file was changed by another process (`sed -i`, formatters, git) after a read, and `write_file` can overwrite that change. `edit_file` still fails if the text it replaces is gone, and `apply_patch` still rejects a file that changed between its own validation and commit. Set `requireReadBeforeWrite: true` to keep the protection; its behavior is unchanged. ([#28935](https://github.com/mastra-ai/mastra/pull/28935)) To keep the protection: ```ts new Workspace({ filesystem, tools: { [WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE]: { requireReadBeforeWrite: true }, [WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: { requireReadBeforeWrite: true }, }, }); ``` - `write_file` now names the real file location when the filesystem maps an absolute path somewhere else. In a Factory sandbox, writing `/tmp/x` stores the file at `/tmp/x`; the tool result now says `Wrote 5 bytes to /tmp/x (resolved to /tmp/x)` so follow-up shell commands look in the right place. Fixes #28862. ([#28902](https://github.com/mastra-ai/mastra/pull/28902)) ## 1.81.0-alpha.2 ### Minor Changes - Added schemas for saved observability list views. Use `savedViewStateSchema` to validate a saved traces, spans, threads or logs view: its filters, time range, sort, columns and page options. A service that only stores views can use `savedViewStateEnvelopeSchema` instead. It checks the version, the page and the 16 KiB size limit. Dashboards can reuse the filter shape through `filterNodeSchema`. It accepts any operator id, so new operators don't need a schema change. ([#28715](https://github.com/mastra-ai/mastra/pull/28715)) ```ts import { savedViewStateSchema } from '@mastra/core/storage'; savedViewStateSchema.parse({ schemaVersion: 1, page: 'traces', filters: [{ fieldId: 'status', value: 'error' }], dateRange: { preset: 'last-24h' }, sort: { field: 'startedAt', direction: 'desc' }, }); ``` - Channel agents with a workspace can now open binary file attachments, such as ZIP and DOCX files, that need an authenticated download (for example Slack files). The file is downloaded through the adapter and saved under `channel-attachments/` in the agent's workspace. The agent gets the workspace path instead of a private URL it can't open. ([#28913](https://github.com/mastra-ai/mastra/pull/28913)) This covers attachments that `inlineMedia` doesn't send to the model as file parts. PDFs are sent inline by default. When the workspace uses `mounts`, the file is saved in the first writable mount other than `/`. Saving follows the workspace's write-file tool policy: if `mastra_workspace_write_file` is disabled or needs approval, the file isn't saved. Adapters whose links don't need a token also get the workspace path. For example, a Discord agent with a writable workspace now gets the workspace path instead of the CDN URL, which expires. The agent still gets the URL for files over 10 MiB, failed downloads, read-only workspaces, workspaces where writes are disabled or need approval, and agents without a workspace. `channel-attachments/` is capped at 100 MiB per workspace so channel participants can't fill the disk. Once a file would go over the cap, the agent gets the URL instead and nothing is written. Change the cap with `maxAttachmentStorageBytes`: ```ts new Agent({ // ... channels: { adapters: { slack }, maxAttachmentStorageBytes: 500 * 1024 * 1024 }, }); ``` - Added an optional `inputAccepted` field to `OutputResult`, the `result` argument of `processOutputResult`. It is set only when `finishReason` is `'error'`, and is `true` when the run accepted the turn's input before it failed. `MessageHistory` uses it to keep the user message of a failed turn that produced no output, so an accepted turn is not lost from history. Failed turns without the flag are still dropped, as before. ([#28988](https://github.com/mastra-ai/mastra/pull/28988)) ```ts import type { Processor } from '@mastra/core/processors'; const processor: Processor = { id: 'accepted-turns', async processOutputResult({ messages, result }) { if (result.finishReason === 'error' && result.inputAccepted) { // The run failed after accepting this turn's input } return messages; }, }; ``` - Fixed resumed runs losing the link to the run they continue. When you resume an agent, durable agent or workflow run, its span now sets `resumedFromSpanId` to the span id of the first run. This stays true after several resumes and after a durable agent recovers. A resumed run also keeps the external parent span of the run it continues. ([#28849](https://github.com/mastra-ai/mastra/pull/28849)) A resume that passes only `tracingOptions.parentSpanId` now stays in the original trace under the first run, and uses that id as its external parent. Before, it started a new trace. ```ts await agent.resumeGenerate({ approved: true }, { runId, toolCallId, tracingOptions: { parentSpanId: callerSpanId } }); // The resumed run stays in the first run's trace. // resumedFromSpanId is the first run's span id, externalParentSpanId is callerSpanId. ``` The `resumedFromSpanId` option of `getOrCreateSpan` is deprecated. Use `resumeFrom` with the saved tracing context instead. - Span aggregates can now group by `model` and `provider` and count their distinct values with `countDistinct.model` and `countDistinct.provider`. Stores opt in with the new `span-aggregate-model-dimensions` feature. ([#28908](https://github.com/mastra-ai/mastra/pull/28908)) ```typescript const { rows } = await mastraClient.aggregateSpans({ timeRange, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'model_generation' } }, groupBy: ['provider', 'model'], measures: ['count', 'tokens.total.sum', 'cost.sum'], }); ``` - Span queries and span aggregates now accept `model` and `provider` filters. Stores opt in with the new `span-query-model-fields` feature. ([#28859](https://github.com/mastra-ai/mastra/pull/28859)) ```typescript const { spans } = await mastraClient.querySpans({ timeRange, where: { op: 'eq', left: { path: 'provider' }, right: { literal: 'anthropic' } }, }); ``` - Added schemas for custom dashboard definitions. These are schemas only: `@mastra/core` adds no storage methods for dashboards, and no store persists them. ([#28714](https://github.com/mastra-ai/mastra/pull/28714)) **Layout** - A definition lays out rows of widgets on a 12-column grid. - Each widget stores its own trace aggregate, span aggregate, metrics, or scores queries. Queries have no time range; the page supplies it. - A view can set its own `description`, `scope`, and `drilldown`, overriding the widget's. A drilldown has an `action` target and a `click` target. **Queries** - A query can have an optional `id`. It starts with a letter, uses only letters, digits, `_` and `-`, and is unique within its view. Column ids are also unique within a view. - Set `compare: 'previousWindow'` on any query to compare with the previous window. - A scores query without `scorerId` covers every scorer, with one series per scorer. **Filters** - `defaults.filters` and drilldown `filters` use the same filter node schema as saved views (`filterNodeSchema`). - Filter groups can nest at most 12 levels deep (`DASHBOARD_MAX_FILTER_DEPTH`). - The page's filters are combined with each query's own. `where` clauses are ANDed. If both set the same metrics or scores filter field, the values must agree (arrays intersect). A contradiction returns no data. **Validation** - `dashboardDefinitionSchema` validates a whole definition. - `dashboardDefinitionEnvelopeSchema` checks only what a service storing dashboards needs: a known `schemaVersion`, at most 20 widgets, 1 to 8 queries per view, at most 64 KiB serialized, unique widget ids, and `kind` and `views` on every widget. New widget kinds can be stored without upgrading that service. **New widget kinds** - Widget `kind` accepts any non-empty string. View `options` are passed through for each kind to validate, so new widget kinds need no `@mastra/core` upgrade. - `DASHBOARD_WIDGET_KINDS` lists the built-in kinds: `kpis`, `table`, `timeseries` and `scores`. **Versions** - Adding an optional field keeps `schemaVersion` the same. - A breaking change bumps `schemaVersion` and adds an upgrade step to `migrateDashboardDefinition`. That function brings any stored version up to the current one before parsing. **Browser apps** - Import the dashboard schemas from `@mastra/core/storage/dashboards`. It loads no Node built-ins and uses no Node globals such as `process` or `Buffer`. - `@mastra/core/storage` reaches agent code that reads `process` when it loads, so it fails in the browser. ```ts import { dashboardDefinitionSchema } from '@mastra/core/storage/dashboards'; const result = dashboardDefinitionSchema.safeParse({ schemaVersion: 1, rows: [ { widgets: [ { id: 'agents', span: 6, kind: 'table', views: [ { queries: [ { source: 'traceAggregate', measures: ['count', 'errorRate'], groupBy: ['rootEntityName'], }, ], }, ], }, ], }, ], }); ``` - Added `outcome` and `stopReason` to advanced span and trace queries. You can now filter, group, and discover values by how a span ended and why, separately from `status`. Span and trace results also return `stopReasonContext`. ([#28884](https://github.com/mastra-ai/mastra/pull/28884)) At trace scope the fields read the root span. Use `spans.some` to find traces where any span hit a limit: ```ts const { traces } = await client.queryTraces({ timeRange, where: { spans: { some: { op: 'eq', left: { path: 'outcome' }, right: { literal: 'limit' } } } }, }); ``` A missing outcome means "not recorded", never success. Stores opt in through the new `span-query-outcome-fields` and `trace-query-outcome-fields` features. - Added support for background tasks owned by code other than agents, such as MCP servers. ([#28425](https://github.com/mastra-ai/mastra/pull/28425)) **Per-task retention.** Tasks accept optional `expiresAt` and `metadata` fields. When the `cleanup` option is set, cleanup deletes a finished task once its own `expiresAt` passes instead of using `completedTtlMs` or `failedTtlMs`. Pending, running, and suspended tasks are never deleted. `metadata` is stored with the task and not read by the manager. ```ts await mastra.backgroundTaskManager.enqueue({ agentId: 'mcp-server:ops', toolName: 'mcp-server:ops:deploy', toolCallId, runId, args, expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000), metadata: { ttlMs: 86_400_000 }, }); ``` **Per-owner concurrency.** `perOwnerConcurrency` overrides `perAgentConcurrency` for specific owners, keyed by `agentId`. Each value must be a number of at least 1. These tasks still count toward `globalConcurrency`. ```ts new Mastra({ backgroundTasks: { enabled: true, globalConcurrency: 60, perOwnerConcurrency: { 'mcp-server:ops': 50 } }, }); ``` **Queued resume.** `resume()` still throws when no concurrency slot is free. Pass `{ onNoSlot: 'queue' }` to keep the task suspended and resume it once a slot frees up. The queue is held in memory by the calling process, so a queued resume is lost if that process exits; the task stays suspended and can be resumed again. A queued resume is dropped if the task is cancelled or resumed elsewhere first. ```ts await mastra.backgroundTaskManager.resume(taskId, resumeData, { onNoSlot: 'queue' }); ``` Storage adapters need a matching release to store `expiresAt` and `metadata`. With an older adapter, cleanup detects that expiry is unsupported and keeps using only the global retention settings, as before. - Added the experimental `getTasksInfo()` and `getTasksInspector()` methods to MCP servers, so tools like Studio can see which tools run as MCP tasks and list, cancel or answer a server's tasks. ([#28629](https://github.com/mastra-ai/mastra/pull/28629)) ```ts const inspector = server.getTasksInspector(); const { tasks } = (await inspector?.list({ status: 'input_required' })) ?? { tasks: [] }; if (tasks[0]) await inspector?.cancel(tasks[0].taskKey); ``` ### Patch Changes - dependencies updates: ([#28928](https://github.com/mastra-ai/mastra/pull/28928)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.62` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.62) (from `npm:@ai-sdk/provider-utils@4.0.61`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.69` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.69) (from `npm:@ai-sdk/provider-utils@5.0.63`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.34` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.34) (from `npm:@ai-sdk/provider@4.0.30`, in `dependencies`) - Fixed recovered durable agents skipping configured completion checks ([#29037](https://github.com/mastra-ai/mastra/pull/29037)) - Fixed provider-reported cost on model generation spans when an agent run pauses on a tool approval or `suspend()` and then resumes. The paused part of the run now records its own cost, and the resumed part no longer repeats the cost of the steps that ran before the pause. ([#28955](https://github.com/mastra-ai/mastra/pull/28955)) - Fixed `@mastra/core/loop/server` failing to load with a module-not-found error. The `createRunCommandTool` export is now importable at runtime. Also removed the `@mastra/core/network/vNext` and `@mastra/core/telemetry/otel-vendor` subpaths, which pointed to files that no longer ship. Fixes [#29014](https://github.com/mastra-ai/mastra/issues/29014). ([#29056](https://github.com/mastra-ai/mastra/pull/29056)) - Fixed `POST /agents/:agentId/runs/:runId/abort` answering `{ aborted: false }` when it reached a server that does not own the run. The run stopped, but the client kept retrying for 10 seconds and then reported that cancellation was not acknowledged. The route now waits up to 2 seconds for the server executing the run to confirm the abort, and answers `{ aborted: true }` once it does. A run whose owner has gone still answers `{ aborted: false }` after that wait. ([#28522](https://github.com/mastra-ai/mastra/pull/28522)) - Fixed agents treating `observationalMemory: { enabled: false }` as if observational memory were on. This now behaves the same as having no observational memory configured, on every engine: ([#28922](https://github.com/mastra-ai/mastra/pull/28922)) - `agent.stream()` and `agent.generate()` with `savePerStep` save each step before the next one runs. - Durable and evented agents title the thread, save each step with `savePerStep`, and save the turn when the run finishes. - Added an `expectedSuspendedAt` condition to background task updates. A write fenced on a suspension time is only applied while the task is still in that same suspension, so an answer meant for an earlier suspension can no longer overwrite a later one. ([#28590](https://github.com/mastra-ai/mastra/pull/28590)) ```ts const task = await storage.getTask(taskId); const applied = await storage.updateTask( taskId, { status: 'running', suspendPayload: undefined }, { expectedStatus: 'suspended', expectedSuspendedAt: task?.suspendedAt ?? null }, ); // `applied` is false when the task suspended again in the meantime. ``` Fixed a delayed background task resume answering the wrong suspension. If a task was resumed another way and suspended again before a resume request was processed, that request used to resume the new suspension with the old data. It is now dropped. - Added a `pinToWorker` option to `backgroundTaskManager.enqueue()` for tasks whose work is already running in the current process. Other processes no longer pick up the queued task and run it a second time while that process is alive. If the process dies, the task uses a retry or fails, so a task with `maxRetries: 0` never runs twice. Running processes check for these tasks every `leaseDurationMs`, so they're recovered without a restart. Pinned work should be idempotent: a lapsed lease doesn't prove the original process stopped. ([#28507](https://github.com/mastra-ai/mastra/pull/28507)) ```ts await mastra.backgroundTaskManager?.enqueue(payload, { executor }, { pinToWorker: true }); ``` - Added the internal support `@mastra/memory` needs to delete a thread's event stream when the thread is deleted. ([#28968](https://github.com/mastra-ai/mastra/pull/28968)) - Fixed durable runs stopping when a lease renewal queued behind other storage writes still belongs to the same execution. The run now waits up to one lease TTL past its deadline for the renewal result, then stops as before if the renewal has still not returned. ([#28936](https://github.com/mastra-ai/mastra/pull/28936)) - Inngest durable agents no longer block memory writes after `modelSettings.timeout.totalMs` expires. When a tool is still running at the deadline, its step is now saved to memory with the tool call. A tool that outlives the abort grace period is recorded as aborted rather than with its late return value. The run still stops with one `MastraTimeoutError` at the deadline in the process that started it; steps already running on a separate Inngest worker are not signalled. The user's message is still saved before the run starts. Durable and evented agents are unchanged: they release their execution claim at the deadline, so a late step, or a user message whose save is still in flight, is not saved. ([#28926](https://github.com/mastra-ai/mastra/pull/28926)) Errors with the id `DURABLE_AGENT_EXECUTION_TIMED_OUT` are no longer thrown, because Inngest durable agents no longer refuse memory writes after the deadline. Remove any checks for that id. The deadline is still reported as a `MastraTimeoutError`. ```ts // Before if (error.id === 'DURABLE_AGENT_EXECUTION_TIMED_OUT') { // handle the deadline } // After import { isMastraTimeoutError } from '@mastra/core/loop'; if (isMastraTimeoutError(error)) { // handle the deadline } ``` - Fixed thread-message listeners accumulating while durable runs wait for approval. ([#29046](https://github.com/mastra-ai/mastra/pull/29046)) - Fixed supervisor agents losing track of which subagent tool paused. When a subagent's tool suspends or needs approval, the supervisor's `tool-call-suspended` and `tool-call-approval` chunks now include `delegatedToolCallId` and `delegatedToolName`. UIs can label each pause with the subagent tool that caused it and tell apart several pauses from the same delegation, including after reloading the conversation from memory. With nested delegation, the fields name the deepest tool call that paused. Keep using `toolCallId` to approve, decline, or resume. Fixes #28583. ([#28991](https://github.com/mastra-ai/mastra/pull/28991)) ```ts for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-suspended' || chunk.type === 'tool-call-approval') { chunk.payload.toolCallId; // the supervisor's call to the subagent, use it to resume chunk.payload.delegatedToolCallId; // the subagent's own tool call that paused chunk.payload.delegatedToolName; // e.g. "ask" } } ``` - Fixed `subscribeToThread({ withInitialHistory: true })` hanging or rejecting when the lease backend (for example Redis) is slow or unavailable, which also kept `AgentController` sessions from opening. The subscription now opens anyway. If the lease read doesn't answer in time, `activeRunId` in the `thread-history` chunk comes from this process's own view of the thread, and a warning is logged. A run that is still live in another process keeps streaming to the subscriber once the backend answers again. Fixes #28750. ([#28924](https://github.com/mastra-ai/mastra/pull/28924)) - Fixed provider-executed tool results that stream partial values, such as OpenAI `image_generation` partial images with AI SDK v6. Partial results now keep `preliminary: true` on `tool-result` chunks and on the `tool-output-available` UI chunks sent to `useChat`, so they stay visible in the stream. Tool-result processing, `toolResults`, saved messages and tracing spans now use only the final result. ([#28903](https://github.com/mastra-ai/mastra/pull/28903)) - Fixed signals and notifications that arrive while a run is finishing. If the run ended before the signal or notification was routed, the agent woke the thread without the session's request context, so a model resolved from that context failed with "No model available". Both now start their run with the session's full stream options. In Mastra Code's Factory, this failure repeated on every retry of a rule-driven run until the process restarted. ([#29015](https://github.com/mastra-ai/mastra/pull/29015)) - Fixed AgentController sessions getting stuck as "running" after a failed tool answer. If answering a suspended tool call or a tool approval fails before the run restarts, the session now goes back to idle. The tool call stays waiting, and you can retry it. Fixes #26870. ([#29005](https://github.com/mastra-ai/mastra/pull/29005)) - Fixed workspace sandbox tools (`execute_command`, `get_process_output`, `kill_process`) sending an empty tool result to the model when a `beforeToolCall` hook blocked the call with `{ error: reason }`. The model now receives the reason as an error result, so it no longer assumes a blocked command ran. Mastra Code's `PreToolUse` hooks block tools this way. ([#28919](https://github.com/mastra-ai/mastra/pull/28919)) - Fixed channel agents being unable to read text attachments such as `.txt`, CSV, JSON, YAML or log files shared in Slack. Previously the agent only received the file's private Slack URL, which can't be opened without Slack credentials. The file is now downloaded through the adapter's authenticated fetch and its contents (up to 128 KB) are given to the agent as text. Fixes #28854. ([#28901](https://github.com/mastra-ai/mastra/pull/28901)) - Studio's model provider picker now lists Mastra Gateway even when `MASTRA_GATEWAY_API_KEY` isn't set. Without the key it shows as disconnected, the same way other providers without an API key do. Existing provider policies such as `AUTO_BLOCK_EXTERNAL_PROVIDERS` and model policy still decide what Studio shows. ([#28824](https://github.com/mastra-ai/mastra/pull/28824)) - Publish tool suspension and approval events only after the parked tool call is saved to memory, so a client that reacts to the event — resuming it, or aborting the run — finds the tool call already persisted instead of racing the save still in flight. When memory is writable and that save fails, the suspension or approval event is withheld, the run remains suspended, and the stream attempts to report the save failure, scoped to the tool call that failed so other tool calls already suspended in the same run stay resumable; read-only memory, and runs without a save queue or thread, behave as before. An approval or error chunk that an output processor rejects is now also written straight to the stream as a fallback, instead of only being logged. ([#28839](https://github.com/mastra-ai/mastra/pull/28839)) - Added the saved view schemas (`savedViewStateSchema`, `savedViewStateEnvelopeSchema`, `SAVED_VIEW_STATE_MAX_BYTES` and related types) to the browser-safe `@mastra/core/storage/dashboards` entry, so browser apps can validate a saved view before sending it. ([#29001](https://github.com/mastra-ai/mastra/pull/29001)) ```ts import { savedViewStateSchema } from '@mastra/core/storage/dashboards'; const result = savedViewStateSchema.safeParse({ schemaVersion: 1, page: 'traces', filters: [{ fieldId: 'status', value: 'error' }], }); if (!result.success) console.error(result.error.issues); ``` - Fixed `LocalSandbox.executeCommand(command, args)` on native Windows. Arguments are quoted for a POSIX shell, but commands ran through `cmd.exe`, so paths like `C:\Users\First Last\repo` were split or kept their quote characters, and tools like `mkdir -p` and `find` resolved to Windows builtins. Argv commands, including `sh -c` scripts, now run through Git Bash, found via `MASTRA_WINDOWS_SHELL`, next to `git.exe` on `PATH`, or at `%ProgramFiles%\Git\bin\bash.exe`. Fixes #28866. ([#28900](https://github.com/mastra-ai/mastra/pull/28900)) - Workspace tools now save their `data-workspace-metadata` chunks to the current response message. Previously, after a task signal rotated the response message id, the chunk was saved under the previous id and replaced that assistant message, losing its reasoning, text, and tool calls from stored history. ([#28853](https://github.com/mastra-ai/mastra/pull/28853)) - Workspace file-modification protection is now off by default and only applies when `requireReadBeforeWrite` is enabled. Previously `write_file`, `edit_file` and `apply_patch` always rejected a file that changed on disk after it was read, with `StaleFileError`, even when `requireReadBeforeWrite` was unset or `false`. Now, with the option unset or `false`, a write succeeds even if the file was changed by another process (`sed -i`, formatters, git) after a read, and `write_file` can overwrite that change. `edit_file` still fails if the text it replaces is gone, and `apply_patch` still rejects a file that changed between its own validation and commit. Set `requireReadBeforeWrite: true` to keep the protection; its behavior is unchanged. ([#28935](https://github.com/mastra-ai/mastra/pull/28935)) To keep the protection: ```ts new Workspace({ filesystem, tools: { [WORKSPACE_TOOLS.FILESYSTEM.EDIT_FILE]: { requireReadBeforeWrite: true }, [WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: { requireReadBeforeWrite: true }, }, }); ``` - `write_file` now names the real file location when the filesystem maps an absolute path somewhere else. In a Factory sandbox, writing `/tmp/x` stores the file at `/tmp/x`; the tool result now says `Wrote 5 bytes to /tmp/x (resolved to /tmp/x)` so follow-up shell commands look in the right place. Fixes #28862. ([#28902](https://github.com/mastra-ai/mastra/pull/28902)) ## 1.81.0-alpha.1 ### Minor Changes - Added factory-scoped sandbox environment. A deployment that hosts several factories can now give a credential to one factory's sessions only: every Factory sandbox provider accepts `env` as a function of the session's sandbox context, which carries the new `factoryId` and `factoryName` fields. Scope secrets by `factoryId`, because names can be edited and aren't unique. ([#28767](https://github.com/mastra-ai/mastra/pull/28767)) ```ts new PlatformFactorySandbox({ env: ctx => (ctx.factoryId === PLATFORM_FACTORY_ID ? { PLATFORM_TOKEN } : {}), }); ``` Platform and Docker sandboxes keep the environment they started with when they resume. When you remove a credential from a factory, recreate its running sandboxes. ### Patch Changes - Fixed `resumeStream()` on evented workflows rejecting its `result` with "promise 'result' was not resolved or rejected when stream finished". The stream now stays open until the resumed run's result is set. ([#28871](https://github.com/mastra-ai/mastra/pull/28871)) - Fixed nested evented workflows running without the caller's `workflows:execute` permission on the child workflow. When a fine-grained authorization (FGA) provider is configured, non-internal nested workflows now require an authenticated user with that permission, or a trusted actor with tenant scope subject to the provider's actor policy. Internal child workflows remain exempt from this check. ([#28669](https://github.com/mastra-ai/mastra/pull/28669)) Schedule-triggered runs carry no user identity or trusted actor by default. Scheduled workflows containing non-internal nested children therefore now fail closed when FGA is configured, matching the default engine's behavior. - Fixed tracing for runs that continue in another process. A durable agent run resumed after a restart or on another server now stays on its original trace instead of opening a second trace that never ends. Evented workflow runs restarted after a crash, or time-travelled, now trace their steps, so scores of a recovered agent run link to the run's trace. ([#28832](https://github.com/mastra-ai/mastra/pull/28832)) - Retry policies that match bad-request errors no longer retry a 400 that rejects the model or a parameter, such as `model_not_found`. These requests fail the same way on every retry. ([#28786](https://github.com/mastra-ai/mastra/pull/28786)) ## 1.81.0-alpha.0 ### Minor Changes - Added `setCookies()` to `MastraBrowser`, so your app can sign a thread's browser in to a site the agent can't sign in to itself, such as a preview behind your own login. The cookie value never passes through the model. Cookies set before the browser opens are added when it does. ([#28631](https://github.com/mastra-ai/mastra/pull/28631)) ```ts await browser.setCookies([{ name: 'my_app_session', value: token, domain: 'app.example.com', path: '/' }], threadId); ``` Browser providers that don't support cookies throw. Custom providers can add support by overriding `addCookiesToOpenBrowser()` and calling `addPendingCookies(threadId)` once a thread's browser opens, before it loads a page. - Added execution outcomes and typed stop-reason details to agent, model, tool, and workflow traces. Traces distinguish failures, cancellations, limits, guardrail blocks, and observer interruptions; TripWire errors now use the name `TripWire` instead of `Error`, including in string representations, stacks, and serialized errors, so they can be identified across workers. ([#28703](https://github.com/mastra-ai/mastra/pull/28703)) Custom spans can record an outcome with a compact stop-reason context: ```ts import { SpanType, type ObservabilityInstance } from '@mastra/core/observability'; function recordGuardrailBlock(observability: ObservabilityInstance, processorId: string) { const span = observability.startSpan({ name: 'policy check', type: SpanType.GENERIC }); span.end({ outcome: 'blocked', stopReason: 'guardrail', stopReasonContext: { processorId } }); } ``` The public `outcome`, `stopReason`, and `stopReasonContext` fields now also allow `null`. Exporter and other consumer types must handle `null` alongside an absent value; arbitrary custom outcome strings and context objects remain supported. Workflow events and durable snapshots can now include tripwire details, including processor-supplied metadata. Agent and generation spans also close when the internal workflow fails without a finish callback, while errors followed by a finish retain their final usage and output. - Added an optional `getRoutes()` method to `SignalProvider`. Routes it returns are added to `server.apiRoutes` automatically when the agent is registered, so webhook and OAuth routes for a signal provider no longer need manual server config. ([#28575](https://github.com/mastra-ai/mastra/pull/28575)) ```ts class AcmeSignals extends SignalProvider<'acme'> { readonly id = 'acme' as const; getRoutes() { return [registerApiRoute('/acme/webhook', { method: 'POST', handler: async c => c.json({ ok: true }) })]; } } // Before: server: { apiRoutes: acme.getRoutes() } // After: just add the provider to the agent const agent = new Agent({ /* ... */ signals: [new AcmeSignals()] }); export const mastra = new Mastra({ agents: { agent } }); ``` - Fixed the browser failing with `ECONNREFUSED` when launched again on a profile it had used before. ([#28675](https://github.com/mastra-ai/mastra/pull/28675)) Before launching Chrome on a local profile, Mastra now clears the launcher logs left by earlier launches (`chrome-err.log`, `chrome-out.log`), which made the next launch connect to the old, dead DevTools port. It also removes a Chrome profile lock left by a browser that is no longer running. Stagehand does this for every thread-scoped browser too, not only on the first launch. Remote browsers (`cdpUrl`, Browserbase) are not affected. If a browser that is still running holds the profile, launch now fails with a `BrowserProfileInUseError` that names the profile and the holding process: ```ts import { BrowserProfileInUseError } from '@mastra/core/browser'; try { await browser.launch(); } catch (error) { if (error instanceof BrowserProfileInUseError) { // Another Chrome is using the profile: close it, or use a different profile. console.error(error.message); } throw error; } ``` Added `browser.retire()` and `browser.killProcesses()` for code that replaces one browser with another. `close()` lets the browser relaunch the next time something uses it; `retire()` closes it for good, so a tool call still holding the old browser gets an error instead of starting an untracked Chrome. `killProcesses()` force-kills the browser's processes when `close()` fails or hangs. It works on macOS and Linux; it cannot force-kill Chrome on Windows. ```ts // Replace oldBrowser with newBrowser without leaving its Chrome running. const retired = oldBrowser.retire(); const timedOut = await Promise.race([ retired.then(() => false), new Promise(resolve => setTimeout(() => resolve(true), 5000)), ]).catch(() => true); if (timedOut) oldBrowser.killProcesses(); ``` A browser that finishes closing after another browser took its profile no longer deletes the new browser's lock. A lock whose holder is a live local process that has not started listening yet (Chrome creates the lock first) is no longer removed as stale; Mastra waits up to 3 seconds for it. - Trace, span and thread queries can now return and filter by `timeToFirstTokenMs`, the time from the start of the model call to its first streamed chunk. Trace and span queries can also sort by it. Trace rows use the root span's value, and thread rows use the first trace's root agent. Missing values sort last. Stores that support it declare the `trace-query-time-to-first-token` and `trace-query-time-to-first-token-sort` features. ([#28699](https://github.com/mastra-ai/mastra/pull/28699)) ```typescript const page = await storage.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, orderBy: [{ field: 'timeToFirstTokenMs', direction: 'desc' }] }), ), ); ``` Durable agents now carry the time to first token of the model call that streamed the run's first chunk across steps, so their generation and agent run spans report it too. A model call that fails before a fallback model, or a durable agent's retry, takes over now ends its model inference span with that error. ### Patch Changes - dependencies updates: ([#28223](https://github.com/mastra-ai/mastra/pull/28223)) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.63` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.63) (from `npm:@ai-sdk/provider-utils@5.0.62`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.30` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.30) (from `npm:@ai-sdk/provider@4.0.29`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`9f03685`](https://github.com/mastra-ai/mastra/commit/9f0368570677b9c36285e4512d0fc3f136594f74)) - Fixed background tasks staying locked to a worker after it shut down gracefully. A worker that stops with `mastra.shutdown()` now releases the tasks it was running right away, so another worker can pick them up immediately instead of waiting for the lease to expire. ([#28719](https://github.com/mastra-ai/mastra/pull/28719)) - `PROCESSOR_WORKFLOW_FAILED` now keeps the underlying error as its `cause`. Previously the failure text included the error message but dropped the error object, so callers could not inspect the original stack, error type or provider fields; the failed workflow's error (or the failed step's error) is now attached. ([#28831](https://github.com/mastra-ai/mastra/pull/28831)) - Flush observability exporters before closing storage on Mastra shutdown, so final spans persist when using DuckDB. Release DuckDB file locks on shutdown, including composed observability storage, concurrent operations, and public connection cleanup. Prevent closed DuckDB connections from reopening, and avoid leaving a connection open while checking Mastra Code's local observability store. ([#28656](https://github.com/mastra-ai/mastra/pull/28656)) - Fixed `output.request` and `onFinish.request` for durable and evented agents. Fresh, uninterrupted `stream()` and `generate()` runs that finish normally in the same process now expose the final provider request body. If a local run fails on a provider error, `output.request` reports the failing attempt's request, when available, rather than an earlier step's request. Provider request bodies are kept out of workflow events and snapshots. ([#28674](https://github.com/mastra-ai/mastra/pull/28674)) **Unchanged cases** - Remote workers - Resumed, recovered, or observed runs - Suspended, aborted, or tripwire runs - Historical request metadata in `output.steps` - Fixed trace trees for agents whose model call is rejected and handled by an error processor (for example a retry on a rate limit). The "request error processor" spans now sit inside the model step whose call failed, next to that step's other processor spans, instead of showing up as a flat list under the agent run. ([#28745](https://github.com/mastra-ai/mastra/pull/28745)) - Improved `Mastra` deployer compatibility: the `deployer` option now accepts any object that implements the `IDeployer` interface (exported from `@mastra/core/deployer`), not only subclasses of `MastraDeployer`. `getDeployer()` now returns `IDeployer | undefined`. ([#28428](https://github.com/mastra-ai/mastra/pull/28428)) This keeps deployers from `@mastra/deployer` (such as `VercelDeployer`) assignable to `Mastra` even when they are built on a separate copy of the base classes. ```ts // Before: deployer had to be typed as MastraDeployer const deployer: MastraDeployer | undefined = mastra.getDeployer(); // After import type { IDeployer } from '@mastra/core/deployer'; const deployer: IDeployer | undefined = mastra.getDeployer(); ``` - Fixed thread stream topics using far more memory than needed for agents without memory, such as the observational memory observer and reflector. The output of their finished runs, successful or failed, stayed on the topic until the stream length cap pushed it out. It is now removed 30 seconds after the run ends. Each run's final outcome stays on the topic, so subscribers that reconnect can still see how it ended. ([#28746](https://github.com/mastra-ai/mastra/pull/28746)) - Fixed channel tool approval actions on tool calls whose ID contains a colon. `tool_approve:` and `tool_deny:` now take everything after the first colon as the tool call ID, so a click no longer resolves to the wrong pending approval. ([#28728](https://github.com/mastra-ai/mastra/pull/28728)) - Fixed Inngest durable agents writing to memory after `modelSettings.timeout.totalMs` expired. A tool that finished after the deadline could still save its result (and the unfinished step) to memory, even with `savePerStep` off. Once the deadline fires, later memory writes are now blocked for steps that run in the same process that started the run. Steps running on a separate Inngest worker do not see the deadline yet. ([#28747](https://github.com/mastra-ai/mastra/pull/28747)) Inngest durable agents now save the accepted user message before the run starts, as the other durable engines do. A run that times out or fails keeps the user's turn in history, even when the deadline fires before that save finishes. - Clarify that `FileStat.path` is a path within the filesystem namespace. ([#28819](https://github.com/mastra-ai/mastra/pull/28819)) - Limit memory used when publishing stalls. Durable agent history keeps less data for each tool result. Tool calls and results are still kept. ([#28652](https://github.com/mastra-ai/mastra/pull/28652)) - Fixed agent runs that hit `modelSettings.timeout.totalMs` losing the user's message from memory. The default and filesystem engines now save the accepted user message when the deadline is reached, as the durable and evented engines already did. Completed steps are still saved only when `savePerStep` is on, unfinished steps are never saved, and read-only memory is respected. ([#28668](https://github.com/mastra-ai/mastra/pull/28668)) - Updated the AI SDK v5 compatibility dependencies so they resolve @ai-sdk/provider-utils 3.0.42, which blocks `.internal` hostnames in download URL validation and encodes provider-returned identifiers in request paths. This patch-level update replaces 3.0.41 (Aikido findings AIKIDO-2026-10990 and AIKIDO-2026-10981): ai 5.0.282, @ai-sdk/gateway 2.0.173, anthropic 2.0.110, google 2.0.106, groq 2.0.59, openai 2.0.137, openai-compatible 1.0.59, and xai 2.0.100. ([#28716](https://github.com/mastra-ai/mastra/pull/28716)) ## 1.80.0 ### Minor Changes - Added `agentModel` to `dataset.startExperiment()` and `dataset.startExperimentAsync()` so you can run an agent experiment against a different model without editing or versioning the agent. The model is passed with each call the experiment makes: the registered agent, its sub-agents and other experiments running at the same time keep their own models, and the agent version is picked the same way as without `agentModel`. ([#28165](https://github.com/mastra-ai/mastra/pull/28165)) ```ts await dataset.startExperiment({ targetType: 'agent', targetId: 'support-agent', agentModel: 'anthropic/claude-haiku-5-5', scorers: ['accuracy'], }); ``` The experiment's `metadata.agentModel` and each run's trace metadata record the model (for example `anthropic/claude-haiku-5-5`), so runs on different models can be told apart. `agentModel` works only with agent targets and is rejected before an experiment record is created otherwise. - Channel `formatError` now receives the failed run's request context as a second argument, so the message posted to Slack, Discord, Teams or Telegram can link back to whatever the run belongs to. Formatters that take only the error keep working. When a stream fails with a raw provider payload instead of an `Error`, the error passed to `formatError` keeps that payload as its `cause`, so the formatter can still tell what failed. ([#27811](https://github.com/mastra-ai/mastra/pull/27811)) ```ts channels: { adapters: { slack: { adapter: createSlackAdapter(), formatError: (error, { requestContext }) => { const ticketId = requestContext?.get('ticketId'); return ticketId ? `❌ ${error.message} (ticket ${ticketId})` : `❌ ${error.message}`; }, }, }, }, ``` - Added `classifyProviderError`, `providerErrorReason` and `toPersistedErrorIdentity` to `@mastra/core/error`: one reader for model-provider failures (authentication, quota, rate limit, context overflow, rejected model or request, timeout, outage) that takes HTTP status and provider codes before wording. A status from the GitHub API or a storage call is not mistaken for the model provider's. `providerRequestId` and `providerModelId` return the provider's request id and the model the failed request named, also from a wrapped cause, and `PROVIDER_CALL_ERROR_NAME` matches the name of the AI SDK's provider call error. ([#28496](https://github.com/mastra-ai/mastra/pull/28496)) Saved error parts now also keep every provider code as `codes` and the provider's `reason` sentence, so a reloaded thread classifies like a live one, including a failure the provider reported mid-stream. `toPersistedErrorIdentity(error)` returns that saved shape, so anything that sends an error to a client can send the same one. The `reason` comes from the provider's response, including a string `detail` field and a response wrapped as another error's cause; any other cause stays out of thread history, and a long reason is cut at 2,000 characters. Each kind names a recovery action. New kinds can arrive in minor releases, and no kind is renamed or removed within 1.x, so keep a default branch when you switch on one: ```ts import { classifyProviderError } from '@mastra/core/error'; switch (classifyProviderError(error)) { case 'rate_limited': // wait, then retry break; case 'quota': // ask for more credit break; default: // show the provider's reason } ``` - `@mastra/core/workspace` now exports `getMimeType`, the extension-based lookup `LocalFilesystem` uses, so custom filesystem providers can fill in `FileStat.mimeType` the same way. ([#28302](https://github.com/mastra-ai/mastra/pull/28302)) ```ts import { getMimeType } from '@mastra/core/workspace'; getMimeType('page.png'); // 'image/png' getMimeType('notes.unknown'); // 'application/octet-stream' ``` - Fixed stored span previews to preserve intentionally empty strings instead of replacing them with payload content. ([#28442](https://github.com/mastra-ai/mastra/pull/28442)) Added `buildSpanPreviews()` to generate input, output, and error previews from a processed span. Support is advertised by the `observability-span-previews` core feature flag. ```ts import { buildSpanPreviews } from '@mastra/core/observability'; const previews = buildSpanPreviews(span); ``` - Added render-only tools that stream display calls and let agents and sub-agents continue without a client result. Use `renderOnly: true` when the browser only needs to show the call: ([#28082](https://github.com/mastra-ai/mastra/pull/28082)) ```typescript import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const showCard = createTool({ id: 'show-card', description: 'Display a product card', inputSchema: z.object({ productId: z.string() }), renderOnly: true, }); ``` - Span records now carry nine native fields in their own columns: `externalParentSpanId`, `resumedFromSpanId`, `model`, `provider`, `outcome`, `stopReason`, `stopReasonContext`, `timeToFirstTokenMs` and `errorCount`. Exported spans can supply them, the storage exporters copy them as given, and a field the span does not set is stored as `null`. This release only adds the fields; later releases fill them in. ([#28389](https://github.com/mastra-ai/mastra/pull/28389)) - Added an opt-in `onMessage` channel handler for messages that do not mention the bot. Call `defaultHandler` to run the agent, post its reply, and subscribe the thread. Unmentioned messages stay ignored when the handler is omitted or `false`. ([#28285](https://github.com/mastra-ai/mastra/pull/28285)) ```ts import type { ChannelHandlers } from '@mastra/core/channels'; const handlers: ChannelHandlers = { onMessage: async (thread, message, defaultHandler) => { if (thread.channelId !== 'slack:C_SUPPORT') return; await defaultHandler(thread, message); }, }; ``` Runs started through `onMessage` report `channel.eventType` as `message`. Existing event types are unchanged. When a queued batch mixes mentioned and unmentioned senders, unmentioned senders go to `onMessage` only if it is set. Otherwise they still reach `onMention`, as before. Fixes https://github.com/mastra-ai/mastra/issues/27060. - Added a `textDisplay` channel adapter option. Set `textDisplay: 'hidden'` to stop posting the agent's reply text, for example when the agent replies through a tool you give it. Tool display, approval prompts, errors and typing status still show, and the text still reaches the agent's stream and memory. ([#28067](https://github.com/mastra-ai/mastra/pull/28067)) ```ts channels: { adapters: { slack: { adapter: createSlackAdapter(), textDisplay: 'hidden', }, }, }, ``` - Agent run spans now show the `model` and `provider` the run was configured with, on regular, durable and evented agents, including resumed runs. Model step spans gained the same two fields for the model each step actually called. ([#28485](https://github.com/mastra-ai/mastra/pull/28485)) - Added opt-in shared-channel tool approvals while keeping requester-only approval as the default. Configure `new SlackProvider({ toolApprovers: 'anyone' })` to allow channel members to approve or deny pending tools. ([#28542](https://github.com/mastra-ai/mastra/pull/28542)) - Added `get(pid, { release: false })` to the sandbox process manager. It reads a process without dismissing it once it has exited, so one reader can look at a finished command's output without taking it from another, such as the agent that started it. The default is unchanged. ([#27919](https://github.com/mastra-ai/mastra/pull/27919)) ```ts // Show a finished command's output without dismissing it const handle = await sandbox.processes.get(pid, { release: false }); console.log(handle?.stdout); ``` - Thread subscriptions now publish a pending user-message chunk when a steer is accepted, so subscribers can present the message before the agent reads it. ([#28204](https://github.com/mastra-ai/mastra/pull/28204)) Queued follow-ups publish queued user-message chunks when accepted and `data-user-message-cancelled` chunks when cancelled. - Added `AgentController.getDefaultMode()`, which returns the mode new sessions start in. It follows `defaultModeId`, then a mode marked as default, then the first mode, so you can read the starting model without creating a session. ([#28402](https://github.com/mastra-ai/mastra/pull/28402)) ```ts const mode = controller.getDefaultMode(); console.log(mode.id, mode.defaultModelId); ``` - Agent calls to OpenAI-compatible Chat Completions providers, including router models that resolve to a Chat Completions endpoint (such as `'groq/...'` or a custom `url`), now send all leading system instructions as one system message. Setting `errorProcessorDefaults: false` turns this merge off. ([#27626](https://github.com/mastra-ai/mastra/pull/27626)) `processLLMRequest` processors now receive `apiProviderId`: the provider id of the model that will actually be called, for example `groq.chat` or `openai.responses`, where `model.provider` would only say `groq` or `openai`. ```ts import type { Processor } from '@mastra/core/processors'; const chatOnlyRewrite: Processor = { id: 'chat-only-rewrite', processLLMRequest({ prompt, apiProviderId }) { if (!apiProviderId?.endsWith('.chat')) return; return { prompt: prompt.filter(message => message.role !== 'system' || message.content.trim() !== '') }; }, }; ``` - Added opt-in `withRunStatus: true` for thread subscriptions to emit ordered `thread-run-status` chunks with `success`, `failed`, `aborted`, or `suspended` outcomes without closing the subscription. Reconnecting subscribers recover outcomes retained by the PubSub backend. Initial history includes `activeRunId`, the run owning the thread during hydration, or `null` when idle. ([#28621](https://github.com/mastra-ai/mastra/pull/28621)) ```ts const subscription = await agent.subscribeToThread({ resourceId: 'user-123', threadId: 'thread-456', withRunStatus: true, }); for await (const chunk of subscription.stream) { if (chunk.type === 'thread-run-status') { console.log(chunk.runId, chunk.payload.status); } } ``` - Removed `model` and `provider` from thread rows returned by `queryThreads()` with a `timeRange` request. A thread can contain many model calls, so a single model and provider was misleading, and looking them up made thread pages slow. ([#28595](https://github.com/mastra-ai/mastra/pull/28595)) Thread pages now default to 25 rows and allow at most 100 (previously 100 and 1,000). Requests with a larger `page.limit` now fail validation. Use `page.next` to load further pages. **Before** ```ts const { threads } = await client.queryTraceThreads({ timeRange: { traces: range }, page: { limit: 500 } }); threads[0].model; // "gpt-4o" ``` **After** ```ts const { threads, page } = await client.queryTraceThreads({ timeRange: { traces: range }, page: { limit: 100 } }); // threads[0].model no longer exists; query the thread's spans for model usage const next = await client.queryTraceThreads({ timeRange: { traces: range }, page: { limit: 100, after: page.next! }, }); ``` ### Patch Changes - dependencies updates: ([#28417](https://github.com/mastra-ai/mastra/pull/28417)) - Updated dependency [`chat@^4.43.0` ↗︎](https://www.npmjs.com/package/chat/v/4.43.0) (from `^4.37.0`, in `dependencies`) - Updated dependency [`dotenv@^17.4.2` ↗︎](https://www.npmjs.com/package/dotenv/v/17.4.2) (from `^17.3.1`, in `dependencies`) - Updated dependency [`fastq@^1.20.3` ↗︎](https://www.npmjs.com/package/fastq/v/1.20.3) (from `^1.20.1`, in `dependencies`) - Updated dependency [`hono@^4.13.13` ↗︎](https://www.npmjs.com/package/hono/v/4.13.13) (from `^4.13.7`, in `dependencies`) - Updated dependency [`hono-openapi@^1.5.0` ↗︎](https://www.npmjs.com/package/hono-openapi/v/1.5.0) (from `^1.3.1`, in `dependencies`) - Updated dependency [`ignore@^7.0.14` ↗︎](https://www.npmjs.com/package/ignore/v/7.0.14) (from `^7.0.5`, in `dependencies`) - Updated dependency [`lru-cache@^11.5.3` ↗︎](https://www.npmjs.com/package/lru-cache/v/11.5.3) (from `^11.2.7`, in `dependencies`) - Updated dependency [`p-map@^7.1.1` ↗︎](https://www.npmjs.com/package/p-map/v/7.1.1) (from `^7.0.4`, in `dependencies`) - Updated dependency [`picomatch@^4.0.7` ↗︎](https://www.npmjs.com/package/picomatch/v/4.0.7) (from `^4.0.3`, in `dependencies`) - Updated dependency [`posthog-node@^5.57.1` ↗︎](https://www.npmjs.com/package/posthog-node/v/5.57.1) (from `^5.46.1`, in `dependencies`) - Updated dependency [`re2js@^2.9.0` ↗︎](https://www.npmjs.com/package/re2js/v/2.9.0) (from `^2.8.6`, in `dependencies`) - Updated dependency [`ws@^8.22.0` ↗︎](https://www.npmjs.com/package/ws/v/8.22.0) (from `^8.21.3`, in `dependencies`) - Updated dependency [`yaml@^2.9.1` ↗︎](https://www.npmjs.com/package/yaml/v/2.9.1) (from `^2.9.0`, in `dependencies`) - Updated dependency [`@ai-sdk/azure@^2.0.143` ↗︎](https://www.npmjs.com/package/@ai-sdk/azure/v/2.0.143) (from `^2.0.142`, in `devDependencies`) - Updated dependency [`@babel/core@^8.0.9` ↗︎](https://www.npmjs.com/package/@babel/core/v/8.0.9) (from `^8.0.1`, in `devDependencies`) - Updated dependency [`@copilotkit/aimock@^1.46.0` ↗︎](https://www.npmjs.com/package/@copilotkit/aimock/v/1.46.0) (from `^1.29.0`, in `devDependencies`) - Updated dependency [`@types/node@22.19.15` ↗︎](https://www.npmjs.com/package/@types/node/v/22.19.15) (from `22.20.5`, in `devDependencies`) - Updated dependency [`@types/ws@^8.18.2` ↗︎](https://www.npmjs.com/package/@types/ws/v/8.18.2) (from `^8.18.1`, in `devDependencies`) - Updated dependency [`arktype@^2.2.9` ↗︎](https://www.npmjs.com/package/arktype/v/2.2.9) (from `^2.2.0`, in `devDependencies`) - Updated dependency [`rollup@^4.65.0` ↗︎](https://www.npmjs.com/package/rollup/v/4.65.0) (from `^4.64.10`, in `devDependencies`) - Updated dependency [`vscode-languageserver-protocol@^3.18.5` ↗︎](https://www.npmjs.com/package/vscode-languageserver-protocol/v/3.18.5) (from `^3.18.4`, in `devDependencies`) - dependencies updates: ([#28018](https://github.com/mastra-ai/mastra/pull/28018)) - Updated dependency [`@modelcontextprotocol/server@2.3.1` ↗︎](https://www.npmjs.com/package/@modelcontextprotocol/server/v/2.3.1) (from `2.2.0`, in `devDependencies`) - Update provider registry and model documentation with latest models and providers ([`c253e13`](https://github.com/mastra-ai/mastra/commit/c253e13ac627d0325f8665004f7af8a49805a9a4)) - Fixed durable and evented agents dropping a background task's result when the task had been dispatched but no worker had picked it up yet. The end-of-step background check counted only `running` tasks. On a pubsub with delivery latency, a just-dispatched task was still `pending`, so the agent stopped waiting and the task's result never reached the stream. Pending tasks now count as outstanding. ([#28228](https://github.com/mastra-ai/mastra/pull/28228)) - Fixed background tasks staying `running` forever when their background-task workflow run ended as failed before a terminal status was saved (for example, when the storage write that marks the task completed throws). The task is now marked `failed` with the error recorded, and `onFailed`/`onTaskFailed` hooks and the `task.failed` event fire once. ([#27933](https://github.com/mastra-ai/mastra/pull/27933)) A per-task `onResult`, `onComplete` or `onFailed` hook that throws no longer stops the `task.completed`/`task.failed` event from being published, so the global `onTaskComplete`/`onTaskFailed` callbacks still fire. - Fixed foreground command exceptions to include a failed exit code in the returned output. ([#28406](https://github.com/mastra-ai/mastra/pull/28406)) - Fixed recovery of ToolSearchProcessor-loaded tools after a crash in an evented agent, with either `in-memory` or `context` storage. Recovery now reads the transcript from the active iteration's run, so the interrupted tool call resumes with its original ID instead of failing with `Tool not found`. ([#28295](https://github.com/mastra-ai/mastra/pull/28295)) Evented `recover()` and `observe()` now restore the latest transcript saved during the iteration instead of the one the iteration started with. Fixes https://github.com/mastra-ai/mastra/issues/28171. - Fixed channel agents losing their channel instructions on runs that didn't start from a chat message, such as a schedule, a Studio prompt or a signal. ([#28078](https://github.com/mastra-ai/mastra/pull/28078)) - Every run on a channel-backed thread now gets the same channel system prompt, so switching between chat messages and other runs keeps the prompt cached. Replies from those runs stream to the channel the same way. - The prompt's opening line changed from "You are communicating via slack." to "This conversation is connected to a slack thread." (or "slack direct message"). This applies to every channel agent. - Messages that come in over a channel now carry a `platform` attribute, for example `platform="slack"`. - With `textDisplay: 'hidden'`, the instructions say that only what the agent posts with tools reaches the conversation, and that to stay silent it posts nothing instead of sending an empty message. - Added a `ProviderHistoryCompat` safeguard that sends an assistant tool call whose stored input is missing or `null` as `{}` on every provider, so a stored row without input can't make providers reject the replayed history. The stored message is unchanged, the call keeps its error result, and an empty-string input is left as it is. ([#28332](https://github.com/mastra-ai/mastra/pull/28332)) - Permission checks in server handlers now use the same provider as route-level checks. If you configure access control only under `studio`, Studio requests are checked against it, including thread access. A request with no applicable provider is denied instead of allowed. ([#28543](https://github.com/mastra-ai/mastra/pull/28543)) - Fixed token usage being reported as `undefined` in `onFinish` when a structured-output run fails schema validation. The model's final chunk, which carries the usage, is now still read after the validation error. ([#28671](https://github.com/mastra-ai/mastra/pull/28671)) - Fixed `processInput()` receiving `agent: undefined` in every input processor after the first. An agent with two or more input processors now passes the running agent to each of them, as `ProcessInputArgs.agent` documents. This matters most with memory enabled, because memory processors run first and configured processors never received the agent. Fixes #28349. ([#28443](https://github.com/mastra-ai/mastra/pull/28443)) - `@mastra/core/channels` now exports `CHAT_CHANNEL_RENDER_CONTEXT_KEY` and the `ChatChannelRenderContext` type, so tools resolved per request can reach the live chat thread a run is replying in (for example, to upload a file into it). ([#27351](https://github.com/mastra-ai/mastra/pull/27351)) - Reject contained local filesystem reads and directory listings when a symlink swap is detected outside configured roots; preserve metadata access to unreadable files. ([#28092](https://github.com/mastra-ai/mastra/pull/28092)) - Exported durable-agent helpers that `@mastra/inngest` reuses instead of copying: `applyReservedMemoryContextKeys`, `emitAbortEvent`, `createDurableIsTaskCompleteStep` and `createDurableGoalStep` from `@mastra/core/agent/durable`, and `createTimeoutAbortSignal` from `@mastra/core/loop`. ([#28232](https://github.com/mastra-ai/mastra/pull/28232)) Fixed `observe().abort()` on a durable run parked on a tool approval or suspension: it now releases the run like the stream `abort()` does, so a later `approveToolCall()` or `resumeStream()` is rejected instead of continuing the aborted run. Fixed an empty step text on durable agents when an output processor holds parts (such as `BatchPartsProcessor`) and the `step-start` event reached the stream after those parts were released. The run that holds the output processors now runs them on `step-start` itself, like every other chunk, so each processor sees it exactly once. A processor that throws on `step-start` now fails the run with an `error` chunk and `finishReason: 'error'`, matching a throw on any other chunk, instead of throwing from `fullStream`. - `StreamErrorRetryProcessor` now decides retries with the shared provider-failure reader from `@mastra/core/error`, so it agrees with every other place Mastra reads a model-provider failure. It retries rate limits, provider outages and timeouts, and never retries a credential or quota refusal. ([#28496](https://github.com/mastra-ai/mastra/pull/28496)) Fixed an exhausted quota being retried when the provider returned it as a retryable `429`. A stream error frame that reports an exhausted quota under a rate-limit code is no longer retried either, and `isBadRequestError` no longer matches a `400` that reports an exhausted credit balance, a usage cap, or a rejected API key. Your own `matchers` now run before the built-in OpenAI Responses matcher, so their `maxRetries`, `delayMs` and `onRetry` apply when both match a stream error. - Signal contents now reach the model as written inside their tag. Previously `&`, `<` and `>` were turned into `&`, `<` and `>`, so commands and code in reminders, notifications and other signals arrived mangled. ([#27150](https://github.com/mastra-ai/mastra/pull/27150)) ```ts await agent.sendSignal( { type: 'reactive', tagName: 'skill', attributes: { name: 'release' }, contents: 'Run `pnpm build && pnpm test 2>&1`.', }, { resourceId, threadId }, ); // Before: Run `pnpm build && pnpm test 2>&1`. // After: Run `pnpm build && pnpm test 2>&1`. ``` A closing tag for the signal's own tag inside its contents, such as ``, is still escaped so contents can't end the tag early. Attribute values are still escaped. - A saved error part of a failed provider call now keeps `modelId`, the model the request named, so a reloaded thread can say which model failed. Only the model name is read from the request; the prompt and the rest of the request body are still never saved. ([#27811](https://github.com/mastra-ai/mastra/pull/27811)) `JsonReadyAgentControllerEvent` now types a flattened error with the optional `statusCode`, `responseBody`, `requestId` and `modelId` that a saved error part keeps, so clients can read them from a live event without a cast. - Fixed `generate()`, `resumeStream()`, and `resumeGenerate()` to reject invalid `modelSettings.timeout` values with the same `TypeError`. Durable and evented agents now reject invalid resolved timeouts before acquiring an initial execution claim, and validate caller settings and static defaults before cold recovery executes. A rejected resume leaves the suspended run available for a retry with corrected settings. ([#28030](https://github.com/mastra-ai/mastra/pull/28030)) - Fixed durable and evented agent streams dropping provider metadata from successful tool-result chunks, including sub-agent text output mappings, while preserving original tool-call metadata in payload-transform callbacks. ([#28025](https://github.com/mastra-ai/mastra/pull/28025)) - Fixed `LocalFilesystem` so writes through symlinked parent directories cannot create files or directories outside the workspace or allowed paths. Writes outside those roots now throw `PermissionError`. Symlinked writes within allowed roots still succeed. Fixes #27916. ([#28148](https://github.com/mastra-ai/mastra/pull/28148)) - Durable agents now honor their configured `notifications.deliveryPolicy`, and forked durable agents retain their Mastra instance so notification signals can be sent from forks. ([#28639](https://github.com/mastra-ai/mastra/pull/28639)) - Restore text streamed before tool approval in resumed durable and evented agent output. Streams configured to close on suspension settle their partial results; streams kept open continue through approval. Earlier completed iterations are not added to the resumed stream's `steps`. ([#27582](https://github.com/mastra-ai/mastra/pull/27582)) - Fixed durable agent streams with `closeOnSuspend: true` closing before they deliver their chunks when the agent uses Redis Streams PubSub. For example, the model can request two approval tool calls in one step. If you approve the first with `resumeStream(..., { closeOnSuspend: true })`, the resumed stream now includes the tool result and the second approval request instead of closing empty. Fixes #28479. ([#28504](https://github.com/mastra-ai/mastra/pull/28504)) - Fixed `observe()` on durable and evented agents throwing `Cannot read private member #options` when reading `fullStream`. Destructured stream methods such as `const { consumeStream } = output` also work on durable and evented agents now. ([#28531](https://github.com/mastra-ai/mastra/pull/28531)) - Restore the persisted delegation tool call on a cold durable-agent resume before updating suspension metadata. This keeps the next sub-agent suspension in memory when a replacement instance resumes with observational memory enabled. ([#28050](https://github.com/mastra-ai/mastra/pull/28050)) - Fixed tool hooks passed to `agent.stream()` and `agent.generate()` being ignored in durable and evented agents. Per-call `beforeToolCall` and `afterToolCall` hooks now take priority over agent-level hooks. Hooks in `defaultOptions.hooks` also run in these engines. ([#27459](https://github.com/mastra-ai/mastra/pull/27459)) Per-call hooks are functions and only live in the process that received the call. If a run resumes in a different process, only constructor hooks and `defaultOptions.hooks` run, so put hooks you always need there. ```ts await agent.stream('Look up the order', { hooks: { beforeToolCall: () => ({ proceed: false, output: { status: 'mocked' } }), }, }); ``` - Fixed processor reminders sent after a durable or evented step returns being lost before the next model request. Fixes [#27457](https://github.com/mastra-ai/mastra/issues/27457). ([#28154](https://github.com/mastra-ai/mastra/pull/28154)) - Allow server-owned HTTP observers of durable and evented `untilIdle` runs to detach without aborting execution, including resumed streams. Detached loops continue processing background tasks and saving the final answer, while explicit run and thread cancellation remain effective. ([#28162](https://github.com/mastra-ai/mastra/pull/28162)) Direct in-process reader cancellation still aborts active execution by default. The server opts into observer-only cancellation internally; ordinary agents retain their existing cancellation behavior. - Fixed cancelling an evented workflow run with workers on several replicas. Previously only the replica that received `workflow.cancel` aborted its steps, so a step running on another replica never saw its `abortSignal` fire and kept running. Cancelling now reaches every replica running a step of the run, including steps of nested workflows. ([#28489](https://github.com/mastra-ai/mastra/pull/28489)) - Fixed evented workflows and evented agents hanging when a step exhausts its retries and publishing the `workflow.fail` event briefly fails. Previously the failure was logged and dropped, so `run.start()`, `agent.generate()` and `agent.stream()` never resolved and the run stayed `running`. The failure is now retried on redelivery (up to 3 publish attempts) so callers receive the error and the run is stored as `failed`. ([#28218](https://github.com/mastra-ai/mastra/pull/28218)) - Fixed the evented workflow engine reporting a `foreach` step as `success` while its iterations were still running. The stored step now stays `running` (no `endedAt`) until every iteration finishes, and `getWorkflowRunById` no longer shows unfinished iterations as `null` entries in the step output, matching the default engine. ([#27932](https://github.com/mastra-ai/mastra/pull/27932)) When an iteration suspends, the step also stays `running` while sibling iterations are still in flight, and becomes `suspended` only once all of them settle. A suspended `foreach` step no longer gets an `endedAt`, matching the default engine. - Fixed evented workflow runs that never finished after being cancelled from another Mastra instance (or after `startAsync()`). The run was marked `canceled` in storage, but the owner's `run.start()` never resolved and watchers never got a finish event. Cancelling now resolves `start()` with a `canceled` result, and the run emits a single `workflow.end`. ([#28434](https://github.com/mastra-ai/mastra/pull/28434)) `run.cancel()` on an evented run now rejects if the cancel event can't be published. The run is only marked `canceled` once the cancel is published, so you can call `cancel()` again from any instance to retry. - Fixed evented agents calling a throwing `stopWhen` three times. A `stopWhen` error now fails the run on the first attempt, the same as on the other engines, so side effects in the predicate run once and the stream rejects with the error straight away. ([#28215](https://github.com/mastra-ai/mastra/pull/28215)) - Fixed durable and evented agents recording raw provider usage on model_step spans. Cache reads now appear under inputDetails.cacheRead like the regular agent, so prompt-cache dashboards no longer show 0% for these agents. ([#28239](https://github.com/mastra-ai/mastra/pull/28239)) - Added optional model release dates to provider catalogs for newest-first model selection. ([#27661](https://github.com/mastra-ai/mastra/pull/27661)) - Fixed orphaned tool results causing requests to fail when switching model providers after hosted tool calls. Matching calls and results are now removed together from outgoing history. Fixes [#28190](https://github.com/mastra-ai/mastra/issues/28190). ([#28286](https://github.com/mastra-ai/mastra/pull/28286)) - Corrected the `maxProcessorRetries` doc comment on per-call agent options. It said that leaving it unset meant no retries. In fact, error processors such as `StreamErrorRetryProcessor` are capped at 3 retries when no cap is set. ([#28576](https://github.com/mastra-ai/mastra/pull/28576)) - Keep recalled messages as memory references in durable-agent checkpoints when `messageHistory.maxTokens` is set. Token counting adds framework-owned estimate caches to messages in memory; exclude only those cache entries from the saved-message identity comparison while retaining stored message content and caller-supplied metadata. ([#28049](https://github.com/mastra-ai/mastra/pull/28049)) - Fixed lost task updates when task tools run concurrently in the same thread. Fixes https://github.com/mastra-ai/mastra/issues/28268. ([#28294](https://github.com/mastra-ai/mastra/pull/28294)) - File-based agents can now save overrides without changing their source files, matching code-defined agents. ([#28159](https://github.com/mastra-ai/mastra/pull/28159)) - Fixed background tasks cancelled from another process continuing to run. When a task was cancelled on a different worker sharing the same storage, the worker running it marked the task cancelled but let the tool keep executing to completion. The running tool's abort signal now fires on that worker's next lease renewal, so the tool stops as cancelled. ([#28523](https://github.com/mastra-ai/mastra/pull/28523)) - Fixed structured output results and validation errors arriving after the finish event, so idle-thread subscribers receive them before the run ends. Runs that finish with an error no longer publish a final structured result or fallback value. ([#28617](https://github.com/mastra-ai/mastra/pull/28617)) Fixed finish callbacks for streams that emit an error before finish, including non-structured runs. Previously accessed result promises still reject, but no longer interrupt stream completion or prevent `onFinish` and final cleanup from running. `onFinish.finishReason` now reports `'error'` for failed runs. - **Fixed** removed messages coming back into the request when several input processors run in one step. ([#28369](https://github.com/mastra-ai/mastra/pull/28369)) When an input processor removed messages in `processInputStep`, as observational memory does when it compacts a long run, the processors after it were still handed the old `messages` array. A processor that returned that array put the removed messages back, so the prompt kept growing even while memory compacted it. Each processor is now handed the messages as they are after the processors before it ran. - Fixed `response.id` being lost when a model provider (for example OpenRouter) streams response metadata across several partial events. A later event that omits a field no longer erases the value an earlier event supplied, so `onStepFinish`, `onFinish` and `result.response` keep the provider's response id. Durable and evented agents now also report the provider's response id, model id and timestamp in `onStepFinish` and `onFinish`. ([#26660](https://github.com/mastra-ai/mastra/pull/26660)) - Fixed `add_reaction` and `remove_reaction` failing with "No channel context" on runs that did not start from a chat message, such as a schedule, a Studio prompt or a woken run. They now find the chat thread through the run's memory thread, the same way the agent's replies are posted. ([#28073](https://github.com/mastra-ai/mastra/pull/28073)) - Fixed missing token usage in streamed responses from Kimi coding-plan models. ([#28537](https://github.com/mastra-ai/mastra/pull/28537)) - Fixed evented agent runs with Postgres storage that hung when a worker process stopped. Another worker can now finish the run, and the original caller gets the result. Completed loop iterations keep their saved results and state without running again. Recovery-only snapshots are removed when a workflow suspends without snapshot persistence. Snapshot pruning still applies at terminal and control-flow boundaries without discarding inputs needed for recovery. ([#28054](https://github.com/mastra-ai/mastra/pull/28054)) Runs started on older versions can finish after upgrading, but do not have guaranteed crash recovery. On stores that support workflow step guards, internal workflow events go through the shared broker instead of staying in the starting process. Runs with `shouldPersistSnapshot` disabled keep a recovery snapshot until they end or suspend. Storage adapters can opt in by returning `true` from `supportsWorkflowStepGuards()`. Their `updateWorkflowResults` implementation must check the optional `startGuard` or `completionGuard` against the stored result in the same atomic operation that writes the updated result. `@mastra/core/storage` exports `assertWorkflowStepGuard`, `WorkflowStepConflictError`, and `isWorkflowStepConflictError` for implementing these checks. - Fixed a run that lost its thread lease cutting off the step it was in the middle of: a model response still streaming, or a tool call such as a long command. The run now starts no new step, lets the current one finish, fail or be aborted, keeps what it produced, and then stops. This applies to standard, durable and evented runs whose steps run in the process that holds the thread lease. A durable run whose steps run in another worker process, such as the Inngest durable agent, still stops as soon as its lease is lost. A step still running after `MASTRA_AGENT_THREAD_LEASE_LOST_TOOL_GRACE_MS` (default 5 minutes) is aborted as before. In a deployment with several processes, this means the finishing step can still save its output to the thread for up to that long after another run has taken the thread over. ([#28359](https://github.com/mastra-ai/mastra/pull/28359)) A lease backend that is slow or doesn't answer no longer stops a run that has a step running, including while it hands the lease back to the run. The run keeps trying to take its lease back and starts no tool until it has it. It gives up when the backend refuses it the lease, which normally means another run holds the thread, or when the backend has not answered for `MASTRA_AGENT_THREAD_LEASE_LOST_TOOL_GRACE_MS` after the lease ran out. Giving up counts as losing the lease: a step still running then gets its own grace period to finish. So with a silent backend, a step can run for up to two grace periods after the lease ran out. An abort still stops the run at once. A durable run can still be stopped by its own execution claim. - Fixed an unhandled rejection when a run lost its thread lease and its agent could not abort the run. Background lease-loss callbacks now contain abort failures. Remote abort requests still report failures so transports that support retries can deliver the request again. ([#28344](https://github.com/mastra-ai/mastra/pull/28344)) - Record a `tool_call` span for tool calls served by experiment `toolMocks`. The span carries the mocked output and the attribute `mocked: true`, so experiment traces show mocked calls next to model steps instead of only inside the model step output. ([#27954](https://github.com/mastra-ai/mastra/pull/27954)) - Fixed messages sent from another process to a busy agent thread waiting until the current run ended. ([#28298](https://github.com/mastra-ai/mastra/pull/28298)) - **Messages join the running run:** a message delivered to a thread's claimed owner while it is running now reaches the run's next model request, the same as a message sent from the owner's own process. - **Queued messages still wait:** messages queued with `queueSignal` or `queueMessage` still wait for the run to finish. - **Both processes need this release:** a message joins the running run only when the sending process and the process that owns the thread both run this version. With an older sender or an older owner, it waits for the run to finish as before. - Fixed servers staying busy forever after using deferred notifications. A saved notification dispatch schedule no longer starts the scheduler on boot unless a deferred notification is still waiting. The once-a-minute dispatch schedule now removes itself after it delivers the last pending notification, and the next deferred notification brings it back. ([#28550](https://github.com/mastra-ai/mastra/pull/28550)) - Exported `readNotificationRoutingRequirement` from `@mastra/core/notifications`, so a notification delivery policy can tell whether a notification was routed to the thread's claimed owner. ([#28066](https://github.com/mastra-ai/mastra/pull/28066)) ```ts import { readNotificationRoutingRequirement } from '@mastra/core/notifications'; const routedToOwner = readNotificationRoutingRequirement(record.metadata)?.requireClaimedOwner === true; ``` - Fixed a call that passes `hooks: { beforeToolCall: null }` (for example from a JSON request body) turning off the agent's own tool hooks. Non-function values in call-time `hooks` are now ignored, so constructor hooks and `defaultOptions.hooks` still run. ([#27459](https://github.com/mastra-ai/mastra/pull/27459)) - Fixed `observe(runId, { offset })` replaying the whole run when the offset is at or past the end of the run's log on a transport that keeps old messages, such as Redis Streams. While the run's log is retained, the observer now skips every event below the offset and receives events once the run reaches it. If the log expired or was cleared and the run starts numbering again from 0, the observer resyncs and receives the new events from the start. ([#28348](https://github.com/mastra-ai/mastra/pull/28348)) `createEventedAgent` can still re-send events from before the offset when processes share a pubsub such as Redis Streams but each uses the default in-memory server cache. Mastra now logs a one-time warning when this happens. To resume evented agents from an offset, configure a server cache that every process shares, such as `RedisServerCache`. - You can now choose which attachment types the Observational Memory Observer receives: `observation.observeAttachments` accepts a MIME type allowlist (`string[]`) in the public types, matching the runtime (images and PDFs by default). Also corrected the `model` option docs: the default is `'auto'`, falling back from the top-level `model` to the other sub-config's `model`, not `'google/gemini-2.5-flash'`. ([#28556](https://github.com/mastra-ai/mastra/pull/28556)) - Keep the current user request and delayed tool results in the prompt when a durable Observational Memory turn resumes on another worker. Preserve completed live background results when reconciling stored messages. ([#28045](https://github.com/mastra-ai/mastra/pull/28045)) - Fixed tool calls and nested workflows in durable agent runs losing their request context metadata. `hideInput` and `hideOutput` now stay in effect for those spans too. Inngest workflow and step spans keep the same settings when a run continues in a later step. ([#28118](https://github.com/mastra-ai/mastra/pull/28118)) - Fixed a TypeScript error when passing steps to `.parallel()` whose `inputSchema` only uses part of the previous step's output. `.parallel()` now accepts the same steps as `.then()`: the previous output only has to include the fields each step needs. Steps that need a field the previous output doesn't have are still rejected. Runtime behavior is unchanged. ([#28225](https://github.com/mastra-ai/mastra/pull/28225)) ```ts const workflow = createWorkflow({ ... }) .then(producesValueAndPrompt) // outputs { value: number; prompt: string } .parallel([readsValue, readsPrompt]); // inputs { value: number } and { prompt: string } — now type-checks ``` - Fixed thread peer discovery reporting live peers as absent when the caller was busy right after asking. Peers answer in a few milliseconds, but a caller whose event loop stayed busy past the 100 ms window closed it before reading any answer. The window now counts only time the caller could read replies, waiting at most one second longer for a busy caller. A peer that really is gone is still reported absent about one window after the caller is free. ([#28098](https://github.com/mastra-ai/mastra/pull/28098)) - Fixed processor hooks receiving `args.agent` as `undefined` during regular, durable and evented agent runs. Hooks such as `processInputStep`, `processOutputStream` and `processToolResult` now receive the running agent, like `processInput` already did. ([#28463](https://github.com/mastra-ai/mastra/pull/28463)) - Fixed missing provider details on failed model calls. When a model call fails with an HTTP error, the error part saved to the thread keeps the `statusCode`, the provider's `responseBody` (capped at 2,000 characters) and the provider `requestId`, alongside `name` and `message`. Chat channels (Slack, Discord, …) now post the provider's explanation instead of just the HTTP status text, for example `❌ Error: Bad Request (HTTP 400): prompt is too long (request id req_123)`. Server logs get the same message plus `statusCode` and `requestId`, but not the raw response body. ([#27906](https://github.com/mastra-ai/mastra/pull/27906)) - Fixed durable and evented onFinish tool results to use the same chunk shape as the default engine, including per-step results. Fixes #27428. ([#28145](https://github.com/mastra-ai/mastra/pull/28145)) - Added a `neon` filesystem icon so Neon Object Storage mounts show their own icon in the Studio file tree. ([#28032](https://github.com/mastra-ai/mastra/pull/28032)) - Log claimed schedule occurrences and accepted publications with their schedule ID, run ID, and scheduled fire time. Report uncertain delivery after publication retries are exhausted. Document best-effort delivery and continued recurring fires without automatically replaying uncertain occurrences. ([#28544](https://github.com/mastra-ai/mastra/pull/28544)) - Fixed Claude 5.x thinking blocks being stripped from earlier assistant messages on every request, which changed the prompt prefix each step and broke Anthropic prompt caching. The `anthropic-strip-empty-signed-reasoning-content` rule in `ProviderHistoryCompat` now runs only after Anthropic rejects the history with the "thinking blocks cannot be modified" error, so valid empty-text signed blocks are replayed unchanged. ([#28552](https://github.com/mastra-ai/mastra/pull/28552)) - Fixed agent runs that reported an API error instead of an abort when the run was cancelled while an error processor was still handling that error. ([#28158](https://github.com/mastra-ai/mastra/pull/28158)) - Fixed `DurableAgent.recover()` reporting an initial pending snapshot without workflow input as corrupt. It now throws `DURABLE_AGENT_RECOVER_CHECKPOINT_NOT_AVAILABLE` with guidance to enable running-checkpoint persistence before starting the run, while preserving malformed-snapshot and ownership checks. ([#28611](https://github.com/mastra-ai/mastra/pull/28611)) - The traces list Input column now previews an agent run started by a signal by its contents, instead of the signal as JSON. Skills read `Skill : `, other non-user signals are prefixed with their tag (for example `system-reminder: CI is red.`), and user signals show their text. ([#28038](https://github.com/mastra-ai/mastra/pull/28038)) - Output processors can now add text to an agent's response when observational memory is enabled and the final step is empty after a tool call. That text now streams and is saved for durable and evented agents, the same as for standard agents. ([#28046](https://github.com/mastra-ai/mastra/pull/28046)) - Fixed `read_file` returning only metadata for `.jsonl`, `.php` and `.dart` files. Their MIME types (`application/jsonl`, `application/x-php`, `application/dart`) are now treated as text, so the file content is returned. ([#28462](https://github.com/mastra-ai/mastra/pull/28462)) - Added optional `batchUpdateUnendedSpans()` to observability storage. It updates only spans that are stored and haven't ended, never creates rows, and returns the spans it changed. The in-memory store implements it. Also added the `span-not-updatable` drop reason for span updates that didn't apply. Part of the fix for [#27479](https://github.com/mastra-ai/mastra/issues/27479). ([#28147](https://github.com/mastra-ai/mastra/pull/28147)) ```ts const store = await mastra.getStorage()?.getStore('observability'); if (typeof store?.batchUpdateUnendedSpans === 'function') { const { updated } = await store.batchUpdateUnendedSpans({ records: [{ traceId, spanId, updates: { endedAt: new Date() } }], }); } ``` - Fixed default workflow runs continuing to later steps and overwriting their status with success after another process canceled the run through shared storage. This covers sequential steps, sleeps, resumed parallel branches, and `dountil`, `dowhile` and `foreach` loops. Loops now stop once they see the cancellation instead of running to completion. ([#27904](https://github.com/mastra-ai/mastra/pull/27904)) Added an optional `expectedStatus` guard to `persistWorkflowSnapshot`. The in-memory, LibSQL, PostgreSQL, MySQL, MSSQL, DSQL and Cloudflare D1 stores check it in the same statement as the write, so they can't overwrite a cancellation. Other stores, such as MongoDB, read the run status just before writing, which leaves a short window where a cancellation can still be lost. Custom workflow stores that honor the guard atomically should return `true` from `supportsGuardedSnapshotWrites()`: ```ts const written = await store.persistWorkflowSnapshot({ workflowName, runId, snapshot, expectedStatus: ['running', 'success'], }); // `written` is false when the stored status is not in the list ``` Added `loadWorkflowRunStatus()`, which reads only a run's status. Stores that don't override it fall back to loading the snapshot. - Fixed threads becoming unusable after a model sent a tool call with arguments that could not be parsed. That call was replayed to the model with no input, so OpenAI-compatible providers such as DeepSeek sent it without `arguments` and rejected every later request on the thread with "missing field `arguments`". The call is now replayed with an empty input. ([#28099](https://github.com/mastra-ai/mastra/pull/28099)) - Fixed `run.restart()` re-running work that had already finished when a workflow crashed inside a parallel block or a `.foreach()`. Each finished parallel arm and foreach item is now saved as soon as it completes, so a restart runs only the arms and items that had not completed. An item that finishes while the previous save is still being written can run again if the process dies before that save lands. This also stops a recovered durable agent from re-running parallel tool calls that had already finished. Fixes #26214. ([#27935](https://github.com/mastra-ai/mastra/pull/27935)) - Fixed resumed agent runs disappearing from storage before their final messages are saved. Final message-save failures now report an error instead of a successful completion. Fixes [#27445](https://github.com/mastra-ai/mastra/issues/27445). ([#28157](https://github.com/mastra-ai/mastra/pull/28157)) - Fixed runs being aborted when their thread lease lapsed only because their process fell behind. Under heavy load a renewal can go out after the lease ran out, or none can be confirmed before the lease's deadline. The run now takes the lease back instead of being aborted. It is still aborted when another run holds the thread, or when the lease backend doesn't answer within one renewal interval. ([#28079](https://github.com/mastra-ai/mastra/pull/28079)) Added a reason to runs stopped because another run took their thread lease. The `abort` chunk carries `payload.reason: 'thread-lease-lost'`, and a session's `agent_end` event carries `abortReason: 'thread-lease-lost'`: ```ts session.subscribe(event => { if (event.type === 'agent_end' && event.reason === 'aborted' && event.abortReason === 'thread-lease-lost') { console.log('Another run took over this thread.'); } }); ``` - Fixed `observe()` sending duplicate frames, and made a run's cached frames exactly what it replays. ([#27352](https://github.com/mastra-ai/mastra/pull/27352)) A durable run's frames are now appended to one log (`MastraServerCache.listPushIndexed`), which allocates the frame's position and stores it before the frame is published to the transport. The cached list is the run's history; the transport is only the live tail. A subscriber tracks one cursor: frames below it are dropped as already seen, the frame at it is delivered, and a gap is filled from the log before carrying on. This replaces the id deduplication and the follower machinery that re-stored frames the log already held, which is where the duplicate frames came from. `CachingPubSubOptions.source` and `CachingPubSub.__setSource()` are still accepted but deprecated and have no effect: evented steps now cache their frames when they publish. On the evented engine, a run's frames, abort requests and execution leases now use the Mastra-level pubsub and cache, because each step publishes from whichever worker claims it; every worker then appends to the same run log. An evented agent constructed with its own `pubsub` on a different transport logs a warning. To observe evented runs from another process, configure the shared transport on Mastra (`new Mastra({ pubsub, cache })`). The default engine is unchanged and keeps using an agent-level `pubsub` when one is set. Because every frame is in the log before it reaches the transport, a frame the transport delivers late is filled in from the log at its original position instead of being dropped. This includes reasoning from an attempt a signal preempted, which clients already see when it arrives on time; it is still kept out of memory and the replacement request. When no shared cache is configured, `observe({ offset })` is live-only: observers see the live tail but cannot resume from an offset. With the default in-memory cache and a transport that reaches other processes, evented steps publish their frames uncached, as before, because each process would otherwise number frames from its own counter. On the evented engine, a tool suspension or approval request is saved to memory before its chunk is published, as on the durable engine, so a client that reads memory as soon as it sees the suspension finds it. - Fixed tool search so complete tool names in a query rank before keyword matches, including names with hyphens and underscores. Repeated query words no longer skew tool search results. Fixes [#28113](https://github.com/mastra-ai/mastra/issues/28113). ([#28141](https://github.com/mastra-ai/mastra/pull/28141)) - Scheduler now retries a failed workflow or agent schedule dispatch up to three times before recording it as failed. On sustained publish errors, dispatch and the error callback can take up to 300 ms longer. ([#28525](https://github.com/mastra-ai/mastra/pull/28525)) - Improved the scheduler so it no longer queries storage every 10 seconds when nothing is due. It now waits until the next schedule is due, and with no active schedules it waits without any timer. Schedules created, updated or resumed through Mastra wake it right away; a schedule written straight to storage is noticed at the next wake or restart. Servers that share a store but not a pubsub (the default pubsub is per-process) only learn about schedules created elsewhere if they use a shared pubsub or turn on `scheduler: { enabled: true }`, which keeps checking at the configured interval. ([#28550](https://github.com/mastra-ai/mastra/pull/28550)) If a dedicated scheduler process must pick up schedules written by other processes or straight to storage, keep it polling: ```ts // Before: polled storage every 10 seconds by default. // After: sleeps until the next fire. Opt back in to polling where you need it. const mastra = new Mastra({ scheduler: { enabled: true } }); ``` - Fixed channel messages and tool approval actions bypassing durable execution on durable and evented agents, including channels attached after construction. ([#27944](https://github.com/mastra-ai/mastra/pull/27944)) - Fixed `mapUserToResourceId` being ignored when defined as a method on a `MastraAuthProvider` subclass. Subclass methods now work the same as passing `mapUserToResourceId` as a constructor option. ([#28297](https://github.com/mastra-ai/mastra/pull/28297)) - Fixed `onFinish.text` including replies rejected by `processOutputStep` guardrails, including discarded retry attempts on durable agents. Earlier accepted step text is preserved. Fixes [COR-1464](https://linear.app/kepler-crm/issue/COR-1464/processoutputstep-abort-cover-evented-and-compare-all-three-engines). ([#28029](https://github.com/mastra-ai/mastra/pull/28029)) - **Fixed canceled runs that hang on tools that ignore cancellation.** ([#28472](https://github.com/mastra-ai/mastra/pull/28472)) - Cancellation gives running tool executions a 250 ms grace period to settle and keeps successful results completed within that window across eager, deferred, and durable execution. - Canceled tool calls emit a `tool-result` chunk carrying a cancellation notice and record it in conversation history so the next turn knows the tool started and may still be running. Check the tool's status before retrying. Execution activity is released, and later results and failures are ignored without changing timeout errors. - Unless the caller cancels the run, retry and fallback still wait for running eager work, record its result or error, and reuse it before the replacement attempt begins. Already-started tool code and external side effects may continue after cancellation. - Fixed `mastra.shutdown()` crashing the process with an unhandled rejection when background tasks were still active. ([#28509](https://github.com/mastra-ai/mastra/pull/28509)) - Shutdown now waits, within its existing drain timeout, for running background tasks to finish cleaning up before closing storage. - Failed run cleanup and failed periodic task cleanup are logged as warnings instead of rejecting. - A background task being resumed when shutdown starts now stays `suspended`, so it can be resumed again, instead of being left `running` and later restarted from scratch. - Studio session cookies are now host-only, so a session is never sent to other deployments on a shared domain. ([#28168](https://github.com/mastra-ai/mastra/pull/28168)) `@mastra/auth-studio`: on secure deployments `MastraAuthStudio` issues `__Host-wos-session` (`Secure`, `Path=/`, no `Domain`). `MASTRA_COOKIE_DOMAIN` is no longer used as the cookie `Domain`. A legacy `wos-session` cookie is still accepted on read, but only so it can be re-issued as `__Host-wos-session`. The new `getLegacySessionClearCookies()` expires the legacy `wos-session` on the current host, on `MASTRA_COOKIE_DOMAIN`, and on each domain in `MASTRA_LEGACY_COOKIE_DOMAINS` (or the `legacyCookieDomains` option). Plain-http `localhost` dev keeps an unprefixed host-only cookie; use `secureCookies` to override. An explicit `cookieDomain` option still issues a `wos-session` cookie with that `Domain`. `@mastra/server`: the SSO callback, `POST /auth/refresh` and `POST /auth/logout` responses now also send the provider's `getLegacySessionClearCookies()`. `@mastra/factory`: the default Studio provider no longer derives `Domain=.mastra.cloud` from `publicUrl`. Its sign-in and sign-out responses also expire legacy session cookies. `@mastra/core`: `ISessionProvider` gains the optional `getLegacySessionClearCookies()`. - You can now filter and group spans by their parent or root entity, for example to count tool calls per agent or per workflow. The root is the top span recorded in the current process, so for a trace continued from another service it is the local root, not the distributed trace's root. Durable agents keep this lineage on spans that end in a later step. Spans stored before this change have no parent or root entity. ([#28047](https://github.com/mastra-ai/mastra/pull/28047)) - Fixed channel agents calling a missing `add_reaction` tool. The channel instructions now mention reacting only when the agent has the `add_reaction` tool, for example via `channels.getTools()`. ([#28067](https://github.com/mastra-ai/mastra/pull/28067)) - Fixed a run being stopped as if another run had taken its thread when its process stalled past the thread lease duration while a lease renewal was in flight. The renewal's late answer now counts as a lapse this process can recover from: the run takes its lease back when nobody else holds it, and stops if it can't. ([#28316](https://github.com/mastra-ai/mastra/pull/28316)) - Fixed tool calls reported inconsistently in `onStepFinish` and `onFinish` steps. ([#28471](https://github.com/mastra-ai/mastra/pull/28471)) - Durable and evented agents now report `toolCalls` in the same chunk shape as the normal agent, leave failed and declined calls out of `toolResults`, and keep tool results rewritten by output processors. After an approval resume, they no longer restore results the stream never emitted, such as results hidden by an output processor or pending client tool calls. Results an output processor holds until the end of the stream are still reported. - `Agent` now keeps a suspended run's per-call `outputProcessors` and applies them when `approveToolCall`, `declineToolCall` or `resumeStream` is called in the same process without its own. Before, the resumed run skipped them, so it could emit content they would have hidden or rewritten. - On every engine, when a model makes several tool calls in one step, step `content` now holds every call and its result, in call order, instead of only the last call. Failed calls keep their existing shape: a `tool-result` whose `output` is `error-text` or `error-json`. - Fixed agent streams dropping chunks when a run fails while the reader is behind. Before, a reader that consumed `fullStream` slowly, or started reading late, could lose the chunks already produced, such as the tool call and tool result, and get only the error. Now it receives every queued chunk first, then the error. This covers the default agent loop, durable agents, and late subscribers to an already-failed stream. ([#28227](https://github.com/mastra-ai/mastra/pull/28227)) - Fixed evented foreach steps keeping a `suspendedAt` timestamp after the step successfully resumed and finished. The evented engine now matches the default engine. ([#28151](https://github.com/mastra-ai/mastra/pull/28151)) - Fixed `getFullOutput().text` dropping text from the step in progress when a run suspends for tool approval or a tool suspension after an earlier step has completed. It now matches `output.text` on every engine, including resumed runs that suspend again. ([#28351](https://github.com/mastra-ai/mastra/pull/28351)) - Fixed durable and evented streams dropping the suspending step's token usage. `usage` and `totalUsage` now settle when the run suspends for a tool call or an approval, including steps that already ran, and a resume still replaces those numbers with the cumulative total. ([#27882](https://github.com/mastra-ai/mastra/pull/27882)) - Fixed spans, logs, metrics, scores and feedback reporting another entity's version. A tool or model call made by a versioned agent no longer reports the agent's version as its own `entityVersionId`, so grouping or filtering by version only counts that version's own entity. A span still reports a version when it was declared for it or inherited from a span of the same entity. Already-stored spans and signals are unchanged. Fixes #28418. ([#28423](https://github.com/mastra-ai/mastra/pull/28423)) - Fixed durable and evented agents running tools with empty arguments when the model returned malformed JSON. Unparseable tool calls now return an error to the model without executing the tool or requesting approval, while valid empty arguments and replayed calls keep working. ([#28332](https://github.com/mastra-ai/mastra/pull/28332)) - Thread lease-loss and cross-instance stop handling now skip `abortRunStream` when a run record's agent object does not implement it, instead of raising an unhandled `abortRunStream is not a function` rejection. Agents that implement `abortRunStream` are unaffected. ([#28469](https://github.com/mastra-ai/mastra/pull/28469)) - Fixed agent thread streams that misreported a run's state when pubsub events were missing. A run parked on approval no longer fails with `AgentThreadLeaseLostError` after its lease lapses, even if this subscriber never saw the approval prompt. A replayed `run-suspended` event for a run that has since finished no longer leaves the thread stuck as active. In both cases the runtime now reads the run's stored status before it decides. ([#27928](https://github.com/mastra-ai/mastra/pull/27928)) - Fixed trace scoring for agent scorers: tool calls that start in the same millisecond (parallel tool calls) now keep their original order, and tools executed by the model provider (for example Anthropic web search) are now included in the scorer's tool invocations, matching what live scoring sees. ([#27953](https://github.com/mastra-ai/mastra/pull/27953)) - Fixed `context.observe` being undefined when a tool is called directly with `tool.execute(input, { requestContext })` or run as a workflow step via `createStep(tool)`. Tools that call `observe.log(...)` or `observe.span(...)` no longer crash on these paths. `observe` falls back to a no-op when no tracing span is active. Callers of `tool.execute()` can now leave `observe` out without a type error. ([#28579](https://github.com/mastra-ai/mastra/pull/28579)) - Fixed resuming a suspended durable or evented agent with `resumeStreamUntilIdle()` (or `resumeStream(..., { untilIdle: true })`). The resume no longer stops the parked run it is taking over, so the resumed tool result is delivered instead of failing with "already finished". ([#28301](https://github.com/mastra-ai/mastra/pull/28301)) - Fixed background tasks failing with "authenticated user is required" when fine-grained authorization is configured. Agent calls also no longer require users to hold workflows:execute on internal agent workflows; only agents:execute on the agent is checked. User-defined nested workflows are still authorized as before. ([#28506](https://github.com/mastra-ai/mastra/pull/28506)) - Fixed total run deadlines not terminating durable and evented agent runs when a tool ignores cancellation. ([#27941](https://github.com/mastra-ai/mastra/pull/27941)) - Progress events from tools used with `createStep(tool)` now reach workflow and agent streams by forwarding the step writer. ([#28615](https://github.com/mastra-ai/mastra/pull/28615)) - Fixed agents saying they can't save information when working memory uses `agentManaged: false`. The instructions now say that memory updates automatically after each response. Agents acknowledge new information instead of claiming they can't save it. Only `readOnly: true` tells agents they cannot update working memory. ([#28441](https://github.com/mastra-ai/mastra/pull/28441)) - Updated dependencies [[`ce5d473`](https://github.com/mastra-ai/mastra/commit/ce5d4731eeeb44779748852618b95c5cb6485bed)]: - @mastra/schema-compat@1.3.17 ## 1.80.0-alpha.7 ### Patch Changes - Fixed Claude 5.x thinking blocks being stripped from earlier assistant messages on every request, which changed the prompt prefix each step and broke Anthropic prompt caching. The `anthropic-strip-empty-signed-reasoning-content` rule in `ProviderHistoryCompat` now runs only after Anthropic rejects the history with the "thinking blocks cannot be modified" error, so valid empty-text signed blocks are replayed unchanged. ([#28552](https://github.com/mastra-ai/mastra/pull/28552)) - **Fixed canceled runs that hang on tools that ignore cancellation.** ([#28472](https://github.com/mastra-ai/mastra/pull/28472)) - Cancellation gives running tool executions a 250 ms grace period to settle and keeps successful results completed within that window across eager, deferred, and durable execution. - Canceled tool calls emit a `tool-result` chunk carrying a cancellation notice and record it in conversation history so the next turn knows the tool started and may still be running. Check the tool's status before retrying. Execution activity is released, and later results and failures are ignored without changing timeout errors. - Unless the caller cancels the run, retry and fallback still wait for running eager work, record its result or error, and reuse it before the replacement attempt begins. Already-started tool code and external side effects may continue after cancellation. ## 1.80.0-alpha.6 ### Minor Changes - Added opt-in `withRunStatus: true` for thread subscriptions to emit ordered `thread-run-status` chunks with `success`, `failed`, `aborted`, or `suspended` outcomes without closing the subscription. Reconnecting subscribers recover outcomes retained by the PubSub backend. Initial history includes `activeRunId`, the run owning the thread during hydration, or `null` when idle. ([#28621](https://github.com/mastra-ai/mastra/pull/28621)) ```ts const subscription = await agent.subscribeToThread({ resourceId: 'user-123', threadId: 'thread-456', withRunStatus: true, }); for await (const chunk of subscription.stream) { if (chunk.type === 'thread-run-status') { console.log(chunk.runId, chunk.payload.status); } } ``` ### Patch Changes - Durable agents now honor their configured `notifications.deliveryPolicy`, and forked durable agents retain their Mastra instance so notification signals can be sent from forks. ([#28639](https://github.com/mastra-ai/mastra/pull/28639)) - Fixed structured output results and validation errors arriving after the finish event, so idle-thread subscribers receive them before the run ends. Runs that finish with an error no longer publish a final structured result or fallback value. ([#28617](https://github.com/mastra-ai/mastra/pull/28617)) Fixed finish callbacks for streams that emit an error before finish, including non-structured runs. Previously accessed result promises still reject, but no longer interrupt stream completion or prevent `onFinish` and final cleanup from running. `onFinish.finishReason` now reports `'error'` for failed runs. - Fixed `observe(runId, { offset })` replaying the whole run when the offset is at or past the end of the run's log on a transport that keeps old messages, such as Redis Streams. While the run's log is retained, the observer now skips every event below the offset and receives events once the run reaches it. If the log expired or was cleared and the run starts numbering again from 0, the observer resyncs and receives the new events from the start. ([#28348](https://github.com/mastra-ai/mastra/pull/28348)) `createEventedAgent` can still re-send events from before the offset when processes share a pubsub such as Redis Streams but each uses the default in-memory server cache. Mastra now logs a one-time warning when this happens. To resume evented agents from an offset, configure a server cache that every process shares, such as `RedisServerCache`. - Fixed `DurableAgent.recover()` reporting an initial pending snapshot without workflow input as corrupt. It now throws `DURABLE_AGENT_RECOVER_CHECKPOINT_NOT_AVAILABLE` with guidance to enable running-checkpoint persistence before starting the run, while preserving malformed-snapshot and ownership checks. ([#28611](https://github.com/mastra-ai/mastra/pull/28611)) ## 1.80.0-alpha.5 ### Minor Changes - Channel `formatError` now receives the failed run's request context as a second argument, so the message posted to Slack, Discord, Teams or Telegram can link back to whatever the run belongs to. Formatters that take only the error keep working. When a stream fails with a raw provider payload instead of an `Error`, the error passed to `formatError` keeps that payload as its `cause`, so the formatter can still tell what failed. ([#27811](https://github.com/mastra-ai/mastra/pull/27811)) ```ts channels: { adapters: { slack: { adapter: createSlackAdapter(), formatError: (error, { requestContext }) => { const ticketId = requestContext?.get('ticketId'); return ticketId ? `❌ ${error.message} (ticket ${ticketId})` : `❌ ${error.message}`; }, }, }, }, ``` - Added `classifyProviderError`, `providerErrorReason` and `toPersistedErrorIdentity` to `@mastra/core/error`: one reader for model-provider failures (authentication, quota, rate limit, context overflow, rejected model or request, timeout, outage) that takes HTTP status and provider codes before wording. A status from the GitHub API or a storage call is not mistaken for the model provider's. `providerRequestId` and `providerModelId` return the provider's request id and the model the failed request named, also from a wrapped cause, and `PROVIDER_CALL_ERROR_NAME` matches the name of the AI SDK's provider call error. ([#28496](https://github.com/mastra-ai/mastra/pull/28496)) Saved error parts now also keep every provider code as `codes` and the provider's `reason` sentence, so a reloaded thread classifies like a live one, including a failure the provider reported mid-stream. `toPersistedErrorIdentity(error)` returns that saved shape, so anything that sends an error to a client can send the same one. The `reason` comes from the provider's response, including a string `detail` field and a response wrapped as another error's cause; any other cause stays out of thread history, and a long reason is cut at 2,000 characters. Each kind names a recovery action. New kinds can arrive in minor releases, and no kind is renamed or removed within 1.x, so keep a default branch when you switch on one: ```ts import { classifyProviderError } from '@mastra/core/error'; switch (classifyProviderError(error)) { case 'rate_limited': // wait, then retry break; case 'quota': // ask for more credit break; default: // show the provider's reason } ``` - Fixed stored span previews to preserve intentionally empty strings instead of replacing them with payload content. ([#28442](https://github.com/mastra-ai/mastra/pull/28442)) Added `buildSpanPreviews()` to generate input, output, and error previews from a processed span. Support is advertised by the `observability-span-previews` core feature flag. ```ts import { buildSpanPreviews } from '@mastra/core/observability'; const previews = buildSpanPreviews(span); ``` - Agent run spans now show the `model` and `provider` the run was configured with, on regular, durable and evented agents, including resumed runs. Model step spans gained the same two fields for the model each step actually called. ([#28485](https://github.com/mastra-ai/mastra/pull/28485)) - Added opt-in shared-channel tool approvals while keeping requester-only approval as the default. Configure `new SlackProvider({ toolApprovers: 'anyone' })` to allow channel members to approve or deny pending tools. ([#28542](https://github.com/mastra-ai/mastra/pull/28542)) - Added `AgentController.getDefaultMode()`, which returns the mode new sessions start in. It follows `defaultModeId`, then a mode marked as default, then the first mode, so you can read the starting model without creating a session. ([#28402](https://github.com/mastra-ai/mastra/pull/28402)) ```ts const mode = controller.getDefaultMode(); console.log(mode.id, mode.defaultModelId); ``` - Removed `model` and `provider` from thread rows returned by `queryThreads()` with a `timeRange` request. A thread can contain many model calls, so a single model and provider was misleading, and looking them up made thread pages slow. ([#28595](https://github.com/mastra-ai/mastra/pull/28595)) Thread pages now default to 25 rows and allow at most 100 (previously 100 and 1,000). Requests with a larger `page.limit` now fail validation. Use `page.next` to load further pages. **Before** ```ts const { threads } = await client.queryTraceThreads({ timeRange: { traces: range }, page: { limit: 500 } }); threads[0].model; // "gpt-4o" ``` **After** ```ts const { threads, page } = await client.queryTraceThreads({ timeRange: { traces: range }, page: { limit: 100 } }); // threads[0].model no longer exists; query the thread's spans for model usage const next = await client.queryTraceThreads({ timeRange: { traces: range }, page: { limit: 100, after: page.next! }, }); ``` ### Patch Changes - Permission checks in server handlers now use the same provider as route-level checks. If you configure access control only under `studio`, Studio requests are checked against it, including thread access. A request with no applicable provider is denied instead of allowed. ([#28543](https://github.com/mastra-ai/mastra/pull/28543)) - `StreamErrorRetryProcessor` now decides retries with the shared provider-failure reader from `@mastra/core/error`, so it agrees with every other place Mastra reads a model-provider failure. It retries rate limits, provider outages and timeouts, and never retries a credential or quota refusal. ([#28496](https://github.com/mastra-ai/mastra/pull/28496)) Fixed an exhausted quota being retried when the provider returned it as a retryable `429`. A stream error frame that reports an exhausted quota under a rate-limit code is no longer retried either, and `isBadRequestError` no longer matches a `400` that reports an exhausted credit balance, a usage cap, or a rejected API key. Your own `matchers` now run before the built-in OpenAI Responses matcher, so their `maxRetries`, `delayMs` and `onRetry` apply when both match a stream error. - A saved error part of a failed provider call now keeps `modelId`, the model the request named, so a reloaded thread can say which model failed. Only the model name is read from the request; the prompt and the rest of the request body are still never saved. ([#27811](https://github.com/mastra-ai/mastra/pull/27811)) `JsonReadyAgentControllerEvent` now types a flattened error with the optional `statusCode`, `responseBody`, `requestId` and `modelId` that a saved error part keeps, so clients can read them from a live event without a cast. - Fixed durable agent streams with `closeOnSuspend: true` closing before they deliver their chunks when the agent uses Redis Streams PubSub. For example, the model can request two approval tool calls in one step. If you approve the first with `resumeStream(..., { closeOnSuspend: true })`, the resumed stream now includes the tool result and the second approval request instead of closing empty. Fixes #28479. ([#28504](https://github.com/mastra-ai/mastra/pull/28504)) - Fixed `observe()` on durable and evented agents throwing `Cannot read private member #options` when reading `fullStream`. Destructured stream methods such as `const { consumeStream } = output` also work on durable and evented agents now. ([#28531](https://github.com/mastra-ai/mastra/pull/28531)) - Fixed cancelling an evented workflow run with workers on several replicas. Previously only the replica that received `workflow.cancel` aborted its steps, so a step running on another replica never saw its `abortSignal` fire and kept running. Cancelling now reaches every replica running a step of the run, including steps of nested workflows. ([#28489](https://github.com/mastra-ai/mastra/pull/28489)) - Fixed orphaned tool results causing requests to fail when switching model providers after hosted tool calls. Matching calls and results are now removed together from outgoing history. Fixes [#28190](https://github.com/mastra-ai/mastra/issues/28190). ([#28286](https://github.com/mastra-ai/mastra/pull/28286)) - Corrected the `maxProcessorRetries` doc comment on per-call agent options. It said that leaving it unset meant no retries. In fact, error processors such as `StreamErrorRetryProcessor` are capped at 3 retries when no cap is set. ([#28576](https://github.com/mastra-ai/mastra/pull/28576)) - Fixed lost task updates when task tools run concurrently in the same thread. Fixes https://github.com/mastra-ai/mastra/issues/28268. ([#28294](https://github.com/mastra-ai/mastra/pull/28294)) - Fixed background tasks cancelled from another process continuing to run. When a task was cancelled on a different worker sharing the same storage, the worker running it marked the task cancelled but let the tool keep executing to completion. The running tool's abort signal now fires on that worker's next lease renewal, so the tool stops as cancelled. ([#28523](https://github.com/mastra-ai/mastra/pull/28523)) - Fixed missing token usage in streamed responses from Kimi coding-plan models. ([#28537](https://github.com/mastra-ai/mastra/pull/28537)) - Fixed a run that lost its thread lease cutting off the step it was in the middle of: a model response still streaming, or a tool call such as a long command. The run now starts no new step, lets the current one finish, fail or be aborted, keeps what it produced, and then stops. This applies to standard, durable and evented runs whose steps run in the process that holds the thread lease. A durable run whose steps run in another worker process, such as the Inngest durable agent, still stops as soon as its lease is lost. A step still running after `MASTRA_AGENT_THREAD_LEASE_LOST_TOOL_GRACE_MS` (default 5 minutes) is aborted as before. In a deployment with several processes, this means the finishing step can still save its output to the thread for up to that long after another run has taken the thread over. ([#28359](https://github.com/mastra-ai/mastra/pull/28359)) A lease backend that is slow or doesn't answer no longer stops a run that has a step running, including while it hands the lease back to the run. The run keeps trying to take its lease back and starts no tool until it has it. It gives up when the backend refuses it the lease, which normally means another run holds the thread, or when the backend has not answered for `MASTRA_AGENT_THREAD_LEASE_LOST_TOOL_GRACE_MS` after the lease ran out. Giving up counts as losing the lease: a step still running then gets its own grace period to finish. So with a silent backend, a step can run for up to two grace periods after the lease ran out. An abort still stops the run at once. A durable run can still be stopped by its own execution claim. - Fixed an unhandled rejection when a run lost its thread lease and its agent could not abort the run. Background lease-loss callbacks now contain abort failures. Remote abort requests still report failures so transports that support retries can deliver the request again. ([#28344](https://github.com/mastra-ai/mastra/pull/28344)) - Fixed servers staying busy forever after using deferred notifications. A saved notification dispatch schedule no longer starts the scheduler on boot unless a deferred notification is still waiting. The once-a-minute dispatch schedule now removes itself after it delivers the last pending notification, and the next deferred notification brings it back. ([#28550](https://github.com/mastra-ai/mastra/pull/28550)) - You can now choose which attachment types the Observational Memory Observer receives: `observation.observeAttachments` accepts a MIME type allowlist (`string[]`) in the public types, matching the runtime (images and PDFs by default). Also corrected the `model` option docs: the default is `'auto'`, falling back from the top-level `model` to the other sub-config's `model`, not `'google/gemini-2.5-flash'`. ([#28556](https://github.com/mastra-ai/mastra/pull/28556)) - Fixed processor hooks receiving `args.agent` as `undefined` during regular, durable and evented agent runs. Hooks such as `processInputStep`, `processOutputStream` and `processToolResult` now receive the running agent, like `processInput` already did. ([#28463](https://github.com/mastra-ai/mastra/pull/28463)) - Log claimed schedule occurrences and accepted publications with their schedule ID, run ID, and scheduled fire time. Report uncertain delivery after publication retries are exhausted. Document best-effort delivery and continued recurring fires without automatically replaying uncertain occurrences. ([#28544](https://github.com/mastra-ai/mastra/pull/28544)) - Added optional `batchUpdateUnendedSpans()` to observability storage. It updates only spans that are stored and haven't ended, never creates rows, and returns the spans it changed. The in-memory store implements it. Also added the `span-not-updatable` drop reason for span updates that didn't apply. Part of the fix for [#27479](https://github.com/mastra-ai/mastra/issues/27479). ([#28147](https://github.com/mastra-ai/mastra/pull/28147)) ```ts const store = await mastra.getStorage()?.getStore('observability'); if (typeof store?.batchUpdateUnendedSpans === 'function') { const { updated } = await store.batchUpdateUnendedSpans({ records: [{ traceId, spanId, updates: { endedAt: new Date() } }], }); } ``` - Fixed default workflow runs continuing to later steps and overwriting their status with success after another process canceled the run through shared storage. This covers sequential steps, sleeps, resumed parallel branches, and `dountil`, `dowhile` and `foreach` loops. Loops now stop once they see the cancellation instead of running to completion. ([#27904](https://github.com/mastra-ai/mastra/pull/27904)) Added an optional `expectedStatus` guard to `persistWorkflowSnapshot`. The in-memory, LibSQL, PostgreSQL, MySQL, MSSQL, DSQL and Cloudflare D1 stores check it in the same statement as the write, so they can't overwrite a cancellation. Other stores, such as MongoDB, read the run status just before writing, which leaves a short window where a cancellation can still be lost. Custom workflow stores that honor the guard atomically should return `true` from `supportsGuardedSnapshotWrites()`: ```ts const written = await store.persistWorkflowSnapshot({ workflowName, runId, snapshot, expectedStatus: ['running', 'success'], }); // `written` is false when the stored status is not in the list ``` Added `loadWorkflowRunStatus()`, which reads only a run's status. Stores that don't override it fall back to loading the snapshot. - Scheduler now retries a failed workflow or agent schedule dispatch up to three times before recording it as failed. On sustained publish errors, dispatch and the error callback can take up to 300 ms longer. ([#28525](https://github.com/mastra-ai/mastra/pull/28525)) - Improved the scheduler so it no longer queries storage every 10 seconds when nothing is due. It now waits until the next schedule is due, and with no active schedules it waits without any timer. Schedules created, updated or resumed through Mastra wake it right away; a schedule written straight to storage is noticed at the next wake or restart. Servers that share a store but not a pubsub (the default pubsub is per-process) only learn about schedules created elsewhere if they use a shared pubsub or turn on `scheduler: { enabled: true }`, which keeps checking at the configured interval. ([#28550](https://github.com/mastra-ai/mastra/pull/28550)) If a dedicated scheduler process must pick up schedules written by other processes or straight to storage, keep it polling: ```ts // Before: polled storage every 10 seconds by default. // After: sleeps until the next fire. Opt back in to polling where you need it. const mastra = new Mastra({ scheduler: { enabled: true } }); ``` - Fixed `mastra.shutdown()` crashing the process with an unhandled rejection when background tasks were still active. ([#28509](https://github.com/mastra-ai/mastra/pull/28509)) - Shutdown now waits, within its existing drain timeout, for running background tasks to finish cleaning up before closing storage. - Failed run cleanup and failed periodic task cleanup are logged as warnings instead of rejecting. - A background task being resumed when shutdown starts now stays `suspended`, so it can be resumed again, instead of being left `running` and later restarted from scratch. - Fixed tool calls reported inconsistently in `onStepFinish` and `onFinish` steps. ([#28471](https://github.com/mastra-ai/mastra/pull/28471)) - Durable and evented agents now report `toolCalls` in the same chunk shape as the normal agent, leave failed and declined calls out of `toolResults`, and keep tool results rewritten by output processors. After an approval resume, they no longer restore results the stream never emitted, such as results hidden by an output processor or pending client tool calls. Results an output processor holds until the end of the stream are still reported. - `Agent` now keeps a suspended run's per-call `outputProcessors` and applies them when `approveToolCall`, `declineToolCall` or `resumeStream` is called in the same process without its own. Before, the resumed run skipped them, so it could emit content they would have hidden or rewritten. - On every engine, when a model makes several tool calls in one step, step `content` now holds every call and its result, in call order, instead of only the last call. Failed calls keep their existing shape: a `tool-result` whose `output` is `error-text` or `error-json`. - Thread lease-loss and cross-instance stop handling now skip `abortRunStream` when a run record's agent object does not implement it, instead of raising an unhandled `abortRunStream is not a function` rejection. Agents that implement `abortRunStream` are unaffected. ([#28469](https://github.com/mastra-ai/mastra/pull/28469)) - Fixed `context.observe` being undefined when a tool is called directly with `tool.execute(input, { requestContext })` or run as a workflow step via `createStep(tool)`. Tools that call `observe.log(...)` or `observe.span(...)` no longer crash on these paths. `observe` falls back to a no-op when no tracing span is active. Callers of `tool.execute()` can now leave `observe` out without a type error. ([#28579](https://github.com/mastra-ai/mastra/pull/28579)) - Fixed background tasks failing with "authenticated user is required" when fine-grained authorization is configured. Agent calls also no longer require users to hold workflows:execute on internal agent workflows; only agents:execute on the agent is checked. User-defined nested workflows are still authorized as before. ([#28506](https://github.com/mastra-ai/mastra/pull/28506)) - Progress events from tools used with `createStep(tool)` now reach workflow and agent streams by forwarding the step writer. ([#28615](https://github.com/mastra-ai/mastra/pull/28615)) ## 1.80.0-alpha.4 ### Minor Changes - `@mastra/core/workspace` now exports `getMimeType`, the extension-based lookup `LocalFilesystem` uses, so custom filesystem providers can fill in `FileStat.mimeType` the same way. ([#28302](https://github.com/mastra-ai/mastra/pull/28302)) ```ts import { getMimeType } from '@mastra/core/workspace'; getMimeType('page.png'); // 'image/png' getMimeType('notes.unknown'); // 'application/octet-stream' ``` - Added an opt-in `onMessage` channel handler for messages that do not mention the bot. Call `defaultHandler` to run the agent, post its reply, and subscribe the thread. Unmentioned messages stay ignored when the handler is omitted or `false`. ([#28285](https://github.com/mastra-ai/mastra/pull/28285)) ```ts import type { ChannelHandlers } from '@mastra/core/channels'; const handlers: ChannelHandlers = { onMessage: async (thread, message, defaultHandler) => { if (thread.channelId !== 'slack:C_SUPPORT') return; await defaultHandler(thread, message); }, }; ``` Runs started through `onMessage` report `channel.eventType` as `message`. Existing event types are unchanged. When a queued batch mixes mentioned and unmentioned senders, unmentioned senders go to `onMessage` only if it is set. Otherwise they still reach `onMention`, as before. Fixes https://github.com/mastra-ai/mastra/issues/27060. ### Patch Changes - dependencies updates: ([#28417](https://github.com/mastra-ai/mastra/pull/28417)) - Updated dependency [`chat@^4.43.0` ↗︎](https://www.npmjs.com/package/chat/v/4.43.0) (from `^4.37.0`, in `dependencies`) - Updated dependency [`dotenv@^17.4.2` ↗︎](https://www.npmjs.com/package/dotenv/v/17.4.2) (from `^17.3.1`, in `dependencies`) - Updated dependency [`fastq@^1.20.3` ↗︎](https://www.npmjs.com/package/fastq/v/1.20.3) (from `^1.20.1`, in `dependencies`) - Updated dependency [`hono@^4.13.13` ↗︎](https://www.npmjs.com/package/hono/v/4.13.13) (from `^4.13.7`, in `dependencies`) - Updated dependency [`hono-openapi@^1.5.0` ↗︎](https://www.npmjs.com/package/hono-openapi/v/1.5.0) (from `^1.3.1`, in `dependencies`) - Updated dependency [`ignore@^7.0.14` ↗︎](https://www.npmjs.com/package/ignore/v/7.0.14) (from `^7.0.5`, in `dependencies`) - Updated dependency [`lru-cache@^11.5.3` ↗︎](https://www.npmjs.com/package/lru-cache/v/11.5.3) (from `^11.2.7`, in `dependencies`) - Updated dependency [`p-map@^7.1.1` ↗︎](https://www.npmjs.com/package/p-map/v/7.1.1) (from `^7.0.4`, in `dependencies`) - Updated dependency [`picomatch@^4.0.7` ↗︎](https://www.npmjs.com/package/picomatch/v/4.0.7) (from `^4.0.3`, in `dependencies`) - Updated dependency [`posthog-node@^5.57.1` ↗︎](https://www.npmjs.com/package/posthog-node/v/5.57.1) (from `^5.46.1`, in `dependencies`) - Updated dependency [`re2js@^2.9.0` ↗︎](https://www.npmjs.com/package/re2js/v/2.9.0) (from `^2.8.6`, in `dependencies`) - Updated dependency [`ws@^8.22.0` ↗︎](https://www.npmjs.com/package/ws/v/8.22.0) (from `^8.21.3`, in `dependencies`) - Updated dependency [`yaml@^2.9.1` ↗︎](https://www.npmjs.com/package/yaml/v/2.9.1) (from `^2.9.0`, in `dependencies`) - Updated dependency [`@ai-sdk/azure@^2.0.143` ↗︎](https://www.npmjs.com/package/@ai-sdk/azure/v/2.0.143) (from `^2.0.142`, in `devDependencies`) - Updated dependency [`@babel/core@^8.0.9` ↗︎](https://www.npmjs.com/package/@babel/core/v/8.0.9) (from `^8.0.1`, in `devDependencies`) - Updated dependency [`@copilotkit/aimock@^1.46.0` ↗︎](https://www.npmjs.com/package/@copilotkit/aimock/v/1.46.0) (from `^1.29.0`, in `devDependencies`) - Updated dependency [`@types/node@22.19.15` ↗︎](https://www.npmjs.com/package/@types/node/v/22.19.15) (from `22.20.5`, in `devDependencies`) - Updated dependency [`@types/ws@^8.18.2` ↗︎](https://www.npmjs.com/package/@types/ws/v/8.18.2) (from `^8.18.1`, in `devDependencies`) - Updated dependency [`arktype@^2.2.9` ↗︎](https://www.npmjs.com/package/arktype/v/2.2.9) (from `^2.2.0`, in `devDependencies`) - Updated dependency [`rollup@^4.65.0` ↗︎](https://www.npmjs.com/package/rollup/v/4.65.0) (from `^4.64.10`, in `devDependencies`) - Updated dependency [`vscode-languageserver-protocol@^3.18.5` ↗︎](https://www.npmjs.com/package/vscode-languageserver-protocol/v/3.18.5) (from `^3.18.4`, in `devDependencies`) - Fixed durable and evented agents dropping a background task's result when the task had been dispatched but no worker had picked it up yet. The end-of-step background check counted only `running` tasks. On a pubsub with delivery latency, a just-dispatched task was still `pending`, so the agent stopped waiting and the task's result never reached the stream. Pending tasks now count as outstanding. ([#28228](https://github.com/mastra-ai/mastra/pull/28228)) - Fixed foreground command exceptions to include a failed exit code in the returned output. ([#28406](https://github.com/mastra-ai/mastra/pull/28406)) - Fixed recovery of ToolSearchProcessor-loaded tools after a crash in an evented agent, with either `in-memory` or `context` storage. Recovery now reads the transcript from the active iteration's run, so the interrupted tool call resumes with its original ID instead of failing with `Tool not found`. ([#28295](https://github.com/mastra-ai/mastra/pull/28295)) Evented `recover()` and `observe()` now restore the latest transcript saved during the iteration instead of the one the iteration started with. Fixes https://github.com/mastra-ai/mastra/issues/28171. - Fixed `processInput()` receiving `agent: undefined` in every input processor after the first. An agent with two or more input processors now passes the running agent to each of them, as `ProcessInputArgs.agent` documents. This matters most with memory enabled, because memory processors run first and configured processors never received the agent. Fixes #28349. ([#28443](https://github.com/mastra-ai/mastra/pull/28443)) - Exported durable-agent helpers that `@mastra/inngest` reuses instead of copying: `applyReservedMemoryContextKeys`, `emitAbortEvent`, `createDurableIsTaskCompleteStep` and `createDurableGoalStep` from `@mastra/core/agent/durable`, and `createTimeoutAbortSignal` from `@mastra/core/loop`. ([#28232](https://github.com/mastra-ai/mastra/pull/28232)) Fixed `observe().abort()` on a durable run parked on a tool approval or suspension: it now releases the run like the stream `abort()` does, so a later `approveToolCall()` or `resumeStream()` is rejected instead of continuing the aborted run. Fixed an empty step text on durable agents when an output processor holds parts (such as `BatchPartsProcessor`) and the `step-start` event reached the stream after those parts were released. The run that holds the output processors now runs them on `step-start` itself, like every other chunk, so each processor sees it exactly once. A processor that throws on `step-start` now fails the run with an `error` chunk and `finishReason: 'error'`, matching a throw on any other chunk, instead of throwing from `fullStream`. - Fixed tool hooks passed to `agent.stream()` and `agent.generate()` being ignored in durable and evented agents. Per-call `beforeToolCall` and `afterToolCall` hooks now take priority over agent-level hooks. Hooks in `defaultOptions.hooks` also run in these engines. ([#27459](https://github.com/mastra-ai/mastra/pull/27459)) Per-call hooks are functions and only live in the process that received the call. If a run resumes in a different process, only constructor hooks and `defaultOptions.hooks` run, so put hooks you always need there. ```ts await agent.stream('Look up the order', { hooks: { beforeToolCall: () => ({ proceed: false, output: { status: 'mocked' } }), }, }); ``` - Fixed evented workflow runs that never finished after being cancelled from another Mastra instance (or after `startAsync()`). The run was marked `canceled` in storage, but the owner's `run.start()` never resolved and watchers never got a finish event. Cancelling now resolves `start()` with a `canceled` result, and the run emits a single `workflow.end`. ([#28434](https://github.com/mastra-ai/mastra/pull/28434)) `run.cancel()` on an evented run now rejects if the cancel event can't be published. The run is only marked `canceled` once the cancel is published, so you can call `cancel()` again from any instance to retry. - Fixed `response.id` being lost when a model provider (for example OpenRouter) streams response metadata across several partial events. A later event that omits a field no longer erases the value an earlier event supplied, so `onStepFinish`, `onFinish` and `result.response` keep the provider's response id. Durable and evented agents now also report the provider's response id, model id and timestamp in `onStepFinish` and `onFinish`. ([#26660](https://github.com/mastra-ai/mastra/pull/26660)) - Fixed a call that passes `hooks: { beforeToolCall: null }` (for example from a JSON request body) turning off the agent's own tool hooks. Non-function values in call-time `hooks` are now ignored, so constructor hooks and `defaultOptions.hooks` still run. ([#27459](https://github.com/mastra-ai/mastra/pull/27459)) - Fixed a TypeScript error when passing steps to `.parallel()` whose `inputSchema` only uses part of the previous step's output. `.parallel()` now accepts the same steps as `.then()`: the previous output only has to include the fields each step needs. Steps that need a field the previous output doesn't have are still rejected. Runtime behavior is unchanged. ([#28225](https://github.com/mastra-ai/mastra/pull/28225)) ```ts const workflow = createWorkflow({ ... }) .then(producesValueAndPrompt) // outputs { value: number; prompt: string } .parallel([readsValue, readsPrompt]); // inputs { value: number } and { prompt: string } — now type-checks ``` - Fixed agent runs that reported an API error instead of an abort when the run was cancelled while an error processor was still handling that error. ([#28158](https://github.com/mastra-ai/mastra/pull/28158)) - Fixed `read_file` returning only metadata for `.jsonl`, `.php` and `.dart` files. Their MIME types (`application/jsonl`, `application/x-php`, `application/dart`) are now treated as text, so the file content is returned. ([#28462](https://github.com/mastra-ai/mastra/pull/28462)) - Fixed `run.restart()` re-running work that had already finished when a workflow crashed inside a parallel block or a `.foreach()`. Each finished parallel arm and foreach item is now saved as soon as it completes, so a restart runs only the arms and items that had not completed. An item that finishes while the previous save is still being written can run again if the process dies before that save lands. This also stops a recovered durable agent from re-running parallel tool calls that had already finished. Fixes #26214. ([#27935](https://github.com/mastra-ai/mastra/pull/27935)) - Fixed resumed agent runs disappearing from storage before their final messages are saved. Final message-save failures now report an error instead of a successful completion. Fixes [#27445](https://github.com/mastra-ai/mastra/issues/27445). ([#28157](https://github.com/mastra-ai/mastra/pull/28157)) - Fixed spans, logs, metrics, scores and feedback reporting another entity's version. A tool or model call made by a versioned agent no longer reports the agent's version as its own `entityVersionId`, so grouping or filtering by version only counts that version's own entity. A span still reports a version when it was declared for it or inherited from a span of the same entity. Already-stored spans and signals are unchanged. Fixes #28418. ([#28423](https://github.com/mastra-ai/mastra/pull/28423)) - Fixed agents saying they can't save information when working memory uses `agentManaged: false`. The instructions now say that memory updates automatically after each response. Agents acknowledge new information instead of claiming they can't save it. Only `readOnly: true` tells agents they cannot update working memory. ([#28441](https://github.com/mastra-ai/mastra/pull/28441)) - Updated dependencies [[`ce5d473`](https://github.com/mastra-ai/mastra/commit/ce5d4731eeeb44779748852618b95c5cb6485bed)]: - @mastra/schema-compat@1.3.17-alpha.0 ## 1.80.0-alpha.3 ### Patch Changes - Added a `ProviderHistoryCompat` safeguard that sends an assistant tool call whose stored input is missing or `null` as `{}` on every provider, so a stored row without input can't make providers reject the replayed history. The stored message is unchanged, the call keeps its error result, and an empty-string input is left as it is. ([#28332](https://github.com/mastra-ai/mastra/pull/28332)) - Restore text streamed before tool approval in resumed durable and evented agent output. Streams configured to close on suspension settle their partial results; streams kept open continue through approval. Earlier completed iterations are not added to the resumed stream's `steps`. ([#27582](https://github.com/mastra-ai/mastra/pull/27582)) - Added optional model release dates to provider catalogs for newest-first model selection. ([#27661](https://github.com/mastra-ai/mastra/pull/27661)) - **Fixed** removed messages coming back into the request when several input processors run in one step. ([#28369](https://github.com/mastra-ai/mastra/pull/28369)) When an input processor removed messages in `processInputStep`, as observational memory does when it compacts a long run, the processors after it were still handed the old `messages` array. A processor that returned that array put the removed messages back, so the prompt kept growing even while memory compacted it. Each processor is now handed the messages as they are after the processors before it ran. - Fixed messages sent from another process to a busy agent thread waiting until the current run ended. ([#28298](https://github.com/mastra-ai/mastra/pull/28298)) - **Messages join the running run:** a message delivered to a thread's claimed owner while it is running now reaches the run's next model request, the same as a message sent from the owner's own process. - **Queued messages still wait:** messages queued with `queueSignal` or `queueMessage` still wait for the run to finish. - **Both processes need this release:** a message joins the running run only when the sending process and the process that owns the thread both run this version. With an older sender or an older owner, it waits for the run to finish as before. - Fixed `observe()` sending duplicate frames, and made a run's cached frames exactly what it replays. ([#27352](https://github.com/mastra-ai/mastra/pull/27352)) A durable run's frames are now appended to one log (`MastraServerCache.listPushIndexed`), which allocates the frame's position and stores it before the frame is published to the transport. The cached list is the run's history; the transport is only the live tail. A subscriber tracks one cursor: frames below it are dropped as already seen, the frame at it is delivered, and a gap is filled from the log before carrying on. This replaces the id deduplication and the follower machinery that re-stored frames the log already held, which is where the duplicate frames came from. `CachingPubSubOptions.source` and `CachingPubSub.__setSource()` are still accepted but deprecated and have no effect: evented steps now cache their frames when they publish. On the evented engine, a run's frames, abort requests and execution leases now use the Mastra-level pubsub and cache, because each step publishes from whichever worker claims it; every worker then appends to the same run log. An evented agent constructed with its own `pubsub` on a different transport logs a warning. To observe evented runs from another process, configure the shared transport on Mastra (`new Mastra({ pubsub, cache })`). The default engine is unchanged and keeps using an agent-level `pubsub` when one is set. Because every frame is in the log before it reaches the transport, a frame the transport delivers late is filled in from the log at its original position instead of being dropped. This includes reasoning from an attempt a signal preempted, which clients already see when it arrives on time; it is still kept out of memory and the replacement request. When no shared cache is configured, `observe({ offset })` is live-only: observers see the live tail but cannot resume from an offset. With the default in-memory cache and a transport that reaches other processes, evented steps publish their frames uncached, as before, because each process would otherwise number frames from its own counter. On the evented engine, a tool suspension or approval request is saved to memory before its chunk is published, as on the durable engine, so a client that reads memory as soon as it sees the suspension finds it. - Fixed a run being stopped as if another run had taken its thread when its process stalled past the thread lease duration while a lease renewal was in flight. The renewal's late answer now counts as a lapse this process can recover from: the run takes its lease back when nobody else holds it, and stops if it can't. ([#28316](https://github.com/mastra-ai/mastra/pull/28316)) - Fixed `getFullOutput().text` dropping text from the step in progress when a run suspends for tool approval or a tool suspension after an earlier step has completed. It now matches `output.text` on every engine, including resumed runs that suspend again. ([#28351](https://github.com/mastra-ai/mastra/pull/28351)) - Fixed durable and evented agents running tools with empty arguments when the model returned malformed JSON. Unparseable tool calls now return an error to the model without executing the tool or requesting approval, while valid empty arguments and replayed calls keep working. ([#28332](https://github.com/mastra-ai/mastra/pull/28332)) - Fixed agent thread streams that misreported a run's state when pubsub events were missing. A run parked on approval no longer fails with `AgentThreadLeaseLostError` after its lease lapses, even if this subscriber never saw the approval prompt. A replayed `run-suspended` event for a run that has since finished no longer leaves the thread stuck as active. In both cases the runtime now reads the run's stored status before it decides. ([#27928](https://github.com/mastra-ai/mastra/pull/27928)) ## 1.80.0-alpha.2 ### Patch Changes - Fixed `mapUserToResourceId` being ignored when defined as a method on a `MastraAuthProvider` subclass. Subclass methods now work the same as passing `mapUserToResourceId` as a constructor option. ([#28297](https://github.com/mastra-ai/mastra/pull/28297)) ## 1.80.0-alpha.1 ### Minor Changes - Added `agentModel` to `dataset.startExperiment()` and `dataset.startExperimentAsync()` so you can run an agent experiment against a different model without editing or versioning the agent. The model is passed with each call the experiment makes: the registered agent, its sub-agents and other experiments running at the same time keep their own models, and the agent version is picked the same way as without `agentModel`. ([#28165](https://github.com/mastra-ai/mastra/pull/28165)) ```ts await dataset.startExperiment({ targetType: 'agent', targetId: 'support-agent', agentModel: 'anthropic/claude-haiku-5-5', scorers: ['accuracy'], }); ``` The experiment's `metadata.agentModel` and each run's trace metadata record the model (for example `anthropic/claude-haiku-5-5`), so runs on different models can be told apart. `agentModel` works only with agent targets and is rejected before an experiment record is created otherwise. ### Patch Changes - Allow server-owned HTTP observers of durable and evented `untilIdle` runs to detach without aborting execution, including resumed streams. Detached loops continue processing background tasks and saving the final answer, while explicit run and thread cancellation remain effective. ([#28162](https://github.com/mastra-ai/mastra/pull/28162)) Direct in-process reader cancellation still aborts active execution by default. The server opts into observer-only cancellation internally; ordinary agents retain their existing cancellation behavior. - Fixed durable and evented agents recording raw provider usage on model_step spans. Cache reads now appear under inputDetails.cacheRead like the regular agent, so prompt-cache dashboards no longer show 0% for these agents. ([#28239](https://github.com/mastra-ai/mastra/pull/28239)) - Fixed evented agent runs with Postgres storage that hung when a worker process stopped. Another worker can now finish the run, and the original caller gets the result. Completed loop iterations keep their saved results and state without running again. Recovery-only snapshots are removed when a workflow suspends without snapshot persistence. Snapshot pruning still applies at terminal and control-flow boundaries without discarding inputs needed for recovery. ([#28054](https://github.com/mastra-ai/mastra/pull/28054)) Runs started on older versions can finish after upgrading, but do not have guaranteed crash recovery. On stores that support workflow step guards, internal workflow events go through the shared broker instead of staying in the starting process. Runs with `shouldPersistSnapshot` disabled keep a recovery snapshot until they end or suspend. Storage adapters can opt in by returning `true` from `supportsWorkflowStepGuards()`. Their `updateWorkflowResults` implementation must check the optional `startGuard` or `completionGuard` against the stored result in the same atomic operation that writes the updated result. `@mastra/core/storage` exports `assertWorkflowStepGuard`, `WorkflowStepConflictError`, and `isWorkflowStepConflictError` for implementing these checks. - Fixed missing provider details on failed model calls. When a model call fails with an HTTP error, the error part saved to the thread keeps the `statusCode`, the provider's `responseBody` (capped at 2,000 characters) and the provider `requestId`, alongside `name` and `message`. Chat channels (Slack, Discord, …) now post the provider's explanation instead of just the HTTP status text, for example `❌ Error: Bad Request (HTTP 400): prompt is too long (request id req_123)`. Server logs get the same message plus `statusCode` and `requestId`, but not the raw response body. ([#27906](https://github.com/mastra-ai/mastra/pull/27906)) - You can now filter and group spans by their parent or root entity, for example to count tool calls per agent or per workflow. The root is the top span recorded in the current process, so for a trace continued from another service it is the local root, not the distributed trace's root. Durable agents keep this lineage on spans that end in a later step. Spans stored before this change have no parent or root entity. ([#28047](https://github.com/mastra-ai/mastra/pull/28047)) - Fixed resuming a suspended durable or evented agent with `resumeStreamUntilIdle()` (or `resumeStream(..., { untilIdle: true })`). The resume no longer stops the parked run it is taking over, so the resumed tool result is delivered instead of failing with "already finished". ([#28301](https://github.com/mastra-ai/mastra/pull/28301)) ## 1.80.0-alpha.0 ### Minor Changes - Added render-only tools that stream display calls and let agents and sub-agents continue without a client result. Use `renderOnly: true` when the browser only needs to show the call: ([#28082](https://github.com/mastra-ai/mastra/pull/28082)) ```typescript import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const showCard = createTool({ id: 'show-card', description: 'Display a product card', inputSchema: z.object({ productId: z.string() }), renderOnly: true, }); ``` - Added a `textDisplay` channel adapter option. Set `textDisplay: 'hidden'` to stop posting the agent's reply text, for example when the agent replies through a tool you give it. Tool display, approval prompts, errors and typing status still show, and the text still reaches the agent's stream and memory. ([#28067](https://github.com/mastra-ai/mastra/pull/28067)) ```ts channels: { adapters: { slack: { adapter: createSlackAdapter(), textDisplay: 'hidden', }, }, }, ``` - Added `get(pid, { release: false })` to the sandbox process manager. It reads a process without dismissing it once it has exited, so one reader can look at a finished command's output without taking it from another, such as the agent that started it. The default is unchanged. ([#27919](https://github.com/mastra-ai/mastra/pull/27919)) ```ts // Show a finished command's output without dismissing it const handle = await sandbox.processes.get(pid, { release: false }); console.log(handle?.stdout); ``` - Thread subscriptions now publish a pending user-message chunk when a steer is accepted, so subscribers can present the message before the agent reads it. ([#28204](https://github.com/mastra-ai/mastra/pull/28204)) Queued follow-ups publish queued user-message chunks when accepted and `data-user-message-cancelled` chunks when cancelled. - Agent calls to OpenAI-compatible Chat Completions providers, including router models that resolve to a Chat Completions endpoint (such as `'groq/...'` or a custom `url`), now send all leading system instructions as one system message. Setting `errorProcessorDefaults: false` turns this merge off. ([#27626](https://github.com/mastra-ai/mastra/pull/27626)) `processLLMRequest` processors now receive `apiProviderId`: the provider id of the model that will actually be called, for example `groq.chat` or `openai.responses`, where `model.provider` would only say `groq` or `openai`. ```ts import type { Processor } from '@mastra/core/processors'; const chatOnlyRewrite: Processor = { id: 'chat-only-rewrite', processLLMRequest({ prompt, apiProviderId }) { if (!apiProviderId?.endsWith('.chat')) return; return { prompt: prompt.filter(message => message.role !== 'system' || message.content.trim() !== '') }; }, }; ``` ### Patch Changes - dependencies updates: ([#28018](https://github.com/mastra-ai/mastra/pull/28018)) - Updated dependency [`@modelcontextprotocol/server@2.3.1` ↗︎](https://www.npmjs.com/package/@modelcontextprotocol/server/v/2.3.1) (from `2.2.0`, in `devDependencies`) - Update provider registry and model documentation with latest models and providers ([`c253e13`](https://github.com/mastra-ai/mastra/commit/c253e13ac627d0325f8665004f7af8a49805a9a4)) - Fixed background tasks staying `running` forever when their background-task workflow run ended as failed before a terminal status was saved (for example, when the storage write that marks the task completed throws). The task is now marked `failed` with the error recorded, and `onFailed`/`onTaskFailed` hooks and the `task.failed` event fire once. ([#27933](https://github.com/mastra-ai/mastra/pull/27933)) A per-task `onResult`, `onComplete` or `onFailed` hook that throws no longer stops the `task.completed`/`task.failed` event from being published, so the global `onTaskComplete`/`onTaskFailed` callbacks still fire. - Fixed channel agents losing their channel instructions on runs that didn't start from a chat message, such as a schedule, a Studio prompt or a signal. ([#28078](https://github.com/mastra-ai/mastra/pull/28078)) - Every run on a channel-backed thread now gets the same channel system prompt, so switching between chat messages and other runs keeps the prompt cached. Replies from those runs stream to the channel the same way. - The prompt's opening line changed from "You are communicating via slack." to "This conversation is connected to a slack thread." (or "slack direct message"). This applies to every channel agent. - Messages that come in over a channel now carry a `platform` attribute, for example `platform="slack"`. - With `textDisplay: 'hidden'`, the instructions say that only what the agent posts with tools reaches the conversation, and that to stay silent it posts nothing instead of sending an empty message. - `@mastra/core/channels` now exports `CHAT_CHANNEL_RENDER_CONTEXT_KEY` and the `ChatChannelRenderContext` type, so tools resolved per request can reach the live chat thread a run is replying in (for example, to upload a file into it). ([#27351](https://github.com/mastra-ai/mastra/pull/27351)) - Reject contained local filesystem reads and directory listings when a symlink swap is detected outside configured roots; preserve metadata access to unreadable files. ([#28092](https://github.com/mastra-ai/mastra/pull/28092)) - Signal contents now reach the model as written inside their tag. Previously `&`, `<` and `>` were turned into `&`, `<` and `>`, so commands and code in reminders, notifications and other signals arrived mangled. ([#27150](https://github.com/mastra-ai/mastra/pull/27150)) ```ts await agent.sendSignal( { type: 'reactive', tagName: 'skill', attributes: { name: 'release' }, contents: 'Run `pnpm build && pnpm test 2>&1`.', }, { resourceId, threadId }, ); // Before: Run `pnpm build && pnpm test 2>&1`. // After: Run `pnpm build && pnpm test 2>&1`. ``` A closing tag for the signal's own tag inside its contents, such as ``, is still escaped so contents can't end the tag early. Attribute values are still escaped. - Fixed `generate()`, `resumeStream()`, and `resumeGenerate()` to reject invalid `modelSettings.timeout` values with the same `TypeError`. Durable and evented agents now reject invalid resolved timeouts before acquiring an initial execution claim, and validate caller settings and static defaults before cold recovery executes. A rejected resume leaves the suspended run available for a retry with corrected settings. ([#28030](https://github.com/mastra-ai/mastra/pull/28030)) - Fixed durable and evented agent streams dropping provider metadata from successful tool-result chunks, including sub-agent text output mappings, while preserving original tool-call metadata in payload-transform callbacks. ([#28025](https://github.com/mastra-ai/mastra/pull/28025)) - Fixed `LocalFilesystem` so writes through symlinked parent directories cannot create files or directories outside the workspace or allowed paths. Writes outside those roots now throw `PermissionError`. Symlinked writes within allowed roots still succeed. Fixes #27916. ([#28148](https://github.com/mastra-ai/mastra/pull/28148)) - Restore the persisted delegation tool call on a cold durable-agent resume before updating suspension metadata. This keeps the next sub-agent suspension in memory when a replacement instance resumes with observational memory enabled. ([#28050](https://github.com/mastra-ai/mastra/pull/28050)) - Fixed processor reminders sent after a durable or evented step returns being lost before the next model request. Fixes [#27457](https://github.com/mastra-ai/mastra/issues/27457). ([#28154](https://github.com/mastra-ai/mastra/pull/28154)) - Fixed evented workflows and evented agents hanging when a step exhausts its retries and publishing the `workflow.fail` event briefly fails. Previously the failure was logged and dropped, so `run.start()`, `agent.generate()` and `agent.stream()` never resolved and the run stayed `running`. The failure is now retried on redelivery (up to 3 publish attempts) so callers receive the error and the run is stored as `failed`. ([#28218](https://github.com/mastra-ai/mastra/pull/28218)) - Fixed the evented workflow engine reporting a `foreach` step as `success` while its iterations were still running. The stored step now stays `running` (no `endedAt`) until every iteration finishes, and `getWorkflowRunById` no longer shows unfinished iterations as `null` entries in the step output, matching the default engine. ([#27932](https://github.com/mastra-ai/mastra/pull/27932)) When an iteration suspends, the step also stays `running` while sibling iterations are still in flight, and becomes `suspended` only once all of them settle. A suspended `foreach` step no longer gets an `endedAt`, matching the default engine. - Fixed evented agents calling a throwing `stopWhen` three times. A `stopWhen` error now fails the run on the first attempt, the same as on the other engines, so side effects in the predicate run once and the stream rejects with the error straight away. ([#28215](https://github.com/mastra-ai/mastra/pull/28215)) - Keep recalled messages as memory references in durable-agent checkpoints when `messageHistory.maxTokens` is set. Token counting adds framework-owned estimate caches to messages in memory; exclude only those cache entries from the saved-message identity comparison while retaining stored message content and caller-supplied metadata. ([#28049](https://github.com/mastra-ai/mastra/pull/28049)) - File-based agents can now save overrides without changing their source files, matching code-defined agents. ([#28159](https://github.com/mastra-ai/mastra/pull/28159)) - Fixed `add_reaction` and `remove_reaction` failing with "No channel context" on runs that did not start from a chat message, such as a schedule, a Studio prompt or a woken run. They now find the chat thread through the run's memory thread, the same way the agent's replies are posted. ([#28073](https://github.com/mastra-ai/mastra/pull/28073)) - Record a `tool_call` span for tool calls served by experiment `toolMocks`. The span carries the mocked output and the attribute `mocked: true`, so experiment traces show mocked calls next to model steps instead of only inside the model step output. ([#27954](https://github.com/mastra-ai/mastra/pull/27954)) - Exported `readNotificationRoutingRequirement` from `@mastra/core/notifications`, so a notification delivery policy can tell whether a notification was routed to the thread's claimed owner. ([#28066](https://github.com/mastra-ai/mastra/pull/28066)) ```ts import { readNotificationRoutingRequirement } from '@mastra/core/notifications'; const routedToOwner = readNotificationRoutingRequirement(record.metadata)?.requireClaimedOwner === true; ``` - Keep the current user request and delayed tool results in the prompt when a durable Observational Memory turn resumes on another worker. Preserve completed live background results when reconciling stored messages. ([#28045](https://github.com/mastra-ai/mastra/pull/28045)) - Fixed tool calls and nested workflows in durable agent runs losing their request context metadata. `hideInput` and `hideOutput` now stay in effect for those spans too. Inngest workflow and step spans keep the same settings when a run continues in a later step. ([#28118](https://github.com/mastra-ai/mastra/pull/28118)) - Fixed thread peer discovery reporting live peers as absent when the caller was busy right after asking. Peers answer in a few milliseconds, but a caller whose event loop stayed busy past the 100 ms window closed it before reading any answer. The window now counts only time the caller could read replies, waiting at most one second longer for a busy caller. A peer that really is gone is still reported absent about one window after the caller is free. ([#28098](https://github.com/mastra-ai/mastra/pull/28098)) - Fixed durable and evented onFinish tool results to use the same chunk shape as the default engine, including per-step results. Fixes #27428. ([#28145](https://github.com/mastra-ai/mastra/pull/28145)) - Added a `neon` filesystem icon so Neon Object Storage mounts show their own icon in the Studio file tree. ([#28032](https://github.com/mastra-ai/mastra/pull/28032)) - The traces list Input column now previews an agent run started by a signal by its contents, instead of the signal as JSON. Skills read `Skill : `, other non-user signals are prefixed with their tag (for example `system-reminder: CI is red.`), and user signals show their text. ([#28038](https://github.com/mastra-ai/mastra/pull/28038)) - Output processors can now add text to an agent's response when observational memory is enabled and the final step is empty after a tool call. That text now streams and is saved for durable and evented agents, the same as for standard agents. ([#28046](https://github.com/mastra-ai/mastra/pull/28046)) - Fixed threads becoming unusable after a model sent a tool call with arguments that could not be parsed. That call was replayed to the model with no input, so OpenAI-compatible providers such as DeepSeek sent it without `arguments` and rejected every later request on the thread with "missing field `arguments`". The call is now replayed with an empty input. ([#28099](https://github.com/mastra-ai/mastra/pull/28099)) - Fixed runs being aborted when their thread lease lapsed only because their process fell behind. Under heavy load a renewal can go out after the lease ran out, or none can be confirmed before the lease's deadline. The run now takes the lease back instead of being aborted. It is still aborted when another run holds the thread, or when the lease backend doesn't answer within one renewal interval. ([#28079](https://github.com/mastra-ai/mastra/pull/28079)) Added a reason to runs stopped because another run took their thread lease. The `abort` chunk carries `payload.reason: 'thread-lease-lost'`, and a session's `agent_end` event carries `abortReason: 'thread-lease-lost'`: ```ts session.subscribe(event => { if (event.type === 'agent_end' && event.reason === 'aborted' && event.abortReason === 'thread-lease-lost') { console.log('Another run took over this thread.'); } }); ``` - Fixed tool search so complete tool names in a query rank before keyword matches, including names with hyphens and underscores. Repeated query words no longer skew tool search results. Fixes [#28113](https://github.com/mastra-ai/mastra/issues/28113). ([#28141](https://github.com/mastra-ai/mastra/pull/28141)) - Fixed channel messages and tool approval actions bypassing durable execution on durable and evented agents, including channels attached after construction. ([#27944](https://github.com/mastra-ai/mastra/pull/27944)) - Fixed `onFinish.text` including replies rejected by `processOutputStep` guardrails, including discarded retry attempts on durable agents. Earlier accepted step text is preserved. Fixes [COR-1464](https://linear.app/kepler-crm/issue/COR-1464/processoutputstep-abort-cover-evented-and-compare-all-three-engines). ([#28029](https://github.com/mastra-ai/mastra/pull/28029)) - Studio session cookies are now host-only, so a session is never sent to other deployments on a shared domain. ([#28168](https://github.com/mastra-ai/mastra/pull/28168)) `@mastra/auth-studio`: on secure deployments `MastraAuthStudio` issues `__Host-wos-session` (`Secure`, `Path=/`, no `Domain`). `MASTRA_COOKIE_DOMAIN` is no longer used as the cookie `Domain`. A legacy `wos-session` cookie is still accepted on read, but only so it can be re-issued as `__Host-wos-session`. The new `getLegacySessionClearCookies()` expires the legacy `wos-session` on the current host, on `MASTRA_COOKIE_DOMAIN`, and on each domain in `MASTRA_LEGACY_COOKIE_DOMAINS` (or the `legacyCookieDomains` option). Plain-http `localhost` dev keeps an unprefixed host-only cookie; use `secureCookies` to override. An explicit `cookieDomain` option still issues a `wos-session` cookie with that `Domain`. `@mastra/server`: the SSO callback, `POST /auth/refresh` and `POST /auth/logout` responses now also send the provider's `getLegacySessionClearCookies()`. `@mastra/factory`: the default Studio provider no longer derives `Domain=.mastra.cloud` from `publicUrl`. Its sign-in and sign-out responses also expire legacy session cookies. `@mastra/core`: `ISessionProvider` gains the optional `getLegacySessionClearCookies()`. - Fixed channel agents calling a missing `add_reaction` tool. The channel instructions now mention reacting only when the agent has the `add_reaction` tool, for example via `channels.getTools()`. ([#28067](https://github.com/mastra-ai/mastra/pull/28067)) - Fixed agent streams dropping chunks when a run fails while the reader is behind. Before, a reader that consumed `fullStream` slowly, or started reading late, could lose the chunks already produced, such as the tool call and tool result, and get only the error. Now it receives every queued chunk first, then the error. This covers the default agent loop, durable agents, and late subscribers to an already-failed stream. ([#28227](https://github.com/mastra-ai/mastra/pull/28227)) - Fixed evented foreach steps keeping a `suspendedAt` timestamp after the step successfully resumed and finished. The evented engine now matches the default engine. ([#28151](https://github.com/mastra-ai/mastra/pull/28151)) - Fixed durable and evented streams dropping the suspending step's token usage. `usage` and `totalUsage` now settle when the run suspends for a tool call or an approval, including steps that already ran, and a resume still replaces those numbers with the cumulative total. ([#27882](https://github.com/mastra-ai/mastra/pull/27882)) - Fixed trace scoring for agent scorers: tool calls that start in the same millisecond (parallel tool calls) now keep their original order, and tools executed by the model provider (for example Anthropic web search) are now included in the scorer's tool invocations, matching what live scoring sees. ([#27953](https://github.com/mastra-ai/mastra/pull/27953)) - Fixed total run deadlines not terminating durable and evented agent runs when a tool ignores cancellation. ([#27941](https://github.com/mastra-ai/mastra/pull/27941)) ## 1.79.0 ### Minor Changes - **Breaking:** Agent controller sessions are now addressed by their session id. Many sessions can share one `resourceId`, so `resourceId` only says whose threads and memory a session works on. ([#27336](https://github.com/mastra-ai/mastra/pull/27336)) - Added `controller.getSession(sessionId)`. Removed `controller.getSessionByResource(resourceId, scope)`. - Removed the `scope` option from `createSession()`. Give each session its own `id` instead. When `id` is omitted it defaults to the `resourceId`, so existing one-session-per-resource code keeps working. - `deleteSession()` takes `{ sessionId }`, and `generateThreadTitle()` takes an optional `sessionId`. - The controller request context exposes `sessionId` next to `resourceId`. ```ts // Before await controller.createSession({ resourceId: 'repo-1', scope: 'run-a' }); const session = await controller.getSessionByResource('repo-1', 'run-a'); await controller.deleteSession({ resourceId: 'repo-1', scope: 'run-a' }); // After await controller.createSession({ id: 'run-a', resourceId: 'repo-1' }); const session = await controller.getSession('run-a'); await controller.deleteSession({ sessionId: 'run-a' }); ``` `createSession` throws when an explicit `resourceId` doesn't match the resource of the live session with that id, so a session id can't be used to take over another resource's session. - `aggregateTraces()` can now group by the root span's `entityId`, and trace queries and aggregates accept `entityId` as a trace-scope `where` field. Use it to split traces that have no `entityName` (for example, scorer runs recorded before scorers set one) by the primitive that produced them, then drill into a group by filtering on the same `entityId`. ([#27684](https://github.com/mastra-ai/mastra/pull/27684)) ```ts const { rows } = await client.aggregateTraces({ timeRange, where: { op: 'notExists', path: 'entityName' }, groupBy: ['entityId'], measures: ['count'], }); ``` Trace-scope `entityId` reads the root span only. Inside `spans.some` and `spans.none`, `entityId` still matches any span in the trace. Stores that support the trace-scope field declare the new `trace-query-root-entity-id` storage feature. - Added the storage contract for span aggregation. Storage adapters can implement `aggregateSpans()` and advertise the `span-aggregate` feature to answer questions like "p95 latency per tool" or "token spend per calling agent" over individual spans. ([#27669](https://github.com/mastra-ai/mastra/pull/27669)) ```ts class MyObservabilityStore extends ObservabilityStorage { getFeatures(): readonly ObservabilityStorageFeature[] { return [...(super.getFeatures() ?? []), 'span-aggregate']; } // `plan` is already validated: tool calls grouped by name, with count and p95 duration async aggregateSpans(plan: TrustedSpanAggregatePlan): Promise { // e.g. { rows: [{ dimensions: { name: 'search' }, measures: { count: 42, 'duration.p95': 812 } }], truncated: false } } } ``` Span aggregates use the same request shape, measures, and limits as trace aggregates. Filters follow `querySpans()`, so counts match the spans `querySpans()` returns. Spans can be grouped by name, span type, and their own, parent, or root entity. Grouping by model or provider is not supported yet. Token and cost measures count usage on the span that recorded it. Adapters without the feature reject the call as not implemented. - Fixed `DurableAgent.observe(runId)` when attaching to a run from another process. ([#26906](https://github.com/mastra-ai/mastra/pull/26906)) - Restored the agent's configured output processors, request context, and `returnScorerData` setting, including when the observing process also runs durable steps. - Preserved live call-time processors, request context, and scorer data when another wrapper observes the run within the same Mastra instance. - **Behavior change:** Cross-process `DurableAgent.observe(runId)` now throws `DURABLE_AGENT_OBSERVE_AGENT_MISMATCH` when the persisted run belongs to an agent with a different ID, matching `resume()` and `recover()`. Use the same agent ID in the observing process as in the process that started the run. - Made durable stream `onChunk` callbacks run serially and in order. - Made `onFinish` wait for pending `onChunk` callbacks to finish, without waiting for transport acknowledgements. **Before:** An observing process could use a different agent ID to attach to a run started by `researcher`: ```ts const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher-observer' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId); ``` **After:** Use the starting agent's ID in the observing process. The previous configuration now throws `DURABLE_AGENT_OBSERVE_AGENT_MISMATCH`: ```ts const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId); ``` - Sandboxes can now expose their own disk through an optional `filesystem` property on `WorkspaceSandbox`. A provider with a file API answers reads, writes, and listings in one request each instead of a shell command per step. Paths are absolute inside the sandbox, and the provider decides containment. Fall back to `executeCommand()` when the property is absent: ([#27859](https://github.com/mastra-ai/mastra/pull/27859)) ```ts const pkg = sandbox.filesystem ? await sandbox.filesystem.readFile('/workspace/app/package.json', { encoding: 'utf-8' }) : (await sandbox.executeCommand?.('cat', ['/workspace/app/package.json']))?.stdout; ``` - Added batch experiment ID filtering to observability metric queries, so you can get token usage and cost for many experiments in one request. ([#27255](https://github.com/mastra-ai/mastra/pull/27255)) ```ts const result = await client.getMetricBreakdown({ name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'], aggregation: 'sum', groupBy: ['experimentId', 'name'], filters: { experimentIds: ['experiment-1', 'experiment-2'] }, }); ``` - `session.thread.transferOwnership` now accepts an optional `threadId`, so you can transfer a thread without switching the session to it first. Without `threadId` it still transfers the active thread. ([#27043](https://github.com/mastra-ai/mastra/pull/27043)) ```ts await session.thread.transferOwnership({ toOwnerId: 'user-456', threadId: 'thread-123' }); ``` - Channel `resolveResourceId` and `resolveThreadId` hooks now receive the per-message `requestContext`. Values a channel handler sets before calling `defaultHandler`, such as a tenant id, can be read directly in the resolvers instead of being looked up a second time. ([#27436](https://github.com/mastra-ai/mastra/pull/27436)) ```ts channels: { handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.requestContext.set('tenantId', await lookupTenant(thread.channelId)); await defaultHandler(thread, message); }, }, resolveResourceId: ({ requestContext }) => `tenant:${requestContext.get('tenantId')}`, } ``` `requestContext` is a required field on `ResolveResourceIdContext` and `ResolveThreadIdContext`. Code that builds these objects by hand, such as tests that call a resolver directly, now needs to pass one: ```ts // Before await resolveResourceId({ platform, thread, message, defaultResourceId }); // After await resolveResourceId({ platform, thread, message, defaultResourceId, requestContext: new RequestContext() }); ``` - Fixed workspace search and file listing ignoring `.gitignore` files below the workspace root. Content search and the file tree now apply every `.gitignore` relative to its own directory (for example, each repository in a multi-repo workspace) and skip `node_modules` unless the path points inside it. This keeps search from reading dependency files one by one on remote filesystems, and keeps file listings free of ignored and dependency files. ([#27687](https://github.com/mastra-ai/mastra/pull/27687)) Filesystems that implement `walk` can now honor two new options: `includeGitignoreFiles` returns `.gitignore` files even when hidden entries are excluded, and `skipDirectoryNames` skips directories such as `node_modules` entirely. ```ts const entries = await filesystem.walk('.', { includeGitignoreFiles: true, skipDirectoryNames: ['node_modules'], }); ``` - Add opt-in `bundler.pnpmWorkspaceSettings` to write pnpm install-policy settings to generated deployment workspaces. Supported keys are `allowBuilds`, `onlyBuiltDependencies`, `ignoredBuiltDependencies`, `minimumReleaseAge`, `minimumReleaseAgeExclude`, `trustPolicy`, `trustPolicyExclude`, `trustPolicyIgnoreAfter`, and `blockExoticSubdeps`; explicit values override copied source settings and any other key is rejected before pnpm runs. Existing behavior is unchanged when omitted. ([#27708](https://github.com/mastra-ai/mastra/pull/27708)) ```ts import { Mastra } from '@mastra/core/mastra'; export const mastra = new Mastra({ bundler: { pnpmWorkspaceSettings: { blockExoticSubdeps: false, }, }, }); ``` - Added iteration-aware final output processing so processors receive the current feedback-continuation iteration as its own assistant message and can transform each iteration independently. ([#26346](https://github.com/mastra-ai/mastra/pull/26346)) Fixed feedback continuations with output processors so rewrites and redactions are preserved in `getFullOutput().text` and `steps[].text` instead of returning raw earlier-iteration text. Custom output processors that persist transcripts should use `messageList.cloneForPersistence()` to retain one assistant response with its original identity and order. ```ts // Before: may persist processor-visible response messages separately. const processor = { id: 'transcript-persister', processOutputResult: async ({ messages, messageList }) => { await saveMessages(messageList.get.response.db()); return messages; }, }; // After: preserves one assistant response with its original identity and order. const processor = { id: 'transcript-persister', processOutputResult: async ({ messages, messageList }) => { await saveMessages(messageList.cloneForPersistence().get.response.db()); return messages; }, }; ``` - Added thread rows with activity timestamps, trace counts, previews, root-agent model details, filtering and sorting. ([#27652](https://github.com/mastra-ai/mastra/pull/27652)) **Breaking contract change:** Thread-row requests use `timeRange.traces` or `timeRange.threads` instead of `traces.timeRange`. Related-trace predicates are not supported in the new contract. Legacy identity requests remain supported for mixed package versions. ```ts // Before const before = { traces: { timeRange: { from, to } } }; // After const after = { timeRange: { traces: { from, to } } }; ``` - Added optional, experimental `getEventsInfo()` and `getEventsInspector()` members to `MCPServerBase`, plus the event info types (`MCPServerEventInfo`, `MCPServerEventStreamFrame`, `MCPServerEventsInspector`, …) and `MCP_SERVER_EVENT_ERROR_IDS`. Servers that declare MCP events use them to let Studio list, poll, stream and test-emit their event types. Servers without events don't need to implement them. ([#27674](https://github.com/mastra-ai/mastra/pull/27674)) ```ts const info = server.getEventsInfo?.(); console.log(info?.events.map(event => event.name)); const inspector = server.getEventsInspector?.(); const { events, cursor } = await inspector!.poll('ticket.created', { arguments: { project: 'web' } }); ``` - Added an `embeddingDimension` option to Memory. When you know your embedder's output dimension, setting it lets semantic recall skip the probe embedding call it otherwise makes once per Memory instance. Startup then makes no billed embedding call and doesn't fail when the embedding provider is unreachable. The vector index name stays the same, so existing indexes keep working. If the configured value doesn't match the length of the embeddings the embedder returns, Memory throws a `MASTRA_MEMORY_EMBEDDING_DIMENSION_MISMATCH` error naming both values before creating or querying any vector index. ([#27588](https://github.com/mastra-ai/mastra/pull/27588)) ```ts const memory = new Memory({ embedder: 'openai/text-embedding-3-small', embeddingDimension: 1536, options: { semanticRecall: true }, }); ``` - Added `timeToFirstChunkMs` to trace and span queries. `queryTraces()` returns the root span's time to first chunk and `querySpans()` returns each span's, both `null` when no chunk was streamed. Both accept `timeToFirstChunkMs` filters with the same operators as `durationMs`. Stores that support it declare the `trace-query-time-to-first-chunk` feature. ([#27277](https://github.com/mastra-ai/mastra/pull/27277)) - Support embedding specification v4 models from AI SDK v7. Core accepts v4 embedding models and exports `embedV4`; Memory and RAG use the matching SDK to generate embeddings while preserving v1, v2, and v3 behavior. ([#27710](https://github.com/mastra-ai/mastra/pull/27710)) - Added a `datasetIds` filter to dataset listing, so you can fetch specific datasets by ID in one request. All matches come back on one page, up to 100 IDs. ([#27255](https://github.com/mastra-ai/mastra/pull/27255)) ```ts const { datasets } = await client.listDatasets({ datasetIds: ['dataset-1', 'dataset-2'] }); ``` - Added the root span's `name` and `spanType` as trace-scope fields for trace queries and trace aggregates. ([#27321](https://github.com/mastra-ai/mastra/pull/27321)) - `parseTraceQueryRequest()` and `parseTraceAggregateRequest()` accept `name` and `spanType` in trace-scope `where` predicates. - `planTraceAggregate()` accepts them in `groupBy` and as `countDistinct.name` / `countDistinct.spanType`. - Trace-scope field discovery lists both with value suggestions, and the `aggregate.dimensions` list includes them. Inside `spans.some` and `spans.none`, `name` and `spanType` still match any span in the trace. ```ts const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'workflow_run' } }, groupBy: ['name'], measures: ['count', 'errorRate'], }), ); ``` Stores that support these fields declare the new `trace-query-root-span-fields` storage feature. - Agent schedules can now send an idle wake to the worker that has claimed the thread. Set `ifIdle.requireClaimedOwner: true` on the schedule, or return it from the `prepare` hook. This works the same way as `agent.sendSignal`: the claim holder runs the turn with its own claim stream options. If no owner answers, the fire fails with `outcome: 'failed'`. Before this change, the option was dropped, so whichever worker took the fire ran the turn itself. ([#27896](https://github.com/mastra-ai/mastra/pull/27896)) ```ts await mastra.schedules.create({ agentId: 'support', cron: '0 * * * *', prompt: 'Check in', threadId, resourceId, ifIdle: { behavior: 'wake', requireClaimedOwner: true }, }); ``` - Added `querySpans()` filters and response fields for span context and parent/root entity information. For example: ([#27272](https://github.com/mastra-ai/mastra/pull/27272)) ```ts where: { op: 'eq', left: { path: 'threadId' }, right: { literal: 'thread-123' } } ``` **Breaking:** `querySpans()` no longer accepts `model` and `provider` filters or returns `cost`, `inputTruncated`, `outputTruncated` and `errorTruncated`. Model/provider values, name matching, duration filtering and previews remain available. The new filters require an updated storage adapter. - Trace and span queries can now sort by `timeToFirstChunkMs`, in either direction. Missing values sort last, and pages stay stable when values tie. Stores that support it declare the `trace-query-time-to-first-chunk-sort` feature. ([#27473](https://github.com/mastra-ai/mastra/pull/27473)) ```typescript const page = await storage.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, orderBy: [{ field: 'timeToFirstChunkMs', direction: 'desc' }] }), ), ); ``` - Span records now carry `timeToFirstChunkMs`, the milliseconds from span start to the first streamed chunk. The storage exporters fill it in, so storage adapters save the value without computing it. ([#27140](https://github.com/mastra-ai/mastra/pull/27140)) - Added `completionStartTime` to agent run and model step span attributes, a `getTimeToFirstChunkMs()` helper that derives time to first chunk for any span, and carried the first-chunk clock through durable and evented agent runs so their generation and agent run spans report it too. ([#26998](https://github.com/mastra-ai/mastra/pull/26998)) - Added `backgroundTasks.untilIdleMaxIdleMs` to set the default idle timeout for `stream({ untilIdle: true })`. Before, calls that did not pass `maxIdleMs` always closed after 5 minutes without new events. This included calls Mastra makes internally, such as waking an agent after `sendMessage`. A background task that ran longer than that finished without a continuation turn. A per-call `untilIdle: { maxIdleMs }` still takes precedence. ([#27757](https://github.com/mastra-ai/mastra/pull/27757)) ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, untilIdleMaxIdleMs: 30 * 60_000, }, }); ``` - Added an opt-in `apply_patch` workspace tool that creates, updates, moves and deletes several files in one call. The tool checks the whole patch before it writes anything. If a write fails partway, it tries to undo the changes it already made. The tool is off by default. Enable it per tool: ([#27913](https://github.com/mastra-ai/mastra/pull/27913)) ```ts new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), tools: { [WORKSPACE_TOOLS.FILESYSTEM.APPLY_PATCH]: { enabled: true } }, }); ``` Setting the top-level `tools.enabled` does not turn it on, so existing agents keep using `write_file` and `edit_file`. Paths follow the same containment, read-before-write and write-lock rules as the other filesystem tools. ### Patch Changes - dependencies updates: ([#27728](https://github.com/mastra-ai/mastra/pull/27728)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.61` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.61) (from `npm:@ai-sdk/provider-utils@4.0.56`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.62` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.62) (from `npm:@ai-sdk/provider-utils@5.0.54`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v5@npm:@ai-sdk/provider@2.0.5` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v5/v/2.0.5) (from `npm:@ai-sdk/provider@2.0.3`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.19` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.19) (from `npm:@ai-sdk/provider@3.0.14`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.29` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.29) (from `npm:@ai-sdk/provider@4.0.27`, in `dependencies`) - Fixed agent runs that were aborted while a tool was running finishing with `finishReason: 'tool-calls'`. When a tool stops because the run's abort signal fired, the run now ends with `finishReason: 'aborted'`, emits an `abort` chunk, and calls `onAbort`, the same as an abort during the model response. ([#27748](https://github.com/mastra-ai/mastra/pull/27748)) - Fixed agent runs that kept calling the model and tools after losing their thread lease. When lease renewal fails, the run is now aborted instead of running beside the run that took over the thread on another process. Applies to regular, durable and evented agents. ([#27581](https://github.com/mastra-ai/mastra/pull/27581)) - The error for a model with an unrecognized `specificationVersion` now lists `v4` (AI SDK v7) among the supported versions. ([#27516](https://github.com/mastra-ai/mastra/pull/27516)) - Fixed session messages starting while another agent runs on the same thread. When an `AgentController` uses an application's agent and has no `pubsub` of its own, its agents without their own `pubsub` now share the application's event bus. Session messages now wait for the other run to finish. A `pubsub` passed to the controller still takes precedence. ([#27631](https://github.com/mastra-ai/mastra/pull/27631)) - Fixed two cases where an AgentController tool approval could get out of sync with its stored run: ([#27371](https://github.com/mastra-ai/mastra/pull/27371)) - **A failed resume no longer loses the answer.** If approving or declining a tool call fails while the run is still waiting on it, the approval prompt is raised again (`tool_approval_required`) so the answer can be retried. Previously a retry returned `{ accepted: false, reason: 'not_pending' }` and the run stayed suspended. - **Detaching no longer declines a pending approval.** `session.thread.detachFromCurrent()` now leaves a parked tool approval pending in storage, as documented, instead of declining it. It can be answered later, for example with `respondToPersistedToolApproval`. - Update provider registry and model documentation with latest models and providers ([`9b60f12`](https://github.com/mastra-ai/mastra/commit/9b60f12b13b28c9664a839aef435e6d10898644d)) - Fixed agent turns stopping early when an awaited background tool returns no result. The agent now sends `null` to the model and continues. ([#27482](https://github.com/mastra-ai/mastra/pull/27482)) - Fixed `untilIdle` continuations losing a completed background task's result when newer messages push it outside the configured history window. ([#27205](https://github.com/mastra-ai/mastra/pull/27205)) - Fixed `workspace_grep` hanging for minutes on large workspaces. A directory search that runs on the host now has a 60-second budget. The budget covers filesystems without native grep, including the default `LocalFilesystem`, as well as the fallback used when a filesystem's native grep fails. Once the budget runs out or the agent run is aborted, the search stops starting new reads (reads already in progress finish) and returns the matches found so far. The summary line marks these results as incomplete and says how many files were scanned. The fallback log message now says why native grep failed (for example, a timeout) instead of always reporting an unsupported pattern. ([#27464](https://github.com/mastra-ai/mastra/pull/27464)) - Reduced durable agent snapshot size: tool results are no longer stored twice per step, so long-running durable agents with large tool outputs write much smaller snapshots. `stopWhen` conditions still receive the full step content. ([#27634](https://github.com/mastra-ai/mastra/pull/27634)) - Added `bufferSize` option to `SystemPromptScrubber` and `ModerationProcessor`. Set `bufferSize: 200` to check per sentence instead of per chunk (fewer LLM calls, catches content split across chunks). Default unchanged; will become the default in a future major. ([#27510](https://github.com/mastra-ai/mastra/pull/27510)) ```ts new SystemPromptScrubber({ model, bufferSize: 200 }); new ModerationProcessor({ model, bufferSize: 200 }); ``` - Fixed live scorers passed at call time (`agent.stream(..., { scorers })`) or through `defaultOptions.scorers` never running unless the same scorer was also registered on the agent or on Mastra. Such scorers now run and save their scores on every agent engine, including durable and evented agents. ([#27714](https://github.com/mastra-ai/mastra/pull/27714)) - Added `byUser` to the `approved` channel tool display event, matching `denied`. A custom `toolDisplay` function can now show who approved a tool call (for example "Approved by Alice"), not only who denied it. The default approved card also names the approving user. `byUser` is undefined in DMs. ([#27584](https://github.com/mastra-ai/mastra/pull/27584)) ```ts toolDisplay: event => { if (event.kind === 'approved') { return { kind: 'post', message: `Approved by ${event.byUser ?? 'you'}` }; } }; ``` - Channel streaming: text the agent writes before its first tool call now posts as its own message in `toolDisplay: 'grouped'` mode. An opening acknowledgement lands above the Thinking Steps widget instead of under it. ([#27350](https://github.com/mastra-ai/mastra/pull/27350)) The plan widget in `'grouped'` and `'timeline'` modes is quieter. The header is a fixed "Working…" label and no longer echoes the current tool. Each task row shows the tool's `description` argument (first line only) when it has one, and falls back to `toolName firstArg` otherwise. Grouped mode is an adapter option on the channel provider: ```ts import { Mastra } from '@mastra/core'; import { SlackProvider } from '@mastra/slack'; export const mastra = new Mastra({ channels: { slack: new SlackProvider({ refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN, toolDisplay: 'grouped', }), }, }); ``` - Fixed an agent run that had lost its thread lease running tools next to the run that took over the thread. Lease renewals run every third of the lease TTL, so a run whose lease was taken over could keep calling tools until its next renewal. A run that holds a thread lease now checks the lease owner before each tool call. If another run owns the lease, the run is aborted and the tool does not run. If the lease backend can't answer, the local lease deadline decides as before. ([#27749](https://github.com/mastra-ai/mastra/pull/27749)) - Classifiers now accept `refusal` answers from decision models instead of throwing. Previously, when a decision model refused one question, `decide()` failed with `Answer '' does not match its question type.` and any workflow `.classifier()` step failed the run. ([#27488](https://github.com/mastra-ai/mastra/pull/27488)) A refused question now returns `{ type: 'refusal' }` while the other questions keep their answers. `RefusalAnswer` is exported and included in every `ClassifierAnswer` type. Refusals have no `choice`, `score`, or `probability`, so equality checks such as `answers.route.choice === 'billing'` evaluate to `false`; check `type === 'refusal'` before relying on numeric fields. ```ts const { answers } = await classifier.decide({ state }); if (answers.unsafe.type === 'refusal') { // Route to review, block, or apply a default. } ``` Classifier scorers fail with an error naming the refused question, and `ModelSelectionProcessor` keeps the configured model and reports `abstained: 'refused'`. - Classifier evaluation traces now record the classifier as their entity, so they show the classifier name on the Traces page instead of "(not set)" and can be filtered and grouped like agents, workflows and scorers. `EntityType` gains a `classifier` value. ([#27756](https://github.com/mastra-ai/mastra/pull/27756)) - Fixed `Classifier.decide()` crashing with `state.map is not a function` on decision providers built for `@ai-sdk/provider` 4.0.25 and newer, such as `@ai-sdk/typesafe-ai` 3.0.16+. String and JSON state are now converted to `text` and `json` parts before the provider is called, matching the AI SDK. Arrays are passed through only when every element is already a state part; other JSON arrays are wrapped in a `json` part. ([#27298](https://github.com/mastra-ai/mastra/pull/27298)) - Fixed `autoResumeSuspendedTools` leaving a resumed plain tool marked as suspended. This happened when the model resumed a regular (non-agent, non-workflow) tool under a new tool call id. The original suspension was never cleared. Every later turn asked the model to resume the tool again. Reloaded clients also showed the call as still pending. The original suspension is now cleared, and a `tool-call-resumed` chunk is emitted for it. This applies to the standard, evented and durable agents. ([#27784](https://github.com/mastra-ai/mastra/pull/27784)) - Fixed approval after a host disconnect without cancelling the parked agent run. ([#27907](https://github.com/mastra-ai/mastra/pull/27907)) - Fixed workspace search and file listing so a deeper `.gitignore` can re-include what a parent ignores (for example `!keep.log` in a subdirectory overriding `*.log`), matching git. Search results from a filesystem's native `grep` now also apply `.gitignore` files in subdirectories of the search path. ([#27695](https://github.com/mastra-ai/mastra/pull/27695)) - Classifier evaluation spans now show what the classifier was asked and what it decided. The span input holds the `state` and the questions, and the span output holds the answers, so routing decisions are visible in traces. Set `hideInput` and `hideOutput` in `tracingOptions` to keep them out of exported traces. ([#27243](https://github.com/mastra-ai/mastra/pull/27243)) - Fixed durable and evented agents losing the conversation when a client tool is approved or declined on an agent without memory. The finish chunk now carries the same `messages` history as the default agent, so the follow-up model request keeps the original user prompt, the tool call and the tool result. The client tool also runs once: the first stream of a durable run stays open after suspension, and it no longer re-runs a tool the approve or decline request can execute. A server-side approve, which sends no client tools, still runs the tool on that open stream. ([#27874](https://github.com/mastra-ai/mastra/pull/27874)) - Fixed durable agents not forwarding a declined sub-agent tool approval to the sub-agent. When a supervisor declines a tool approval raised by a sub-agent, the decline and its reason now reach the suspended sub-agent run, matching the approve path and the regular `Agent`. Declining the supervisor's own tool approval still denies the call locally. ([#27374](https://github.com/mastra-ai/mastra/pull/27374)) - Fixed durable and evented agents placing `isTaskComplete` feedback after the model's next tool call and its result. The feedback now comes right before the model's retry, as it does with regular agents, so the model reads it as the reason for continuing. Fixes #27548. ([#27743](https://github.com/mastra-ai/mastra/pull/27743)) - Fixed durable and evented agents ignoring per-model `modelSettings` in a model list. When an agent is configured with `model: [{ model, modelSettings: { temperature: 0.1 } }, ...]`, each entry's settings now reach the provider on every attempt, including retries and fallbacks, instead of falling back to call-time settings. ([#27727](https://github.com/mastra-ai/mastra/pull/27727)) - Fixed DurableAgent ignoring the reserved `mastra__threadId` and `mastra__resourceId` request-context keys. Durable and evented agents now route memory to the thread and resource set on the request context, as the regular Agent does, instead of to the thread or resource named in the call's `memory` options. ([#27575](https://github.com/mastra-ai/mastra/pull/27575)) - Fixed durable agent resumes that lose a resume race or arrive after the run finished ([#27365](https://github.com/mastra-ai/mastra/issues/27365)). ([#27379](https://github.com/mastra-ai/mastra/pull/27379)) - A second approval of the same tool call fails right away with `AGENT_RESUME_ALREADY_CLAIMED` while the first resume runs in the same process, including when both approvals arrive at the same moment. Before, it waited for the whole resumed turn or about 5 s and then failed with `DURABLE_AGENT_EXECUTION_CONFLICT`. - A resume that loses the race reports the error only on its own stream. Clients following the run no longer get an error event or a span ended with an error while the winning resume completes. - Resuming a run whose snapshot was already deleted (for example because another instance finished it) now fails with `AGENT_RESUME_NO_SNAPSHOT_FOUND` and no longer writes a new `pending` workflow row. - A resume that loses the race no longer blocks the winning run's memory writes. - The durable execution lease now follows `MASTRA_AGENT_THREAD_LEASE_TTL_MS` (renewed every third of it). The default TTL drops from 30 s to 15 s, so a run left by a crashed process becomes recoverable sooner. - You can now resume durable agents (`createDurableAgent`, `createInngestAgent`) with the same `resumeGenerate(resumeData, { runId })` call you use with `Agent`. ([#27299](https://github.com/mastra-ai/mastra/pull/27299)) ```ts // Before (durable-only order, still supported but deprecated) await durableAgent.resumeGenerate(runId, { approved: true }); // After (same as Agent) await durableAgent.resumeGenerate({ approved: true }, { runId }); ``` One deprecated call shape now throws instead of guessing: a string run ID followed by resume data that itself contains a string `runId`, with no third argument, for example `resumeGenerate('run-A', { runId: 'x', approved: true })`. To keep using the deprecated order, pass an explicit options argument: `resumeGenerate('run-A', { runId: 'x', approved: true }, {})`. - Fixed durable and evented agents dropping earlier tool calls from the prompt on a second output processor retry. When a processor called `abort(reason, { retry: true })` more than once in a run that had already made tool calls, the next request lost the earlier tool call and its result, which broke the provider prompt cache. Durable and evented agents now keep the full history across retries, matching the standard agent loop (#27561). ([#27758](https://github.com/mastra-ai/mastra/pull/27758)) - Fixed a durable `AgentController` session hanging after a prompt left unanswered for longer than `MASTRA_SUSPENDED_RUN_TTL_MS`. Later messages now run and finish as usual. The dropped prompt can still be answered. ([#27632](https://github.com/mastra-ai/mastra/pull/27632)) - Fixed `stopWhen` on durable agents (`DurableAgent`, evented and Inngest) receiving steps without `content`. Predicates such as `steps.at(-1)?.content.some(...)` used to throw and end the run with a stream error. They now see the same step content that `stream.steps` reports. ([#27524](https://github.com/mastra-ai/mastra/pull/27524)) - Fixed `resumeGenerate()` and `getFullOutput()` on durable agents sometimes never completing when a resumed run suspends again. ([#27384](https://github.com/mastra-ai/mastra/pull/27384)) - Fixed durable and evented agents reporting no token usage for a step that suspends on a tool call. `generate()` results and `tool-call-approval` / `tool-call-suspended` chunks now include that step's usage, matching the regular agent. ([#27493](https://github.com/mastra-ai/mastra/pull/27493)) - Fixed durable and evented agents ignoring `generateTitle.emitEvent` and `onTitleGenerated`. Streams from these agents now include the transient `data-thread-title` chunk before `finish`, and `memory.onTitleGenerated` is called with the saved title, matching the standard agent. ([#27606](https://github.com/mastra-ai/mastra/pull/27606)) - Fixed durable and evented agents returning an empty `response.messages` when a `processOutputResult` processor rejects the final answer. `response.messages` now contains the assistant's draft, matching `text`, `steps` and the normal loop, so apps can still show the rejected reply to a reviewer. ([#27737](https://github.com/mastra-ai/mastra/pull/27737)) - Fixed evented workflow restarts replaying completed steps and ensured recovered durable-agent streams finish without repeating saved effects. ([#27852](https://github.com/mastra-ai/mastra/pull/27852)) - Fixed `run.stream()` on the evented workflow engine reporting `workflow-finish` with `workflowStatus: 'success'` when the run failed. The evented engine now emits the failed step's `workflow-step-result` before finishing, matching the default engine, so stream consumers and UIs see `failed`. ([#27559](https://github.com/mastra-ai/mastra/pull/27559)) - Fixed evented `.foreach()` runs where more than one iteration fails. The run now keeps the first iteration's error instead of the last one, and fails exactly once. Previously, an iteration failing after the run had already failed replaced the stored error and published a second `workflow.fail`. Time-travel replays that hit a failure again also failed twice. ([#26651](https://github.com/mastra-ai/mastra/pull/26651)) - Server model usage telemetry now counts tokens as model calls finish and reports them every 15 minutes and on shutdown as a new `mastra_model_token_usage_v2` event. Each `mastra_model_token_usage_v2` event contains only tokens the reporting process used, so redeploys, replicas, and lost home directories can't make it resend earlier usage. The startup `mastra_model_token_usage` event is still sent, unchanged, for compatibility, and can still resend earlier usage in those cases. Set `MASTRA_TELEMETRY_DISABLED=1` to turn off telemetry. ([#26986](https://github.com/mastra-ai/mastra/pull/26986)) - Fixed durable agent startup recovery so unrelated runs can recover concurrently. Timed-out attempts are checked again after settlement without starting duplicate executions or releasing live ownership. ([#27768](https://github.com/mastra-ai/mastra/pull/27768)) - xAI models now accept `xhigh` reasoning effort. Groq, xAI, Together AI, DeepInfra, Cerebras and Alibaba models now run on the AI SDK v7 provider packages, like DeepSeek. ([#26530](https://github.com/mastra-ai/mastra/pull/26530)) ```ts await agent.generate('Prove that there are infinitely many primes.', { providerOptions: { xai: { reasoningEffort: 'xhigh' } }, }); ``` - Fixed `fetchWithRetry` leaving the bodies of failed HTTP responses open when it retried or gave up, which could keep connections busy until garbage collection. Failed responses are now cancelled before retrying or throwing; successful responses are still returned unread. ([#27439](https://github.com/mastra-ai/mastra/pull/27439)) - `Agent.reorderModels()` and `Agent.updateModelInModelList()` now return `true` when the edit was applied and `false` when it was not (no model list, unknown id, or a function-valued `model`). Function-valued models are left untouched so per-request model selection keeps working. ([#27192](https://github.com/mastra-ai/mastra/pull/27192)) ```ts if (!agent.updateModelInModelList({ id, enabled: false })) { // edit was not applied } ``` - Fixed output stream processors dropping text they were still holding when a model stream ended. A reply shorter than one `BatchPartsProcessor` batch (for example with `emitOnNonText: false`) used to stream and save as empty; held parts are now released at the end of each model stream and passed through the processors that come after the one that held them. ([#27565](https://github.com/mastra-ai/mastra/pull/27565)) - Fixed a Session that follows a thread keeping a parked tool call after another Session aborted the run, or after the resumed run ended with a non-success finish such as `content-filter`. The follower now ends the run and retracts the call with `tool_suspension_cancelled`, so `suspensions.hasPending()` returns false. This also works when the follower runs in a different Mastra instance that shares the same pubsub ([#26997](https://github.com/mastra-ai/mastra/issues/26997)). ([#27786](https://github.com/mastra-ai/mastra/pull/27786)) - Fixed a Session that follows a thread keeping a tool call marked as pending after the run was resumed on another instance and completed. The follower now drops the answered call, so `suspensions.hasPending()` returns false and aborting no longer emits a spurious `tool_suspension_cancelled` event. ([#27226](https://github.com/mastra-ai/mastra/pull/27226)) - Fixed durable, evented, and Inngest agent streams to reject when `stopWhen` throws, matching plain agents. ([#26921](https://github.com/mastra-ai/mastra/pull/26921)) Previously, these stream consumers could handle this failure as an `error` chunk. Catch the `fullStream` rejection instead: ```ts try { for await (const chunk of output.fullStream) { // Handle streamed chunks. } } catch (error) { // Handle the error thrown by stopWhen. } ``` Durable transports reconstruct the error from its serialized name and message. The original stack, cause, and subclass identity do not survive a cross-process boundary. Core durable and evented workflows classify this failure with the reserved internal `DURABLE_AGENT_STOP_WHEN_FAILED` identifier. Inngest's serve transport uses the memoized step's `stop-when-` id prefix because its `StepError` serialization does not retain that identifier or its details. Any failure inside that Inngest step is therefore classified as a `stopWhen` failure. - Fixed Gemini 3 agents with working memory failing with "Function call is missing a thought_signature" when continuing a turn (for example after a client tool result). Gemini signs only the first tool call in a parallel step; when that call was `updateWorkingMemory`, removing it from saved history left the step without a signed first call. The next tool call in the step is now saved with Google's documented `skip_thought_signature_validator` signature, so the step replays on any Gemini client. ([#27787](https://github.com/mastra-ai/mastra/pull/27787)) - Fixed the goal judge reading the `TrailingAssistantGuard`'s synthetic `Continue.` turn as the latest user message. On models that reject a trailing assistant turn (Claude 4.6+, Gemini 3+), the judge was told the user had said `Continue.` when they had not, hiding the real latest user message and miscounting assistant steps since it. The guard now marks its continuation, and the goal judge skips it. ([#27503](https://github.com/mastra-ai/mastra/pull/27503)) - Simplified Azure gateway defaults and synchronous stream handling without changing runtime behavior. ([#27646](https://github.com/mastra-ai/mastra/pull/27646)) - Fixed replies being saved as a separate assistant message on Gemini 3+ (and Anthropic 4.6+ with structured output) when the input ends on an assistant message. The reply is now saved into that assistant message, the same as with other providers. The synthetic user turn ("Continue." or "Generate the structured response.") Mastra sends to these providers is also no longer kept in the run's message list after the request is built. ([#27503](https://github.com/mastra-ai/mastra/pull/27503)) - Fixed output stream processors that hold parts (such as `BatchPartsProcessor` and `PIIDetector`) re-emitting or leaking text when a model call failed. When a call is retried, the dropped attempt's text is no longer repeated before the retry's reply. When a call errors or is aborted, the text it was holding is dropped instead of being sent, and the error or abort still reaches the caller. Parts a processor holds back and sends again now reach the stream instead of getting stuck, so the reply's `text-end` is no longer lost. ([#27615](https://github.com/mastra-ai/mastra/pull/27615)) - Fixed live DurableAgent observers attaching to runs owned by a different agent ID while preserving same-ID observation. Prepared runs now retain the DurableAgent's effective ID and name so ownership remains consistent after a restart. ([#27353](https://github.com/mastra-ai/mastra/pull/27353)) - Stop an agent run as soon as another replica asks to abort it, even after the run has lost its thread lease. Previously the abort request was ignored once the lease was gone, so the run kept calling tools until its own lease renewal noticed the loss. ([#27738](https://github.com/mastra-ai/mastra/pull/27738)) - Fixed token usage being counted twice after a tool call suspends and the run resumes. Spans and metrics for the resumed run now report only the tokens used after resuming, including when the resumed run suspends again. ([#27634](https://github.com/mastra-ai/mastra/pull/27634)) - Fixed automatic durable agent recovery stopping after temporary storage or pubsub errors. Skipped runs are retried when the backend becomes available, without another restart or manual recovery. ([#27354](https://github.com/mastra-ai/mastra/pull/27354)) - Fixed slow provider listing in `mastra dev`. Opening the model picker in Studio (`GET /api/agents/providers`) no longer re-reads the shared provider cache from disk for every provider, so the request drops from seconds to well under a second. ([#26711](https://github.com/mastra-ai/mastra/pull/26711)) - Failed turns on the default Agent now save the same history as durable agents. ([#27571](https://github.com/mastra-ai/mastra/pull/27571)) - A reply that fails mid-stream keeps the text streamed so far, followed by the error, instead of only the error. - When an input processor throws, the user message and the error are saved to memory, and the stream ends with an `error` chunk instead of the run failing with nothing saved. - Fixed live scorers passed to `stream()`/`generate()` via `scorers`, or set in an agent's `defaultOptions.scorers`, being skipped on the Inngest engine when they are not registered on the agent or on Mastra. They now run after the call, as they already do on the default and evented engines. ([#27719](https://github.com/mastra-ai/mastra/pull/27719)) - Fixed tool calls disappearing from conversation history when a message or reminder arrived while the tool was still running. The call and its result now stay in history, so the model sees its own completed work on the next turn. ([#26963](https://github.com/mastra-ai/mastra/pull/26963)) - Fixed a thread that stopped starting runs after a run waiting on a tool prompt was followed by another run on the same thread. Once that other run finished, every new message, signal or notification was reported delivered but never ran until the process restarted. The waiting run now gives the thread up when another run takes it over, and it can still be resumed with the user's answer. A run that is still waiting on its prompt keeps holding the thread as before. ([#27545](https://github.com/mastra-ai/mastra/pull/27545)) - Fixed `LocalSandbox` commands that could hang past their timeout. When a command times out, it is asked to stop and then force-killed two seconds later if it is still running, including child processes that keep its output open. Previously a command that ignored the stop request kept the tool call waiting forever. ([#26879](https://github.com/mastra-ai/mastra/pull/26879)) - Stop an agent run whose thread lease can no longer be confirmed. Previously, if lease renewals failed with an error or never answered (for example during a Redis outage on one process), the run kept calling the model and tools after its lease expired, alongside the successor run that took over the thread. Each run now aborts once one lease TTL has passed since its last confirmed acquire or renewal, the same way it already aborts when a renewal reports the lease lost. ([#27731](https://github.com/mastra-ai/mastra/pull/27731)) - Fixed assistant message merging slowing down quadratically with the number of message parts. A step with thousands of reasoning items (common with OpenAI Responses reasoning) could previously block the event loop for tens of seconds; merging is now linear and produces the same merged messages. ([#27912](https://github.com/mastra-ai/mastra/pull/27912)) - Fixed evented `foreach` restarts hanging when the next iteration had not started and nested workflow iterations re-running already completed child steps. ([#26368](https://github.com/mastra-ai/mastra/pull/26368)) - Fixed MCP tool results containing images or audio being dropped by AI SDK v5 models and rejected by AI SDK v7 models such as Amazon Bedrock (`unsupported tool content part type: image-data`). MCP media content is now passed to every model in the format it supports. ([#26662](https://github.com/mastra-ai/mastra/pull/26662)) - Fixed a mode's `availableTools` being dropped after the run paused for the user. Approving or declining a tool call, answering `ask_user` or `request_access`, or rejecting a plan used to continue the run with every tool the agent has, including tools the mode excludes. The continuation now gets the same restricted tool set as the first model call. ([#27864](https://github.com/mastra-ai/mastra/pull/27864)) - Fixed notification dispatch stalling behind notifications it leaves pending. When a delivery policy holds notifications for another process's dispatcher (or they wait on an active run), `dispatchDueNotifications` now reads past them instead of re-reading the same first page every pass, so notifications behind them are still delivered. `limit` now counts only the notifications a pass delivers or fails. ([#27426](https://github.com/mastra-ai/mastra/pull/27426)) - Fixed notifications that ran out of delivery attempts disappearing from the inbox. The `notification-inbox` tool's default `list` now includes `failed` notifications, reporting them as `failed` with their last delivery error, and marks them seen so they show up once rather than on every list. ([#27453](https://github.com/mastra-ai/mastra/pull/27453)) - Fixed `observe()` hanging forever on a durable or evented agent run that was aborted while waiting for tool approval. Once the rejected approval or resume releases the run, observers now receive the `abort` and `finish` chunks and close. ([#27923](https://github.com/mastra-ai/mastra/pull/27923)) - Fixed agent runs stopping when background observational memory buffering fails. The failure is still reported as an `om_buffering_failed` event, and the turn keeps going. A failed synchronous observation still follows the configured failure policy. ([#27438](https://github.com/mastra-ai/mastra/pull/27438)) - Fixed a prompt waiting on the user disappearing from the session's display when another run on the same thread finished. The prompt could still be answered, but nothing showed it. Only the ending run's own prompts are now cleared. ([#27555](https://github.com/mastra-ai/mastra/pull/27555)) - Fixed `get_process_output` with `wait: true` hanging after a background command had exited. When the command left a child running that still held its output open (`cmd &`, or a child of `timeout`), the wait never returned. It now returns once the command exits and its output has had a moment to drain, and says when a process the command started is still running. That process can still be stopped with `kill_process`, and is still stopped with the sandbox. A foreground command that starts a process in the background (`cmd &`) now returns once its shell exits rather than at its timeout, and the process it left behind is stopped when it returns. Use `background: true` for processes that should keep running. ([#27578](https://github.com/mastra-ai/mastra/pull/27578)) - Fixed notifications that failed on their first delivery attempt. Delivery now retries automatically, up to five attempts, instead of staying pending forever. Retries preserve the claimed-owner requirement. ([#25837](https://github.com/mastra-ai/mastra/pull/25837)) - Fixed `Agent.stream()` ending with a `finish` chunk (reason `stop`) when an output processor aborts in `processOutputResult`. The stream now ends with a `tripwire` chunk and `status` is `'tripwire'`, matching durable agents and other processor tripwires. ([#27377](https://github.com/mastra-ai/mastra/pull/27377)) - Fixed resumed agent spans to remain children of the initial agent run across repeated suspensions, matching durable agent tracing. ([#27149](https://github.com/mastra-ai/mastra/pull/27149)) - `respondToPersistedToolApproval` now accepts `alwaysAllowCategory`. Approving a tool call restored after a restart with "always allow" grants the tool's category to the run's thread, matching a live approval. ([#27222](https://github.com/mastra-ai/mastra/pull/27222)) ```ts await session.respondToPersistedToolApproval({ toolCallId, approved: true, alwaysAllowCategory: true }); ``` - Fixed trajectory scorers in `runEvals` and dataset experiments getting a flat list of tool calls instead of the full trace when storage is configured. `runEvals` also no longer waits up to 6 seconds per item before scoring. ([#27271](https://github.com/mastra-ai/mastra/pull/27271)) - Fixed a TypeScript error (TS2345) when chaining processor steps with `.then()` or `.parallel()`. Steps built with `createStep(processor)` now accept the output of a previous processor step, so the output guardrails workflow from the docs type-checks: ([#27764](https://github.com/mastra-ai/mastra/pull/27764)) ```ts const workflow = createWorkflow({ id: 'output-guardrails', inputSchema: ProcessorStepSchema, outputSchema: ProcessorStepSchema, }) .then(createStep(new TokenLimiterProcessor({ limit: 1000 }))) .then(createStep(new BatchPartsProcessor())) .commit(); ``` - Fixed approved tools added by an input processor running with the suspended run's request context. When you approve a tool call with `approveToolCall({ requestContext })`, a tool that an input processor added now sees the request context you passed, just like a tool defined on the agent. ([#27570](https://github.com/mastra-ai/mastra/pull/27570)) - Fixed experiment comparisons when item, scorer, or experiment IDs match built-in object property names such as `constructor`, `toString`, or `__proto__`. Comparisons previously threw or dropped those entries; they now group and report them like any other ID. ([#27718](https://github.com/mastra-ai/mastra/pull/27718)) - Fixed a misleading "No memory is configured but resourceId and threadId were passed in args" warning logged every time Observational Memory ran its Observer or Reflector. The warning now fires only when `memory.thread` and `memory.resource` are passed to an agent that has no memory, not when the ids come from the request context (auth middleware, AgentController), which every agent in the request inherits. ([#27865](https://github.com/mastra-ai/mastra/pull/27865)) - Fixed several ways a suspended agent run could be resumed twice, never, or after it was aborted. ([#27791](https://github.com/mastra-ai/mastra/pull/27791)) - **Approving after abort:** approving, declining, or resuming a run after `abortRunStream()` / `abortThreadStream()` hit its suspension now fails with `AGENT_RESUME_ABORTED_RUN` instead of running the tool or starting another model request. Fixes #27617. - **`listSuspendedRuns()` after resume:** `DurableAgent` now marks a resumed run as running as soon as it resumes, so it drops out of `listSuspendedRuns()` right away, including in other processes. A second resume of the same run fails with `AGENT_RESUME_ALREADY_CLAIMED` instead of running twice. Fixes #27619. - **Resumer process dies:** a running resume keeps renewing its claim. If the process dies, a later resume or approval fails with `AGENT_RESUME_ALREADY_CLAIMED` until the claim lapses, then takes the run over (or fails with `AGENT_RESUME_RUN_FINISHED` when recovery already completed it). Fixes #27366. - Fixed durable agent runs saving every tool output a second time in their final snapshot, which made the last snapshot write noticeably larger than the earlier ones. ([#27672](https://github.com/mastra-ai/mastra/pull/27672)) - Fix experiment comparisons to average repeated trial scores per item instead of keeping only the last score. Items retain equal weight when experiments have different numbers of trials. ([#27705](https://github.com/mastra-ai/mastra/pull/27705)) - Fixed notifications getting stuck when the delivery policy throws while the notification is being sent. The notification now records a failed delivery attempt and is retried by the notification dispatcher, up to the attempt limit, instead of staying pending forever. ([#27736](https://github.com/mastra-ai/mastra/pull/27736)) - Fixed the 30-second default timeout for `LocalSandbox` foreground commands. A command run without a timeout could previously wait forever. Configured defaults and per-command timeouts are honored, including `0` to disable the timeout, and cloned sandboxes keep the configured default. Background processes keep their existing lifetime. ([#26878](https://github.com/mastra-ai/mastra/pull/26878)) - Fixed `respondToToolSuspension()` hanging when answering a tool call (such as `ask_user`) that was parked during a run you aborted. The resumed run now streams to the Session and completes normally, instead of being silently dropped. ([#26899](https://github.com/mastra-ai/mastra/pull/26899)) - Fixed system messages added by input processors (such as the `TaskSignalProvider` task-list instruction) going missing when an `Agent` resumes after tool approval. Fixes #24844. ([#27372](https://github.com/mastra-ai/mastra/pull/27372)) Resumed runs now reuse the system prompt from the suspended run, matching durable agents. Instructions or system messages passed only at resume time no longer replace it. - Fixed `Agent.resumeGenerate()` and `Agent.resumeStream()` dropping `returnScorerData` on a plain Agent. A run started with `returnScorerData: true` now returns scoring data after a tool-approval or suspend resume, including when the resume happens in a different process or `defaultOptions` disable it. Passing `returnScorerData` to the resume call still overrides the run's value. ([#27747](https://github.com/mastra-ai/mastra/pull/27747)) - Fixed lost messages when a per-step save fails. With `savePerStep` enabled, a failed write used to drop the drained messages from the unsaved set, so no later save wrote them and the user's message or the step's output disappeared from the thread. Failed saves now mark those messages unsaved again, and the next save writes them. ([#27556](https://github.com/mastra-ai/mastra/pull/27556)) - Clarified that schedule `onError` and `onAbort` hooks only observe failures and aborts the schedule worker sees directly. Threaded schedules report failures after a run starts on the thread's own run, not through these hooks. ([#27812](https://github.com/mastra-ai/mastra/pull/27812)) - Scorer run and scorer step spans now record the scorer name as `entityName`, falling back to the scorer ID. Previously scorer traces had no entity name, so filtering or grouping traces by entity name left them out. ([#27684](https://github.com/mastra-ai/mastra/pull/27684)) - Fixed authenticated agent controller sessions failing with `AGENT_MEMORY_THREAD_RESOURCE_MISMATCH` when auth uses `mapUserToResourceId`. The controller now sets the reserved memory resource and thread keys to the session's own resource and thread. Other auth values in the request context are kept. ([#27304](https://github.com/mastra-ai/mastra/pull/27304)) - Fixed channel conversations (Slack, Discord, Telegram and others) never getting an automatic title. New channel threads now start without a title, like any other thread, so `generateTitle` names them from the conversation. ([#27251](https://github.com/mastra-ai/mastra/pull/27251)) Before, every new channel thread was saved with a generic label such as `slack conversation`, which counted as an existing title and blocked title generation. With `generateTitle` turned off, new channel threads now appear untitled in Studio, which lists them by creation date, instead of showing that label. Existing threads keep their current title. - Fixed trace query validation errors for an oversized `in` / `notIn` set pointing at the whole `where` predicate instead of its `set`. Zod 4.6.7 stopped reporting which union branch failed in this case. ([#27699](https://github.com/mastra-ai/mastra/pull/27699)) - Bump for npm ([#27221](https://github.com/mastra-ai/mastra/pull/27221)) - Fixed `System message must be at the beginning` errors from models served through Chat Completions APIs (e.g. Qwen3.5/3.6 or Gemma 3 on vLLM). Multiple leading system messages are now merged into one for Chat Completions providers. Prompts are left unchanged when an earlier system message carries its own provider options (such as a cache breakpoint). Providers with a dedicated system field (Anthropic, Google, Bedrock) and the OpenAI Responses API are unchanged. ([#27307](https://github.com/mastra-ai/mastra/pull/27307)) - Fixed skill tools (`skill`, `skill_read`, `skill_search`) pausing for approval when `requireToolApproval` is enabled globally. Previously every skill load in an approval-enabled agent (including durable agents) suspended for an approval nobody needed. ([#27296](https://github.com/mastra-ai/mastra/pull/27296)) - Fixed stored skills ignoring the `metadata` filter. `skills.list({ metadata })` and `skills.listResolved({ metadata })` now return only skills whose resolved version metadata matches every key/value pair, with correct `total` and pagination. ([#27462](https://github.com/mastra-ai/mastra/pull/27462)) ```ts const skills = await storage.getStore('skills'); await skills.listResolved({ metadata: { publishedBy: 'user-123' } }); ``` - Fixed Slack channel messages so agents receive the text of Block Kit layout blocks (header, section, context, markdown), not just the notification fallback. Block-only messages now reach the agent. Thread history includes the block text too. ([#27393](https://github.com/mastra-ai/mastra/pull/27393)) - Fixed input processor rejections being ignored by agents running in separate worker processes. Upgrade both API and worker processes to apply the fix. ([#27730](https://github.com/mastra-ai/mastra/pull/27730)) - Fixed evented workflows losing recoverable snapshots when terminal notifications fail. ([#27355](https://github.com/mastra-ai/mastra/pull/27355)) - Fixed durable agent startup recovery getting stuck behind a stalled run. Each run's recovery wait is limited to 30 seconds without cancelling its execution, and recovery stops starting new work during shutdown. ([#27357](https://github.com/mastra-ai/mastra/pull/27357)) - Removed the `gray-matter` dependency. SKILL.md frontmatter is now parsed with the `yaml` package directly, which drops the vulnerable `sprintf-js` package (GHSA-hp3w-g68c-fv3c, no patched release available) from the production dependency tree. YAML frontmatter that worked before keeps working; JavaScript frontmatter (`---js`) is still rejected. ([#27198](https://github.com/mastra-ai/mastra/pull/27198)) - Fixed durable agent runs losing user input when a worker stops before the run finishes. ([#27835](https://github.com/mastra-ai/mastra/pull/27835)) Durable runs now stop before calling the model when the accepted input cannot be saved — for example when the storage write fails or the caller has no write permission on the thread — instead of answering without persisting it. Memory configured with `readOnly` still runs without writing. - Agent controller session routes no longer start a session as a side effect. Only `POST /agent-controller/:controllerId/sessions` starts one. Before, a message, model switch or state write that reached the server first created the session on a new random thread. The write then landed on a thread the client never read, so it looked lost. ([#27474](https://github.com/mastra-ai/mastra/pull/27474)) Every other session route now acts on a started session. After a server restart it rebuilds the session from its stored thread. If the session was never started, the route returns `404` and creates nothing. ```ts const session = client.getAgentController('code').session(resourceId); await session.create({ threadId }); // start first await session.sendMessage('Hello'); // 404 if the session was never started ``` `AgentController.createSession` accepts `resumeOnly: true` to get this behavior. It throws `SessionNotStartedError` when no stored thread matches. Factory now starts the session on its own thread when the session is created, so the first prompt can be sent right away. - Fixed paused tool calls picking up a different mode's tools when they resume. Approving, declining, or answering a paused tool call now uses the tools of the mode the run was paused in. Before, switching modes while a call waited gave the continuation the new mode's tools. A switch to an unrestricted mode could expose every tool to a run that started in a restricted one. The same applied when answering a suspension from another thread. Approving a plan still moves the run to the plan's transition mode. Fixes #27873. ([#27911](https://github.com/mastra-ai/mastra/pull/27911)) - Fixed `stopWhen` receiving the wrong step content when `savePerStep` is enabled. After a run was resumed (for example after a tool suspended to ask the user a question), a step could show up with no tool results, so conditions such as "stop after N failed tool calls" counted wrong and fired late. ([#27521](https://github.com/mastra-ai/mastra/pull/27521)) - Calling `abort()` with no reason from an output processor's `processOutputStream` now reports `Tripwire triggered by ` on durable and evented agents, the same as `Agent.stream()` and every other processor hook. Before this fix those engines reported `Stream part blocked by `. ([#27567](https://github.com/mastra-ai/mastra/pull/27567)) - Fixed tool payload transforms not being applied to streamed tool calls. When a model streamed tool-call arguments, the `tool-call` chunk sent to the stream had no transform metadata, so UIs showed the raw arguments instead of the redacted display version. The stream now emits the model's final tool call, which carries the transform. ([#27501](https://github.com/mastra-ai/mastra/pull/27501)) A tool call ID of `__proto__` no longer breaks the stream. A later step that reuses a tool call ID is now kept as its own call in `toolCalls` instead of being dropped. - Fixed missing token usage when a tool call suspends or waits for approval. Suspended `stream()` and `generate()` results now include the step's tokens. After resuming, `totalUsage` stays cumulative and token metrics count those tokens once. See #27232. ([#27425](https://github.com/mastra-ai/mastra/pull/27425)) - Fixed messages being lost when sent to a thread shortly after the same instance cleaned up the thread's abandoned suspended run. Such a send used to return `deliver` while nothing received the message; it now returns `blocked`. Retry the send, and once the old run's lease expires (15 seconds by default) it starts a new run. ([#27389](https://github.com/mastra-ai/mastra/pull/27389)) - Added `AgentController.setThreadModel` to change a session thread's model without recreating the session. A running session bound to the thread switches immediately; otherwise the model is saved for its next run. ([#27591](https://github.com/mastra-ai/mastra/pull/27591)) ```ts const { live } = await controller.setThreadModel({ resourceId: 'session-1', threadId: 'thread-1', modelId: 'openai/gpt-5.6', }); ``` - Fixed `isBadRequestError` retrying invalid API key errors. Newer OpenAI provider versions report `invalid_api_key` stream errors with status 400, so they were retried as bad requests instead of failing right away. Authorization errors now stay terminal. ([#27788](https://github.com/mastra-ai/mastra/pull/27788)) - Fixed trace, span and thread query validation errors for oversized membership sets. An `in`/`notIn` predicate with more than 100 values points at the set again (for example `where.set` or `where.spans.some.set`). Since the zod 4.6.7 upgrade, the error pointed only at `where`. ([#27782](https://github.com/mastra-ai/mastra/pull/27782)) - Improved cancellation of durable and evented agent tool and model calls after execution ownership is lost. Cooperative tools receive an abort signal so superseded work can stop sooner without canceling the recovering run. Recovery may run a tool again, so tools must be safe to repeat. Cancellation cannot undo work already completed. ([#27490](https://github.com/mastra-ai/mastra/pull/27490)) Pass the tool's abort signal to operations that support cancellation: ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const fetchText = createTool({ id: 'fetch-text', description: 'Fetch text from a URL.', inputSchema: z.object({ url: z.string().url() }), outputSchema: z.string(), execute: async ({ url }, context) => { // Without the signal, the request keeps running after ownership is lost. const response = await fetch(url, { signal: context?.abortSignal }); return response.text(); }, }); ``` - Fixed `timeTravel()` running the same steps twice when called concurrently on one run. `timeTravel()` now atomically claims the run before executing, like `resume()` does. A losing call throws `WORKFLOW_TIME_TRAVEL_ALREADY_CLAIMED` (or `WORKFLOW_TIME_TRAVEL_RUN_RUNNING` if the run is already executing) and `POST /api/workflows/:workflowId/time-travel-async` returns `409 Conflict`. The claim isn't enforced with storage adapters that don't support concurrent updates, when `shouldPersistSnapshot` excludes the `running` status, or in the Inngest runner; concurrent calls there can still run steps twice. Fixes #27279. ([#27313](https://github.com/mastra-ai/mastra/pull/27313)) - Fixed agents crashing with `Class constructor ... cannot be invoked without 'new'` when a tool's schema is a class-based Standard Schema, such as an Effect `Schema.Class` adapted with `Schema.toStandardSchemaV1`. Tools can now use class-based schemas the same way as `Schema.Struct`. ([#27317](https://github.com/mastra-ai/mastra/pull/27317)) - Fixed agents stopping after a server tool whose `execute` returns `undefined`. The tool now records a `null` result, and the agent includes the tool call and result in the next turn. ([#27392](https://github.com/mastra-ai/mastra/pull/27392)) - Fixed trace scoring so `type: 'agent'` scorers receive the tool calls an agent made. Scoring a stored agent trace (for example with `scoreTraceBatch` or `POST /api/observability/traces/score`) missed tool calls recorded under a model step. Tool calls and MCP tool calls are now included in the order they started. Calls made inside sub-agents or inside a tool are not credited to the parent agent. ([#27390](https://github.com/mastra-ai/mastra/pull/27390)) - Fixed `UnixSocketPubSub` intermittently failing to start a broker when the socket path is a bare relative name like `events.sock` or `./events.sock`. The broker's random private socket name could look like a number (for example `.28381e422682`), which Node treated as a TCP port and rejected `subscribe()` with `ERR_SOCKET_BAD_PORT` or `ENOENT`. The private socket path now always keeps its `./` prefix. ([#27894](https://github.com/mastra-ai/mastra/pull/27894)) - Fixed durable and evented agents losing a suspended run when an `untilIdle` stream is cancelled. Breaking out of `streamUntilIdle()` (or the request disconnecting) while the run waits for a tool approval or a `suspend()` resume now only stops reading, so `resumeStreamUntilIdle()` and `approveToolCall()` work afterwards instead of failing with "No registry entry found for run … Cannot resume". Cancelling while a tool is still running still aborts the run, and `result.abort()` still releases a suspended run. ([#27563](https://github.com/mastra-ai/mastra/pull/27563)) - Tools that require approval now validate their input before asking for approval. Previously an agent asked a person to approve a call whose arguments did not match the tool's `inputSchema`, and the call then failed validation after it was approved. Now the validation error goes straight back to the model so it can retry, and no `tool-call-approval` is emitted. This applies to `Agent`, durable agents, and agent networks. Fixes #26771. ([#26973](https://github.com/mastra-ai/mastra/pull/26973)) - Security: bump the internal `@ai-sdk/provider-utils-v6` alias from 4.0.40 to 4.0.56 to pick up the fix for AIKIDO-2026-526145. No public API changes. ([#27023](https://github.com/mastra-ai/mastra/pull/27023)) - Fixed deferred notifications running with no request context when the agent's delivery policy throws at delivery time. The delivery attempt now fails with the policy's error and is retried up to the attempt limit, instead of falling back to a bare wake that runs without a user. ([#27729](https://github.com/mastra-ai/mastra/pull/27729)) Fixed low-priority notification summaries failing to save when the agent's memory depends on who owns the thread. The summary is still saved without starting a run, but now carries the delivery policy's request context. - Fixed observation of unknown and completed durable agent runs so streams close promptly after replaying retained events, including when the requested offset skips the final event or a completed workflow snapshot remains after cleanup. Observers stay attached when lifecycle state cannot be read. ([#27358](https://github.com/mastra-ai/mastra/pull/27358)) - Fixed a message sent from an `AgentController` session being dropped when another agent was mid-run on the session's thread. The message now waits for that run to end and then runs. Pressing Stop while it waits drops it. If that run is waiting for an answer to its own prompt, the message isn't sent and the session shows an error saying so. A message sent while the thread is still held by a waiting run that was dropped from memory now waits for that hold to expire, then runs. Signals sent straight to the agent still report the thread as blocked. ([#27624](https://github.com/mastra-ai/mastra/pull/27624)) - Fixed goal judges and subagents hanging inside an Agent Controller session. Previously they inherited the session's memory thread and waited on it forever. Goal judges now use their own goal-specific thread. Forked subagents use the cloned thread. Non-forked subagents no longer inherit the session's thread and resource IDs. ([#27323](https://github.com/mastra-ai/mastra/pull/27323)) - Boot-time workflow recovery no longer re-runs a workflow run that another live server instance is still executing. Previously, when one replica started while another was mid-run, `restartAllActiveWorkflowRuns()` restarted every `running` run and its steps executed twice. ([#27508](https://github.com/mastra-ai/mastra/pull/27508)) Workflow runs now hold the existing storage run-ownership lease (the one durable agents use) while they execute. `run.restart()` throws a `WORKFLOW_RESTART_RUN_OWNED` error instead of taking over a live run. The boot sweep skips those runs and checks them again once the lease could have expired, so runs left behind by a crashed process are still recovered. Storage adapters without run-ownership support keep the previous behavior. - Updated dependencies [[`fae54c5`](https://github.com/mastra-ai/mastra/commit/fae54c57917cd1f527937ca42b5ab7dfe1d55a2a)]: - @mastra/schema-compat@1.3.16 ## 1.79.0-alpha.12 ### Minor Changes - Added optional, experimental `getEventsInfo()` and `getEventsInspector()` members to `MCPServerBase`, plus the event info types (`MCPServerEventInfo`, `MCPServerEventStreamFrame`, `MCPServerEventsInspector`, …) and `MCP_SERVER_EVENT_ERROR_IDS`. Servers that declare MCP events use them to let Studio list, poll, stream and test-emit their event types. Servers without events don't need to implement them. ([#27674](https://github.com/mastra-ai/mastra/pull/27674)) ```ts const info = server.getEventsInfo?.(); console.log(info?.events.map(event => event.name)); const inspector = server.getEventsInspector?.(); const { events, cursor } = await inspector!.poll('ticket.created', { arguments: { project: 'web' } }); ``` - Agent schedules can now send an idle wake to the worker that has claimed the thread. Set `ifIdle.requireClaimedOwner: true` on the schedule, or return it from the `prepare` hook. This works the same way as `agent.sendSignal`: the claim holder runs the turn with its own claim stream options. If no owner answers, the fire fails with `outcome: 'failed'`. Before this change, the option was dropped, so whichever worker took the fire ran the turn itself. ([#27896](https://github.com/mastra-ai/mastra/pull/27896)) ```ts await mastra.schedules.create({ agentId: 'support', cron: '0 * * * *', prompt: 'Check in', threadId, resourceId, ifIdle: { behavior: 'wake', requireClaimedOwner: true }, }); ``` - Added an opt-in `apply_patch` workspace tool that creates, updates, moves and deletes several files in one call. The tool checks the whole patch before it writes anything. If a write fails partway, it tries to undo the changes it already made. The tool is off by default. Enable it per tool: ([#27913](https://github.com/mastra-ai/mastra/pull/27913)) ```ts new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), tools: { [WORKSPACE_TOOLS.FILESYSTEM.APPLY_PATCH]: { enabled: true } }, }); ``` Setting the top-level `tools.enabled` does not turn it on, so existing agents keep using `write_file` and `edit_file`. Paths follow the same containment, read-before-write and write-lock rules as the other filesystem tools. ### Patch Changes - Fixed `autoResumeSuspendedTools` leaving a resumed plain tool marked as suspended. This happened when the model resumed a regular (non-agent, non-workflow) tool under a new tool call id. The original suspension was never cleared. Every later turn asked the model to resume the tool again. Reloaded clients also showed the call as still pending. The original suspension is now cleared, and a `tool-call-resumed` chunk is emitted for it. This applies to the standard, evented and durable agents. ([#27784](https://github.com/mastra-ai/mastra/pull/27784)) - Fixed approval after a host disconnect without cancelling the parked agent run. ([#27907](https://github.com/mastra-ai/mastra/pull/27907)) - Fixed evented workflow restarts replaying completed steps and ensured recovered durable-agent streams finish without repeating saved effects. ([#27852](https://github.com/mastra-ai/mastra/pull/27852)) - xAI models now accept `xhigh` reasoning effort. Groq, xAI, Together AI, DeepInfra, Cerebras and Alibaba models now run on the AI SDK v7 provider packages, like DeepSeek. ([#26530](https://github.com/mastra-ai/mastra/pull/26530)) ```ts await agent.generate('Prove that there are infinitely many primes.', { providerOptions: { xai: { reasoningEffort: 'xhigh' } }, }); ``` - Fixed assistant message merging slowing down quadratically with the number of message parts. A step with thousands of reasoning items (common with OpenAI Responses reasoning) could previously block the event loop for tens of seconds; merging is now linear and produces the same merged messages. ([#27912](https://github.com/mastra-ai/mastra/pull/27912)) - Fixed `observe()` hanging forever on a durable or evented agent run that was aborted while waiting for tool approval. Once the rejected approval or resume releases the run, observers now receive the `abort` and `finish` chunks and close. ([#27923](https://github.com/mastra-ai/mastra/pull/27923)) - Fixed paused tool calls picking up a different mode's tools when they resume. Approving, declining, or answering a paused tool call now uses the tools of the mode the run was paused in. Before, switching modes while a call waited gave the continuation the new mode's tools. A switch to an unrestricted mode could expose every tool to a run that started in a restricted one. The same applied when answering a suspension from another thread. Approving a plan still moves the run to the plan's transition mode. Fixes #27873. ([#27911](https://github.com/mastra-ai/mastra/pull/27911)) ## 1.79.0-alpha.11 ### Minor Changes - Sandboxes can now expose their own disk through an optional `filesystem` property on `WorkspaceSandbox`. A provider with a file API answers reads, writes, and listings in one request each instead of a shell command per step. Paths are absolute inside the sandbox, and the provider decides containment. Fall back to `executeCommand()` when the property is absent: ([#27859](https://github.com/mastra-ai/mastra/pull/27859)) ```ts const pkg = sandbox.filesystem ? await sandbox.filesystem.readFile('/workspace/app/package.json', { encoding: 'utf-8' }) : (await sandbox.executeCommand?.('cat', ['/workspace/app/package.json']))?.stdout; ``` ### Patch Changes - Fixed durable and evented agents losing the conversation when a client tool is approved or declined on an agent without memory. The finish chunk now carries the same `messages` history as the default agent, so the follow-up model request keeps the original user prompt, the tool call and the tool result. The client tool also runs once: the first stream of a durable run stays open after suspension, and it no longer re-runs a tool the approve or decline request can execute. A server-side approve, which sends no client tools, still runs the tool on that open stream. ([#27874](https://github.com/mastra-ai/mastra/pull/27874)) - Fixed a durable `AgentController` session hanging after a prompt left unanswered for longer than `MASTRA_SUSPENDED_RUN_TTL_MS`. Later messages now run and finish as usual. The dropped prompt can still be answered. ([#27632](https://github.com/mastra-ai/mastra/pull/27632)) - Fixed a mode's `availableTools` being dropped after the run paused for the user. Approving or declining a tool call, answering `ask_user` or `request_access`, or rejecting a plan used to continue the run with every tool the agent has, including tools the mode excludes. The continuation now gets the same restricted tool set as the first model call. ([#27864](https://github.com/mastra-ai/mastra/pull/27864)) - Fixed a misleading "No memory is configured but resourceId and threadId were passed in args" warning logged every time Observational Memory ran its Observer or Reflector. The warning now fires only when `memory.thread` and `memory.resource` are passed to an agent that has no memory, not when the ids come from the request context (auth middleware, AgentController), which every agent in the request inherits. ([#27865](https://github.com/mastra-ai/mastra/pull/27865)) - Fixed several ways a suspended agent run could be resumed twice, never, or after it was aborted. ([#27791](https://github.com/mastra-ai/mastra/pull/27791)) - **Approving after abort:** approving, declining, or resuming a run after `abortRunStream()` / `abortThreadStream()` hit its suspension now fails with `AGENT_RESUME_ABORTED_RUN` instead of running the tool or starting another model request. Fixes #27617. - **`listSuspendedRuns()` after resume:** `DurableAgent` now marks a resumed run as running as soon as it resumes, so it drops out of `listSuspendedRuns()` right away, including in other processes. A second resume of the same run fails with `AGENT_RESUME_ALREADY_CLAIMED` instead of running twice. Fixes #27619. - **Resumer process dies:** a running resume keeps renewing its claim. If the process dies, a later resume or approval fails with `AGENT_RESUME_ALREADY_CLAIMED` until the claim lapses, then takes the run over (or fails with `AGENT_RESUME_RUN_FINISHED` when recovery already completed it). Fixes #27366. - Fixed `UnixSocketPubSub` intermittently failing to start a broker when the socket path is a bare relative name like `events.sock` or `./events.sock`. The broker's random private socket name could look like a number (for example `.28381e422682`), which Node treated as a TCP port and rejected `subscribe()` with `ERR_SOCKET_BAD_PORT` or `ENOENT`. The private socket path now always keeps its `./` prefix. ([#27894](https://github.com/mastra-ai/mastra/pull/27894)) ## 1.79.0-alpha.10 ### Patch Changes - Fixed session messages starting while another agent runs on the same thread. When an `AgentController` uses an application's agent and has no `pubsub` of its own, its agents without their own `pubsub` now share the application's event bus. Session messages now wait for the other run to finish. A `pubsub` passed to the controller still takes precedence. ([#27631](https://github.com/mastra-ai/mastra/pull/27631)) - Fixed an agent run that had lost its thread lease running tools next to the run that took over the thread. Lease renewals run every third of the lease TTL, so a run whose lease was taken over could keep calling tools until its next renewal. A run that holds a thread lease now checks the lease owner before each tool call. If another run owns the lease, the run is aborted and the tool does not run. If the lease backend can't answer, the local lease deadline decides as before. ([#27749](https://github.com/mastra-ai/mastra/pull/27749)) - Fixed the goal judge reading the `TrailingAssistantGuard`'s synthetic `Continue.` turn as the latest user message. On models that reject a trailing assistant turn (Claude 4.6+, Gemini 3+), the judge was told the user had said `Continue.` when they had not, hiding the real latest user message and miscounting assistant steps since it. The guard now marks its continuation, and the goal judge skips it. ([#27503](https://github.com/mastra-ai/mastra/pull/27503)) - Fixed replies being saved as a separate assistant message on Gemini 3+ (and Anthropic 4.6+ with structured output) when the input ends on an assistant message. The reply is now saved into that assistant message, the same as with other providers. The synthetic user turn ("Continue." or "Generate the structured response.") Mastra sends to these providers is also no longer kept in the run's message list after the request is built. ([#27503](https://github.com/mastra-ai/mastra/pull/27503)) - Fixed durable agent runs losing user input when a worker stops before the run finishes. ([#27835](https://github.com/mastra-ai/mastra/pull/27835)) Durable runs now stop before calling the model when the accepted input cannot be saved — for example when the storage write fails or the caller has no write permission on the thread — instead of answering without persisting it. Memory configured with `readOnly` still runs without writing. ## 1.79.0-alpha.9 ### Minor Changes - **Breaking:** Agent controller sessions are now addressed by their session id. Many sessions can share one `resourceId`, so `resourceId` only says whose threads and memory a session works on. ([#27336](https://github.com/mastra-ai/mastra/pull/27336)) - Added `controller.getSession(sessionId)`. Removed `controller.getSessionByResource(resourceId, scope)`. - Removed the `scope` option from `createSession()`. Give each session its own `id` instead. When `id` is omitted it defaults to the `resourceId`, so existing one-session-per-resource code keeps working. - `deleteSession()` takes `{ sessionId }`, and `generateThreadTitle()` takes an optional `sessionId`. - The controller request context exposes `sessionId` next to `resourceId`. ```ts // Before await controller.createSession({ resourceId: 'repo-1', scope: 'run-a' }); const session = await controller.getSessionByResource('repo-1', 'run-a'); await controller.deleteSession({ resourceId: 'repo-1', scope: 'run-a' }); // After await controller.createSession({ id: 'run-a', resourceId: 'repo-1' }); const session = await controller.getSession('run-a'); await controller.deleteSession({ sessionId: 'run-a' }); ``` `createSession` throws when an explicit `resourceId` doesn't match the resource of the live session with that id, so a session id can't be used to take over another resource's session. - `aggregateTraces()` can now group by the root span's `entityId`, and trace queries and aggregates accept `entityId` as a trace-scope `where` field. Use it to split traces that have no `entityName` (for example, scorer runs recorded before scorers set one) by the primitive that produced them, then drill into a group by filtering on the same `entityId`. ([#27684](https://github.com/mastra-ai/mastra/pull/27684)) ```ts const { rows } = await client.aggregateTraces({ timeRange, where: { op: 'notExists', path: 'entityName' }, groupBy: ['entityId'], measures: ['count'], }); ``` Trace-scope `entityId` reads the root span only. Inside `spans.some` and `spans.none`, `entityId` still matches any span in the trace. Stores that support the trace-scope field declare the new `trace-query-root-entity-id` storage feature. - Added the storage contract for span aggregation. Storage adapters can implement `aggregateSpans()` and advertise the `span-aggregate` feature to answer questions like "p95 latency per tool" or "token spend per calling agent" over individual spans. ([#27669](https://github.com/mastra-ai/mastra/pull/27669)) ```ts class MyObservabilityStore extends ObservabilityStorage { getFeatures(): readonly ObservabilityStorageFeature[] { return [...(super.getFeatures() ?? []), 'span-aggregate']; } // `plan` is already validated: tool calls grouped by name, with count and p95 duration async aggregateSpans(plan: TrustedSpanAggregatePlan): Promise { // e.g. { rows: [{ dimensions: { name: 'search' }, measures: { count: 42, 'duration.p95': 812 } }], truncated: false } } } ``` Span aggregates use the same request shape, measures, and limits as trace aggregates. Filters follow `querySpans()`, so counts match the spans `querySpans()` returns. Spans can be grouped by name, span type, and their own, parent, or root entity. Grouping by model or provider is not supported yet. Token and cost measures count usage on the span that recorded it. Adapters without the feature reject the call as not implemented. - Added an `embeddingDimension` option to Memory. When you know your embedder's output dimension, setting it lets semantic recall skip the probe embedding call it otherwise makes once per Memory instance. Startup then makes no billed embedding call and doesn't fail when the embedding provider is unreachable. The vector index name stays the same, so existing indexes keep working. If the configured value doesn't match the length of the embeddings the embedder returns, Memory throws a `MASTRA_MEMORY_EMBEDDING_DIMENSION_MISMATCH` error naming both values before creating or querying any vector index. ([#27588](https://github.com/mastra-ai/mastra/pull/27588)) ```ts const memory = new Memory({ embedder: 'openai/text-embedding-3-small', embeddingDimension: 1536, options: { semanticRecall: true }, }); ``` ### Patch Changes - Fixed durable agent startup recovery so unrelated runs can recover concurrently. Timed-out attempts are checked again after settlement without starting duplicate executions or releasing live ownership. ([#27768](https://github.com/mastra-ai/mastra/pull/27768)) - Fixed a Session that follows a thread keeping a parked tool call after another Session aborted the run, or after the resumed run ended with a non-success finish such as `content-filter`. The follower now ends the run and retracts the call with `tool_suspension_cancelled`, so `suspensions.hasPending()` returns false. This also works when the follower runs in a different Mastra instance that shares the same pubsub ([#26997](https://github.com/mastra-ai/mastra/issues/26997)). ([#27786](https://github.com/mastra-ai/mastra/pull/27786)) - Fixed Gemini 3 agents with working memory failing with "Function call is missing a thought_signature" when continuing a turn (for example after a client tool result). Gemini signs only the first tool call in a parallel step; when that call was `updateWorkingMemory`, removing it from saved history left the step without a signed first call. The next tool call in the step is now saved with Google's documented `skip_thought_signature_validator` signature, so the step replays on any Gemini client. ([#27787](https://github.com/mastra-ai/mastra/pull/27787)) - Simplified Azure gateway defaults and synchronous stream handling without changing runtime behavior. ([#27646](https://github.com/mastra-ai/mastra/pull/27646)) - Fixed evented `foreach` restarts hanging when the next iteration had not started and nested workflow iterations re-running already completed child steps. ([#26368](https://github.com/mastra-ai/mastra/pull/26368)) - Clarified that schedule `onError` and `onAbort` hooks only observe failures and aborts the schedule worker sees directly. Threaded schedules report failures after a run starts on the thread's own run, not through these hooks. ([#27812](https://github.com/mastra-ai/mastra/pull/27812)) - Scorer run and scorer step spans now record the scorer name as `entityName`, falling back to the scorer ID. Previously scorer traces had no entity name, so filtering or grouping traces by entity name left them out. ([#27684](https://github.com/mastra-ai/mastra/pull/27684)) - Fixed trace query validation errors for an oversized `in` / `notIn` set pointing at the whole `where` predicate instead of its `set`. Zod 4.6.7 stopped reporting which union branch failed in this case. ([#27699](https://github.com/mastra-ai/mastra/pull/27699)) ## 1.79.0-alpha.8 ### Minor Changes - Trace and span queries can now sort by `timeToFirstChunkMs`, in either direction. Missing values sort last, and pages stay stable when values tie. Stores that support it declare the `trace-query-time-to-first-chunk-sort` feature. ([#27473](https://github.com/mastra-ai/mastra/pull/27473)) ```typescript const page = await storage.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, orderBy: [{ field: 'timeToFirstChunkMs', direction: 'desc' }] }), ), ); ``` ### Patch Changes - Fixed a TypeScript error (TS2345) when chaining processor steps with `.then()` or `.parallel()`. Steps built with `createStep(processor)` now accept the output of a previous processor step, so the output guardrails workflow from the docs type-checks: ([#27764](https://github.com/mastra-ai/mastra/pull/27764)) ```ts const workflow = createWorkflow({ id: 'output-guardrails', inputSchema: ProcessorStepSchema, outputSchema: ProcessorStepSchema, }) .then(createStep(new TokenLimiterProcessor({ limit: 1000 }))) .then(createStep(new BatchPartsProcessor())) .commit(); ``` - Fixed `isBadRequestError` retrying invalid API key errors. Newer OpenAI provider versions report `invalid_api_key` stream errors with status 400, so they were retried as bad requests instead of failing right away. Authorization errors now stay terminal. ([#27788](https://github.com/mastra-ai/mastra/pull/27788)) - Fixed trace, span and thread query validation errors for oversized membership sets. An `in`/`notIn` predicate with more than 100 values points at the set again (for example `where.set` or `where.spans.some.set`). Since the zod 4.6.7 upgrade, the error pointed only at `where`. ([#27782](https://github.com/mastra-ai/mastra/pull/27782)) ## 1.79.0-alpha.7 ### Minor Changes - Add opt-in `bundler.pnpmWorkspaceSettings` to write pnpm install-policy settings to generated deployment workspaces. Supported keys are `allowBuilds`, `onlyBuiltDependencies`, `ignoredBuiltDependencies`, `minimumReleaseAge`, `minimumReleaseAgeExclude`, `trustPolicy`, `trustPolicyExclude`, `trustPolicyIgnoreAfter`, and `blockExoticSubdeps`; explicit values override copied source settings and any other key is rejected before pnpm runs. Existing behavior is unchanged when omitted. ([#27708](https://github.com/mastra-ai/mastra/pull/27708)) ```ts import { Mastra } from '@mastra/core/mastra'; export const mastra = new Mastra({ bundler: { pnpmWorkspaceSettings: { blockExoticSubdeps: false, }, }, }); ``` - Added `backgroundTasks.untilIdleMaxIdleMs` to set the default idle timeout for `stream({ untilIdle: true })`. Before, calls that did not pass `maxIdleMs` always closed after 5 minutes without new events. This included calls Mastra makes internally, such as waking an agent after `sendMessage`. A background task that ran longer than that finished without a continuation turn. A per-call `untilIdle: { maxIdleMs }` still takes precedence. ([#27757](https://github.com/mastra-ai/mastra/pull/27757)) ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, untilIdleMaxIdleMs: 30 * 60_000, }, }); ``` ### Patch Changes - dependencies updates: ([#27728](https://github.com/mastra-ai/mastra/pull/27728)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.61` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.61) (from `npm:@ai-sdk/provider-utils@4.0.56`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.62` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.62) (from `npm:@ai-sdk/provider-utils@5.0.54`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v5@npm:@ai-sdk/provider@2.0.5` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v5/v/2.0.5) (from `npm:@ai-sdk/provider@2.0.3`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.19` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.19) (from `npm:@ai-sdk/provider@3.0.14`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.29` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.29) (from `npm:@ai-sdk/provider@4.0.27`, in `dependencies`) - Fixed agent runs that were aborted while a tool was running finishing with `finishReason: 'tool-calls'`. When a tool stops because the run's abort signal fired, the run now ends with `finishReason: 'aborted'`, emits an `abort` chunk, and calls `onAbort`, the same as an abort during the model response. ([#27748](https://github.com/mastra-ai/mastra/pull/27748)) - Channel streaming: text the agent writes before its first tool call now posts as its own message in `toolDisplay: 'grouped'` mode. An opening acknowledgement lands above the Thinking Steps widget instead of under it. ([#27350](https://github.com/mastra-ai/mastra/pull/27350)) The plan widget in `'grouped'` and `'timeline'` modes is quieter. The header is a fixed "Working…" label and no longer echoes the current tool. Each task row shows the tool's `description` argument (first line only) when it has one, and falls back to `toolName firstArg` otherwise. Grouped mode is an adapter option on the channel provider: ```ts import { Mastra } from '@mastra/core'; import { SlackProvider } from '@mastra/slack'; export const mastra = new Mastra({ channels: { slack: new SlackProvider({ refreshToken: process.env.SLACK_APP_CONFIG_REFRESH_TOKEN, toolDisplay: 'grouped', }), }, }); ``` - Classifier evaluation traces now record the classifier as their entity, so they show the classifier name on the Traces page instead of "(not set)" and can be filtered and grouped like agents, workflows and scorers. `EntityType` gains a `classifier` value. ([#27756](https://github.com/mastra-ai/mastra/pull/27756)) - Fixed durable and evented agents placing `isTaskComplete` feedback after the model's next tool call and its result. The feedback now comes right before the model's retry, as it does with regular agents, so the model reads it as the reason for continuing. Fixes #27548. ([#27743](https://github.com/mastra-ai/mastra/pull/27743)) - Fixed durable and evented agents ignoring per-model `modelSettings` in a model list. When an agent is configured with `model: [{ model, modelSettings: { temperature: 0.1 } }, ...]`, each entry's settings now reach the provider on every attempt, including retries and fallbacks, instead of falling back to call-time settings. ([#27727](https://github.com/mastra-ai/mastra/pull/27727)) - Fixed durable and evented agents dropping earlier tool calls from the prompt on a second output processor retry. When a processor called `abort(reason, { retry: true })` more than once in a run that had already made tool calls, the next request lost the earlier tool call and its result, which broke the provider prompt cache. Durable and evented agents now keep the full history across retries, matching the standard agent loop (#27561). ([#27758](https://github.com/mastra-ai/mastra/pull/27758)) - Fixed durable and evented agents returning an empty `response.messages` when a `processOutputResult` processor rejects the final answer. `response.messages` now contains the assistant's draft, matching `text`, `steps` and the normal loop, so apps can still show the rejected reply to a reviewer. ([#27737](https://github.com/mastra-ai/mastra/pull/27737)) - Fixed live DurableAgent observers attaching to runs owned by a different agent ID while preserving same-ID observation. Prepared runs now retain the DurableAgent's effective ID and name so ownership remains consistent after a restart. ([#27353](https://github.com/mastra-ai/mastra/pull/27353)) - Stop an agent run as soon as another replica asks to abort it, even after the run has lost its thread lease. Previously the abort request was ignored once the lease was gone, so the run kept calling tools until its own lease renewal noticed the loss. ([#27738](https://github.com/mastra-ai/mastra/pull/27738)) - Stop an agent run whose thread lease can no longer be confirmed. Previously, if lease renewals failed with an error or never answered (for example during a Redis outage on one process), the run kept calling the model and tools after its lease expired, alongside the successor run that took over the thread. Each run now aborts once one lease TTL has passed since its last confirmed acquire or renewal, the same way it already aborts when a renewal reports the lease lost. ([#27731](https://github.com/mastra-ai/mastra/pull/27731)) - Fixed notifications getting stuck when the delivery policy throws while the notification is being sent. The notification now records a failed delivery attempt and is retried by the notification dispatcher, up to the attempt limit, instead of staying pending forever. ([#27736](https://github.com/mastra-ai/mastra/pull/27736)) - Fixed `Agent.resumeGenerate()` and `Agent.resumeStream()` dropping `returnScorerData` on a plain Agent. A run started with `returnScorerData: true` now returns scoring data after a tool-approval or suspend resume, including when the resume happens in a different process or `defaultOptions` disable it. Passing `returnScorerData` to the resume call still overrides the run's value. ([#27747](https://github.com/mastra-ai/mastra/pull/27747)) - Fixed input processor rejections being ignored by agents running in separate worker processes. Upgrade both API and worker processes to apply the fix. ([#27730](https://github.com/mastra-ai/mastra/pull/27730)) - Fixed tool payload transforms not being applied to streamed tool calls. When a model streamed tool-call arguments, the `tool-call` chunk sent to the stream had no transform metadata, so UIs showed the raw arguments instead of the redacted display version. The stream now emits the model's final tool call, which carries the transform. ([#27501](https://github.com/mastra-ai/mastra/pull/27501)) A tool call ID of `__proto__` no longer breaks the stream. A later step that reuses a tool call ID is now kept as its own call in `toolCalls` instead of being dropped. - Fixed deferred notifications running with no request context when the agent's delivery policy throws at delivery time. The delivery attempt now fails with the policy's error and is retried up to the attempt limit, instead of falling back to a bare wake that runs without a user. ([#27729](https://github.com/mastra-ai/mastra/pull/27729)) Fixed low-priority notification summaries failing to save when the agent's memory depends on who owns the thread. The summary is still saved without starting a run, but now carries the delivery policy's request context. - Fixed observation of unknown and completed durable agent runs so streams close promptly after replaying retained events, including when the requested offset skips the final event or a completed workflow snapshot remains after cleanup. Observers stay attached when lifecycle state cannot be read. ([#27358](https://github.com/mastra-ai/mastra/pull/27358)) ## 1.79.0-alpha.6 ### Minor Changes - Support embedding specification v4 models from AI SDK v7. Core accepts v4 embedding models and exports `embedV4`; Memory and RAG use the matching SDK to generate embeddings while preserving v1, v2, and v3 behavior. ([#27710](https://github.com/mastra-ai/mastra/pull/27710)) ### Patch Changes - Fixed live scorers passed at call time (`agent.stream(..., { scorers })`) or through `defaultOptions.scorers` never running unless the same scorer was also registered on the agent or on Mastra. Such scorers now run and save their scores on every agent engine, including durable and evented agents. ([#27714](https://github.com/mastra-ai/mastra/pull/27714)) - Fixed live scorers passed to `stream()`/`generate()` via `scorers`, or set in an agent's `defaultOptions.scorers`, being skipped on the Inngest engine when they are not registered on the agent or on Mastra. They now run after the call, as they already do on the default and evented engines. ([#27719](https://github.com/mastra-ai/mastra/pull/27719)) - Fixed experiment comparisons when item, scorer, or experiment IDs match built-in object property names such as `constructor`, `toString`, or `__proto__`. Comparisons previously threw or dropped those entries; they now group and report them like any other ID. ([#27718](https://github.com/mastra-ai/mastra/pull/27718)) - Fix experiment comparisons to average repeated trial scores per item instead of keeping only the last score. Items retain equal weight when experiments have different numbers of trials. ([#27705](https://github.com/mastra-ai/mastra/pull/27705)) ## 1.79.0-alpha.5 ### Minor Changes - Fixed workspace search and file listing ignoring `.gitignore` files below the workspace root. Content search and the file tree now apply every `.gitignore` relative to its own directory (for example, each repository in a multi-repo workspace) and skip `node_modules` unless the path points inside it. This keeps search from reading dependency files one by one on remote filesystems, and keeps file listings free of ignored and dependency files. ([#27687](https://github.com/mastra-ai/mastra/pull/27687)) Filesystems that implement `walk` can now honor two new options: `includeGitignoreFiles` returns `.gitignore` files even when hidden entries are excluded, and `skipDirectoryNames` skips directories such as `node_modules` entirely. ```ts const entries = await filesystem.walk('.', { includeGitignoreFiles: true, skipDirectoryNames: ['node_modules'], }); ``` ### Patch Changes - Fixed agent runs that kept calling the model and tools after losing their thread lease. When lease renewal fails, the run is now aborted instead of running beside the run that took over the thread on another process. Applies to regular, durable and evented agents. ([#27581](https://github.com/mastra-ai/mastra/pull/27581)) - Fixed workspace search and file listing so a deeper `.gitignore` can re-include what a parent ignores (for example `!keep.log` in a subdirectory overriding `*.log`), matching git. Search results from a filesystem's native `grep` now also apply `.gitignore` files in subdirectories of the search path. ([#27695](https://github.com/mastra-ai/mastra/pull/27695)) - Fixed DurableAgent ignoring the reserved `mastra__threadId` and `mastra__resourceId` request-context keys. Durable and evented agents now route memory to the thread and resource set on the request context, as the regular Agent does, instead of to the thread or resource named in the call's `memory` options. ([#27575](https://github.com/mastra-ai/mastra/pull/27575)) - Fixed durable and evented agents ignoring `generateTitle.emitEvent` and `onTitleGenerated`. Streams from these agents now include the transient `data-thread-title` chunk before `finish`, and `memory.onTitleGenerated` is called with the saved title, matching the standard agent. ([#27606](https://github.com/mastra-ai/mastra/pull/27606)) - Fixed output stream processors that hold parts (such as `BatchPartsProcessor` and `PIIDetector`) re-emitting or leaking text when a model call failed. When a call is retried, the dropped attempt's text is no longer repeated before the retry's reply. When a call errors or is aborted, the text it was holding is dropped instead of being sent, and the error or abort still reaches the caller. Parts a processor holds back and sends again now reach the stream instead of getting stuck, so the reply's `text-end` is no longer lost. ([#27615](https://github.com/mastra-ai/mastra/pull/27615)) - Added `AgentController.setThreadModel` to change a session thread's model without recreating the session. A running session bound to the thread switches immediately; otherwise the model is saved for its next run. ([#27591](https://github.com/mastra-ai/mastra/pull/27591)) ```ts const { live } = await controller.setThreadModel({ resourceId: 'session-1', threadId: 'thread-1', modelId: 'openai/gpt-5.6', }); ``` ## 1.79.0-alpha.4 ### Minor Changes - Channel `resolveResourceId` and `resolveThreadId` hooks now receive the per-message `requestContext`. Values a channel handler sets before calling `defaultHandler`, such as a tenant id, can be read directly in the resolvers instead of being looked up a second time. ([#27436](https://github.com/mastra-ai/mastra/pull/27436)) ```ts channels: { handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.requestContext.set('tenantId', await lookupTenant(thread.channelId)); await defaultHandler(thread, message); }, }, resolveResourceId: ({ requestContext }) => `tenant:${requestContext.get('tenantId')}`, } ``` `requestContext` is a required field on `ResolveResourceIdContext` and `ResolveThreadIdContext`. Code that builds these objects by hand, such as tests that call a resolver directly, now needs to pass one: ```ts // Before await resolveResourceId({ platform, thread, message, defaultResourceId }); // After await resolveResourceId({ platform, thread, message, defaultResourceId, requestContext: new RequestContext() }); ``` - Added thread rows with activity timestamps, trace counts, previews, root-agent model details, filtering and sorting. ([#27652](https://github.com/mastra-ai/mastra/pull/27652)) **Breaking contract change:** Thread-row requests use `timeRange.traces` or `timeRange.threads` instead of `traces.timeRange`. Related-trace predicates are not supported in the new contract. Legacy identity requests remain supported for mixed package versions. ```ts // Before const before = { traces: { timeRange: { from, to } } }; // After const after = { timeRange: { traces: { from, to } } }; ``` - Added `timeToFirstChunkMs` to trace and span queries. `queryTraces()` returns the root span's time to first chunk and `querySpans()` returns each span's, both `null` when no chunk was streamed. Both accept `timeToFirstChunkMs` filters with the same operators as `durationMs`. Stores that support it declare the `trace-query-time-to-first-chunk` feature. ([#27277](https://github.com/mastra-ai/mastra/pull/27277)) - Added the root span's `name` and `spanType` as trace-scope fields for trace queries and trace aggregates. ([#27321](https://github.com/mastra-ai/mastra/pull/27321)) - `parseTraceQueryRequest()` and `parseTraceAggregateRequest()` accept `name` and `spanType` in trace-scope `where` predicates. - `planTraceAggregate()` accepts them in `groupBy` and as `countDistinct.name` / `countDistinct.spanType`. - Trace-scope field discovery lists both with value suggestions, and the `aggregate.dimensions` list includes them. Inside `spans.some` and `spans.none`, `name` and `spanType` still match any span in the trace. ```ts const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-08-08T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'workflow_run' } }, groupBy: ['name'], measures: ['count', 'errorRate'], }), ); ``` Stores that support these fields declare the new `trace-query-root-span-fields` storage feature. ### Patch Changes - The error for a model with an unrecognized `specificationVersion` now lists `v4` (AI SDK v7) among the supported versions. ([#27516](https://github.com/mastra-ai/mastra/pull/27516)) - Fixed two cases where an AgentController tool approval could get out of sync with its stored run: ([#27371](https://github.com/mastra-ai/mastra/pull/27371)) - **A failed resume no longer loses the answer.** If approving or declining a tool call fails while the run is still waiting on it, the approval prompt is raised again (`tool_approval_required`) so the answer can be retried. Previously a retry returned `{ accepted: false, reason: 'not_pending' }` and the run stayed suspended. - **Detaching no longer declines a pending approval.** `session.thread.detachFromCurrent()` now leaves a parked tool approval pending in storage, as documented, instead of declining it. It can be answered later, for example with `respondToPersistedToolApproval`. - Fixed agent turns stopping early when an awaited background tool returns no result. The agent now sends `null` to the model and continues. ([#27482](https://github.com/mastra-ai/mastra/pull/27482)) - Fixed `workspace_grep` hanging for minutes on large workspaces. A directory search that runs on the host now has a 60-second budget. The budget covers filesystems without native grep, including the default `LocalFilesystem`, as well as the fallback used when a filesystem's native grep fails. Once the budget runs out or the agent run is aborted, the search stops starting new reads (reads already in progress finish) and returns the matches found so far. The summary line marks these results as incomplete and says how many files were scanned. The fallback log message now says why native grep failed (for example, a timeout) instead of always reporting an unsupported pattern. ([#27464](https://github.com/mastra-ai/mastra/pull/27464)) - Reduced durable agent snapshot size: tool results are no longer stored twice per step, so long-running durable agents with large tool outputs write much smaller snapshots. `stopWhen` conditions still receive the full step content. ([#27634](https://github.com/mastra-ai/mastra/pull/27634)) - Added `bufferSize` option to `SystemPromptScrubber` and `ModerationProcessor`. Set `bufferSize: 200` to check per sentence instead of per chunk (fewer LLM calls, catches content split across chunks). Default unchanged; will become the default in a future major. ([#27510](https://github.com/mastra-ai/mastra/pull/27510)) ```ts new SystemPromptScrubber({ model, bufferSize: 200 }); new ModerationProcessor({ model, bufferSize: 200 }); ``` - Added `byUser` to the `approved` channel tool display event, matching `denied`. A custom `toolDisplay` function can now show who approved a tool call (for example "Approved by Alice"), not only who denied it. The default approved card also names the approving user. `byUser` is undefined in DMs. ([#27584](https://github.com/mastra-ai/mastra/pull/27584)) ```ts toolDisplay: event => { if (event.kind === 'approved') { return { kind: 'post', message: `Approved by ${event.byUser ?? 'you'}` }; } }; ``` - Classifiers now accept `refusal` answers from decision models instead of throwing. Previously, when a decision model refused one question, `decide()` failed with `Answer '' does not match its question type.` and any workflow `.classifier()` step failed the run. ([#27488](https://github.com/mastra-ai/mastra/pull/27488)) A refused question now returns `{ type: 'refusal' }` while the other questions keep their answers. `RefusalAnswer` is exported and included in every `ClassifierAnswer` type. Refusals have no `choice`, `score`, or `probability`, so equality checks such as `answers.route.choice === 'billing'` evaluate to `false`; check `type === 'refusal'` before relying on numeric fields. ```ts const { answers } = await classifier.decide({ state }); if (answers.unsafe.type === 'refusal') { // Route to review, block, or apply a default. } ``` Classifier scorers fail with an error naming the refused question, and `ModelSelectionProcessor` keeps the configured model and reports `abstained: 'refused'`. - Fixed durable agents not forwarding a declined sub-agent tool approval to the sub-agent. When a supervisor declines a tool approval raised by a sub-agent, the decline and its reason now reach the suspended sub-agent run, matching the approve path and the regular `Agent`. Declining the supervisor's own tool approval still denies the call locally. ([#27374](https://github.com/mastra-ai/mastra/pull/27374)) - Fixed durable agent resumes that lose a resume race or arrive after the run finished ([#27365](https://github.com/mastra-ai/mastra/issues/27365)). ([#27379](https://github.com/mastra-ai/mastra/pull/27379)) - A second approval of the same tool call fails right away with `AGENT_RESUME_ALREADY_CLAIMED` while the first resume runs in the same process, including when both approvals arrive at the same moment. Before, it waited for the whole resumed turn or about 5 s and then failed with `DURABLE_AGENT_EXECUTION_CONFLICT`. - A resume that loses the race reports the error only on its own stream. Clients following the run no longer get an error event or a span ended with an error while the winning resume completes. - Resuming a run whose snapshot was already deleted (for example because another instance finished it) now fails with `AGENT_RESUME_NO_SNAPSHOT_FOUND` and no longer writes a new `pending` workflow row. - A resume that loses the race no longer blocks the winning run's memory writes. - The durable execution lease now follows `MASTRA_AGENT_THREAD_LEASE_TTL_MS` (renewed every third of it). The default TTL drops from 30 s to 15 s, so a run left by a crashed process becomes recoverable sooner. - Fixed `stopWhen` on durable agents (`DurableAgent`, evented and Inngest) receiving steps without `content`. Predicates such as `steps.at(-1)?.content.some(...)` used to throw and end the run with a stream error. They now see the same step content that `stream.steps` reports. ([#27524](https://github.com/mastra-ai/mastra/pull/27524)) - Fixed `resumeGenerate()` and `getFullOutput()` on durable agents sometimes never completing when a resumed run suspends again. ([#27384](https://github.com/mastra-ai/mastra/pull/27384)) - Fixed durable and evented agents reporting no token usage for a step that suspends on a tool call. `generate()` results and `tool-call-approval` / `tool-call-suspended` chunks now include that step's usage, matching the regular agent. ([#27493](https://github.com/mastra-ai/mastra/pull/27493)) - Fixed `run.stream()` on the evented workflow engine reporting `workflow-finish` with `workflowStatus: 'success'` when the run failed. The evented engine now emits the failed step's `workflow-step-result` before finishing, matching the default engine, so stream consumers and UIs see `failed`. ([#27559](https://github.com/mastra-ai/mastra/pull/27559)) - Fixed evented `.foreach()` runs where more than one iteration fails. The run now keeps the first iteration's error instead of the last one, and fails exactly once. Previously, an iteration failing after the run had already failed replaced the stored error and published a second `workflow.fail`. Time-travel replays that hit a failure again also failed twice. ([#26651](https://github.com/mastra-ai/mastra/pull/26651)) - Fixed `fetchWithRetry` leaving the bodies of failed HTTP responses open when it retried or gave up, which could keep connections busy until garbage collection. Failed responses are now cancelled before retrying or throwing; successful responses are still returned unread. ([#27439](https://github.com/mastra-ai/mastra/pull/27439)) - `Agent.reorderModels()` and `Agent.updateModelInModelList()` now return `true` when the edit was applied and `false` when it was not (no model list, unknown id, or a function-valued `model`). Function-valued models are left untouched so per-request model selection keeps working. ([#27192](https://github.com/mastra-ai/mastra/pull/27192)) ```ts if (!agent.updateModelInModelList({ id, enabled: false })) { // edit was not applied } ``` - Fixed output stream processors dropping text they were still holding when a model stream ended. A reply shorter than one `BatchPartsProcessor` batch (for example with `emitOnNonText: false`) used to stream and save as empty; held parts are now released at the end of each model stream and passed through the processors that come after the one that held them. ([#27565](https://github.com/mastra-ai/mastra/pull/27565)) - Fixed token usage being counted twice after a tool call suspends and the run resumes. Spans and metrics for the resumed run now report only the tokens used after resuming, including when the resumed run suspends again. ([#27634](https://github.com/mastra-ai/mastra/pull/27634)) - Fixed automatic durable agent recovery stopping after temporary storage or pubsub errors. Skipped runs are retried when the backend becomes available, without another restart or manual recovery. ([#27354](https://github.com/mastra-ai/mastra/pull/27354)) - Fixed slow provider listing in `mastra dev`. Opening the model picker in Studio (`GET /api/agents/providers`) no longer re-reads the shared provider cache from disk for every provider, so the request drops from seconds to well under a second. ([#26711](https://github.com/mastra-ai/mastra/pull/26711)) - Failed turns on the default Agent now save the same history as durable agents. ([#27571](https://github.com/mastra-ai/mastra/pull/27571)) - A reply that fails mid-stream keeps the text streamed so far, followed by the error, instead of only the error. - When an input processor throws, the user message and the error are saved to memory, and the stream ends with an `error` chunk instead of the run failing with nothing saved. - Fixed tool calls disappearing from conversation history when a message or reminder arrived while the tool was still running. The call and its result now stay in history, so the model sees its own completed work on the next turn. ([#26963](https://github.com/mastra-ai/mastra/pull/26963)) - Fixed a thread that stopped starting runs after a run waiting on a tool prompt was followed by another run on the same thread. Once that other run finished, every new message, signal or notification was reported delivered but never ran until the process restarted. The waiting run now gives the thread up when another run takes it over, and it can still be resumed with the user's answer. A run that is still waiting on its prompt keeps holding the thread as before. ([#27545](https://github.com/mastra-ai/mastra/pull/27545)) - Fixed notification dispatch stalling behind notifications it leaves pending. When a delivery policy holds notifications for another process's dispatcher (or they wait on an active run), `dispatchDueNotifications` now reads past them instead of re-reading the same first page every pass, so notifications behind them are still delivered. `limit` now counts only the notifications a pass delivers or fails. ([#27426](https://github.com/mastra-ai/mastra/pull/27426)) - Fixed notifications that ran out of delivery attempts disappearing from the inbox. The `notification-inbox` tool's default `list` now includes `failed` notifications, reporting them as `failed` with their last delivery error, and marks them seen so they show up once rather than on every list. ([#27453](https://github.com/mastra-ai/mastra/pull/27453)) - Fixed agent runs stopping when background observational memory buffering fails. The failure is still reported as an `om_buffering_failed` event, and the turn keeps going. A failed synchronous observation still follows the configured failure policy. ([#27438](https://github.com/mastra-ai/mastra/pull/27438)) - Fixed a prompt waiting on the user disappearing from the session's display when another run on the same thread finished. The prompt could still be answered, but nothing showed it. Only the ending run's own prompts are now cleared. ([#27555](https://github.com/mastra-ai/mastra/pull/27555)) - Fixed `get_process_output` with `wait: true` hanging after a background command had exited. When the command left a child running that still held its output open (`cmd &`, or a child of `timeout`), the wait never returned. It now returns once the command exits and its output has had a moment to drain, and says when a process the command started is still running. That process can still be stopped with `kill_process`, and is still stopped with the sandbox. A foreground command that starts a process in the background (`cmd &`) now returns once its shell exits rather than at its timeout, and the process it left behind is stopped when it returns. Use `background: true` for processes that should keep running. ([#27578](https://github.com/mastra-ai/mastra/pull/27578)) - Fixed `Agent.stream()` ending with a `finish` chunk (reason `stop`) when an output processor aborts in `processOutputResult`. The stream now ends with a `tripwire` chunk and `status` is `'tripwire'`, matching durable agents and other processor tripwires. ([#27377](https://github.com/mastra-ai/mastra/pull/27377)) - Fixed approved tools added by an input processor running with the suspended run's request context. When you approve a tool call with `approveToolCall({ requestContext })`, a tool that an input processor added now sees the request context you passed, just like a tool defined on the agent. ([#27570](https://github.com/mastra-ai/mastra/pull/27570)) - Fixed durable agent runs saving every tool output a second time in their final snapshot, which made the last snapshot write noticeably larger than the earlier ones. ([#27672](https://github.com/mastra-ai/mastra/pull/27672)) - Fixed `respondToToolSuspension()` hanging when answering a tool call (such as `ask_user`) that was parked during a run you aborted. The resumed run now streams to the Session and completes normally, instead of being silently dropped. ([#26899](https://github.com/mastra-ai/mastra/pull/26899)) - Fixed system messages added by input processors (such as the `TaskSignalProvider` task-list instruction) going missing when an `Agent` resumes after tool approval. Fixes #24844. ([#27372](https://github.com/mastra-ai/mastra/pull/27372)) Resumed runs now reuse the system prompt from the suspended run, matching durable agents. Instructions or system messages passed only at resume time no longer replace it. - Fixed lost messages when a per-step save fails. With `savePerStep` enabled, a failed write used to drop the drained messages from the unsaved set, so no later save wrote them and the user's message or the step's output disappeared from the thread. Failed saves now mark those messages unsaved again, and the next save writes them. ([#27556](https://github.com/mastra-ai/mastra/pull/27556)) - Fixed `System message must be at the beginning` errors from models served through Chat Completions APIs (e.g. Qwen3.5/3.6 or Gemma 3 on vLLM). Multiple leading system messages are now merged into one for Chat Completions providers. Prompts are left unchanged when an earlier system message carries its own provider options (such as a cache breakpoint). Providers with a dedicated system field (Anthropic, Google, Bedrock) and the OpenAI Responses API are unchanged. ([#27307](https://github.com/mastra-ai/mastra/pull/27307)) - Fixed skill tools (`skill`, `skill_read`, `skill_search`) pausing for approval when `requireToolApproval` is enabled globally. Previously every skill load in an approval-enabled agent (including durable agents) suspended for an approval nobody needed. ([#27296](https://github.com/mastra-ai/mastra/pull/27296)) - Fixed stored skills ignoring the `metadata` filter. `skills.list({ metadata })` and `skills.listResolved({ metadata })` now return only skills whose resolved version metadata matches every key/value pair, with correct `total` and pagination. ([#27462](https://github.com/mastra-ai/mastra/pull/27462)) ```ts const skills = await storage.getStore('skills'); await skills.listResolved({ metadata: { publishedBy: 'user-123' } }); ``` - Fixed Slack channel messages so agents receive the text of Block Kit layout blocks (header, section, context, markdown), not just the notification fallback. Block-only messages now reach the agent. Thread history includes the block text too. ([#27393](https://github.com/mastra-ai/mastra/pull/27393)) - Fixed evented workflows losing recoverable snapshots when terminal notifications fail. ([#27355](https://github.com/mastra-ai/mastra/pull/27355)) - Fixed durable agent startup recovery getting stuck behind a stalled run. Each run's recovery wait is limited to 30 seconds without cancelling its execution, and recovery stops starting new work during shutdown. ([#27357](https://github.com/mastra-ai/mastra/pull/27357)) - Agent controller session routes no longer start a session as a side effect. Only `POST /agent-controller/:controllerId/sessions` starts one. Before, a message, model switch or state write that reached the server first created the session on a new random thread. The write then landed on a thread the client never read, so it looked lost. ([#27474](https://github.com/mastra-ai/mastra/pull/27474)) Every other session route now acts on a started session. After a server restart it rebuilds the session from its stored thread. If the session was never started, the route returns `404` and creates nothing. ```ts const session = client.getAgentController('code').session(resourceId); await session.create({ threadId }); // start first await session.sendMessage('Hello'); // 404 if the session was never started ``` `AgentController.createSession` accepts `resumeOnly: true` to get this behavior. It throws `SessionNotStartedError` when no stored thread matches. Factory now starts the session on its own thread when the session is created, so the first prompt can be sent right away. - Fixed `stopWhen` receiving the wrong step content when `savePerStep` is enabled. After a run was resumed (for example after a tool suspended to ask the user a question), a step could show up with no tool results, so conditions such as "stop after N failed tool calls" counted wrong and fired late. ([#27521](https://github.com/mastra-ai/mastra/pull/27521)) - Calling `abort()` with no reason from an output processor's `processOutputStream` now reports `Tripwire triggered by ` on durable and evented agents, the same as `Agent.stream()` and every other processor hook. Before this fix those engines reported `Stream part blocked by `. ([#27567](https://github.com/mastra-ai/mastra/pull/27567)) - Fixed missing token usage when a tool call suspends or waits for approval. Suspended `stream()` and `generate()` results now include the step's tokens. After resuming, `totalUsage` stays cumulative and token metrics count those tokens once. See #27232. ([#27425](https://github.com/mastra-ai/mastra/pull/27425)) - Fixed messages being lost when sent to a thread shortly after the same instance cleaned up the thread's abandoned suspended run. Such a send used to return `deliver` while nothing received the message; it now returns `blocked`. Retry the send, and once the old run's lease expires (15 seconds by default) it starts a new run. ([#27389](https://github.com/mastra-ai/mastra/pull/27389)) - Improved cancellation of durable and evented agent tool and model calls after execution ownership is lost. Cooperative tools receive an abort signal so superseded work can stop sooner without canceling the recovering run. Recovery may run a tool again, so tools must be safe to repeat. Cancellation cannot undo work already completed. ([#27490](https://github.com/mastra-ai/mastra/pull/27490)) Pass the tool's abort signal to operations that support cancellation: ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const fetchText = createTool({ id: 'fetch-text', description: 'Fetch text from a URL.', inputSchema: z.object({ url: z.string().url() }), outputSchema: z.string(), execute: async ({ url }, context) => { // Without the signal, the request keeps running after ownership is lost. const response = await fetch(url, { signal: context?.abortSignal }); return response.text(); }, }); ``` - Fixed `timeTravel()` running the same steps twice when called concurrently on one run. `timeTravel()` now atomically claims the run before executing, like `resume()` does. A losing call throws `WORKFLOW_TIME_TRAVEL_ALREADY_CLAIMED` (or `WORKFLOW_TIME_TRAVEL_RUN_RUNNING` if the run is already executing) and `POST /api/workflows/:workflowId/time-travel-async` returns `409 Conflict`. The claim isn't enforced with storage adapters that don't support concurrent updates, when `shouldPersistSnapshot` excludes the `running` status, or in the Inngest runner; concurrent calls there can still run steps twice. Fixes #27279. ([#27313](https://github.com/mastra-ai/mastra/pull/27313)) - Fixed agents stopping after a server tool whose `execute` returns `undefined`. The tool now records a `null` result, and the agent includes the tool call and result in the next turn. ([#27392](https://github.com/mastra-ai/mastra/pull/27392)) - Fixed trace scoring so `type: 'agent'` scorers receive the tool calls an agent made. Scoring a stored agent trace (for example with `scoreTraceBatch` or `POST /api/observability/traces/score`) missed tool calls recorded under a model step. Tool calls and MCP tool calls are now included in the order they started. Calls made inside sub-agents or inside a tool are not credited to the parent agent. ([#27390](https://github.com/mastra-ai/mastra/pull/27390)) - Fixed durable and evented agents losing a suspended run when an `untilIdle` stream is cancelled. Breaking out of `streamUntilIdle()` (or the request disconnecting) while the run waits for a tool approval or a `suspend()` resume now only stops reading, so `resumeStreamUntilIdle()` and `approveToolCall()` work afterwards instead of failing with "No registry entry found for run … Cannot resume". Cancelling while a tool is still running still aborts the run, and `result.abort()` still releases a suspended run. ([#27563](https://github.com/mastra-ai/mastra/pull/27563)) - Tools that require approval now validate their input before asking for approval. Previously an agent asked a person to approve a call whose arguments did not match the tool's `inputSchema`, and the call then failed validation after it was approved. Now the validation error goes straight back to the model so it can retry, and no `tool-call-approval` is emitted. This applies to `Agent`, durable agents, and agent networks. Fixes #26771. ([#26973](https://github.com/mastra-ai/mastra/pull/26973)) - Security: bump the internal `@ai-sdk/provider-utils-v6` alias from 4.0.40 to 4.0.56 to pick up the fix for AIKIDO-2026-526145. No public API changes. ([#27023](https://github.com/mastra-ai/mastra/pull/27023)) - Fixed a message sent from an `AgentController` session being dropped when another agent was mid-run on the session's thread. The message now waits for that run to end and then runs. Pressing Stop while it waits drops it. If that run is waiting for an answer to its own prompt, the message isn't sent and the session shows an error saying so. A message sent while the thread is still held by a waiting run that was dropped from memory now waits for that hold to expire, then runs. Signals sent straight to the agent still report the thread as blocked. ([#27624](https://github.com/mastra-ai/mastra/pull/27624)) - Boot-time workflow recovery no longer re-runs a workflow run that another live server instance is still executing. Previously, when one replica started while another was mid-run, `restartAllActiveWorkflowRuns()` restarted every `running` run and its steps executed twice. ([#27508](https://github.com/mastra-ai/mastra/pull/27508)) Workflow runs now hold the existing storage run-ownership lease (the one durable agents use) while they execute. `run.restart()` throws a `WORKFLOW_RESTART_RUN_OWNED` error instead of taking over a live run. The boot sweep skips those runs and checks them again once the lease could have expired, so runs left behind by a crashed process are still recovered. Storage adapters without run-ownership support keep the previous behavior. ## 1.79.0-alpha.3 ### Minor Changes - Added iteration-aware final output processing so processors receive the current feedback-continuation iteration as its own assistant message and can transform each iteration independently. ([#26346](https://github.com/mastra-ai/mastra/pull/26346)) Fixed feedback continuations with output processors so rewrites and redactions are preserved in `getFullOutput().text` and `steps[].text` instead of returning raw earlier-iteration text. Custom output processors that persist transcripts should use `messageList.cloneForPersistence()` to retain one assistant response with its original identity and order. ```ts // Before: may persist processor-visible response messages separately. const processor = { id: 'transcript-persister', processOutputResult: async ({ messages, messageList }) => { await saveMessages(messageList.get.response.db()); return messages; }, }; // After: preserves one assistant response with its original identity and order. const processor = { id: 'transcript-persister', processOutputResult: async ({ messages, messageList }) => { await saveMessages(messageList.cloneForPersistence().get.response.db()); return messages; }, }; ``` ### Patch Changes - Fixed MCP tool results containing images or audio being dropped by AI SDK v5 models and rejected by AI SDK v7 models such as Amazon Bedrock (`unsupported tool content part type: image-data`). MCP media content is now passed to every model in the format it supports. ([#26662](https://github.com/mastra-ai/mastra/pull/26662)) - Fixed notifications that failed on their first delivery attempt. Delivery now retries automatically, up to five attempts, instead of staying pending forever. Retries preserve the claimed-owner requirement. ([#25837](https://github.com/mastra-ai/mastra/pull/25837)) - `respondToPersistedToolApproval` now accepts `alwaysAllowCategory`. Approving a tool call restored after a restart with "always allow" grants the tool's category to the run's thread, matching a live approval. ([#27222](https://github.com/mastra-ai/mastra/pull/27222)) ```ts await session.respondToPersistedToolApproval({ toolCallId, approved: true, alwaysAllowCategory: true }); ``` - Fixed agents crashing with `Class constructor ... cannot be invoked without 'new'` when a tool's schema is a class-based Standard Schema, such as an Effect `Schema.Class` adapted with `Schema.toStandardSchemaV1`. Tools can now use class-based schemas the same way as `Schema.Struct`. ([#27317](https://github.com/mastra-ai/mastra/pull/27317)) - Fixed goal judges and subagents hanging inside an Agent Controller session. Previously they inherited the session's memory thread and waited on it forever. Goal judges now use their own goal-specific thread. Forked subagents use the cloned thread. Non-forked subagents no longer inherit the session's thread and resource IDs. ([#27323](https://github.com/mastra-ai/mastra/pull/27323)) ## 1.79.0-alpha.2 ### Minor Changes - `session.thread.transferOwnership` now accepts an optional `threadId`, so you can transfer a thread without switching the session to it first. Without `threadId` it still transfers the active thread. ([#27043](https://github.com/mastra-ai/mastra/pull/27043)) ```ts await session.thread.transferOwnership({ toOwnerId: 'user-456', threadId: 'thread-123' }); ``` - Span records now carry `timeToFirstChunkMs`, the milliseconds from span start to the first streamed chunk. The storage exporters fill it in, so storage adapters save the value without computing it. ([#27140](https://github.com/mastra-ai/mastra/pull/27140)) ### Patch Changes - Fixed `Classifier.decide()` crashing with `state.map is not a function` on decision providers built for `@ai-sdk/provider` 4.0.25 and newer, such as `@ai-sdk/typesafe-ai` 3.0.16+. String and JSON state are now converted to `text` and `json` parts before the provider is called, matching the AI SDK. Arrays are passed through only when every element is already a state part; other JSON arrays are wrapped in a `json` part. ([#27298](https://github.com/mastra-ai/mastra/pull/27298)) - You can now resume durable agents (`createDurableAgent`, `createInngestAgent`) with the same `resumeGenerate(resumeData, { runId })` call you use with `Agent`. ([#27299](https://github.com/mastra-ai/mastra/pull/27299)) ```ts // Before (durable-only order, still supported but deprecated) await durableAgent.resumeGenerate(runId, { approved: true }); // After (same as Agent) await durableAgent.resumeGenerate({ approved: true }, { runId }); ``` One deprecated call shape now throws instead of guessing: a string run ID followed by resume data that itself contains a string `runId`, with no third argument, for example `resumeGenerate('run-A', { runId: 'x', approved: true })`. To keep using the deprecated order, pass an explicit options argument: `resumeGenerate('run-A', { runId: 'x', approved: true }, {})`. - Fixed `LocalSandbox` commands that could hang past their timeout. When a command times out, it is asked to stop and then force-killed two seconds later if it is still running, including child processes that keep its output open. Previously a command that ignored the stop request kept the tool call waiting forever. ([#26879](https://github.com/mastra-ai/mastra/pull/26879)) - Fixed trajectory scorers in `runEvals` and dataset experiments getting a flat list of tool calls instead of the full trace when storage is configured. `runEvals` also no longer waits up to 6 seconds per item before scoring. ([#27271](https://github.com/mastra-ai/mastra/pull/27271)) - Fixed the 30-second default timeout for `LocalSandbox` foreground commands. A command run without a timeout could previously wait forever. Configured defaults and per-command timeouts are honored, including `0` to disable the timeout, and cloned sandboxes keep the configured default. Background processes keep their existing lifetime. ([#26878](https://github.com/mastra-ai/mastra/pull/26878)) - Fixed authenticated agent controller sessions failing with `AGENT_MEMORY_THREAD_RESOURCE_MISMATCH` when auth uses `mapUserToResourceId`. The controller now sets the reserved memory resource and thread keys to the session's own resource and thread. Other auth values in the request context are kept. ([#27304](https://github.com/mastra-ai/mastra/pull/27304)) - Removed the `gray-matter` dependency. SKILL.md frontmatter is now parsed with the `yaml` package directly, which drops the vulnerable `sprintf-js` package (GHSA-hp3w-g68c-fv3c, no patched release available) from the production dependency tree. YAML frontmatter that worked before keeps working; JavaScript frontmatter (`---js`) is still rejected. ([#27198](https://github.com/mastra-ai/mastra/pull/27198)) ## 1.79.0-alpha.1 ### Minor Changes - Fixed `DurableAgent.observe(runId)` when attaching to a run from another process. ([#26906](https://github.com/mastra-ai/mastra/pull/26906)) - Restored the agent's configured output processors, request context, and `returnScorerData` setting, including when the observing process also runs durable steps. - Preserved live call-time processors, request context, and scorer data when another wrapper observes the run within the same Mastra instance. - **Behavior change:** Cross-process `DurableAgent.observe(runId)` now throws `DURABLE_AGENT_OBSERVE_AGENT_MISMATCH` when the persisted run belongs to an agent with a different ID, matching `resume()` and `recover()`. Use the same agent ID in the observing process as in the process that started the run. - Made durable stream `onChunk` callbacks run serially and in order. - Made `onFinish` wait for pending `onChunk` callbacks to finish, without waiting for transport acknowledgements. **Before:** An observing process could use a different agent ID to attach to a run started by `researcher`: ```ts const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher-observer' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId); ``` **After:** Use the starting agent's ID in the observing process. The previous configuration now throws `DURABLE_AGENT_OBSERVE_AGENT_MISMATCH`: ```ts const observer = createDurableAgent({ agent: new Agent({ ...agentConfig, id: 'researcher' }), pubsub, }); // After registering observer with Mastra and the shared storage: await observer.observe(runId); ``` - Added batch experiment ID filtering to observability metric queries, so you can get token usage and cost for many experiments in one request. ([#27255](https://github.com/mastra-ai/mastra/pull/27255)) ```ts const result = await client.getMetricBreakdown({ name: ['mastra_model_total_input_tokens', 'mastra_model_total_output_tokens'], aggregation: 'sum', groupBy: ['experimentId', 'name'], filters: { experimentIds: ['experiment-1', 'experiment-2'] }, }); ``` - Added a `datasetIds` filter to dataset listing, so you can fetch specific datasets by ID in one request. All matches come back on one page, up to 100 IDs. ([#27255](https://github.com/mastra-ai/mastra/pull/27255)) ```ts const { datasets } = await client.listDatasets({ datasetIds: ['dataset-1', 'dataset-2'] }); ``` - Added `querySpans()` filters and response fields for span context and parent/root entity information. For example: ([#27272](https://github.com/mastra-ai/mastra/pull/27272)) ```ts where: { op: 'eq', left: { path: 'threadId' }, right: { literal: 'thread-123' } } ``` **Breaking:** `querySpans()` no longer accepts `model` and `provider` filters or returns `cost`, `inputTruncated`, `outputTruncated` and `errorTruncated`. Model/provider values, name matching, duration filtering and previews remain available. The new filters require an updated storage adapter. - Added `completionStartTime` to agent run and model step span attributes, a `getTimeToFirstChunkMs()` helper that derives time to first chunk for any span, and carried the first-chunk clock through durable and evented agent runs so their generation and agent run spans report it too. ([#26998](https://github.com/mastra-ai/mastra/pull/26998)) ### Patch Changes - Classifier evaluation spans now show what the classifier was asked and what it decided. The span input holds the `state` and the questions, and the span output holds the answers, so routing decisions are visible in traces. Set `hideInput` and `hideOutput` in `tracingOptions` to keep them out of exported traces. ([#27243](https://github.com/mastra-ai/mastra/pull/27243)) - Server model usage telemetry now counts tokens as model calls finish and reports them every 15 minutes and on shutdown as a new `mastra_model_token_usage_v2` event. Each `mastra_model_token_usage_v2` event contains only tokens the reporting process used, so redeploys, replicas, and lost home directories can't make it resend earlier usage. The startup `mastra_model_token_usage` event is still sent, unchanged, for compatibility, and can still resend earlier usage in those cases. Set `MASTRA_TELEMETRY_DISABLED=1` to turn off telemetry. ([#26986](https://github.com/mastra-ai/mastra/pull/26986)) - Fixed durable, evented, and Inngest agent streams to reject when `stopWhen` throws, matching plain agents. ([#26921](https://github.com/mastra-ai/mastra/pull/26921)) Previously, these stream consumers could handle this failure as an `error` chunk. Catch the `fullStream` rejection instead: ```ts try { for await (const chunk of output.fullStream) { // Handle streamed chunks. } } catch (error) { // Handle the error thrown by stopWhen. } ``` Durable transports reconstruct the error from its serialized name and message. The original stack, cause, and subclass identity do not survive a cross-process boundary. Core durable and evented workflows classify this failure with the reserved internal `DURABLE_AGENT_STOP_WHEN_FAILED` identifier. Inngest's serve transport uses the memoized step's `stop-when-` id prefix because its `StepError` serialization does not retain that identifier or its details. Any failure inside that Inngest step is therefore classified as a `stopWhen` failure. - Fixed resumed agent spans to remain children of the initial agent run across repeated suspensions, matching durable agent tracing. ([#27149](https://github.com/mastra-ai/mastra/pull/27149)) - Fixed channel conversations (Slack, Discord, Telegram and others) never getting an automatic title. New channel threads now start without a title, like any other thread, so `generateTitle` names them from the conversation. ([#27251](https://github.com/mastra-ai/mastra/pull/27251)) Before, every new channel thread was saved with a generic label such as `slack conversation`, which counted as an existing title and blocked title generation. With `generateTitle` turned off, new channel threads now appear untitled in Studio, which lists them by creation date, instead of showing that label. Existing threads keep their current title. ## 1.78.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`9b60f12`](https://github.com/mastra-ai/mastra/commit/9b60f12b13b28c9664a839aef435e6d10898644d)) - Fixed `untilIdle` continuations losing a completed background task's result when newer messages push it outside the configured history window. ([#27205](https://github.com/mastra-ai/mastra/pull/27205)) - Fixed a Session that follows a thread keeping a tool call marked as pending after the run was resumed on another instance and completed. The follower now drops the answered call, so `suspensions.hasPending()` returns false and aborting no longer emits a spurious `tool_suspension_cancelled` event. ([#27226](https://github.com/mastra-ai/mastra/pull/27226)) - Bump for npm ([#27221](https://github.com/mastra-ai/mastra/pull/27221)) - Updated dependencies [[`fae54c5`](https://github.com/mastra-ai/mastra/commit/fae54c57917cd1f527937ca42b5ab7dfe1d55a2a)]: - @mastra/schema-compat@1.3.16-alpha.0 ## 1.78.0 ### Minor Changes - Added `scopes` to agents: the addresses a run acts as, written as `:` (for example `org:acme`, `resource:user-123`, `thread:conversation-123`). ([#26593](https://github.com/mastra-ai/mastra/pull/26593)) **Setting scopes.** Pass them per call on `stream`, `generate`, `network`, `sendMessage`, `queueMessage` and `sendSignal`, set them on the agent, or set them from trusted middleware with the new `MASTRA_SCOPES_KEY` request context key. Mastra combines all three. Read an agent's configured scopes with `agent.getScopes()`. **Memory.** Memory reads its resource and thread from `resource:` and `thread:` scopes, in addition to `memory.resource`, `memory.thread` and the existing request context keys. If they name different values, the call throws a 400 error instead of silently picking one. Runs with no scopes from any source behave as before. ```ts const agent = new Agent({ id: 'support', model: 'openai/gpt-5-mini', memory: new Memory(), scopes: ({ requestContext }) => { // Set by your auth middleware. Request bodies can fill unreserved keys, so only read trusted ones. const orgId = requestContext.get('org-id'); return orgId ? [`org:${orgId}`] : []; }, }); // Before await agent.generate('Hi', { memory: { resource: 'user-123', thread: 'conversation-123' } }); // After await agent.generate('Hi', { scopes: ['resource:user-123', 'thread:conversation-123'] }); ``` **Nested calls.** Tools, processors and subagents see the run's other scopes (for example `org:acme`) under `MASTRA_SCOPES_KEY`, but not its `resource:` and `thread:` scopes, so nested agent calls can choose their own thread. **Suspended runs.** Suspended runs keep their scopes when resumed: a resume may repeat some or all of them but cannot add new ones. **Upgrading.** Upgrade `@mastra/server` together with this release. While an older server is registered, agents refuse scopes from the call and the request context with an `AGENT_SCOPES_SERVER_OUTDATED` error (HTTP 400); the agent's own `scopes` option still applies. This detection relies on the server registering itself with Mastra, which `@mastra/server` has done since `1.0.0-beta.5`. Servers older than that — and custom server adapters that never call `mastra.setMastraServer()` — are indistinguishable from no server at all, so they are not detected: upgrade them rather than relying on the guard. Runs suspended before this release have no recorded scope set; resuming one adopts the scopes supplied at resume time (the same trust checks as a fresh run apply) instead of rejecting them. Runs suspended after the upgrade record their set — including the empty set — and a resume may not add to it. - Added `traceAggregateDiscoverySchema` and an optional `aggregate` field on the trace-query fields response, describing the dimensions and measures `aggregateTraces()` accepts. ([#27185](https://github.com/mastra-ai/mastra/pull/27185)) - Added `queueSignal()` to `Agent` and AgentController `Session`. It queues a typed signal behind the active run like `queueMessage()`, but keeps the signal type, tag, and attributes instead of turning it into a plain user message. ([#27081](https://github.com/mastra-ai/mastra/pull/27081)) ```ts agent.queueSignal( { type: 'reactive', tagName: 'skill', attributes: { name: 'triage' }, contents: 'Triage the new issue.' }, { resourceId: 'user-123', threadId: 'thread-abc' }, ); ``` Runs started by a signal now record it on the `AGENT_RUN` span as `attributes.signal` (`id`, `type`, `tagName`, `attributes`), so traces show which signal started a run. - Memory can now contribute error processors to an agent. `MastraMemory.getErrorProcessors()` works like `getInputProcessors()` and `getOutputProcessors()`: the agent appends what it returns to its resolved `errorProcessors`, on both regular and durable agents. These processors run after configured and default error processors, and stay in place when a call passes its own `errorProcessors`. The base implementation returns none, so existing memory classes are unaffected. ([#26029](https://github.com/mastra-ai/mastra/pull/26029)) ```ts class MyMemory extends MastraMemory { async getErrorProcessors() { return [new MyRecoveryProcessor()]; } } ``` - Added one preview format for span input, output and error. Trace lists (`queryTraces()`, `listTracesLight()`) now return `outputPreview` and `errorPreview` next to `inputPreview`, and `querySpans()` rows add `errorPreview` and `errorTruncated`. ([#26567](https://github.com/mastra-ai/mastra/pull/26567)) **New exports from `@mastra/core/storage`** - `buildPreview(value)` builds a preview from a decoded payload, and `buildPreviewFromJson(json)` from stored JSON text. Both cut to `PREVIEW_MAX_LENGTH` (100) characters and end with `…` when cut. - Exported spans and span records accept optional `inputPreview`, `outputPreview` and `errorPreview`, preserved through create/update record builders. Stores trim nonblank supplied previews and cap them like generated ones (100 characters plus `…`, never splitting a surrogate pair); missing or blank previews are generated from the payload. ```ts import { buildPreview } from '@mastra/core/storage'; buildPreview({ messages: [{ role: 'user', content: 'Where is my order?' }] }); // 'Where is my order?' buildPreview({ message: 'Tool failed' }); // 'Tool failed' ``` **Behavior changes** - For a message list, previews now show the last user message instead of every user message joined with `|`. - `querySpans()` previews are now at most 100 characters of readable text plus an ellipsis instead of the first 256 characters of raw JSON. `buildInputPreview()` and `INPUT_PREVIEW_MAX_LENGTH` are deprecated. Use `buildPreview()` and `PREVIEW_MAX_LENGTH`. `buildInputPreview(input, maxLength)` still caps its preview at `maxLength`. - Added a `beforeApproval` hook to tools that require approval. It runs before the agent pauses for approval and can reject the call by returning `{ proceed: false, output }`. The agent then skips the approval pause and does not execute the tool, and `output` goes back to the model as the tool result. Invalid calls are returned to the model to fix, and people are only asked to approve valid ones. ([#26927](https://github.com/mastra-ai/mastra/pull/26927)) ```ts const issueRefund = createTool({ id: 'issue-refund', inputSchema: z.object({ orderId: z.string(), amount: z.number() }), requireApproval: true, beforeApproval: async ({ orderId, amount }) => { const order = await orders.find(orderId); if (!order) return { proceed: false, output: { error: `Order ${orderId} not found` } }; if (amount > order.paid) return { proceed: false, output: { error: `Amount exceeds ${order.paid}` } }; }, execute: async ({ orderId, amount }) => refunds.issue(orderId, amount), }); ``` The hook does not run again when the call resumes after an approval decision. If the hook throws, the error is logged and the call proceeds to the approval gate. Keep validating inside `execute` too, because state can change while the call is waiting for approval. - Removed the 31-day limit on trace search time ranges. You can now search all of your retained trace data, as long as the range starts before it ends. ([#27042](https://github.com/mastra-ai/mastra/pull/27042)) ```ts // Before: rejected because the range is longer than 31 days // After: returns traces from the whole six-month range await mastraClient.queryTraces({ timeRange: { from: '2026-04-01T00:00:00Z', to: '2026-10-01T00:00:00Z' }, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`bed1ccc`](https://github.com/mastra-ai/mastra/commit/bed1cccea5817d68faa42a15fda3a0b329f70680)) - Fixed `resumeGenerate()` and `resumeStream()` using up a suspended run when the resume data does not match the tool's `resumeSchema`. They now throw `AGENT_RESUME_INVALID_DATA` and the run stays suspended, so you can resume again with valid data. ([#27130](https://github.com/mastra-ai/mastra/pull/27130)) - Fixed trace trees for agents that use output stream processors. In multi-step runs, each step's output stream processor span now sits inside that step and also covers the step's tool results, instead of every span showing up as a flat list under the agent run. Structured output processor spans stay under the agent run, because structured output runs once on the final result. ([#27172](https://github.com/mastra-ai/mastra/pull/27172)) - Fixed live assistant messages missing the model that wrote them. Messages streamed by an `AgentController` session now carry `modelId` and `provider` in their metadata, so a client can show the model without reloading the thread. After a fallback, they name the fallback model. ([#27030](https://github.com/mastra-ai/mastra/pull/27030)) - Fixed model inference spans so they record the messages sent to the model. Observability backends that read the inference span, such as Langfuse through the OpenTelemetry exporter or Datadog, now show the input for every model call instead of an empty value. ([#26995](https://github.com/mastra-ai/mastra/pull/26995)) - Durable agent turns no longer create an unused tool stream writer on every LLM step. A writer is now created only when a step actually uses it. ([#26987](https://github.com/mastra-ai/mastra/pull/26987)) - Fixed `.foreach()` on the evented workflow engine when an iteration fails. The run now ends with `status: 'failed'` and the iteration's error. Before, it hung forever (`run.start()` never resolved) or, with `concurrency: 1`, finished as `success` with a corrupted output array. Iterations that finish after the failure no longer mark the foreach step successful. ([#26971](https://github.com/mastra-ai/mastra/pull/26971)) - Fixed workflow agent steps to support durable agents on both execution engines. ([#26919](https://github.com/mastra-ai/mastra/pull/26919)) - Fixed full agent output repeating earlier text after a feedback continuation. ([#26543](https://github.com/mastra-ai/mastra/pull/26543)) - Fixed a resumed workflow step losing its `suspendData` when the run restarts after a crash. The step now gets the payload it passed to `suspend()` again, alongside its `resumeData`. ([#27127](https://github.com/mastra-ai/mastra/pull/27127)) - Disabled workspace tools no longer add setup work on every agent turn. Agents with workspace tools turned off now start each turn faster and allocate less memory. Fixes #26401. ([#26657](https://github.com/mastra-ai/mastra/pull/26657)) - Fixed Amazon Bedrock India inference profiles (`in.` prefix, e.g. `in.anthropic.claude-sonnet-5`) sending `temperature` to models that reject it, which returned a 400. They now resolve model capabilities like the other geography prefixes. Fixes #26387. ([#26982](https://github.com/mastra-ai/mastra/pull/26982)) - Fixed step scorers in nested workflows so their scores are saved. Before, scores from steps inside a child workflow were silently dropped. ([#26984](https://github.com/mastra-ai/mastra/pull/26984)) - Fixed agent runs crashing with `RangeError: Invalid string length` when a command printed a very large amount of output. `sandbox.executeCommand()` now keeps the newest 1 MiB of stdout and stderr by default, the same limit spawned processes already use. Streaming callbacks still receive every chunk, and the result reports `stdoutTruncated` and `stdoutDroppedBytes` (and the stderr equivalents) when output was dropped. ([#27106](https://github.com/mastra-ai/mastra/pull/27106)) If you need the complete output, pass `maxRetainedBytes: Infinity`: ```ts // Before: all output was kept by default const result = await sandbox.executeCommand('cat', ['data.json']); // After: pass Infinity to keep all output const result = await sandbox.executeCommand('cat', ['data.json'], { maxRetainedBytes: Infinity }); ``` Complete output is limited to 256 MiB per stream. A command that prints more is killed, and `executeCommand()` (or `handle.wait()`) rejects with `ProcessOutputLimitError` instead of crashing the process. The `execute_command` and `get_process_output` tools now tell the model when the start of the output was dropped, so it doesn't mistake the newest output for all of it. - Fixed document citations disappearing after a reload. When a model streams a document source (for example an Anthropic PDF citation), memory now stores it as a `source-document` part with its media type, title and filename. Previously it was saved as a URL source with an empty `url`, so reloaded conversations showed an empty link instead of the cited document. ([#26805](https://github.com/mastra-ai/mastra/pull/26805)) ```ts const { messages } = await memory.recall({ threadId, resourceId }); // assistant parts now include: // { type: 'source-document', sourceId: 'doc-1', mediaType: 'application/pdf', title: 'Q3 report', filename: 'q3.pdf' } ``` AI SDK v5 UI messages now also keep `source-document` parts and the `sourceId` and `title` of URL sources when converting to and from stored messages. - Fixed durable recovery observers hanging when recovery fails before the stream starts. ([#26918](https://github.com/mastra-ai/mastra/pull/26918)) - Fixed `resumeGenerate()` and `resumeStream()` saving messages to the thread when `memory.options.readOnly` is `true`. Resuming a suspended run now leaves read-only threads untouched, matching `generate()` and `stream()`. ([#27056](https://github.com/mastra-ai/mastra/pull/27056)) - Fixed background task events leaking across resources. Previously, calling `BackgroundTaskManager.stream()` without a `resourceId` delivered every resource's task events, including tool arguments and results. Without a `resourceId`, `stream()` now only delivers tasks that have no resource. To subscribe to every resource, pass `allResources: true`: ([#26895](https://github.com/mastra-ai/mastra/pull/26895)) ```ts const events = mastra.backgroundTaskManager.stream({ agentId: 'my-agent', allResources: true }); ``` The `/background-tasks`, `/background-tasks/stream` and `/background-tasks/:backgroundTaskId` routes now use the resource from the request context. A resource-scoped caller only sees its own tasks: requesting a different `resourceId` returns 403, and fetching another resource's task returns 404. An empty resource id in the request context is rejected with 403. Requests without a resource in the request context keep access to all resources. - Fixed agent controller channel sessions losing the sender's identity on the first message in a new chat thread. A channel session now opens directly on its chat thread, keeping the inbound request context, instead of creating an extra empty thread and switching over without it. Hosts that resolve memory per user from the request context's `user` no longer fail on a new thread. ([#27113](https://github.com/mastra-ai/mastra/pull/27113)) - Fixed prompt caching after a suspended or tool-approval run resumes from Postgres storage. ([#26943](https://github.com/mastra-ai/mastra/pull/26943)) Tool-call arguments and JSON tool results in model prompts now use sorted object keys, so the same content produces the same request after a run resumes. Stored messages are unchanged. - Fixed new conversation failures when ToolSearchProcessor uses context storage with memory. ([#26902](https://github.com/mastra-ai/mastra/pull/26902)) - Fixed agents sending the same request until `maxSteps` when a text-only response ended with a non-standard finish reason, such as `other`. The run now ends after that response, so the text and token usage are no longer repeated. ([#26905](https://github.com/mastra-ai/mastra/pull/26905)) - Fixed two OpenAI errors with tool inputs and structured output: ([#26979](https://github.com/mastra-ai/mastra/pull/26979)) - Optional tool inputs that accept more than one type can now be left empty. Before, OpenAI models filled them with made-up values. - Structured output with a field that has free-form keys, such as `z.record(z.string(), z.string())`, now works on OpenAI instead of failing with a 400 error. These schemas are sent without strict mode. To keep strict mode anyway, set `providerOptions.openai.strictJsonSchema: true`. - Fixed `createStep(agent)` running a `durable: true` agent as a plain agent. When the agent is registered with Mastra, the workflow step now runs through the registered durable wrapper on both the default and evented engines, so durable streaming and cleanup apply as expected. ([#27078](https://github.com/mastra-ai/mastra/pull/27078)) A step now logs a warning when a `durable: true` agent still has to run non-durably: when no durable wrapper for it is registered on the workflow's Mastra instance, or when it uses a legacy v1 model. - Fixed token estimates for long numbers. A long number used to count as one token, so output full of numbers could grow far past memory and token limits without triggering them. Long numbers are now counted accurately, and limits trigger at the right time. ([#27208](https://github.com/mastra-ai/mastra/pull/27208)) Estimates saved on existing messages are recalculated, so existing conversations also get the corrected counts. - Fixed tool `transform` replacing stored tool-call arguments with a `Tool input-available payload unavailable` placeholder when only another phase (such as `transcript.output`) was configured. A tool-level transform now only changes the phases it defines, so stored `args` round-trip unchanged and replaying the thread with Anthropic preserved thinking no longer fails. Transformers that throw or return `undefined` still fall back to the placeholder, and raw input deltas stay suppressed. Fixes #26597. ([#26965](https://github.com/mastra-ai/mastra/pull/26965)) - Updated dependencies [[`c6230e3`](https://github.com/mastra-ai/mastra/commit/c6230e30f76b4b912bad68f426d730da1cb67d83), [`cb015a1`](https://github.com/mastra-ai/mastra/commit/cb015a1be6560cbaf4deaef219cb02e63b263bc3)]: - @mastra/schema-compat@1.3.15 ## 1.78.0-alpha.9 ### Minor Changes - Added `traceAggregateDiscoverySchema` and an optional `aggregate` field on the trace-query fields response, describing the dimensions and measures `aggregateTraces()` accepts. ([#27185](https://github.com/mastra-ai/mastra/pull/27185)) ### Patch Changes - Fixed trace trees for agents that use output stream processors. In multi-step runs, each step's output stream processor span now sits inside that step and also covers the step's tool results, instead of every span showing up as a flat list under the agent run. Structured output processor spans stay under the agent run, because structured output runs once on the final result. ([#27172](https://github.com/mastra-ai/mastra/pull/27172)) ## 1.78.0-alpha.8 ### Patch Changes - Fixed model inference spans so they record the messages sent to the model. Observability backends that read the inference span, such as Langfuse through the OpenTelemetry exporter or Datadog, now show the input for every model call instead of an empty value. ([#26995](https://github.com/mastra-ai/mastra/pull/26995)) - Fixed agents sending the same request until `maxSteps` when a text-only response ended with a non-standard finish reason, such as `other`. The run now ends after that response, so the text and token usage are no longer repeated. ([#26905](https://github.com/mastra-ai/mastra/pull/26905)) ## 1.78.0-alpha.7 ### Patch Changes - Fixed full agent output repeating earlier text after a feedback continuation. ([#26543](https://github.com/mastra-ai/mastra/pull/26543)) - Fixed `createStep(agent)` running a `durable: true` agent as a plain agent. When the agent is registered with Mastra, the workflow step now runs through the registered durable wrapper on both the default and evented engines, so durable streaming and cleanup apply as expected. ([#27078](https://github.com/mastra-ai/mastra/pull/27078)) A step now logs a warning when a `durable: true` agent still has to run non-durably: when no durable wrapper for it is registered on the workflow's Mastra instance, or when it uses a legacy v1 model. ## 1.78.0-alpha.6 ### Minor Changes - Added `queueSignal()` to `Agent` and AgentController `Session`. It queues a typed signal behind the active run like `queueMessage()`, but keeps the signal type, tag, and attributes instead of turning it into a plain user message. ([#27081](https://github.com/mastra-ai/mastra/pull/27081)) ```ts agent.queueSignal( { type: 'reactive', tagName: 'skill', attributes: { name: 'triage' }, contents: 'Triage the new issue.' }, { resourceId: 'user-123', threadId: 'thread-abc' }, ); ``` Runs started by a signal now record it on the `AGENT_RUN` span as `attributes.signal` (`id`, `type`, `tagName`, `attributes`), so traces show which signal started a run. ### Patch Changes - Fixed `resumeGenerate()` and `resumeStream()` using up a suspended run when the resume data does not match the tool's `resumeSchema`. They now throw `AGENT_RESUME_INVALID_DATA` and the run stays suspended, so you can resume again with valid data. ([#27130](https://github.com/mastra-ai/mastra/pull/27130)) - Fixed live assistant messages missing the model that wrote them. Messages streamed by an `AgentController` session now carry `modelId` and `provider` in their metadata, so a client can show the model without reloading the thread. After a fallback, they name the fallback model. ([#27030](https://github.com/mastra-ai/mastra/pull/27030)) - Fixed a resumed workflow step losing its `suspendData` when the run restarts after a crash. The step now gets the payload it passed to `suspend()` again, alongside its `resumeData`. ([#27127](https://github.com/mastra-ai/mastra/pull/27127)) ## 1.78.0-alpha.5 ### Patch Changes - Fixed agent runs crashing with `RangeError: Invalid string length` when a command printed a very large amount of output. `sandbox.executeCommand()` now keeps the newest 1 MiB of stdout and stderr by default, the same limit spawned processes already use. Streaming callbacks still receive every chunk, and the result reports `stdoutTruncated` and `stdoutDroppedBytes` (and the stderr equivalents) when output was dropped. ([#27106](https://github.com/mastra-ai/mastra/pull/27106)) If you need the complete output, pass `maxRetainedBytes: Infinity`: ```ts // Before: all output was kept by default const result = await sandbox.executeCommand('cat', ['data.json']); // After: pass Infinity to keep all output const result = await sandbox.executeCommand('cat', ['data.json'], { maxRetainedBytes: Infinity }); ``` Complete output is limited to 256 MiB per stream. A command that prints more is killed, and `executeCommand()` (or `handle.wait()`) rejects with `ProcessOutputLimitError` instead of crashing the process. The `execute_command` and `get_process_output` tools now tell the model when the start of the output was dropped, so it doesn't mistake the newest output for all of it. ## 1.78.0-alpha.4 ### Patch Changes - Fixed document citations disappearing after a reload. When a model streams a document source (for example an Anthropic PDF citation), memory now stores it as a `source-document` part with its media type, title and filename. Previously it was saved as a URL source with an empty `url`, so reloaded conversations showed an empty link instead of the cited document. ([#26805](https://github.com/mastra-ai/mastra/pull/26805)) ```ts const { messages } = await memory.recall({ threadId, resourceId }); // assistant parts now include: // { type: 'source-document', sourceId: 'doc-1', mediaType: 'application/pdf', title: 'Q3 report', filename: 'q3.pdf' } ``` AI SDK v5 UI messages now also keep `source-document` parts and the `sourceId` and `title` of URL sources when converting to and from stored messages. - Fixed agent controller channel sessions losing the sender's identity on the first message in a new chat thread. A channel session now opens directly on its chat thread, keeping the inbound request context, instead of creating an extra empty thread and switching over without it. Hosts that resolve memory per user from the request context's `user` no longer fail on a new thread. ([#27113](https://github.com/mastra-ai/mastra/pull/27113)) ## 1.78.0-alpha.3 ### Minor Changes - Memory can now contribute error processors to an agent. `MastraMemory.getErrorProcessors()` works like `getInputProcessors()` and `getOutputProcessors()`: the agent appends what it returns to its resolved `errorProcessors`, on both regular and durable agents. These processors run after configured and default error processors, and stay in place when a call passes its own `errorProcessors`. The base implementation returns none, so existing memory classes are unaffected. ([#26029](https://github.com/mastra-ai/mastra/pull/26029)) ```ts class MyMemory extends MastraMemory { async getErrorProcessors() { return [new MyRecoveryProcessor()]; } } ``` - Added one preview format for span input, output and error. Trace lists (`queryTraces()`, `listTracesLight()`) now return `outputPreview` and `errorPreview` next to `inputPreview`, and `querySpans()` rows add `errorPreview` and `errorTruncated`. ([#26567](https://github.com/mastra-ai/mastra/pull/26567)) **New exports from `@mastra/core/storage`** - `buildPreview(value)` builds a preview from a decoded payload, and `buildPreviewFromJson(json)` from stored JSON text. Both cut to `PREVIEW_MAX_LENGTH` (100) characters and end with `…` when cut. - Exported spans and span records accept optional `inputPreview`, `outputPreview` and `errorPreview`, preserved through create/update record builders. Stores trim nonblank supplied previews and cap them like generated ones (100 characters plus `…`, never splitting a surrogate pair); missing or blank previews are generated from the payload. ```ts import { buildPreview } from '@mastra/core/storage'; buildPreview({ messages: [{ role: 'user', content: 'Where is my order?' }] }); // 'Where is my order?' buildPreview({ message: 'Tool failed' }); // 'Tool failed' ``` **Behavior changes** - For a message list, previews now show the last user message instead of every user message joined with `|`. - `querySpans()` previews are now at most 100 characters of readable text plus an ellipsis instead of the first 256 characters of raw JSON. `buildInputPreview()` and `INPUT_PREVIEW_MAX_LENGTH` are deprecated. Use `buildPreview()` and `PREVIEW_MAX_LENGTH`. `buildInputPreview(input, maxLength)` still caps its preview at `maxLength`. - Removed the 31-day limit on trace search time ranges. You can now search all of your retained trace data, as long as the range starts before it ends. ([#27042](https://github.com/mastra-ai/mastra/pull/27042)) ```ts // Before: rejected because the range is longer than 31 days // After: returns traces from the whole six-month range await mastraClient.queryTraces({ timeRange: { from: '2026-04-01T00:00:00Z', to: '2026-10-01T00:00:00Z' }, }); ``` ### Patch Changes - Fixed `.foreach()` on the evented workflow engine when an iteration fails. The run now ends with `status: 'failed'` and the iteration's error. Before, it hung forever (`run.start()` never resolved) or, with `concurrency: 1`, finished as `success` with a corrupted output array. Iterations that finish after the failure no longer mark the foreach step successful. ([#26971](https://github.com/mastra-ai/mastra/pull/26971)) - Fixed workflow agent steps to support durable agents on both execution engines. ([#26919](https://github.com/mastra-ai/mastra/pull/26919)) - Fixed `resumeGenerate()` and `resumeStream()` saving messages to the thread when `memory.options.readOnly` is `true`. Resuming a suspended run now leaves read-only threads untouched, matching `generate()` and `stream()`. ([#27056](https://github.com/mastra-ai/mastra/pull/27056)) - Fixed prompt caching after a suspended or tool-approval run resumes from Postgres storage. ([#26943](https://github.com/mastra-ai/mastra/pull/26943)) Tool-call arguments and JSON tool results in model prompts now use sorted object keys, so the same content produces the same request after a run resumes. Stored messages are unchanged. - Fixed two OpenAI errors with tool inputs and structured output: ([#26979](https://github.com/mastra-ai/mastra/pull/26979)) - Optional tool inputs that accept more than one type can now be left empty. Before, OpenAI models filled them with made-up values. - Structured output with a field that has free-form keys, such as `z.record(z.string(), z.string())`, now works on OpenAI instead of failing with a 400 error. These schemas are sent without strict mode. To keep strict mode anyway, set `providerOptions.openai.strictJsonSchema: true`. - Updated dependencies [[`c6230e3`](https://github.com/mastra-ai/mastra/commit/c6230e30f76b4b912bad68f426d730da1cb67d83), [`cb015a1`](https://github.com/mastra-ai/mastra/commit/cb015a1be6560cbaf4deaef219cb02e63b263bc3)]: - @mastra/schema-compat@1.3.15-alpha.0 ## 1.78.0-alpha.2 ### Patch Changes - Disabled workspace tools no longer add setup work on every agent turn. Agents with workspace tools turned off now start each turn faster and allocate less memory. Fixes #26401. ([#26657](https://github.com/mastra-ai/mastra/pull/26657)) - Fixed Amazon Bedrock India inference profiles (`in.` prefix, e.g. `in.anthropic.claude-sonnet-5`) sending `temperature` to models that reject it, which returned a 400. They now resolve model capabilities like the other geography prefixes. Fixes #26387. ([#26982](https://github.com/mastra-ai/mastra/pull/26982)) - Fixed step scorers in nested workflows so their scores are saved. Before, scores from steps inside a child workflow were silently dropped. ([#26984](https://github.com/mastra-ai/mastra/pull/26984)) - Fixed new conversation failures when ToolSearchProcessor uses context storage with memory. ([#26902](https://github.com/mastra-ai/mastra/pull/26902)) ## 1.78.0-alpha.1 ### Minor Changes - Added a `beforeApproval` hook to tools that require approval. It runs before the agent pauses for approval and can reject the call by returning `{ proceed: false, output }`. The agent then skips the approval pause and does not execute the tool, and `output` goes back to the model as the tool result. Invalid calls are returned to the model to fix, and people are only asked to approve valid ones. ([#26927](https://github.com/mastra-ai/mastra/pull/26927)) ```ts const issueRefund = createTool({ id: 'issue-refund', inputSchema: z.object({ orderId: z.string(), amount: z.number() }), requireApproval: true, beforeApproval: async ({ orderId, amount }) => { const order = await orders.find(orderId); if (!order) return { proceed: false, output: { error: `Order ${orderId} not found` } }; if (amount > order.paid) return { proceed: false, output: { error: `Amount exceeds ${order.paid}` } }; }, execute: async ({ orderId, amount }) => refunds.issue(orderId, amount), }); ``` The hook does not run again when the call resumes after an approval decision. If the hook throws, the error is logged and the call proceeds to the approval gate. Keep validating inside `execute` too, because state can change while the call is waiting for approval. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`bed1ccc`](https://github.com/mastra-ai/mastra/commit/bed1cccea5817d68faa42a15fda3a0b329f70680)) - Durable agent turns no longer create an unused tool stream writer on every LLM step. A writer is now created only when a step actually uses it. ([#26987](https://github.com/mastra-ai/mastra/pull/26987)) - Fixed tool `transform` replacing stored tool-call arguments with a `Tool input-available payload unavailable` placeholder when only another phase (such as `transcript.output`) was configured. A tool-level transform now only changes the phases it defines, so stored `args` round-trip unchanged and replaying the thread with Anthropic preserved thinking no longer fails. Transformers that throw or return `undefined` still fall back to the placeholder, and raw input deltas stay suppressed. Fixes #26597. ([#26965](https://github.com/mastra-ai/mastra/pull/26965)) ## 1.78.0-alpha.0 ### Minor Changes - Added `scopes` to agents: the addresses a run acts as, written as `:` (for example `org:acme`, `resource:user-123`, `thread:conversation-123`). ([#26593](https://github.com/mastra-ai/mastra/pull/26593)) **Setting scopes.** Pass them per call on `stream`, `generate`, `network`, `sendMessage`, `queueMessage` and `sendSignal`, set them on the agent, or set them from trusted middleware with the new `MASTRA_SCOPES_KEY` request context key. Mastra combines all three. Read an agent's configured scopes with `agent.getScopes()`. **Memory.** Memory reads its resource and thread from `resource:` and `thread:` scopes, in addition to `memory.resource`, `memory.thread` and the existing request context keys. If they name different values, the call throws a 400 error instead of silently picking one. Runs with no scopes from any source behave as before. ```ts const agent = new Agent({ id: 'support', model: 'openai/gpt-5-mini', memory: new Memory(), scopes: ({ requestContext }) => { // Set by your auth middleware. Request bodies can fill unreserved keys, so only read trusted ones. const orgId = requestContext.get('org-id'); return orgId ? [`org:${orgId}`] : []; }, }); // Before await agent.generate('Hi', { memory: { resource: 'user-123', thread: 'conversation-123' } }); // After await agent.generate('Hi', { scopes: ['resource:user-123', 'thread:conversation-123'] }); ``` **Nested calls.** Tools, processors and subagents see the run's other scopes (for example `org:acme`) under `MASTRA_SCOPES_KEY`, but not its `resource:` and `thread:` scopes, so nested agent calls can choose their own thread. **Suspended runs.** Suspended runs keep their scopes when resumed: a resume may repeat some or all of them but cannot add new ones. **Upgrading.** Upgrade `@mastra/server` together with this release. While an older server is registered, agents refuse scopes from the call and the request context with an `AGENT_SCOPES_SERVER_OUTDATED` error (HTTP 400); the agent's own `scopes` option still applies. This detection relies on the server registering itself with Mastra, which `@mastra/server` has done since `1.0.0-beta.5`. Servers older than that — and custom server adapters that never call `mastra.setMastraServer()` — are indistinguishable from no server at all, so they are not detected: upgrade them rather than relying on the guard. Runs suspended before this release have no recorded scope set; resuming one adopts the scopes supplied at resume time (the same trust checks as a fresh run apply) instead of rejecting them. Runs suspended after the upgrade record their set — including the empty set — and a resume may not add to it. ### Patch Changes - Fixed durable recovery observers hanging when recovery fails before the stream starts. ([#26918](https://github.com/mastra-ai/mastra/pull/26918)) - Fixed background task events leaking across resources. Previously, calling `BackgroundTaskManager.stream()` without a `resourceId` delivered every resource's task events, including tool arguments and results. Without a `resourceId`, `stream()` now only delivers tasks that have no resource. To subscribe to every resource, pass `allResources: true`: ([#26895](https://github.com/mastra-ai/mastra/pull/26895)) ```ts const events = mastra.backgroundTaskManager.stream({ agentId: 'my-agent', allResources: true }); ``` The `/background-tasks`, `/background-tasks/stream` and `/background-tasks/:backgroundTaskId` routes now use the resource from the request context. A resource-scoped caller only sees its own tasks: requesting a different `resourceId` returns 403, and fetching another resource's task returns 404. An empty resource id in the request context is rejected with 403. Requests without a resource in the request context keep access to all resources. ## 1.77.0 ### Minor Changes - AgentController `steer` and `followUp` now accept file attachments, matching `sendMessage`. Attached files reach the model the same way whether the message is sent right away or queued behind an active run. ([#26909](https://github.com/mastra-ai/mastra/pull/26909)) ```typescript await session.followUp({ content: 'Then compare against this screenshot.', files: [{ data: screenshotBase64, mediaType: 'image/png', filename: 'expected.png' }], }); ``` The `/steer` and `/follow-up` routes accept the same `files` field and limits as `/messages`. - Added `FactorySandbox`, the contract a host implements to plug a sandbox provider into Mastra Factory. The bare callback that builds one `MastraSandbox` per session still works, but it gives a provider no way to expose tunable settings, a prebuilt repository template or template builds. A `FactorySandbox` owns all four, and lives in `@mastra/core/workspace` so provider packages implement it without depending on `@mastra/factory`. ([#26467](https://github.com/mastra-ai/mastra/pull/26467)) **Required:** `provider` and `create`, which returns the session's `MastraSandbox`. **Optional:** `settings`, a schema of what a user can tune (zod, JSON Schema or Standard Schema; every field optional, absent means the provider default; no settings when omitted); `template` returns the environment's repository template for a context and settings; `builds` starts, reads and lists template builds ahead of any session. ```ts import { FactorySandbox } from '@mastra/core/workspace'; import type { FactorySandboxContext } from '@mastra/core/workspace'; import type { PublicSchema } from '@mastra/core/schema'; interface Settings { region?: 'us' | 'eu'; } class MyFactorySandbox extends FactorySandbox { readonly provider = 'my-cloud'; readonly settings: PublicSchema = { type: 'object', properties: { region: { type: 'string', enum: ['us', 'eu'], default: 'us' } }, additionalProperties: false, }; create(ctx: FactorySandboxContext, settings: Settings) { return new MyCloudSandbox({ id: ctx.sessionId, sandboxId: ctx.sandboxId, template: this.template(ctx, settings), workingDirectory: ctx.workingDirectory, region: settings.region ?? 'us', }); } // Optional: the sandbox template image Factory can build ahead of time template(ctx: FactorySandboxContext, settings: Settings) { return myCloudTemplate(ctx, settings); } // Optional: build that template ahead of time and report on it readonly builds = { start: async (ctx: FactorySandboxContext, settings: Settings) => myCloud.startBuild(this.template(ctx, settings)), get: async (ctx: FactorySandboxContext, settings: Settings, buildId: string) => myCloud.buildStatus(buildId), list: async (ctx: FactorySandboxContext, settings: Settings) => myCloud.builds(this.template(ctx, settings).name), }; } ``` - Added authenticated message authors to AgentController sessions. Pass `author` to `sendMessage`, `queueMessage`, or `steer`, and read it back with `getMessageAuthor`. ([#26549](https://github.com/mastra-ai/mastra/pull/26549)) ```ts await session.sendMessage({ content: 'Hi', author: { id: 'user-123' } }); const messages = await session.thread.listMessages({ threadId: session.thread.requireId() }); const message = messages.findLast(message => message.role === 'signal')!; const author = getMessageAuthor(message); ``` - Added an optional `pricing` field to gateway provider configs. A custom gateway can now state what its models cost, in USD per 1 million tokens with the same keys as models.dev, so calls to private or reseller models get cost estimates. ([#26875](https://github.com/mastra-ai/mastra/pull/26875)) ```ts async fetchProviders() { return { 'my-provider': { name: 'My Provider', models: ['model-1'], apiKeyEnvVar: 'MY_API_KEY', gateway: 'private', url: 'https://api.myprovider.com/v1', pricing: { 'model-1': { input: 0.5, output: 1.5 } }, }, }; } ``` - Added `links` to spans: references to spans in other traces. Pass `links` when starting a span to relate it to a span in another trace without joining that trace. Mastra storage saves the links with the span. ([#26059](https://github.com/mastra-ai/mastra/pull/26059)) ```ts const span = getOrCreateSpan({ type: SpanType.GENERIC, name: 'handle request', links: [{ traceId: callerTraceId, spanId: callerSpanId }], mastra, }); ``` Also added `parseTraceparent()` and `formatTraceparent()` to `@mastra/core/observability` for W3C `traceparent` values. - Fixed observational memory losing context when a reflection, a buffered-observation write, and an observation activation overlap. The in-memory store now keeps buffered observations across a reflection, never lets activation drop a chunk written by someone else or move the observation cursor backward, and refuses observational memory lifecycle writes (buffered-observation appends, activation, active-observation commits, reflections) to a superseded generation. ([#25906](https://github.com/mastra-ai/mastra/pull/25906)) Storage gained two methods that report the outcome of a write, plus optional inputs, so callers can detect these cases: - `commitActiveObservations` writes active observations and returns `{ applied, reason }`. It accepts `expectedActiveObservations` and never writes to a superseded generation. - `appendBufferedObservations` appends a buffered chunk to the current generation and returns `{ persisted, recordId }`. - `createReflectionGeneration` and `swapBufferedReflectionToActive` accept `newRecordId`; the reflection applied when the returned record has that id. `swapBufferedToActive` reports `retired`. - Observational memory records carry `supersededBy`, set when a newer generation replaces them. **Before:** ```ts const memory = await storage.getStore('memory'); await memory.updateActiveObservations({ id: record.id, observations, tokenCount, lastObservedAt: new Date(), }); ``` **After:** ```ts const memory = await storage.getStore('memory'); const result = await memory.commitActiveObservations({ id: record.id, observations, tokenCount, lastObservedAt: new Date(), expectedActiveObservations: record.activeObservations, }); if (!result.applied) { // 'retired': a newer generation replaced this record. 'conflict': another writer changed it first. // Read the current record and observe again. } ``` `updateActiveObservations` and `updateBufferedObservations` keep returning `Promise`. In stores that implement the new methods, `updateActiveObservations` now throws when the observations were not written (the generation was superseded, or `expectedActiveObservations` no longer matches) instead of resolving as if they had been. An older `@mastra/memory` running against an upgraded store therefore reports a failed observation and observes the same messages again next turn, rather than marking them observed without saving their observations. Storage adapters that do not implement these keep their previous behavior. Upgrade `@mastra/core`, `@mastra/memory`, and your storage adapter together to get the full fix; every process writing to the same database needs the upgraded adapter. - Added transferable AgentController thread ownership and a per-run owner snapshot in request context. New threads record their owner and creator, while ownership changes to the active thread become visible on the next run. ([#26549](https://github.com/mastra-ai/mastra/pull/26549)) ```ts await session.thread.transferOwnership({ toOwnerId: 'owner-b' }); ``` ### Patch Changes - Fixed aborted agent streams reporting empty token usage. When a stream is aborted, `onFinish` now receives the usage of steps that completed before the abort: `totalUsage` is the sum across those steps and `usage` is the last completed step. Observability and cost tracking for cancelled runs now include that completed-step usage. Usage from the step that was still running when the abort happened is not reported. If no step completed before the abort, `usage` and `totalUsage` still have undefined token counts. ([#26629](https://github.com/mastra-ai/mastra/pull/26629)) - Update provider registry and model documentation with latest models and providers ([`eaa9d12`](https://github.com/mastra-ai/mastra/commit/eaa9d12407f4962e94e0d5cd49a643e6e317d60a)) - Added an optional `project_id2` to server feature and model-usage telemetry while preserving existing project identifiers and usage cursors. UUID-shaped `MASTRA_PROJECT_ID` values are sent unhashed with an `mp_` prefix for direct Platform lookup. Other values fall back to a hash of the normalized Git `origin` remote, discovered asynchronously without blocking startup. The field is omitted when neither source is available. ([#22067](https://github.com/mastra-ai/mastra/pull/22067)) These events are project-linked, not anonymous. Git-derived identifiers can also be matched against known repository URLs. Set `MASTRA_TELEMETRY_DISABLED=1` to disable telemetry and skip Git discovery. - Fixed a tool running twice when the same suspended tool call is approved, declined, or resumed concurrently (for example a double click, a client retry, or two open tabs). `approveToolCall`, `declineToolCall`, `resumeStream`, and their generate variants now claim the suspended run before resuming it. Only one caller resumes; the others fail with `AGENT_RESUME_ALREADY_CLAIMED`. ([#26688](https://github.com/mastra-ai/mastra/pull/26688)) ```ts const results = await Promise.allSettled([ agent.approveToolCall({ runId, toolCallId }), agent.approveToolCall({ runId, toolCallId }), ]); // One fulfilled, one rejected with AGENT_RESUME_ALREADY_CLAIMED. The tool runs once. ``` Durable agents report the losing resume the same way, as an error chunk in its stream with the `AGENT_RESUME_ALREADY_CLAIMED` id. What each resume error means: - `AGENT_RESUME_ALREADY_CLAIMED`: another approval or resume of this run is running or just finished. Stop retrying. - `AGENT_RESUME_RUN_NOT_SUSPENDED` (durable agents): the run exists but isn't waiting for approval, for example because it already finished or failed. `details.status` gives its state. - `AGENT_RESUME_NO_SNAPSHOT_FOUND`: there is no record of a suspended run. Check the `runId`. Agents delete a run's record when it finishes, so an approval sent after that also gets this error. Resuming a workflow run that isn't suspended now throws an error with the id `WORKFLOW_RUN_NOT_SUSPENDED`. The message is unchanged. Storage adapters that support atomic updates (for example PostgreSQL, LibSQL, MongoDB, and the in-memory store) protect against duplicates across processes. Other adapters protect against duplicates within a single process. - Fixed durable, evented, and Inngest agents leaking one run's messages into another run's model prompt when two Mastra instances in the same process reuse a `runId`. ([#26787](https://github.com/mastra-ai/mastra/pull/26787)) - Fixed durable agents returning empty step data in callbacks and results. `onStepFinish`, `onFinish`, `onAbort`, `output.steps` and `output.response` now include each step's response messages and model id, the same as a regular agent. A `model` passed to a durable agent's `stream()` call is now the model that actually runs, instead of the agent's default model. (#26780) ([#26933](https://github.com/mastra-ai/mastra/pull/26933)) - Document that in-memory, LibSQL, and PostgreSQL workflow result updates no-op after run deletion while remaining adapters are aligned under COR-1448. ([#26901](https://github.com/mastra-ai/mastra/pull/26901)) - Fixed agent experiment results losing their error details. A failed agent run now stores `error.message` and `error.stack` instead of an empty object. Added exported `AgentExperimentOutput`, `ScorerExperimentOutput`, and `ExperimentResultOutput` types that describe what each experiment target stores in `output`. ([#26726](https://github.com/mastra-ai/mastra/pull/26726)) - Fixed output guardrails leaving blocked replies in memory. When an output processor aborts in `processOutputResult`, the agent (including durable agents) now deletes the reply messages that the run had already saved. The blocked text no longer stays in memory and is not sent back to the model on the next turn. This affected turns that paused for tool approval (approved or declined) and turns run with `savePerStep: true`. The user's message, earlier turns, and messages sent in as input are kept. Fixes #26215. ([#26252](https://github.com/mastra-ai/mastra/pull/26252)) - Added `targetType` to experiment results. The result `output` is now typed based on the target, so checking `targetType` gives you typed agent and scorer outputs: ([#26726](https://github.com/mastra-ai/mastra/pull/26726)) ```ts const { results } = await client.listDatasetExperimentResults(datasetId, experimentId); for (const result of results) { if (result.targetType === 'agent') { console.log(result.output?.text, result.output?.error?.message); } else if (result.targetType === 'scorer') { console.log(result.output?.score); } } ``` Workflow, processor, and caller-driven results (`targetType: null`) keep `output` as `unknown`. - Workflow steps, conditions, and dynamic sleeps now create their `writer` stream only when your code reads `writer`. Steps that never use it no longer allocate a stream on every run, which reduces memory churn in agent loops. An agent turn, including a durable agent turn, now creates only the writers its tools use. Steps that call `writer.write()` or `writer.custom()` behave as before. ([#26665](https://github.com/mastra-ai/mastra/pull/26665)) - Fixed `mastra.datasets.compareExperiments()` silently comparing only the baseline and one other experiment. Every ID in `experimentIds` now gets a result entry on each item (`null` when that experiment did not run the item). Duplicate IDs are ignored, and passing a `baselineId` that isn't one of `experimentIds` now throws instead of being ignored. ([#26725](https://github.com/mastra-ai/mastra/pull/26725)) ```ts const comparison = await mastra.datasets.compareExperiments({ experimentIds: ['exp-baseline', 'exp-prompt-v2', 'exp-new-model'], baselineId: 'exp-baseline', }); // comparison.items[0].results has entries for all three experiments ``` - Fixed evented agents and nested evented workflows failing with `Workflow with id ... not found` when an orchestration worker runs steps on the API server through `MASTRA_STEP_EXECUTION_URL`. The worker now sends the parent workflow with each step request, so the server can find steps that are only reachable through their parent workflow, such as the steps of an evented agent. Fixes [#26676](https://github.com/mastra-ai/mastra/issues/26676). ([#26695](https://github.com/mastra-ai/mastra/pull/26695)) Also fixed nested evented workflows hanging when their parent workflow is registered under a key that differs from its `id`. - Fixed delayed interruption of Evented agent responses while preserving partial output and graceful run cleanup. ([#26926](https://github.com/mastra-ai/mastra/pull/26926)) - Fixed core builds failing when regenerated model provider types grow large. ([#26876](https://github.com/mastra-ai/mastra/pull/26876)) - Fixed tools returned by an input processor's `processInputStep` (such as `ToolSearchProcessor`'s loaded tools) running with an empty `context.agent.agentId`. They now receive the agent's id, the same as tools assigned to the agent directly, in both regular and durable agents. ([#26816](https://github.com/mastra-ai/mastra/pull/26816)) - Fixed TypeScript types for Standard Schemas that transform their input, such as Effect's `Schema.toStandardSchemaV1(Schema.DateFromString)`. Tool `execute` functions now receive the transformed output type instead of a union of input and output types. Tool `requestContextSchema` now accepts these schemas, and tool request context is typed with the transformed output. ([#26815](https://github.com/mastra-ai/mastra/pull/26815)) ```typescript const DateSchema = Schema.toStandardSchemaV1(Schema.Struct({ date: Schema.DateFromString })); const tool = createTool({ id: 'schedule', description: 'Schedules an event', inputSchema: DateSchema, requestContextSchema: DateSchema, execute: async ({ date }, { requestContext }) => { date.toISOString(); // date is typed as Date requestContext.get('date').toISOString(); // request context value is typed as Date }, }); ``` - Fixed the `StorageMCPClientToolsConfig` and `StoredMCPClientToolsConfig` JSDoc. It previously said that omitting `tools` includes all MCP tools. For an agent-level MCP client reference, omitting `tools` selects no tools. Use `tools: {}` to include all tools. ([#26738](https://github.com/mastra-ai/mastra/pull/26738)) - `ExpectedStep` now supports `provider_tool_call` steps, so you can write typed trajectory expectations for provider-executed tools such as web search. The trajectory scorer compares their `toolArgs`, `toolResult`, and `success` fields. ([#26778](https://github.com/mastra-ai/mastra/pull/26778)) ```ts const steps: ExpectedStep[] = [ { name: 'web_search', stepType: 'provider_tool_call', toolArgs: { query: 'weather' } }, ]; ``` - Fixed provider tool errors being sent back to the model as successful results. When a provider-run tool returns an error, Mastra now saves it as an error and sends it back as an error in later steps and recalled conversations. ([#26652](https://github.com/mastra-ai/mastra/pull/26652)) - Fixed live scorers missing tool calls when observational memory is enabled. Observational memory removes messages from the agent's context once it has observed them, and live scorers only saw the messages that were left. On runs that suspend and resume, scorer `output` could miss every tool call made before `suspend()`, or end up empty. Scorers now receive every response message the run produced, across all suspend/resume segments, under the original `runId`. The prompt sent to the model is unchanged. ([#26687](https://github.com/mastra-ai/mastra/pull/26687)) - Fixed reasoning from later steps being dropped when the reasoning text is empty. This affected providers that store reasoning identity outside the text, such as Anthropic, Bedrock and Gemini signatures or redacted thinking. Each distinct reasoning block now stays in the conversation when its provider metadata, provider options or reasoning details have an item ID, signature, redacted data, encrypted content or digest field. Identical reasoning blocks are not added again. ([#26941](https://github.com/mastra-ai/mastra/pull/26941)) - Fixed observational memory's temporal-gap reminder appearing after the user message it describes. A signal can set `metadata.precedesMessageId` to a message already in the conversation. That signal now sorts directly before the message in the prompt. Saved history keeps the same order. Models no longer read the reminder as the latest turn. ([#26800](https://github.com/mastra-ai/mastra/pull/26800)) - Updated the `observation.blockAfter` documentation for Observational Memory: between `messageTokens` and `blockAfter` only background buffering and activation run, and a blocking observation runs at or above `blockAfter` when no buffered chunk activates. ([#22078](https://github.com/mastra-ai/mastra/pull/22078)) - Fixed `structuredOutput` so it only uses the final answer of a run. ([#26639](https://github.com/mastra-ai/mastra/pull/26639)) - Text an agent writes before calling a tool (like "Let me look that up.") no longer fails validation or ends the run early. - `stream.object` now resolves to the final answer, not an earlier JSON draft. - Durable agents now return the final answer instead of the first JSON they saw. ([#25038](https://github.com/mastra-ai/mastra/issues/25038)) - Fixed untilIdle streams dropping background tasks started by the run they replaced: a new stream({ untilIdle: true }) on the same thread aborts the earlier idle wrapper, and the new wrapper only tracked tasks whose events it saw itself — so an in-flight task's completion woke nobody and the agent never reported its result. The idle loop now adopts still-running and pending background tasks for its agent, thread, and resource when it starts, so their completions trigger a continuation turn as usual. Fixes #26841 ([#26848](https://github.com/mastra-ai/mastra/pull/26848)) - Preserve output processor retry metadata in durable step events ([#26898](https://github.com/mastra-ai/mastra/pull/26898)) - Improved AgentController sessions so each thread restores its saved mode, model, state, token usage, and pending tool prompts when selected. Switching away and `detachFromCurrent()` cancel actively running local executions only for the initiating session. Listeners detach without cancelling the run. Parked runs and pending prompts remain available when switching back. ([#26058](https://github.com/mastra-ai/mastra/pull/26058)) Fixed first-thread creation to retain explicit startup mode, model, and thinking selections. Later new conversations use controller defaults. Asynchronous state writes and resumed tools keep their source thread's preferences without changing another active conversation. Addressed resumes recover the originating agent after restart and support repeated suspensions while another thread stays active. The executing run saves token usage to its own thread and retains unsaved measurements for retry on the next step. Subscribers display saved totals without counting replayed steps again or saving usage. Storage-free sessions can still receive inactive wake signals. Custom state and permissions remain host-scoped. Changed `session.thread.set()` and `session.thread.clear()` to throw during thread lifecycle transitions. Added address-based suspension APIs and event fields so callers can safely claim and resume a prompt after switching threads: `const claim = session.claimToolSuspension(toolCallId, runId)` returns an `address` that can be passed to `session.respondToToolSuspension({ address, resumeData })`. Exported `SuspensionAddress` and `getSuspensionAddressKey` for hosts that persist or key those addresses. Deprecated the `SessionRecord.modeId`, `modelId`, `state`, and `pending` fields, the `HarnessStorage.appendPendingItem()`, `updatePendingItem()`, and `removePendingItem()` helpers, and `SessionSuspensions.resolveToolCallId()`. - Updated dependencies [[`ca92c02`](https://github.com/mastra-ai/mastra/commit/ca92c02696427baad81dd7a61b049ef37c332452)]: - @mastra/schema-compat@1.3.14 ## 1.77.0-alpha.4 ### Minor Changes - AgentController `steer` and `followUp` now accept file attachments, matching `sendMessage`. Attached files reach the model the same way whether the message is sent right away or queued behind an active run. ([#26909](https://github.com/mastra-ai/mastra/pull/26909)) ```typescript await session.followUp({ content: 'Then compare against this screenshot.', files: [{ data: screenshotBase64, mediaType: 'image/png', filename: 'expected.png' }], }); ``` The `/steer` and `/follow-up` routes accept the same `files` field and limits as `/messages`. - Added `FactorySandbox`, the contract a host implements to plug a sandbox provider into Mastra Factory. The bare callback that builds one `MastraSandbox` per session still works, but it gives a provider no way to expose tunable settings, a prebuilt repository template or template builds. A `FactorySandbox` owns all four, and lives in `@mastra/core/workspace` so provider packages implement it without depending on `@mastra/factory`. ([#26467](https://github.com/mastra-ai/mastra/pull/26467)) **Required:** `provider` and `create`, which returns the session's `MastraSandbox`. **Optional:** `settings`, a schema of what a user can tune (zod, JSON Schema or Standard Schema; every field optional, absent means the provider default; no settings when omitted); `template` returns the environment's repository template for a context and settings; `builds` starts, reads and lists template builds ahead of any session. ```ts import { FactorySandbox } from '@mastra/core/workspace'; import type { FactorySandboxContext } from '@mastra/core/workspace'; import type { PublicSchema } from '@mastra/core/schema'; interface Settings { region?: 'us' | 'eu'; } class MyFactorySandbox extends FactorySandbox { readonly provider = 'my-cloud'; readonly settings: PublicSchema = { type: 'object', properties: { region: { type: 'string', enum: ['us', 'eu'], default: 'us' } }, additionalProperties: false, }; create(ctx: FactorySandboxContext, settings: Settings) { return new MyCloudSandbox({ id: ctx.sessionId, sandboxId: ctx.sandboxId, template: this.template(ctx, settings), workingDirectory: ctx.workingDirectory, region: settings.region ?? 'us', }); } // Optional: the sandbox template image Factory can build ahead of time template(ctx: FactorySandboxContext, settings: Settings) { return myCloudTemplate(ctx, settings); } // Optional: build that template ahead of time and report on it readonly builds = { start: async (ctx: FactorySandboxContext, settings: Settings) => myCloud.startBuild(this.template(ctx, settings)), get: async (ctx: FactorySandboxContext, settings: Settings, buildId: string) => myCloud.buildStatus(buildId), list: async (ctx: FactorySandboxContext, settings: Settings) => myCloud.builds(this.template(ctx, settings).name), }; } ``` - Added an optional `pricing` field to gateway provider configs. A custom gateway can now state what its models cost, in USD per 1 million tokens with the same keys as models.dev, so calls to private or reseller models get cost estimates. ([#26875](https://github.com/mastra-ai/mastra/pull/26875)) ```ts async fetchProviders() { return { 'my-provider': { name: 'My Provider', models: ['model-1'], apiKeyEnvVar: 'MY_API_KEY', gateway: 'private', url: 'https://api.myprovider.com/v1', pricing: { 'model-1': { input: 0.5, output: 1.5 } }, }, }; } ``` ### Patch Changes - Fixed durable agents returning empty step data in callbacks and results. `onStepFinish`, `onFinish`, `onAbort`, `output.steps` and `output.response` now include each step's response messages and model id, the same as a regular agent. A `model` passed to a durable agent's `stream()` call is now the model that actually runs, instead of the agent's default model. (#26780) ([#26933](https://github.com/mastra-ai/mastra/pull/26933)) - Document that in-memory, LibSQL, and PostgreSQL workflow result updates no-op after run deletion while remaining adapters are aligned under COR-1448. ([#26901](https://github.com/mastra-ai/mastra/pull/26901)) - Fixed delayed interruption of Evented agent responses while preserving partial output and graceful run cleanup. ([#26926](https://github.com/mastra-ai/mastra/pull/26926)) - Fixed core builds failing when regenerated model provider types grow large. ([#26876](https://github.com/mastra-ai/mastra/pull/26876)) - Fixed TypeScript types for Standard Schemas that transform their input, such as Effect's `Schema.toStandardSchemaV1(Schema.DateFromString)`. Tool `execute` functions now receive the transformed output type instead of a union of input and output types. Tool `requestContextSchema` now accepts these schemas, and tool request context is typed with the transformed output. ([#26815](https://github.com/mastra-ai/mastra/pull/26815)) ```typescript const DateSchema = Schema.toStandardSchemaV1(Schema.Struct({ date: Schema.DateFromString })); const tool = createTool({ id: 'schedule', description: 'Schedules an event', inputSchema: DateSchema, requestContextSchema: DateSchema, execute: async ({ date }, { requestContext }) => { date.toISOString(); // date is typed as Date requestContext.get('date').toISOString(); // request context value is typed as Date }, }); ``` - Fixed reasoning from later steps being dropped when the reasoning text is empty. This affected providers that store reasoning identity outside the text, such as Anthropic, Bedrock and Gemini signatures or redacted thinking. Each distinct reasoning block now stays in the conversation when its provider metadata, provider options or reasoning details have an item ID, signature, redacted data, encrypted content or digest field. Identical reasoning blocks are not added again. ([#26941](https://github.com/mastra-ai/mastra/pull/26941)) - Preserve output processor retry metadata in durable step events ([#26898](https://github.com/mastra-ai/mastra/pull/26898)) - Updated dependencies [[`ca92c02`](https://github.com/mastra-ai/mastra/commit/ca92c02696427baad81dd7a61b049ef37c332452)]: - @mastra/schema-compat@1.3.14-alpha.0 ## 1.77.0-alpha.3 ### Minor Changes - Added authenticated message authors to AgentController sessions. Pass `author` to `sendMessage`, `queueMessage`, or `steer`, and read it back with `getMessageAuthor`. ([#26549](https://github.com/mastra-ai/mastra/pull/26549)) ```ts await session.sendMessage({ content: 'Hi', author: { id: 'user-123' } }); const messages = await session.thread.listMessages({ threadId: session.thread.requireId() }); const message = messages.findLast(message => message.role === 'signal')!; const author = getMessageAuthor(message); ``` - Added transferable AgentController thread ownership and a per-run owner snapshot in request context. New threads record their owner and creator, while ownership changes to the active thread become visible on the next run. ([#26549](https://github.com/mastra-ai/mastra/pull/26549)) ```ts await session.thread.transferOwnership({ toOwnerId: 'owner-b' }); ``` ### Patch Changes - Fixed aborted agent streams reporting empty token usage. When a stream is aborted, `onFinish` now receives the usage of steps that completed before the abort: `totalUsage` is the sum across those steps and `usage` is the last completed step. Observability and cost tracking for cancelled runs now include that completed-step usage. Usage from the step that was still running when the abort happened is not reported. If no step completed before the abort, `usage` and `totalUsage` still have undefined token counts. ([#26629](https://github.com/mastra-ai/mastra/pull/26629)) - Fixed a tool running twice when the same suspended tool call is approved, declined, or resumed concurrently (for example a double click, a client retry, or two open tabs). `approveToolCall`, `declineToolCall`, `resumeStream`, and their generate variants now claim the suspended run before resuming it. Only one caller resumes; the others fail with `AGENT_RESUME_ALREADY_CLAIMED`. ([#26688](https://github.com/mastra-ai/mastra/pull/26688)) ```ts const results = await Promise.allSettled([ agent.approveToolCall({ runId, toolCallId }), agent.approveToolCall({ runId, toolCallId }), ]); // One fulfilled, one rejected with AGENT_RESUME_ALREADY_CLAIMED. The tool runs once. ``` Durable agents report the losing resume the same way, as an error chunk in its stream with the `AGENT_RESUME_ALREADY_CLAIMED` id. What each resume error means: - `AGENT_RESUME_ALREADY_CLAIMED`: another approval or resume of this run is running or just finished. Stop retrying. - `AGENT_RESUME_RUN_NOT_SUSPENDED` (durable agents): the run exists but isn't waiting for approval, for example because it already finished or failed. `details.status` gives its state. - `AGENT_RESUME_NO_SNAPSHOT_FOUND`: there is no record of a suspended run. Check the `runId`. Agents delete a run's record when it finishes, so an approval sent after that also gets this error. Resuming a workflow run that isn't suspended now throws an error with the id `WORKFLOW_RUN_NOT_SUSPENDED`. The message is unchanged. Storage adapters that support atomic updates (for example PostgreSQL, LibSQL, MongoDB, and the in-memory store) protect against duplicates across processes. Other adapters protect against duplicates within a single process. - Fixed tools returned by an input processor's `processInputStep` (such as `ToolSearchProcessor`'s loaded tools) running with an empty `context.agent.agentId`. They now receive the agent's id, the same as tools assigned to the agent directly, in both regular and durable agents. ([#26816](https://github.com/mastra-ai/mastra/pull/26816)) - Fixed untilIdle streams dropping background tasks started by the run they replaced: a new stream({ untilIdle: true }) on the same thread aborts the earlier idle wrapper, and the new wrapper only tracked tasks whose events it saw itself — so an in-flight task's completion woke nobody and the agent never reported its result. The idle loop now adopts still-running and pending background tasks for its agent, thread, and resource when it starts, so their completions trigger a continuation turn as usual. Fixes #26841 ([#26848](https://github.com/mastra-ai/mastra/pull/26848)) ## 1.77.0-alpha.2 ### Patch Changes - Fixed durable, evented, and Inngest agents leaking one run's messages into another run's model prompt when two Mastra instances in the same process reuse a `runId`. ([#26787](https://github.com/mastra-ai/mastra/pull/26787)) - Fixed output guardrails leaving blocked replies in memory. When an output processor aborts in `processOutputResult`, the agent (including durable agents) now deletes the reply messages that the run had already saved. The blocked text no longer stays in memory and is not sent back to the model on the next turn. This affected turns that paused for tool approval (approved or declined) and turns run with `savePerStep: true`. The user's message, earlier turns, and messages sent in as input are kept. Fixes #26215. ([#26252](https://github.com/mastra-ai/mastra/pull/26252)) - Fixed evented agents and nested evented workflows failing with `Workflow with id ... not found` when an orchestration worker runs steps on the API server through `MASTRA_STEP_EXECUTION_URL`. The worker now sends the parent workflow with each step request, so the server can find steps that are only reachable through their parent workflow, such as the steps of an evented agent. Fixes [#26676](https://github.com/mastra-ai/mastra/issues/26676). ([#26695](https://github.com/mastra-ai/mastra/pull/26695)) Also fixed nested evented workflows hanging when their parent workflow is registered under a key that differs from its `id`. - Fixed provider tool errors being sent back to the model as successful results. When a provider-run tool returns an error, Mastra now saves it as an error and sends it back as an error in later steps and recalled conversations. ([#26652](https://github.com/mastra-ai/mastra/pull/26652)) - Fixed live scorers missing tool calls when observational memory is enabled. Observational memory removes messages from the agent's context once it has observed them, and live scorers only saw the messages that were left. On runs that suspend and resume, scorer `output` could miss every tool call made before `suspend()`, or end up empty. Scorers now receive every response message the run produced, across all suspend/resume segments, under the original `runId`. The prompt sent to the model is unchanged. ([#26687](https://github.com/mastra-ai/mastra/pull/26687)) - Improved AgentController sessions so each thread restores its saved mode, model, state, token usage, and pending tool prompts when selected. Switching away and `detachFromCurrent()` cancel actively running local executions only for the initiating session. Listeners detach without cancelling the run. Parked runs and pending prompts remain available when switching back. ([#26058](https://github.com/mastra-ai/mastra/pull/26058)) Fixed first-thread creation to retain explicit startup mode, model, and thinking selections. Later new conversations use controller defaults. Asynchronous state writes and resumed tools keep their source thread's preferences without changing another active conversation. Addressed resumes recover the originating agent after restart and support repeated suspensions while another thread stays active. The executing run saves token usage to its own thread and retains unsaved measurements for retry on the next step. Subscribers display saved totals without counting replayed steps again or saving usage. Storage-free sessions can still receive inactive wake signals. Custom state and permissions remain host-scoped. Changed `session.thread.set()` and `session.thread.clear()` to throw during thread lifecycle transitions. Added address-based suspension APIs and event fields so callers can safely claim and resume a prompt after switching threads: `const claim = session.claimToolSuspension(toolCallId, runId)` returns an `address` that can be passed to `session.respondToToolSuspension({ address, resumeData })`. Exported `SuspensionAddress` and `getSuspensionAddressKey` for hosts that persist or key those addresses. Deprecated the `SessionRecord.modeId`, `modelId`, `state`, and `pending` fields, the `HarnessStorage.appendPendingItem()`, `updatePendingItem()`, and `removePendingItem()` helpers, and `SessionSuspensions.resolveToolCallId()`. ## 1.77.0-alpha.1 ### Minor Changes - Added `links` to spans: references to spans in other traces. Pass `links` when starting a span to relate it to a span in another trace without joining that trace. Mastra storage saves the links with the span. ([#26059](https://github.com/mastra-ai/mastra/pull/26059)) ```ts const span = getOrCreateSpan({ type: SpanType.GENERIC, name: 'handle request', links: [{ traceId: callerTraceId, spanId: callerSpanId }], mastra, }); ``` Also added `parseTraceparent()` and `formatTraceparent()` to `@mastra/core/observability` for W3C `traceparent` values. ### Patch Changes - Fixed agent experiment results losing their error details. A failed agent run now stores `error.message` and `error.stack` instead of an empty object. Added exported `AgentExperimentOutput`, `ScorerExperimentOutput`, and `ExperimentResultOutput` types that describe what each experiment target stores in `output`. ([#26726](https://github.com/mastra-ai/mastra/pull/26726)) - Added `targetType` to experiment results. The result `output` is now typed based on the target, so checking `targetType` gives you typed agent and scorer outputs: ([#26726](https://github.com/mastra-ai/mastra/pull/26726)) ```ts const { results } = await client.listDatasetExperimentResults(datasetId, experimentId); for (const result of results) { if (result.targetType === 'agent') { console.log(result.output?.text, result.output?.error?.message); } else if (result.targetType === 'scorer') { console.log(result.output?.score); } } ``` Workflow, processor, and caller-driven results (`targetType: null`) keep `output` as `unknown`. - Workflow steps, conditions, and dynamic sleeps now create their `writer` stream only when your code reads `writer`. Steps that never use it no longer allocate a stream on every run, which reduces memory churn in agent loops. An agent turn, including a durable agent turn, now creates only the writers its tools use. Steps that call `writer.write()` or `writer.custom()` behave as before. ([#26665](https://github.com/mastra-ai/mastra/pull/26665)) - Fixed `mastra.datasets.compareExperiments()` silently comparing only the baseline and one other experiment. Every ID in `experimentIds` now gets a result entry on each item (`null` when that experiment did not run the item). Duplicate IDs are ignored, and passing a `baselineId` that isn't one of `experimentIds` now throws instead of being ignored. ([#26725](https://github.com/mastra-ai/mastra/pull/26725)) ```ts const comparison = await mastra.datasets.compareExperiments({ experimentIds: ['exp-baseline', 'exp-prompt-v2', 'exp-new-model'], baselineId: 'exp-baseline', }); // comparison.items[0].results has entries for all three experiments ``` - Fixed the `StorageMCPClientToolsConfig` and `StoredMCPClientToolsConfig` JSDoc. It previously said that omitting `tools` includes all MCP tools. For an agent-level MCP client reference, omitting `tools` selects no tools. Use `tools: {}` to include all tools. ([#26738](https://github.com/mastra-ai/mastra/pull/26738)) - `ExpectedStep` now supports `provider_tool_call` steps, so you can write typed trajectory expectations for provider-executed tools such as web search. The trajectory scorer compares their `toolArgs`, `toolResult`, and `success` fields. ([#26778](https://github.com/mastra-ai/mastra/pull/26778)) ```ts const steps: ExpectedStep[] = [ { name: 'web_search', stepType: 'provider_tool_call', toolArgs: { query: 'weather' } }, ]; ``` - Fixed `structuredOutput` so it only uses the final answer of a run. ([#26639](https://github.com/mastra-ai/mastra/pull/26639)) - Text an agent writes before calling a tool (like "Let me look that up.") no longer fails validation or ends the run early. - `stream.object` now resolves to the final answer, not an earlier JSON draft. - Durable agents now return the final answer instead of the first JSON they saw. ([#25038](https://github.com/mastra-ai/mastra/issues/25038)) ## 1.77.0-alpha.0 ### Minor Changes - Fixed observational memory losing context when a reflection, a buffered-observation write, and an observation activation overlap. The in-memory store now keeps buffered observations across a reflection, never lets activation drop a chunk written by someone else or move the observation cursor backward, and refuses observational memory lifecycle writes (buffered-observation appends, activation, active-observation commits, reflections) to a superseded generation. ([#25906](https://github.com/mastra-ai/mastra/pull/25906)) Storage gained two methods that report the outcome of a write, plus optional inputs, so callers can detect these cases: - `commitActiveObservations` writes active observations and returns `{ applied, reason }`. It accepts `expectedActiveObservations` and never writes to a superseded generation. - `appendBufferedObservations` appends a buffered chunk to the current generation and returns `{ persisted, recordId }`. - `createReflectionGeneration` and `swapBufferedReflectionToActive` accept `newRecordId`; the reflection applied when the returned record has that id. `swapBufferedToActive` reports `retired`. - Observational memory records carry `supersededBy`, set when a newer generation replaces them. **Before:** ```ts const memory = await storage.getStore('memory'); await memory.updateActiveObservations({ id: record.id, observations, tokenCount, lastObservedAt: new Date(), }); ``` **After:** ```ts const memory = await storage.getStore('memory'); const result = await memory.commitActiveObservations({ id: record.id, observations, tokenCount, lastObservedAt: new Date(), expectedActiveObservations: record.activeObservations, }); if (!result.applied) { // 'retired': a newer generation replaced this record. 'conflict': another writer changed it first. // Read the current record and observe again. } ``` `updateActiveObservations` and `updateBufferedObservations` keep returning `Promise`. In stores that implement the new methods, `updateActiveObservations` now throws when the observations were not written (the generation was superseded, or `expectedActiveObservations` no longer matches) instead of resolving as if they had been. An older `@mastra/memory` running against an upgraded store therefore reports a failed observation and observes the same messages again next turn, rather than marking them observed without saving their observations. Storage adapters that do not implement these keep their previous behavior. Upgrade `@mastra/core`, `@mastra/memory`, and your storage adapter together to get the full fix; every process writing to the same database needs the upgraded adapter. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`eaa9d12`](https://github.com/mastra-ai/mastra/commit/eaa9d12407f4962e94e0d5cd49a643e6e317d60a)) - Updated the `observation.blockAfter` documentation for Observational Memory: between `messageTokens` and `blockAfter` only background buffering and activation run, and a blocking observation runs at or above `blockAfter` when no buffered chunk activates. ([#22078](https://github.com/mastra-ai/mastra/pull/22078)) ## 1.76.0 ### Minor Changes - Concurrent schema-based working memory updates no longer lose each other's fields when using PostgreSQL. With `scope: 'resource'`, the update tool now deep-merges partial updates inside a row-locked transaction, so writers in separate processes that change different fields all keep their changes. Other storage adapters keep the existing in-process behavior. ([#25848](https://github.com/mastra-ai/mastra/pull/25848)) You can also merge from your own code or over HTTP: ```ts await memory.mergeWorkingMemory({ threadId, resourceId, workingMemory: { city: 'Berlin' } }); await client.updateWorkingMemory({ agentId, threadId, resourceId, workingMemory: JSON.stringify({ city: 'Berlin' }), mode: 'merge', }); ``` Merges are rejected (HTTP `400` from the server) when the storage adapter doesn't support atomic merges, instead of silently replacing the record. - `processLLMRequest` now receives the workspace of the step, the one the agent's tools use, so a processor can work with the agent's sandbox or filesystem before a model call. ([#25814](https://github.com/mastra-ai/mastra/pull/25814)) ```ts const processor: Processor = { id: 'my-processor', async processLLMRequest({ prompt, workspace }) { if (workspace?.hasSandboxConfig()) { // ... } }, }; ``` - Added `threadMetadata` to the `onDelegationStart` return value so delegated sub-agent threads can carry metadata such as a tenant ID. The metadata is applied when the sub-agent thread is created, which makes those threads findable with metadata-filtered queries. ([#25200](https://github.com/mastra-ai/mastra/pull/25200)) ```ts await supervisor.generate('Summarize the report', { memory: { thread: 'parent-thread', resource: 'user-1' }, delegation: { onDelegationStart: () => ({ threadMetadata: { tenantId: 'acme' } }), }, }); await memory.listThreads({ filter: { metadata: { tenantId: 'acme' } } }); ``` - Fixed durable agent crash recovery re-running runs that were still executing. Recovery restarted every run left in the `running` state, even when another process was still driving it. Both executions then wrote to the same thread, and the original one could overwrite the recovered assistant messages ([#23734](https://github.com/mastra-ai/mastra/issues/23734)). ([#26418](https://github.com/mastra-ai/mastra/pull/26418)) Each run is now claimed by the execution driving it: `stream()`, `generate()`, `resume()`, or `recover()`. The claim is renewed while the run executes and lapses about 30 seconds after its process dies. - `recoverActiveRuns()` skips runs whose claim is still live. Skipped runs are returned with `status: 'skipped'`, a `reason`, and, for a run live in another process, a `retryAt` time. - `mastra.recoverAllDurableAgents()` counts skipped runs in a new `skipped` field and checks each one again once its claim could have lapsed, so a run whose process crashed is still recovered. - `recover()` throws `DURABLE_AGENT_RUN_ACTIVE` for a live run. Pass `force: true` to take the run over. The execution that held it stops. - `recover()` throws `DURABLE_AGENT_RECOVER_RUN_ACTIVE_LOCALLY` for a run the same process is still executing, even with `force: true`. Previously it started a second execution of the run. Wait for the run to finish instead. - Starting a run with a `runId` that another execution holds waits up to 5 seconds, then throws `DURABLE_AGENT_EXECUTION_CONFLICT`. - `mastra.shutdown()` releases runs still executing after the drain window, so the next boot recovers them right away. - Run streams drop events from an execution that lost its run. With a storage adapter that supports run fencing, that execution's writes are also rejected, so it can't overwrite messages, threads, or workflow state. A warning is logged when the workflows or memory store doesn't support run fencing: whenever runs are recovered, through `recoverActiveRuns()` or `recover()`, and, with `recovery: { durableAgents: 'auto' }`, when a run starts. ```ts const { recovered } = await durableAgent.recoverActiveRuns(); for (const run of recovered) { if (run.status === 'skipped' && run.reason === 'run-active') { console.log(`${run.runId} is still running. Check again at ${new Date(run.retryAt!)}`); } } // Take a run over even though another execution still holds it const stream = await durableAgent.recover(runId, { force: true }); ``` See [Run ownership](https://mastra.ai/docs/harness/durable-agents#run-ownership) for how claims are enforced and how recovery works across multiple instances. - Added `tracingOptions.nestUnderParent` to attach a run as a child of an earlier run in the same trace, for example an LLM judge under the run it scores. The trace then keeps the name, input, output, and tags of the run that started it. The new `nestUnderRun()` helper builds these options from the earlier run's result. ([#25691](https://github.com/mastra-ai/mastra/pull/25691)) ```ts import { nestUnderRun } from '@mastra/core/observability'; const turn = await assistant.generate('When does my order ship?'); await judge.generate('Grade this answer', { tracingOptions: nestUnderRun(turn), // same as: { traceId: turn.traceId, parentSpanId: turn.spanId, nestUnderParent: true } }); ``` Runs that pass `parentSpanId` without the new option behave as before. - Added `getModelReasoningOptions` to read which reasoning controls a model accepts, as published by models.dev: named effort levels, a thinking-token budget, or an on/off toggle. Returns `[]` when models.dev lists the model without reasoning controls, and `undefined` when no data describes the model. ([#26225](https://github.com/mastra-ai/mastra/pull/26225)) ```ts import { getModelReasoningOptions } from '@mastra/core/llm'; getModelReasoningOptions('anthropic/claude-sonnet-4-6'); // [{ type: 'effort', values: ['low', 'medium', 'high', 'max'] }, { type: 'budget_tokens', min: 1024 }] getModelReasoningOptions('anthropic/claude-haiku-4-5'); // [{ type: 'budget_tokens', min: 1024 }] ``` Use it to offer only the reasoning levels a model supports instead of hard-coding them per model. - Agents now keep working when the model rejects a file the user sent. Before, the model call failed, and since the file stayed in the thread, every later turn failed the same way, even a text-only one. ([#25814](https://github.com/mastra-ai/mastra/pull/25814)) A new default error processor, `UnsupportedFileHandler`, takes the rejected file out of the prompt and calls the model again. When the agent's workspace has a sandbox, the file is uploaded there and the model gets its path, so the agent can still work on it with its tools. Otherwise, the model gets the `[Attachment unavailable: ]` placeholder Mastra already uses for attachments it can't use. Only the prompt changes: the stored message keeps the file, so a thread that already holds such a file works again on its next turn. It handles the refusals that provider SDKs, such as OpenAI's and Anthropic's, raise before the request goes out, and the errors a provider returns over HTTP for a request that holds such a file, like Gemini's `502`. A rate limit or another error that asks to try again later keeps the file: the request is sent again unchanged. Every agent gets it, alongside the other default error processors, and `errorProcessorDefaults: false` turns it off with them. Uploaded files can be read by other threads that share the same sandbox. - Rearchitected an experimental memory feature. ([#26024](https://github.com/mastra-ai/mastra/pull/26024)) - Agents using the default error processors now recover from Gemini's intermittent `MALFORMED_FUNCTION_CALL` finish instead of failing the turn with `AGENT_STREAM_ERROR`. The default `stream-error-retry-processor` resends the failed step unchanged up to 2 times with a short backoff. No tool runs on that step, so the retry cannot repeat side effects. Durable agents are not covered yet. ([#26612](https://github.com/mastra-ai/mastra/pull/26612)) The matcher is exported as `isMalformedFunctionCallError`, so a custom `StreamErrorRetryProcessor` can reuse it: ```ts import { StreamErrorRetryProcessor, isMalformedFunctionCallError } from '@mastra/core/processors'; const agent = new Agent({ // ... errorProcessors: [new StreamErrorRetryProcessor({ matchers: [isMalformedFunctionCallError], maxRetries: 2 })], }); ``` - Added `FileUploadProcessor`, an input processor that uploads the files a user sends to the workspace sandbox and gives the model the sandbox path instead of the file content. ([#25814](https://github.com/mastra-ai/mastra/pull/25814)) Use it when an agent works on user files with its sandbox tools. A `filter` function decides, file by file, what is uploaded; the files it rejects, such as images, keep going to the model. Only the prompt sent to the model changes: the stored messages keep their files, so the thread and clients still have the original. Memory is optional. Files are named after a hash of their content, so a file is written once and the next turns only check that it's still in the sandbox. Files of the thread history and files sent with signals are handled too. The processor never downloads anything itself: a link the model fetches, or a provider file ID, is left to the model. Pass the same workspace to the agent and to the processor: ```ts import { FileUploadProcessor, FILE_UPLOAD_ERROR_CODES } from '@mastra/core/processors'; import type { FileUploadTripwireMetadata } from '@mastra/core/processors'; const agent = new Agent({ // ... workspace, inputProcessors: [ new FileUploadProcessor({ workspace, filter: ({ mimeType, extension }) => mimeType === 'application/pdf' || extension === 'xlsx', maxFileSize: () => 25 * 1024 * 1024, }), ], }); const result = await agent.generate(messages); if (result.tripwire?.processorId === 'file-upload') { const { code } = result.tripwire.metadata as FileUploadTripwireMetadata; if (code === FILE_UPLOAD_ERROR_CODES.FILE_TOO_LARGE) { // ... } } ``` When a file of the current turn can't be uploaded, the processor stops the turn and reports why in the tripwire metadata. `FILE_UPLOAD_ERROR_CODES` lists every code. A file of the thread history that can't be uploaded is replaced by a note in the prompt instead, so it never blocks the thread. - Added `outputOptions` to `run.restart()`, matching `run.start()` and `run.resume()`. Set `includeState: true` to get the final workflow state in the result. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) ```typescript const result = await run.restart({ outputOptions: { includeState: true } }); console.log(result.state); ``` - Added run fencing to the storage API, so storage adapters can reject writes from a durable agent execution that lost its run to crash recovery. ([#26418](https://github.com/mastra-ai/mastra/pull/26418)) - Workflows stores have new `claimRunOwnership()`, `renewRunOwnership()`, `releaseRunOwnership()`, and `getRunOwnership()` methods. - Memory stores have new `raiseRunFence()` and `retireRunFence()` methods. - Methods that write run data, such as `saveMessages()`, `saveThread()`, `persistWorkflowSnapshot()`, and `updateWorkflowState()`, accept an optional `fence`. A write whose fence is no longer the run's current claim throws `RunFenceConflictError`. - `supportsRunFencing()` reports whether a store implements this. It can return a promise, for stores that must probe the backend first, and should reject when the store can't tell yet. It defaults to `false`, so custom storage adapters keep working without changes, and durable agents fall back to the PubSub lease for them. - A run is fully fenced only when both the workflows store and the memory store it writes to support fencing. If only the workflows store does, for example because a composite store takes memory from another adapter or the agent's `Memory` has its own storage, an execution that lost the run can't write workflow state anymore, but its messages, threads, and working memory still land. If only the memory store does, nothing is fenced. - Stores that support fencing keep one ownership record per run, plus one memory fence record per run with memory. Stores with retention support prune them with the new `workflows.runOwnership` and `memory.runFences` retention keys. Ownership records age out by inactivity, and fence records once the execution holding them settles. A run's first generation is taken from the store's clock, so a run that reuses a `runId` after its records were pruned still claims a higher generation than its earlier executions. **Pruning run fencing records** ```ts const storage = new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db', retention: { workflows: { runOwnership: { maxAge: '1d' } }, memory: { runFences: { maxAge: '1d' } }, }, }); // From your own scheduler await storage.prune(); ``` **Writing with a fence** ```ts import { RunFenceConflictError } from '@mastra/core/storage'; const workflows = await storage.getStore('workflows'); const claim = await workflows.claimRunOwnership({ runId, ownerId: 'worker-a', leaseMs: 30_000 }); if (claim.acquired) { const fence = { runId, ownerId: 'worker-a', generation: claim.record.generation }; try { await workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot, fence }); } catch (error) { if (error instanceof RunFenceConflictError) { // Another execution claimed the run since. Nothing was written. } } } ``` **Opting in from a custom adapter** ```ts import { RunFenceConflictError, resolveRunFence, WorkflowsStorage } from '@mastra/core/storage'; class MyWorkflowsStorage extends WorkflowsStorage { supportsRunFencing() { return true; } // Also implement claimRunOwnership(), renewRunOwnership(), releaseRunOwnership() and getRunOwnership(). async persistWorkflowSnapshot({ workflowName, runId, snapshot, fence }) { // The fence passed in, or the one of the durable agent execution making this write const current = resolveRunFence(this, fence, runId); // In one transaction: if `current` is set and is no longer the run's claim, // throw new RunFenceConflictError(current, 'persistWorkflowSnapshot'). Otherwise write. } } ``` - Added `matches` and `notMatches` operators to advanced trace queries. They match whole words or phrases, ignoring case, on span `name` and feedback `comment`. ([#25111](https://github.com/mastra-ai/mastra/pull/25111)) ```ts { feedback: { some: { op: 'matches', left: { path: 'comment' }, right: { literal: 'incorrect dosage' } } } } ``` - Faster, configurable workflow recovery on server boot. ([#26607](https://github.com/mastra-ai/mastra/pull/26607)) - Active workflow runs now restart in parallel (up to 5 at a time by default) instead of one by one, and durable agent recovery no longer waits for the workflow sweep. - New `recovery.workflows: 'off'` turns off boot-time workflow restarts for the whole instance; `recovery.workflowConcurrency` sets the parallelism (`Infinity` for no limit). - `workflow.restartAllActiveWorkflowRuns()` also restarts runs concurrently, capped by `recovery.workflowConcurrency`. Set it to `1` if runs share side effects that must not overlap. ```ts new Mastra({ recovery: { workflows: 'auto', workflowConcurrency: 10 }, }); ``` - Added an optional `usageId` field to exported metrics. Every token and cost metric produced from the same model usage shares one `usageId`, so storage can group a model call's metric rows without relying on the span. Other metrics leave it unset. ([#26461](https://github.com/mastra-ai/mastra/pull/26461)) - AgentController sessions now support automatic observational memory model selection per role. `AgentControllerOMConfig.observerModel`/`reflectorModel` and `session.om..switchModel({ modelId })` accept `'auto'` to follow the active main model, and `resolveAutoModelId` lets a consumer decide which concrete model an automatic role uses. `session.om..model()` reports the role's configured intent (`'auto'`, a model ID, or `undefined`) while `modelId()` keeps returning the effective concrete model. `defaultObserverModelId`/`defaultReflectorModelId` remain the concrete fallback, and persisted explicit selections are unchanged. ([#24508](https://github.com/mastra-ai/mastra/pull/24508)) Dynamic model functions can now return a labeled model, `{ model, id }`, so code that only sees the resolved model object still knows its full `provider/model` ID. Resolved models expose that label as a read-only `id`, and models created from a plain router ID set it automatically. ```ts const agent = new Agent({ model: ({ requestContext }) => ({ model: createMyModel(requestContext), id: 'openai/gpt-5.6-sol' }), }); ``` ### Patch Changes - Fixed aborted suspended runs staying listed as suspended. When `session.abort()` cancels a run that is parked in a tool `suspend()` (for example `ask_user`), the run's in-memory registration and its workflow snapshot rows are now released, so `agent.listSuspendedRuns()` no longer returns it and memory no longer grows with each aborted run. This applies to both regular and durable agents. ([#26181](https://github.com/mastra-ai/mastra/pull/26181)) - AgentController sessions now forward the agent's `isTaskComplete` verdict as a live `task_complete_evaluation` event, so web clients can show "the check failed, the agent is fixing it" right away. `@mastra/client-js` recognizes the new event type. ([#26185](https://github.com/mastra-ai/mastra/pull/26185)) The session stream also sends the current display state, including the message being streamed, as its first event. A page that opens or reloads mid-run now sees the in-flight assistant text instead of waiting until it is stored. `currentMessage` can hold a message that is already stored (for example between steps or after a run), so clients should merge it by message id rather than append it. - Fixed retried API errors leaking the failed attempt's partial text. ([#26654](https://github.com/mastra-ai/mastra/pull/26654)) A provider could stream some text, fail mid-stream, and then succeed when an error processor retried. The final answer came out as the failed prefix followed by the recovered answer, for example `"The answer is The answer is 42."`. The retry request also sent the failed prefix back to the model. The failed attempt's partial text is now discarded, for both `Agent` and durable agents. The final `text` and the retry prompt no longer include it, and neither do `response.messages` and stored messages. Steps that completed before the failed attempt are kept. Text chunks already streamed live from the failed attempt still reach `textStream` and `fullStream` consumers. The step that carries them finishes with reason `retry`. - Keep Studio and Factory sessions alive for the identity provider's full session length. ([#26447](https://github.com/mastra-ai/mastra/pull/26447)) - `MastraAuthStudio` session cookies now last 14 days by default (was a hardcoded 24 hours), configurable via the new `sessionMaxAgeSeconds` option or the `MASTRA_SESSION_MAX_AGE` environment variable. - When the shared API renews the session during verification, `MastraAuthStudio` re-issues the renewed cookie under the deployment's own cookie domain and exposes it through a new optional `consumePendingResponseHeaders` provider hook. - `@mastra/server`'s auth middleware, `CompositeAuth`, the Factory auth gate, and Factory's per-route `ensureFactoryAuthUser` (used by routes declared `requiresAuth: false`, which skip the gate) forward those headers to the browser, as does the public `GET /auth/me` route. Forwarding is best-effort and never fails a request. ```ts import { MastraAuthStudio } from '@mastra/auth-studio'; // Defaults to 14 days. Override per deployment, or set MASTRA_SESSION_MAX_AGE (seconds). const auth = new MastraAuthStudio({ sessionMaxAgeSeconds: 7 * 24 * 60 * 60 }); ``` - Update provider registry and model documentation with latest models and providers ([`2233844`](https://github.com/mastra-ai/mastra/commit/223384452984718e16fc660d29f0d5d93a1ebaf2)) - Fixed concurrent durable agent runs saving each other's conversation. When runs of the same durable agent (or parent workflows sharing a nested workflow) started at the same time, a nested run's first saved snapshot could hold another run's input and state. Listing runs for one resource could return another run's conversation while it was in progress, and recovering a run that crashed before its first step continued as the other run: it sent that conversation to the model and saved the reply to the other run's thread. Each nested run now saves its own input and state. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed evented workflows losing state changes after a restart. When a workflow run on the evented engine was restarted after a crash, steps resumed with the state from the run's start (or last suspension) instead of the state produced by the steps that already finished, so any `setState` updates made since then were lost. The workflow state is now recorded together with each step's result, and restarts resume from it. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed DurableAgent streams with `closeOnSuspend: true` closing before the suspended run was saved. Approving a tool call right after the stream ends now works from any process, instead of sometimes failing with "This workflow run was not suspended". Fixes #26454. ([#26556](https://github.com/mastra-ai/mastra/pull/26556)) - Faster startup when restarting active workflow runs. Mastra no longer scans storage for workflows that set `autoRestartActiveRuns: false`. It also skips the internal workflows that run agent processors, since those only run during an agent call. ([#26588](https://github.com/mastra-ai/mastra/pull/26588)) - Fixed the `messageFilter` and `onDelegationStart` delegation types to match the messages they actually receive. Both now receive AI SDK model messages (`ModelMessage[]`) — the processor-adjusted prompt the supervisor model saw, including system messages, with tool calls removed — and `messageFilter` returns `ModelMessage[]`. Previously they were typed as stored database messages, so filters reading fields like `content.parts` or `id` could silently let parent context through. Update filters to read `role` and `content`. Fixes [#25983](https://github.com/mastra-ai/mastra/issues/25983). ([#26161](https://github.com/mastra-ai/mastra/pull/26161)) - Fixed `UnixSocketPubSub` leaving lease files behind when a lease release was followed immediately by `close()`. The release was aborted mid-way, so a thread claim released during shutdown could stay on disk until another process noticed the holder had exited. A release that starts before `close()` now completes, and `close()` waits for it, for up to one second if another process holds the lease's lock. New lease operations after `close()` still fail. ([#26292](https://github.com/mastra-ai/mastra/pull/26292)) - Fixed output-stream processing for durable tool results after a restart or cleanup, including Inngest resumes. Restored processors receive the request context, and concurrent cold calls share the published processor pipeline and state. When the agent resolves but its processor pipeline is missing, processor reconstruction or cold-worker dependency-resolution failures (tools, memory, and workspace) stop the step instead of exposing unprocessed output. Unregistered agents, persistence-only callers, and complete live pipelines retain their dependency-resolution fallback. Fixes #26148. ([#26367](https://github.com/mastra-ai/mastra/pull/26367)) - Fixed Inngest tool resumes after a worker restart or cleanup to emit tool-call-resumed before tool-result, preserving configured display transforms. ([#26370](https://github.com/mastra-ai/mastra/pull/26370)) - `Classifier` now supports AI SDK decision models. Pass a model from a provider's `decisionModel()` factory and Mastra calls `doDecide()`. Models that only implement the deprecated `doEvaluate()` contract still work. `ClassifierProcessor`, `ModelSelectionProcessor`, and classifier scorers accept the same models. ([#26279](https://github.com/mastra-ai/mastra/pull/26279)) To match the AI SDK naming, `Classifier.decide()` replaces `Classifier.evaluate()` and `MastraDecisionModel` replaces `MastraEvaluationModel`. The old names are deprecated aliases and will be removed in a future release. `EvaluationModelResult`, `MastraEvaluationModelInterface`, `ConfiguredClassifierEvaluateOptions`, and `PerCallClassifierEvaluateOptions` are likewise deprecated in favor of their `Decision`/`Decide` counterparts. ```ts import { openai } from '@ai-sdk/openai'; import { Classifier } from '@mastra/core/classifier'; const classifier = new Classifier({ id: 'support-router', model: openai.decisionModel('gpt-6-luna'), questions: { urgent: { type: 'boolean', instructions: 'Is this request urgent?' }, }, }); const result = await classifier.decide({ state: 'My order never arrived' }); ``` - Fixed tool calls failing validation when the model's arguments needed more than one automatic fix. Tool calls that send lists or objects as text, send `null` for optional fields, or use `query`, `message`, or `input` instead of `prompt` now validate even when several of these happen in the same call. For example, `{ args: '["a.py"]', note: null }` is now accepted. ([#25859](https://github.com/mastra-ai/mastra/pull/25859)) - Fixed plain agents emitting awaited background tool results twice. ([#26344](https://github.com/mastra-ai/mastra/pull/26344)) - Fixed durable and evented agents so they no longer execute tools excluded by `prepareStep`. ([#26339](https://github.com/mastra-ai/mastra/pull/26339)) - Fixed three durable agent bugs: ([#26583](https://github.com/mastra-ai/mastra/pull/26583)) - Aborting a suspended run with `abortThreadStream()`, `abortRunStream()` or the stream result's `abort()` now removes it from `listSuspendedRuns()`. Previously the run stayed listed as suspended after the abort. This also applies to runs waiting for tool approval: aborting them now releases the run instead of leaving it for a later approve or decline. - Client tool spans on Inngest agents are now parented to the exported agent run span instead of a span that was never exported. - Inngest agent streams no longer hang when an event exceeds the Realtime message size limit. Oversized `finish` events are sent without the step history, and oversized `error` events keep the error name and a shortened message so `onError` still fires. Oversized non-terminal events are replaced with a `data-oversized-event` chunk so clients know an event was reduced, and a warning is logged through the Mastra logger. - Reduced how much a long durable agent run writes to storage, and made runs in flight at shutdown recoverable ([#26600](https://github.com/mastra-ai/mastra/issues/26600)). ([#26642](https://github.com/mastra-ai/mastra/pull/26642)) - **Smaller checkpoints:** a durable run's checkpoints no longer repeat messages the run has already saved to memory (for example with `savePerStep: true`). They store a small reference to the memory row instead, as recalled messages already did. This covers every copy of the conversation in a checkpoint, including the run input and the final step results. Messages that change after being saved stay inline. This applies to agents with a fixed memory instance. - **Checkpoint on shutdown:** with the default snapshot policy and `recovery.durableAgents: 'off'`, a durable run still in flight when `mastra.shutdown()` starts now persists a `running` checkpoint at each step boundary it reaches during the drain. If it reaches one before the drain ends, it can be resumed later with `recover()` or `recoverActiveRuns()` instead of being lost. ```typescript // Process A: a run is still in flight when the process shuts down. await mastra.shutdown({ drainTimeout: 5_000 }); // Process B: resume every run checkpointed during the drain. await mastra.recoverAllDurableAgents(); // or per agent: await durableAgent.recoverActiveRuns(); ``` - Fixed a durable agent thread staying busy forever when `cleanup()` was called before the run finished. Cleanup now waits for the run to settle, so `onFinish`/`onError` fire and the thread is released for the next request. Fixes #25974. ([#26587](https://github.com/mastra-ai/mastra/pull/26587)) - Fixed `sendSignal(..., { ifIdle: { behavior: 'wake' } })` on a DurableAgent so the accepted result's `output.text` now resolves to the generated text, the same as with a regular Agent. Previously it resolved to `undefined`. ([#26564](https://github.com/mastra-ai/mastra/pull/26564)) ```ts const { accepted } = durableAgent.sendSignal(signal, { resourceId, threadId, ifIdle: { behavior: 'wake' } }); const result = await accepted; await result.output?.text; // now the generated text ``` - Fixed durable agents ending a turn without an answer when Gemini returns a malformed tool call (`MALFORMED_FUNCTION_CALL`). Durable agents now retry the request like regular agents do, and report an error if the retries fail. ([#26636](https://github.com/mastra-ai/mastra/pull/26636)) - Fixed durable agents running the model call outside the model step span context. `getCurrentSpan()` inside AI SDK model middleware and span-correlated logs now resolve to the model span for durable agents, matching regular agents. ([#26184](https://github.com/mastra-ai/mastra/pull/26184)) - Fixed durable agents saving each step of a turn as a separate message. A multi-step turn is now saved as one message, as with `Agent`. Conversation history now holds more turns, and the streamed message ID matches the saved message. ([#26586](https://github.com/mastra-ai/mastra/pull/26586)) - Fixed durable agents, including Inngest agents, ignoring an `abort()` from an output processor's `processOutputResult`. The run now reports the tripwire with `finishReason: 'other'`, as a regular `Agent` does. The rejected answer is no longer saved to memory. ([#26178](https://github.com/mastra-ai/mastra/pull/26178)) - Fixed durable agents saving raw model output after `processOutputStream` changed or removed streamed text. Stored text now matches the stream, as it does for regular agents. Redacted values no longer reappear on reload or reach the model on a later turn. ([#26559](https://github.com/mastra-ai/mastra/pull/26559)) - Fixed `DurableAgent.recover()` dropping the agent's tools when a run was interrupted during a model request. The recovered model call now gets the same tools as the original run, so it can still call them instead of only replying with text. Fixes #25890. ([#26535](https://github.com/mastra-ai/mastra/pull/26535)) - Fixed two durable agent bugs: ([#26585](https://github.com/mastra-ai/mastra/pull/26585)) - `Mastra.shutdown()` now closes the pubsub, so open Redis connections no longer keep the process running after shutdown. - Read-only memory is now respected when a durable run finishes from a saved snapshot, so read-only turns are no longer saved. - Fixed durable and evented agents not saving the structured output object to memory. The saved assistant message now includes `metadata.structuredOutput`, matching regular agents, so reconnecting clients and recovered runs can read the object. ([#26452](https://github.com/mastra-ai/mastra/pull/26452)) As with regular agents: - Truncated responses (finish reason `length` or `content-filter`) and responses that fail validation do not save `metadata.structuredOutput`. The response message itself is still saved. - With `errorStrategy: 'fallback'`, the `fallbackValue` is saved instead. This does not yet apply when `structuredOutput.model` is set to a separate structuring model (see [#26431](https://github.com/mastra-ai/mastra/issues/26431)). Fixes [#26432](https://github.com/mastra-ai/mastra/issues/26432). - Fixed durable agents ignoring `structuredOutput.model`. The separate structuring model now runs when the turn finishes, so `output.object` resolves and `metadata.structuredOutput` is saved on the assistant message, matching `Agent`. ([#26571](https://github.com/mastra-ai/mastra/pull/26571)) - Fixed durable agent runs that could not be resumed after a crash right after a tool suspended or asked for approval (#26435). The `tool-call-suspended` and `tool-call-approval` chunks are now published only after the suspended snapshot is saved, so a client that sees the question can always resume the run. ([#26589](https://github.com/mastra-ai/mastra/pull/26589)) Detaching an `AgentController` session from a thread no longer declines and deletes a parked durable run, so another session or process can still resume it. - Fixed `DurableAgent.recover()` losing tools that `ToolSearchProcessor` loaded during the run. After a crash, recovery in a fresh process now reads the run's latest transcript and restores those tools, so the model's pending tool call runs once instead of failing with `ToolNotFoundError` and being retried under a new call id. ([#26592](https://github.com/mastra-ai/mastra/pull/26592)) - Fixed durable agents (such as Inngest agents) silently dropping call-time `toolsets` when the worker runs in a separate process. Toolset tools contain server-side code that can't be serialized, so the worker now throws an error naming the missing tools instead of running without them. To use these tools on a separate worker, register them on the agent (statically or via `requestContext`). ([#25860](https://github.com/mastra-ai/mastra/pull/25860)) - Fixed `DurableAgent` `abort()` not stopping in-flight tool or model calls when `modelSettings.timeout.totalMs` is set and the run's registry entry had been evicted under heavy concurrency. ([#26628](https://github.com/mastra-ai/mastra/pull/26628)) - Fixed agent signal delivery errors being cut off at the resource id. Messages such as "No claimed thread owner responded" now name the thread and resource in full, so the whole error is stored and shown. ([#26292](https://github.com/mastra-ai/mastra/pull/26292)) - Fixed evented workflows and evented agents storing the caller's auth token (`mastra__authToken`) in workflow snapshots. The token is now stripped before every snapshot write, matching the default workflow engine; steps still receive the live token while the run executes, and resumed runs use the token from the resuming request instead of any token left in older snapshots. Fixes #26217. ([#26638](https://github.com/mastra-ai/mastra/pull/26638)) - Fixed durable agents on the evented engine running the process out of memory on long runs. The engine persisted every completed step's full `payload` and `output` — including the whole, monotonically growing iteration state — because its step-result merge writes bypassed the workflow's `pruneSnapshot` hook, so snapshots and per-write heap cost grew without bound. The engine now prunes after each non-parallel step, and the durable loop no longer retains all completed-step history now that its same-iteration reads are declared. ([#26553](https://github.com/mastra-ai/mastra/pull/26553)) - Fixed evented workflows returning a stale result from `run.resume()` on persistent pubsubs such as `@mastra/redis-streams` and `@mastra/valkey-streams`. Awaited `start()`, `resume()`, `restart()` and time-travel calls now only read finish events published after the call, so `resume()` returns the resumed result instead of the earlier `suspended` one, and waiting no longer re-reads the whole `workflows-finish` stream. ([#26300](https://github.com/mastra-ai/mastra/pull/26300)) - Fixed messages queued during an active agent run being dropped when that run is aborted. Queued messages now start their own run after the abort instead of inheriting the aborted run's cancellation. ([#26280](https://github.com/mastra-ai/mastra/pull/26280)) - `execute_command` no longer tells the agent an aborted command was killed unless the sandbox confirms it (`killed: true`). When the provider omits kill metadata or throws after the run is cancelled, the tool now notes that the run was cancelled and keeps the provider's error, so agents are not falsely assured that remote work stopped. ([#26606](https://github.com/mastra-ai/mastra/pull/26606)) - Exported `ServerConfig` from `@mastra/core/server` and `OPENSEARCH_PROMPT` from `@mastra/opensearch` so the documented imports resolve. Fixed JSDoc import paths in `@mastra/evals` to use `@mastra/evals/scorers/prebuilt` and `@mastra/evals/scorers/utils`. ([#26440](https://github.com/mastra-ai/mastra/pull/26440)) - Fixed MCP App tools not being detectable by AI SDK UI hosts. For tools whose MCP server exposes an app UI (`mcp._meta.ui.resourceUri`), `toAISdkStream` now sets `toolMetadata.app` (`resourceUri`, `mimeType: 'text/html;profile=mcp-app'`, and `serverId` when available) on `tool-input-start` and `tool-input-available` chunks, so hosts can render MCP Apps without a custom stream transform. Fixes #25892. ([#26352](https://github.com/mastra-ai/mastra/pull/26352)) - Fixed foreign durable run lookups mutating the local registry and breaking shutdown. ([#26189](https://github.com/mastra-ai/mastra/pull/26189)) - Fixed channel bot tokens and other internal Mastra state leaking into traces and score records ([#25893](https://github.com/mastra-ai/mastra/issues/25893)). ([#26243](https://github.com/mastra-ai/mastra/pull/26243)) Agents with `channels` configured exported the live Telegram/Slack adapter on every span's `requestContext`, including bot and app tokens, and added hundreds of KB to each span. Delegated runs exported the parent agent's memory instance, and agent-controller runs walked the whole workspace. **What changed** - Reserved Mastra request-context keys (those starting with `mastra__` or `__mastra_`) are no longer exported on spans or saved on score records. The auth token is now omitted instead of shown as `[REDACTED]`. Thread and resource IDs are still recorded as span metadata. - Workspaces now show as `{ id, name, status }` in traces and score records instead of exposing their configuration and providers. Score records also use `serializeForSpan()` for any other object that defines it. - Request-context keys without a reserved prefix keep their existing representation. If you set your own keys starting with `mastra__` or `__mastra_`, rename them to keep them in traces. - Corrected the documented defaults for `SerializationOptions.maxStringLength` (131072) and `maxDepth` (8) to match the runtime defaults. ([#26425](https://github.com/mastra-ai/mastra/pull/26425)) - Fixed tools running with empty input when using `agent.stream()`. Some providers, like OpenAI Responses and Anthropic programmatic tool calling, send tool arguments only at the end of a tool call. Tools now wait for the complete arguments before they run. Tools with required inputs no longer fail validation in this case. ([#26251](https://github.com/mastra-ai/mastra/pull/26251)) - Experimental durable and evented agents now restart a model request that has only produced reasoning when a new message or signal arrives, so the agent answers with the new input instead of finishing a stale response. This matches the default agent. ([#25878](https://github.com/mastra-ai/mastra/pull/25878)) - The restarted request keeps the same step: it doesn't use up `maxSteps` or add token usage, and the discarded reasoning stays out of history. - Input processors always finish; only the model request is cancelled. - In traces, the cancelled request's step and inference spans end with finish reason `interrupted`. - Reduced storage used by agent runs waiting on tool approval or a suspended tool. Each suspended snapshot now stores the conversation one fewer time, cutting snapshot size by about 29% in a 12-approval run. Suspended runs resume exactly as before. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed follow-up signals being silently dropped when the process that forwarded them to another thread owner later took over that thread. Previously, the retained signal was treated as the process's own echo and ignored. Signals the process queued locally are still deduplicated. ([#25847](https://github.com/mastra-ai/mastra/pull/25847)) - Fixed Inngest durable agents persisting the agent's system prompt in every step result of workflow snapshots. Inngest agent workflows now apply the same snapshot pruning as the core `DurableAgent`, so instructions are only kept on the run input. `pruneAgentLoopSnapshot` is now exported from `@mastra/core/agent/durable`. ([#26152](https://github.com/mastra-ai/mastra/pull/26152)) ```ts import { pruneAgentLoopSnapshot } from '@mastra/core/agent/durable'; import { createWorkflow } from '@mastra/core/workflows'; const workflow = createWorkflow({ id: 'my-agent-loop', inputSchema, outputSchema, options: { pruneSnapshot: pruneAgentLoopSnapshot }, }); ``` - Fixed cross-process events going missing after a process restarted. Processes that share a socket, such as Mastra Code sessions, could split into separate groups that never received each other's events. A message sent from one session to another could then fail to arrive until every process restarted. A restart, a busy machine briefly refusing connections, or a deleted socket file could cause the split. ([#26292](https://github.com/mastra-ai/mastra/pull/26292)) - Refused connections are retried before a process takes over the socket. - A broker removes the socket path only while it still points at its own socket. - A broker that loses its socket path rejoins the shared group automatically. - Fixed durable and evented runs to filter internal messages before saving them to memory. ([#26186](https://github.com/mastra-ai/mastra/pull/26186)) - Durable and evented agents now include `runId` in `onStepFinish` and `onFinish`, matching the regular Agent. Use `runId` to tell which run an event came from when callbacks are shared across runs. ([#26531](https://github.com/mastra-ai/mastra/pull/26531)) `onFinish` also now includes `model`, `messages`, `object`, `error` and `usedFallbackValue`. You can read the structured output and response messages directly in the callback. Fixes #26524. - Fixed agent runs that never finished when a processor tripwire stopped the stream. The stream output now reports status `tripwire`, and Agent Controller sessions end the run with an error that includes the processor's reason instead of staying busy forever. ([#26142](https://github.com/mastra-ai/mastra/pull/26142)) - Fixed queued messages and signals arriving too late during reasoning in the default agent loop. Reasoning-only model requests are cancelled and restarted with pending input in the same run, without keeping discarded reasoning in model history or interrupting text and tool output. Interrupted requests reuse the same logical step without consuming maxSteps, advancing stepNumber, or contributing token usage. The run's `reasoning`, `reasoningText` and `getFullOutput()` exclude discarded reasoning, and tracers can close the cancelled request's inference span through the optional `interruptInference()` tracker method. ([#25861](https://github.com/mastra-ai/mastra/pull/25861)) - Fixed evented workflows dropping state changes made inside a nested workflow that runs as a `.dowhile()` or `.dountil()` loop body. Each iteration started from the state the loop had before the nested workflow ran, so `setState` updates from the loop body were lost and later steps never saw them. The nested workflow's final state now carries into the next iteration and the steps after the loop, matching the default engine. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Reduced storage used by agent runs that recall conversation history from memory. Saved run state now stores recalled messages as references to the stored messages instead of full copies. When the run continues, the messages are loaded back from memory. ([#26222](https://github.com/mastra-ai/mastra/pull/26222)) In a 12-step run that recalled 20 messages, the saved run state was about 25% smaller for durable agents and about 21% smaller for agents waiting on tool approval. This applies to agents configured with a memory instance. Agents whose memory is a function, or that use memory inherited from a supervisor agent, keep full copies of recalled messages, because a resumed run may not pass the request context that memory was chosen from. **Loading recalled messages back.** If a recalled message is edited while a run is suspended, the resumed run uses the stored version. If it is deleted, the resumed run drops it. Both are logged at debug level. When a run is resumed or recovered, the messages are loaded before anything runs, so if memory storage can't load them, the call (for example `approveToolCall` or `recover`) throws and the run stays as it was, so you can retry it. A durable agent also loads them in the middle of a run when its process has no cached copy: when a step runs on a different worker (evented engine with a distributed pubsub), after more than 10 minutes between steps (for example, a slow tool call), or when more than 1,000 durable runs are in progress in one process. A memory storage failure at that point is retried for about 10 seconds, with each retry logged as a warning. If memory storage is still failing after that, the run fails. **Storage reads.** Runs that store recalled messages as references look them up in memory storage by id, once when first storing them and again whenever they are loaded back as described above. Cloudflare KV storage finds a message by id by checking every thread, so on KV each lookup reads every thread once per recalled message. **Upgrading and downgrading.** Runs started on an earlier version still resume and finish normally. Runs started on this version can't be resumed after downgrading to an earlier `@mastra/core` version. - Fixed `PIIDetector` sending a message's original text to the model when redaction left nothing usable. With `strategy: 'redact'`, a message is now dropped instead of passed through unredacted when redaction leaves no text or only whitespace (for example an SSN-only message with `redactionMethod: 'remove'`), or when PII is flagged with no usable spans. This applies to `processInput` and `processOutputResult`. Fixes #25553. ([#26209](https://github.com/mastra-ai/mastra/pull/26209)) - Reduced storage used by durable agent runs. The conversation transcript is now saved once per workflow snapshot instead of being copied into every step's input and output. In a 12-iteration tool-calling run this cut persisted snapshot size by about 31% on the default engine and 63% on the evented engine. Runs started on an earlier version still resume and finish normally. Runs started on this version can't be resumed after downgrading to an earlier `@mastra/core` version. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed subagents silently returning an empty result when they call a client tool (a tool without `execute`). Subagents can't run client tools, so their run stops at that tool call. Previously the supervisor received empty text and `subAgentToolResults: []` with no indication why. Now the delegation result includes `subAgentPendingToolCalls` listing the unresolved calls, the supervisor model is told which tools were never run, and a warning is logged. ([#26257](https://github.com/mastra-ai/mastra/pull/26257)) ```ts const supervisor = new Agent({ // ... agents: { shopper }, defaultOptions: { delegation: { onDelegationComplete: ({ result }) => { if (result.subAgentPendingToolCalls) { // e.g. [{ toolName: 'add-to-cart', toolCallId: '...', args: { productId: 'sku-1' } }] } }, }, }, }); ``` Give client tools to the supervisor, or use `suspend()` in the subagent's tool when it needs input from the user. - Fixed a memory leak where agents with sub-agents, workflows, or `autoResumeSuspendedTools` kept growing the heap on every tool conversion (each `generate`/`stream` call and agent listing). Resume fields injected into tool input schemas are now created once instead of per conversion, so they no longer pile up in Zod's global schema registry when `zod@3.25.x` is installed. Fixes #26160. ([#26167](https://github.com/mastra-ai/mastra/pull/26167)) - Fixed duplicated assistant messages after a suspended tool or sub-agent call resumes. Each earlier reply and tool call now appears only once in saved conversation history. Fixes #26150. ([#26163](https://github.com/mastra-ai/mastra/pull/26163)) - Agent runs whose model stream stops without a finish reason now end with an error instead of being reported as complete. ([#26226](https://github.com/mastra-ai/mastra/pull/26226)) - Fixed agents being told to call `updateWorkingMemory` when `workingMemory.agentManaged` is `false`. The tool is not available in that mode, so agents now receive the read-only working memory instruction instead (#25896). ([#26198](https://github.com/mastra-ai/mastra/pull/26198)) - Fixed `resumeStream` ignoring the `requestContext` and `delegation` you pass when a supervisor run is resumed in the same process that suspended it. The resumed delegation now calls the `onDelegationStart` hook from the resume call, and sub-agent tools see the resume call's `requestContext`. `DurableAgent` tools approved with `approveToolCall` in the same process now also run with the request context passed to that call. Fixes [#25960](https://github.com/mastra-ai/mastra/issues/25960). ([#26686](https://github.com/mastra-ai/mastra/pull/26686)) - Setting `toolChoice: 'none'` now keeps earlier tool calls and results in the conversation, so the model can give a final text answer based on them. Amazon Bedrock no longer drops that tool history or rejects extended-thinking blocks. ([#26232](https://github.com/mastra-ai/mastra/pull/26232)) ```ts prepareStep: ({ stepNumber }) => (stepNumber >= 3 ? { toolChoice: 'none' } : undefined); ``` Tools are still left out when the step sends a structured-output schema as the response format, because some providers (such as Gemini) reject tools combined with it. - Fixed three bugs in the in-memory storage adapter so it behaves like the database adapters: ([#26418](https://github.com/mastra-ai/mastra/pull/26418)) - `listMessages` now reports `hasMore` correctly when `include` is combined with pagination or several threads. Before, it could report no more pages while filtered messages remained, or report more pages when `include` had already returned every message. - Saving an existing channel installation again no longer resets its `createdAt`. - `batchDeleteTraces` with `organizationId` or `resourceId` now deletes only that tenant's spans. Before, it removed the whole trace or nothing, depending on which root span was written last. - Added `closeOnSuspend` to `DurableAgent.recover()`, matching `stream()` and `resume()`. When a recovered run suspends (for example, waiting on tool approval), the returned `fullStream` now ends, so callers can hand off to `resume()` instead of hanging. ([#26555](https://github.com/mastra-ai/mastra/pull/26555)) ```ts const recovered = await agent.recover(runId, { closeOnSuspend: true }); for await (const chunk of recovered.fullStream) { // ends after `tool-call-approval` } ``` - Fixed thread subscribers missing approval requests and completion events after a durable agent run recovers from a crash. Recovered runs that pause for tool approval can now be approved and finish normally, including when multiple server instances share Redis. ([#26554](https://github.com/mastra-ai/mastra/pull/26554)) - Fixed a guardrail leak where a reply rejected by an output processor's `processOutputStep` was still saved to the memory thread and returned as `result.text` when the run ended on the tripwire (after `maxProcessorRetries` ran out, or on an abort without retry). The rejected reply is now dropped; the user message and any earlier accepted steps, such as tool calls and results, are still kept. Fixes #26048. ([#26168](https://github.com/mastra-ai/mastra/pull/26168)) - Fixed saved agent steps after tool approval or stream resumption. After `approveToolCall`, `declineToolCall`, or `resumeStream`, each saved step now contains only its own content. Later steps no longer repeat earlier tool calls, so snapshots stay small and processors and `prepareStep` see the right content. Fixes #26475. ([#26646](https://github.com/mastra-ai/mastra/pull/26646)) - Fixed abortRunStream so runs without a thread can be stopped by run ID. These runs now pass their run-level abort signal to tools and sub-agents, matching threaded streams. After a run settles, aborting the caller signal no longer reaches tools or sub-agents that retained the run signal. ([#26188](https://github.com/mastra-ai/mastra/pull/26188)) - Fixed internal Agent Controller operations so configured authorization providers enforce read and execute permissions. Read routes authorize `agent-controller:read` when inspecting an existing live session. If the in-memory session is missing after a restart, they preserve recovery behavior by recreating it through the `agent-controller:execute`-authorized path. Other operations that create, resume, or mutate sessions also require `agent-controller:execute`. Applications without an authorization provider are unchanged. ([#26092](https://github.com/mastra-ai/mastra/pull/26092)) - Fixed durable agents forwarding the parent conversation to delegated agents and delegation hooks, including transient request-processor context. Ordinary tools now receive input-only messages consistently with non-durable agents. ([#26365](https://github.com/mastra-ai/mastra/pull/26365)) - Fixed restarted workflows losing state changes made inside a nested workflow. When a run restarted after a crash while a nested workflow was still running, the nested workflow finished but its `setState` updates never reached the parent, so later steps saw the old state. The nested workflow's final state now carries back to the parent, as it already did for `start()` and `resume()`. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Reduced durable agent snapshot size when crash recovery is on. Running snapshots no longer save a copy of the full provider request (prompt and tool schemas) for each tool-calling step. Resume and recovery behave the same as before. Fixes #26462. ([#26568](https://github.com/mastra-ai/mastra/pull/26568)) - Fixed durable, evented, and Inngest agents to stop before model execution when processor resolution or input processing throws. ([#26347](https://github.com/mastra-ai/mastra/pull/26347)) - Fixed cross-process durable run observers to return parsed structured output objects. ([#26195](https://github.com/mastra-ai/mastra/pull/26195)) - Token and cost metrics now carry a `usageId` shared by all rows from the same model call. Usage rolled up from hidden model calls onto a visible span gets a separate `usageId` per call, so each call stays distinguishable even though the rows share a span. ([#26461](https://github.com/mastra-ai/mastra/pull/26461)) - Fixed evented approval runs recreating deleted durable run registry keys during finalization. ([#26315](https://github.com/mastra-ai/mastra/pull/26315)) - Fixed evented workflows to recover sleeps after a process restart by replaying the sleeping path without rerunning completed steps. ([#26268](https://github.com/mastra-ai/mastra/pull/26268)) - Fixed stored signals rendering different markup after they are reloaded from storage. Signal attributes now render in a stable order, so the same message produces the same prompt text on every turn. ([#25957](https://github.com/mastra-ai/mastra/pull/25957)) - Fixed approved tool suspensions reappearing as pending after a restart. When a new session subscribed to a thread with initial history, suspensions that had already been answered could be registered again, showing stale approval prompts and making auto-resume code fail completed runs. Only suspensions on the latest assistant message are now treated as pending. ([#26165](https://github.com/mastra-ai/mastra/pull/26165)) - Agents now receive file and image contents from AI SDK v7 message input. ([#26177](https://github.com/mastra-ai/mastra/pull/26177)) Unsupported file data now throws an error instead of sending an empty file. - Fixed durable and evented step-finish events to report whether the agent loop actually continued. ([#26348](https://github.com/mastra-ai/mastra/pull/26348)) - Fixed EventEmitterPubSub so publish() no longer rejects when a subscriber throws synchronously; the error is logged and delivery continues. ([#26187](https://github.com/mastra-ai/mastra/pull/26187)) - Fixed nested workflows losing the parent's state when a run restarted before the nested workflow's first step finished. After a crash in that window, the nested workflow restarted with empty state instead of the state the parent passed in, so steps reading that state failed. The nested workflow now restarts with the parent's state. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed two thread ownership bugs in durable agents running across multiple instances: ([#25080](https://github.com/mastra-ai/mastra/pull/25080)) - A replayed `run-registered` event from a finished run no longer takes over as the thread's active run, whether or not its record is still cached locally (for example when Redis redelivers the event after the run finished). - `sendSignal` now recognizes an active run owned by another instance while the thread is observed, so `ifActive` behavior (for example `discard`) applies instead of waking a new run. - Fixed agent runs stopping mid-task after a tool approval. Approving or declining a tool call now resumes the run with the controller's full step budget instead of the agent's default of 5 steps, so the run no longer ends as "complete" a few steps later with work still pending. ([#26493](https://github.com/mastra-ai/mastra/pull/26493)) - Fixed `ToolCallFilter` removing tool results from the current run after Observational Memory pruned messages mid-run or a suspended run was resumed. The filter now keeps those results so the model can still use them. ([#26350](https://github.com/mastra-ai/mastra/pull/26350)) - Fixed agent steps that start directly with a tool call being merged into the previous step in the conversation history sent to the model. Calls that ran one after another, where the second used the first's result, no longer look like parallel calls. Parallel tool calls within a single step still stay together. ([#26685](https://github.com/mastra-ai/mastra/pull/26685)) - Fixed durable, evented, and Inngest agents leaking one-step prepareStep system message overrides into later model steps. ([#26343](https://github.com/mastra-ai/mastra/pull/26343)) - Bumped the MCP SDK dependencies for GHSA-6qxp-vccf-f47h (high). `@modelcontextprotocol/client`, `@modelcontextprotocol/server` and `@modelcontextprotocol/core` move from 2.0.0 to 2.2.0, and `@modelcontextprotocol/sdk` from ^1.29.0 to ^1.31.0. No API changes. ([#26260](https://github.com/mastra-ai/mastra/pull/26260)) - Fixed internal agent memory reads and writes so configured authorization providers enforce permissions with actor and acting-agent attribution across standard, durable, and delegated execution paths. Applications without an authorization provider are unchanged. Applications with an authorization provider must grant `memory:read` and `memory:write` for the affected memory threads to users and system actors that run agents in process; otherwise those operations now fail with a 403 response. ([#26092](https://github.com/mastra-ai/mastra/pull/26092)) - Fixed resumed durable and evented agent outputs losing tool calls made before suspension or approval, even after a restart or repeated approvals (COR-1398). ([#26364](https://github.com/mastra-ai/mastra/pull/26364)) - Fixed workflows exposed to agents as tools running in parallel with other tool calls even when they can suspend. A workflow tool now reports that it can suspend when any of its steps (including nested workflows) declares a `suspendSchema` or `resumeSchema`, so `toolCallConcurrency` runs it one call at a time, the same as a regular tool with a `suspendSchema`. Steps that call `suspend()` should declare a `suspendSchema` or `resumeSchema` so the workflow tool is detected as suspendable. ([#26608](https://github.com/mastra-ai/mastra/pull/26608)) - Preserve the original `TypeError` when plain agents reject invalid `modelSettings.timeout` stream options, matching durable and evented agents ([#26340](https://github.com/mastra-ai/mastra/pull/26340)) - Fixed aborted agent streams reporting the wrong finish reason and emitting completed tool results more than once. ([#26345](https://github.com/mastra-ai/mastra/pull/26345)) ## 1.76.0-alpha.9 ### Patch Changes - Fixed retried API errors leaking the failed attempt's partial text. ([#26654](https://github.com/mastra-ai/mastra/pull/26654)) A provider could stream some text, fail mid-stream, and then succeed when an error processor retried. The final answer came out as the failed prefix followed by the recovered answer, for example `"The answer is The answer is 42."`. The retry request also sent the failed prefix back to the model. The failed attempt's partial text is now discarded, for both `Agent` and durable agents. The final `text` and the retry prompt no longer include it, and neither do `response.messages` and stored messages. Steps that completed before the failed attempt are kept. Text chunks already streamed live from the failed attempt still reach `textStream` and `fullStream` consumers. The step that carries them finishes with reason `retry`. - Fixed `resumeStream` ignoring the `requestContext` and `delegation` you pass when a supervisor run is resumed in the same process that suspended it. The resumed delegation now calls the `onDelegationStart` hook from the resume call, and sub-agent tools see the resume call's `requestContext`. `DurableAgent` tools approved with `approveToolCall` in the same process now also run with the request context passed to that call. Fixes [#25960](https://github.com/mastra-ai/mastra/issues/25960). ([#26686](https://github.com/mastra-ai/mastra/pull/26686)) - Fixed agent steps that start directly with a tool call being merged into the previous step in the conversation history sent to the model. Calls that ran one after another, where the second used the first's result, no longer look like parallel calls. Parallel tool calls within a single step still stay together. ([#26685](https://github.com/mastra-ai/mastra/pull/26685)) ## 1.76.0-alpha.8 ### Minor Changes - Fixed durable agent crash recovery re-running runs that were still executing. Recovery restarted every run left in the `running` state, even when another process was still driving it. Both executions then wrote to the same thread, and the original one could overwrite the recovered assistant messages ([#23734](https://github.com/mastra-ai/mastra/issues/23734)). ([#26418](https://github.com/mastra-ai/mastra/pull/26418)) Each run is now claimed by the execution driving it: `stream()`, `generate()`, `resume()`, or `recover()`. The claim is renewed while the run executes and lapses about 30 seconds after its process dies. - `recoverActiveRuns()` skips runs whose claim is still live. Skipped runs are returned with `status: 'skipped'`, a `reason`, and, for a run live in another process, a `retryAt` time. - `mastra.recoverAllDurableAgents()` counts skipped runs in a new `skipped` field and checks each one again once its claim could have lapsed, so a run whose process crashed is still recovered. - `recover()` throws `DURABLE_AGENT_RUN_ACTIVE` for a live run. Pass `force: true` to take the run over. The execution that held it stops. - `recover()` throws `DURABLE_AGENT_RECOVER_RUN_ACTIVE_LOCALLY` for a run the same process is still executing, even with `force: true`. Previously it started a second execution of the run. Wait for the run to finish instead. - Starting a run with a `runId` that another execution holds waits up to 5 seconds, then throws `DURABLE_AGENT_EXECUTION_CONFLICT`. - `mastra.shutdown()` releases runs still executing after the drain window, so the next boot recovers them right away. - Run streams drop events from an execution that lost its run. With a storage adapter that supports run fencing, that execution's writes are also rejected, so it can't overwrite messages, threads, or workflow state. A warning is logged when the workflows or memory store doesn't support run fencing: whenever runs are recovered, through `recoverActiveRuns()` or `recover()`, and, with `recovery: { durableAgents: 'auto' }`, when a run starts. ```ts const { recovered } = await durableAgent.recoverActiveRuns(); for (const run of recovered) { if (run.status === 'skipped' && run.reason === 'run-active') { console.log(`${run.runId} is still running. Check again at ${new Date(run.retryAt!)}`); } } // Take a run over even though another execution still holds it const stream = await durableAgent.recover(runId, { force: true }); ``` See [Run ownership](https://mastra.ai/docs/harness/durable-agents#run-ownership) for how claims are enforced and how recovery works across multiple instances. - Agents using the default error processors now recover from Gemini's intermittent `MALFORMED_FUNCTION_CALL` finish instead of failing the turn with `AGENT_STREAM_ERROR`. The default `stream-error-retry-processor` resends the failed step unchanged up to 2 times with a short backoff. No tool runs on that step, so the retry cannot repeat side effects. Durable agents are not covered yet. ([#26612](https://github.com/mastra-ai/mastra/pull/26612)) The matcher is exported as `isMalformedFunctionCallError`, so a custom `StreamErrorRetryProcessor` can reuse it: ```ts import { StreamErrorRetryProcessor, isMalformedFunctionCallError } from '@mastra/core/processors'; const agent = new Agent({ // ... errorProcessors: [new StreamErrorRetryProcessor({ matchers: [isMalformedFunctionCallError], maxRetries: 2 })], }); ``` - Added run fencing to the storage API, so storage adapters can reject writes from a durable agent execution that lost its run to crash recovery. ([#26418](https://github.com/mastra-ai/mastra/pull/26418)) - Workflows stores have new `claimRunOwnership()`, `renewRunOwnership()`, `releaseRunOwnership()`, and `getRunOwnership()` methods. - Memory stores have new `raiseRunFence()` and `retireRunFence()` methods. - Methods that write run data, such as `saveMessages()`, `saveThread()`, `persistWorkflowSnapshot()`, and `updateWorkflowState()`, accept an optional `fence`. A write whose fence is no longer the run's current claim throws `RunFenceConflictError`. - `supportsRunFencing()` reports whether a store implements this. It can return a promise, for stores that must probe the backend first, and should reject when the store can't tell yet. It defaults to `false`, so custom storage adapters keep working without changes, and durable agents fall back to the PubSub lease for them. - A run is fully fenced only when both the workflows store and the memory store it writes to support fencing. If only the workflows store does, for example because a composite store takes memory from another adapter or the agent's `Memory` has its own storage, an execution that lost the run can't write workflow state anymore, but its messages, threads, and working memory still land. If only the memory store does, nothing is fenced. - Stores that support fencing keep one ownership record per run, plus one memory fence record per run with memory. Stores with retention support prune them with the new `workflows.runOwnership` and `memory.runFences` retention keys. Ownership records age out by inactivity, and fence records once the execution holding them settles. A run's first generation is taken from the store's clock, so a run that reuses a `runId` after its records were pruned still claims a higher generation than its earlier executions. **Pruning run fencing records** ```ts const storage = new LibSQLStore({ id: 'mastra-storage', url: 'file:./mastra.db', retention: { workflows: { runOwnership: { maxAge: '1d' } }, memory: { runFences: { maxAge: '1d' } }, }, }); // From your own scheduler await storage.prune(); ``` **Writing with a fence** ```ts import { RunFenceConflictError } from '@mastra/core/storage'; const workflows = await storage.getStore('workflows'); const claim = await workflows.claimRunOwnership({ runId, ownerId: 'worker-a', leaseMs: 30_000 }); if (claim.acquired) { const fence = { runId, ownerId: 'worker-a', generation: claim.record.generation }; try { await workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot, fence }); } catch (error) { if (error instanceof RunFenceConflictError) { // Another execution claimed the run since. Nothing was written. } } } ``` **Opting in from a custom adapter** ```ts import { RunFenceConflictError, resolveRunFence, WorkflowsStorage } from '@mastra/core/storage'; class MyWorkflowsStorage extends WorkflowsStorage { supportsRunFencing() { return true; } // Also implement claimRunOwnership(), renewRunOwnership(), releaseRunOwnership() and getRunOwnership(). async persistWorkflowSnapshot({ workflowName, runId, snapshot, fence }) { // The fence passed in, or the one of the durable agent execution making this write const current = resolveRunFence(this, fence, runId); // In one transaction: if `current` is set and is no longer the run's claim, // throw new RunFenceConflictError(current, 'persistWorkflowSnapshot'). Otherwise write. } } ``` - Faster, configurable workflow recovery on server boot. ([#26607](https://github.com/mastra-ai/mastra/pull/26607)) - Active workflow runs now restart in parallel (up to 5 at a time by default) instead of one by one, and durable agent recovery no longer waits for the workflow sweep. - New `recovery.workflows: 'off'` turns off boot-time workflow restarts for the whole instance; `recovery.workflowConcurrency` sets the parallelism (`Infinity` for no limit). - `workflow.restartAllActiveWorkflowRuns()` also restarts runs concurrently, capped by `recovery.workflowConcurrency`. Set it to `1` if runs share side effects that must not overlap. ```ts new Mastra({ recovery: { workflows: 'auto', workflowConcurrency: 10 }, }); ``` ### Patch Changes - Reduced how much a long durable agent run writes to storage, and made runs in flight at shutdown recoverable ([#26600](https://github.com/mastra-ai/mastra/issues/26600)). ([#26642](https://github.com/mastra-ai/mastra/pull/26642)) - **Smaller checkpoints:** a durable run's checkpoints no longer repeat messages the run has already saved to memory (for example with `savePerStep: true`). They store a small reference to the memory row instead, as recalled messages already did. This covers every copy of the conversation in a checkpoint, including the run input and the final step results. Messages that change after being saved stay inline. This applies to agents with a fixed memory instance. - **Checkpoint on shutdown:** with the default snapshot policy and `recovery.durableAgents: 'off'`, a durable run still in flight when `mastra.shutdown()` starts now persists a `running` checkpoint at each step boundary it reaches during the drain. If it reaches one before the drain ends, it can be resumed later with `recover()` or `recoverActiveRuns()` instead of being lost. ```typescript // Process A: a run is still in flight when the process shuts down. await mastra.shutdown({ drainTimeout: 5_000 }); // Process B: resume every run checkpointed during the drain. await mastra.recoverAllDurableAgents(); // or per agent: await durableAgent.recoverActiveRuns(); ``` - Fixed durable agents ending a turn without an answer when Gemini returns a malformed tool call (`MALFORMED_FUNCTION_CALL`). Durable agents now retry the request like regular agents do, and report an error if the retries fail. ([#26636](https://github.com/mastra-ai/mastra/pull/26636)) - Fixed durable agents ignoring `structuredOutput.model`. The separate structuring model now runs when the turn finishes, so `output.object` resolves and `metadata.structuredOutput` is saved on the assistant message, matching `Agent`. ([#26571](https://github.com/mastra-ai/mastra/pull/26571)) - Fixed `DurableAgent` `abort()` not stopping in-flight tool or model calls when `modelSettings.timeout.totalMs` is set and the run's registry entry had been evicted under heavy concurrency. ([#26628](https://github.com/mastra-ai/mastra/pull/26628)) - Fixed evented workflows and evented agents storing the caller's auth token (`mastra__authToken`) in workflow snapshots. The token is now stripped before every snapshot write, matching the default workflow engine; steps still receive the live token while the run executes, and resumed runs use the token from the resuming request instead of any token left in older snapshots. Fixes #26217. ([#26638](https://github.com/mastra-ai/mastra/pull/26638)) - Fixed durable agents on the evented engine running the process out of memory on long runs. The engine persisted every completed step's full `payload` and `output` — including the whole, monotonically growing iteration state — because its step-result merge writes bypassed the workflow's `pruneSnapshot` hook, so snapshots and per-write heap cost grew without bound. The engine now prunes after each non-parallel step, and the durable loop no longer retains all completed-step history now that its same-iteration reads are declared. ([#26553](https://github.com/mastra-ai/mastra/pull/26553)) - `execute_command` no longer tells the agent an aborted command was killed unless the sandbox confirms it (`killed: true`). When the provider omits kill metadata or throws after the run is cancelled, the tool now notes that the run was cancelled and keeps the provider's error, so agents are not falsely assured that remote work stopped. ([#26606](https://github.com/mastra-ai/mastra/pull/26606)) - Reduced storage used by agent runs that recall conversation history from memory. Saved run state now stores recalled messages as references to the stored messages instead of full copies. When the run continues, the messages are loaded back from memory. ([#26222](https://github.com/mastra-ai/mastra/pull/26222)) In a 12-step run that recalled 20 messages, the saved run state was about 25% smaller for durable agents and about 21% smaller for agents waiting on tool approval. This applies to agents configured with a memory instance. Agents whose memory is a function, or that use memory inherited from a supervisor agent, keep full copies of recalled messages, because a resumed run may not pass the request context that memory was chosen from. **Loading recalled messages back.** If a recalled message is edited while a run is suspended, the resumed run uses the stored version. If it is deleted, the resumed run drops it. Both are logged at debug level. When a run is resumed or recovered, the messages are loaded before anything runs, so if memory storage can't load them, the call (for example `approveToolCall` or `recover`) throws and the run stays as it was, so you can retry it. A durable agent also loads them in the middle of a run when its process has no cached copy: when a step runs on a different worker (evented engine with a distributed pubsub), after more than 10 minutes between steps (for example, a slow tool call), or when more than 1,000 durable runs are in progress in one process. A memory storage failure at that point is retried for about 10 seconds, with each retry logged as a warning. If memory storage is still failing after that, the run fails. **Storage reads.** Runs that store recalled messages as references look them up in memory storage by id, once when first storing them and again whenever they are loaded back as described above. Cloudflare KV storage finds a message by id by checking every thread, so on KV each lookup reads every thread once per recalled message. **Upgrading and downgrading.** Runs started on an earlier version still resume and finish normally. Runs started on this version can't be resumed after downgrading to an earlier `@mastra/core` version. - Fixed three bugs in the in-memory storage adapter so it behaves like the database adapters: ([#26418](https://github.com/mastra-ai/mastra/pull/26418)) - `listMessages` now reports `hasMore` correctly when `include` is combined with pagination or several threads. Before, it could report no more pages while filtered messages remained, or report more pages when `include` had already returned every message. - Saving an existing channel installation again no longer resets its `createdAt`. - `batchDeleteTraces` with `organizationId` or `resourceId` now deletes only that tenant's spans. Before, it removed the whole trace or nothing, depending on which root span was written last. - Fixed saved agent steps after tool approval or stream resumption. After `approveToolCall`, `declineToolCall`, or `resumeStream`, each saved step now contains only its own content. Later steps no longer repeat earlier tool calls, so snapshots stay small and processors and `prepareStep` see the right content. Fixes #26475. ([#26646](https://github.com/mastra-ai/mastra/pull/26646)) - Reduced durable agent snapshot size when crash recovery is on. Running snapshots no longer save a copy of the full provider request (prompt and tool schemas) for each tool-calling step. Resume and recovery behave the same as before. Fixes #26462. ([#26568](https://github.com/mastra-ai/mastra/pull/26568)) - Fixed workflows exposed to agents as tools running in parallel with other tool calls even when they can suspend. A workflow tool now reports that it can suspend when any of its steps (including nested workflows) declares a `suspendSchema` or `resumeSchema`, so `toolCallConcurrency` runs it one call at a time, the same as a regular tool with a `suspendSchema`. Steps that call `suspend()` should declare a `suspendSchema` or `resumeSchema` so the workflow tool is detected as suspendable. ([#26608](https://github.com/mastra-ai/mastra/pull/26608)) ## 1.76.0-alpha.7 ### Patch Changes - Faster startup when restarting active workflow runs. Mastra no longer scans storage for workflows that set `autoRestartActiveRuns: false`. It also skips the internal workflows that run agent processors, since those only run during an agent call. ([#26588](https://github.com/mastra-ai/mastra/pull/26588)) - Fixed three durable agent bugs: ([#26583](https://github.com/mastra-ai/mastra/pull/26583)) - Aborting a suspended run with `abortThreadStream()`, `abortRunStream()` or the stream result's `abort()` now removes it from `listSuspendedRuns()`. Previously the run stayed listed as suspended after the abort. This also applies to runs waiting for tool approval: aborting them now releases the run instead of leaving it for a later approve or decline. - Client tool spans on Inngest agents are now parented to the exported agent run span instead of a span that was never exported. - Inngest agent streams no longer hang when an event exceeds the Realtime message size limit. Oversized `finish` events are sent without the step history, and oversized `error` events keep the error name and a shortened message so `onError` still fires. Oversized non-terminal events are replaced with a `data-oversized-event` chunk so clients know an event was reduced, and a warning is logged through the Mastra logger. - Fixed a durable agent thread staying busy forever when `cleanup()` was called before the run finished. Cleanup now waits for the run to settle, so `onFinish`/`onError` fire and the thread is released for the next request. Fixes #25974. ([#26587](https://github.com/mastra-ai/mastra/pull/26587)) - Fixed durable agents saving each step of a turn as a separate message. A multi-step turn is now saved as one message, as with `Agent`. Conversation history now holds more turns, and the streamed message ID matches the saved message. ([#26586](https://github.com/mastra-ai/mastra/pull/26586)) - Fixed two durable agent bugs: ([#26585](https://github.com/mastra-ai/mastra/pull/26585)) - `Mastra.shutdown()` now closes the pubsub, so open Redis connections no longer keep the process running after shutdown. - Read-only memory is now respected when a durable run finishes from a saved snapshot, so read-only turns are no longer saved. - Fixed durable agent runs that could not be resumed after a crash right after a tool suspended or asked for approval (#26435). The `tool-call-suspended` and `tool-call-approval` chunks are now published only after the suspended snapshot is saved, so a client that sees the question can always resume the run. ([#26589](https://github.com/mastra-ai/mastra/pull/26589)) Detaching an `AgentController` session from a thread no longer declines and deletes a parked durable run, so another session or process can still resume it. - Fixed `DurableAgent.recover()` losing tools that `ToolSearchProcessor` loaded during the run. After a crash, recovery in a fresh process now reads the run's latest transcript and restores those tools, so the model's pending tool call runs once instead of failing with `ToolNotFoundError` and being retried under a new call id. ([#26592](https://github.com/mastra-ai/mastra/pull/26592)) - Experimental durable and evented agents now restart a model request that has only produced reasoning when a new message or signal arrives, so the agent answers with the new input instead of finishing a stale response. This matches the default agent. ([#25878](https://github.com/mastra-ai/mastra/pull/25878)) - The restarted request keeps the same step: it doesn't use up `maxSteps` or add token usage, and the discarded reasoning stays out of history. - Input processors always finish; only the model request is cancelled. - In traces, the cancelled request's step and inference spans end with finish reason `interrupted`. ## 1.76.0-alpha.6 ### Minor Changes - Added `threadMetadata` to the `onDelegationStart` return value so delegated sub-agent threads can carry metadata such as a tenant ID. The metadata is applied when the sub-agent thread is created, which makes those threads findable with metadata-filtered queries. ([#25200](https://github.com/mastra-ai/mastra/pull/25200)) ```ts await supervisor.generate('Summarize the report', { memory: { thread: 'parent-thread', resource: 'user-1' }, delegation: { onDelegationStart: () => ({ threadMetadata: { tenantId: 'acme' } }), }, }); await memory.listThreads({ filter: { metadata: { tenantId: 'acme' } } }); ``` ### Patch Changes - AgentController sessions now forward the agent's `isTaskComplete` verdict as a live `task_complete_evaluation` event, so web clients can show "the check failed, the agent is fixing it" right away. `@mastra/client-js` recognizes the new event type. ([#26185](https://github.com/mastra-ai/mastra/pull/26185)) The session stream also sends the current display state, including the message being streamed, as its first event. A page that opens or reloads mid-run now sees the in-flight assistant text instead of waiting until it is stored. `currentMessage` can hold a message that is already stored (for example between steps or after a run), so clients should merge it by message id rather than append it. - Fixed DurableAgent streams with `closeOnSuspend: true` closing before the suspended run was saved. Approving a tool call right after the stream ends now works from any process, instead of sometimes failing with "This workflow run was not suspended". Fixes #26454. ([#26556](https://github.com/mastra-ai/mastra/pull/26556)) - Fixed `sendSignal(..., { ifIdle: { behavior: 'wake' } })` on a DurableAgent so the accepted result's `output.text` now resolves to the generated text, the same as with a regular Agent. Previously it resolved to `undefined`. ([#26564](https://github.com/mastra-ai/mastra/pull/26564)) ```ts const { accepted } = durableAgent.sendSignal(signal, { resourceId, threadId, ifIdle: { behavior: 'wake' } }); const result = await accepted; await result.output?.text; // now the generated text ``` - Fixed durable agents saving raw model output after `processOutputStream` changed or removed streamed text. Stored text now matches the stream, as it does for regular agents. Redacted values no longer reappear on reload or reach the model on a later turn. ([#26559](https://github.com/mastra-ai/mastra/pull/26559)) - Fixed `DurableAgent.recover()` dropping the agent's tools when a run was interrupted during a model request. The recovered model call now gets the same tools as the original run, so it can still call them instead of only replying with text. Fixes #25890. ([#26535](https://github.com/mastra-ai/mastra/pull/26535)) - Fixed MCP App tools not being detectable by AI SDK UI hosts. For tools whose MCP server exposes an app UI (`mcp._meta.ui.resourceUri`), `toAISdkStream` now sets `toolMetadata.app` (`resourceUri`, `mimeType: 'text/html;profile=mcp-app'`, and `serverId` when available) on `tool-input-start` and `tool-input-available` chunks, so hosts can render MCP Apps without a custom stream transform. Fixes #25892. ([#26352](https://github.com/mastra-ai/mastra/pull/26352)) - Durable and evented agents now include `runId` in `onStepFinish` and `onFinish`, matching the regular Agent. Use `runId` to tell which run an event came from when callbacks are shared across runs. ([#26531](https://github.com/mastra-ai/mastra/pull/26531)) `onFinish` also now includes `model`, `messages`, `object`, `error` and `usedFallbackValue`. You can read the structured output and response messages directly in the callback. Fixes #26524. - Fixed queued messages and signals arriving too late during reasoning in the default agent loop. Reasoning-only model requests are cancelled and restarted with pending input in the same run, without keeping discarded reasoning in model history or interrupting text and tool output. Interrupted requests reuse the same logical step without consuming maxSteps, advancing stepNumber, or contributing token usage. The run's `reasoning`, `reasoningText` and `getFullOutput()` exclude discarded reasoning, and tracers can close the cancelled request's inference span through the optional `interruptInference()` tracker method. ([#25861](https://github.com/mastra-ai/mastra/pull/25861)) - Added `closeOnSuspend` to `DurableAgent.recover()`, matching `stream()` and `resume()`. When a recovered run suspends (for example, waiting on tool approval), the returned `fullStream` now ends, so callers can hand off to `resume()` instead of hanging. ([#26555](https://github.com/mastra-ai/mastra/pull/26555)) ```ts const recovered = await agent.recover(runId, { closeOnSuspend: true }); for await (const chunk of recovered.fullStream) { // ends after `tool-call-approval` } ``` - Fixed thread subscribers missing approval requests and completion events after a durable agent run recovers from a crash. Recovered runs that pause for tool approval can now be approved and finish normally, including when multiple server instances share Redis. ([#26554](https://github.com/mastra-ai/mastra/pull/26554)) - Fixed durable and evented step-finish events to report whether the agent loop actually continued. ([#26348](https://github.com/mastra-ai/mastra/pull/26348)) - Fixed two thread ownership bugs in durable agents running across multiple instances: ([#25080](https://github.com/mastra-ai/mastra/pull/25080)) - A replayed `run-registered` event from a finished run no longer takes over as the thread's active run, whether or not its record is still cached locally (for example when Redis redelivers the event after the run finished). - `sendSignal` now recognizes an active run owned by another instance while the thread is observed, so `ifActive` behavior (for example `discard`) applies instead of waking a new run. ## 1.76.0-alpha.5 ### Patch Changes - Fixed aborted suspended runs staying listed as suspended. When `session.abort()` cancels a run that is parked in a tool `suspend()` (for example `ask_user`), the run's in-memory registration and its workflow snapshot rows are now released, so `agent.listSuspendedRuns()` no longer returns it and memory no longer grows with each aborted run. This applies to both regular and durable agents. ([#26181](https://github.com/mastra-ai/mastra/pull/26181)) - Fixed tools running with empty input when using `agent.stream()`. Some providers, like OpenAI Responses and Anthropic programmatic tool calling, send tool arguments only at the end of a tool call. Tools now wait for the complete arguments before they run. Tools with required inputs no longer fail validation in this case. ([#26251](https://github.com/mastra-ai/mastra/pull/26251)) - Fixed durable, evented, and Inngest agents to stop before model execution when processor resolution or input processing throws. ([#26347](https://github.com/mastra-ai/mastra/pull/26347)) - Fixed aborted agent streams reporting the wrong finish reason and emitting completed tool results more than once. ([#26345](https://github.com/mastra-ai/mastra/pull/26345)) ## 1.76.0-alpha.4 ### Minor Changes - `processLLMRequest` now receives the workspace of the step, the one the agent's tools use, so a processor can work with the agent's sandbox or filesystem before a model call. ([#25814](https://github.com/mastra-ai/mastra/pull/25814)) ```ts const processor: Processor = { id: 'my-processor', async processLLMRequest({ prompt, workspace }) { if (workspace?.hasSandboxConfig()) { // ... } }, }; ``` - Agents now keep working when the model rejects a file the user sent. Before, the model call failed, and since the file stayed in the thread, every later turn failed the same way, even a text-only one. ([#25814](https://github.com/mastra-ai/mastra/pull/25814)) A new default error processor, `UnsupportedFileHandler`, takes the rejected file out of the prompt and calls the model again. When the agent's workspace has a sandbox, the file is uploaded there and the model gets its path, so the agent can still work on it with its tools. Otherwise, the model gets the `[Attachment unavailable: ]` placeholder Mastra already uses for attachments it can't use. Only the prompt changes: the stored message keeps the file, so a thread that already holds such a file works again on its next turn. It handles the refusals that provider SDKs, such as OpenAI's and Anthropic's, raise before the request goes out, and the errors a provider returns over HTTP for a request that holds such a file, like Gemini's `502`. A rate limit or another error that asks to try again later keeps the file: the request is sent again unchanged. Every agent gets it, alongside the other default error processors, and `errorProcessorDefaults: false` turns it off with them. Uploaded files can be read by other threads that share the same sandbox. - Rearchitected an experimental memory feature. ([#26024](https://github.com/mastra-ai/mastra/pull/26024)) - Added `FileUploadProcessor`, an input processor that uploads the files a user sends to the workspace sandbox and gives the model the sandbox path instead of the file content. ([#25814](https://github.com/mastra-ai/mastra/pull/25814)) Use it when an agent works on user files with its sandbox tools. A `filter` function decides, file by file, what is uploaded; the files it rejects, such as images, keep going to the model. Only the prompt sent to the model changes: the stored messages keep their files, so the thread and clients still have the original. Memory is optional. Files are named after a hash of their content, so a file is written once and the next turns only check that it's still in the sandbox. Files of the thread history and files sent with signals are handled too. The processor never downloads anything itself: a link the model fetches, or a provider file ID, is left to the model. Pass the same workspace to the agent and to the processor: ```ts import { FileUploadProcessor, FILE_UPLOAD_ERROR_CODES } from '@mastra/core/processors'; import type { FileUploadTripwireMetadata } from '@mastra/core/processors'; const agent = new Agent({ // ... workspace, inputProcessors: [ new FileUploadProcessor({ workspace, filter: ({ mimeType, extension }) => mimeType === 'application/pdf' || extension === 'xlsx', maxFileSize: () => 25 * 1024 * 1024, }), ], }); const result = await agent.generate(messages); if (result.tripwire?.processorId === 'file-upload') { const { code } = result.tripwire.metadata as FileUploadTripwireMetadata; if (code === FILE_UPLOAD_ERROR_CODES.FILE_TOO_LARGE) { // ... } } ``` When a file of the current turn can't be uploaded, the processor stops the turn and reports why in the tripwire metadata. `FILE_UPLOAD_ERROR_CODES` lists every code. A file of the thread history that can't be uploaded is replaced by a note in the prompt instead, so it never blocks the thread. - AgentController sessions now support automatic observational memory model selection per role. `AgentControllerOMConfig.observerModel`/`reflectorModel` and `session.om..switchModel({ modelId })` accept `'auto'` to follow the active main model, and `resolveAutoModelId` lets a consumer decide which concrete model an automatic role uses. `session.om..model()` reports the role's configured intent (`'auto'`, a model ID, or `undefined`) while `modelId()` keeps returning the effective concrete model. `defaultObserverModelId`/`defaultReflectorModelId` remain the concrete fallback, and persisted explicit selections are unchanged. ([#24508](https://github.com/mastra-ai/mastra/pull/24508)) Dynamic model functions can now return a labeled model, `{ model, id }`, so code that only sees the resolved model object still knows its full `provider/model` ID. Resolved models expose that label as a read-only `id`, and models created from a plain router ID set it automatically. ```ts const agent = new Agent({ model: ({ requestContext }) => ({ model: createMyModel(requestContext), id: 'openai/gpt-5.6-sol' }), }); ``` ### Patch Changes - Fixed durable agents (such as Inngest agents) silently dropping call-time `toolsets` when the worker runs in a separate process. Toolset tools contain server-side code that can't be serialized, so the worker now throws an error naming the missing tools instead of running without them. To use these tools on a separate worker, register them on the agent (statically or via `requestContext`). ([#25860](https://github.com/mastra-ai/mastra/pull/25860)) - Fixed channel bot tokens and other internal Mastra state leaking into traces and score records ([#25893](https://github.com/mastra-ai/mastra/issues/25893)). ([#26243](https://github.com/mastra-ai/mastra/pull/26243)) Agents with `channels` configured exported the live Telegram/Slack adapter on every span's `requestContext`, including bot and app tokens, and added hundreds of KB to each span. Delegated runs exported the parent agent's memory instance, and agent-controller runs walked the whole workspace. **What changed** - Reserved Mastra request-context keys (those starting with `mastra__` or `__mastra_`) are no longer exported on spans or saved on score records. The auth token is now omitted instead of shown as `[REDACTED]`. Thread and resource IDs are still recorded as span metadata. - Workspaces now show as `{ id, name, status }` in traces and score records instead of exposing their configuration and providers. Score records also use `serializeForSpan()` for any other object that defines it. - Request-context keys without a reserved prefix keep their existing representation. If you set your own keys starting with `mastra__` or `__mastra_`, rename them to keep them in traces. - Agent runs whose model stream stops without a finish reason now end with an error instead of being reported as complete. ([#26226](https://github.com/mastra-ai/mastra/pull/26226)) - Fixed internal Agent Controller operations so configured authorization providers enforce read and execute permissions. Read routes authorize `agent-controller:read` when inspecting an existing live session. If the in-memory session is missing after a restart, they preserve recovery behavior by recreating it through the `agent-controller:execute`-authorized path. Other operations that create, resume, or mutate sessions also require `agent-controller:execute`. Applications without an authorization provider are unchanged. ([#26092](https://github.com/mastra-ai/mastra/pull/26092)) - Fixed evented workflows to recover sleeps after a process restart by replaying the sleeping path without rerunning completed steps. ([#26268](https://github.com/mastra-ai/mastra/pull/26268)) - Fixed agent runs stopping mid-task after a tool approval. Approving or declining a tool call now resumes the run with the controller's full step budget instead of the agent's default of 5 steps, so the run no longer ends as "complete" a few steps later with work still pending. ([#26493](https://github.com/mastra-ai/mastra/pull/26493)) - Fixed internal agent memory reads and writes so configured authorization providers enforce permissions with actor and acting-agent attribution across standard, durable, and delegated execution paths. Applications without an authorization provider are unchanged. Applications with an authorization provider must grant `memory:read` and `memory:write` for the affected memory threads to users and system actors that run agents in process; otherwise those operations now fail with a 403 response. ([#26092](https://github.com/mastra-ai/mastra/pull/26092)) ## 1.76.0-alpha.3 ### Minor Changes - Added an optional `usageId` field to exported metrics. Every token and cost metric produced from the same model usage shares one `usageId`, so storage can group a model call's metric rows without relying on the span. Other metrics leave it unset. ([#26461](https://github.com/mastra-ai/mastra/pull/26461)) ### Patch Changes - Keep Studio and Factory sessions alive for the identity provider's full session length. ([#26447](https://github.com/mastra-ai/mastra/pull/26447)) - `MastraAuthStudio` session cookies now last 14 days by default (was a hardcoded 24 hours), configurable via the new `sessionMaxAgeSeconds` option or the `MASTRA_SESSION_MAX_AGE` environment variable. - When the shared API renews the session during verification, `MastraAuthStudio` re-issues the renewed cookie under the deployment's own cookie domain and exposes it through a new optional `consumePendingResponseHeaders` provider hook. - `@mastra/server`'s auth middleware, `CompositeAuth`, the Factory auth gate, and Factory's per-route `ensureFactoryAuthUser` (used by routes declared `requiresAuth: false`, which skip the gate) forward those headers to the browser, as does the public `GET /auth/me` route. Forwarding is best-effort and never fails a request. ```ts import { MastraAuthStudio } from '@mastra/auth-studio'; // Defaults to 14 days. Override per deployment, or set MASTRA_SESSION_MAX_AGE (seconds). const auth = new MastraAuthStudio({ sessionMaxAgeSeconds: 7 * 24 * 60 * 60 }); ``` - Fixed output-stream processing for durable tool results after a restart or cleanup, including Inngest resumes. Restored processors receive the request context, and concurrent cold calls share the published processor pipeline and state. When the agent resolves but its processor pipeline is missing, processor reconstruction or cold-worker dependency-resolution failures (tools, memory, and workspace) stop the step instead of exposing unprocessed output. Unregistered agents, persistence-only callers, and complete live pipelines retain their dependency-resolution fallback. Fixes #26148. ([#26367](https://github.com/mastra-ai/mastra/pull/26367)) - Fixed Inngest tool resumes after a worker restart or cleanup to emit tool-call-resumed before tool-result, preserving configured display transforms. ([#26370](https://github.com/mastra-ai/mastra/pull/26370)) - Fixed plain agents emitting awaited background tool results twice. ([#26344](https://github.com/mastra-ai/mastra/pull/26344)) - Fixed durable and evented agents so they no longer execute tools excluded by `prepareStep`. ([#26339](https://github.com/mastra-ai/mastra/pull/26339)) - Fixed durable and evented agents not saving the structured output object to memory. The saved assistant message now includes `metadata.structuredOutput`, matching regular agents, so reconnecting clients and recovered runs can read the object. ([#26452](https://github.com/mastra-ai/mastra/pull/26452)) As with regular agents: - Truncated responses (finish reason `length` or `content-filter`) and responses that fail validation do not save `metadata.structuredOutput`. The response message itself is still saved. - With `errorStrategy: 'fallback'`, the `fallbackValue` is saved instead. This does not yet apply when `structuredOutput.model` is set to a separate structuring model (see [#26431](https://github.com/mastra-ai/mastra/issues/26431)). Fixes [#26432](https://github.com/mastra-ai/mastra/issues/26432). - Fixed agents being told to call `updateWorkingMemory` when `workingMemory.agentManaged` is `false`. The tool is not available in that mode, so agents now receive the read-only working memory instruction instead (#25896). ([#26198](https://github.com/mastra-ai/mastra/pull/26198)) - Fixed durable agents forwarding the parent conversation to delegated agents and delegation hooks, including transient request-processor context. Ordinary tools now receive input-only messages consistently with non-durable agents. ([#26365](https://github.com/mastra-ai/mastra/pull/26365)) - Token and cost metrics now carry a `usageId` shared by all rows from the same model call. Usage rolled up from hidden model calls onto a visible span gets a separate `usageId` per call, so each call stays distinguishable even though the rows share a span. ([#26461](https://github.com/mastra-ai/mastra/pull/26461)) - Fixed durable, evented, and Inngest agents leaking one-step prepareStep system message overrides into later model steps. ([#26343](https://github.com/mastra-ai/mastra/pull/26343)) - Preserve the original `TypeError` when plain agents reject invalid `modelSettings.timeout` stream options, matching durable and evented agents ([#26340](https://github.com/mastra-ai/mastra/pull/26340)) ## 1.76.0-alpha.2 ### Minor Changes - Added `outputOptions` to `run.restart()`, matching `run.start()` and `run.resume()`. Set `includeState: true` to get the final workflow state in the result. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) ```typescript const result = await run.restart({ outputOptions: { includeState: true } }); console.log(result.state); ``` ### Patch Changes - Fixed concurrent durable agent runs saving each other's conversation. When runs of the same durable agent (or parent workflows sharing a nested workflow) started at the same time, a nested run's first saved snapshot could hold another run's input and state. Listing runs for one resource could return another run's conversation while it was in progress, and recovering a run that crashed before its first step continued as the other run: it sent that conversation to the model and saved the reply to the other run's thread. Each nested run now saves its own input and state. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed evented workflows losing state changes after a restart. When a workflow run on the evented engine was restarted after a crash, steps resumed with the state from the run's start (or last suspension) instead of the state produced by the steps that already finished, so any `setState` updates made since then were lost. The workflow state is now recorded together with each step's result, and restarts resume from it. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Exported `ServerConfig` from `@mastra/core/server` and `OPENSEARCH_PROMPT` from `@mastra/opensearch` so the documented imports resolve. Fixed JSDoc import paths in `@mastra/evals` to use `@mastra/evals/scorers/prebuilt` and `@mastra/evals/scorers/utils`. ([#26440](https://github.com/mastra-ai/mastra/pull/26440)) - Corrected the documented defaults for `SerializationOptions.maxStringLength` (131072) and `maxDepth` (8) to match the runtime defaults. ([#26425](https://github.com/mastra-ai/mastra/pull/26425)) - Reduced storage used by agent runs waiting on tool approval or a suspended tool. Each suspended snapshot now stores the conversation one fewer time, cutting snapshot size by about 29% in a 12-approval run. Suspended runs resume exactly as before. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed evented workflows dropping state changes made inside a nested workflow that runs as a `.dowhile()` or `.dountil()` loop body. Each iteration started from the state the loop had before the nested workflow ran, so `setState` updates from the loop body were lost and later steps never saw them. The nested workflow's final state now carries into the next iteration and the steps after the loop, matching the default engine. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Reduced storage used by durable agent runs. The conversation transcript is now saved once per workflow snapshot instead of being copied into every step's input and output. In a 12-iteration tool-calling run this cut persisted snapshot size by about 31% on the default engine and 63% on the evented engine. Runs started on an earlier version still resume and finish normally. Runs started on this version can't be resumed after downgrading to an earlier `@mastra/core` version. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed restarted workflows losing state changes made inside a nested workflow. When a run restarted after a crash while a nested workflow was still running, the nested workflow finished but its `setState` updates never reached the parent, so later steps saw the old state. The nested workflow's final state now carries back to the parent, as it already did for `start()` and `resume()`. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed nested workflows losing the parent's state when a run restarted before the nested workflow's first step finished. After a crash in that window, the nested workflow restarted with empty state instead of the state the parent passed in, so steps reading that state failed. The nested workflow now restarts with the parent's state. ([#26221](https://github.com/mastra-ai/mastra/pull/26221)) - Fixed resumed durable and evented agent outputs losing tool calls made before suspension or approval, even after a restart or repeated approvals (COR-1398). ([#26364](https://github.com/mastra-ai/mastra/pull/26364)) ## 1.76.0-alpha.1 ### Minor Changes - Concurrent schema-based working memory updates no longer lose each other's fields when using PostgreSQL. With `scope: 'resource'`, the update tool now deep-merges partial updates inside a row-locked transaction, so writers in separate processes that change different fields all keep their changes. Other storage adapters keep the existing in-process behavior. ([#25848](https://github.com/mastra-ai/mastra/pull/25848)) You can also merge from your own code or over HTTP: ```ts await memory.mergeWorkingMemory({ threadId, resourceId, workingMemory: { city: 'Berlin' } }); await client.updateWorkingMemory({ agentId, threadId, resourceId, workingMemory: JSON.stringify({ city: 'Berlin' }), mode: 'merge', }); ``` Merges are rejected (HTTP `400` from the server) when the storage adapter doesn't support atomic merges, instead of silently replacing the record. - Added `getModelReasoningOptions` to read which reasoning controls a model accepts, as published by models.dev: named effort levels, a thinking-token budget, or an on/off toggle. Returns `[]` when models.dev lists the model without reasoning controls, and `undefined` when no data describes the model. ([#26225](https://github.com/mastra-ai/mastra/pull/26225)) ```ts import { getModelReasoningOptions } from '@mastra/core/llm'; getModelReasoningOptions('anthropic/claude-sonnet-4-6'); // [{ type: 'effort', values: ['low', 'medium', 'high', 'max'] }, { type: 'budget_tokens', min: 1024 }] getModelReasoningOptions('anthropic/claude-haiku-4-5'); // [{ type: 'budget_tokens', min: 1024 }] ``` Use it to offer only the reasoning levels a model supports instead of hard-coding them per model. ### Patch Changes - Fixed the `messageFilter` and `onDelegationStart` delegation types to match the messages they actually receive. Both now receive AI SDK model messages (`ModelMessage[]`) — the processor-adjusted prompt the supervisor model saw, including system messages, with tool calls removed — and `messageFilter` returns `ModelMessage[]`. Previously they were typed as stored database messages, so filters reading fields like `content.parts` or `id` could silently let parent context through. Update filters to read `role` and `content`. Fixes [#25983](https://github.com/mastra-ai/mastra/issues/25983). ([#26161](https://github.com/mastra-ai/mastra/pull/26161)) - Fixed `UnixSocketPubSub` leaving lease files behind when a lease release was followed immediately by `close()`. The release was aborted mid-way, so a thread claim released during shutdown could stay on disk until another process noticed the holder had exited. A release that starts before `close()` now completes, and `close()` waits for it, for up to one second if another process holds the lease's lock. New lease operations after `close()` still fail. ([#26292](https://github.com/mastra-ai/mastra/pull/26292)) - `Classifier` now supports AI SDK decision models. Pass a model from a provider's `decisionModel()` factory and Mastra calls `doDecide()`. Models that only implement the deprecated `doEvaluate()` contract still work. `ClassifierProcessor`, `ModelSelectionProcessor`, and classifier scorers accept the same models. ([#26279](https://github.com/mastra-ai/mastra/pull/26279)) To match the AI SDK naming, `Classifier.decide()` replaces `Classifier.evaluate()` and `MastraDecisionModel` replaces `MastraEvaluationModel`. The old names are deprecated aliases and will be removed in a future release. `EvaluationModelResult`, `MastraEvaluationModelInterface`, `ConfiguredClassifierEvaluateOptions`, and `PerCallClassifierEvaluateOptions` are likewise deprecated in favor of their `Decision`/`Decide` counterparts. ```ts import { openai } from '@ai-sdk/openai'; import { Classifier } from '@mastra/core/classifier'; const classifier = new Classifier({ id: 'support-router', model: openai.decisionModel('gpt-6-luna'), questions: { urgent: { type: 'boolean', instructions: 'Is this request urgent?' }, }, }); const result = await classifier.decide({ state: 'My order never arrived' }); ``` - Fixed tool calls failing validation when the model's arguments needed more than one automatic fix. Tool calls that send lists or objects as text, send `null` for optional fields, or use `query`, `message`, or `input` instead of `prompt` now validate even when several of these happen in the same call. For example, `{ args: '["a.py"]', note: null }` is now accepted. ([#25859](https://github.com/mastra-ai/mastra/pull/25859)) - Fixed agent signal delivery errors being cut off at the resource id. Messages such as "No claimed thread owner responded" now name the thread and resource in full, so the whole error is stored and shown. ([#26292](https://github.com/mastra-ai/mastra/pull/26292)) - Fixed evented workflows returning a stale result from `run.resume()` on persistent pubsubs such as `@mastra/redis-streams` and `@mastra/valkey-streams`. Awaited `start()`, `resume()`, `restart()` and time-travel calls now only read finish events published after the call, so `resume()` returns the resumed result instead of the earlier `suspended` one, and waiting no longer re-reads the whole `workflows-finish` stream. ([#26300](https://github.com/mastra-ai/mastra/pull/26300)) - Fixed messages queued during an active agent run being dropped when that run is aborted. Queued messages now start their own run after the abort instead of inheriting the aborted run's cancellation. ([#26280](https://github.com/mastra-ai/mastra/pull/26280)) - Fixed follow-up signals being silently dropped when the process that forwarded them to another thread owner later took over that thread. Previously, the retained signal was treated as the process's own echo and ignored. Signals the process queued locally are still deduplicated. ([#25847](https://github.com/mastra-ai/mastra/pull/25847)) - Fixed Inngest durable agents persisting the agent's system prompt in every step result of workflow snapshots. Inngest agent workflows now apply the same snapshot pruning as the core `DurableAgent`, so instructions are only kept on the run input. `pruneAgentLoopSnapshot` is now exported from `@mastra/core/agent/durable`. ([#26152](https://github.com/mastra-ai/mastra/pull/26152)) ```ts import { pruneAgentLoopSnapshot } from '@mastra/core/agent/durable'; import { createWorkflow } from '@mastra/core/workflows'; const workflow = createWorkflow({ id: 'my-agent-loop', inputSchema, outputSchema, options: { pruneSnapshot: pruneAgentLoopSnapshot }, }); ``` - Fixed cross-process events going missing after a process restarted. Processes that share a socket, such as Mastra Code sessions, could split into separate groups that never received each other's events. A message sent from one session to another could then fail to arrive until every process restarted. A restart, a busy machine briefly refusing connections, or a deleted socket file could cause the split. ([#26292](https://github.com/mastra-ai/mastra/pull/26292)) - Refused connections are retried before a process takes over the socket. - A broker removes the socket path only while it still points at its own socket. - A broker that loses its socket path rejoins the shared group automatically. - Fixed agent runs that never finished when a processor tripwire stopped the stream. The stream output now reports status `tripwire`, and Agent Controller sessions end the run with an error that includes the processor's reason instead of staying busy forever. ([#26142](https://github.com/mastra-ai/mastra/pull/26142)) - Fixed subagents silently returning an empty result when they call a client tool (a tool without `execute`). Subagents can't run client tools, so their run stops at that tool call. Previously the supervisor received empty text and `subAgentToolResults: []` with no indication why. Now the delegation result includes `subAgentPendingToolCalls` listing the unresolved calls, the supervisor model is told which tools were never run, and a warning is logged. ([#26257](https://github.com/mastra-ai/mastra/pull/26257)) ```ts const supervisor = new Agent({ // ... agents: { shopper }, defaultOptions: { delegation: { onDelegationComplete: ({ result }) => { if (result.subAgentPendingToolCalls) { // e.g. [{ toolName: 'add-to-cart', toolCallId: '...', args: { productId: 'sku-1' } }] } }, }, }, }); ``` Give client tools to the supervisor, or use `suspend()` in the subagent's tool when it needs input from the user. - Fixed a memory leak where agents with sub-agents, workflows, or `autoResumeSuspendedTools` kept growing the heap on every tool conversion (each `generate`/`stream` call and agent listing). Resume fields injected into tool input schemas are now created once instead of per conversion, so they no longer pile up in Zod's global schema registry when `zod@3.25.x` is installed. Fixes #26160. ([#26167](https://github.com/mastra-ai/mastra/pull/26167)) - Fixed duplicated assistant messages after a suspended tool or sub-agent call resumes. Each earlier reply and tool call now appears only once in saved conversation history. Fixes #26150. ([#26163](https://github.com/mastra-ai/mastra/pull/26163)) - Fixed abortRunStream so runs without a thread can be stopped by run ID. These runs now pass their run-level abort signal to tools and sub-agents, matching threaded streams. After a run settles, aborting the caller signal no longer reaches tools or sub-agents that retained the run signal. ([#26188](https://github.com/mastra-ai/mastra/pull/26188)) - Fixed evented approval runs recreating deleted durable run registry keys during finalization. ([#26315](https://github.com/mastra-ai/mastra/pull/26315)) - Agents now receive file and image contents from AI SDK v7 message input. ([#26177](https://github.com/mastra-ai/mastra/pull/26177)) Unsupported file data now throws an error instead of sending an empty file. - Fixed `ToolCallFilter` removing tool results from the current run after Observational Memory pruned messages mid-run or a suspended run was resumed. The filter now keeps those results so the model can still use them. ([#26350](https://github.com/mastra-ai/mastra/pull/26350)) - Bumped the MCP SDK dependencies for GHSA-6qxp-vccf-f47h (high). `@modelcontextprotocol/client`, `@modelcontextprotocol/server` and `@modelcontextprotocol/core` move from 2.0.0 to 2.2.0, and `@modelcontextprotocol/sdk` from ^1.29.0 to ^1.31.0. No API changes. ([#26260](https://github.com/mastra-ai/mastra/pull/26260)) ## 1.76.0-alpha.0 ### Minor Changes - Added `tracingOptions.nestUnderParent` to attach a run as a child of an earlier run in the same trace, for example an LLM judge under the run it scores. The trace then keeps the name, input, output, and tags of the run that started it. The new `nestUnderRun()` helper builds these options from the earlier run's result. ([#25691](https://github.com/mastra-ai/mastra/pull/25691)) ```ts import { nestUnderRun } from '@mastra/core/observability'; const turn = await assistant.generate('When does my order ship?'); await judge.generate('Grade this answer', { tracingOptions: nestUnderRun(turn), // same as: { traceId: turn.traceId, parentSpanId: turn.spanId, nestUnderParent: true } }); ``` Runs that pass `parentSpanId` without the new option behave as before. - Added `matches` and `notMatches` operators to advanced trace queries. They match whole words or phrases, ignoring case, on span `name` and feedback `comment`. ([#25111](https://github.com/mastra-ai/mastra/pull/25111)) ```ts { feedback: { some: { op: 'matches', left: { path: 'comment' }, right: { literal: 'incorrect dosage' } } } } ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`2233844`](https://github.com/mastra-ai/mastra/commit/223384452984718e16fc660d29f0d5d93a1ebaf2)) - Fixed durable agents running the model call outside the model step span context. `getCurrentSpan()` inside AI SDK model middleware and span-correlated logs now resolve to the model span for durable agents, matching regular agents. ([#26184](https://github.com/mastra-ai/mastra/pull/26184)) - Fixed durable agents, including Inngest agents, ignoring an `abort()` from an output processor's `processOutputResult`. The run now reports the tripwire with `finishReason: 'other'`, as a regular `Agent` does. The rejected answer is no longer saved to memory. ([#26178](https://github.com/mastra-ai/mastra/pull/26178)) - Fixed foreign durable run lookups mutating the local registry and breaking shutdown. ([#26189](https://github.com/mastra-ai/mastra/pull/26189)) - Fixed durable and evented runs to filter internal messages before saving them to memory. ([#26186](https://github.com/mastra-ai/mastra/pull/26186)) - Fixed `PIIDetector` sending a message's original text to the model when redaction left nothing usable. With `strategy: 'redact'`, a message is now dropped instead of passed through unredacted when redaction leaves no text or only whitespace (for example an SSN-only message with `redactionMethod: 'remove'`), or when PII is flagged with no usable spans. This applies to `processInput` and `processOutputResult`. Fixes #25553. ([#26209](https://github.com/mastra-ai/mastra/pull/26209)) - Setting `toolChoice: 'none'` now keeps earlier tool calls and results in the conversation, so the model can give a final text answer based on them. Amazon Bedrock no longer drops that tool history or rejects extended-thinking blocks. ([#26232](https://github.com/mastra-ai/mastra/pull/26232)) ```ts prepareStep: ({ stepNumber }) => (stepNumber >= 3 ? { toolChoice: 'none' } : undefined); ``` Tools are still left out when the step sends a structured-output schema as the response format, because some providers (such as Gemini) reject tools combined with it. - Fixed a guardrail leak where a reply rejected by an output processor's `processOutputStep` was still saved to the memory thread and returned as `result.text` when the run ended on the tripwire (after `maxProcessorRetries` ran out, or on an abort without retry). The rejected reply is now dropped; the user message and any earlier accepted steps, such as tool calls and results, are still kept. Fixes #26048. ([#26168](https://github.com/mastra-ai/mastra/pull/26168)) - Fixed cross-process durable run observers to return parsed structured output objects. ([#26195](https://github.com/mastra-ai/mastra/pull/26195)) - Fixed stored signals rendering different markup after they are reloaded from storage. Signal attributes now render in a stable order, so the same message produces the same prompt text on every turn. ([#25957](https://github.com/mastra-ai/mastra/pull/25957)) - Fixed approved tool suspensions reappearing as pending after a restart. When a new session subscribed to a thread with initial history, suspensions that had already been answered could be registered again, showing stale approval prompts and making auto-resume code fail completed runs. Only suspensions on the latest assistant message are now treated as pending. ([#26165](https://github.com/mastra-ai/mastra/pull/26165)) - Fixed EventEmitterPubSub so publish() no longer rejects when a subscriber throws synchronously; the error is logged and delivery continues. ([#26187](https://github.com/mastra-ai/mastra/pull/26187)) ## 1.75.0 ### Minor Changes - Added the `StorageMemoryRef` type so a stored agent's `memory` field can reference a registered memory instance with `{ type: 'id', memoryId }`, or hold an inline config. Existing inline configs are still accepted. ([#25850](https://github.com/mastra-ai/mastra/pull/25850)) - Added support for several processes sharing one notification store, where only some of them can run a given thread. ([#25741](https://github.com/mastra-ai/mastra/pull/25741)) `dispatchDueNotifications` accepts a `resourceId` to dispatch only that resource's due notifications. A delivery policy can return `hold: true` to leave a due notification pending for another dispatcher, without counting a delivery attempt. `hold` only applies when a due notification is dispatched. When a notification is first sent, return an action that does not deliver it, such as `defer`: ```ts import { defaultNotificationDeliveryDecision } from '@mastra/core/notifications'; const agent = new Agent({ // ... notifications: { deliveryPolicy: { decide: input => canRunHere(input.record.resourceId) ? defaultNotificationDeliveryDecision(input) : { action: 'defer', deliverAt: input.now, hold: true }, }, }, }); ``` - Added an explicit reconcile step for channel installations whose connect flow finishes outside the app (for example Discord's bot invite, which never redirects back). Channel providers can implement the new optional `reconcileInstallation(agentId)` method, exposed over `POST /api/channels/:platform/:agentId/reconcile` and `client.channels.reconcileInstallation(platform, agentId)`. The route requires the same write access as connecting, and returns the agent's fresh installation — or `null` when the platform doesn't support reconciliation. Listing installations is now a pure read and never changes state. ([#25993](https://github.com/mastra-ai/mastra/pull/25993)) ```ts const installation = await client.channels.reconcileInstallation('discord', 'my-agent'); // { id, platform, agentId, status: 'active', ... } once the invite completed ``` - Added optional `title`, `websiteUrl`, and `icons` options to `MCPServerConfig`, exposed on `MCPServerBase`, so MCP server implementations can announce a display title, website, and icons to clients. ([#25985](https://github.com/mastra-ai/mastra/pull/25985)) - Added token and cost measures to `aggregateTraces()`. Requests can now ask for `tokens.input`, `tokens.output`, `tokens.total`, `tokens.reasoning`, and `tokens.cached` (each as `.sum` or `.avg`), plus `cost.sum` and `cost.avg`, and use them in `having` and `orderBy`. ([#25735](https://github.com/mastra-ai/mastra/pull/25735)) Usage is summed per trace first, then per group. Averages are per trace with token usage, so traces with no recorded usage don't pull them down. Any request for a `cost.*` measure also returns `cost` on each row: `cost.coverage` is the share of traces with token usage whose cost was fully priced, and `cost.unit` is the currency. When a group mixes currencies, `cost.sum` and `cost.avg` are `null` and `cost.unit` is `"mixed"`. Groups whose measure is `null` sort last and never satisfy a `having` condition. ```ts import { parseTraceAggregateRequest, planTraceAggregate } from '@mastra/core/storage'; const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-06-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, groupBy: ['entityName'], interval: '1d', measures: ['count', 'tokens.input.sum', 'tokens.output.sum', 'cost.sum'], orderBy: { field: 'cost.sum', direction: 'desc' }, limit: 20, }), ); const { rows } = await storage.aggregateTraces(plan); // rows[0].measures → { count, 'tokens.input.sum', 'tokens.output.sum', 'cost.sum' } // rows[0].cost → { coverage: 0.96, unit: 'usd' } ``` `TokenMetrics`, the names of the token usage metrics, is now exported from `@mastra/core/observability`. - Semantic recall now works with a vector store that generates embeddings itself, so a self-embedding store needs no client-side `embedder`. ([#25009](https://github.com/mastra-ai/mastra/pull/25009)) Added `isSelfEmbedding` to `MastraVector`. It defaults to false, so every existing store is unaffected. A store that embeds text itself overrides it to return true, and semantic recall against that store needs no `embedder`: ```ts const memory = new Memory({ storage, vector: new MongoDBVector({ id: 'vec', uri, dbName, autoEmbed: { model: 'voyage-4' } }), options: { semanticRecall: true }, }); ``` A configured `embedder` still takes precedence, so adding one to the example above returns to client-side embedding. Messages embedded by the store are kept in an index named `memory_messages_selfembed`, separate from the indexes holding client-supplied vectors. Configuring semantic recall with neither an embedder nor a self-embedding store now says so, naming both options. - Added a storage API for querying individual completed spans with filters, cursors, bounded previews, and model cost. ([#25791](https://github.com/mastra-ai/mastra/pull/25791)) Shared cursor, timeout, and resource-limit errors now say "query" so they also describe span queries accurately. Error codes are unchanged. ```typescript import { planSpanQuery } from '@mastra/core/storage'; const result = await storage.querySpans( planSpanQuery({ timeRange: { from: '2026-10-01T00:00:00Z', to: '2026-10-02T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'tool_call' } }, }), ); ``` - Changed AgentController sessions to keep one active model instead of a separate model for each mode. Switching modes no longer changes the model automatically, and `session.model.switch` no longer accepts `modeId` or `scope`. The `model_changed` event no longer includes `modeId` or `scope`; consumers should read its `modelId` and current `thinkingLevel` fields. When an existing thread is first reopened, its active legacy per-mode selection is copied to `currentModelId`, a migration marker is stored, and obsolete `modeModelId_*` keys are removed. Later model selections persist as the thread's authoritative `currentModelId`. Use `session.model.set` for an in-memory selection that should not be persisted or emit a model-change event. ([#25997](https://github.com/mastra-ai/mastra/pull/25997)) **Before** ```ts await session.model.switch({ modelId: 'openai/gpt-5.6', modeId: 'build' }); await session.mode.switch({ modeId: 'plan' }); // Restored the plan mode model. ``` **After** ```ts await session.model.switch('openai/gpt-5.6'); await session.mode.switch({ modeId: 'plan' }); // Keeps openai/gpt-5.6 active. ``` - Added `traceId` to agent stream chunks, next to `runId`. Clients that call `/stream` can now link a run to its trace without switching to `/generate`. The field is undefined when tracing is disabled. Custom `data-*` chunks (for example, from `writer.custom()`) are left exactly as written and don't include `traceId`. Fixes [#25811](https://github.com/mastra-ai/mastra/issues/25811). ([#25927](https://github.com/mastra-ai/mastra/pull/25927)) ```ts const stream = await agent.stream('hi'); for await (const chunk of stream.fullStream) { console.log(chunk.runId, chunk.traceId); } ``` - Added a `createSession()` option to start a session without creating a thread, and `session.thread.ensureId()` to create one on first use. Sessions that are never used no longer leave empty threads behind. Sending a message or signal creates the thread automatically. The default is unchanged: sessions still get a thread when none matches. ([#22561](https://github.com/mastra-ai/mastra/pull/22561)) ```ts const session = await controller.createSession({ createInitialThread: false }); // Returns the current thread, or creates one. Concurrent calls share one thread. const threadId = await session.thread.ensureId(); ``` - Added the per-provider object form to the `ObservationalMemoryActivationTTL` type, so `activateAfterIdle` accepts values like `{ default: 'auto', anthropic: '1h' }`. New `ObservationalMemoryActivationTTLValue` and `ObservationalMemoryActivationTTLByProvider` types are exported. ([#25727](https://github.com/mastra-ai/mastra/pull/25727)) - Added an `outputValidation` option to `createTool` ([#23799](https://github.com/mastra-ai/mastra/issues/23799)). When a tool's result fails `outputSchema` validation, Mastra replaces the result with a validation error, so the model is told the call failed. That happens even when the tool's side effect (an order placed, a message sent) has already happened, which invites a retry. Set `outputValidation: 'warn'` to return the tool's actual result unchanged instead. `'strict'` is the default and keeps the existing behavior. ([#26001](https://github.com/mastra-ai/mastra/pull/26001)) ```ts export const placeOrderTool = createTool({ id: 'place-order', description: 'Places an order', inputSchema: z.object({ sku: z.string(), quantity: z.number() }), outputSchema: z.object({ orderId: z.string(), total: z.number() }), outputValidation: 'warn', execute: async ({ sku, quantity }) => orders.create({ sku, quantity }), }); ``` Also improved how output validation failures are reported: - Every output validation failure now writes a warning to the Mastra logger. Previously, a failure from a `createTool` tool left no trace in the logs. - The tool call trace span is now marked as failed when a `createTool` tool returns a validation error. Previously it was recorded as successful. - Sensitive fields such as `apiKey`, `token`, and `password` are now redacted from the tool output shown in the validation error message. - Changed `session.model.switch` to accept a model ID followed by an optional options object. Pass `{ thinkingLevel }` to apply and persist model and thinking level together. Every `model_changed` event includes the current thinking level, even when it is unchanged. Sessions synchronize both preferences from persisted thread settings before the next request, including thinking-only changes and removed overrides. Switches canceled by a thread change before any selection is committed reject without counting model use; writes already committed to the captured thread remain successful. ([#26069](https://github.com/mastra-ai/mastra/pull/26069)) ```ts // Before await session.model.switch({ modelId: 'openai/gpt-5.5' }); await session.state.set({ thinkingLevel: 'high' }); // After await session.model.switch('openai/gpt-5.5', { thinkingLevel: 'high' }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b54fda3`](https://github.com/mastra-ai/mastra/commit/b54fda3f30330d65e52bf34802f0aa4035e30ef8)) - Fixed MCP resource reads dropping `mimeType` and `_meta`. Reading a resource from a server registered through `MCPClient` (`MCPClientServerProxy.readResource()`) and reading an app resource from a local `MCPServer` (`MCPServer.readResource()`, used by Studio) now return the same metadata as `listResources()` and the MCP `resources/read` request, so MCP App `ui://` resources keep their content type and UI settings such as CSP. Fixes #23068. ([#25992](https://github.com/mastra-ai/mastra/pull/25992)) - Fixed a process crash with `TypeError: Invalid state: ReadableStream is locked` after a provider errored mid-stream. The crash happened when an agent had output processors, such as `BatchPartsProcessor` or `RegexFilterProcessor`, and a retrying error processor, such as `StreamErrorRetryProcessor`. Output processors no longer carry buffered parts across the retry. A stream that is already being read now reports through `onError` instead of raising an unhandled rejection, which on Node 22 exited the process and dropped every in-flight run. ([#25803](https://github.com/mastra-ai/mastra/pull/25803)) - Fixed `subscribeToThread({ withInitialHistory: true })` returning `thread-history` messages newest first. The initial history now lists the newest page of messages oldest first, matching `listThreadMessages` and the documented behavior. ([#25821](https://github.com/mastra-ai/mastra/pull/25821)) - Added an optional `scope` field to `AuthorizeOpts` so tool providers know whether a new connection is shared, per-author, or caller-supplied. ([#26002](https://github.com/mastra-ai/mastra/pull/26002)) - Fixed workspace search reporting the wrong `lineRange` when a custom tokenizer preserves case. Highlighting now uses the same tokens as retrieval, so searching `Python` points at the line containing `Python` rather than a line containing `python`. ([#26014](https://github.com/mastra-ai/mastra/pull/26014)) - Fixed three cases where a durable agent's stream, or a regular agent's saved approval metadata, did not match what the stream reported. ([#26006](https://github.com/mastra-ai/mastra/pull/26006)) - Tool result chunks now include `providerExecuted`, which tells the caller whether the tool ran on the provider's side. It was missing on durable runs, so clients received `undefined` where regular agents report the value. - Approval requests on durable agents now publish the same `resumeSchema` as regular agents, including `$schema`, `additionalProperties: false` and the field descriptions. The accepted resume data is unchanged. - Approval metadata saved by regular agents now carries the same `resumeSchema` as the live approval request, including the optional `reason` field the saved copy was missing. The accepted resume data is unchanged. - Fixed dataset schema updates validating items against a previously proposed schema, and fixed items being validated against the old schema after changing the schema of an empty dataset. ([#25659](https://github.com/mastra-ai/mastra/pull/25659)) - Fixed model call spans that listed tools the model did not receive. Each `MODEL_INFERENCE` span now has a `tools` attribute with the tool definitions (name, description, parameters) sent to the provider on that call, after input processors, `prepareStep`, `activeTools`, and `toolChoice` are applied. `availableTools` on the same span now matches it: it is empty when `toolChoice` is `'none'`, and it no longer lists names that are not registered tools. ([#25917](https://github.com/mastra-ai/mastra/pull/25917)) - Fixed directly resumed nested workflows so successful children continue suspended parent runs. Fixes #24588. ([#25817](https://github.com/mastra-ai/mastra/pull/25817)) - Fixed call-time `clientTools` being dropped by durable agents (such as Inngest agents) when the workflow worker runs in a different process from the caller. Client tool schemas are now carried on the workflow input and restored when the worker rebuilds the run's tools, so the model can call them as expected. ([#25664](https://github.com/mastra-ai/mastra/pull/25664)) - Fixed approval ordering for tools added by input processors, such as `ToolSearchProcessor`. Approving one call now releases the next call's approval request instead of leaving the run stuck. ([#25639](https://github.com/mastra-ai/mastra/pull/25639)) Fixed `foreach` behavior for all evented workflows. Evented workflows now stay suspended until unfinished iterations resume, and queued iterations start only when capacity is available. - Fixed DurableAgent traces after a resume or crash recovery. The resumed or recovered agent run is now nested under the original agent run, so the trace stays a single tree instead of splitting into two root spans. ([#25862](https://github.com/mastra-ai/mastra/pull/25862)) Crash recovery traces are also complete now: the agent run left open by the stopped process is ended with an `interrupted` status, and the recovered agent run is ended when the run finishes. Previously a recovered trace could be missing from the trace list or show only the part before the crash. - Fix durable and evented tool approvals so each approval resumes its own tool call and the agent finishes after the last approval ([#25831](https://github.com/mastra-ai/mastra/pull/25831)) - Persisted channel thread history as individually attributed messages, including attachments and a marker for omitted messages, instead of one text block on first mention. ([#25717](https://github.com/mastra-ai/mastra/pull/25717)) - Fixed `execute_command` and `get_process_output` results for commands stopped by an aborted run. The result now says the command was aborted instead of showing only a kill exit code such as `Exit code: 128`, which agents mistook for a real command failure. This covers foreground commands, background processes killed when the run that started them is aborted, and processes killed while `get_process_output` was waiting on them. ([#25752](https://github.com/mastra-ai/mastra/pull/25752)) `get_process_output` on a background process that exited without printing anything now returns its exit status (for example `Exit code: 0`) instead of `(no output yet)`, so it no longer looks like the process is still running. - Fixed `fetchWithRetry` ignoring cancellation. An already-aborted `signal` now rejects immediately, and aborting during a request or backoff delay stops further retries instead of waiting through the remaining delays. Retries for non-aborted requests are unchanged. ([#26164](https://github.com/mastra-ai/mastra/pull/26164)) - Removed the @experimental annotation from Agent goal APIs (goal config, setObjective, GoalSignalProvider) now that goals are stable. ([#25941](https://github.com/mastra-ai/mastra/pull/25941)) - Preserved extra fields in tool results passed to model messages, client callbacks, and loop callbacks. Client `onOutput` and `toModelOutput` callbacks now receive results containing only a `value` field as that field's value. AI SDK error outputs are stored as failed invocations, skipped by client callbacks, and exposed to response consumers as their underlying error value. ([#25680](https://github.com/mastra-ai/mastra/pull/25680)) - Fixed pausing, clearing, or replacing a goal while its judge is running being silently undone. The judge's verdict is now discarded when the objective changed during evaluation, so the agent stops instead of continuing a goal you already stopped. ([#26093](https://github.com/mastra-ai/mastra/pull/26093)) - Fixed `goal.scorer` so a string resolves a registered scorer by its id, as documented. Previously it was looked up by registration key, so a scorer registered as `scorers: { testsPass }` with id `tests-pass` failed with "Scorer with tests-pass not found" and paused the goal. Registration keys still work as a fallback. ([#26154](https://github.com/mastra-ai/mastra/pull/26154)) - Fixed agent runs failing when the goal judge's feedback signal could not be streamed (for example, because a durable agent's transport had closed). The verdict is still recorded and the run finishes normally. ([#25665](https://github.com/mastra-ai/mastra/pull/25665)) - Resource read results from MCP servers now include the optional `mimeType` and `_meta` fields, both in the `MCPServerBase.readResource()` type and in the `POST /mcp/:serverId/resources/read` response returned to `readMcpServerResource()`. ([#25992](https://github.com/mastra-ai/mastra/pull/25992)) - Fixed executor interruptions so default-engine workflow runs remain recoverable instead of being recorded as canceled. Fixes #24586. ([#24684](https://github.com/mastra-ai/mastra/pull/24684)) - Removed the @experimental annotation from Agent signal APIs (sendSignal, subscribeToThread, sendMessage, queueMessage, cancelQueuedMessages, abortThread, state and notification signals, and signal providers) now that signals are stable. ([#25946](https://github.com/mastra-ai/mastra/pull/25946)) - Fixed a leak where an output stream processor that throws let the original, unprocessed chunk through. For example, a redaction processor that crashed would still stream the text it was meant to redact. ([#25826](https://github.com/mastra-ai/mastra/pull/25826)) Now streamed content chunks processed inside regular, durable, and evented runs stop before the failing chunk is emitted, emit an `error` chunk, and finish with `finishReason: 'error'`. Durable tool-chunk processor failures also fail their workflow step before the tool chunk is published. For durable and evented agents, failures on lifecycle chunks such as `start`, `step-start`, `step-finish`, and `finish` error `fullStream` and reject derived output promises. For regular agents, `step-start` and `finish` failures emit an `error` chunk and resolve with `finishReason: 'error'`, while a `step-finish` failure errors the stream; `start` is not passed to output stream processors. Previously the processor error was logged and the unprocessed chunk was emitted while the stream finished with `finishReason: 'stop'`. `abort()` in a processor still blocks the chunk as before. ```ts const result = await agent.stream('Reveal the card number'); try { for await (const chunk of result.fullStream) { if (chunk.type === 'error') { console.error(chunk.payload.error); // e.g. "redactor crashed" } } } catch (error) { // Some lifecycle-chunk processor failures error the stream directly. console.error(error); } ``` - Added internal groundwork for querying individual spans across traces. ([#25701](https://github.com/mastra-ai/mastra/pull/25701)) - Fixed durable threads that reported suspended tool calls as lost after a restart. Pending tool calls remain available for a response. ([#25764](https://github.com/mastra-ai/mastra/pull/25764)) - Fixed `DurableAgent` and `EventedAgent` runs to fail and report the error when mapping a server-executed tool result, including an awaited background result, throws in `toModelOutput`. Client-executed tools and completed deferred background tasks continue with the raw result when their mapper throws. ([#25762](https://github.com/mastra-ai/mastra/pull/25762)) - Fixed evented agents hanging forever when workflow workers run in a separate process (for example, an API server started with `MASTRA_WORKERS=false` plus a dedicated worker deployment). Runs now reach the worker process and stream back to the caller. Values used in more than one place in a workflow now keep their data when sent between processes instead of arriving as `null`. A process without workers now logs a warning instead of silently dropping a workflow it can only run locally. ([#26036](https://github.com/mastra-ai/mastra/pull/26036)) - Fixed sendToolApproval to preserve separate approval decisions when custom resume data is provided. Approval-gated calls now reject custom resume data unless it is a non-null object that can carry the decision. ([#25632](https://github.com/mastra-ai/mastra/pull/25632)) - Fixed `stream(..., { untilIdle: true })` repeating earlier output when a background task triggers a follow-up turn. The supplied `runId` can abort the initial or follow-up turn through `abortRunStream(runId)` or `abortThreadStream(...)`. ([#25661](https://github.com/mastra-ai/mastra/pull/25661)) - Fixed agents running out of memory when `untilIdle` is set in `defaultOptions`. `agent.stream()` and `agent.resumeStream()` now wait for background tasks once instead of looping endlessly before calling the model. Fixes [#26043](https://github.com/mastra-ai/mastra/issues/26043). ([#26125](https://github.com/mastra-ai/mastra/pull/26125)) - Fixed skill publishing so it rejects JavaScript frontmatter (`---js`) in `SKILL.md` instead of running it. Publishing now parses `SKILL.md` the same way as loading and validating a skill. Fixes [#25551](https://github.com/mastra-ai/mastra/issues/25551). ([#25689](https://github.com/mastra-ai/mastra/pull/25689)) - Fixed agent runs stopping with "Interrupted" when an Observational Memory reflection didn't compress enough on its first try. The reflector retries at a stronger compression level, but each retry was reported as a failure, and the agent controller cancelled the run before the retry could finish. Retry attempts are now marked as retrying, so only a final failure stops the run. ([#26105](https://github.com/mastra-ai/mastra/pull/26105)) - Fixed structured output failing with 400 errors on OpenAI-compatible providers (for example Azure AI Foundry through `@ai-sdk/openai-compatible` with `supportsStructuredOutputs: true`). These providers send a strict JSON schema, but Mastra only prepared schemas for strict mode when the provider name started with `openai`, so requests failed with errors like `'uniqueItems' is not permitted` or `Missing 'subject'`. Schemas are now prepared whenever the model sends a strict JSON schema, and `null` values for optional fields are accepted in the response. Setting `strictJsonSchema: false` in the provider options keeps the schema unchanged. ([#25707](https://github.com/mastra-ai/mastra/pull/25707)) - Fixed requests failing with empty assistant content after switching models mid-thread, such as from an OpenAI reasoning model to Claude. When reasoning the new provider cannot accept is removed from history, assistant turns that contained only that reasoning are now left out instead of being sent empty. ([#23050](https://github.com/mastra-ai/mastra/pull/23050)) - Fixed batched channel messages from other senders skipping custom `onMention`, `onDirectMessage`, and `onSubscribedMessage` handlers when `burst`, `debounce`, or `queue` concurrency is set. ([#25854](https://github.com/mastra-ai/mastra/pull/25854)) Your handler now runs once for each sender's turn, with its own `ctx.requestContext`, `ctx.signalMetadata`, and `ctx.skipped`. A handler can now be called several times for one dispatch. - Cancelling a run while structured output is being generated no longer logs a false "Structured output processing failed" error. The structuring model call is now cancelled along with the run, and no output chunks are written after the run's stream has closed. ([#25662](https://github.com/mastra-ai/mastra/pull/25662)) - Fixed `restart()` for evented workflows that crashed while resuming a nested workflow. The resumed step now keeps its resume data and completes on restart instead of suspending again. Fixes [#25365](https://github.com/mastra-ai/mastra/issues/25365). ([#25630](https://github.com/mastra-ai/mastra/pull/25630)) - Fixed a thread failing on every turn when one of its attachments can't be used: a file that now returns 404, a relative path such as `/api/images/foo.png`, or inline content that isn't valid file data. If error processors and fallback models don't recover a failed download, the agent now answers with an `[Attachment unavailable: ]` placeholder and logs a warning. The attachment is recorded on its stored message, so later turns reuse the placeholder without downloading it again. See #23705. ([#25051](https://github.com/mastra-ai/mastra/pull/25051)) - Fixed aborting a thread run that had several messages sent to it: the follow-up run now answers all of them in one turn, instead of one message per run (which made you abort once per pending message). Messages added with `queueMessage` still run one at a time after it. ([#25749](https://github.com/mastra-ai/mastra/pull/25749)) - Fixed durable and evented agent terminal behavior. Aborted runs now emit an `abort` chunk, report `finishReason` as `aborted`, and call `onAbort` instead of `onFinish`. Failed runs call `onError` instead of `onFinish`. `resume()`, `observe()`, and `recover()` now accept an `onAbort` callback. ([#25765](https://github.com/mastra-ai/mastra/pull/25765)) - Fixed `ResponseCache` sharing cached responses between users who are identified only through `memory: { resource }`. When no `scope` is set, the cache now falls back to the memory resource ID after the auth resource ID, so each user gets their own cache entries. Set `scope: null` to keep sharing responses across users. ([#26192](https://github.com/mastra-ai/mastra/pull/26192)) - Fixed error-processor retries being dropped when a run reached its `maxSteps` limit. An agent run with `maxSteps: 1` that hit a transient 429/5xx would previously resolve as a successful empty (or partial) response with `finishReason: 'retry'`. Retries now re-run the failed step without counting against `maxSteps`, and if every retry fails the run ends with `finishReason: 'error'` and the last provider error (including fields like `statusCode`) on `output.error` and `onError`, on the default, durable, and evented engines. ([#25804](https://github.com/mastra-ai/mastra/pull/25804)) - Fixed streamed `PIIDetector` output with the `block` and `filter` strategies. When a Social Security number, email address or similar value arrived split across several stream chunks, the first part of it reached the user before the rest was recognized. Now `block` stops the response before any part of the value is shown, and `filter` removes the text up to and including the value while keeping the text after it. Very long email addresses are now also held until they are complete, including with `redact`. As with `redact`, the end of a response may arrive slightly later. ([#26082](https://github.com/mastra-ai/mastra/pull/26082)) - Fixed fresh-process durable agent resumes replaying suspended output when cached stream history is unavailable. ([#25760](https://github.com/mastra-ai/mastra/pull/25760)) - Fixed usage aggregation so omitted provider token counts remain unknown across agent, workflow, and durable streams. Reported cache and reasoning details remain additive. ([#25402](https://github.com/mastra-ai/mastra/pull/25402)) `AccumulatedUsage` from `@mastra/core/agent/durable` and `WorkflowDataPart['data']['output']['usage']` from `@mastra/ai-sdk` now represent incomplete primary counters as `undefined` instead of measured zeroes. When every input and output count is known, `totalTokens` can still be derived from those complete aggregates even if the provider omitted its total. Derived totals now sum input and output tokens without adding `reasoningTokens`. For example, `{ inputTokens: 10, outputTokens: 20, totalTokens: 30 }` followed by `{ outputTokens: 5 }` now produces `{ inputTokens: undefined, outputTokens: 25, totalTokens: undefined }`. Observability still records known per-step token contributions and marks their aggregate as incomplete. `TokenCostControl` treats the partial estimated cost as a known lower bound: hard and soft thresholds still apply when that lower bound crosses them, while lower values do not imply the complete cost is under budget. The Responses API returns `usage: null` for an incomplete aggregate instead of fabricating zero-valued counters. Durable iteration state written by this version may omit unknown primary counters and cannot be resumed by an older worker after a rollback. (#23469) - Fixed thread streams treating a run as finished while it was still waiting for a tool approval or a suspended tool. When thread updates were delivered with a delay (for example with `@mastra/redis-streams`), the thread could unblock and subscribers on other servers could lose the pending approval. The run now stays waiting until the approval or tool resumes it. ([#25962](https://github.com/mastra-ai/mastra/pull/25962)) - Added optional `rules` to `prompt_block_ref` instruction blocks so stored agents can save and preview per-usage display conditions on prompt block references. ([#25991](https://github.com/mastra-ai/mastra/pull/25991)) ```ts await client.getStoredAgent('support-agent').update({ instructions: [ { type: 'prompt_block_ref', id: 'default-user-prompt', rules: { operator: 'AND', conditions: [{ field: 'userPrompt', operator: 'not_exists' }] }, }, ], }); ``` - Fixed `processToolResult` aborts in output processors so they end the run at the first aborted tool result. Previously, aborting with parallel tool calls crashed the run with `Controller is already closed`, and aborting a single tool call made an extra model call after the stream had ended. ([#26157](https://github.com/mastra-ai/mastra/pull/26157)) - Fixed `ToolNotFoundError` when approving a tool loaded by `ToolSearchProcessor` with `storage: 'context'` after the run resumes without in-process state — for example a `DurableAgent` resuming after a restart or after its run registry entry expired, or `resumeStream` landing on a different instance. The resumed run now rebuilds the loaded tools from the thread's persisted messages. ([#25666](https://github.com/mastra-ai/mastra/pull/25666)) - Tool input validation errors now include the path of each invalid field. Previously, MCP 1.x servers returned errors like `Invalid input: expected array, received string` with no indication of which argument was wrong. Errors now read, for example: ([#25794](https://github.com/mastra-ai/mastra/pull/25794)) ``` - items.0.tags: Invalid input: expected array, received string - options.destination: Invalid input: expected string, received undefined ``` Fixes #25766. - Fixed two restart problems on durable runs. ([#26035](https://github.com/mastra-ai/mastra/pull/26035)) **Recovering a suspended durable agent run** now fails immediately with a clear message pointing at `resume()`. Previously it resolved, then emitted a lone "This workflow run was not active" error. Continue a run that is suspended on a tool call or an approval with `resume(runId, ...)`. **Restarting an evented workflow in a process that has not started its workers** now starts them before the restart is published. Previously the restart was published to no one, so the run stalled forever with no error, and starting the workers later could not revive it. This does not change instances configured with `workers: false` (or `MASTRA_WORKERS=false`). Those still publish the restart to the broker, so the run completes only if another worker consumes it. - Fixed agents sending an invented `.` user message before a conversation that starts with an assistant message, such as a voice agent that greets first. Mastra now adds it only for Amazon Bedrock and Google Gemini, which reject assistant-first conversations. OpenAI, Anthropic, Groq and other providers get the conversation unchanged. The message is added by the default `ProviderHistoryCompat` processor, after your input processors run, and is still not saved to memory. Fixes [#22874](https://github.com/mastra-ai/mastra/issues/22874). ([#25074](https://github.com/mastra-ai/mastra/pull/25074)) `MessageList` prompt getters (such as `messageList.get.all.aiV5.prompt()`) no longer add the `.` message, and the `ensureGeminiCompatibleMessages` helper is removed. If you build prompts from a `MessageList` yourself and send them to Bedrock or Gemini, add the user turn before a leading assistant message: ```ts const prompt = messageList.get.all.aiV5.prompt(); const first = prompt.findIndex(message => message.role !== 'system'); if (prompt[first]?.role === 'assistant') { prompt.splice(first, 0, { role: 'user', content: '.' }); } ``` - Added captureThreadRunTerminalPublish() to detect when an open thread continuation has delivered terminal output. ([#25759](https://github.com/mastra-ai/mastra/pull/25759)) - Fixed onIterationComplete feedback delivery when the default agent loop has finished. ([#25761](https://github.com/mastra-ai/mastra/pull/25761)) - Fixed Workspace search indexing creating an extra chunk at the end of large files that only repeated content from the previous chunk. ([#26166](https://github.com/mastra-ai/mastra/pull/26166)) - Fixed cron schedules whose cadence runs out, such as year-pinned crons. ([#25654](https://github.com/mastra-ai/mastra/pull/25654)) - The final occurrence now fires once, and the schedule then moves to `completed`. - Editing or resuming a schedule with no future occurrence now completes it instead of failing, whatever status the row held. - An invalid cron expression or timezone now returns an input error (400) instead of a server error. - `ScheduleStatus` now includes `completed`, so code that switches over every schedule status needs a branch for it. ## 1.75.0-alpha.8 ### Patch Changes - Fixed three cases where a durable agent's stream, or a regular agent's saved approval metadata, did not match what the stream reported. ([#26006](https://github.com/mastra-ai/mastra/pull/26006)) - Tool result chunks now include `providerExecuted`, which tells the caller whether the tool ran on the provider's side. It was missing on durable runs, so clients received `undefined` where regular agents report the value. - Approval requests on durable agents now publish the same `resumeSchema` as regular agents, including `$schema`, `additionalProperties: false` and the field descriptions. The accepted resume data is unchanged. - Approval metadata saved by regular agents now carries the same `resumeSchema` as the live approval request, including the optional `reason` field the saved copy was missing. The accepted resume data is unchanged. - Fixed `fetchWithRetry` ignoring cancellation. An already-aborted `signal` now rejects immediately, and aborting during a request or backoff delay stops further retries instead of waiting through the remaining delays. Retries for non-aborted requests are unchanged. ([#26164](https://github.com/mastra-ai/mastra/pull/26164)) - Fixed pausing, clearing, or replacing a goal while its judge is running being silently undone. The judge's verdict is now discarded when the objective changed during evaluation, so the agent stops instead of continuing a goal you already stopped. ([#26093](https://github.com/mastra-ai/mastra/pull/26093)) - Fixed `goal.scorer` so a string resolves a registered scorer by its id, as documented. Previously it was looked up by registration key, so a scorer registered as `scorers: { testsPass }` with id `tests-pass` failed with "Scorer with tests-pass not found" and paused the goal. Registration keys still work as a fallback. ([#26154](https://github.com/mastra-ai/mastra/pull/26154)) - Fixed evented agents hanging forever when workflow workers run in a separate process (for example, an API server started with `MASTRA_WORKERS=false` plus a dedicated worker deployment). Runs now reach the worker process and stream back to the caller. Values used in more than one place in a workflow now keep their data when sent between processes instead of arriving as `null`. A process without workers now logs a warning instead of silently dropping a workflow it can only run locally. ([#26036](https://github.com/mastra-ai/mastra/pull/26036)) - Fixed agents running out of memory when `untilIdle` is set in `defaultOptions`. `agent.stream()` and `agent.resumeStream()` now wait for background tasks once instead of looping endlessly before calling the model. Fixes [#26043](https://github.com/mastra-ai/mastra/issues/26043). ([#26125](https://github.com/mastra-ai/mastra/pull/26125)) - Fixed agent runs stopping with "Interrupted" when an Observational Memory reflection didn't compress enough on its first try. The reflector retries at a stronger compression level, but each retry was reported as a failure, and the agent controller cancelled the run before the retry could finish. Retry attempts are now marked as retrying, so only a final failure stops the run. ([#26105](https://github.com/mastra-ai/mastra/pull/26105)) - Fixed `ResponseCache` sharing cached responses between users who are identified only through `memory: { resource }`. When no `scope` is set, the cache now falls back to the memory resource ID after the auth resource ID, so each user gets their own cache entries. Set `scope: null` to keep sharing responses across users. ([#26192](https://github.com/mastra-ai/mastra/pull/26192)) - Fixed `processToolResult` aborts in output processors so they end the run at the first aborted tool result. Previously, aborting with parallel tool calls crashed the run with `Controller is already closed`, and aborting a single tool call made an extra model call after the stream had ended. ([#26157](https://github.com/mastra-ai/mastra/pull/26157)) - Fixed two restart problems on durable runs. ([#26035](https://github.com/mastra-ai/mastra/pull/26035)) **Recovering a suspended durable agent run** now fails immediately with a clear message pointing at `resume()`. Previously it resolved, then emitted a lone "This workflow run was not active" error. Continue a run that is suspended on a tool call or an approval with `resume(runId, ...)`. **Restarting an evented workflow in a process that has not started its workers** now starts them before the restart is published. Previously the restart was published to no one, so the run stalled forever with no error, and starting the workers later could not revive it. This does not change instances configured with `workers: false` (or `MASTRA_WORKERS=false`). Those still publish the restart to the broker, so the run completes only if another worker consumes it. - Fixed Workspace search indexing creating an extra chunk at the end of large files that only repeated content from the previous chunk. ([#26166](https://github.com/mastra-ai/mastra/pull/26166)) ## 1.75.0-alpha.7 ### Minor Changes - Changed AgentController sessions to keep one active model instead of a separate model for each mode. Switching modes no longer changes the model automatically, and `session.model.switch` no longer accepts `modeId` or `scope`. The `model_changed` event no longer includes `modeId` or `scope`; consumers should read its `modelId` and current `thinkingLevel` fields. When an existing thread is first reopened, its active legacy per-mode selection is copied to `currentModelId`, a migration marker is stored, and obsolete `modeModelId_*` keys are removed. Later model selections persist as the thread's authoritative `currentModelId`. Use `session.model.set` for an in-memory selection that should not be persisted or emit a model-change event. ([#25997](https://github.com/mastra-ai/mastra/pull/25997)) **Before** ```ts await session.model.switch({ modelId: 'openai/gpt-5.6', modeId: 'build' }); await session.mode.switch({ modeId: 'plan' }); // Restored the plan mode model. ``` **After** ```ts await session.model.switch('openai/gpt-5.6'); await session.mode.switch({ modeId: 'plan' }); // Keeps openai/gpt-5.6 active. ``` - Changed `session.model.switch` to accept a model ID followed by an optional options object. Pass `{ thinkingLevel }` to apply and persist model and thinking level together. Every `model_changed` event includes the current thinking level, even when it is unchanged. Sessions synchronize both preferences from persisted thread settings before the next request, including thinking-only changes and removed overrides. Switches canceled by a thread change before any selection is committed reject without counting model use; writes already committed to the captured thread remain successful. ([#26069](https://github.com/mastra-ai/mastra/pull/26069)) ```ts // Before await session.model.switch({ modelId: 'openai/gpt-5.5' }); await session.state.set({ thinkingLevel: 'high' }); // After await session.model.switch('openai/gpt-5.5', { thinkingLevel: 'high' }); ``` ### Patch Changes - Fixed MCP resource reads dropping `mimeType` and `_meta`. Reading a resource from a server registered through `MCPClient` (`MCPClientServerProxy.readResource()`) and reading an app resource from a local `MCPServer` (`MCPServer.readResource()`, used by Studio) now return the same metadata as `listResources()` and the MCP `resources/read` request, so MCP App `ui://` resources keep their content type and UI settings such as CSP. Fixes #23068. ([#25992](https://github.com/mastra-ai/mastra/pull/25992)) - Fixed workspace search reporting the wrong `lineRange` when a custom tokenizer preserves case. Highlighting now uses the same tokens as retrieval, so searching `Python` points at the line containing `Python` rather than a line containing `python`. ([#26014](https://github.com/mastra-ai/mastra/pull/26014)) - Fixed directly resumed nested workflows so successful children continue suspended parent runs. Fixes #24588. ([#25817](https://github.com/mastra-ai/mastra/pull/25817)) - Resource read results from MCP servers now include the optional `mimeType` and `_meta` fields, both in the `MCPServerBase.readResource()` type and in the `POST /mcp/:serverId/resources/read` response returned to `readMcpServerResource()`. ([#25992](https://github.com/mastra-ai/mastra/pull/25992)) - Fixed streamed `PIIDetector` output with the `block` and `filter` strategies. When a Social Security number, email address or similar value arrived split across several stream chunks, the first part of it reached the user before the rest was recognized. Now `block` stops the response before any part of the value is shown, and `filter` removes the text up to and including the value while keeping the text after it. Very long email addresses are now also held until they are complete, including with `redact`. As with `redact`, the end of a response may arrive slightly later. ([#26082](https://github.com/mastra-ai/mastra/pull/26082)) - Fixed thread streams treating a run as finished while it was still waiting for a tool approval or a suspended tool. When thread updates were delivered with a delay (for example with `@mastra/redis-streams`), the thread could unblock and subscribers on other servers could lose the pending approval. The run now stays waiting until the approval or tool resumes it. ([#25962](https://github.com/mastra-ai/mastra/pull/25962)) ## 1.75.0-alpha.6 ### Patch Changes - Fixed agents sending an invented `.` user message before a conversation that starts with an assistant message, such as a voice agent that greets first. Mastra now adds it only for Amazon Bedrock and Google Gemini, which reject assistant-first conversations. OpenAI, Anthropic, Groq and other providers get the conversation unchanged. The message is added by the default `ProviderHistoryCompat` processor, after your input processors run, and is still not saved to memory. Fixes [#22874](https://github.com/mastra-ai/mastra/issues/22874). ([#25074](https://github.com/mastra-ai/mastra/pull/25074)) `MessageList` prompt getters (such as `messageList.get.all.aiV5.prompt()`) no longer add the `.` message, and the `ensureGeminiCompatibleMessages` helper is removed. If you build prompts from a `MessageList` yourself and send them to Bedrock or Gemini, add the user turn before a leading assistant message: ```ts const prompt = messageList.get.all.aiV5.prompt(); const first = prompt.findIndex(message => message.role !== 'system'); if (prompt[first]?.role === 'assistant') { prompt.splice(first, 0, { role: 'user', content: '.' }); } ``` ## 1.75.0-alpha.5 ### Minor Changes - Added an explicit reconcile step for channel installations whose connect flow finishes outside the app (for example Discord's bot invite, which never redirects back). Channel providers can implement the new optional `reconcileInstallation(agentId)` method, exposed over `POST /api/channels/:platform/:agentId/reconcile` and `client.channels.reconcileInstallation(platform, agentId)`. The route requires the same write access as connecting, and returns the agent's fresh installation — or `null` when the platform doesn't support reconciliation. Listing installations is now a pure read and never changes state. ([#25993](https://github.com/mastra-ai/mastra/pull/25993)) ```ts const installation = await client.channels.reconcileInstallation('discord', 'my-agent'); // { id, platform, agentId, status: 'active', ... } once the invite completed ``` - Added optional `title`, `websiteUrl`, and `icons` options to `MCPServerConfig`, exposed on `MCPServerBase`, so MCP server implementations can announce a display title, website, and icons to clients. ([#25985](https://github.com/mastra-ai/mastra/pull/25985)) - Added an `outputValidation` option to `createTool` ([#23799](https://github.com/mastra-ai/mastra/issues/23799)). When a tool's result fails `outputSchema` validation, Mastra replaces the result with a validation error, so the model is told the call failed. That happens even when the tool's side effect (an order placed, a message sent) has already happened, which invites a retry. Set `outputValidation: 'warn'` to return the tool's actual result unchanged instead. `'strict'` is the default and keeps the existing behavior. ([#26001](https://github.com/mastra-ai/mastra/pull/26001)) ```ts export const placeOrderTool = createTool({ id: 'place-order', description: 'Places an order', inputSchema: z.object({ sku: z.string(), quantity: z.number() }), outputSchema: z.object({ orderId: z.string(), total: z.number() }), outputValidation: 'warn', execute: async ({ sku, quantity }) => orders.create({ sku, quantity }), }); ``` Also improved how output validation failures are reported: - Every output validation failure now writes a warning to the Mastra logger. Previously, a failure from a `createTool` tool left no trace in the logs. - The tool call trace span is now marked as failed when a `createTool` tool returns a validation error. Previously it was recorded as successful. - Sensitive fields such as `apiKey`, `token`, and `password` are now redacted from the tool output shown in the validation error message. ### Patch Changes - Added an optional `scope` field to `AuthorizeOpts` so tool providers know whether a new connection is shared, per-author, or caller-supplied. ([#26002](https://github.com/mastra-ai/mastra/pull/26002)) - Fixed DurableAgent traces after a resume or crash recovery. The resumed or recovered agent run is now nested under the original agent run, so the trace stays a single tree instead of splitting into two root spans. ([#25862](https://github.com/mastra-ai/mastra/pull/25862)) Crash recovery traces are also complete now: the agent run left open by the stopped process is ended with an `interrupted` status, and the recovered agent run is ended when the run finishes. Previously a recovered trace could be missing from the trace list or show only the part before the crash. - Fixed a thread failing on every turn when one of its attachments can't be used: a file that now returns 404, a relative path such as `/api/images/foo.png`, or inline content that isn't valid file data. If error processors and fallback models don't recover a failed download, the agent now answers with an `[Attachment unavailable: ]` placeholder and logs a warning. The attachment is recorded on its stored message, so later turns reuse the placeholder without downloading it again. See #23705. ([#25051](https://github.com/mastra-ai/mastra/pull/25051)) - Added optional `rules` to `prompt_block_ref` instruction blocks so stored agents can save and preview per-usage display conditions on prompt block references. ([#25991](https://github.com/mastra-ai/mastra/pull/25991)) ```ts await client.getStoredAgent('support-agent').update({ instructions: [ { type: 'prompt_block_ref', id: 'default-user-prompt', rules: { operator: 'AND', conditions: [{ field: 'userPrompt', operator: 'not_exists' }] }, }, ], }); ``` ## 1.75.0-alpha.4 ### Minor Changes - Added `traceId` to agent stream chunks, next to `runId`. Clients that call `/stream` can now link a run to its trace without switching to `/generate`. The field is undefined when tracing is disabled. Custom `data-*` chunks (for example, from `writer.custom()`) are left exactly as written and don't include `traceId`. Fixes [#25811](https://github.com/mastra-ai/mastra/issues/25811). ([#25927](https://github.com/mastra-ai/mastra/pull/25927)) ```ts const stream = await agent.stream('hi'); for await (const chunk of stream.fullStream) { console.log(chunk.runId, chunk.traceId); } ``` ### Patch Changes - Fixed model call spans that listed tools the model did not receive. Each `MODEL_INFERENCE` span now has a `tools` attribute with the tool definitions (name, description, parameters) sent to the provider on that call, after input processors, `prepareStep`, `activeTools`, and `toolChoice` are applied. `availableTools` on the same span now matches it: it is empty when `toolChoice` is `'none'`, and it no longer lists names that are not registered tools. ([#25917](https://github.com/mastra-ai/mastra/pull/25917)) - Removed the @experimental annotation from Agent goal APIs (goal config, setObjective, GoalSignalProvider) now that goals are stable. ([#25941](https://github.com/mastra-ai/mastra/pull/25941)) - Removed the @experimental annotation from Agent signal APIs (sendSignal, subscribeToThread, sendMessage, queueMessage, cancelQueuedMessages, abortThread, state and notification signals, and signal providers) now that signals are stable. ([#25946](https://github.com/mastra-ai/mastra/pull/25946)) ## 1.75.0-alpha.3 ### Minor Changes - Added a storage API for querying individual completed spans with filters, cursors, bounded previews, and model cost. ([#25791](https://github.com/mastra-ai/mastra/pull/25791)) Shared cursor, timeout, and resource-limit errors now say "query" so they also describe span queries accurately. Error codes are unchanged. ```typescript import { planSpanQuery } from '@mastra/core/storage'; const result = await storage.querySpans( planSpanQuery({ timeRange: { from: '2026-10-01T00:00:00Z', to: '2026-10-02T00:00:00Z' }, where: { op: 'eq', left: { path: 'spanType' }, right: { literal: 'tool_call' } }, }), ); ``` ### Patch Changes - Fixed a leak where an output stream processor that throws let the original, unprocessed chunk through. For example, a redaction processor that crashed would still stream the text it was meant to redact. ([#25826](https://github.com/mastra-ai/mastra/pull/25826)) Now streamed content chunks processed inside regular, durable, and evented runs stop before the failing chunk is emitted, emit an `error` chunk, and finish with `finishReason: 'error'`. Durable tool-chunk processor failures also fail their workflow step before the tool chunk is published. For durable and evented agents, failures on lifecycle chunks such as `start`, `step-start`, `step-finish`, and `finish` error `fullStream` and reject derived output promises. For regular agents, `step-start` and `finish` failures emit an `error` chunk and resolve with `finishReason: 'error'`, while a `step-finish` failure errors the stream; `start` is not passed to output stream processors. Previously the processor error was logged and the unprocessed chunk was emitted while the stream finished with `finishReason: 'stop'`. `abort()` in a processor still blocks the chunk as before. ```ts const result = await agent.stream('Reveal the card number'); try { for await (const chunk of result.fullStream) { if (chunk.type === 'error') { console.error(chunk.payload.error); // e.g. "redactor crashed" } } } catch (error) { // Some lifecycle-chunk processor failures error the stream directly. console.error(error); } ``` ## 1.75.0-alpha.2 ### Minor Changes - Added the `StorageMemoryRef` type so a stored agent's `memory` field can reference a registered memory instance with `{ type: 'id', memoryId }`, or hold an inline config. Existing inline configs are still accepted. ([#25850](https://github.com/mastra-ai/mastra/pull/25850)) - Added token and cost measures to `aggregateTraces()`. Requests can now ask for `tokens.input`, `tokens.output`, `tokens.total`, `tokens.reasoning`, and `tokens.cached` (each as `.sum` or `.avg`), plus `cost.sum` and `cost.avg`, and use them in `having` and `orderBy`. ([#25735](https://github.com/mastra-ai/mastra/pull/25735)) Usage is summed per trace first, then per group. Averages are per trace with token usage, so traces with no recorded usage don't pull them down. Any request for a `cost.*` measure also returns `cost` on each row: `cost.coverage` is the share of traces with token usage whose cost was fully priced, and `cost.unit` is the currency. When a group mixes currencies, `cost.sum` and `cost.avg` are `null` and `cost.unit` is `"mixed"`. Groups whose measure is `null` sort last and never satisfy a `having` condition. ```ts import { parseTraceAggregateRequest, planTraceAggregate } from '@mastra/core/storage'; const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-06-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, groupBy: ['entityName'], interval: '1d', measures: ['count', 'tokens.input.sum', 'tokens.output.sum', 'cost.sum'], orderBy: { field: 'cost.sum', direction: 'desc' }, limit: 20, }), ); const { rows } = await storage.aggregateTraces(plan); // rows[0].measures → { count, 'tokens.input.sum', 'tokens.output.sum', 'cost.sum' } // rows[0].cost → { coverage: 0.96, unit: 'usd' } ``` `TokenMetrics`, the names of the token usage metrics, is now exported from `@mastra/core/observability`. ### Patch Changes - Fixed `subscribeToThread({ withInitialHistory: true })` returning `thread-history` messages newest first. The initial history now lists the newest page of messages oldest first, matching `listThreadMessages` and the documented behavior. ([#25821](https://github.com/mastra-ai/mastra/pull/25821)) - Fixed dataset schema updates validating items against a previously proposed schema, and fixed items being validated against the old schema after changing the schema of an empty dataset. ([#25659](https://github.com/mastra-ai/mastra/pull/25659)) - Fixed call-time `clientTools` being dropped by durable agents (such as Inngest agents) when the workflow worker runs in a different process from the caller. Client tool schemas are now carried on the workflow input and restored when the worker rebuilds the run's tools, so the model can call them as expected. ([#25664](https://github.com/mastra-ai/mastra/pull/25664)) - Fix durable and evented tool approvals so each approval resumes its own tool call and the agent finishes after the last approval ([#25831](https://github.com/mastra-ai/mastra/pull/25831)) - Fixed agent runs failing when the goal judge's feedback signal could not be streamed (for example, because a durable agent's transport had closed). The verdict is still recorded and the run finishes normally. ([#25665](https://github.com/mastra-ai/mastra/pull/25665)) - Added internal groundwork for querying individual spans across traces. ([#25701](https://github.com/mastra-ai/mastra/pull/25701)) - Fixed durable threads that reported suspended tool calls as lost after a restart. Pending tool calls remain available for a response. ([#25764](https://github.com/mastra-ai/mastra/pull/25764)) - Fixed `DurableAgent` and `EventedAgent` runs to fail and report the error when mapping a server-executed tool result, including an awaited background result, throws in `toModelOutput`. Client-executed tools and completed deferred background tasks continue with the raw result when their mapper throws. ([#25762](https://github.com/mastra-ai/mastra/pull/25762)) - Fixed `stream(..., { untilIdle: true })` repeating earlier output when a background task triggers a follow-up turn. The supplied `runId` can abort the initial or follow-up turn through `abortRunStream(runId)` or `abortThreadStream(...)`. ([#25661](https://github.com/mastra-ai/mastra/pull/25661)) - Fixed skill publishing so it rejects JavaScript frontmatter (`---js`) in `SKILL.md` instead of running it. Publishing now parses `SKILL.md` the same way as loading and validating a skill. Fixes [#25551](https://github.com/mastra-ai/mastra/issues/25551). ([#25689](https://github.com/mastra-ai/mastra/pull/25689)) - Fixed batched channel messages from other senders skipping custom `onMention`, `onDirectMessage`, and `onSubscribedMessage` handlers when `burst`, `debounce`, or `queue` concurrency is set. ([#25854](https://github.com/mastra-ai/mastra/pull/25854)) Your handler now runs once for each sender's turn, with its own `ctx.requestContext`, `ctx.signalMetadata`, and `ctx.skipped`. A handler can now be called several times for one dispatch. - Cancelling a run while structured output is being generated no longer logs a false "Structured output processing failed" error. The structuring model call is now cancelled along with the run, and no output chunks are written after the run's stream has closed. ([#25662](https://github.com/mastra-ai/mastra/pull/25662)) - Fixed `restart()` for evented workflows that crashed while resuming a nested workflow. The resumed step now keeps its resume data and completes on restart instead of suspending again. Fixes [#25365](https://github.com/mastra-ai/mastra/issues/25365). ([#25630](https://github.com/mastra-ai/mastra/pull/25630)) - Fixed durable and evented agent terminal behavior. Aborted runs now emit an `abort` chunk, report `finishReason` as `aborted`, and call `onAbort` instead of `onFinish`. Failed runs call `onError` instead of `onFinish`. `resume()`, `observe()`, and `recover()` now accept an `onAbort` callback. ([#25765](https://github.com/mastra-ai/mastra/pull/25765)) - Fixed error-processor retries being dropped when a run reached its `maxSteps` limit. An agent run with `maxSteps: 1` that hit a transient 429/5xx would previously resolve as a successful empty (or partial) response with `finishReason: 'retry'`. Retries now re-run the failed step without counting against `maxSteps`, and if every retry fails the run ends with `finishReason: 'error'` and the last provider error (including fields like `statusCode`) on `output.error` and `onError`, on the default, durable, and evented engines. ([#25804](https://github.com/mastra-ai/mastra/pull/25804)) - Fixed fresh-process durable agent resumes replaying suspended output when cached stream history is unavailable. ([#25760](https://github.com/mastra-ai/mastra/pull/25760)) - Fixed `ToolNotFoundError` when approving a tool loaded by `ToolSearchProcessor` with `storage: 'context'` after the run resumes without in-process state — for example a `DurableAgent` resuming after a restart or after its run registry entry expired, or `resumeStream` landing on a different instance. The resumed run now rebuilds the loaded tools from the thread's persisted messages. ([#25666](https://github.com/mastra-ai/mastra/pull/25666)) - Added captureThreadRunTerminalPublish() to detect when an open thread continuation has delivered terminal output. ([#25759](https://github.com/mastra-ai/mastra/pull/25759)) - Fixed onIterationComplete feedback delivery when the default agent loop has finished. ([#25761](https://github.com/mastra-ai/mastra/pull/25761)) - Fixed cron schedules whose cadence runs out, such as year-pinned crons. ([#25654](https://github.com/mastra-ai/mastra/pull/25654)) - The final occurrence now fires once, and the schedule then moves to `completed`. - Editing or resuming a schedule with no future occurrence now completes it instead of failing, whatever status the row held. - An invalid cron expression or timezone now returns an input error (400) instead of a server error. - `ScheduleStatus` now includes `completed`, so code that switches over every schedule status needs a branch for it. ## 1.75.0-alpha.1 ### Minor Changes - Semantic recall now works with a vector store that generates embeddings itself, so a self-embedding store needs no client-side `embedder`. ([#25009](https://github.com/mastra-ai/mastra/pull/25009)) Added `isSelfEmbedding` to `MastraVector`. It defaults to false, so every existing store is unaffected. A store that embeds text itself overrides it to return true, and semantic recall against that store needs no `embedder`: ```ts const memory = new Memory({ storage, vector: new MongoDBVector({ id: 'vec', uri, dbName, autoEmbed: { model: 'voyage-4' } }), options: { semanticRecall: true }, }); ``` A configured `embedder` still takes precedence, so adding one to the example above returns to client-side embedding. Messages embedded by the store are kept in an index named `memory_messages_selfembed`, separate from the indexes holding client-supplied vectors. Configuring semantic recall with neither an embedder nor a self-embedding store now says so, naming both options. - Added a `createSession()` option to start a session without creating a thread, and `session.thread.ensureId()` to create one on first use. Sessions that are never used no longer leave empty threads behind. Sending a message or signal creates the thread automatically. The default is unchanged: sessions still get a thread when none matches. ([#22561](https://github.com/mastra-ai/mastra/pull/22561)) ```ts const session = await controller.createSession({ createInitialThread: false }); // Returns the current thread, or creates one. Concurrent calls share one thread. const threadId = await session.thread.ensureId(); ``` ### Patch Changes - Fixed a process crash with `TypeError: Invalid state: ReadableStream is locked` after a provider errored mid-stream. The crash happened when an agent had output processors, such as `BatchPartsProcessor` or `RegexFilterProcessor`, and a retrying error processor, such as `StreamErrorRetryProcessor`. Output processors no longer carry buffered parts across the retry. A stream that is already being read now reports through `onError` instead of raising an unhandled rejection, which on Node 22 exited the process and dropped every in-flight run. ([#25803](https://github.com/mastra-ai/mastra/pull/25803)) - Fixed `execute_command` and `get_process_output` results for commands stopped by an aborted run. The result now says the command was aborted instead of showing only a kill exit code such as `Exit code: 128`, which agents mistook for a real command failure. This covers foreground commands, background processes killed when the run that started them is aborted, and processes killed while `get_process_output` was waiting on them. ([#25752](https://github.com/mastra-ai/mastra/pull/25752)) `get_process_output` on a background process that exited without printing anything now returns its exit status (for example `Exit code: 0`) instead of `(no output yet)`, so it no longer looks like the process is still running. - Fixed executor interruptions so default-engine workflow runs remain recoverable instead of being recorded as canceled. Fixes #24586. ([#24684](https://github.com/mastra-ai/mastra/pull/24684)) - Fixed structured output failing with 400 errors on OpenAI-compatible providers (for example Azure AI Foundry through `@ai-sdk/openai-compatible` with `supportsStructuredOutputs: true`). These providers send a strict JSON schema, but Mastra only prepared schemas for strict mode when the provider name started with `openai`, so requests failed with errors like `'uniqueItems' is not permitted` or `Missing 'subject'`. Schemas are now prepared whenever the model sends a strict JSON schema, and `null` values for optional fields are accepted in the response. Setting `strictJsonSchema: false` in the provider options keeps the schema unchanged. ([#25707](https://github.com/mastra-ai/mastra/pull/25707)) - Tool input validation errors now include the path of each invalid field. Previously, MCP 1.x servers returned errors like `Invalid input: expected array, received string` with no indication of which argument was wrong. Errors now read, for example: ([#25794](https://github.com/mastra-ai/mastra/pull/25794)) ``` - items.0.tags: Invalid input: expected array, received string - options.destination: Invalid input: expected string, received undefined ``` Fixes #25766. ## 1.75.0-alpha.0 ### Minor Changes - Added support for several processes sharing one notification store, where only some of them can run a given thread. ([#25741](https://github.com/mastra-ai/mastra/pull/25741)) `dispatchDueNotifications` accepts a `resourceId` to dispatch only that resource's due notifications. A delivery policy can return `hold: true` to leave a due notification pending for another dispatcher, without counting a delivery attempt. `hold` only applies when a due notification is dispatched. When a notification is first sent, return an action that does not deliver it, such as `defer`: ```ts import { defaultNotificationDeliveryDecision } from '@mastra/core/notifications'; const agent = new Agent({ // ... notifications: { deliveryPolicy: { decide: input => canRunHere(input.record.resourceId) ? defaultNotificationDeliveryDecision(input) : { action: 'defer', deliverAt: input.now, hold: true }, }, }, }); ``` - Added the per-provider object form to the `ObservationalMemoryActivationTTL` type, so `activateAfterIdle` accepts values like `{ default: 'auto', anthropic: '1h' }`. New `ObservationalMemoryActivationTTLValue` and `ObservationalMemoryActivationTTLByProvider` types are exported. ([#25727](https://github.com/mastra-ai/mastra/pull/25727)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b54fda3`](https://github.com/mastra-ai/mastra/commit/b54fda3f30330d65e52bf34802f0aa4035e30ef8)) - Fixed approval ordering for tools added by input processors, such as `ToolSearchProcessor`. Approving one call now releases the next call's approval request instead of leaving the run stuck. ([#25639](https://github.com/mastra-ai/mastra/pull/25639)) Fixed `foreach` behavior for all evented workflows. Evented workflows now stay suspended until unfinished iterations resume, and queued iterations start only when capacity is available. - Persisted channel thread history as individually attributed messages, including attachments and a marker for omitted messages, instead of one text block on first mention. ([#25717](https://github.com/mastra-ai/mastra/pull/25717)) - Preserved extra fields in tool results passed to model messages, client callbacks, and loop callbacks. Client `onOutput` and `toModelOutput` callbacks now receive results containing only a `value` field as that field's value. AI SDK error outputs are stored as failed invocations, skipped by client callbacks, and exposed to response consumers as their underlying error value. ([#25680](https://github.com/mastra-ai/mastra/pull/25680)) - Fixed sendToolApproval to preserve separate approval decisions when custom resume data is provided. Approval-gated calls now reject custom resume data unless it is a non-null object that can carry the decision. ([#25632](https://github.com/mastra-ai/mastra/pull/25632)) - Fixed requests failing with empty assistant content after switching models mid-thread, such as from an OpenAI reasoning model to Claude. When reasoning the new provider cannot accept is removed from history, assistant turns that contained only that reasoning are now left out instead of being sent empty. ([#23050](https://github.com/mastra-ai/mastra/pull/23050)) - Fixed aborting a thread run that had several messages sent to it: the follow-up run now answers all of them in one turn, instead of one message per run (which made you abort once per pending message). Messages added with `queueMessage` still run one at a time after it. ([#25749](https://github.com/mastra-ai/mastra/pull/25749)) - Fixed usage aggregation so omitted provider token counts remain unknown across agent, workflow, and durable streams. Reported cache and reasoning details remain additive. ([#25402](https://github.com/mastra-ai/mastra/pull/25402)) `AccumulatedUsage` from `@mastra/core/agent/durable` and `WorkflowDataPart['data']['output']['usage']` from `@mastra/ai-sdk` now represent incomplete primary counters as `undefined` instead of measured zeroes. When every input and output count is known, `totalTokens` can still be derived from those complete aggregates even if the provider omitted its total. Derived totals now sum input and output tokens without adding `reasoningTokens`. For example, `{ inputTokens: 10, outputTokens: 20, totalTokens: 30 }` followed by `{ outputTokens: 5 }` now produces `{ inputTokens: undefined, outputTokens: 25, totalTokens: undefined }`. Observability still records known per-step token contributions and marks their aggregate as incomplete. `TokenCostControl` treats the partial estimated cost as a known lower bound: hard and soft thresholds still apply when that lower bound crosses them, while lower values do not imply the complete cost is under budget. The Responses API returns `usage: null` for an incomplete aggregate instead of fabricating zero-valued counters. Durable iteration state written by this version may omit unknown primary counters and cannot be resumed by an older worker after a rollback. (#23469) ## 1.74.0 ### Minor Changes - Added `agent.getMessages()` to tool execution context in standard and durable agent loops. Tools can read the current conversation, including remembered messages and in-run responses, without changing the existing input-only `messages` field. ([#25525](https://github.com/mastra-ai/mastra/pull/25525)) ```ts execute: async (input, context) => { const messages = context?.agent?.getMessages?.() ?? []; return { messageCount: messages.length }; }; ``` The getter reflects message-list removals, but not transient transforms applied only to the provider prompt. Treat returned messages as read-only. - Added group filtering and generation ordering to observational memory history. For example, `getObservationalMemoryHistory(threadId, resourceId, 1, { groupId, sortDirection: "ASC" })` finds the earliest retained record containing a group in active observations or persisted buffered chunks. Adapters advertise support through `supportsObservationalMemoryHistorySearch`. Pass `recordId` to read one record by ID; it only matches records for the requested thread or resource. ([#25525](https://github.com/mastra-ai/mastra/pull/25525)) Convex users need to redeploy their Mastra server functions for these filters to apply. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ac54c46`](https://github.com/mastra-ai/mastra/commit/ac54c4617d1bebffe9e4c1034e084e25528a94c9)) - Fixed chat channel agents ignoring mentions of their current display name after being renamed. For example, a Slack app renamed from `acme-bot` to `helper` now responds to `@helper` in channels, instead of only recognising its original username. The bot's current profile name is looked up once through the adapter and exposed as `botDisplayName` on the channel context. ([#25652](https://github.com/mastra-ai/mastra/pull/25652)) - Raise transitive security dependency floors (dompurify, js-yaml, @ai-sdk/provider-utils) and bump nested/template deps (nodemailer, fastify, hono, ajv) for the 2026-10-01 Vanta remediation pass. ([#25694](https://github.com/mastra-ai/mastra/pull/25694)) ## 1.73.1-alpha.1 ### Patch Changes - Raise transitive security dependency floors (dompurify, js-yaml, @ai-sdk/provider-utils) and bump nested/template deps (nodemailer, fastify, hono, ajv) for the 2026-10-01 Vanta remediation pass. ([#25694](https://github.com/mastra-ai/mastra/pull/25694)) ## 1.73.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ac54c46`](https://github.com/mastra-ai/mastra/commit/ac54c4617d1bebffe9e4c1034e084e25528a94c9)) - Fixed chat channel agents ignoring mentions of their current display name after being renamed. For example, a Slack app renamed from `acme-bot` to `helper` now responds to `@helper` in channels, instead of only recognising its original username. The bot's current profile name is looked up once through the adapter and exposed as `botDisplayName` on the channel context. ([#25652](https://github.com/mastra-ai/mastra/pull/25652)) ## 1.73.0 ### Minor Changes - Every agent now recovers from transient provider failures, assistant-prefill rejections, and provider history incompatibilities with no configuration. Three error processors — `ProviderHistoryCompat`, `PrefillErrorHandler`, and `StreamErrorRetryProcessor` — are on by default, in the order that repairs history before anything retries. A `ProviderHistoryCompat` in `errorProcessors` now also repairs the outbound prompt before the provider sees it, instead of only reacting to a rejection. ([#24473](https://github.com/mastra-ai/mastra/pull/24473)) You stay in control of the list: ```ts // Replace one default: your instance with the same id takes its slot. // Retries above 3 also need maxProcessorRetries. new Agent({ ..., errorProcessors: [new StreamErrorRetryProcessor({ maxRetries: 5 })], maxProcessorRetries: 5 }) // Run only your own processors — none of the defaults are merged in. new Agent({ ..., errorProcessors: [myProcessor], errorProcessorDefaults: false }) // Run with no error processors at all. new Agent({ ..., errorProcessorDefaults: false }) ``` `errorProcessorDefaults: false` is the only opt-out: an empty `errorProcessors` list merges like any other and still gets the defaults. The default retry processor retries transient failures only. Transient failures recover through provider `isRetryable` metadata, the built-in OpenAI stream-error matcher, or the connection-reset matcher, while deterministic failures — a rejected structured-output attempt, a validation error, or any HTTP 400 the repair processors cannot fix — surface immediately instead of being replayed unchanged. Pass `StreamErrorRetryProcessor({ retryUnknownErrors: true })` in `errorProcessors` to retry unmatched errors. `createCodingAgent` keeps its shipped behavior, retrying both unmatched errors and bad-request responses, as it always has. Error-processor retries are bounded by a safety cap of `3` per turn when you don't set `maxProcessorRetries`; the defaults stop well below it on their own. The "errorProcessors are configured without an explicit maxProcessorRetries" warning now fires only when you configured error processors yourself, not for the framework defaults. - Export `findGatewayForModel` and `getGatewayId` from `@mastra/core/llm` so callers can check which registered gateway the model router would pick for a model ID. Like the router, `findGatewayForModel` skips disabled gateways. ([#25482](https://github.com/mastra-ai/mastra/pull/25482)) ```ts import { findGatewayForModel, getGatewayId } from '@mastra/core/llm'; const gateway = findGatewayForModel('acme/fast-1', Object.values(mastra.listGateways() ?? {})); console.log(getGatewayId(gateway)); ``` - Added a `tool-call-resumed` stream chunk. It is emitted when a suspended or approval-gated tool call resumes, on both regular and durable agents, and comes before that call's `tool-result`. `payload.kind` tells you whether it was a suspension or an approval. Clients can now tell that a suspension was answered even when a sub-agent suspended through a delegation call and the delegation's later result replaces the tool output. Fixes [#24280](https://github.com/mastra-ai/mastra/issues/24280). ([#25491](https://github.com/mastra-ai/mastra/pull/25491)) ```ts const stream = await agent.resumeStream({ answer: 'yes' }, { runId, toolCallId }); for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-resumed') { markAnswered(chunk.payload.toolCallId); } } ``` - Invalid tool-call IDs on Anthropic models are now rewritten in the outbound request, so they no longer reach Anthropic and no longer require a failed call to recover. Persisted history keeps its original IDs. The rule's reactive repair (`errorPatterns` and `fix`) stays as the fallback for Claude served through a provider the preemptive check doesn't recognize, such as Vertex-hosted Claude, and for placements outside the agent's prompt path. ([#24505](https://github.com/mastra-ai/mastra/pull/24505)) - Added an optional `resolveSubagentModel(modelId, { requestContext })` option to `AgentControllerConfig`. Subagent models can now resolve with the calling run's request context, so custom providers and tenant credentials work for subagents the same way they do for the main agent. The context passed in doesn't include the parent run's thread or resource IDs. ([#25482](https://github.com/mastra-ai/mastra/pull/25482)) ```ts const controller = new AgentController({ // ... resolveSubagentModel: (modelId, { requestContext }) => resolveTenantModel(modelId, requestContext), }); ``` ### Patch Changes - Fixed aborting an agent run taking up to 10 seconds when the provider reported low remaining rate-limit tokens. The pause between steps now ends as soon as the run's `abortSignal` fires. ([#25648](https://github.com/mastra-ai/mastra/pull/25648)) - Update provider registry and model documentation with latest models and providers ([`42b8761`](https://github.com/mastra-ai/mastra/commit/42b8761d917453cfe9b0b189c51442a5398fbf27)) - Fixed trace aggregation returning a server error when a `having` filter used `includes` or `notIncludes`. These operators are now rejected with a validation error. ([#25607](https://github.com/mastra-ai/mastra/pull/25607)) - Fixed saving builder workflows with classifier steps when the model sends `null` for `maxRetries` or `providerOptions`. These values are now treated as omitted, the same as `retries` and `metadata`, instead of failing validation. ([#25531](https://github.com/mastra-ai/mastra/pull/25531)) - Fixed startAsync so it returns only after workflow execution is durably recoverable. Fixes #24585. ([#24673](https://github.com/mastra-ai/mastra/pull/24673)) - Fixed misleading tool input validation errors. When a model sent `null` for optional fields and the call still failed for another reason, the error listed the `null` fields as problems and hid the real cause. Errors now report only the issues that remain after `null` values are removed, so models can fix the actual problem on the next attempt. ([#25656](https://github.com/mastra-ai/mastra/pull/25656)) - Fixed agent controller sessions losing the signed-in user when memory is configured as a function. Cloning a thread, forking a subagent, and opening a thread's history subscription (opening a session, switching or creating a thread, sending a message or notification, resuming a suspended tool) now pass the caller's request context to the memory factory. If the session already has the thread open, the open subscription is reused and its history keeps the memory it resolved when it opened. Per-user memory now resolves correctly instead of failing with a missing user context. ([#25082](https://github.com/mastra-ai/mastra/pull/25082)) Pass the caller's context when cloning, switching, or creating a thread: ```ts await session.thread.clone({ sourceThreadId, requestContext }); await session.thread.switch({ threadId, requestContext }); await session.thread.create({ title, requestContext }); await session.thread.delete({ threadId, requestContext }); ``` Deleting a thread also removes it from the caller's resolved memory, so a cloned thread's messages are not left behind. A message sent while a run is active joins that run and uses the memory resolved for the caller who started it. The next run resolves memory with the context of the caller whose message starts it. - Fixed durable agents stopping when an output processor asks for a retry. `abort(reason, { retry: true })` in `processOutputStep` now calls the model again with the reason as feedback, up to `maxProcessorRetries` (set on the agent or per call), like regular agents do. ([#25585](https://github.com/mastra-ai/mastra/pull/25585)) Fixed durable agent streams ending early when an output processor blocks a response. The stream now ends with a `finish` chunk, `finishReason` is `'tripwire'`, `onFinish` is called, and `output.tripwire` holds the reason. Durable streams no longer emit a separate `tripwire` chunk for this case, matching regular agents. See #22980. **Migrating from the `tripwire` chunk** If you listened for the `tripwire` chunk on a durable stream, read `finishReason` and `output.tripwire` instead: ```ts // Before for await (const chunk of output.fullStream) { if (chunk.type === 'tripwire') console.log(chunk.payload.reason); } // After for await (const chunk of output.fullStream) { // render chunks as usual } if ((await output.finishReason) === 'tripwire') { console.log(output.tripwire?.reason); } ``` - Fixed durable agents reporting `retryCount: 0` to processor hooks after an API-error retry. Hooks like `processInputStep`, `processLLMRequest` and `processOutputStep` now see the same retry count as with a regular `Agent`. ([#25642](https://github.com/mastra-ai/mastra/pull/25642)) - Fixed long-running evented workflow steps running twice when the broker redelivers the step event before the step finishes. Workers now send a heartbeat while a step runs, so the broker does not hand the event to another worker. Each step run is also fenced with a lease, so a redelivered copy is dropped while the original is still running. Redis Streams and Valkey Streams support the heartbeat. Steps should still be idempotent, because a worker crash can still cause a step to run again. ([#24769](https://github.com/mastra-ai/mastra/pull/24769)) - Fixed `run.timeTravel()` rejecting array `inputData` when targeting a top-level `.foreach()` step. Each array element is now validated against the step's input schema, so Studio's "Run next step" works at foreach loops, including for Inngest workflows. ([#25626](https://github.com/mastra-ai/mastra/pull/25626)) - Fixed durable agents running output processors twice on tool results. A `processOutputStream` processor now sees each tool result, tool error, and denied tool call once, so counters, billing, and edits to tool results are no longer applied twice. Fixes part of [#22980](https://github.com/mastra-ai/mastra/issues/22980). ([#25578](https://github.com/mastra-ai/mastra/pull/25578)) - Use Web Crypto for portable identifiers, random secrets, and asynchronous sandbox profile hashing ([#25462](https://github.com/mastra-ai/mastra/pull/25462)) - Fixed slow loading of long conversation histories. Loading stored messages took time that grew with the square of the thread length, so a thread with 10,000 messages could block the server for several seconds on every load. Loading now grows linearly with the thread length. Fixes [#24143](https://github.com/mastra-ai/mastra/issues/24143). ([#25061](https://github.com/mastra-ai/mastra/pull/25061)) - Tool approval and tool suspension requests now report whether they were actually accepted. Previously, `approveTool()` and `respondToToolSuspension()` always returned success, even when the decision was stale, a duplicate, or answered a question that was no longer pending. They now resolve to `{ ok: false, reason }` in those cases, so you can tell an accepted decision from one that was ignored. ([#25087](https://github.com/mastra-ai/mastra/pull/25087)) ```ts const ack = await session.approveTool(toolCallId, true); if (!ack.ok) console.warn(`Approval not applied: ${ack.reason}`); ``` `approveTool()` and `respondToToolSuspension()` no longer retry automatically. A retried decision would be rejected as already handled, so an applied decision could be reported as ignored. - Fixed evented workflows so concurrent resume calls continue a suspension only once. ([#25458](https://github.com/mastra-ai/mastra/pull/25458)) - Fixed `DurableAgent` construction to preserve dynamic model resolution until request context is available. Wrapping an agent with a request-dependent model no longer resolves the model eagerly during startup. ([#25359](https://github.com/mastra-ai/mastra/pull/25359)) - Reduced durable agent snapshot size by no longer saving the agent's system prompt on every step. Resume, tool approval, and trace rebuilds still use the copy kept in the run input, so behavior and traces are unchanged. ([#25609](https://github.com/mastra-ai/mastra/pull/25609)) - Include `threadId` and `resourceId` on lightweight trace list rows so the Thread ID and Resource ID columns render when the trace query API is unavailable. ([#25433](https://github.com/mastra-ai/mastra/pull/25433)) - Fixed filesystem storage writing MCP client config fields like `name` and `servers` onto the client record during `update()`. Only record-level fields are stored there now; config stays in versions. ([#25646](https://github.com/mastra-ai/mastra/pull/25646)) - Fixed durable agents rebuilding a run on another worker, or after a restart, with the wrong request processors. The rebuilt run now uses the processors configured on the wrapped agent, so processors that rewrite the outbound model request keep running, and a per-call `errorProcessors: []` still turns error processors off. ([#24473](https://github.com/mastra-ai/mastra/pull/24473)) - Fixed evented workflow restarts that occur before the first step is saved. ([#25389](https://github.com/mastra-ai/mastra/pull/25389)) - Fixed a follow-up message sent right after aborting a run replaying the aborted run. A new thread subscription no longer re-emits the aborted run's lifecycle events (such as a second `agent_start`) while that run is still shutting down. ([#25641](https://github.com/mastra-ai/mastra/pull/25641)) - Fixed `run.cancel()` overwriting the status of a workflow run that had already finished. Canceling a run that ended as `success`, `failed`, `canceled`, `tripwire` or `bailed` is now a no-op, so its stored status is preserved. This applies to both the default and evented workflow engines, and to the `POST /api/workflows/:workflowId/runs/:runId/cancel` route. ([#25414](https://github.com/mastra-ai/mastra/issues/25414)) ([#25628](https://github.com/mastra-ai/mastra/pull/25628)) - Fixed `stream.text` and the last step's `text` resolving to an empty string when an agent has an output processor and the run stops via `stopWhen` on a step that contains both text and a tool call. Output processors that intentionally clear text still work as before. ([#25381](https://github.com/mastra-ai/mastra/pull/25381)) - Fixed `ToolCallFilter` stripping a resumed run's own tool calls. When a tool suspended the agent (for example `askUserTool`) and the run was continued with `resumeStream()`, the filter treated the suspended tool call and its result as prior history and removed them from the model prompt. The model never saw its question or the user's answer, so it asked again and the run suspended forever. Tool calls made in the current run, including everything before a suspension, now stay in the prompt after resume. Fixes https://github.com/mastra-ai/mastra/issues/24382 ([#25566](https://github.com/mastra-ai/mastra/pull/25566)) - Fixed `resumeStream` running resumed tool calls one at a time under `toolCallConcurrency: { strategy: 'called' }`. Parallel-safe tool calls that paused together, such as sub-agent delegations, now resume in parallel. If one call pauses again, its siblings still continue. ([#25490](https://github.com/mastra-ai/mastra/pull/25490)) - Fixed background workflow tools filling Redis with oversized progress updates (#25590). When a background workflow step called an agent, each progress update carried a full copy of the model request and message history, so a single update could reach 60MB. Progress updates now leave those copies out and no longer repeat the task's arguments, while progress information such as text, finish reason, and usage is still delivered. ([#25606](https://github.com/mastra-ai/mastra/pull/25606)) - Fixed durable agents dropping custom `resumeData` on tool approval. When a caller approves a tool call with extra keys, for example `sendToolApproval({ approved: true, resumeData: { approved: true, note: 'hello' } })`, the durable agent now forwards that payload to the tool's `execute` through `context.agent.resumeData`, matching the non-durable agent. A bare `{ approved: true }` is still not forwarded. Fixes https://github.com/mastra-ai/mastra/issues/24561 ([#25562](https://github.com/mastra-ai/mastra/pull/25562)) - Fixed `LocalSandbox` seeding a working directory with a mix of two checkpoint versions when `snapshot()` replaced the checkpoint during `start()`. The seed now retries until it copies one complete checkpoint. ([#25528](https://github.com/mastra-ai/mastra/pull/25528)) - Fixed tool request context validation accepting invalid values when `requestContextSchema` is a Valibot schema. Valibot returns both a value and issues on failure, and the issues are now reported instead of being ignored. ([#25644](https://github.com/mastra-ai/mastra/pull/25644)) - Fixed goals using up a run when the goal judge errors. A failed judge call (for example, a dropped network connection) still pauses the goal so it can be resumed, but the failed attempt no longer counts against the goal's run limit. Fixes [#22446](https://github.com/mastra-ai/mastra/issues/22446). ([#25205](https://github.com/mastra-ai/mastra/pull/25205)) - Updated dependencies [[`4ac8bc7`](https://github.com/mastra-ai/mastra/commit/4ac8bc76e97278370f4ee0efabe71e9295f8f03f)]: - @mastra/schema-compat@1.3.13 ## 1.73.0-alpha.1 ### Minor Changes - Added a `tool-call-resumed` stream chunk. It is emitted when a suspended or approval-gated tool call resumes, on both regular and durable agents, and comes before that call's `tool-result`. `payload.kind` tells you whether it was a suspension or an approval. Clients can now tell that a suspension was answered even when a sub-agent suspended through a delegation call and the delegation's later result replaces the tool output. Fixes [#24280](https://github.com/mastra-ai/mastra/issues/24280). ([#25491](https://github.com/mastra-ai/mastra/pull/25491)) ```ts const stream = await agent.resumeStream({ answer: 'yes' }, { runId, toolCallId }); for await (const chunk of stream.fullStream) { if (chunk.type === 'tool-call-resumed') { markAnswered(chunk.payload.toolCallId); } } ``` ### Patch Changes - Fixed aborting an agent run taking up to 10 seconds when the provider reported low remaining rate-limit tokens. The pause between steps now ends as soon as the run's `abortSignal` fires. ([#25648](https://github.com/mastra-ai/mastra/pull/25648)) - Fixed trace aggregation returning a server error when a `having` filter used `includes` or `notIncludes`. These operators are now rejected with a validation error. ([#25607](https://github.com/mastra-ai/mastra/pull/25607)) - Fixed misleading tool input validation errors. When a model sent `null` for optional fields and the call still failed for another reason, the error listed the `null` fields as problems and hid the real cause. Errors now report only the issues that remain after `null` values are removed, so models can fix the actual problem on the next attempt. ([#25656](https://github.com/mastra-ai/mastra/pull/25656)) - Fixed agent controller sessions losing the signed-in user when memory is configured as a function. Cloning a thread, forking a subagent, and opening a thread's history subscription (opening a session, switching or creating a thread, sending a message or notification, resuming a suspended tool) now pass the caller's request context to the memory factory. If the session already has the thread open, the open subscription is reused and its history keeps the memory it resolved when it opened. Per-user memory now resolves correctly instead of failing with a missing user context. ([#25082](https://github.com/mastra-ai/mastra/pull/25082)) Pass the caller's context when cloning, switching, or creating a thread: ```ts await session.thread.clone({ sourceThreadId, requestContext }); await session.thread.switch({ threadId, requestContext }); await session.thread.create({ title, requestContext }); await session.thread.delete({ threadId, requestContext }); ``` Deleting a thread also removes it from the caller's resolved memory, so a cloned thread's messages are not left behind. A message sent while a run is active joins that run and uses the memory resolved for the caller who started it. The next run resolves memory with the context of the caller whose message starts it. - Fixed durable agents stopping when an output processor asks for a retry. `abort(reason, { retry: true })` in `processOutputStep` now calls the model again with the reason as feedback, up to `maxProcessorRetries` (set on the agent or per call), like regular agents do. ([#25585](https://github.com/mastra-ai/mastra/pull/25585)) Fixed durable agent streams ending early when an output processor blocks a response. The stream now ends with a `finish` chunk, `finishReason` is `'tripwire'`, `onFinish` is called, and `output.tripwire` holds the reason. Durable streams no longer emit a separate `tripwire` chunk for this case, matching regular agents. See #22980. **Migrating from the `tripwire` chunk** If you listened for the `tripwire` chunk on a durable stream, read `finishReason` and `output.tripwire` instead: ```ts // Before for await (const chunk of output.fullStream) { if (chunk.type === 'tripwire') console.log(chunk.payload.reason); } // After for await (const chunk of output.fullStream) { // render chunks as usual } if ((await output.finishReason) === 'tripwire') { console.log(output.tripwire?.reason); } ``` - Fixed durable agents reporting `retryCount: 0` to processor hooks after an API-error retry. Hooks like `processInputStep`, `processLLMRequest` and `processOutputStep` now see the same retry count as with a regular `Agent`. ([#25642](https://github.com/mastra-ai/mastra/pull/25642)) - Fixed `run.timeTravel()` rejecting array `inputData` when targeting a top-level `.foreach()` step. Each array element is now validated against the step's input schema, so Studio's "Run next step" works at foreach loops, including for Inngest workflows. ([#25626](https://github.com/mastra-ai/mastra/pull/25626)) - Fixed durable agents running output processors twice on tool results. A `processOutputStream` processor now sees each tool result, tool error, and denied tool call once, so counters, billing, and edits to tool results are no longer applied twice. Fixes part of [#22980](https://github.com/mastra-ai/mastra/issues/22980). ([#25578](https://github.com/mastra-ai/mastra/pull/25578)) - Tool approval and tool suspension requests now report whether they were actually accepted. Previously, `approveTool()` and `respondToToolSuspension()` always returned success, even when the decision was stale, a duplicate, or answered a question that was no longer pending. They now resolve to `{ ok: false, reason }` in those cases, so you can tell an accepted decision from one that was ignored. ([#25087](https://github.com/mastra-ai/mastra/pull/25087)) ```ts const ack = await session.approveTool(toolCallId, true); if (!ack.ok) console.warn(`Approval not applied: ${ack.reason}`); ``` `approveTool()` and `respondToToolSuspension()` no longer retry automatically. A retried decision would be rejected as already handled, so an applied decision could be reported as ignored. - Reduced durable agent snapshot size by no longer saving the agent's system prompt on every step. Resume, tool approval, and trace rebuilds still use the copy kept in the run input, so behavior and traces are unchanged. ([#25609](https://github.com/mastra-ai/mastra/pull/25609)) - Fixed filesystem storage writing MCP client config fields like `name` and `servers` onto the client record during `update()`. Only record-level fields are stored there now; config stays in versions. ([#25646](https://github.com/mastra-ai/mastra/pull/25646)) - Fixed a follow-up message sent right after aborting a run replaying the aborted run. A new thread subscription no longer re-emits the aborted run's lifecycle events (such as a second `agent_start`) while that run is still shutting down. ([#25641](https://github.com/mastra-ai/mastra/pull/25641)) - Fixed `run.cancel()` overwriting the status of a workflow run that had already finished. Canceling a run that ended as `success`, `failed`, `canceled`, `tripwire` or `bailed` is now a no-op, so its stored status is preserved. This applies to both the default and evented workflow engines, and to the `POST /api/workflows/:workflowId/runs/:runId/cancel` route. ([#25414](https://github.com/mastra-ai/mastra/issues/25414)) ([#25628](https://github.com/mastra-ai/mastra/pull/25628)) - Fixed `resumeStream` running resumed tool calls one at a time under `toolCallConcurrency: { strategy: 'called' }`. Parallel-safe tool calls that paused together, such as sub-agent delegations, now resume in parallel. If one call pauses again, its siblings still continue. ([#25490](https://github.com/mastra-ai/mastra/pull/25490)) - Fixed background workflow tools filling Redis with oversized progress updates (#25590). When a background workflow step called an agent, each progress update carried a full copy of the model request and message history, so a single update could reach 60MB. Progress updates now leave those copies out and no longer repeat the task's arguments, while progress information such as text, finish reason, and usage is still delivered. ([#25606](https://github.com/mastra-ai/mastra/pull/25606)) - Fixed tool request context validation accepting invalid values when `requestContextSchema` is a Valibot schema. Valibot returns both a value and issues on failure, and the issues are now reported instead of being ignored. ([#25644](https://github.com/mastra-ai/mastra/pull/25644)) - Fixed goals using up a run when the goal judge errors. A failed judge call (for example, a dropped network connection) still pauses the goal so it can be resumed, but the failed attempt no longer counts against the goal's run limit. Fixes [#22446](https://github.com/mastra-ai/mastra/issues/22446). ([#25205](https://github.com/mastra-ai/mastra/pull/25205)) - Updated dependencies [[`4ac8bc7`](https://github.com/mastra-ai/mastra/commit/4ac8bc76e97278370f4ee0efabe71e9295f8f03f)]: - @mastra/schema-compat@1.3.13-alpha.0 ## 1.73.0-alpha.0 ### Minor Changes - Every agent now recovers from transient provider failures, assistant-prefill rejections, and provider history incompatibilities with no configuration. Three error processors — `ProviderHistoryCompat`, `PrefillErrorHandler`, and `StreamErrorRetryProcessor` — are on by default, in the order that repairs history before anything retries. A `ProviderHistoryCompat` in `errorProcessors` now also repairs the outbound prompt before the provider sees it, instead of only reacting to a rejection. ([#24473](https://github.com/mastra-ai/mastra/pull/24473)) You stay in control of the list: ```ts // Replace one default: your instance with the same id takes its slot. // Retries above 3 also need maxProcessorRetries. new Agent({ ..., errorProcessors: [new StreamErrorRetryProcessor({ maxRetries: 5 })], maxProcessorRetries: 5 }) // Run only your own processors — none of the defaults are merged in. new Agent({ ..., errorProcessors: [myProcessor], errorProcessorDefaults: false }) // Run with no error processors at all. new Agent({ ..., errorProcessorDefaults: false }) ``` `errorProcessorDefaults: false` is the only opt-out: an empty `errorProcessors` list merges like any other and still gets the defaults. The default retry processor retries transient failures only. Transient failures recover through provider `isRetryable` metadata, the built-in OpenAI stream-error matcher, or the connection-reset matcher, while deterministic failures — a rejected structured-output attempt, a validation error, or any HTTP 400 the repair processors cannot fix — surface immediately instead of being replayed unchanged. Pass `StreamErrorRetryProcessor({ retryUnknownErrors: true })` in `errorProcessors` to retry unmatched errors. `createCodingAgent` keeps its shipped behavior, retrying both unmatched errors and bad-request responses, as it always has. Error-processor retries are bounded by a safety cap of `3` per turn when you don't set `maxProcessorRetries`; the defaults stop well below it on their own. The "errorProcessors are configured without an explicit maxProcessorRetries" warning now fires only when you configured error processors yourself, not for the framework defaults. - Export `findGatewayForModel` and `getGatewayId` from `@mastra/core/llm` so callers can check which registered gateway the model router would pick for a model ID. Like the router, `findGatewayForModel` skips disabled gateways. ([#25482](https://github.com/mastra-ai/mastra/pull/25482)) ```ts import { findGatewayForModel, getGatewayId } from '@mastra/core/llm'; const gateway = findGatewayForModel('acme/fast-1', Object.values(mastra.listGateways() ?? {})); console.log(getGatewayId(gateway)); ``` - Invalid tool-call IDs on Anthropic models are now rewritten in the outbound request, so they no longer reach Anthropic and no longer require a failed call to recover. Persisted history keeps its original IDs. The rule's reactive repair (`errorPatterns` and `fix`) stays as the fallback for Claude served through a provider the preemptive check doesn't recognize, such as Vertex-hosted Claude, and for placements outside the agent's prompt path. ([#24505](https://github.com/mastra-ai/mastra/pull/24505)) - Added an optional `resolveSubagentModel(modelId, { requestContext })` option to `AgentControllerConfig`. Subagent models can now resolve with the calling run's request context, so custom providers and tenant credentials work for subagents the same way they do for the main agent. The context passed in doesn't include the parent run's thread or resource IDs. ([#25482](https://github.com/mastra-ai/mastra/pull/25482)) ```ts const controller = new AgentController({ // ... resolveSubagentModel: (modelId, { requestContext }) => resolveTenantModel(modelId, requestContext), }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`42b8761`](https://github.com/mastra-ai/mastra/commit/42b8761d917453cfe9b0b189c51442a5398fbf27)) - Fixed saving builder workflows with classifier steps when the model sends `null` for `maxRetries` or `providerOptions`. These values are now treated as omitted, the same as `retries` and `metadata`, instead of failing validation. ([#25531](https://github.com/mastra-ai/mastra/pull/25531)) - Fixed startAsync so it returns only after workflow execution is durably recoverable. Fixes #24585. ([#24673](https://github.com/mastra-ai/mastra/pull/24673)) - Fixed long-running evented workflow steps running twice when the broker redelivers the step event before the step finishes. Workers now send a heartbeat while a step runs, so the broker does not hand the event to another worker. Each step run is also fenced with a lease, so a redelivered copy is dropped while the original is still running. Redis Streams and Valkey Streams support the heartbeat. Steps should still be idempotent, because a worker crash can still cause a step to run again. ([#24769](https://github.com/mastra-ai/mastra/pull/24769)) - Use Web Crypto for portable identifiers, random secrets, and asynchronous sandbox profile hashing ([#25462](https://github.com/mastra-ai/mastra/pull/25462)) - Fixed slow loading of long conversation histories. Loading stored messages took time that grew with the square of the thread length, so a thread with 10,000 messages could block the server for several seconds on every load. Loading now grows linearly with the thread length. Fixes [#24143](https://github.com/mastra-ai/mastra/issues/24143). ([#25061](https://github.com/mastra-ai/mastra/pull/25061)) - Fixed evented workflows so concurrent resume calls continue a suspension only once. ([#25458](https://github.com/mastra-ai/mastra/pull/25458)) - Fixed `DurableAgent` construction to preserve dynamic model resolution until request context is available. Wrapping an agent with a request-dependent model no longer resolves the model eagerly during startup. ([#25359](https://github.com/mastra-ai/mastra/pull/25359)) - Include `threadId` and `resourceId` on lightweight trace list rows so the Thread ID and Resource ID columns render when the trace query API is unavailable. ([#25433](https://github.com/mastra-ai/mastra/pull/25433)) - Fixed durable agents rebuilding a run on another worker, or after a restart, with the wrong request processors. The rebuilt run now uses the processors configured on the wrapped agent, so processors that rewrite the outbound model request keep running, and a per-call `errorProcessors: []` still turns error processors off. ([#24473](https://github.com/mastra-ai/mastra/pull/24473)) - Fixed evented workflow restarts that occur before the first step is saved. ([#25389](https://github.com/mastra-ai/mastra/pull/25389)) - Fixed `stream.text` and the last step's `text` resolving to an empty string when an agent has an output processor and the run stops via `stopWhen` on a step that contains both text and a tool call. Output processors that intentionally clear text still work as before. ([#25381](https://github.com/mastra-ai/mastra/pull/25381)) - Fixed `ToolCallFilter` stripping a resumed run's own tool calls. When a tool suspended the agent (for example `askUserTool`) and the run was continued with `resumeStream()`, the filter treated the suspended tool call and its result as prior history and removed them from the model prompt. The model never saw its question or the user's answer, so it asked again and the run suspended forever. Tool calls made in the current run, including everything before a suspension, now stay in the prompt after resume. Fixes https://github.com/mastra-ai/mastra/issues/24382 ([#25566](https://github.com/mastra-ai/mastra/pull/25566)) - Fixed durable agents dropping custom `resumeData` on tool approval. When a caller approves a tool call with extra keys, for example `sendToolApproval({ approved: true, resumeData: { approved: true, note: 'hello' } })`, the durable agent now forwards that payload to the tool's `execute` through `context.agent.resumeData`, matching the non-durable agent. A bare `{ approved: true }` is still not forwarded. Fixes https://github.com/mastra-ai/mastra/issues/24561 ([#25562](https://github.com/mastra-ai/mastra/pull/25562)) - Fixed `LocalSandbox` seeding a working directory with a mix of two checkpoint versions when `snapshot()` replaced the checkpoint during `start()`. The seed now retries until it copies one complete checkpoint. ([#25528](https://github.com/mastra-ai/mastra/pull/25528)) ## 1.72.0 ### Minor Changes - Added `maxRetries` and `failurePolicy` options to the observational memory `observation` and `reflection` settings. When `failurePolicy` is `'continue'`, an Observer or Reflector model failure no longer ends the agent turn. ([#24863](https://github.com/mastra-ai/mastra/pull/24863)) ```ts import { Memory } from '@mastra/memory'; const memory = new Memory({ options: { observationalMemory: { observation: { maxRetries: 2, failurePolicy: 'continue' }, reflection: { maxRetries: 2, failurePolicy: 'continue' }, }, }, }); ``` - Added support for resolving channel providers dynamically. The `channels` option on the `Mastra` constructor now also accepts a resolver function, so channel connections added on the Mastra platform show up on a running server without a redeploy. ([#25149](https://github.com/mastra-ai/mastra/pull/25149)) ```typescript import { Mastra } from '@mastra/core/mastra'; import { channels } from '@mastra/connect'; export const mastra = new Mastra({ channels: await channels({ projectId: 'my-project' }), }); ``` A resolver exposes its webhook and OAuth routes up front through `getRoutes()`, and the providers themselves are resolved at runtime. Use the new `mastra.resolveChannels()` method to get the current provider map; `getChannelProviders()` keeps returning the latest resolved snapshot. Static `channels` records keep working unchanged. - Fence background task execution with a persisted, expiring ownership lease. ([#24841](https://github.com/mastra-ai/mastra/pull/24841)) Several managers can now share one storage without running the same task twice. A running task records which worker owns it and when that ownership lapses, recovery only reclaims a task whose lease has expired, and a worker that lost ownership can no longer save a result — so a stale run cannot overwrite the current owner's outcome. Configure the lease length with `leaseDurationMs`: ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, leaseDurationMs: 30_000 }, }); ``` `recoverStaleTasksOnStart` keeps its default of `true`, which is now safe because recovery is lease-fenced. Storage writes can additionally be made conditional on the current owner and lease: ```ts await storage.updateTask( taskId, { status: 'completed', result }, { expectedStatus: 'running', expectedOwnerId: 'worker-1' }, ); ``` The new write conditions are only enforced by storage adapters that understand them. On an older storage package the manager still runs, but the conditions are ignored and writes fall back to unfenced behaviour — so upgrade the storage package alongside `@mastra/core` to get the guarantee. - Added an opt-in `summary` flag to `listWorkflowRuns`. When set, storage adapters may return only `{ status, timestamp }` for each run snapshot instead of the full snapshot. ([#25135](https://github.com/mastra-ai/mastra/pull/25135)) ```ts const { runs } = await workflow.listWorkflowRuns({ perPage: 20, page: 0, summary: true }); ``` - Storage adapters that support the new `trace-aggregate` capability can return aggregated trace results through `ObservabilityStorage.aggregateTraces()`, which executes a `TrustedTraceAggregatePlan` and returns a `TraceAggregateResponse`. The capability is declared alongside `trace-query`. Adapters that do not support it reject the call with a `MastraError` whose id is `OBSERVABILITY_STORAGE_AGGREGATE_TRACES_NOT_IMPLEMENTED`. ([#25124](https://github.com/mastra-ai/mastra/pull/25124)) ```ts import { parseTraceAggregateRequest, planTraceAggregate } from '@mastra/core/storage'; const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, measures: ['count'], }), ); const response = await storage.aggregateTraces(plan); ``` - Added `detach()` to `DurableAgent.observe()` so an observer can stop watching a run without affecting it. Leaving the `for await` loop over `fullStream` early (`break`, `return`, or a thrown error) now also detaches, instead of keeping the subscription open for the life of the process. ([#25048](https://github.com/mastra-ai/mastra/pull/25048)) ```ts const { output, detach } = await agent.observe(runId); request.signal.addEventListener('abort', detach, { once: true }); if (request.signal.aborted) detach(); for await (const chunk of output.fullStream) { send(chunk); } ``` `cleanup()` is unchanged. It still removes the run and its cached events, so only the process that owns the run should call it. **Behavior change:** when `idleTimeoutMs` fires without an `isAlive` probe, the observer now detaches, and the run's registry entry and cached events are kept. Before, a quiet but still-running run (for example, one waiting on a slow tool) was cleaned up. If you rely on the idle timeout to clean up runs whose process crashed, pass `isAlive` so the run is only cleaned up when it reports the run is no longer running. - Added an optional `sandboxId` to the `WorkspaceSandbox` interface so hosts can read a provider's physical, reattachable sandbox id and persist it for deterministic reattach. ([#24004](https://github.com/mastra-ai/mastra/pull/24004)) ```ts const id = sandbox.sandboxId ?? sandbox.id; // physical id when the provider exposes one ``` - Added `CyberRefusalHandler`, a processor that retries once when an OpenAI or Anthropic cybersecurity safeguard refuses ordinary work partway through an agent run. These refusals are often false positives, and nudging the model to continue usually gets past them. A second refusal on the same step is treated as genuine. ([#25172](https://github.com/mastra-ai/mastra/pull/25172)) - **OpenAI** refusals (`cyber_policy`, "This content was flagged for possible cybersecurity risk") fail the model call. Register the handler in `errorProcessors`, before `StreamErrorRetryProcessor`. - **Anthropic** refusals stop the response with a `cyber` classifier refusal (`content-filter` finish reason). Register the handler in `outputProcessors`; the refused step is rolled back before the retry. ```ts import { Agent } from '@mastra/core/agent'; import { CyberRefusalHandler } from '@mastra/core/processors'; const agent = new Agent({ id: 'coder', name: 'Coder', instructions: 'You are a coding agent', model: 'openai/gpt-5.6-sol', errorProcessors: [new CyberRefusalHandler()], outputProcessors: [new CyberRefusalHandler()], maxProcessorRetries: 3, }); ``` Coding agents created with `createCodingAgent` include it in both lanes by default, along with a `maxProcessorRetries` budget of 3 — output-step retries have no implicit default, so without a budget the Anthropic retry would be treated as an abort. - Added a `requireDescription` option to the workspace `execute_command` tool. When enabled, the tool asks the model for a short plain-language `description` of each command, listed before `command` in the tool schema, so UIs can show it instead of the raw command. The option is off by default and the tool schema is unchanged when it is off. ([#25032](https://github.com/mastra-ai/mastra/pull/25032)) ```ts const workspace = new Workspace({ sandbox, tools: { [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: { requireDescription: true }, }, }); ``` - Added `runId`, `sessionId`, `userId`, and `organizationId` filters to advanced trace queries. Use them at trace scope or inside `spans.some` / `spans.none` with `eq`, `ne`, `in`, `notIn`, `exists`, and `notExists`. Value discovery does not suggest these values, and `organizationId` can only narrow the trusted tenant scope. ([#24935](https://github.com/mastra-ai/mastra/pull/24935)) ```ts const result = await observability.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, where: { op: 'and', args: [ { op: 'eq', left: { path: 'userId' }, right: { literal: 'user-42' } }, { op: 'eq', left: { path: 'sessionId' }, right: { literal: 'session-9' } }, ], }, }), ), ); ``` - Added `withInitialHistory` to `subscribeToThread`. The subscription first emits a single `thread-history` chunk with the stored thread messages, then only the retained parts that storage doesn't already cover, then live parts. Completed runs no longer replay on a retained backend such as Redis Streams, so chat UIs skip the replay animation and answered tool approvals aren't re-run. Pending approvals still come through so clients can prompt for them, and approvals already answered by a resumed run don't. Parts are compared to storage by when the model produced them, so slow publishing can't make stored parts appear again. ([#24874](https://github.com/mastra-ai/mastra/pull/24874)) `AgentController` sessions now subscribe with `withInitialHistory`, so a session opened after a restart no longer re-acts on approvals from runs that already finished. The `thread-history` chunk type only appears on subscriptions that pass `withInitialHistory`; `agent.stream()` and plain subscriptions are typed exactly as before. ```ts const subscription = await agent.subscribeToThread({ threadId, resourceId, withInitialHistory: { perPage: 40 }, }); for await (const chunk of subscription.stream) { if (chunk.type === 'thread-history') renderHistory(chunk.payload.messages); else applyLiveChunk(chunk); } ``` - Fixed scheduled workflows hanging when started directly on serverless hosts (#18807). Declaring `schedule` on a workflow no longer switches it to the evented engine unless the `MASTRA_WORKERS` environment variable is set. Dev servers, single-process servers, and serverless hosts keep the default engine, so an HTTP-triggered `run.start()` runs in-process like any other workflow instead of waiting for a worker that never starts. ([#25430](https://github.com/mastra-ai/mastra/pull/25430)) **What changes** - Without `MASTRA_WORKERS`, scheduled workflows run on the default engine and no longer need storage with concurrent-update support. Cron fires run in-process on the host that runs the scheduler. - With `MASTRA_WORKERS` set to any value (split-worker deployments), scheduled workflows still run on the evented engine, and Mastra logs a warning to the console when one is created. Set `MASTRA_WORKERS` on every process in a split deployment, including `false` on the API. - `schedule` declared on Inngest workflows is ignored with a warning. Use Inngest's `cron` instead. ```typescript import { createWorkflow } from '@mastra/core/workflows'; const dailyReport = createWorkflow({ id: 'daily-report', schedule: { cron: '0 9 * * *' }, // ... }).commit(); // Before: dailyReport.engineType === 'evented', and run.start() hung without workers // After (MASTRA_WORKERS unset): dailyReport.engineType === 'default' const run = await dailyReport.createRun(); await run.start({ inputData }); // completes in-process ``` To keep a scheduled workflow on the evented engine everywhere, import `createWorkflow` from `@mastra/core/workflows/evented`. - Added `PubSub.trimTopic(topic, { runId })` (no-op by default). Once a thread run completes successfully and its messages are saved, Mastra deletes that run's entries from the thread topic, so retained backends no longer keep finished runs. Entries are matched by `runId`, so a run resumed after a server restart also clears what it published before the restart. Runs still in progress, waiting for approval, or owned by another process are never touched. Threads whose agent has no storage are not trimmed. ([#24875](https://github.com/mastra-ai/mastra/pull/24875)) Long runs are also trimmed as they go: each time messages are saved mid-run (by Observational Memory, or by `savePerStep` on `Agent` and `DurableAgent`), the parts up to the last finished step are dropped from the topic via the new `producedBefore` option. Pending approval and suspension prompts stay until the run itself is trimmed. Runs that fail, are canceled, or never start (for example a woken idle thread with no usable model credential) are deleted from the topic 30 seconds after they end. Nothing from them was saved, so reconnecting subscribers already ignore them; the delay lets subscribers reading live still see the failure. ### Patch Changes - Fixed cancellation during session startup so cancelled requests do not reach the model or interrupt a newer turn. Added the provider finish reason to AgentController error events so clients can distinguish token limits and refusals from execution failures. ([#24321](https://github.com/mastra-ai/mastra/pull/24321)) Added a distinct session startup cancellation error so clients can handle interruptions without hiding provider or transport failures. ```ts import { isSessionStartupCancelledError } from '@mastra/core/agent-controller'; try { await session.sendMessage({ content: 'Hello' }); } catch (error) { if (!isSessionStartupCancelledError(error)) throw error; console.log('Interrupted'); } ``` - Fixed cost estimates for Anthropic 1-hour prompt-cache writes. The 5-minute and 1-hour cache-write token counts are now read from the provider's raw usage, so 1-hour writes are priced at their real rate instead of the cheaper 5-minute rate. ([#25011](https://github.com/mastra-ai/mastra/issues/25011)) ([#25089](https://github.com/mastra-ai/mastra/pull/25089)) - Fixed `textStream` and `elementStream` returning incomplete array elements when streaming structured output with an array schema. Previously, if an element arrived across several tokens, the streams could keep an early partial version (for example `{"a":1}` instead of `{"a":1,"b":2}`) that didn't match the final `object`. Each element is now emitted only once it is complete, so the streamed elements always match the final result. ([#25278](https://github.com/mastra-ai/mastra/pull/25278)) - Update provider registry and model documentation with latest models and providers ([`e1c3193`](https://github.com/mastra-ai/mastra/commit/e1c3193b18ca68e5cca27f7dce9b0381a6e7b95d)) - Fixed tool approvals so they always go to the run that requested them. Approving a tool call after a newer run has started on the thread no longer resumes the wrong run. ([#24874](https://github.com/mastra-ai/mastra/pull/24874)) `agent.sendToolApproval({ threadId, resourceId, runId, approved })` resumes exactly the run you name, including after a server restart. If that run has already ended, the call throws instead of resuming a different suspended run on the thread. - Fixed `DurableAgent.recover()` failing with `Cannot read properties of undefined (reading 'messages')` or `(reading 'length')` when a default-engine run was killed while a tool was running or between steps. Running checkpoints now keep the conversation state a restart reads back, while still trimming older history to keep storage small. ([#25107](https://github.com/mastra-ai/mastra/pull/25107)) - Fixed channel messages being dropped when `chatOptions.concurrency` uses `burst`, `debounce`, or `queue`. Messages the Chat SDK batches together now reach the agent in the order they were sent and are saved to memory. Consecutive messages from one sender are merged into one turn; each sender's messages run as their own turn under that sender's identity. Custom channel handlers can read the batched messages from `context.skipped`. Fixes #22496. ([#25168](https://github.com/mastra-ai/mastra/pull/25168)) ```ts import { Agent } from '@mastra/core/agent'; import { createWhatsAppAdapter } from '@chat-adapter/whatsapp'; export const agent = new Agent({ id: 'support-agent', name: 'Support Agent', instructions: 'You are a helpful assistant.', model: 'openai/gpt-5-mini', channels: { adapters: { whatsapp: createWhatsAppAdapter() }, chatOptions: { concurrency: 'debounce' }, handlers: { onDirectMessage: async (thread, message, defaultHandler, context) => { console.log(`${context.skipped.length} earlier messages were batched`); await defaultHandler(thread, message); }, }, }, }); ``` - Fixed Channels tool approval cards so only the user who triggered the tool call can approve or decline it. In shared channels, clicks from other users are now ignored and logged instead of resuming the run. ([#25165](https://github.com/mastra-ai/mastra/pull/25165)) - Fixed an unhandled EPIPE error that crashed processes using UnixSocketPubSub when the broker process exited before stream teardown. Unsubscribing after the broker is gone now completes cleanly, and a publish that races `close()` now rejects with a clear "UnixSocketPubSub is closed" error instead of a raw socket error. ([#25126](https://github.com/mastra-ai/mastra/pull/25126)) - Fixed workspace `grep` and `list_files` failing with "No mount for path: .gitignore" when a `CompositeFilesystem` has no root (`/`) mount. Reads of unmounted paths now report a not-found (`ENOENT`) error, so a missing root `.gitignore` is treated as absent. ([#25263](https://github.com/mastra-ai/mastra/pull/25263)) - Fixed `restart()` and automatic recovery re-running a workflow step that had already finished. If the process stopped right after a step completed but before the next one started, restarting the run executed that step again, and for loops, foreach, parallel blocks, and branches that meant repeating every item, arm, or iteration. Restart now continues from the next step, and a run whose last step had already finished completes with that step's saved output instead of crashing. Agent-loop checkpoints now keep the just-finished step's conversation so the next step still receives it after a restart. Fixes [#24615](https://github.com/mastra-ai/mastra/issues/24615). ([#25114](https://github.com/mastra-ai/mastra/pull/25114)) `DurableAgent.recover()` no longer hangs when the run stopped after the agent had already sent its final answer. The recovered stream now finishes with that saved answer, and `onFinish` runs once. - Fixed a claimed thread owner running an idle wake twice when a sender retried the same cross-agent message after its acceptance acknowledgement was lost. Such a retry now resolves to the outcome the first attempt already accepted — its run, or the reason it failed or was cancelled before it ran — instead of starting a second turn that repeats the turn's tool side effects. ([#24774](https://github.com/mastra-ai/mastra/pull/24774)) - Fixed durable agents to reject invalid fallback timeout settings before preparation side effects. ([#25188](https://github.com/mastra-ai/mastra/pull/25188)) - Fixed tool result objects with a `value` key dropping sibling fields. ([#25191](https://github.com/mastra-ai/mastra/pull/25191)) - Fixed `DurableAgent` ignoring `savePerStep`. Each step is now saved to memory before the run continues, as with `Agent`, so a durable run that is interrupted mid-way keeps the steps it had already finished. ([#24875](https://github.com/mastra-ai/mastra/pull/24875)) ```ts await durableAgent.stream('Research and summarize', { memory: { thread: 'thread-1', resource: 'user-1' }, savePerStep: true, }); ``` - Durable agents now honor the `awaited` background-task disposition by keeping the model turn open until the authoritative tool result is persisted. Replayed workflow steps adopt the matching persisted task, resume or restart it according to its status, and reconcile terminal results instead of dispatching the tool again. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Background tool results on the durable engine now apply configured transcript transforms, so payloads a user asked to redact are no longer persisted raw to thread history. They also use the configured Mastra `idGenerator` for appended message ids instead of falling back to random UUIDs. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Durable and evented agents now honor call-time `modelSettings.maxRetries`. Previously the durable llm-execution step's retry ladder only read retry counts from serialized model-list entries (hardcoding 0 for single-model agents), so a `maxRetries` passed via `stream()`/`generate()` model settings was silently ignored and failed requests were never retried. The agent-level retry config now rides on the serialized workflow options, and the ladder applies the same precedence as the in-process loop: an explicitly configured agent-level `maxRetries` (including 0) wins, otherwise the call-time `modelSettings.maxRetries` applies. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Data chunks emitted by output processors via `writer.custom()` are now persisted to thread history on the durable engine, matching the regular agent loop (#19375). Transient chunks remain stream-only. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Provider-executed tool results that arrive in a later stream (tool call in one step, result in the next) are now committed to the transcript on the durable engine instead of staying stuck in the call state (ports #14282 to the durable agent loop). Configured transcript transforms also apply to these deferred results instead of being silently skipped. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable agents mishandling provider-executed tools (like Anthropic web search). Calls whose result had not arrived yet no longer fail with ToolNotFoundError, and results are no longer committed twice, which previously overwrote their provider metadata. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable agents looping until maxSteps when a model response ended with a terminal finish reason (content-filter refusal or length truncation) alongside a tool call. The loop now stops instead of re-sending the same request and re-triggering the same refusal (ports #17893 to the durable agent loop). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Serialize `modelSettings.timeout` into the durable workflow input. `serializeModelSettings` dropped the `timeout` field entirely, so a durable run's execution time budgets were lost across the serialization boundary: cold recovery (`DurableAgent.recover()`) could not restore the run-level `totalMs` budget — a restarted run lost its configured deadline and ran unbounded — and the per-call `stepMs`/`firstChunkMs` budgets never reached the durable llm-execution step's shared execute wrapper. All three subfields now survive serialization (positive finite numbers only), and a recovered session re-arms the persisted `totalMs` budget. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed `createDurableAgent` dropping non-transient `data-*` parts that tools write with `context.writer.custom()`. These parts are now saved to memory, just like with a regular `Agent`. Transient parts still only go to the stream. ([#25283](https://github.com/mastra-ai/mastra/pull/25283)) - Output processors' `processToolResult` hooks now run on the durable engine. Previously they were skipped entirely, so a redaction processor had no effect on durable streams or transcripts. Processor mutations and tripwire blocks now apply to the emitted chunk and the persisted transcript, before the raw value can reach subscribers. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - The run-level `modelSettings.timeout.totalMs` budget is now enforced on durable agents (ports #21724 to the durable agent loop). Previously only the per-call budget applied, so a hanging provider or a long tool chain could run forever. The total budget now bounds the whole run, is re-armed with the original value on cold resume and recovery, and expiry surfaces as a run error rather than a silent stop. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Tool payload transforms now persist their state on the durable engine. Transcript-target transforms correctly redact stored args and results in thread history; previously only the streamed chunks were transformed and raw values were silently persisted. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed DurableAgent dropping cached-input, cache-write, and reasoning token counts from `model_generation` span usage, so tracing exporters and cache/reasoning token metrics now match the regular Agent. ([#25392](https://github.com/mastra-ai/mastra/pull/25392)) - Durable runs of stored agents now stay pinned to the agent version they started on. Both resuming a suspended run (#22128) and recovering a crashed run re-resolve the original pinned version instead of silently switching to the latest published version. If the pinned version no longer resolves, the run falls back to the current definition with a warning. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed saved dynamic workflows losing `.map()` entries that use `initData: true`. These mappings now keep reading from the workflow's initial input after the workflow is saved and loaded again, the same as they do in memory. Workflows saved before this fix need to be saved again. ([#25152](https://github.com/mastra-ai/mastra/pull/25152)) - Fixed `Session.sendMessage()` hanging forever when its run never produced a completion event, for example when several turns started at once on the same thread. It now rejects if the session's stream processing fails, and resolves if the run is aborted or the session's thread subscription is torn down. See [#25140](https://github.com/mastra-ai/mastra/issues/25140). ([#25375](https://github.com/mastra-ai/mastra/pull/25375)) - Fixed delegation bail() taking one extra model turn on the evented engine. When an onDelegationComplete hook calls ctx.bail(), the supervisor loop now stops in the same iteration on every engine. Previously the bail signal was passed between workflow steps by mutating a shared request context, which only works when all steps run in one process — on the evented engine each step gets its own copy, so the loop made one more model request before stopping. The bail signal now travels on the tool call step's serialized output, which crosses process and event boundaries reliably. The Inngest agentic loop's continuation predicate now also honors this flag, so bail() stops Inngest-hosted supervisor loops in the same iteration too (previously it only stopped via maxSteps). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Propagate the caller's actor identity through the evented workflow engine. Previously the evented engine accepted `actor` at the API surface (used for the fine-grained-authorization gate) but dropped it before execution: `execute()` params omitted `actor` and the workflow event payloads carried `requestContext` but not `actor`, so steps, tools, and loop conditions running on the evented engine never saw the caller's identity — actor-based authorization inside tools silently saw no actor. The actor signal now rides the event envelope alongside `requestContext` across every hop (start, resume, restart, time travel, step events) and reaches step, tool, agent, and condition execution contexts, matching the default engine. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - EventedAgent runs on the built-in evented workflow engine again. It previously fell back to the default in-process engine because the evented path crashed on resume and recovery. Those bugs are fixed, and the engine is now covered by the durable-agent conformance suite — including crash recovery: a fresh process over the same storage can resume in-flight runs via `recover(runId)` / `recoverActiveRuns()`. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed evented workflow snapshots growing quadratically with `.foreach()` input size. Each iteration's progress record no longer stores a copy of the whole input array, so large foreach runs no longer produce huge snapshots that can stall storage. Fixes #24943. ([#25376](https://github.com/mastra-ai/mastra/pull/25376)) - Durable agents no longer fail to start when their storage cannot apply concurrent workflow updates atomically. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) The evented execution engine advances a run from concurrent workers, so it refuses to start on a storage adapter whose workflows domain does not support atomic concurrent updates. Durable agents now degrade to the default in-process engine with a warning in that case, the same way an agent with no Mastra host already did, rather than throwing on the first `.stream()` call. This keeps agents on adapters such as `@mastra/redis` and `@mastra/valkey` working, at the cost of evented execution — the warning names the adapter and the capability it is missing. A workflow that opts into the evented engine directly, by declaring a `schedule`, still gets an error, because there is no other engine it could have meant. That error now names the storage adapter it came from, names the missing capability, and lists the adapters that support it; it previously only suggested removing the `schedule` field. - Fixed a redelivery race on the evented engine where a `workflow.step.run` event redelivered by an at-least-once transport after the step had already suspended would re-execute the step and overwrite the stored suspended record, stripping the suspendPayload that resume recovery depends on. The mid-step pre-write now drops a non-resume delivery when both the run and the target step record are suspended — a state in which no legitimate delivery exists — so the suspended record and its resume artifact survive redelivery. The check is a read-then-write that narrows the race window; fully closing it would need compare-and-set support on `updateWorkflowResults` across storage adapters. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed evented workflows failing the entire run when a `.branch()` condition throws. A throwing condition is now logged and treated as false, so the remaining branches still run — matching the default workflow engine. ([#25279](https://github.com/mastra-ai/mastra/pull/25279)) - Fixed DurableAgent and EventedAgent treating falsy primitive resume payloads (`false`, `0`, `''`) as "no resume data" in the durable tool-call step. Suspended background tool tasks resumed with a falsy payload were stranded while a duplicate task was dispatched. The durable loop now uses the same nullish check as the regular agent loop (#22363), so only `undefined`/`null` mean "not resuming". ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed structured output on durable agents using the evented engine. Schemas passed as `structuredOutput.schema` now stay available on later model calls and after a run is recovered. Workflow options that cannot be saved now fail with an error naming the field. ([#25075](https://github.com/mastra-ai/mastra/pull/25075)) - Stored agents backed by FilesystemStore now retain durable execution settings. ([#25123](https://github.com/mastra-ai/mastra/pull/25123)) - Fixed `foreach` over a nested workflow failing with "already resumed by another caller" when one iteration suspended while others were still starting. Each iteration now starts its own child workflow run, and suspended iterations can be resumed individually with `forEachIndex`. ([#25394](https://github.com/mastra-ai/mastra/pull/25394)) - **Added atomic cross-process thread ownership handoff** ([#24898](https://github.com/mastra-ai/mastra/pull/24898)) `agent.claimThreadOwnership()` now accepts `yieldOwnership`, `onOwnershipYielded`, and `onOwnershipLost` callbacks. When another process asks for a thread and `yieldOwnership` returns `true`, the thread passes to that process in the same request. `onOwnershipYielded` runs after the handoff. Owner lookups for message delivery never call `yieldOwnership`. If another process takes an expired claim first, `onOwnershipLost` runs so the caller can retry. `UnixSocketPubSub` now coordinates thread ownership between processes that share a socket directory. A running owner restores an expired claim when no other process has taken it. When an owner hands off a thread or exits, another process can take over immediately. **Before** ```ts const claim = await agent.claimThreadOwnership({ resourceId, threadId, }); ``` **After** ```ts let claimActive = true; const claim = await agent.claimThreadOwnership({ resourceId, threadId, yieldOwnership: () => session.thread.getId() !== threadId, onOwnershipYielded: () => { claimActive = false; }, onOwnershipLost: () => scheduleClaimRetry(threadId), }); ``` - Fixed durable agents crashing during crash recovery when the process was restarted while an LLM call was in flight. The active step now restarts from its own preserved input instead of a completed predecessor's pruned output, which previously caused "Cannot read properties of undefined (reading 'messages')" (#22636). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - EventedAgent tool-approval, tool-denial, and in-execution resume no longer fail with "Cannot read properties of undefined (reading 'messages')": the evented engine retains running conversation history in persisted agent-loop snapshots, since its storage-merged step results are live data (unlike the default engine's write-only snapshots) ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed Gemini 3 rejecting the next request with "Corrupted tool call context" when native Google Search (`webSearchTool`) and another tool (such as a skill) were called in the same step. The provider-executed flag from the tool call is now kept when its result arrives without one, so the search call and its result stay together in the conversation history. ([#24888](https://github.com/mastra-ai/mastra/pull/24888)) - A paused goal now says why it paused. `/goal status` and the goal modal show the cause — evaluation budget exhausted, or a judge that failed to evaluate — and it survives a reload instead of disappearing after the moment it happened. Resuming or completing a goal clears the cause, so a later pause never shows a stale one. ([#24618](https://github.com/mastra-ai/mastra/pull/24618)) - Removed the unused `executeDurableToolCalls` helper and its `ToolExecutionContext`/`ToolExecutionError` types from `@mastra/core/agent/durable`. This code was never wired into any agent loop; both the durable and regular loops execute tools through their own step implementations. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed `_BASE_URL` being ignored for providers such as Google, xAI, Perplexity, Cerebras, DeepInfra, Together AI, and Vercel. Setting `GOOGLE_BASE_URL` (or the equivalent for other providers) now routes requests and API keys through your proxy or gateway instead of the public provider host. ([#25354](https://github.com/mastra-ai/mastra/pull/25354)) - **Approval prompts are now independent per thread and run.** Concurrent and detached approvals stop overwriting, stranding, or answering each other's gates: gates are keyed by tool call and tagged with the thread and run that opened them, abort and user-message interjection release only the current thread's gates, detached-thread approvals no longer fire notifications or permission hooks, and an approved run resumes against the thread, run, resource, agent, and cancellation signal that actually parked it — so a mode switch, resource re-scope, or a successor run cannot redirect or cancel the parked continuation. ([#24776](https://github.com/mastra-ai/mastra/pull/24776)) **Approval responses now require the gate's `toolCallId`.** Without the id, a response could release whichever gate happened to be parked — another thread's or another run's — so an id-less response is rejected instead of silently applied. Every caller inside the repo already passes the id; this only affects external callers of the session API. Before: ```ts session.respondToToolApproval({ decision: 'approve' }); ``` After: ```ts session.respondToToolApproval({ decision: 'approve', toolCallId }); ``` Similarly, `SessionApproval.arm()` now requires a `toolCallId`, and `getToolCallId()` is replaced by `getToolCallIds()` (which accepts an optional thread/run filter). **"Always allow" grants are now scoped to the gate's thread.** Approving with `always_allow_category` grants the tool's category to the thread that owns the gate rather than the whole session, so a background thread's approval cannot widen what the current thread may run without prompting. `grantCategory`, `grantTool`, `hasCategoryGrant`, `hasToolGrant`, and `getGrants` accept an optional `threadId`; passing one scopes the grant to that thread, omitting it keeps the existing session-wide behavior for embedders that grant explicitly. - Fixed cross-agent wakes with `requireClaimedOwner` failing with "No claimed thread owner responded" when the owning agent was alive but slow to answer (for example on a busy host or CI). Owner discovery for wakes now retries with growing timeouts for up to 1 second before giving up, and the error states how long it waited. ([#25488](https://github.com/mastra-ai/mastra/pull/25488)) Both limits can be tuned with environment variables: - `MASTRA_AGENT_THREAD_OWNER_DISCOVERY_TIMEOUT_MS` — per-attempt wait (default `100`) - `MASTRA_AGENT_THREAD_WAKE_OWNER_DISCOVERY_DEADLINE_MS` — total wake discovery budget (default `1000`) - Fixed follow-up DeepSeek requests failing in thinking mode after switching providers or using tools. ([#25189](https://github.com/mastra-ai/mastra/pull/25189)) - Added an `outputEncoding` option to `LocalSandbox` so command output in non-UTF-8 encodings is decoded correctly. On Windows systems using a legacy code page, such as Chinese (GBK / code page 936), native commands previously returned garbled text. ([#25266](https://github.com/mastra-ai/mastra/pull/25266)) ```ts const sandbox = new LocalSandbox({ outputEncoding: 'gbk' }); ``` UTF-8 remains the default. Fixes #25249. - Fixed agents with working memory seeing the word "null" in their instructions when no working memory had been saved yet, for example on the first message of a new thread. The agent now sees "No working memory data available." instead. Fixes [#23724](https://github.com/mastra-ai/mastra/issues/23724). ([#25057](https://github.com/mastra-ai/mastra/pull/25057)) - Fixed `run.restart()` on the default workflow engine after a process crash during a nested workflow resume. Restart no longer fails with "This workflow run was not suspended", and the resumed step keeps its resume data and completes instead of suspending again. Fixes [#25187](https://github.com/mastra-ai/mastra/issues/25187). ([#25234](https://github.com/mastra-ai/mastra/pull/25234)) Fixed `timeTravel()` ignoring resume values such as `false` or `0`. - Evented workflows now persist the running step record and routing state before executing each step, so runs killed mid-step recover via restart() instead of failing with "Execution path is empty" ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed CachingPubSub dropping events that arrive from another caching tier. When a durable agent's stream cache follows a source bus that is itself a CachingPubSub with its own cache (for example a user-supplied CachingPubSub passed to new Mastra({ pubsub })), events from that tier already carry an index and were treated as local echoes and discarded — they never reached the agent's subscribers and could not be replayed. The follower now only treats indexed events as echoes when the source shares the same transport; foreign-indexed events are cached under a fresh index and republished so live delivery and replay both work across tiers. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable agent resumes (including `InngestAgent`) running another agent's tool when two agents register tools with the same id. On a cold worker, tool calls now resolve against the run's own agent before falling back to Mastra-wide tools. ([#25393](https://github.com/mastra-ai/mastra/pull/25393)) - Deprecated Observational Memory `scope: 'resource'` in the memory config types. Editors now show the `scope` option as deprecated. Remove it to use the default thread scope, and enable `retrieval` or resource-scoped working memory for cross-thread continuity. ([#24933](https://github.com/mastra-ai/mastra/pull/24933)) - Fixed evented agent streams losing events when the agent uses a custom pubsub. The evented workflow engine publishes run events on the Mastra instance's pubsub, but the agent's stream listened only on its own pubsub — with a custom pubsub configured, suspend, finish, and tool events silently never reached the stream. The agent's caching pubsub now also follows the Mastra instance's pubsub as a source, so evented runs stream correctly regardless of which pubsub the agent was configured with. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed tool approvals for Inngest durable agents ([#25154](https://github.com/mastra-ai/mastra/issues/25154)). ([#25167](https://github.com/mastra-ai/mastra/pull/25167)) - Runs created with `createInngestAgent()` that wait on tool approval now appear in `GET /api/agents/:agentId/suspended-runs` and `agent.listSuspendedRuns()`. - `approve-tool-call` and `decline-tool-call` now accept these runs. They no longer return "Access denied: durable run belongs to a different resource". - Durable runs waiting on an approval-gated tool now report `requiresApproval: true` and the tool's `args`. - Fixed a race where a background tool result could be saved to memory without its configured transcript transform applied. When a background task finished while the turn was still being persisted, the memory history save could land last and store the raw tool result instead of the redacted one. All persistence paths now apply the same transcript redaction, so raw payloads never reach storage. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed threads getting stuck on Anthropic and Bedrock after a step failed mid-reasoning. Prompts now leave out reasoning that the provider cannot accept, so later turns no longer fail. ([#24928](https://github.com/mastra-ai/mastra/pull/24928)) - Fixed model fallback logging so recovered failures no longer page as errors. When a model in a `models` chain fails and another model is still available, Mastra now logs a single warning naming the failed and next model instead of two error-level records. Failures of the last model in the chain are still reported as errors. ([#25489](https://github.com/mastra-ai/mastra/pull/25489)) - Processor retry feedback no longer invalidates the provider prompt cache. When a processor calls `abort(reason, { retry: true })`, the reason used to be added as a system message ahead of the conversation, so the retry and later steps missed the cache for the whole history. The reason is now added as a system reminder after the conversation, so each retry only adds to the previous request. ([#25171](https://github.com/mastra-ai/mastra/pull/25171)) The reason is sent verbatim as `{reason}`. It is no longer wrapped in the `[Processor Feedback] Your previous response was not accepted: … Please try again with the feedback in mind.` template, so write the reason as the instruction you want the model to follow. The reminder is kept in thread history as a system-reminder signal, which default memory recall hides, like other processor reminders. - Documented two `aggregateTraces()` result rules: null dimension values sort last in both sort directions, and a time range with no matching traces returns `rows: []` rather than a single zero-valued row. ([#25016](https://github.com/mastra-ai/mastra/pull/25016)) - Fixed `structuredOutput.jsonPromptInjection: false` being ignored when a structuring `model` is set. The main model's prompt no longer receives the full JSON schema on every step when you opt out of injection. ([#25478](https://github.com/mastra-ai/mastra/pull/25478)) - Fixed tool search loading every `search_tools` hit when `autoLoad` is `false`. Only `load_tool` now activates tools, so the tool list stays stable and the prompt cache is preserved across steps. ([#25076](https://github.com/mastra-ai/mastra/pull/25076)) - Fixed generated thread titles re-creating a thread that was deleted while the title was still being generated. Deleted threads now stay deleted, and the title save no longer overwrites thread metadata that changed during generation. ([#25385](https://github.com/mastra-ai/mastra/pull/25385)) - Fixed agent runs with `readOnly` memory still persisting their transcript when a background tool task completed. The background-result flush now honors the readOnly contract the same way the suspension flush already did. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable and evented agents to reject invalid model timeout settings before starting a run. ([#25079](https://github.com/mastra-ai/mastra/pull/25079)) - Fixed thread subscribers showing a run as idle after a tool approval. When a run resumes, thread subscribers now get a `start` event for the resumed part. This applies to subscribers that were already listening and to those that join while the run resumes, so the UI shows the run as running again and can cancel it. ([#24875](https://github.com/mastra-ai/mastra/pull/24875)) - Fixed `createRunCommandTool` security checks on Windows. `allowedBasePaths` now accepts working directories with backslash paths (previously every subdirectory was rejected), command allow/block lists now recognize Windows paths such as `C:\tools\rm.exe`, and the unsafe-character filter now rejects dangerous input consistently on every call. ([#25383](https://github.com/mastra-ai/mastra/pull/25383)) - Added a `getBasePath` option to `AgentsMDInjector`. When set, relative tool paths resolve against that directory instead of `process.cwd()`, and `AGENTS.md`/`CLAUDE.md` files outside it are never injected. ([#25071](https://github.com/mastra-ai/mastra/pull/25071)) ```ts new AgentsMDInjector({ getBasePath: () => '/path/to/checkout', }); ``` - Fixed Anthropic extended-thinking threads with working memory getting stuck on every turn with "thinking blocks in the latest assistant message cannot be modified". When the model spent a step only on an `updateWorkingMemory` call, the saved history kept that step's thinking without its tool call, so later requests replayed it merged into the next step. That step is now dropped when the call is hidden. ([#24924](https://github.com/mastra-ai/mastra/pull/24924)) Threads already saved in this state recover when `ProviderHistoryCompat` is in the agent's error processors: on that Anthropic error it drops the leftover thinking step and retries once. ```ts import { Agent } from '@mastra/core/agent'; import { ProviderHistoryCompat } from '@mastra/core/processors'; const agent = new Agent({ // ... errorProcessors: [new ProviderHistoryCompat()], }); ``` Fixes [#22798](https://github.com/mastra-ai/mastra/issues/22798). - Fixed the durable agent engine leaking raw tool output when an output or result processor throws. Previously, if a processor (for example a redaction processor) failed with a non-tripwire error, the durable engine would warn and continue with the unprocessed value: the raw tool result was still emitted on the stream and persisted to the transcript. Both paths now fail closed — a throwing stream processor suppresses the chunk instead of emitting the original, and a throwing tool-result processor substitutes an error placeholder for both emission and persistence. The run itself stays alive in both cases. This matches the regular loop, which already refused to emit or persist raw values when a processor throws. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable agents looping until maxSteps when a provider ends the stream with finishReason 'other' and no output. The empty response now surfaces as a stream error after one attempt, and completion checkers no longer grade errored iterations (ports #22273 to the durable agent loop). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed subagents without memory failing immediately when their workspace has a browser. These subagents now complete their tasks and still see browser context in their prompt. ([#25291](https://github.com/mastra-ai/mastra/pull/25291)) - `submit_plan` results now record the reviewer's decision. `submittedPlan.action` is `approved` or `rejected`, and `submittedPlan.feedback` holds the reviewer's comments when they asked for changes. UIs can show the outcome from message history without parsing the result text. ([#25413](https://github.com/mastra-ai/mastra/pull/25413)) - Fixed semantic recall running messages from other conversations together on a single line. Each recalled message now appears on its own line in the system prompt, so the model can tell where one message ends and the next begins. ([#25059](https://github.com/mastra-ai/mastra/pull/25059)) - Fixed durable tool payload transforms after cross-process recovery. ([#25387](https://github.com/mastra-ai/mastra/pull/25387)) - Added the assistant message id to tool-input events so consumers can attribute streamed tool arguments to the model step that produced them. ([#25196](https://github.com/mastra-ai/mastra/pull/25196)) - Added `withInitialHistory` to thread subscriptions over HTTP. Pass it to `subscribeToThread` in `@mastra/client-js`, or to `useChat` in `@mastra/react`, to get stored thread messages as one `thread-history` chunk before live parts. Completed runs no longer replay on reconnect. ([#24876](https://github.com/mastra-ai/mastra/pull/24876)) ```ts const subscription = await agent.subscribeToThread({ threadId, resourceId, withInitialHistory: { perPage: 40 } }); ``` ```tsx const chat = useChat({ agentId, threadId, resourceId, enableThreadSignals: true, withInitialHistory: true }); ``` Agent controller tool-approval requests now resume the stored suspended run when no approval is waiting in the session, so approval cards restored from thread history after a restart can be answered. - Fixed DurableAgent streams to hide response metadata chunks and attribute finish chunks to the agent. ([#25078](https://github.com/mastra-ai/mastra/pull/25078)) - A function-form `toolDisplay` on a channel adapter can now render the approval card's final state after a user clicks Approve or Deny. Previously, Mastra always replaced a custom approval card with its own English "Approved" or "Denied" text, which also showed the tool's internal registry key. ([#25486](https://github.com/mastra-ai/mastra/pull/25486)) The function now receives `approved` and `denied` events (`denied` includes `byUser`). Return `{ kind: 'post', message }` to replace the card: ```ts channels: { adapters: { slack: { adapter: createSlackAdapter(), toolDisplay: event => { if (event.kind === 'approved') return { kind: 'post', message: 'Onaylandı' }; if (event.kind === 'denied') return { kind: 'post', message: `Reddedildi: ${event.byUser ?? ''}` }; return undefined; }, }, }, }, ``` If the function returns nothing, a blank message, or throws, the default card is shown. If your function switches exhaustively on `event.kind`, add cases for the two new kinds: ```ts case 'approved': return { kind: 'post', message: 'Approved' }; case 'denied': return { kind: 'post', message: `Denied${event.byUser ? ` by ${event.byUser}` : ''}` }; ``` - Fixed a TypeScript error when passing tools made with `createTool` to `new Mastra({ tools })` in projects that enable `exactOptionalPropertyTypes`. You no longer need a cast to register these tools. ([#25064](https://github.com/mastra-ai/mastra/pull/25064)) - Tool invocation parts now record `updatedAt` whenever their state changes (approval response, result, error), and `tool-call-approval` chunks carry the matching `updatedAt`. Compare them to tell whether a streamed approval is newer than the stored tool call. ([#24874](https://github.com/mastra-ai/mastra/pull/24874)) ```ts for await (const chunk of subscription.stream) { if (chunk.type !== 'tool-call-approval') continue; const stored = storedParts.get(chunk.payload.toolCallId); if (stored?.updatedAt && chunk.payload.updatedAt <= stored.updatedAt) continue; // already stored promptForApproval(chunk); } ``` - Corrected the `ToolSearchProcessor` `search.minScore` documentation. Scores are raw BM25 relevance plus name-match boosts and can exceed 1, rather than falling in a 0-1 range. ([#25498](https://github.com/mastra-ai/mastra/pull/25498)) - The evented workflow engine now honors `emitStepEvents: false` (step-lifecycle watch events are suppressed instead of the option being silently ignored) and fails loudly when `createRun()` is called on an evented workflow that has no registered Mastra host, instead of starting a run that hangs or fails deep inside the event processor. Run-level events and execution routing are unaffected by the step-event gate. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - `result.text` now keeps the answer when an output processor trips the wire. It matches `result.steps[].text`, `getFullOutput().text` and `result.response.messages`. Check `result.tripwire` to see whether the output was rejected. ([#25483](https://github.com/mastra-ai/mastra/pull/25483)) ```ts const result = await agent.generate('...'); if (result.tripwire) { // result.text still holds the rejected answer for logging or review } ``` - Fixed channel handler logs to preserve serialized error details and correlation. ([#25349](https://github.com/mastra-ai/mastra/pull/25349)) - Fixed the TypeScript type for DurableAgent stream options to include providerOptions. Provider-specific options (like OpenAI reasoning settings) were already forwarded at runtime, but TypeScript rejected them when passed to stream() or generate() on a durable agent. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed suspended tool calls losing their suspension details when stored messages are converted to AI SDK v6 or v7 UI messages. `toAISdkMessages(messages, { version: 'v6' })` and `{ version: 'v7' }` now include the `data-tool-call-suspended` part (with the suspend payload and resume schema), matching the v5 output, so suspended tools stay resumable after a page reload. ([#25134](https://github.com/mastra-ai/mastra/pull/25134)) - Fixed `requestContextSchema` being silently ignored by durable and evented agents. `DurableAgent.stream()` (and evented subclasses) now validate the request context against the agent's `requestContextSchema` before starting execution, matching `Agent.stream()` and `Agent.generate()`. Invalid or missing context values now reject with the same validation error, instead of the run starting with unvalidated context. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed evented agent streams hanging forever when a run fails before its first chunk. A workflow-level failure now surfaces as an error on the stream instead of leaving the consumer waiting. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed AgentController not showing live progress for subagents delegated through `Agent.agents`. These subagents now appear in `displayState.activeSubagents` while they run, with their tool calls and text streaming in, instead of only showing up once they finish. Fixes #25019. ([#25424](https://github.com/mastra-ai/mastra/pull/25424)) - Fixed trajectory scorers in dataset experiments receiving an empty trajectory for workflow targets when no trace is stored. They now receive the workflow's executed steps, matching `runEvals`. ([#25269](https://github.com/mastra-ai/mastra/pull/25269)) - Updated dependencies [[`3f50f2a`](https://github.com/mastra-ai/mastra/commit/3f50f2a59068fc91ef7ccc513e5d93845faa298e), [`3913a33`](https://github.com/mastra-ai/mastra/commit/3913a33fd5b13dc226b1ed6253c9357cb392dd04), [`08a0aea`](https://github.com/mastra-ai/mastra/commit/08a0aea2f2af12276e333c62aaf368a9240ff68f)]: - @mastra/schema-compat@1.3.12 ## 1.72.0-alpha.10 ### Minor Changes - Added an optional `sandboxId` to the `WorkspaceSandbox` interface so hosts can read a provider's physical, reattachable sandbox id and persist it for deterministic reattach. ([#24004](https://github.com/mastra-ai/mastra/pull/24004)) ```ts const id = sandbox.sandboxId ?? sandbox.id; // physical id when the provider exposes one ``` ### Patch Changes - Fixed `foreach` over a nested workflow failing with "already resumed by another caller" when one iteration suspended while others were still starting. Each iteration now starts its own child workflow run, and suspended iterations can be resumed individually with `forEachIndex`. ([#25394](https://github.com/mastra-ai/mastra/pull/25394)) - Fixed cross-agent wakes with `requireClaimedOwner` failing with "No claimed thread owner responded" when the owning agent was alive but slow to answer (for example on a busy host or CI). Owner discovery for wakes now retries with growing timeouts for up to 1 second before giving up, and the error states how long it waited. ([#25488](https://github.com/mastra-ai/mastra/pull/25488)) Both limits can be tuned with environment variables: - `MASTRA_AGENT_THREAD_OWNER_DISCOVERY_TIMEOUT_MS` — per-attempt wait (default `100`) - `MASTRA_AGENT_THREAD_WAKE_OWNER_DISCOVERY_DEADLINE_MS` — total wake discovery budget (default `1000`) - Fixed `run.restart()` on the default workflow engine after a process crash during a nested workflow resume. Restart no longer fails with "This workflow run was not suspended", and the resumed step keeps its resume data and completes instead of suspending again. Fixes [#25187](https://github.com/mastra-ai/mastra/issues/25187). ([#25234](https://github.com/mastra-ai/mastra/pull/25234)) Fixed `timeTravel()` ignoring resume values such as `false` or `0`. - Fixed model fallback logging so recovered failures no longer page as errors. When a model in a `models` chain fails and another model is still available, Mastra now logs a single warning naming the failed and next model instead of two error-level records. Failures of the last model in the chain are still reported as errors. ([#25489](https://github.com/mastra-ai/mastra/pull/25489)) - Fixed `structuredOutput.jsonPromptInjection: false` being ignored when a structuring `model` is set. The main model's prompt no longer receives the full JSON schema on every step when you opt out of injection. ([#25478](https://github.com/mastra-ai/mastra/pull/25478)) - Fixed durable tool payload transforms after cross-process recovery. ([#25387](https://github.com/mastra-ai/mastra/pull/25387)) - A function-form `toolDisplay` on a channel adapter can now render the approval card's final state after a user clicks Approve or Deny. Previously, Mastra always replaced a custom approval card with its own English "Approved" or "Denied" text, which also showed the tool's internal registry key. ([#25486](https://github.com/mastra-ai/mastra/pull/25486)) The function now receives `approved` and `denied` events (`denied` includes `byUser`). Return `{ kind: 'post', message }` to replace the card: ```ts channels: { adapters: { slack: { adapter: createSlackAdapter(), toolDisplay: event => { if (event.kind === 'approved') return { kind: 'post', message: 'Onaylandı' }; if (event.kind === 'denied') return { kind: 'post', message: `Reddedildi: ${event.byUser ?? ''}` }; return undefined; }, }, }, }, ``` If the function returns nothing, a blank message, or throws, the default card is shown. If your function switches exhaustively on `event.kind`, add cases for the two new kinds: ```ts case 'approved': return { kind: 'post', message: 'Approved' }; case 'denied': return { kind: 'post', message: `Denied${event.byUser ? ` by ${event.byUser}` : ''}` }; ``` - Corrected the `ToolSearchProcessor` `search.minScore` documentation. Scores are raw BM25 relevance plus name-match boosts and can exceed 1, rather than falling in a 0-1 range. ([#25498](https://github.com/mastra-ai/mastra/pull/25498)) - `result.text` now keeps the answer when an output processor trips the wire. It matches `result.steps[].text`, `getFullOutput().text` and `result.response.messages`. Check `result.tripwire` to see whether the output was rejected. ([#25483](https://github.com/mastra-ai/mastra/pull/25483)) ```ts const result = await agent.generate('...'); if (result.tripwire) { // result.text still holds the rejected answer for logging or review } ``` ## 1.72.0-alpha.9 ### Minor Changes - Added `runId`, `sessionId`, `userId`, and `organizationId` filters to advanced trace queries. Use them at trace scope or inside `spans.some` / `spans.none` with `eq`, `ne`, `in`, `notIn`, `exists`, and `notExists`. Value discovery does not suggest these values, and `organizationId` can only narrow the trusted tenant scope. ([#24935](https://github.com/mastra-ai/mastra/pull/24935)) ```ts const result = await observability.queryTraces( planTraceQuery( parseTraceQueryRequest({ timeRange, where: { op: 'and', args: [ { op: 'eq', left: { path: 'userId' }, right: { literal: 'user-42' } }, { op: 'eq', left: { path: 'sessionId' }, right: { literal: 'session-9' } }, ], }, }), ), ); ``` - Fixed scheduled workflows hanging when started directly on serverless hosts (#18807). Declaring `schedule` on a workflow no longer switches it to the evented engine unless the `MASTRA_WORKERS` environment variable is set. Dev servers, single-process servers, and serverless hosts keep the default engine, so an HTTP-triggered `run.start()` runs in-process like any other workflow instead of waiting for a worker that never starts. ([#25430](https://github.com/mastra-ai/mastra/pull/25430)) **What changes** - Without `MASTRA_WORKERS`, scheduled workflows run on the default engine and no longer need storage with concurrent-update support. Cron fires run in-process on the host that runs the scheduler. - With `MASTRA_WORKERS` set to any value (split-worker deployments), scheduled workflows still run on the evented engine, and Mastra logs a warning to the console when one is created. Set `MASTRA_WORKERS` on every process in a split deployment, including `false` on the API. - `schedule` declared on Inngest workflows is ignored with a warning. Use Inngest's `cron` instead. ```typescript import { createWorkflow } from '@mastra/core/workflows'; const dailyReport = createWorkflow({ id: 'daily-report', schedule: { cron: '0 9 * * *' }, // ... }).commit(); // Before: dailyReport.engineType === 'evented', and run.start() hung without workers // After (MASTRA_WORKERS unset): dailyReport.engineType === 'default' const run = await dailyReport.createRun(); await run.start({ inputData }); // completes in-process ``` To keep a scheduled workflow on the evented engine everywhere, import `createWorkflow` from `@mastra/core/workflows/evented`. ### Patch Changes - Fixed DurableAgent dropping cached-input, cache-write, and reasoning token counts from `model_generation` span usage, so tracing exporters and cache/reasoning token metrics now match the regular Agent. ([#25392](https://github.com/mastra-ai/mastra/pull/25392)) - Fixed durable agent resumes (including `InngestAgent`) running another agent's tool when two agents register tools with the same id. On a cold worker, tool calls now resolve against the run's own agent before falling back to Mastra-wide tools. ([#25393](https://github.com/mastra-ai/mastra/pull/25393)) - Fixed generated thread titles re-creating a thread that was deleted while the title was still being generated. Deleted threads now stay deleted, and the title save no longer overwrites thread metadata that changed during generation. ([#25385](https://github.com/mastra-ai/mastra/pull/25385)) - Fixed `createRunCommandTool` security checks on Windows. `allowedBasePaths` now accepts working directories with backslash paths (previously every subdirectory was rejected), command allow/block lists now recognize Windows paths such as `C:\tools\rm.exe`, and the unsafe-character filter now rejects dangerous input consistently on every call. ([#25383](https://github.com/mastra-ai/mastra/pull/25383)) - `submit_plan` results now record the reviewer's decision. `submittedPlan.action` is `approved` or `rejected`, and `submittedPlan.feedback` holds the reviewer's comments when they asked for changes. UIs can show the outcome from message history without parsing the result text. ([#25413](https://github.com/mastra-ai/mastra/pull/25413)) - Fixed AgentController not showing live progress for subagents delegated through `Agent.agents`. These subagents now appear in `displayState.activeSubagents` while they run, with their tool calls and text streaming in, instead of only showing up once they finish. Fixes #25019. ([#25424](https://github.com/mastra-ai/mastra/pull/25424)) ## 1.72.0-alpha.8 ### Patch Changes - Fixed `Session.sendMessage()` hanging forever when its run never produced a completion event, for example when several turns started at once on the same thread. It now rejects if the session's stream processing fails, and resolves if the run is aborted or the session's thread subscription is torn down. See [#25140](https://github.com/mastra-ai/mastra/issues/25140). ([#25375](https://github.com/mastra-ai/mastra/pull/25375)) - Fixed evented workflow snapshots growing quadratically with `.foreach()` input size. Each iteration's progress record no longer stores a copy of the whole input array, so large foreach runs no longer produce huge snapshots that can stall storage. Fixes #24943. ([#25376](https://github.com/mastra-ai/mastra/pull/25376)) ## 1.72.0-alpha.7 ### Patch Changes - Fixed cancellation during session startup so cancelled requests do not reach the model or interrupt a newer turn. Added the provider finish reason to AgentController error events so clients can distinguish token limits and refusals from execution failures. ([#24321](https://github.com/mastra-ai/mastra/pull/24321)) Added a distinct session startup cancellation error so clients can handle interruptions without hiding provider or transport failures. ```ts import { isSessionStartupCancelledError } from '@mastra/core/agent-controller'; try { await session.sendMessage({ content: 'Hello' }); } catch (error) { if (!isSessionStartupCancelledError(error)) throw error; console.log('Interrupted'); } ``` - Fixed an unhandled EPIPE error that crashed processes using UnixSocketPubSub when the broker process exited before stream teardown. Unsubscribing after the broker is gone now completes cleanly, and a publish that races `close()` now rejects with a clear "UnixSocketPubSub is closed" error instead of a raw socket error. ([#25126](https://github.com/mastra-ai/mastra/pull/25126)) - Fixed a claimed thread owner running an idle wake twice when a sender retried the same cross-agent message after its acceptance acknowledgement was lost. Such a retry now resolves to the outcome the first attempt already accepted — its run, or the reason it failed or was cancelled before it ran — instead of starting a second turn that repeats the turn's tool side effects. ([#24774](https://github.com/mastra-ai/mastra/pull/24774)) - Fixed durable agents to reject invalid fallback timeout settings before preparation side effects. ([#25188](https://github.com/mastra-ai/mastra/pull/25188)) - Fixed tool result objects with a `value` key dropping sibling fields. ([#25191](https://github.com/mastra-ai/mastra/pull/25191)) - Fixed `_BASE_URL` being ignored for providers such as Google, xAI, Perplexity, Cerebras, DeepInfra, Together AI, and Vercel. Setting `GOOGLE_BASE_URL` (or the equivalent for other providers) now routes requests and API keys through your proxy or gateway instead of the public provider host. ([#25354](https://github.com/mastra-ai/mastra/pull/25354)) - **Approval prompts are now independent per thread and run.** Concurrent and detached approvals stop overwriting, stranding, or answering each other's gates: gates are keyed by tool call and tagged with the thread and run that opened them, abort and user-message interjection release only the current thread's gates, detached-thread approvals no longer fire notifications or permission hooks, and an approved run resumes against the thread, run, resource, agent, and cancellation signal that actually parked it — so a mode switch, resource re-scope, or a successor run cannot redirect or cancel the parked continuation. ([#24776](https://github.com/mastra-ai/mastra/pull/24776)) **Approval responses now require the gate's `toolCallId`.** Without the id, a response could release whichever gate happened to be parked — another thread's or another run's — so an id-less response is rejected instead of silently applied. Every caller inside the repo already passes the id; this only affects external callers of the session API. Before: ```ts session.respondToToolApproval({ decision: 'approve' }); ``` After: ```ts session.respondToToolApproval({ decision: 'approve', toolCallId }); ``` Similarly, `SessionApproval.arm()` now requires a `toolCallId`, and `getToolCallId()` is replaced by `getToolCallIds()` (which accepts an optional thread/run filter). **"Always allow" grants are now scoped to the gate's thread.** Approving with `always_allow_category` grants the tool's category to the thread that owns the gate rather than the whole session, so a background thread's approval cannot widen what the current thread may run without prompting. `grantCategory`, `grantTool`, `hasCategoryGrant`, `hasToolGrant`, and `getGrants` accept an optional `threadId`; passing one scopes the grant to that thread, omitting it keeps the existing session-wide behavior for embedders that grant explicitly. - Fixed channel handler logs to preserve serialized error details and correlation. ([#25349](https://github.com/mastra-ai/mastra/pull/25349)) ## 1.72.0-alpha.6 ## 1.72.0-alpha.5 ### Patch Changes - Fixed `textStream` and `elementStream` returning incomplete array elements when streaming structured output with an array schema. Previously, if an element arrived across several tokens, the streams could keep an early partial version (for example `{"a":1}` instead of `{"a":1,"b":2}`) that didn't match the final `object`. Each element is now emitted only once it is complete, so the streamed elements always match the final result. ([#25278](https://github.com/mastra-ai/mastra/pull/25278)) - Fixed workspace `grep` and `list_files` failing with "No mount for path: .gitignore" when a `CompositeFilesystem` has no root (`/`) mount. Reads of unmounted paths now report a not-found (`ENOENT`) error, so a missing root `.gitignore` is treated as absent. ([#25263](https://github.com/mastra-ai/mastra/pull/25263)) - Fixed `createDurableAgent` dropping non-transient `data-*` parts that tools write with `context.writer.custom()`. These parts are now saved to memory, just like with a regular `Agent`. Transient parts still only go to the stream. ([#25283](https://github.com/mastra-ai/mastra/pull/25283)) - Fixed evented workflows failing the entire run when a `.branch()` condition throws. A throwing condition is now logged and treated as false, so the remaining branches still run — matching the default workflow engine. ([#25279](https://github.com/mastra-ai/mastra/pull/25279)) - Added an `outputEncoding` option to `LocalSandbox` so command output in non-UTF-8 encodings is decoded correctly. On Windows systems using a legacy code page, such as Chinese (GBK / code page 936), native commands previously returned garbled text. ([#25266](https://github.com/mastra-ai/mastra/pull/25266)) ```ts const sandbox = new LocalSandbox({ outputEncoding: 'gbk' }); ``` UTF-8 remains the default. Fixes #25249. - Fixed threads getting stuck on Anthropic and Bedrock after a step failed mid-reasoning. Prompts now leave out reasoning that the provider cannot accept, so later turns no longer fail. ([#24928](https://github.com/mastra-ai/mastra/pull/24928)) - Fixed subagents without memory failing immediately when their workspace has a browser. These subagents now complete their tasks and still see browser context in their prompt. ([#25291](https://github.com/mastra-ai/mastra/pull/25291)) - Fixed trajectory scorers in dataset experiments receiving an empty trajectory for workflow targets when no trace is stored. They now receive the workflow's executed steps, matching `runEvals`. ([#25269](https://github.com/mastra-ai/mastra/pull/25269)) - Updated dependencies [[`3f50f2a`](https://github.com/mastra-ai/mastra/commit/3f50f2a59068fc91ef7ccc513e5d93845faa298e)]: - @mastra/schema-compat@1.3.12-alpha.1 ## 1.72.0-alpha.4 ### Patch Changes - Fixed channel messages being dropped when `chatOptions.concurrency` uses `burst`, `debounce`, or `queue`. Messages the Chat SDK batches together now reach the agent in the order they were sent and are saved to memory. Consecutive messages from one sender are merged into one turn; each sender's messages run as their own turn under that sender's identity. Custom channel handlers can read the batched messages from `context.skipped`. Fixes #22496. ([#25168](https://github.com/mastra-ai/mastra/pull/25168)) ```ts import { Agent } from '@mastra/core/agent'; import { createWhatsAppAdapter } from '@chat-adapter/whatsapp'; export const agent = new Agent({ id: 'support-agent', name: 'Support Agent', instructions: 'You are a helpful assistant.', model: 'openai/gpt-5-mini', channels: { adapters: { whatsapp: createWhatsAppAdapter() }, chatOptions: { concurrency: 'debounce' }, handlers: { onDirectMessage: async (thread, message, defaultHandler, context) => { console.log(`${context.skipped.length} earlier messages were batched`); await defaultHandler(thread, message); }, }, }, }); ``` ## 1.72.0-alpha.3 ### Minor Changes - Added `maxRetries` and `failurePolicy` options to the observational memory `observation` and `reflection` settings. When `failurePolicy` is `'continue'`, an Observer or Reflector model failure no longer ends the agent turn. ([#24863](https://github.com/mastra-ai/mastra/pull/24863)) ```ts import { Memory } from '@mastra/memory'; const memory = new Memory({ options: { observationalMemory: { observation: { maxRetries: 2, failurePolicy: 'continue' }, reflection: { maxRetries: 2, failurePolicy: 'continue' }, }, }, }); ``` - Fence background task execution with a persisted, expiring ownership lease. ([#24841](https://github.com/mastra-ai/mastra/pull/24841)) Several managers can now share one storage without running the same task twice. A running task records which worker owns it and when that ownership lapses, recovery only reclaims a task whose lease has expired, and a worker that lost ownership can no longer save a result — so a stale run cannot overwrite the current owner's outcome. Configure the lease length with `leaseDurationMs`: ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, leaseDurationMs: 30_000 }, }); ``` `recoverStaleTasksOnStart` keeps its default of `true`, which is now safe because recovery is lease-fenced. Storage writes can additionally be made conditional on the current owner and lease: ```ts await storage.updateTask( taskId, { status: 'completed', result }, { expectedStatus: 'running', expectedOwnerId: 'worker-1' }, ); ``` The new write conditions are only enforced by storage adapters that understand them. On an older storage package the manager still runs, but the conditions are ignored and writes fall back to unfenced behaviour — so upgrade the storage package alongside `@mastra/core` to get the guarantee. - Added `CyberRefusalHandler`, a processor that retries once when an OpenAI or Anthropic cybersecurity safeguard refuses ordinary work partway through an agent run. These refusals are often false positives, and nudging the model to continue usually gets past them. A second refusal on the same step is treated as genuine. ([#25172](https://github.com/mastra-ai/mastra/pull/25172)) - **OpenAI** refusals (`cyber_policy`, "This content was flagged for possible cybersecurity risk") fail the model call. Register the handler in `errorProcessors`, before `StreamErrorRetryProcessor`. - **Anthropic** refusals stop the response with a `cyber` classifier refusal (`content-filter` finish reason). Register the handler in `outputProcessors`; the refused step is rolled back before the retry. ```ts import { Agent } from '@mastra/core/agent'; import { CyberRefusalHandler } from '@mastra/core/processors'; const agent = new Agent({ id: 'coder', name: 'Coder', instructions: 'You are a coding agent', model: 'openai/gpt-5.6-sol', errorProcessors: [new CyberRefusalHandler()], outputProcessors: [new CyberRefusalHandler()], maxProcessorRetries: 3, }); ``` Coding agents created with `createCodingAgent` include it in both lanes by default, along with a `maxProcessorRetries` budget of 3 — output-step retries have no implicit default, so without a budget the Anthropic retry would be treated as an abort. ### Patch Changes - Fixed Channels tool approval cards so only the user who triggered the tool call can approve or decline it. In shared channels, clicks from other users are now ignored and logged instead of resuming the run. ([#25165](https://github.com/mastra-ai/mastra/pull/25165)) - Fixed Gemini 3 rejecting the next request with "Corrupted tool call context" when native Google Search (`webSearchTool`) and another tool (such as a skill) were called in the same step. The provider-executed flag from the tool call is now kept when its result arrives without one, so the search call and its result stay together in the conversation history. ([#24888](https://github.com/mastra-ai/mastra/pull/24888)) - Fixed follow-up DeepSeek requests failing in thinking mode after switching providers or using tools. ([#25189](https://github.com/mastra-ai/mastra/pull/25189)) - Fixed tool approvals for Inngest durable agents ([#25154](https://github.com/mastra-ai/mastra/issues/25154)). ([#25167](https://github.com/mastra-ai/mastra/pull/25167)) - Runs created with `createInngestAgent()` that wait on tool approval now appear in `GET /api/agents/:agentId/suspended-runs` and `agent.listSuspendedRuns()`. - `approve-tool-call` and `decline-tool-call` now accept these runs. They no longer return "Access denied: durable run belongs to a different resource". - Durable runs waiting on an approval-gated tool now report `requiresApproval: true` and the tool's `args`. - Processor retry feedback no longer invalidates the provider prompt cache. When a processor calls `abort(reason, { retry: true })`, the reason used to be added as a system message ahead of the conversation, so the retry and later steps missed the cache for the whole history. The reason is now added as a system reminder after the conversation, so each retry only adds to the previous request. ([#25171](https://github.com/mastra-ai/mastra/pull/25171)) The reason is sent verbatim as `{reason}`. It is no longer wrapped in the `[Processor Feedback] Your previous response was not accepted: … Please try again with the feedback in mind.` template, so write the reason as the instruction you want the model to follow. The reminder is kept in thread history as a system-reminder signal, which default memory recall hides, like other processor reminders. - Added the assistant message id to tool-input events so consumers can attribute streamed tool arguments to the model step that produced them. ([#25196](https://github.com/mastra-ai/mastra/pull/25196)) ## 1.72.0-alpha.2 ### Minor Changes - Added support for resolving channel providers dynamically. The `channels` option on the `Mastra` constructor now also accepts a resolver function, so channel connections added on the Mastra platform show up on a running server without a redeploy. ([#25149](https://github.com/mastra-ai/mastra/pull/25149)) ```typescript import { Mastra } from '@mastra/core/mastra'; import { channels } from '@mastra/connect'; export const mastra = new Mastra({ channels: await channels({ projectId: 'my-project' }), }); ``` A resolver exposes its webhook and OAuth routes up front through `getRoutes()`, and the providers themselves are resolved at runtime. Use the new `mastra.resolveChannels()` method to get the current provider map; `getChannelProviders()` keeps returning the latest resolved snapshot. Static `channels` records keep working unchanged. - Added an opt-in `summary` flag to `listWorkflowRuns`. When set, storage adapters may return only `{ status, timestamp }` for each run snapshot instead of the full snapshot. ([#25135](https://github.com/mastra-ai/mastra/pull/25135)) ```ts const { runs } = await workflow.listWorkflowRuns({ perPage: 20, page: 0, summary: true }); ``` - Storage adapters that support the new `trace-aggregate` capability can return aggregated trace results through `ObservabilityStorage.aggregateTraces()`, which executes a `TrustedTraceAggregatePlan` and returns a `TraceAggregateResponse`. The capability is declared alongside `trace-query`. Adapters that do not support it reject the call with a `MastraError` whose id is `OBSERVABILITY_STORAGE_AGGREGATE_TRACES_NOT_IMPLEMENTED`. ([#25124](https://github.com/mastra-ai/mastra/pull/25124)) ```ts import { parseTraceAggregateRequest, planTraceAggregate } from '@mastra/core/storage'; const plan = planTraceAggregate( parseTraceAggregateRequest({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, measures: ['count'], }), ); const response = await storage.aggregateTraces(plan); ``` ### Patch Changes - Fixed saved dynamic workflows losing `.map()` entries that use `initData: true`. These mappings now keep reading from the workflow's initial input after the workflow is saved and loaded again, the same as they do in memory. Workflows saved before this fix need to be saved again. ([#25152](https://github.com/mastra-ai/mastra/pull/25152)) - Stored agents backed by FilesystemStore now retain durable execution settings. ([#25123](https://github.com/mastra-ai/mastra/pull/25123)) - Documented two `aggregateTraces()` result rules: null dimension values sort last in both sort directions, and a time range with no matching traces returns `rows: []` rather than a single zero-valued row. ([#25016](https://github.com/mastra-ai/mastra/pull/25016)) - Fixed suspended tool calls losing their suspension details when stored messages are converted to AI SDK v6 or v7 UI messages. `toAISdkMessages(messages, { version: 'v6' })` and `{ version: 'v7' }` now include the `data-tool-call-suspended` part (with the suspend payload and resume schema), matching the v5 output, so suspended tools stay resumable after a page reload. ([#25134](https://github.com/mastra-ai/mastra/pull/25134)) ## 1.72.0-alpha.1 ### Minor Changes - Added a `requireDescription` option to the workspace `execute_command` tool. When enabled, the tool asks the model for a short plain-language `description` of each command, listed before `command` in the tool schema, so UIs can show it instead of the raw command. The option is off by default and the tool schema is unchanged when it is off. ([#25032](https://github.com/mastra-ai/mastra/pull/25032)) ```ts const workspace = new Workspace({ sandbox, tools: { [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: { requireDescription: true }, }, }); ``` ### Patch Changes - Fixed cost estimates for Anthropic 1-hour prompt-cache writes. The 5-minute and 1-hour cache-write token counts are now read from the provider's raw usage, so 1-hour writes are priced at their real rate instead of the cheaper 5-minute rate. ([#25011](https://github.com/mastra-ai/mastra/issues/25011)) ([#25089](https://github.com/mastra-ai/mastra/pull/25089)) - Fixed `DurableAgent.recover()` failing with `Cannot read properties of undefined (reading 'messages')` or `(reading 'length')` when a default-engine run was killed while a tool was running or between steps. Running checkpoints now keep the conversation state a restart reads back, while still trimming older history to keep storage small. ([#25107](https://github.com/mastra-ai/mastra/pull/25107)) - Fixed `restart()` and automatic recovery re-running a workflow step that had already finished. If the process stopped right after a step completed but before the next one started, restarting the run executed that step again, and for loops, foreach, parallel blocks, and branches that meant repeating every item, arm, or iteration. Restart now continues from the next step, and a run whose last step had already finished completes with that step's saved output instead of crashing. Agent-loop checkpoints now keep the just-finished step's conversation so the next step still receives it after a restart. Fixes [#24615](https://github.com/mastra-ai/mastra/issues/24615). ([#25114](https://github.com/mastra-ai/mastra/pull/25114)) `DurableAgent.recover()` no longer hangs when the run stopped after the agent had already sent its final answer. The recovered stream now finishes with that saved answer, and `onFinish` runs once. - Fixed structured output on durable agents using the evented engine. Schemas passed as `structuredOutput.schema` now stay available on later model calls and after a run is recovered. Workflow options that cannot be saved now fail with an error naming the field. ([#25075](https://github.com/mastra-ai/mastra/pull/25075)) - Fixed tool search loading every `search_tools` hit when `autoLoad` is `false`. Only `load_tool` now activates tools, so the tool list stays stable and the prompt cache is preserved across steps. ([#25076](https://github.com/mastra-ai/mastra/pull/25076)) - Fixed durable and evented agents to reject invalid model timeout settings before starting a run. ([#25079](https://github.com/mastra-ai/mastra/pull/25079)) - Added a `getBasePath` option to `AgentsMDInjector`. When set, relative tool paths resolve against that directory instead of `process.cwd()`, and `AGENTS.md`/`CLAUDE.md` files outside it are never injected. ([#25071](https://github.com/mastra-ai/mastra/pull/25071)) ```ts new AgentsMDInjector({ getBasePath: () => '/path/to/checkout', }); ``` - Fixed DurableAgent streams to hide response metadata chunks and attribute finish chunks to the agent. ([#25078](https://github.com/mastra-ai/mastra/pull/25078)) - Updated dependencies [[`3913a33`](https://github.com/mastra-ai/mastra/commit/3913a33fd5b13dc226b1ed6253c9357cb392dd04), [`08a0aea`](https://github.com/mastra-ai/mastra/commit/08a0aea2f2af12276e333c62aaf368a9240ff68f)]: - @mastra/schema-compat@1.3.12-alpha.0 ## 1.72.0-alpha.0 ### Minor Changes - Added `detach()` to `DurableAgent.observe()` so an observer can stop watching a run without affecting it. Leaving the `for await` loop over `fullStream` early (`break`, `return`, or a thrown error) now also detaches, instead of keeping the subscription open for the life of the process. ([#25048](https://github.com/mastra-ai/mastra/pull/25048)) ```ts const { output, detach } = await agent.observe(runId); request.signal.addEventListener('abort', detach, { once: true }); if (request.signal.aborted) detach(); for await (const chunk of output.fullStream) { send(chunk); } ``` `cleanup()` is unchanged. It still removes the run and its cached events, so only the process that owns the run should call it. **Behavior change:** when `idleTimeoutMs` fires without an `isAlive` probe, the observer now detaches, and the run's registry entry and cached events are kept. Before, a quiet but still-running run (for example, one waiting on a slow tool) was cleaned up. If you rely on the idle timeout to clean up runs whose process crashed, pass `isAlive` so the run is only cleaned up when it reports the run is no longer running. - Added `withInitialHistory` to `subscribeToThread`. The subscription first emits a single `thread-history` chunk with the stored thread messages, then only the retained parts that storage doesn't already cover, then live parts. Completed runs no longer replay on a retained backend such as Redis Streams, so chat UIs skip the replay animation and answered tool approvals aren't re-run. Pending approvals still come through so clients can prompt for them, and approvals already answered by a resumed run don't. Parts are compared to storage by when the model produced them, so slow publishing can't make stored parts appear again. ([#24874](https://github.com/mastra-ai/mastra/pull/24874)) `AgentController` sessions now subscribe with `withInitialHistory`, so a session opened after a restart no longer re-acts on approvals from runs that already finished. The `thread-history` chunk type only appears on subscriptions that pass `withInitialHistory`; `agent.stream()` and plain subscriptions are typed exactly as before. ```ts const subscription = await agent.subscribeToThread({ threadId, resourceId, withInitialHistory: { perPage: 40 }, }); for await (const chunk of subscription.stream) { if (chunk.type === 'thread-history') renderHistory(chunk.payload.messages); else applyLiveChunk(chunk); } ``` - Added `PubSub.trimTopic(topic, { runId })` (no-op by default). Once a thread run completes successfully and its messages are saved, Mastra deletes that run's entries from the thread topic, so retained backends no longer keep finished runs. Entries are matched by `runId`, so a run resumed after a server restart also clears what it published before the restart. Runs still in progress, waiting for approval, or owned by another process are never touched. Threads whose agent has no storage are not trimmed. ([#24875](https://github.com/mastra-ai/mastra/pull/24875)) Long runs are also trimmed as they go: each time messages are saved mid-run (by Observational Memory, or by `savePerStep` on `Agent` and `DurableAgent`), the parts up to the last finished step are dropped from the topic via the new `producedBefore` option. Pending approval and suspension prompts stay until the run itself is trimmed. Runs that fail, are canceled, or never start (for example a woken idle thread with no usable model credential) are deleted from the topic 30 seconds after they end. Nothing from them was saved, so reconnecting subscribers already ignore them; the delay lets subscribers reading live still see the failure. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`e1c3193`](https://github.com/mastra-ai/mastra/commit/e1c3193b18ca68e5cca27f7dce9b0381a6e7b95d)) - Fixed tool approvals so they always go to the run that requested them. Approving a tool call after a newer run has started on the thread no longer resumes the wrong run. ([#24874](https://github.com/mastra-ai/mastra/pull/24874)) `agent.sendToolApproval({ threadId, resourceId, runId, approved })` resumes exactly the run you name, including after a server restart. If that run has already ended, the call throws instead of resuming a different suspended run on the thread. - Fixed `DurableAgent` ignoring `savePerStep`. Each step is now saved to memory before the run continues, as with `Agent`, so a durable run that is interrupted mid-way keeps the steps it had already finished. ([#24875](https://github.com/mastra-ai/mastra/pull/24875)) ```ts await durableAgent.stream('Research and summarize', { memory: { thread: 'thread-1', resource: 'user-1' }, savePerStep: true, }); ``` - Durable agents now honor the `awaited` background-task disposition by keeping the model turn open until the authoritative tool result is persisted. Replayed workflow steps adopt the matching persisted task, resume or restart it according to its status, and reconcile terminal results instead of dispatching the tool again. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Background tool results on the durable engine now apply configured transcript transforms, so payloads a user asked to redact are no longer persisted raw to thread history. They also use the configured Mastra `idGenerator` for appended message ids instead of falling back to random UUIDs. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Durable and evented agents now honor call-time `modelSettings.maxRetries`. Previously the durable llm-execution step's retry ladder only read retry counts from serialized model-list entries (hardcoding 0 for single-model agents), so a `maxRetries` passed via `stream()`/`generate()` model settings was silently ignored and failed requests were never retried. The agent-level retry config now rides on the serialized workflow options, and the ladder applies the same precedence as the in-process loop: an explicitly configured agent-level `maxRetries` (including 0) wins, otherwise the call-time `modelSettings.maxRetries` applies. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Data chunks emitted by output processors via `writer.custom()` are now persisted to thread history on the durable engine, matching the regular agent loop (#19375). Transient chunks remain stream-only. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Provider-executed tool results that arrive in a later stream (tool call in one step, result in the next) are now committed to the transcript on the durable engine instead of staying stuck in the call state (ports #14282 to the durable agent loop). Configured transcript transforms also apply to these deferred results instead of being silently skipped. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable agents mishandling provider-executed tools (like Anthropic web search). Calls whose result had not arrived yet no longer fail with ToolNotFoundError, and results are no longer committed twice, which previously overwrote their provider metadata. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable agents looping until maxSteps when a model response ended with a terminal finish reason (content-filter refusal or length truncation) alongside a tool call. The loop now stops instead of re-sending the same request and re-triggering the same refusal (ports #17893 to the durable agent loop). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Serialize `modelSettings.timeout` into the durable workflow input. `serializeModelSettings` dropped the `timeout` field entirely, so a durable run's execution time budgets were lost across the serialization boundary: cold recovery (`DurableAgent.recover()`) could not restore the run-level `totalMs` budget — a restarted run lost its configured deadline and ran unbounded — and the per-call `stepMs`/`firstChunkMs` budgets never reached the durable llm-execution step's shared execute wrapper. All three subfields now survive serialization (positive finite numbers only), and a recovered session re-arms the persisted `totalMs` budget. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Output processors' `processToolResult` hooks now run on the durable engine. Previously they were skipped entirely, so a redaction processor had no effect on durable streams or transcripts. Processor mutations and tripwire blocks now apply to the emitted chunk and the persisted transcript, before the raw value can reach subscribers. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - The run-level `modelSettings.timeout.totalMs` budget is now enforced on durable agents (ports #21724 to the durable agent loop). Previously only the per-call budget applied, so a hanging provider or a long tool chain could run forever. The total budget now bounds the whole run, is re-armed with the original value on cold resume and recovery, and expiry surfaces as a run error rather than a silent stop. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Tool payload transforms now persist their state on the durable engine. Transcript-target transforms correctly redact stored args and results in thread history; previously only the streamed chunks were transformed and raw values were silently persisted. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Durable runs of stored agents now stay pinned to the agent version they started on. Both resuming a suspended run (#22128) and recovering a crashed run re-resolve the original pinned version instead of silently switching to the latest published version. If the pinned version no longer resolves, the run falls back to the current definition with a warning. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed delegation bail() taking one extra model turn on the evented engine. When an onDelegationComplete hook calls ctx.bail(), the supervisor loop now stops in the same iteration on every engine. Previously the bail signal was passed between workflow steps by mutating a shared request context, which only works when all steps run in one process — on the evented engine each step gets its own copy, so the loop made one more model request before stopping. The bail signal now travels on the tool call step's serialized output, which crosses process and event boundaries reliably. The Inngest agentic loop's continuation predicate now also honors this flag, so bail() stops Inngest-hosted supervisor loops in the same iteration too (previously it only stopped via maxSteps). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Propagate the caller's actor identity through the evented workflow engine. Previously the evented engine accepted `actor` at the API surface (used for the fine-grained-authorization gate) but dropped it before execution: `execute()` params omitted `actor` and the workflow event payloads carried `requestContext` but not `actor`, so steps, tools, and loop conditions running on the evented engine never saw the caller's identity — actor-based authorization inside tools silently saw no actor. The actor signal now rides the event envelope alongside `requestContext` across every hop (start, resume, restart, time travel, step events) and reaches step, tool, agent, and condition execution contexts, matching the default engine. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - EventedAgent runs on the built-in evented workflow engine again. It previously fell back to the default in-process engine because the evented path crashed on resume and recovery. Those bugs are fixed, and the engine is now covered by the durable-agent conformance suite — including crash recovery: a fresh process over the same storage can resume in-flight runs via `recover(runId)` / `recoverActiveRuns()`. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Durable agents no longer fail to start when their storage cannot apply concurrent workflow updates atomically. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) The evented execution engine advances a run from concurrent workers, so it refuses to start on a storage adapter whose workflows domain does not support atomic concurrent updates. Durable agents now degrade to the default in-process engine with a warning in that case, the same way an agent with no Mastra host already did, rather than throwing on the first `.stream()` call. This keeps agents on adapters such as `@mastra/redis` and `@mastra/valkey` working, at the cost of evented execution — the warning names the adapter and the capability it is missing. A workflow that opts into the evented engine directly, by declaring a `schedule`, still gets an error, because there is no other engine it could have meant. That error now names the storage adapter it came from, names the missing capability, and lists the adapters that support it; it previously only suggested removing the `schedule` field. - Fixed a redelivery race on the evented engine where a `workflow.step.run` event redelivered by an at-least-once transport after the step had already suspended would re-execute the step and overwrite the stored suspended record, stripping the suspendPayload that resume recovery depends on. The mid-step pre-write now drops a non-resume delivery when both the run and the target step record are suspended — a state in which no legitimate delivery exists — so the suspended record and its resume artifact survive redelivery. The check is a read-then-write that narrows the race window; fully closing it would need compare-and-set support on `updateWorkflowResults` across storage adapters. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed DurableAgent and EventedAgent treating falsy primitive resume payloads (`false`, `0`, `''`) as "no resume data" in the durable tool-call step. Suspended background tool tasks resumed with a falsy payload were stranded while a duplicate task was dispatched. The durable loop now uses the same nullish check as the regular agent loop (#22363), so only `undefined`/`null` mean "not resuming". ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - **Added atomic cross-process thread ownership handoff** ([#24898](https://github.com/mastra-ai/mastra/pull/24898)) `agent.claimThreadOwnership()` now accepts `yieldOwnership`, `onOwnershipYielded`, and `onOwnershipLost` callbacks. When another process asks for a thread and `yieldOwnership` returns `true`, the thread passes to that process in the same request. `onOwnershipYielded` runs after the handoff. Owner lookups for message delivery never call `yieldOwnership`. If another process takes an expired claim first, `onOwnershipLost` runs so the caller can retry. `UnixSocketPubSub` now coordinates thread ownership between processes that share a socket directory. A running owner restores an expired claim when no other process has taken it. When an owner hands off a thread or exits, another process can take over immediately. **Before** ```ts const claim = await agent.claimThreadOwnership({ resourceId, threadId, }); ``` **After** ```ts let claimActive = true; const claim = await agent.claimThreadOwnership({ resourceId, threadId, yieldOwnership: () => session.thread.getId() !== threadId, onOwnershipYielded: () => { claimActive = false; }, onOwnershipLost: () => scheduleClaimRetry(threadId), }); ``` - Fixed durable agents crashing during crash recovery when the process was restarted while an LLM call was in flight. The active step now restarts from its own preserved input instead of a completed predecessor's pruned output, which previously caused "Cannot read properties of undefined (reading 'messages')" (#22636). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - EventedAgent tool-approval, tool-denial, and in-execution resume no longer fail with "Cannot read properties of undefined (reading 'messages')": the evented engine retains running conversation history in persisted agent-loop snapshots, since its storage-merged step results are live data (unlike the default engine's write-only snapshots) ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Removed the unused `executeDurableToolCalls` helper and its `ToolExecutionContext`/`ToolExecutionError` types from `@mastra/core/agent/durable`. This code was never wired into any agent loop; both the durable and regular loops execute tools through their own step implementations. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed agents with working memory seeing the word "null" in their instructions when no working memory had been saved yet, for example on the first message of a new thread. The agent now sees "No working memory data available." instead. Fixes [#23724](https://github.com/mastra-ai/mastra/issues/23724). ([#25057](https://github.com/mastra-ai/mastra/pull/25057)) - Evented workflows now persist the running step record and routing state before executing each step, so runs killed mid-step recover via restart() instead of failing with "Execution path is empty" ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed CachingPubSub dropping events that arrive from another caching tier. When a durable agent's stream cache follows a source bus that is itself a CachingPubSub with its own cache (for example a user-supplied CachingPubSub passed to new Mastra({ pubsub })), events from that tier already carry an index and were treated as local echoes and discarded — they never reached the agent's subscribers and could not be replayed. The follower now only treats indexed events as echoes when the source shares the same transport; foreign-indexed events are cached under a fresh index and republished so live delivery and replay both work across tiers. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Deprecated Observational Memory `scope: 'resource'` in the memory config types. Editors now show the `scope` option as deprecated. Remove it to use the default thread scope, and enable `retrieval` or resource-scoped working memory for cross-thread continuity. ([#24933](https://github.com/mastra-ai/mastra/pull/24933)) - Fixed evented agent streams losing events when the agent uses a custom pubsub. The evented workflow engine publishes run events on the Mastra instance's pubsub, but the agent's stream listened only on its own pubsub — with a custom pubsub configured, suspend, finish, and tool events silently never reached the stream. The agent's caching pubsub now also follows the Mastra instance's pubsub as a source, so evented runs stream correctly regardless of which pubsub the agent was configured with. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed a race where a background tool result could be saved to memory without its configured transcript transform applied. When a background task finished while the turn was still being persisted, the memory history save could land last and store the raw tool result instead of the redacted one. All persistence paths now apply the same transcript redaction, so raw payloads never reach storage. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed agent runs with `readOnly` memory still persisting their transcript when a background tool task completed. The background-result flush now honors the readOnly contract the same way the suspension flush already did. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed thread subscribers showing a run as idle after a tool approval. When a run resumes, thread subscribers now get a `start` event for the resumed part. This applies to subscribers that were already listening and to those that join while the run resumes, so the UI shows the run as running again and can cancel it. ([#24875](https://github.com/mastra-ai/mastra/pull/24875)) - Fixed Anthropic extended-thinking threads with working memory getting stuck on every turn with "thinking blocks in the latest assistant message cannot be modified". When the model spent a step only on an `updateWorkingMemory` call, the saved history kept that step's thinking without its tool call, so later requests replayed it merged into the next step. That step is now dropped when the call is hidden. ([#24924](https://github.com/mastra-ai/mastra/pull/24924)) Threads already saved in this state recover when `ProviderHistoryCompat` is in the agent's error processors: on that Anthropic error it drops the leftover thinking step and retries once. ```ts import { Agent } from '@mastra/core/agent'; import { ProviderHistoryCompat } from '@mastra/core/processors'; const agent = new Agent({ // ... errorProcessors: [new ProviderHistoryCompat()], }); ``` Fixes [#22798](https://github.com/mastra-ai/mastra/issues/22798). - Fixed the durable agent engine leaking raw tool output when an output or result processor throws. Previously, if a processor (for example a redaction processor) failed with a non-tripwire error, the durable engine would warn and continue with the unprocessed value: the raw tool result was still emitted on the stream and persisted to the transcript. Both paths now fail closed — a throwing stream processor suppresses the chunk instead of emitting the original, and a throwing tool-result processor substitutes an error placeholder for both emission and persistence. The run itself stays alive in both cases. This matches the regular loop, which already refused to emit or persist raw values when a processor throws. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed durable agents looping until maxSteps when a provider ends the stream with finishReason 'other' and no output. The empty response now surfaces as a stream error after one attempt, and completion checkers no longer grade errored iterations (ports #22273 to the durable agent loop). ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Added `withInitialHistory` to thread subscriptions over HTTP. Pass it to `subscribeToThread` in `@mastra/client-js`, or to `useChat` in `@mastra/react`, to get stored thread messages as one `thread-history` chunk before live parts. Completed runs no longer replay on reconnect. ([#24876](https://github.com/mastra-ai/mastra/pull/24876)) ```ts const subscription = await agent.subscribeToThread({ threadId, resourceId, withInitialHistory: { perPage: 40 } }); ``` ```tsx const chat = useChat({ agentId, threadId, resourceId, enableThreadSignals: true, withInitialHistory: true }); ``` Agent controller tool-approval requests now resume the stored suspended run when no approval is waiting in the session, so approval cards restored from thread history after a restart can be answered. - Fixed a TypeScript error when passing tools made with `createTool` to `new Mastra({ tools })` in projects that enable `exactOptionalPropertyTypes`. You no longer need a cast to register these tools. ([#25064](https://github.com/mastra-ai/mastra/pull/25064)) - Tool invocation parts now record `updatedAt` whenever their state changes (approval response, result, error), and `tool-call-approval` chunks carry the matching `updatedAt`. Compare them to tell whether a streamed approval is newer than the stored tool call. ([#24874](https://github.com/mastra-ai/mastra/pull/24874)) ```ts for await (const chunk of subscription.stream) { if (chunk.type !== 'tool-call-approval') continue; const stored = storedParts.get(chunk.payload.toolCallId); if (stored?.updatedAt && chunk.payload.updatedAt <= stored.updatedAt) continue; // already stored promptForApproval(chunk); } ``` - The evented workflow engine now honors `emitStepEvents: false` (step-lifecycle watch events are suppressed instead of the option being silently ignored) and fails loudly when `createRun()` is called on an evented workflow that has no registered Mastra host, instead of starting a run that hangs or fails deep inside the event processor. Run-level events and execution routing are unaffected by the step-event gate. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed the TypeScript type for DurableAgent stream options to include providerOptions. Provider-specific options (like OpenAI reasoning settings) were already forwarded at runtime, but TypeScript rejected them when passed to stream() or generate() on a durable agent. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed `requestContextSchema` being silently ignored by durable and evented agents. `DurableAgent.stream()` (and evented subclasses) now validate the request context against the agent's `requestContextSchema` before starting execution, matching `Agent.stream()` and `Agent.generate()`. Invalid or missing context values now reject with the same validation error, instead of the run starting with unvalidated context. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) - Fixed evented agent streams hanging forever when a run fails before its first chunk. A workflow-level failure now surfaces as an error on the stream instead of leaving the consumer waiting. ([#24569](https://github.com/mastra-ai/mastra/pull/24569)) ## 1.71.0 ### Minor Changes - Added per-endpoint discovery features that observability storage can declare from `getFeatures()`: `entity-type-discovery`, `entity-name-discovery`, `service-name-discovery`, `environment-discovery`, `tag-discovery` and `metric-discovery`. The in-memory observability store now declares `metrics`, `logs` and discovery support. ([#25008](https://github.com/mastra-ai/mastra/pull/25008)) Custom observability stores can declare what they support so Studio only calls those APIs: ```ts import { ObservabilityStorage } from '@mastra/core/storage'; class MyObservabilityStore extends ObservabilityStorage { override getFeatures() { return ['logs', 'entity-name-discovery', 'tag-discovery'] as const; } } ``` - Added `planTraceAggregate()`. It checks a parsed `aggregateTraces()` request and returns a `TrustedTraceAggregatePlan` that any storage backend can execute without re-validating the request. ([#24868](https://github.com/mastra-ai/mastra/pull/24868)) ```ts import { parseTraceAggregateRequest, planTraceAggregate } from '@mastra/core/storage'; const plan = planTraceAggregate(parseTraceAggregateRequest(request), { scope: { organizationId: 'org_123' }, }); // plan.dimensions, plan.measures, plan.having, plan.orderBy, plan.limit, plan.where ``` **Validation rules** - `timeRange` and `where` are validated exactly like `queryTraces()`. - `groupBy` accepts only supported dimensions. - `countDistinct` accepts `traceId` or any field that `groupBy` accepts. - `having` can reference a requested measure or `count`. - `orderBy` can reference a requested measure, a requested dimension, or `count`. `bucket` is not an ordering target. - The time range can be at most 365 days. - An `interval` can touch at most 1000 UTC-aligned buckets, and `limit × buckets` can be at most 10,000 rows. `having`, `orderBy`, and `limit` apply to groups using measures computed over the whole time range; with an `interval`, each returned group is a complete series in bucket order. The plan's JSDoc spells this out for backends. Invalid requests throw `TraceQueryValidationError` with the same issue codes and JSON paths as `planTraceQuery()`. The new `too_many_buckets` code names the smallest permitted interval; `too_many_rows` asks the caller to lower `limit` or widen `interval`. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`fc7d2c1`](https://github.com/mastra-ai/mastra/commit/fc7d2c102e911f43f70f425e67c970231ea19363)) - Tools now start as soon as their own arguments are complete, instead of waiting for the model to finish streaming the whole step. This is on by default and removes seconds of idle time from steps that call several tools. ([#25005](https://github.com/mastra-ai/mastra/pull/25005)) ```ts // Default: each tool starts as soon as its call is complete. const eager = await agent.stream('Compare the weather in Paris and Rome'); // Opt out to restore the previous scheduling. const deferred = await agent.stream('Compare the weather in Paris and Rome', { eagerToolExecution: false, }); ``` These calls still wait for the model to finish: tools that need approval, declare a suspend schema, run on the provider or client, or run in the background, and runs that use the `called` concurrency strategy or output processors that run after the stream. A tool that already finished is never run again and its result is always kept, including on retry, fallback, and abort. A tool still running when an attempt is retried, falls back, or is aborted finishes first, and its result is kept. A tool that calls `suspend()` at runtime also runs once: the run suspends instead of retrying the model. See the `stream()` reference for the full rules. - Added a `feedback` observability storage feature so clients can identify whether a store supports the feedback APIs. Storage adapters that implement the feedback methods declare the feature to advertise support. ([#25020](https://github.com/mastra-ai/mastra/pull/25020)) ```ts import { ObservabilityStorage } from '@mastra/core/storage'; class MyObservabilityStore extends ObservabilityStorage { public getFeatures() { return ['feedback'] as const; } } ``` - Fixed the Workspace example to use current providers and distinguish local paths from cloud mount paths. ([#24988](https://github.com/mastra-ai/mastra/pull/24988)) - Fixed a denial-of-service risk in dataset schema validation. Regex `pattern` and `patternProperties` in dataset input and ground truth schemas now run on a linear-time (RE2) engine, so a crafted pattern can no longer freeze the server. Fixes #24981. ([#25044](https://github.com/mastra-ai/mastra/pull/25044)) **Behavior changes** - Patterns using syntax RE2 cannot run, such as lookarounds (`(?=...)`, `(? console.log('model selection', decision), }), ], }); ``` To use a `Classifier` you already have, pass it as `classifier` and map its typed answers to a model with `select`. Routing doesn't always save money. Models don't share prompt caches, and a cheaper model can take more steps. Measure cost and quality on your own traffic first. **Behavior** - The chosen model serves the whole run. Set `scope: 'first-step'` to change only the first call. - If the classifier fails, the agent's configured model is used. - If the chosen model fails, the agent's fallback models take over. - With `minProbability` set, the configured model is used when the confidence is too low or missing. - Added a `closeOnSuspend` option to durable agent `stream()` and `resume()`, so callers can end the stream when a tool suspends. ([#24894](https://github.com/mastra-ai/mastra/pull/24894)) Previously, the stream returned by `DurableAgent.stream()` (and `createInngestAgent().stream()`) stayed open after a tool suspended for approval or user input, and there was no public way to change that. Loops over `fullStream` hung, so integrations like AG-UI could not emit `RUN_FINISHED`. Pass `closeOnSuspend: true` to close the stream at the suspension boundary, matching non-durable `Agent.stream()`: ```ts const result = await durableAgent.stream('hi', { closeOnSuspend: true }); for await (const chunk of result.fullStream) { // loop ends after the tool-call-suspended chunk } ``` The default is unchanged (`false`): the stream stays open across suspension. - Added helpers to check which trace fields you can group by and which measures you can request for the upcoming `aggregateTraces()` API. ([#24847](https://github.com/mastra-ai/mastra/pull/24847)) - Group by trace fields such as `status` or `userId`, or by top-level `metadata.` paths. - Supported measures: `count`, `duration.avg`/`min`/`max`/`p50`/`p90`/`p95`/`p99`, `errorCount`, `errorRate`, and `countDistinct.`. Percentile values can be approximate. ```ts import { getTraceAggregateDimensionDescriptors, isTraceAggregateDimension, parseTraceAggregateMeasure, } from '@mastra/core/storage'; isTraceAggregateDimension('metadata.tenant'); // true — top-level metadata keys are groupable isTraceAggregateDimension('metadata.customer.id'); // false — nested paths are not isTraceAggregateDimension('traceId'); // false — identity fields are not dimensions parseTraceAggregateMeasure('duration.p95'); // { type: 'canonical', measure: 'duration.p95', rule: { approximate: true, ... } } parseTraceAggregateMeasure('countDistinct.traceId'); // { type: 'countDistinct', field: 'traceId' } const dimensions = getTraceAggregateDimensionDescriptors(); // canonical trace-field descriptors (metadata. is checked via isTraceAggregateDimension) ``` - Filter client-echoed history before memory processors load stored messages. ([#24076](https://github.com/mastra-ai/mastra/pull/24076)) On a thread that already has stored messages, memory now keeps only the new part of the request input: the trailing user messages, plus any tool outcomes the client sends for calls the stored conversation still has pending (results, errors, denials, and approval answers). This works whether the outcome arrives on its own or together with the next user message. An empty thread is still seeded with the full input, with assistant provider metadata stripped. When an input message has the same ID as a stored message, the stored message remains the base so its reasoning, provider metadata, ordering, and timestamp are retained. Client tool outcomes only fill in calls that are still pending, so an echo can't overwrite a stored tool result. This prevents lossy client echoes from orphaning OpenAI reasoning items, re-persisting user messages with client timestamps, or duplicating assistant text during history replay. Observational Memory uses the same stored-base layering behavior. This is a behavior change: on an existing thread, any input message before the last assistant message that isn't stored is removed. That includes few-shot examples and caller-assembled message arrays, not only assistant messages sent to modify the thread or user messages re-sent with a changed `createdAt` to reorder it. Use `memory.saveMessages` or the memory store's `updateMessages` to change stored history. To opt out, set the new `retainFullInput` memory option, per call on `memory.options` or agent-wide in the memory constructor options. The request input is then processed exactly as supplied, history still loads, and every input message that isn't already stored is saved to the thread. The `useAgent` structured output path uses it so its replayed request keeps the parent's message prefix. Fixes #24052. - Added typed descriptions for processor span payloads and pipeline attributes. Processor spans now record their exact pipeline phase, so consumers can narrow supported payloads without guessing their shape. ([#24672](https://github.com/mastra-ai/mastra/pull/24672)) ```ts import { describeSpanOutput } from '@mastra/core/observability'; const output = describeSpanOutput(span); if (output?.type === 'processor' && output.value.phase === 'outputStream') { output.value.data.totalChunks; // number } ``` Added `describeProcessorPipeline` for executor, pipeline position, hook duration, mutations, and tripwire details. Unknown attributes remain separate from the known fields, so no value is rendered twice. Spans recorded before the phase existed keep their untyped shape and fall back to JSON. Fixed missing message-list mutation logs in workflow processor executions. - Added `createClassifierScorer()` for using a configured `Classifier` as a typed Mastra scorer. Select one classifier question, get a score between 0 and 1, and retain the classifier evidence in the scorer result. ([#24792](https://github.com/mastra-ai/mastra/pull/24792)) ```typescript import { Classifier } from '@mastra/core/classifier'; import { createClassifierScorer } from '@mastra/core/evals'; import { getAssistantMessageFromRunOutput } from '@mastra/evals/scorers/utils'; const classifier = new Classifier({ id: 'response-quality', model, questions: { quality: { type: 'score', criteria: ['Incorrect', 'Partially correct', 'Correct'], }, }, }); const scorer = createClassifierScorer({ id: 'response-quality-scorer', classifier, question: 'quality', type: 'agent', state: ({ run }) => ({ output: getAssistantMessageFromRunOutput(run.output) ?? '' }), }); ``` - Added tag predicates to advanced trace queries. Trace predicates accept `includes` and `notIncludes` on the `tags` field, and `exists` / `notExists` on `tags` mean "at least one tag" / "no tags". Missing and empty tag lists are treated alike. `tags` also supports value discovery. ([#24554](https://github.com/mastra-ai/mastra/pull/24554)) **Example** ```ts { op: 'includes', path: 'tags', value: 'manual-review' } ``` - Add an optional `title` to `createTool()` so a tool call can carry a human-readable display name. Closes #20249. ([#24117](https://github.com/mastra-ai/mastra/pull/24117)) The title is never sent to the model. It is stamped on the `tool-call-input-streaming-start` and `tool-call` stream chunks and persisted on the stored tool-invocation part, so a chat UI can label the call after a reload without a client-side name catalog. ```ts const weatherTool = createTool({ id: 'get_weather_by_coordinates', title: 'Weather Lookup', description: 'Fetches the current weather for a latitude/longitude pair', inputSchema: z.object({ lat: z.number(), lon: z.number() }), execute: async ({ lat, lon }) => fetchWeather(lat, lon), }); ``` - Added a trusted tenant scope to advanced trace queries. Hosts pass `{ organizationId, resourceId? }` to `planTraceQuery`, `planThreadQuery`, and the discovery planners; the scope is carried on the trusted plan, ANDed into every root and related-signal scan by stores, and bound into keyset cursors so a cursor reused under another scope fails with `TRACE_QUERY_CURSOR_CONFLICT` before storage runs. Callers still can't name `organizationId` or `projectId` in predicates. No scope means no filter, so self-hosted behavior is unchanged. ([#24566](https://github.com/mastra-ai/mastra/pull/24566)) **Example** ```ts const plan = planTraceQuery(parseTraceQueryRequest(request), { scope: { organizationId: 'org_123', resourceId: 'project_456' }, }); ``` - Added trace-level `durationMs` predicates to advanced trace queries. ([#24635](https://github.com/mastra-ai/mastra/pull/24635)) Previously, duration filtering required a span relation, which can match a child span: ```typescript where: { spans: { some: { op: "gt", left: { path: "durationMs" }, right: { literal: 5000 } } } } ``` Use the top-level field to evaluate only the selected completed root: ```typescript where: { op: "gt", left: { path: "durationMs" }, right: { literal: 5000 } } ``` - Expanded `cancelQueuedMessages({ signalIds })` to cancel pending input across all Agents sharing the runtime and memory thread. Added `clearPendingSignals` to thread abort options. ([#23942](https://github.com/mastra-ai/mastra/pull/23942)) **Changed behavior** - Signal-ID cancellation previously matched only the calling Agent's queued messages. It now also covers other Agents' messages, pre-run signals, and signals pending in an active run. - The `queueOwnerId` selector still cancels only the calling Agent's queued messages in that owner group. - Cancellation remains effective during lease handoffs. Clear-on-abort prevents queued input from returning after a preparation failure. - Running Agents listen for remote signals, cancellation, and abort requests without requiring `subscribeToThread()`. Disconnecting the last thread observer no longer disables these controls. - Remote thread aborts also stop durable runs through their existing abort transport, including when clearing pending input without an observer. - Remote input already in transit survives run handoffs. Observer-only signal copies don't keep execution listeners alive or become new input when a thread is reused. - Input queued on a claimed thread remains remotely cancellable after the claim is released, even without an open thread subscription. - Cancellation results exclude observer history and report only pending work removed locally. Delayed enqueue retries can't restore cancelled input while the execution listener remains active. ```typescript const thread = { resourceId: 'user-123', threadId: 'thread-abc' }; // Selected pending input across Agents sharing the thread. agent.cancelQueuedMessages({ ...thread, signalIds: ['signal-123'] }); // Existing Agent-scoped owner-group behavior. agent.cancelQueuedMessages({ ...thread, queueOwnerId: 'session-123' }); agent.abortThreadStream({ ...thread, clearPendingSignals: true }); // Existing behavior: abort without clearing pending input. agent.abortThreadStream(thread); ``` Selected-ID cancellation publishes all requested IDs through PubSub, even when none are pending locally, so other processes subscribed to the thread can remove matching pending input. Propagation is asynchronous and best-effort. The result reports only local cancellations, without remote acknowledgements. Clear-on-abort forwards the clear flag to the active owner, but doesn't clear every process's queues. Neither operation cancels `continueWithMessages()` continuations or undoes persisted effects. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`bfde500`](https://github.com/mastra-ai/mastra/commit/bfde5009d1d9bdbce241132b3df9e638ad805fab)) - Fixed channel threads getting permanently stuck after a tool call suspended on adapters that cannot show approval buttons. With `toolDisplay: 'hidden'`, runs now auto-resume suspended tools on platforms without interactive buttons (for example SMS, iMessage or custom gateways). Set the new `approvalButtons` adapter option to override the detection. Inbound messages that don't start a run are now logged at warn level instead of being dropped silently. ([#24913](https://github.com/mastra-ai/mastra/pull/24913)) ```ts channels: { adapters: { imessage: { adapter: imessageAdapter, toolDisplay: 'hidden' }, // now auto-resumes custom: { adapter: customAdapter, toolDisplay: 'hidden', approvalButtons: true }, }, } ``` - Fixed client-sent copies of stored messages changing what's stored. When a request includes a message with the same ID as a stored one: ([#24895](https://github.com/mastra-ai/mastra/pull/24895)) - The stored message is kept as is. Client text, reasoning, and metadata no longer replace or add to it. - For assistant messages, the client copy can only fill in a tool outcome for a call the stored message still has pending. - To change a stored message, update it in storage. - This also applies with `retainFullInput`, so the client's rendered copy can't undo an output processor's rewrite of a saved message, such as a redacted card number. Fixes #20836. - Channel approval cards (Slack, Telegram, etc.) now show the sub-agent tool awaiting approval and its arguments, instead of the supervisor's `agent-` delegation call. ([#24907](https://github.com/mastra-ai/mastra/pull/24907)) - Fixed in-memory dataset configuration reads to return undefined for unset or cleared tags, target type, target IDs, and scorer IDs, matching LibSQL. Serialized responses omit these properties instead of returning null. Consumers should use nullish checks rather than require explicit null properties. Empty arrays remain distinct from cleared settings. ([#23937](https://github.com/mastra-ai/mastra/pull/23937)) - Fixed agent controller chat channels showing Approve/Deny buttons for tools that run or are blocked automatically. Channels now only show approval buttons when a person actually needs to decide (tools with an `ask` policy). Fixes #22379. ([#24914](https://github.com/mastra-ai/mastra/pull/24914)) - Fixed per-request reply topics leaking streams on persistent pub/sub backends. The agent runtime's cross-process flows (thread owner discovery, peer discovery, and idle-signal acceptance) create a unique reply topic per request; on backends like Redis Streams, subscribing creates a real stream key that previously outlived the request forever. Reply topics are now deleted via `clearTopic` as soon as their request settles, and a request whose timeout fires before its subscribe finishes no longer publishes at all. Requests also carry the caller's absolute deadline: a responder that receives one late (backlog replay on a fresh subscription, redelivery) drops it instead of replying into the released reply topic — or, for idle signals, starting a run the caller already reported as timed out. A clock-skew grace keeps responders from dropping live requests when process clocks drift. No change for the in-memory pub/sub, where `clearTopic` is a no-op. ([#24580](https://github.com/mastra-ai/mastra/pull/24580)) Full cleanup on Redis Streams requires the matching `@mastra/redis-streams` release. With an older one, `unsubscribe()` does nothing while a subscribe is still in flight, and that late subscribe recreates the stream key after `clearTopic` deletes it. - Fixed `TokenLimiterProcessor` counting history that later prompt processors remove from the request. In the default `best-fit` and `contiguous` trim modes, the input budget is now enforced on the provider prompt in `processLLMRequest`, after earlier prompt processors such as `ToolCallFilter` have run, so only tokens that reach the model are counted. Tool calls and their results are kept or removed together, and stored messages are no longer changed. ([#24679](https://github.com/mastra-ai/mastra/pull/24679)) Where `processLLMRequest` doesn't run, such as `generateLegacy()`, `streamLegacy()` and limiters inside a processor workflow, `processInputStep` still trims stored messages as before. - Durable agent turns now use far fewer Inngest steps, including when observability is not configured. A 20-step agent turn previously used 133–158 Inngest steps. ([#24782](https://github.com/mastra-ai/mastra/pull/24782)) - Fixed images and files returned by tools going missing when an agent uses a router model such as `anthropic/*`. The selected model now receives the complete tool result, including images and files. ([#24910](https://github.com/mastra-ai/mastra/pull/24910)) - Dynamic workflow validation now reads JSON-Schema `properties` as own keys, so a field named `constructor` or `toString` no longer looks present on every schema ([#24900](https://github.com/mastra-ai/mastra/pull/24900)) - Reduced stored durable agent iteration data for large prompts and tool sets. ([#24946](https://github.com/mastra-ai/mastra/pull/24946)) - Fixed Bedrock-hosted OpenAI models sending unsupported temperature and top-p settings. Fixes #24815. ([#24886](https://github.com/mastra-ai/mastra/pull/24886)) - Added public Zod request and response schemas and types for the upcoming `aggregateTraces()` observability operation: `traceAggregateRequestSchema`, `traceAggregateResponseSchema`, and `parseTraceAggregateRequest()`. Selection (`timeRange`, `where`) reuses the existing trace-query schemas, so aggregate and list queries validate the same population. No runtime operation ships yet; the storage method and HTTP route follow in later releases. ([#24834](https://github.com/mastra-ai/mastra/pull/24834)) ```ts import { parseTraceAggregateRequest } from '@mastra/core/storage'; const request = parseTraceAggregateRequest({ timeRange: { from: '2026-09-01T00:00:00Z', to: '2026-09-08T00:00:00Z' }, groupBy: ['entityName'], measures: ['count', 'duration.p95'], }); // request.limit === 100, request.orderBy === { field: 'count', direction: 'desc' } ``` ## 1.70.0-alpha.4 ### Patch Changes - Reduced stored durable agent iteration data for large prompts and tool sets. ([#24946](https://github.com/mastra-ai/mastra/pull/24946)) ## 1.70.0-alpha.3 ### Patch Changes - Fixed channel threads getting permanently stuck after a tool call suspended on adapters that cannot show approval buttons. With `toolDisplay: 'hidden'`, runs now auto-resume suspended tools on platforms without interactive buttons (for example SMS, iMessage or custom gateways). Set the new `approvalButtons` adapter option to override the detection. Inbound messages that don't start a run are now logged at warn level instead of being dropped silently. ([#24913](https://github.com/mastra-ai/mastra/pull/24913)) ```ts channels: { adapters: { imessage: { adapter: imessageAdapter, toolDisplay: 'hidden' }, // now auto-resumes custom: { adapter: customAdapter, toolDisplay: 'hidden', approvalButtons: true }, }, } ``` - Fixed client-sent copies of stored messages changing what's stored. When a request includes a message with the same ID as a stored one: ([#24895](https://github.com/mastra-ai/mastra/pull/24895)) - The stored message is kept as is. Client text, reasoning, and metadata no longer replace or add to it. - For assistant messages, the client copy can only fill in a tool outcome for a call the stored message still has pending. - To change a stored message, update it in storage. - This also applies with `retainFullInput`, so the client's rendered copy can't undo an output processor's rewrite of a saved message, such as a redacted card number. Fixes #20836. - Channel approval cards (Slack, Telegram, etc.) now show the sub-agent tool awaiting approval and its arguments, instead of the supervisor's `agent-` delegation call. ([#24907](https://github.com/mastra-ai/mastra/pull/24907)) - Fixed agent controller chat channels showing Approve/Deny buttons for tools that run or are blocked automatically. Channels now only show approval buttons when a person actually needs to decide (tools with an `ask` policy). Fixes #22379. ([#24914](https://github.com/mastra-ai/mastra/pull/24914)) - Fixed `TokenLimiterProcessor` counting history that later prompt processors remove from the request. In the default `best-fit` and `contiguous` trim modes, the input budget is now enforced on the provider prompt in `processLLMRequest`, after earlier prompt processors such as `ToolCallFilter` have run, so only tokens that reach the model are counted. Tool calls and their results are kept or removed together, and stored messages are no longer changed. ([#24679](https://github.com/mastra-ai/mastra/pull/24679)) Where `processLLMRequest` doesn't run, such as `generateLegacy()`, `streamLegacy()` and limiters inside a processor workflow, `processInputStep` still trims stored messages as before. - Fixed images and files returned by tools going missing when an agent uses a router model such as `anthropic/*`. The selected model now receives the complete tool result, including images and files. ([#24910](https://github.com/mastra-ai/mastra/pull/24910)) ## 1.70.0-alpha.2 ### Minor Changes - Added a `closeOnSuspend` option to durable agent `stream()` and `resume()`, so callers can end the stream when a tool suspends. ([#24894](https://github.com/mastra-ai/mastra/pull/24894)) Previously, the stream returned by `DurableAgent.stream()` (and `createInngestAgent().stream()`) stayed open after a tool suspended for approval or user input, and there was no public way to change that. Loops over `fullStream` hung, so integrations like AG-UI could not emit `RUN_FINISHED`. Pass `closeOnSuspend: true` to close the stream at the suspension boundary, matching non-durable `Agent.stream()`: ```ts const result = await durableAgent.stream('hi', { closeOnSuspend: true }); for await (const chunk of result.fullStream) { // loop ends after the tool-call-suspended chunk } ``` The default is unchanged (`false`): the stream stays open across suspension. - Added trace-level `durationMs` predicates to advanced trace queries. ([#24635](https://github.com/mastra-ai/mastra/pull/24635)) Previously, duration filtering required a span relation, which can match a child span: ```typescript where: { spans: { some: { op: "gt", left: { path: "durationMs" }, right: { literal: 5000 } } } } ``` Use the top-level field to evaluate only the selected completed root: ```typescript where: { op: "gt", left: { path: "durationMs" }, right: { literal: 5000 } } ``` ### Patch Changes - Dynamic workflow validation now reads JSON-Schema `properties` as own keys, so a field named `constructor` or `toString` no longer looks present on every schema ([#24900](https://github.com/mastra-ai/mastra/pull/24900)) ## 1.70.0-alpha.1 ### Minor Changes - Added `ModelSelectionProcessor`, which picks the model for each request with a classifier. Keep a capable model as the agent's default and let simple requests run on a cheaper one. ([#24865](https://github.com/mastra-ai/mastra/pull/24865)) Describe each model and the requests it should handle. The processor builds the classifier for you: ```ts import { Agent } from '@mastra/core/agent'; import { ModelSelectionProcessor } from '@mastra/core/processors'; // `model` is the evaluation model that makes the decision (`EvaluationModelV4 | MastraEvaluationModel`, the same type Classifier accepts). new Agent({ name: 'support-agent', model: 'openai/gpt-5.6-sol', inputProcessors: [ new ModelSelectionProcessor({ model, choices: [ { model: 'openai/gpt-5-mini', criteria: 'Answerable in one or two sentences with no reasoning steps' }, { model: 'openai/gpt-5.6-sol', criteria: 'Requires multi-step reasoning or careful judgment' }, ], onDecision: decision => console.log('model selection', decision), }), ], }); ``` To use a `Classifier` you already have, pass it as `classifier` and map its typed answers to a model with `select`. Routing doesn't always save money. Models don't share prompt caches, and a cheaper model can take more steps. Measure cost and quality on your own traffic first. **Behavior** - The chosen model serves the whole run. Set `scope: 'first-step'` to change only the first call. - If the classifier fails, the agent's configured model is used. - If the chosen model fails, the agent's fallback models take over. - With `minProbability` set, the configured model is used when the confidence is too low or missing. - Added helpers to check which trace fields you can group by and which measures you can request for the upcoming `aggregateTraces()` API. ([#24847](https://github.com/mastra-ai/mastra/pull/24847)) - Group by trace fields such as `status` or `userId`, or by top-level `metadata.` paths. - Supported measures: `count`, `duration.avg`/`min`/`max`/`p50`/`p90`/`p95`/`p99`, `errorCount`, `errorRate`, and `countDistinct.`. Percentile values can be approximate. ```ts import { getTraceAggregateDimensionDescriptors, isTraceAggregateDimension, parseTraceAggregateMeasure, } from '@mastra/core/storage'; isTraceAggregateDimension('metadata.tenant'); // true — top-level metadata keys are groupable isTraceAggregateDimension('metadata.customer.id'); // false — nested paths are not isTraceAggregateDimension('traceId'); // false — identity fields are not dimensions parseTraceAggregateMeasure('duration.p95'); // { type: 'canonical', measure: 'duration.p95', rule: { approximate: true, ... } } parseTraceAggregateMeasure('countDistinct.traceId'); // { type: 'countDistinct', field: 'traceId' } const dimensions = getTraceAggregateDimensionDescriptors(); // canonical trace-field descriptors (metadata. is checked via isTraceAggregateDimension) ``` - Filter client-echoed history before memory processors load stored messages. ([#24076](https://github.com/mastra-ai/mastra/pull/24076)) On a thread that already has stored messages, memory now keeps only the new part of the request input: the trailing user messages, plus any tool outcomes the client sends for calls the stored conversation still has pending (results, errors, denials, and approval answers). This works whether the outcome arrives on its own or together with the next user message. An empty thread is still seeded with the full input, with assistant provider metadata stripped. When an input message has the same ID as a stored message, the stored message remains the base so its reasoning, provider metadata, ordering, and timestamp are retained. Client tool outcomes only fill in calls that are still pending, so an echo can't overwrite a stored tool result. This prevents lossy client echoes from orphaning OpenAI reasoning items, re-persisting user messages with client timestamps, or duplicating assistant text during history replay. Observational Memory uses the same stored-base layering behavior. This is a behavior change: on an existing thread, any input message before the last assistant message that isn't stored is removed. That includes few-shot examples and caller-assembled message arrays, not only assistant messages sent to modify the thread or user messages re-sent with a changed `createdAt` to reorder it. Use `memory.saveMessages` or the memory store's `updateMessages` to change stored history. To opt out, set the new `retainFullInput` memory option, per call on `memory.options` or agent-wide in the memory constructor options. The request input is then processed exactly as supplied, history still loads, and every input message that isn't already stored is saved to the thread. The `useAgent` structured output path uses it so its replayed request keeps the parent's message prefix. Fixes #24052. - Added `createClassifierScorer()` for using a configured `Classifier` as a typed Mastra scorer. Select one classifier question, get a score between 0 and 1, and retain the classifier evidence in the scorer result. ([#24792](https://github.com/mastra-ai/mastra/pull/24792)) ```typescript import { Classifier } from '@mastra/core/classifier'; import { createClassifierScorer } from '@mastra/core/evals'; import { getAssistantMessageFromRunOutput } from '@mastra/evals/scorers/utils'; const classifier = new Classifier({ id: 'response-quality', model, questions: { quality: { type: 'score', criteria: ['Incorrect', 'Partially correct', 'Correct'], }, }, }); const scorer = createClassifierScorer({ id: 'response-quality-scorer', classifier, question: 'quality', type: 'agent', state: ({ run }) => ({ output: getAssistantMessageFromRunOutput(run.output) ?? '' }), }); ``` - Added tag predicates to advanced trace queries. Trace predicates accept `includes` and `notIncludes` on the `tags` field, and `exists` / `notExists` on `tags` mean "at least one tag" / "no tags". Missing and empty tag lists are treated alike. `tags` also supports value discovery. ([#24554](https://github.com/mastra-ai/mastra/pull/24554)) **Example** ```ts { op: 'includes', path: 'tags', value: 'manual-review' } ``` - Expanded `cancelQueuedMessages({ signalIds })` to cancel pending input across all Agents sharing the runtime and memory thread. Added `clearPendingSignals` to thread abort options. ([#23942](https://github.com/mastra-ai/mastra/pull/23942)) **Changed behavior** - Signal-ID cancellation previously matched only the calling Agent's queued messages. It now also covers other Agents' messages, pre-run signals, and signals pending in an active run. - The `queueOwnerId` selector still cancels only the calling Agent's queued messages in that owner group. - Cancellation remains effective during lease handoffs. Clear-on-abort prevents queued input from returning after a preparation failure. - Running Agents listen for remote signals, cancellation, and abort requests without requiring `subscribeToThread()`. Disconnecting the last thread observer no longer disables these controls. - Remote thread aborts also stop durable runs through their existing abort transport, including when clearing pending input without an observer. - Remote input already in transit survives run handoffs. Observer-only signal copies don't keep execution listeners alive or become new input when a thread is reused. - Input queued on a claimed thread remains remotely cancellable after the claim is released, even without an open thread subscription. - Cancellation results exclude observer history and report only pending work removed locally. Delayed enqueue retries can't restore cancelled input while the execution listener remains active. ```typescript const thread = { resourceId: 'user-123', threadId: 'thread-abc' }; // Selected pending input across Agents sharing the thread. agent.cancelQueuedMessages({ ...thread, signalIds: ['signal-123'] }); // Existing Agent-scoped owner-group behavior. agent.cancelQueuedMessages({ ...thread, queueOwnerId: 'session-123' }); agent.abortThreadStream({ ...thread, clearPendingSignals: true }); // Existing behavior: abort without clearing pending input. agent.abortThreadStream(thread); ``` Selected-ID cancellation publishes all requested IDs through PubSub, even when none are pending locally, so other processes subscribed to the thread can remove matching pending input. Propagation is asynchronous and best-effort. The result reports only local cancellations, without remote acknowledgements. Clear-on-abort forwards the clear flag to the active owner, but doesn't clear every process's queues. Neither operation cancels `continueWithMessages()` continuations or undoes persisted effects. ### Patch Changes - Fixed in-memory dataset configuration reads to return undefined for unset or cleared tags, target type, target IDs, and scorer IDs, matching LibSQL. Serialized responses omit these properties instead of returning null. Consumers should use nullish checks rather than require explicit null properties. Empty arrays remain distinct from cleared settings. ([#23937](https://github.com/mastra-ai/mastra/pull/23937)) - Fixed per-request reply topics leaking streams on persistent pub/sub backends. The agent runtime's cross-process flows (thread owner discovery, peer discovery, and idle-signal acceptance) create a unique reply topic per request; on backends like Redis Streams, subscribing creates a real stream key that previously outlived the request forever. Reply topics are now deleted via `clearTopic` as soon as their request settles, and a request whose timeout fires before its subscribe finishes no longer publishes at all. Requests also carry the caller's absolute deadline: a responder that receives one late (backlog replay on a fresh subscription, redelivery) drops it instead of replying into the released reply topic — or, for idle signals, starting a run the caller already reported as timed out. A clock-skew grace keeps responders from dropping live requests when process clocks drift. No change for the in-memory pub/sub, where `clearTopic` is a no-op. ([#24580](https://github.com/mastra-ai/mastra/pull/24580)) Full cleanup on Redis Streams requires the matching `@mastra/redis-streams` release. With an older one, `unsubscribe()` does nothing while a subscribe is still in flight, and that late subscribe recreates the stream key after `clearTopic` deletes it. - Fixed Bedrock-hosted OpenAI models sending unsupported temperature and top-p settings. Fixes #24815. ([#24886](https://github.com/mastra-ai/mastra/pull/24886)) - Added public Zod request and response schemas and types for the upcoming `aggregateTraces()` observability operation: `traceAggregateRequestSchema`, `traceAggregateResponseSchema`, and `parseTraceAggregateRequest()`. Selection (`timeRange`, `where`) reuses the existing trace-query schemas, so aggregate and list queries validate the same population. No runtime operation ships yet; the storage method and HTTP route follow in later releases. ([#24834](https://github.com/mastra-ai/mastra/pull/24834)) ```ts import { parseTraceAggregateRequest } from '@mastra/core/storage'; const request = parseTraceAggregateRequest({ timeRange: { from: '2026-09-01T00:00:00Z', to: '2026-09-08T00:00:00Z' }, groupBy: ['entityName'], measures: ['count', 'duration.p95'], }); // request.limit === 100, request.orderBy === { field: 'count', direction: 'desc' } ``` ## 1.70.0-alpha.0 ### Minor Changes - Added typed descriptions for processor span payloads and pipeline attributes. Processor spans now record their exact pipeline phase, so consumers can narrow supported payloads without guessing their shape. ([#24672](https://github.com/mastra-ai/mastra/pull/24672)) ```ts import { describeSpanOutput } from '@mastra/core/observability'; const output = describeSpanOutput(span); if (output?.type === 'processor' && output.value.phase === 'outputStream') { output.value.data.totalChunks; // number } ``` Added `describeProcessorPipeline` for executor, pipeline position, hook duration, mutations, and tripwire details. Unknown attributes remain separate from the known fields, so no value is rendered twice. Spans recorded before the phase existed keep their untyped shape and fall back to JSON. Fixed missing message-list mutation logs in workflow processor executions. - Add an optional `title` to `createTool()` so a tool call can carry a human-readable display name. Closes #20249. ([#24117](https://github.com/mastra-ai/mastra/pull/24117)) The title is never sent to the model. It is stamped on the `tool-call-input-streaming-start` and `tool-call` stream chunks and persisted on the stored tool-invocation part, so a chat UI can label the call after a reload without a client-side name catalog. ```ts const weatherTool = createTool({ id: 'get_weather_by_coordinates', title: 'Weather Lookup', description: 'Fetches the current weather for a latitude/longitude pair', inputSchema: z.object({ lat: z.number(), lon: z.number() }), execute: async ({ lat, lon }) => fetchWeather(lat, lon), }); ``` - Added a trusted tenant scope to advanced trace queries. Hosts pass `{ organizationId, resourceId? }` to `planTraceQuery`, `planThreadQuery`, and the discovery planners; the scope is carried on the trusted plan, ANDed into every root and related-signal scan by stores, and bound into keyset cursors so a cursor reused under another scope fails with `TRACE_QUERY_CURSOR_CONFLICT` before storage runs. Callers still can't name `organizationId` or `projectId` in predicates. No scope means no filter, so self-hosted behavior is unchanged. ([#24566](https://github.com/mastra-ai/mastra/pull/24566)) **Example** ```ts const plan = planTraceQuery(parseTraceQueryRequest(request), { scope: { organizationId: 'org_123', resourceId: 'project_456' }, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`bfde500`](https://github.com/mastra-ai/mastra/commit/bfde5009d1d9bdbce241132b3df9e638ad805fab)) - Durable agent turns now use far fewer Inngest steps, including when observability is not configured. A 20-step agent turn previously used 133–158 Inngest steps. ([#24782](https://github.com/mastra-ai/mastra/pull/24782)) ## 1.69.0 ### Minor Changes - Added classifier registration on `Mastra`. ([#24738](https://github.com/mastra-ai/mastra/pull/24738)) Classifiers can now be passed to `new Mastra({ classifiers })` and accessed with `getClassifier`, `getClassifierById`, `listClassifiers`, `addClassifier`, and `removeClassifier`. Registered classifiers evaluated without an active trace start a root `CLASSIFIER_EVALUATION` span through the `Mastra` instance's configured observability provider. ```ts const mastra = new Mastra({ classifiers: { router } }); const classifier = mastra.getClassifier('router'); ``` - Added configured classifiers as typed workflow steps with fluent and dynamic graph support. Classifier steps expose complete typed answers and token usage for existing branch and conditional control flow. ([#24747](https://github.com/mastra-ai/mastra/pull/24747)) ```ts workflow .map({ message: { initData: true, path: 'message' } }) .classifier(router) .branch([ [async ({ inputData }) => inputData.answers.route.choice === 'billing', billingStep], [async ({ inputData }) => inputData.answers.route.choice === 'support', supportStep], ]); ``` - Deprecated the `group` option on the trace-query request contract. Grouping remains functional until the next major release; use the `queryThreads` thread-query contract for new code (exposed as `queryTraceThreads()` in `@mastra/client-js`). ([#23790](https://github.com/mastra-ai/mastra/pull/23790)) **Before:** ```ts import type { TraceQueryRequest } from '@mastra/core/storage'; const request: TraceQueryRequest = { timeRange, group: { by: ['threadId'] } }; ``` **After:** ```ts import type { QueryThreadsInput } from '@mastra/core/storage'; const input: QueryThreadsInput = { traces: { timeRange } }; ``` - Added `context.background.adopt()` so tools can return an acknowledgement while native background tasks track their existing operation through completion and cancellation. ([#24418](https://github.com/mastra-ai/mastra/pull/24418)) Previously, background tools had to keep `execute()` pending: ```ts execute: async (input, context) => { const operation = startResearch(input, context.abortSignal); return await operation.finished; }; ``` Tools can now adopt their operation during native background execution: ```ts execute: async (input, context) => { const operation = startResearch(input, context.abortSignal); if (context.background) { context.background.adopt({ completion: operation.finished, cancel: reason => operation.cancel(reason), }); return { answer: 'Research started' }; } return await operation.finished; }; ``` `startResearch` represents the tool's operation API. Its `finished` promise must resolve with the final tool result after cleanup, or reject on failure. Adopt once, before `execute()` returns. The handle stays in memory and cannot resume after a process restart. - Added `rootSpanName` to `tracingOptions` so each agent or workflow run can set its own root span name. Runs of the same workflow no longer all show up as `workflow run: 'my-workflow'` in trace lists. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```ts await run.start({ inputData: { skillId: 'typescript' }, tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, }); ``` The name applies to the root span only. Child spans keep their default names, and entity filters still match on the workflow or agent id. Related: https://github.com/mastra-ai/mastra/issues/24518 - Added `ClassifierProcessor` for applying typed classifier policies to agent input, output, and streaming content. ([#24768](https://github.com/mastra-ai/mastra/pull/24768)) ```typescript import { Agent } from '@mastra/core/agent'; import { Classifier } from '@mastra/core/classifier'; import { ClassifierProcessor } from '@mastra/core/processors'; const safety = new Classifier({ id: 'safety', model, questions: { unsafe: { type: 'boolean', criteria: { true: 'Unsafe', false: 'Safe' } }, }, }); const agent = new Agent({ id: 'support-agent', name: 'Support agent', instructions: 'Answer support questions.', model: 'openai/gpt-5-mini', inputProcessors: [ new ClassifierProcessor({ classifier: safety, onResult: (answers, { abort }) => { if (answers.unsafe.probability > 0.8) abort('Rejected by safety policy'); }, }), ], }); ``` - Added a typed Classifier primitive for fixed-option evaluation with AI SDK evaluation models. ([#24458](https://github.com/mastra-ai/mastra/pull/24458)) ```ts import { Classifier } from '@mastra/core/classifier'; const classifier = new Classifier({ id: 'router', model }); const result = await classifier.evaluate({ state: request, questions: { route: { type: 'choice', criteria: { support: 'Support request', sales: 'Sales request' }, }, }, }); ``` - Added `validateSkillContent()` and exported `validateSkillMetadata()` from `@mastra/core/skills`, so apps can validate a `SKILL.md` before saving it using the same rules applied at load time. ([#24766](https://github.com/mastra-ai/mastra/pull/24766)) ```typescript import { validateSkillContent } from '@mastra/core/skills'; const result = validateSkillContent({ content: skillMarkdown, directoryName: 'my-skill' }); if (!result.valid) console.error(result.errors); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7fefefd`](https://github.com/mastra-ai/mastra/commit/7fefefdcb91e15f8bf60b5b2148ef27cf1352faf)) - Fixed deferred aborts for paused tool calls and tool approvals. They no longer stop a new run started after switching threads or running `/new`. ([#24743](https://github.com/mastra-ai/mastra/pull/24743)) - `ClassifierProcessor` now fails closed by default. When the classifier call fails, the request is aborted instead of letting unchecked content through. Pass `errorStrategy: 'warn'` to keep the previous fail-open behavior: ([#24793](https://github.com/mastra-ai/mastra/pull/24793)) ```ts new ClassifierProcessor({ classifier, onResult, errorStrategy: 'warn' }); ``` - Honor `tracingOptions.rootSpanName` when creating a root span. The caller-supplied name replaces the default `agent run: ''` or `workflow run: ''` name on the root span only. Works for the default and Inngest workflow engines because the name is applied when the span starts, before any durable snapshot is taken. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```ts const span = observability.startSpan({ type: SpanType.WORKFLOW_RUN, name: "workflow run: 'skill-analyze'", tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, }); span.name; // "skill-analyze: typescript" ``` Related: https://github.com/mastra-ai/mastra/issues/24518 - Tool calls whose approval policy is a function now run in parallel when that policy returns `false` for the actual call. This applies to the default `toolCallConcurrency` strategy and to `strategy: 'called'`. Previously, any function policy forced sequential execution. This included the policy that `MCPClient` attaches when `requireToolApproval` is a function. Each call's policy is evaluated once, and that verdict is reused when the tool runs. ([#24763](https://github.com/mastra-ai/mastra/pull/24763)) - Fixed a crash where a background process started with the `execute_command` workspace tool could terminate the host process. If the exit callback threw or the process could not be observed after the PID was returned, the failure escaped as an unhandled promise rejection. These failures are now caught and logged instead. ([#24638](https://github.com/mastra-ai/mastra/pull/24638)) - Fixed internal spans being exported after they are rebuilt with `rebuildSpan()` when `includeInternalSpans` is disabled. Spans created with `tracingPolicy.internal` now keep their internal status through `exportSpan()` and `rebuildSpan()`, so they stay out of exporters by default and remain included when `includeInternalSpans` is enabled. ([#24742](https://github.com/mastra-ai/mastra/pull/24742)) - Fixed `JSON.stringify(requestContext)` throwing `TypeError: Do not know how to serialize a BigInt` when a BigInt was stored directly in `RequestContext`. Such values are now left out of `toJSON()` output, matching how nested BigInts were already handled. BigInts are still included when `BigInt.prototype.toJSON` returns a JSON-serializable value, and `requestContext.get()` still returns the original value. ([#24751](https://github.com/mastra-ai/mastra/pull/24751)) - Fixed `runEvals` saving `mastra__authToken` and values that cannot be stored in score rows. Saved scores keep string, number and boolean request context values, and nested object values use dotted keys. ([#24753](https://github.com/mastra-ai/mastra/pull/24753)) - Added `rootSpanName` to the generated `tracingOptions` request types so per-run root span names can be sent from the client. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```ts const run = await client.getWorkflow('skillAnalyze').createRun(); await run.startAsync({ inputData: { skillId: 'typescript' }, tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, }); ``` Related: https://github.com/mastra-ai/mastra/issues/24518 - Fixed chat channel messages (Slack, Discord, etc.) silently disappearing when the agent failed during setup. If workspace, instructions, tools, or model resolution throws before the run starts, the error is now posted back to the chat thread instead of being dropped with no reply. ([#24300](https://github.com/mastra-ai/mastra/pull/24300)) - Fix an event-loop hang when a run starts immediately after a persisted idle signal. The synthetic run that rebroadcasts the signal resolved its completion promise before its deferred cleanup ran, so a same-thread run waiting on it re-awaited an already-resolved promise in an unbounded microtask loop — pinning a CPU core and stopping timers and HTTP process-wide. The waiter now yields to the timer queue when the same run is still active after its completion promise settles. ([#24613](https://github.com/mastra-ai/mastra/pull/24613)) - Accept `rootSpanName` in `tracingOptions` on agent and workflow HTTP routes so clients can set a per-run root span name. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```http POST /api/workflows/skillAnalyze/start-async { "inputData": { "skillId": "typescript" }, "tracingOptions": { "rootSpanName": "skill-analyze: typescript" } } ``` Related: https://github.com/mastra-ai/mastra/issues/24518 ## 1.69.0-alpha.4 ### Minor Changes - Added configured classifiers as typed workflow steps with fluent and dynamic graph support. Classifier steps expose complete typed answers and token usage for existing branch and conditional control flow. ([#24747](https://github.com/mastra-ai/mastra/pull/24747)) ```ts workflow .map({ message: { initData: true, path: 'message' } }) .classifier(router) .branch([ [async ({ inputData }) => inputData.answers.route.choice === 'billing', billingStep], [async ({ inputData }) => inputData.answers.route.choice === 'support', supportStep], ]); ``` - Deprecated the `group` option on the trace-query request contract. Grouping remains functional until the next major release; use the `queryThreads` thread-query contract for new code (exposed as `queryTraceThreads()` in `@mastra/client-js`). ([#23790](https://github.com/mastra-ai/mastra/pull/23790)) **Before:** ```ts import type { TraceQueryRequest } from '@mastra/core/storage'; const request: TraceQueryRequest = { timeRange, group: { by: ['threadId'] } }; ``` **After:** ```ts import type { QueryThreadsInput } from '@mastra/core/storage'; const input: QueryThreadsInput = { traces: { timeRange } }; ``` - Added `rootSpanName` to `tracingOptions` so each agent or workflow run can set its own root span name. Runs of the same workflow no longer all show up as `workflow run: 'my-workflow'` in trace lists. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```ts await run.start({ inputData: { skillId: 'typescript' }, tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, }); ``` The name applies to the root span only. Child spans keep their default names, and entity filters still match on the workflow or agent id. Related: https://github.com/mastra-ai/mastra/issues/24518 - Added `ClassifierProcessor` for applying typed classifier policies to agent input, output, and streaming content. ([#24768](https://github.com/mastra-ai/mastra/pull/24768)) ```typescript import { Agent } from '@mastra/core/agent'; import { Classifier } from '@mastra/core/classifier'; import { ClassifierProcessor } from '@mastra/core/processors'; const safety = new Classifier({ id: 'safety', model, questions: { unsafe: { type: 'boolean', criteria: { true: 'Unsafe', false: 'Safe' } }, }, }); const agent = new Agent({ id: 'support-agent', name: 'Support agent', instructions: 'Answer support questions.', model: 'openai/gpt-5-mini', inputProcessors: [ new ClassifierProcessor({ classifier: safety, onResult: (answers, { abort }) => { if (answers.unsafe.probability > 0.8) abort('Rejected by safety policy'); }, }), ], }); ``` ### Patch Changes - `ClassifierProcessor` now fails closed by default. When the classifier call fails, the request is aborted instead of letting unchecked content through. Pass `errorStrategy: 'warn'` to keep the previous fail-open behavior: ([#24793](https://github.com/mastra-ai/mastra/pull/24793)) ```ts new ClassifierProcessor({ classifier, onResult, errorStrategy: 'warn' }); ``` - Honor `tracingOptions.rootSpanName` when creating a root span. The caller-supplied name replaces the default `agent run: ''` or `workflow run: ''` name on the root span only. Works for the default and Inngest workflow engines because the name is applied when the span starts, before any durable snapshot is taken. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```ts const span = observability.startSpan({ type: SpanType.WORKFLOW_RUN, name: "workflow run: 'skill-analyze'", tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, }); span.name; // "skill-analyze: typescript" ``` Related: https://github.com/mastra-ai/mastra/issues/24518 - Tool calls whose approval policy is a function now run in parallel when that policy returns `false` for the actual call. This applies to the default `toolCallConcurrency` strategy and to `strategy: 'called'`. Previously, any function policy forced sequential execution. This included the policy that `MCPClient` attaches when `requireToolApproval` is a function. Each call's policy is evaluated once, and that verdict is reused when the tool runs. ([#24763](https://github.com/mastra-ai/mastra/pull/24763)) - Fixed internal spans being exported after they are rebuilt with `rebuildSpan()` when `includeInternalSpans` is disabled. Spans created with `tracingPolicy.internal` now keep their internal status through `exportSpan()` and `rebuildSpan()`, so they stay out of exporters by default and remain included when `includeInternalSpans` is enabled. ([#24742](https://github.com/mastra-ai/mastra/pull/24742)) - Added `rootSpanName` to the generated `tracingOptions` request types so per-run root span names can be sent from the client. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```ts const run = await client.getWorkflow('skillAnalyze').createRun(); await run.startAsync({ inputData: { skillId: 'typescript' }, tracingOptions: { rootSpanName: 'skill-analyze: typescript' }, }); ``` Related: https://github.com/mastra-ai/mastra/issues/24518 - Fix an event-loop hang when a run starts immediately after a persisted idle signal. The synthetic run that rebroadcasts the signal resolved its completion promise before its deferred cleanup ran, so a same-thread run waiting on it re-awaited an already-resolved promise in an unbounded microtask loop — pinning a CPU core and stopping timers and HTTP process-wide. The waiter now yields to the timer queue when the same run is still active after its completion promise settles. ([#24613](https://github.com/mastra-ai/mastra/pull/24613)) - Accept `rootSpanName` in `tracingOptions` on agent and workflow HTTP routes so clients can set a per-run root span name. ([#24564](https://github.com/mastra-ai/mastra/pull/24564)) ```http POST /api/workflows/skillAnalyze/start-async { "inputData": { "skillId": "typescript" }, "tracingOptions": { "rootSpanName": "skill-analyze: typescript" } } ``` Related: https://github.com/mastra-ai/mastra/issues/24518 ## 1.69.0-alpha.3 ### Minor Changes - Added classifier registration on `Mastra`. ([#24738](https://github.com/mastra-ai/mastra/pull/24738)) Classifiers can now be passed to `new Mastra({ classifiers })` and accessed with `getClassifier`, `getClassifierById`, `listClassifiers`, `addClassifier`, and `removeClassifier`. Registered classifiers evaluated without an active trace start a root `CLASSIFIER_EVALUATION` span through the `Mastra` instance's configured observability provider. ```ts const mastra = new Mastra({ classifiers: { router } }); const classifier = mastra.getClassifier('router'); ``` - Added `context.background.adopt()` so tools can return an acknowledgement while native background tasks track their existing operation through completion and cancellation. ([#24418](https://github.com/mastra-ai/mastra/pull/24418)) Previously, background tools had to keep `execute()` pending: ```ts execute: async (input, context) => { const operation = startResearch(input, context.abortSignal); return await operation.finished; }; ``` Tools can now adopt their operation during native background execution: ```ts execute: async (input, context) => { const operation = startResearch(input, context.abortSignal); if (context.background) { context.background.adopt({ completion: operation.finished, cancel: reason => operation.cancel(reason), }); return { answer: 'Research started' }; } return await operation.finished; }; ``` `startResearch` represents the tool's operation API. Its `finished` promise must resolve with the final tool result after cleanup, or reject on failure. Adopt once, before `execute()` returns. The handle stays in memory and cannot resume after a process restart. - Added `validateSkillContent()` and exported `validateSkillMetadata()` from `@mastra/core/skills`, so apps can validate a `SKILL.md` before saving it using the same rules applied at load time. ([#24766](https://github.com/mastra-ai/mastra/pull/24766)) ```typescript import { validateSkillContent } from '@mastra/core/skills'; const result = validateSkillContent({ content: skillMarkdown, directoryName: 'my-skill' }); if (!result.valid) console.error(result.errors); ``` ### Patch Changes - Fixed deferred aborts for paused tool calls and tool approvals. They no longer stop a new run started after switching threads or running `/new`. ([#24743](https://github.com/mastra-ai/mastra/pull/24743)) - Fixed `JSON.stringify(requestContext)` throwing `TypeError: Do not know how to serialize a BigInt` when a BigInt was stored directly in `RequestContext`. Such values are now left out of `toJSON()` output, matching how nested BigInts were already handled. BigInts are still included when `BigInt.prototype.toJSON` returns a JSON-serializable value, and `requestContext.get()` still returns the original value. ([#24751](https://github.com/mastra-ai/mastra/pull/24751)) - Fixed `runEvals` saving `mastra__authToken` and values that cannot be stored in score rows. Saved scores keep string, number and boolean request context values, and nested object values use dotted keys. ([#24753](https://github.com/mastra-ai/mastra/pull/24753)) ## 1.69.0-alpha.2 ### Minor Changes - Added a typed Classifier primitive for fixed-option evaluation with AI SDK evaluation models. ([#24458](https://github.com/mastra-ai/mastra/pull/24458)) ```ts import { Classifier } from '@mastra/core/classifier'; const classifier = new Classifier({ id: 'router', model }); const result = await classifier.evaluate({ state: request, questions: { route: { type: 'choice', criteria: { support: 'Support request', sales: 'Sales request' }, }, }, }); ``` ### Patch Changes - Fixed a crash where a background process started with the `execute_command` workspace tool could terminate the host process. If the exit callback threw or the process could not be observed after the PID was returned, the failure escaped as an unhandled promise rejection. These failures are now caught and logged instead. ([#24638](https://github.com/mastra-ai/mastra/pull/24638)) ## 1.69.0-alpha.1 ## 1.69.0-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7fefefd`](https://github.com/mastra-ai/mastra/commit/7fefefdcb91e15f8bf60b5b2148ef27cf1352faf)) - Fixed chat channel messages (Slack, Discord, etc.) silently disappearing when the agent failed during setup. If workspace, instructions, tools, or model resolution throws before the run starts, the error is now posted back to the chat thread instead of being dropped with no reply. ([#24300](https://github.com/mastra-ai/mastra/pull/24300)) ## 1.68.0 ### 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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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); ``` - 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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/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](https://github.com/mastra-ai/mastra/pull/24451)) - Durable agents no longer persist `running` checkpoints by default, and `createDurableAgent()` accepts a new `shouldPersistSnapshot` option to control snapshot persistence ([#23915](https://github.com/mastra-ai/mastra/issues/23915)). ([#23978](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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 }, }, }); ``` - Added canonical trace-query field descriptors and bounded discovery contracts for queryable fields and values. ([#24057](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24008)) - Update provider registry and model documentation with latest models and providers ([`1e68460`](https://github.com/mastra-ai/mastra/commit/1e68460205d0061c6dbc7a7e7a50950236af774b)) - Improved runtime portability by using Web Crypto for cryptographic operations that do not require changes to existing synchronous APIs. ([#24369](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24048)) - 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](https://github.com/mastra-ai/mastra/pull/24402)) - Fixed structured output fallback instructions to include the requested JSON schema. ([#24457](https://github.com/mastra-ai/mastra/pull/24457)) - Ignore malformed numeric `Retry-After` values instead of parsing them as years and delaying retries up to the configured cap. ([#23997](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24048)) - **`createCodingAgent` now repairs recoverable bad requests instead of replaying them.** ([#24469](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24027)) - 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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24229)) - Fixed ModelRouter URL capability discovery failures being hidden and permanently cached. See #24436. ([#24454](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24083)) - Fixed queued follow-up messages being saved without an answer after the active run is aborted. ([#23926](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/23988)) - Fixed completed workflow runs remaining in memory after resume. ([#24267](https://github.com/mastra-ai/mastra/pull/24267)) - **Breaking change** ([#22978](https://github.com/mastra-ai/mastra/pull/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); ``` - CommonJS consumers can now use core token counting, slug generation, and workspace operations without ESM loading errors. ([#24272](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/23111). ([#24366](https://github.com/mastra-ai/mastra/pull/24366)) - Surface filesystem read failures in the workspace `grep` tool instead of silently reporting them as a complete "0 matches" search. ([#24053](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24055)) - Fixed structured output fallback warnings to use the agent logger and aligned the loop fallback logger with error-level defaults. ([#24455](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24510)) - Fixed idle wake signals losing the caller's request context and misreporting their outcome when the thread had a claimed owner. ([#24347](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24486)) - Fixed a WebSocket denial-of-service advisory by updating ws to 8.21.3. ([#24027](https://github.com/mastra-ai/mastra/pull/24027)) - 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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/23791)) ```ts await session.thread.rename({ title: 'Initial title', pin: false }); ``` Fixes #22421 - 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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24027)) - 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](https://github.com/mastra-ai/mastra/pull/23685)) - Fixed shared tools exposing `_background` to agents that do not support background execution. ([#23120](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24189)) - Fixed invalid model timeout settings being silently ignored. ([#24463](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24313)) - Fixed subscribed thread streams missing signals that arrive while an aborted run is being cleaned up. ([#23696](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/23903). ([#23981](https://github.com/mastra-ai/mastra/pull/23981)) - Fixed the `skill_read` tool corrupting binary skill files. A PNG or PDF is now reported as `Binary file: ()` 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](https://github.com/mastra-ai/mastra/pull/24101)) - Fixed skill discovery for `Workspace` instances that use a dynamic `filesystem` resolver. ([#24317](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24081)) - Removed the redundant direct Ajv dependency. Schema compatibility bundles its validator and standalone types without requiring a separate Ajv installation. ([#23998](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/23914)) - **Fixed commands that read stdin hanging until timeout** ([#24336](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24453)) - Fixed model router cache initialization so Cloudflare Workers can load bundles before request handling begins. ([#24454](https://github.com/mastra-ai/mastra/pull/24454)) - Sanitize delegated agent lifecycle payloads in thread-stream broadcasts to prevent conversation data from compounding across delegation levels. ([#24399](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24462)) - Throw a clear isolation error when LocalSandbox is configured to use macOS Seatbelt on Windows. ([#24273](https://github.com/mastra-ai/mastra/pull/24273)) - Fixed durable agents on cross-process engines (like Inngest) dropping `requestContext` values written by input processors. ([#24133](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/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](https://github.com/mastra-ai/mastra/pull/23962)) - Fixed `TokenLimiterProcessor` truncation (`strategy: 'truncate'`) splitting UTF-16 surrogate pairs. ([#24096](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/23420)) - Fixed unrecorded conditional arms appearing successful when time-travelling past them. Preserve explicit replacement output for recorded failed arms. ([#24184](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/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' }); ``` - 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](https://github.com/mastra-ai/mastra/pull/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` 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](https://github.com/mastra-ai/mastra/pull/24335)) - Updated dependencies [[`5085475`](https://github.com/mastra-ai/mastra/commit/5085475c0da226e618eb3ee2676d347788c3fb00), [`fef227a`](https://github.com/mastra-ai/mastra/commit/fef227a8b7cb0ad7f68087e26d1fd2051054a61a), [`6c781fd`](https://github.com/mastra-ai/mastra/commit/6c781fda62eb0b0b74d016f188ed0b2db5cfdceb)]: - @mastra/schema-compat@1.3.11 ## 1.68.0-alpha.11 ### Minor Changes - 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](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/pull/24150)) ```ts await tool.execute(args, { tracingContext: { currentSpan: requestSpan }, skipToolSpan: true, }); ``` See https://github.com/mastra-ai/mastra/issues/23921 ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/24313)) - 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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24510)) - 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](https://github.com/mastra-ai/mastra/pull/24313)) ## 1.68.0-alpha.10 ### Patch Changes - Improved runtime portability by using Web Crypto for cryptographic operations that do not require changes to existing synchronous APIs. ([#24369](https://github.com/mastra-ai/mastra/pull/24369)) ## 1.68.0-alpha.9 ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/24402)) - Fixed structured output to reject responses truncated by token limits or content filters. ([#24464](https://github.com/mastra-ai/mastra/pull/24464)) - 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](https://github.com/mastra-ai/mastra/pull/24486)) - 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](https://github.com/mastra-ai/mastra/pull/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. - 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](https://github.com/mastra-ai/mastra/pull/24376)) ```ts await agent.listSuspendedRuns({ threadId: 'thread-123' }); ``` ## 1.68.0-alpha.8 ### Minor Changes - Added sandbox start options forwarding so providers can support cancellable startup operations. ([#24451](https://github.com/mastra-ai/mastra/pull/24451)) ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/24467)) - Fixed structured output fallback instructions to include the requested JSON schema. ([#24457](https://github.com/mastra-ai/mastra/pull/24457)) - **`createCodingAgent` now repairs recoverable bad requests instead of replaying them.** ([#24469](https://github.com/mastra-ai/mastra/pull/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 invalid model timeout settings being silently ignored. ([#24463](https://github.com/mastra-ai/mastra/pull/24463)) - Fixed fallback models restarting from the primary model between agent tool-call steps. ([#23725](https://github.com/mastra-ai/mastra/pull/23725)) - 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](https://github.com/mastra-ai/mastra/pull/24462)) - Fixed structured output failures to preserve validation errors and raw invalid model output for diagnostics. ([#24465](https://github.com/mastra-ai/mastra/pull/24465)) ## 1.68.0-alpha.7 ### Minor Changes - 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](https://github.com/mastra-ai/mastra/pull/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 `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](https://github.com/mastra-ai/mastra/pull/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 } ``` ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/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 ModelRouter URL capability discovery failures being hidden and permanently cached. See #24436. ([#24454](https://github.com/mastra-ai/mastra/pull/24454)) - Fixed structured output fallback warnings to use the agent logger and aligned the loop fallback logger with error-level defaults. ([#24455](https://github.com/mastra-ai/mastra/pull/24455)) - Fixed idle wake signals losing the caller's request context and misreporting their outcome when the thread had a claimed owner. ([#24347](https://github.com/mastra-ai/mastra/pull/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. - 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](https://github.com/mastra-ai/mastra/pull/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 thread aborts so callers can require the intended run to still be active before it is stopped. ([#24452](https://github.com/mastra-ai/mastra/pull/24452)) - **Fixed commands that read stdin hanging until timeout** ([#24336](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/24453)) - Fixed model router cache initialization so Cloudflare Workers can load bundles before request handling begins. ([#24454](https://github.com/mastra-ai/mastra/pull/24454)) - Fixed durable agent runs so thread title generation no longer delays completion. ([#24335](https://github.com/mastra-ai/mastra/pull/24335)) ## 1.68.0-alpha.6 ### Minor Changes - Added `generateTitle.emitEvent` so HTTP and stream clients receive the generated thread title without polling. ([#24247](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/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. ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/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. - Mastra's internal modelOutput metadata is no longer leaked to providers in the outgoing prompt. - 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](https://github.com/mastra-ai/mastra/issues/23111). ([#24366](https://github.com/mastra-ai/mastra/pull/24366)) - 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](https://github.com/mastra-ai/mastra/pull/24266)) - 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](https://github.com/mastra-ai/mastra/pull/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. ## 1.68.0-alpha.5 ### Minor Changes - Added list-compatible page pagination to advanced trace queries while preserving keyset cursors. ([#24061](https://github.com/mastra-ai/mastra/pull/24061)) ```ts const result = await client.queryTraces({ timeRange, pagination: { page: 0, perPage: 25 }, }); ``` ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/24293)) - 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](https://github.com/mastra-ai/mastra/pull/24258)) - Fixed durable agent approval resumes so live assistant events and token usage are recorded once. (#23116) ([#24265](https://github.com/mastra-ai/mastra/pull/24265)) - 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](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/pull/24306)) - Fixed skill discovery for `Workspace` instances that use a dynamic `filesystem` resolver. ([#24317](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/pull/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. ## 1.68.0-alpha.4 ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/24229)) - 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](https://github.com/mastra-ai/mastra/pull/23988)) - Fixed completed workflow runs remaining in memory after resume. ([#24267](https://github.com/mastra-ai/mastra/pull/24267)) - CommonJS consumers can now use core token counting, slug generation, and workspace operations without ESM loading errors. ([#24272](https://github.com/mastra-ai/mastra/pull/24272)) - Throw a clear isolation error when LocalSandbox is configured to use macOS Seatbelt on Windows. ([#24273](https://github.com/mastra-ai/mastra/pull/24273)) - Fixed unrecorded conditional arms appearing successful when time-travelling past them. Preserve explicit replacement output for recorded failed arms. ([#24184](https://github.com/mastra-ai/mastra/pull/24184)) ## 1.68.0-alpha.3 ### Minor Changes - Added consistent, resumable `prune()` execution for storage adapters, including bounded work, pause intervals, and cancellation. ([#23466](https://github.com/mastra-ai/mastra/pull/23466)) ```ts const controller = new AbortController(); await storage.prune({ maxBatches: 10, maxRows: 10_000, pauseMs: 25, signal: controller.signal, }); ``` - Added support for refreshing advertised agent peer details without reclaiming thread ownership. ([#23696](https://github.com/mastra-ai/mastra/pull/23696)) ```ts const updated = agent.updateThreadPeerAdvertisement({ resourceId: 'resource-1', threadId: 'thread-1', peer: { title: 'Updated thread title', metadata: { mode: 'review' } }, }); ``` - 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](https://github.com/mastra-ai/mastra/pull/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); ``` - 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](https://github.com/mastra-ai/mastra/pull/23902)) ```ts import { parseDatasetSnapshot } from '@mastra/core/datasets'; const snapshot = parseDatasetSnapshot(artifactJson, { maxBytes: 8 * 1024 * 1024 }); ``` - 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](https://github.com/mastra-ai/mastra/pull/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 a stable resource-limit error for bounded trace-query field and value discovery. ([#24169](https://github.com/mastra-ai/mastra/pull/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 - 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](https://github.com/mastra-ai/mastra/pull/24171)) - 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](https://github.com/mastra-ai/mastra/pull/23685)) - 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](https://github.com/mastra-ai/mastra/pull/24189)) - Fixed subscribed thread streams missing signals that arrive while an aborted run is being cleaned up. ([#23696](https://github.com/mastra-ai/mastra/pull/23696)) - Fixed the `skill_read` tool corrupting binary skill files. A PNG or PDF is now reported as `Binary file: ()` 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](https://github.com/mastra-ai/mastra/pull/24101)) - 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](https://github.com/mastra-ai/mastra/pull/24104)) - 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](https://github.com/mastra-ai/mastra/pull/24059)) - 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](https://github.com/mastra-ai/mastra/pull/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' }); ``` - 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](https://github.com/mastra-ai/mastra/pull/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` over step statuses, TypeScript will now require a `'canceled'` case. This reflects a value the runtime was already producing. ## 1.68.0-alpha.2 ### Minor Changes - Added A2A v1.0 remote subagent delegation with explicit protocol selection on `A2AAgent`. Existing integrations continue to use v0.3 by default. ([#24134](https://github.com/mastra-ai/mastra/pull/24134)) ```typescript const remoteAgent = new A2AAgent({ url: 'https://example.com/.well-known/agent-card.json', protocolVersion: '1.0', }); ``` - Added MCP 2026-07-28 server contracts to `MCPServerBase` while keeping MCP 1.x servers working unchanged. ([#23875](https://github.com/mastra-ai/mastra/pull/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; }, }); ``` ### Patch Changes - 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](https://github.com/mastra-ai/mastra/pull/24048)) - 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](https://github.com/mastra-ai/mastra/pull/24048)) - Fixed queued follow-up messages being saved without an answer after the active run is aborted. ([#23926](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/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](https://github.com/mastra-ai/mastra/pull/24122)) - 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](https://github.com/mastra-ai/mastra/pull/23791)) ```ts await session.thread.rename({ title: 'Initial title', pin: false }); ``` Fixes #22421 - 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](https://github.com/mastra-ai/mastra/pull/23998)) - Removed the redundant direct Ajv dependency. Schema compatibility bundles its validator and standalone types without requiring a separate Ajv installation. ([#23998](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/pull/23914)) - Fixed durable agents on cross-process engines (like Inngest) dropping `requestContext` values written by input processors. ([#24133](https://github.com/mastra-ai/mastra/pull/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](https://github.com/mastra-ai/mastra/issues/23904) - Fixed `TokenLimiterProcessor` truncation (`strategy: 'truncate'`) splitting UTF-16 surrogate pairs. ([#24096](https://github.com/mastra-ai/mastra/pull/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. - Updated dependencies [[`5085475`](https://github.com/mastra-ai/mastra/commit/5085475c0da226e618eb3ee2676d347788c3fb00), [`6c781fd`](https://github.com/mastra-ai/mastra/commit/6c781fda62eb0b0b74d016f188ed0b2db5cfdceb)]: - @mastra/schema-compat@1.3.11-alpha.1 ## 1.68.0-alpha.1 ### 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](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/pull/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', }, }); ``` - 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](https://github.com/mastra-ai/mastra/pull/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 }, }, }); ``` - Added canonical trace-query field descriptors and bounded discovery contracts for queryable fields and values. ([#24057](https://github.com/mastra-ai/mastra/pull/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', }), ), ); ``` ### 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](https://github.com/mastra-ai/mastra/pull/24008)) - Fixed query-parser, static-site generation, parseBody, and XSS security issues by updating hono to 4.13.7. ([#24027](https://github.com/mastra-ai/mastra/pull/24027)) - 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](https://github.com/mastra-ai/mastra/pull/24083)) - Surface filesystem read failures in the workspace `grep` tool instead of silently reporting them as a complete "0 matches" search. ([#24053](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/pull/24055)) - Fixed a WebSocket denial-of-service advisory by updating ws to 8.21.3. ([#24027](https://github.com/mastra-ai/mastra/pull/24027)) - 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](https://github.com/mastra-ai/mastra/pull/24056)) - Fixed a chat-library security advisory by updating chat to 4.37.0. Agent and factory chat APIs are unchanged. ([#24027](https://github.com/mastra-ai/mastra/pull/24027)) - 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](https://github.com/mastra-ai/mastra/pull/24082)) - 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](https://github.com/mastra-ai/mastra/pull/24081)) - 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](https://github.com/mastra-ai/mastra/pull/23962)) - 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](https://github.com/mastra-ai/mastra/pull/23420)) - 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](https://github.com/mastra-ai/mastra/pull/24041)) - Updated dependencies [[`fef227a`](https://github.com/mastra-ai/mastra/commit/fef227a8b7cb0ad7f68087e26d1fd2051054a61a)]: - @mastra/schema-compat@1.3.11-alpha.0 ## 1.68.0-alpha.0 ### Minor Changes - Durable agents no longer persist `running` checkpoints by default, and `createDurableAgent()` accepts a new `shouldPersistSnapshot` option to control snapshot persistence ([#23915](https://github.com/mastra-ai/mastra/issues/23915)). ([#23978](https://github.com/mastra-ai/mastra/pull/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 Hono handler and route types to the server exports. ([#23992](https://github.com/mastra-ai/mastra/pull/23992)) ```ts import type { MiddlewareHandler } from '@mastra/core/server'; const middleware: MiddlewareHandler = async (_context, next) => { await next(); }; ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`1e68460`](https://github.com/mastra-ai/mastra/commit/1e68460205d0061c6dbc7a7e7a50950236af774b)) - Ignore malformed numeric `Retry-After` values instead of parsing them as years and delaying retries up to the configured cap. ([#23997](https://github.com/mastra-ai/mastra/pull/23997)) - **Breaking change** ([#22978](https://github.com/mastra-ai/mastra/pull/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); ``` - Fixed shared tools exposing `_background` to agents that do not support background execution. ([#23120](https://github.com/mastra-ai/mastra/pull/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 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](https://github.com/mastra-ai/mastra/issues/23903). ([#23981](https://github.com/mastra-ai/mastra/pull/23981)) ## 1.67.0 ### Minor Changes - Added the Studio Workflow Builder backend. Configure the editor with the new `workflowBuilder` option to enable a hidden, editor-owned agent that authors persisted workflow definitions: ([#23493](https://github.com/mastra-ai/mastra/pull/23493)) ```ts import { Mastra } from '@mastra/core'; import { MastraEditor } from '@mastra/editor'; const mastra = new Mastra({ editor: new MastraEditor({ workflowBuilder: { enabled: true, model: 'openai/gpt-5.5', // optional, this is the default lastMessages: 100, // optional, raise or lower how much authoring history the agent recalls }, }), }); ``` The server exposes two new endpoints for it: `GET /editor/workflow-builder/settings` reports availability and the admin model policy, and `POST /editor/workflow-builder/stream` streams responses from the builder agent. Access is gated by the `stored-workflows:read` and `stored-workflows:write` permissions, and the `stored:` permission umbrella now also matches `stored-workflows:`, so roles granted `stored` access can use the stored-workflow endpoints. - Added per-call background execution dispositions, awaited background calls, caller-scoped completion signals, and bounded delegated background tool execution. ([#23026](https://github.com/mastra-ai/mastra/pull/23026)) Background-eligible tools continue to run deferred by default. Use `defaultDisposition: 'foreground'` when eligibility should only give the model the option to background individual calls: ```ts const tool = createTool({ id: 'research', background: { enabled: true, defaultDisposition: 'foreground', }, // ... }); ``` Eligible calls can now override their execution mode with `_background.disposition`: ```json { "topic": "distributed systems", "_background": { "disposition": "awaited" } } ``` Add the background-work signal processor when tools need caller-scoped completion signals: ```ts import { Agent } from '@mastra/core/agent'; import { createBackgroundWorkSignalProcessor } from '@mastra/core/processors'; const agent = new Agent({ // ... inputProcessors: [createBackgroundWorkSignalProcessor()], }); const stream = await agent.stream('Research distributed systems'); for await (const part of stream.fullStream) { if ( part.type === 'data-signal' && (part.data.tagName === 'work-completed' || part.data.tagName === 'work-failed') ) { handleBackgroundSignal(part.data); } } function handleBackgroundSignal(signal: { tagName?: string }) { if (signal.tagName === 'work-completed') { // Handle successful background work. } else if (signal.tagName === 'work-failed') { // Handle failed background work. } } ``` `deferred` returns a task placeholder while the run continues, `awaited` uses durable background execution while holding the current branch for the authoritative result, and `foreground` executes inline. The legacy `_background.enabled` field remains supported. - Added `NotificationsStorage.updateNotificationsStatus()` to set one status on many notifications of a thread in a single write. The notification inbox tool now uses it to mark a viewed page seen with one round-trip instead of one update per record. ([#23718](https://github.com/mastra-ai/mastra/pull/23718)) ```ts // Before: one write per notification await Promise.all(ids.map(id => storage.updateNotification({ threadId, id, status: 'seen' }))); // After: one write for the whole page; returns the updated records const seen = await storage.updateNotificationsStatus({ threadId, ids, status: 'seen' }); ``` Adapters that don't override the method fall back to per-record `updateNotification` calls, so existing custom storages keep working. - Added experimental tools for connecting Mastra Code agents and sending prioritized signals between freshly advertised peer threads. Cross-agent communication is off by default. Enable it with the "Experimental cross-agent communication" toggle in `/settings`, then restart Mastra Code. Embedded clients can enable it directly: ([#21986](https://github.com/mastra-ai/mastra/pull/21986)) ```typescript import { createMastraCode } from 'mastracode'; const mastraCode = await createMastraCode({ crossAgentSignals: true, }); ``` Use `agent_connections_list` to discover peers, `agent_connect` to save an exact peer endpoint, `agent_signal_send` to send a correlated message, and `agent_disconnect` to remove the saved connection. Sends require a currently advertised thread owner, return an error when delivery isn't acknowledged, and retain a bounded sender-side history so sequential retries can reuse the same `messageId`. A busy claimed owner acknowledges a safely queued wake immediately and delivers it after the active run finishes. - Restored reactive and system-reminder signals in live streams by default. Added caller-local `hideSignals` to modern agent streams, resume/until-idle streams, and thread subscriptions. Exclusions leave model context, persistence, transforms, and other subscribers unchanged. ([#23554](https://github.com/mastra-ai/mastra/pull/23554)) ```ts // Before: reminders were hidden from every live consumer. const output = await agent.stream('Continue'); // Now: reminders are visible by default; opt out for this caller only. const filtered = await agent.stream('Continue', { hideSignals: ['reactive', 'system-reminder'], }); const subscription = await agent.subscribeToThread({ threadId: 'thread-1', resourceId: 'user-1', hideSignals: ['reactive'], }); ``` Set `hideSignals: true` to hide all recognized signals, `false` or `[]` to show all, or an array to hide selected types. Stream exclusions normalize legacy aliases. They filter streamed signal chunks after transforms, not aggregate output, and are not a security boundary. Shared execution options also accept `hideSignals` on `generate()` and `resumeGenerate()` without filtering their returned results. HTTP/client-js options are unchanged. Fixed directory-scoped instruction discovery so completed file operations load the nearest `AGENTS.md` into the next model request. Repeated operations in the same directory no longer load duplicate instructions, aliased paths resolve to the same instructions, and operations with multiple path fields can discover instructions for each destination. - Added configurable error handling to model-backed guardrail processors. Existing configurations continue to warn and fail open when an internal model call fails: ([#23934](https://github.com/mastra-ai/mastra/pull/23934)) ```ts new PromptInjectionDetector({ model: 'openrouter/openai/gpt-oss-safeguard-20b', }); ``` Set `errorStrategy: 'strict'` to stop processing with a tripwire instead of allowing unchecked content: ```ts new PromptInjectionDetector({ model: 'openrouter/openai/gpt-oss-safeguard-20b', errorStrategy: 'strict', }); ``` - Added `hookDurationMs` to output stream processor spans. It is the time spent inside `processOutputStream`, summed across all chunks. Fixes https://github.com/mastra-ai/mastra/issues/22343 ([#23832](https://github.com/mastra-ai/mastra/pull/23832)) Read it from the span attributes in any exporter: ```ts import type { ObservabilityExporter, ExportedSpan } from '@mastra/core/observability'; const exporter: ObservabilityExporter = { name: 'processor-cost', async exportSpan(span: ExportedSpan) { if (span.entityType === 'output_processor') { console.log(span.name, span.attributes?.hookDurationMs); } }, }; ``` - Added a storage contract for observability adapters to query thread identities using cross-trace predicates. ([#23558](https://github.com/mastra-ai/mastra/pull/23558)) - Added `hideSignals` to the core memory recall contract so memory implementations and callers can explicitly control which stored signal messages are returned. Omitted exclusions preserve the existing reminder-hidden history default, and explicit visibility settings take precedence over the deprecated `includeSystemReminders` option. ([#23554](https://github.com/mastra-ai/mastra/pull/23554)) ```ts await memory.recall({ threadId: 'thread-1', hideSignals: false }); await memory.recall({ threadId: 'thread-1', hideSignals: true }); await memory.recall({ threadId: 'thread-1', hideSignals: ['reactive', 'system-reminder'], }); ``` Use `true` to hide all recognized signal types, `false` or `[]` to include all, or an array to hide selected exact stored types. Legacy reminder rows without a recognized encoded signal type match `system-reminder`. Returned-message filtering preserves pagination totals, raw storage, ordinary messages, and model context. - Exported `validateToolInput` from `@mastra/core/tools` (alongside the existing `validateToolOutput`) for validating a value against a tool's input schema: ([#23493](https://github.com/mastra-ai/mastra/pull/23493)) ```ts import { validateToolInput } from '@mastra/core/tools'; const { data, error } = validateToolInput(myTool.inputSchema, input, myTool.id); if (error) { // error is a ValidationError describing the schema mismatch } ``` - Added `delegation.enableResultReferences` so a later subagent delegation can reuse an earlier subagent's result verbatim, instead of the supervisor restating it. When enabled, each successful foreground subagent result ends with a `[ref: ]` line (for example `explorer-1`), and every delegation tool gains a `contextFromRefs` input that inserts the referenced results ahead of the prompt. Rejected, failed, empty, and background-task delegations don't get a reference. Off by default; when it's off the tool schemas and model output are unchanged. ([#22940](https://github.com/mastra-ai/mastra/pull/22940)) ```ts const supervisor = new Agent({ id: 'supervisor', instructions: 'Delegate research to explorer, then hand the findings to implementer.', model: 'openai/gpt-5', agents: { explorer, implementer }, }); await supervisor.generate('Find and fix the token refresh bug', { delegation: { enableResultReferences: true }, }); // 1. agent-explorer → "...expiry check uses `<` instead of `<=`.\n\n[ref: explorer-1]" // 2. agent-implementer({ prompt: 'Fix the bug', contextFromRefs: ['explorer-1'] }) // receives the explorer's text verbatim before the prompt. ``` Referenced text is subagent output. If subagents handle untrusted input, validate or rewrite `resultText` in `onDelegationComplete` (or through processors) before it can be referenced. Closes https://github.com/mastra-ai/mastra/issues/22910 - Added an optional per-file `mode` to `WorkspaceSandbox.writeFiles` inputs so callers can set POSIX permissions (`0o001`–`0o777`) when provisioning files. The Docker sandbox applies the requested mode to each uploaded file, falling back to `0644` when omitted. Sandboxes that cannot honor an explicit mode (Vercel, E2B, Daytona, Cloudflare) reject the request with `SandboxUnsupportedFeatureError` instead of silently ignoring it. Closes #23580. ([#23652](https://github.com/mastra-ai/mastra/pull/23652)) ```ts await sandbox.writeFiles([ { path: 'scripts/setup.sh', content: '#!/bin/sh\necho ready\n', mode: 0o755 }, { path: 'config/private.json', content: JSON.stringify({ token }), mode: 0o600 }, { path: 'README.md', content: 'Sandbox instructions' }, // defaults to 0644 ]); ``` `@mastra/core` now exports `validateSandboxFileMode`, `assertModesUnsupported`, and `SandboxUnsupportedFeatureError` for sandbox providers; the built-in providers' `@mastra/core` peer dependency floor is raised to `1.67.0` accordingly. - Add thread ownership transfer (resourceId reassignment). ([#23533](https://github.com/mastra-ai/mastra/pull/23533)) You can now transfer an existing thread to a different resource, reassigning both the thread and its messages to the new `resourceId` while preserving the thread's original `createdAt` timestamp. This supports scenarios like moving a private thread into a shared workspace without the previous upsert workaround. - `@mastra/core` / `@mastra/memory`: new `Memory.updateThreadResourceId({ threadId, resourceId })` method, backed by a default `MemoryStorage.updateThreadResourceId` implementation. When semantic recall is enabled, the message vectors are migrated to the new `resourceId` so resource-scoped retrieval keeps surfacing the transferred thread. - `@mastra/server`: new `POST /memory/threads/:threadId/transfer` route. The endpoint is restricted to privileged, non-resource-scoped callers and rejects requests made with a resolved resource scope. - `@mastra/client-js`: new `MemoryThread.transfer({ resourceId })` method. - `@mastra/pg`, `@mastra/libsql`, `@mastra/mssql`, `@mastra/dsql`, `@mastra/oracledb`, `@mastra/mysql`, `@mastra/spanner`: atomic, serialized `updateThreadResourceId` overrides. The thread and all of its messages are moved inside a single transaction, so overlapping transfers of the same thread cannot interleave and leave split ownership. Postgres, MySQL, SQL Server and Oracle take a row lock (`SELECT ... FOR UPDATE` / `UPDLOCK, HOLDLOCK`); libSQL and Spanner serialize their write transactions; Aurora DSQL relies on its optimistic concurrency control with automatic retry. Adapters without a transaction primitive fall back to the base best-effort implementation, which fails closed by reverting on error. ```typescript // Server-side, from a privileged (non-resource-scoped) context: const thread = await memory.updateThreadResourceId({ threadId: 'thread-123', resourceId: 'new-resource-456', }); // Client-side: const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); const thread = client.getMemoryThread('thread-123', 'agent-id'); await thread.transfer({ resourceId: 'new-resource-456' }); ``` - Added an optional `abortSignal` to the `WorkspaceSandbox.writeFiles` contract so file uploads can be cancelled. Also added a `SandboxAbortError` (code `ABORTED`) that providers throw when a write is cancelled. ([#23644](https://github.com/mastra-ai/mastra/pull/23644)) ```ts const controller = new AbortController(); await sandbox.writeFiles?.(files, { abortSignal: controller.signal }); ``` Observing the signal is provider-dependent. Providers that do not support cancellation ignore the option and run to completion. ### Patch Changes - A workflow `.agent()` step that declares `structuredOutput.schema` now fails when the agent finishes without producing an object, instead of silently reporting `success` and returning `{ text }`. The step throws a `MastraError` (`STRUCTURED_OUTPUT_OBJECT_UNDEFINED`) carrying the `finishReason`, matching how the rest of the agent stack guards missing structured output. A validly-parsed falsy object (e.g. `0`) is still treated as produced, and steps without a declared schema are unaffected. Fixes #23403. ([#23559](https://github.com/mastra-ai/mastra/pull/23559)) - Sampling settings that a model does not accept are now left out of the request for models passed in directly and for models hosted on Amazon Bedrock. Previously these settings were only removed for models referenced by name, so directly-passed and Bedrock-hosted models could fail their requests. Fixes #23319. ([#23549](https://github.com/mastra-ai/mastra/pull/23549)) - Update provider registry and model documentation with latest models and providers ([`e86be03`](https://github.com/mastra-ai/mastra/commit/e86be034c017fca7deae7d1ebb34d36413928cb8)) - Fixed `DurableAgent` losing tools discovered by `ToolSearchProcessor` after the first search. With `includeResolvedTools: true`, the first model step correctly saw only `search_tools`, but the next step saw only `search_tools` again instead of the tool the search auto-loaded, so real models looped on `search_tools` forever. The durable loop now keeps the complete resolved toolset separate from the narrowed per-step snapshot, so tools loaded by `search_tools` or `load_tool` become available on the following step. Fixes #22933. ([#23889](https://github.com/mastra-ai/mastra/pull/23889)) - Fixed signals that wake an idle thread so they continue through durable execution when the agent is wrapped by a durable integration such as `@mastra/inngest`, instead of falling back to the wrapped agent's in-process run. Fixes [#23800](https://github.com/mastra-ai/mastra/issues/23800). ([#23868](https://github.com/mastra-ai/mastra/pull/23868)) - Fixed browser providers signalling a remote browser's PID on the host machine. When a provider connected to an existing browser over `cdpUrl` (or a Firecrawl/Browserbase cloud session), it captured the remote browser's PID and, on disconnect, ran `process.kill(-pid, 'SIGKILL')` locally. That PID belongs to another host or container, so the signal hit an unrelated local process group — and when the remote Chromium was its container's PID 1, `kill(-1)` broadcast SIGKILL to every process the Mastra user owned. ([#23599](https://github.com/mastra-ai/mastra/pull/23599)) Providers now skip PID capture whenever the browser was reached over CDP, so there is nothing to signal for browsers we do not own. As defense in depth, `killProcessGroup` in `@mastra/core` now refuses any PID that cannot name a killable local process group (non-integer, negative, `0`, or `1`). Locally launched browsers are unaffected and still have their process group cleaned up. - Channel adapters can now discard the agent's buffered reply when a run is aborted. A new `onAbort: 'flush' | 'discard'` option on the adapter config controls what the static (non-streaming) driver does with not-yet-posted text on an `abort` chunk: `'flush'` (the default) posts the partial reply as before, and `'discard'` drops it so nothing is posted. This supports human-takeover flows on non-streaming platforms, where an in-flight agent reply should not appear as a truncated message beside the operator's own reply. ([#23655](https://github.com/mastra-ai/mastra/pull/23655)) ```ts const adapter = new MyChannelAdapter({ // ...existing config onAbort: 'discard', // drop buffered text on abort instead of posting it }); ``` Fixes #23640. - Fixed an issue where calling sendSignal() from a processToolResult processor hook could silently drop the just-completed tool call and result from later model inputs and saved history. The in-flight response message is now only sealed when a message id rotation follows, so the next streamed step merges into it instead of replacing it. (#21940) ([#23634](https://github.com/mastra-ai/mastra/pull/23634)) - Added `copyThread()` so a thread and its messages can be duplicated without loading every message payload into the Node heap. Fixes #23434. ([#23567](https://github.com/mastra-ai/mastra/pull/23567)) `memory.cloneThread()` keeps its existing signature and still returns `clonedMessages`, but the copy now happens inside the database first (via `INSERT … SELECT` on LibSQL and Postgres) and the messages are read back afterwards only because the caller asked for them. Forked subagents and other callers that only need the new thread id use the new `memory.copyThread()`, which never returns message content. When semantic recall is enabled, the copied messages are still read back to generate embeddings, but in batches of 100 instead of all at once. ```ts // Same as before: returns the copied messages. const { thread: clonedThread, clonedMessages } = await memory.cloneThread({ sourceThreadId }); // New: copy without returning message payloads. const { thread: copiedThread, messageIdMap } = await memory.copyThread({ sourceThreadId }); ``` Storage adapters now implement `copyThread()`; the base `cloneThread()` is derived from it. The unreleased `hydrateMessages` option has been removed. - `ProviderHistoryCompat` now handles signed thinking blocks that cross providers. The new `anthropic-strip-foreign-signed-reasoning` rule drops signed reasoning from the outbound prompt when the turn that produced it was stamped with a different provider (for example Kimi For Coding ↔ `anthropic/claude-sonnet-4-6`), since the receiving provider rejects a foreign signature with ``Invalid `signature` in `thinking` block``. To support provenance-aware rules, `processLLMRequest` args now expose the `messageList` the prompt was built from. Goal scorers created with `createGoalScorer` now include `ProviderHistoryCompat` in their input and error processor lanes by default, since goal judges talk to the same providers as the agent they judge. ([#23695](https://github.com/mastra-ai/mastra/pull/23695)) ```ts import { ProviderHistoryCompat } from '@mastra/core/processors'; export const agent = new Agent({ inputProcessors: [new ProviderHistoryCompat()], errorProcessors: [new ProviderHistoryCompat()], }); ``` - Fixed `Mastra.shutdown()` tearing down pub/sub before in-flight workflow runs could finish. Runs that were mid-step when `mastra.shutdown()` was called used to hang forever because the events they needed no longer had a consumer; durable agent runs were drained too late for the drain to help. ([#23168](https://github.com/mastra-ai/mastra/pull/23168)) `shutdown()` now waits for in-flight workflow runs (plain and durable agent) to reach a finished or suspended state before stopping workers, bounded by a new `drainTimeout` option (default 5 seconds). Workers also wait for events they are already processing before tearing down. The timeout is one shared deadline for the whole shutdown: the workflow drain, background task cancellation, and worker teardown all draw from it, so a stuck step can never hold `shutdown()` open for longer than `drainTimeout` before workspace and storage cleanup. A durable agent whose terminal error event cannot be published (for example because the pub/sub client is already closing) now logs a warning instead of surfacing an unhandled rejection. Note: the durable agent wait was previously unbounded. If you rely on `shutdown()` waiting longer than 5 seconds for durable agent runs, pass a larger `drainTimeout`. ```typescript await mastra.shutdown({ drainTimeout: 30_000 }); ``` Fixes https://github.com/mastra-ai/mastra/issues/22863 - Fixed sub-agent delegation so processed supervisor context remains available without exposing observational memory control messages or rerunning observational memory for inherited memory. ([#21929](https://github.com/mastra-ai/mastra/pull/21929)) - Improved core primitive declarations with concise usage examples and directions to bundled documentation. ([#23489](https://github.com/mastra-ai/mastra/pull/23489)) - Added persisted error parts for failed agent turns so thread history retains terminal failures. ([#23867](https://github.com/mastra-ai/mastra/pull/23867)) ```typescript const memory = await agent.getMemory(); const { messages } = await memory!.recall({ threadId: 'thread-123', perPage: false }); for (const message of messages) { for (const part of message.content.parts ?? []) { if (part.type === 'error') { console.error(part.error.name, part.error.message); } } } ``` - Updated Session follow-ups to share the Agent-owned thread queue and pending count across collaborators. Steering retains its abort-then-send behavior without clearing queued follow-ups. Session cleanup preserves submitted messages while cancelling unfinished local preparation. ([#23235](https://github.com/mastra-ai/mastra/pull/23235)) **Breaking change:** replace `subscribeQueuedMessages({ resourceId, threadId }, listener)` with `subscribeThreadEvents({ resourceId, threadId }, listener)`. The listener now receives a typed event instead of a `{ count }` snapshot: ```typescript const unsubscribe = agent.subscribeThreadEvents({ resourceId, threadId }, event => { if (event.type === 'queue-count-changed') { console.log(event.count); } }); ``` This API currently reports only local pending queue counts, not composite thread state or individual message lifecycle events. Explicit cancellation remains available by signal ID or optional queue owner. Observation and cancellation apply only to local pending messages, not running or remote work. - Fix AgentController Session turning missing/unknown token counts into false zeros. Step-finish now skips fabricating, persisting, and emitting a usage update when a step reports no usable primary counts (prompt/completion/total all absent), instead of folding a `{0,0,0}` tally into the running total. `loadMetadata()` no longer calls `resetTokenUsage()` when a persisted metadata read fails transiently, so a measured tally is preserved instead of being destroyed by a read error. Fixes #23471. ([#23657](https://github.com/mastra-ai/mastra/pull/23657)) - Fix assistant text parts being reordered when merging multiple text parts after tool results. Account for synthetic step-start markers without moving later tool results across text, preserving the order used for stored messages and model history. ([#23840](https://github.com/mastra-ai/mastra/pull/23840)) - Fixed file attachment names being lost when stored messages are converted to AI SDK v5 UI messages. ([#23523](https://github.com/mastra-ai/mastra/pull/23523)) - Fixed background tasks whose initial dispatch is rejected by marking still-pending tasks as failed and freeing local capacity for queued work. Tasks already claimed by a worker are preserved when a transport reports an ambiguous publication failure. ([#23024](https://github.com/mastra-ai/mastra/pull/23024)) - Fixed workflow retry counts leaking across concurrent runs and foreach items. ([#23593](https://github.com/mastra-ai/mastra/pull/23593)) - Fixed the notification inbox tool leaving notifications pending forever after the agent viewed them. Listing, reading, or searching now marks the returned unread notifications as seen. `list` defaults to unread notifications in pages of 20 and reports `hasMore` and `markedSeen`; pass `status: 'seen'` to list already-viewed notifications or `limit` to change the page size. Internal `metadata` and `payload` fields are no longer included in list and search results. ([#23710](https://github.com/mastra-ai/mastra/pull/23710)) - Fixed execute_command to include stderr when commands exit successfully. (#23679) ([#23683](https://github.com/mastra-ai/mastra/pull/23683)) - Fixed durable agents continuing past a tool call that the client is meant to execute. ([#23612](https://github.com/mastra-ai/mastra/pull/23612)) A tool declared without `execute` runs on the client, which answers it on a follow-up request, so the run must end at the call. Durable agents instead recorded an empty result for it and called the model again — the agent answered as though the tool had returned nothing, and the client never received the call. ```ts const agent = new Agent({ model, durable: true, // No `execute`: the client runs this tool and sends the result back. tools: { approveInvoice: createTool({ id: 'approveInvoice', inputSchema }) }, }); // The turn now ends at the call so the client can answer it. const { output } = await agent.stream('Approve invoice 42'); ``` See [#23295](https://github.com/mastra-ai/mastra/issues/23295). - Fixed Unix socket pubsub clients hanging forever on startup when connecting to a broker from an older build that never acknowledges membership requests. Both `subscribe` and `unsubscribe` now proceed best-effort after a configurable timeout when no acknowledgement arrives, so newer clients no longer deadlock against legacy brokers. ([#23699](https://github.com/mastra-ai/mastra/pull/23699)) Configure the timeout via the new `membershipAckTimeoutMs` option (default 5000ms): ```ts const pubsub = new UnixSocketPubSub(socketPath, { membershipAckTimeoutMs: 5000 }); ``` - Reduced TypeScript memory usage for applications that define many tools with Zod schemas. ([#23677](https://github.com/mastra-ai/mastra/pull/23677)) - Durable agents attached to a chat channel now render their final answer after a tool call. Previously the first tool step ended channel rendering, so the tool cards posted but the answer that followed was silently dropped — the run still reported success and saved the message to the thread. Any output processor reading `stepResult.isContinued` on `step-finish` chunks now receives it on durable runs too, matching regular agent runs, including when a tool error makes the loop continue past a model `stop`. Fixes #23341. ([#23610](https://github.com/mastra-ai/mastra/pull/23610)) - `summarizeNotifications()` now counts notification sources whose names collide with `Object.prototype` members (for example `__proto__`, `constructor`, `toString`) as ordinary own numeric properties. The per-source and per-priority accumulators are seeded with null-prototype objects, so a `__proto__` source is no longer silently dropped from the summary and `constructor`/`toString` sources no longer produce non-numeric string counts. Fixes #23693. ([#23700](https://github.com/mastra-ai/mastra/pull/23700)) - Fixed a stuck agent run when a message is sent immediately after aborting. Calling `abort()` (or `steer()`) and sending another message right away now correctly starts a fresh, observable run instead of losing its start/end events and leaving the session stuck in a running state. ([#23565](https://github.com/mastra-ai/mastra/pull/23565)) - Simplified core maintenance without changing schema validation, event serialization, or callback behavior. ([#23729](https://github.com/mastra-ai/mastra/pull/23729)) - Improved background task execution across multiple processes. Invocation-bound tasks stay on the process that owns their executor, cancellation reliably reaches local work, and queued tasks continue after dispatch failures. ([#23024](https://github.com/mastra-ai/mastra/pull/23024)) **Stale task recovery** Startup recovery remains enabled by default. Disable it when multiple live managers share storage and task ownership cannot be verified across processes: ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, recoverStaleTasksOnStart: false, }, }); ``` - Improved streaming performance for output processors by avoiding unnecessary workflow runs for each chunk. ([#23435](https://github.com/mastra-ai/mastra/pull/23435)) - Only emit the "logger already wired to another Mastra instance" re-attach warning when `loggerOptions.export` is enabled. With `export: false` there is no observability export target to clobber, so attaching a shared logger to multiple `Mastra` instances no longer prints a spurious warning. ([#23544](https://github.com/mastra-ai/mastra/pull/23544)) - Scorer judge configuration now accepts an optional `modelSettings` field (temperature, topP, topK, maxOutputTokens, maxRetries, frequencyPenalty, presencePenalty, timeout, etc.), forwarded to the internal judge agent run. It can be set at the scorer level and overridden per step, removing the need for an input-processor workaround. Closes #23458. ([#23637](https://github.com/mastra-ai/mastra/pull/23637)) ```ts const scorer = createScorer({ id: 'answer-relevancy', description: 'Scores answer relevancy', judge: { model: openai('gpt-4o'), instructions: 'Return a relevancy score.', // New: configure the judge model call directly modelSettings: { temperature: 0, maxRetries: 3 }, }, }) .analyze({ description: 'analyze', outputSchema: z.object({ value: z.number() }), createPrompt: () => 'analyze this', // Optional per-step override (replaces the scorer-level value) judge: { modelSettings: { temperature: 0.7 } }, }) .generateScore(({ results }) => results.analyzeStepResult.value); ``` - Improved default goal-judge retries by adding JSON instructions to the latest user message. The first attempt still selects the supported output format automatically. Validation remains strict. Other scorers keep their existing retry behavior. ([#23901](https://github.com/mastra-ai/mastra/pull/23901)) - Fix background sub-agent tool approvals restarting the delegation instead of resuming it. When a sub-agent run as a background task suspends on a tool approval, the nested run id (carried in `suspendOptions.runId`) is now bridged into the background task's persisted suspend data, so `resume` restores it and the sub-agent continues its existing run and executes the approved tool — rather than starting a fresh run and re-suspending on the same approval. Fixes #23626. ([#23659](https://github.com/mastra-ai/mastra/pull/23659)) - Honor the configured logger and error strategy when structured output uses a separate model. Handled validation failures now warn through the agent logger or return the configured fallback without misleading error-level console logs. Preserve fallback metadata on structured output results. ([#23849](https://github.com/mastra-ai/mastra/pull/23849)) - Fixed a fractional `tail` value producing a truncation notice that disagreed with the returned output. The `execute_command` and `get_process_output` tools now reject a non-integer `tail`, and the `[showing last N of M lines]` notice always reports a whole number equal to the lines actually returned. ([#23570](https://github.com/mastra-ai/mastra/pull/23570)) - Fix network completion scoring to honor its timeout as a hard deadline. Previously `runCompletionScorers` awaited every scorer to settle after the deadline, so a slow scorer could return a late passing verdict with `complete: true`, a never-settling scorer could hang the call indefinitely, and the default timeout timer was never cleared. Parallel and sequential scoring now race each scorer against a single shared deadline, return promptly when it elapses, preserve the evidence of scorers that already finished, mark unfinished checks as errored failures, prevent late results from changing the returned verdict, and clear the timer on completion. Non-Error scorer rejections (`null`, `undefined`, strings, plain objects, and objects with throwing `message` getters) are now converted into explicit scorer failures instead of crashing the error handler or losing their reason. Fixes #23449. ([#23564](https://github.com/mastra-ai/mastra/pull/23564)) - Reject non-finite, fractional, and negative numeric pagination inputs while preserving defaults, zero-sized pages, and fetch-all pagination. Throw a clear error when directly creating a subagent tool without any subagent definitions. ([#23846](https://github.com/mastra-ai/mastra/pull/23846)) - Fixed workspace tool output truncation producing invalid Unicode when cutting through emoji. ([#23431](https://github.com/mastra-ai/mastra/pull/23431)) - Add an opt-in `injectCatalog` option to `ToolSearchProcessor`. When enabled, the available-tool catalog (each tool's name and a short description) is injected into the system prompt so the agent can skip the `search_tools` turn and call `load_tool` directly, collapsing the default `search -> load -> use` (3 turns) into `load -> use` (2 turns). `search_tools` stays available as a keyword fallback, injected entries respect the `filter` hook, and the option defaults to `false` so existing behavior is unchanged. Best suited to small/medium tool sets where listing the catalog inline is cheaper than a discovery round-trip. Closes #16463. ([#23578](https://github.com/mastra-ai/mastra/pull/23578)) - Fixed processor step `modelSettings` types so they accept the `timeout` field (`stepMs`, `totalMs`, `firstChunkMs`) that Mastra already applies at runtime. Input processors can now return `modelSettings.timeout` inline without a TypeScript error, and reading `timeout` from step arguments type-checks correctly. ([#23560](https://github.com/mastra-ai/mastra/pull/23560)) - Added consumer groups to `UnixSocketPubSub`. ([#23020](https://github.com/mastra-ai/mastra/pull/23020)) **Usage** ```typescript // Every ungrouped subscriber receives each message. await pubsub.subscribe('events', handleAllEvents); // One subscriber in the group receives each message. await pubsub.subscribe('events', handleWorkerEvent, { group: 'workers' }); ``` **Behavior** - Delivers each message once per named group and rotates delivery among available group members. - Restores group membership when a client reconnects. - Retries rejected local deliveries a bounded number of times. - Include token `usage` diagnostics in the `STRUCTURED_OUTPUT_OBJECT_UNDEFINED` error thrown when a workflow agent step declares a structured-output schema but produces no object, so loggers and workflow error consumers retain the finish result's usage data. ([#23563](https://github.com/mastra-ai/mastra/pull/23563)) - Fixed notification delivery policy so a `source` named after a JavaScript `Object.prototype` member (such as `constructor`, `toString`, or `hasOwnProperty`) no longer bypasses your configured `priorities` and `default`. Previously such a source resolved an inherited function and was delivered even when `default: 'discard'` was set. Source and priority lookups now only match keys you explicitly configured. Fixes #23694. ([#23701](https://github.com/mastra-ai/mastra/pull/23701)) - Add `WidenModelId` and `WidenedMastraModelConfig` type helpers that replace the model-id literal union with `string` for internal plumbing. Public config fields keep `MastraModelConfig` for autocomplete; internal code that merges model values (`??`, ternaries) should widen first so TypeScript does not subtype-reduce the full model registry union. ([#23947](https://github.com/mastra-ai/mastra/pull/23947)) - Improved workspace `grep` and `list_files` tool performance on remote filesystems. The `list_files` tree walk now issues directory listings concurrently instead of one at a time, and `WorkspaceFilesystem` providers can implement optional `walk()` and `grep()` methods to run tree walks and content searches natively in a single call. The workspace tools use these capabilities automatically when available and fall back to the existing host-side walk otherwise, so a grep over a remote sandbox filesystem no longer needs one network round trip per directory and file. When a native capability fails and the tools fall back to the host-side walk, the downgrade is logged through the workspace logger (`info` for an unsupported grep pattern, `warn` for any other failure) so unexpected per-file round trips are visible. `Workspace` now exposes a read-only `logger` getter. Fixes https://github.com/mastra-ai/mastra/issues/22285 ([#22317](https://github.com/mastra-ai/mastra/pull/22317)) ```ts import { UnsupportedGrepPatternError } from '@mastra/core/workspace'; import type { WorkspaceFilesystem, WalkEntry, FilesystemGrepResult } from '@mastra/core/workspace'; class RemoteFilesystem implements WorkspaceFilesystem { // ...required methods... // Optional: return every entry under `path` in one call. async walk(path: string, options?: { maxDepth?: number; includeHidden?: boolean }): Promise { return this.api.listTree(path, options); } // Optional: run the search remotely. `column` must be a UTF-16 index. // Throw UnsupportedGrepPatternError to let the tool fall back to the host walk. async grep(options: { pattern: string; path: string; caseSensitive: boolean }): Promise { if (!this.api.supportsRegex(options.pattern)) throw new UnsupportedGrepPatternError(options.pattern); return this.api.search(options); } } ``` - Workflow control-flow spans now carry the identity of the graph entry that created them, so observability tools can correlate a span with the authored operation instead of inferring it from structure. ([#23542](https://github.com/mastra-ai/mastra/pull/23542)) - `.parallel()`, `.branch()`, `.dowhile()`, `.dountil()`, and `.foreach()` container spans use the authored entry `id` for their display name (e.g. `parallel: 'check-document'`) and expose the entry `id`, `description`, and `metadata` as span attributes (`entryId`, `entryDescription`, `entryMetadata`). Spans without an authored `id` keep their previous structural name as a fallback. - `.sleep()` and `.sleepUntil()` spans keep their descriptive duration/date names and now expose the same identity attributes. - `.map()` step spans now forward the entry `description` and `metadata` (they already exposed the entry `id`). For example, given: ```ts workflow.parallel([checkSpelling, checkGrammar], { id: 'check-document', metadata: { title: 'Check document' }, }); ``` the container span is now named `parallel: 'check-document'` and its attributes include `entryId: 'check-document'` and `entryMetadata: { title: 'Check document' }`, letting an exporter display "Check document" and distinguish it from other parallel groups with the same branch count. - Fix workspace `read_file` throwing `TypeError: mimeType.startsWith is not a function` for files whose extension matches an inherited `Object.prototype` member (e.g. `file.constructor`, `file.__proto__`). `getMimeType` now only resolves own string entries of its MIME table, so these filenames fall back to `application/octet-stream` and read as text like any other unknown extension. Fixes #23957. ([#23959](https://github.com/mastra-ai/mastra/pull/23959)) - Fixed live agent scores being dropped when tracing is enabled. Durable agents now save scorer results, and scores link to the exported agent span instead of a hidden workflow step span. (#23465) ([#23600](https://github.com/mastra-ai/mastra/pull/23600)) - Updated dependencies [[`ffe16f1`](https://github.com/mastra-ai/mastra/commit/ffe16f17447449b7155f1f15992e3c9e5f6511ac), [`80608ed`](https://github.com/mastra-ai/mastra/commit/80608ede1a9e5d7d8488ac511245bf327e8987e3)]: - @mastra/schema-compat@1.3.10 ## 1.67.0-alpha.6 ### Patch Changes - Fix workspace `read_file` throwing `TypeError: mimeType.startsWith is not a function` for files whose extension matches an inherited `Object.prototype` member (e.g. `file.constructor`, `file.__proto__`). `getMimeType` now only resolves own string entries of its MIME table, so these filenames fall back to `application/octet-stream` and read as text like any other unknown extension. Fixes #23957. ([#23959](https://github.com/mastra-ai/mastra/pull/23959)) ## 1.67.0-alpha.5 ### Minor Changes - Added `NotificationsStorage.updateNotificationsStatus()` to set one status on many notifications of a thread in a single write. The notification inbox tool now uses it to mark a viewed page seen with one round-trip instead of one update per record. ([#23718](https://github.com/mastra-ai/mastra/pull/23718)) ```ts // Before: one write per notification await Promise.all(ids.map(id => storage.updateNotification({ threadId, id, status: 'seen' }))); // After: one write for the whole page; returns the updated records const seen = await storage.updateNotificationsStatus({ threadId, ids, status: 'seen' }); ``` Adapters that don't override the method fall back to per-record `updateNotification` calls, so existing custom storages keep working. - Added configurable error handling to model-backed guardrail processors. Existing configurations continue to warn and fail open when an internal model call fails: ([#23934](https://github.com/mastra-ai/mastra/pull/23934)) ```ts new PromptInjectionDetector({ model: 'openrouter/openai/gpt-oss-safeguard-20b', }); ``` Set `errorStrategy: 'strict'` to stop processing with a tripwire instead of allowing unchecked content: ```ts new PromptInjectionDetector({ model: 'openrouter/openai/gpt-oss-safeguard-20b', errorStrategy: 'strict', }); ``` - Added `hookDurationMs` to output stream processor spans. It is the time spent inside `processOutputStream`, summed across all chunks. Fixes https://github.com/mastra-ai/mastra/issues/22343 ([#23832](https://github.com/mastra-ai/mastra/pull/23832)) Read it from the span attributes in any exporter: ```ts import type { ObservabilityExporter, ExportedSpan } from '@mastra/core/observability'; const exporter: ObservabilityExporter = { name: 'processor-cost', async exportSpan(span: ExportedSpan) { if (span.entityType === 'output_processor') { console.log(span.name, span.attributes?.hookDurationMs); } }, }; ``` ### Patch Changes - Fixed `DurableAgent` losing tools discovered by `ToolSearchProcessor` after the first search. With `includeResolvedTools: true`, the first model step correctly saw only `search_tools`, but the next step saw only `search_tools` again instead of the tool the search auto-loaded, so real models looped on `search_tools` forever. The durable loop now keeps the complete resolved toolset separate from the narrowed per-step snapshot, so tools loaded by `search_tools` or `load_tool` become available on the following step. Fixes #22933. ([#23889](https://github.com/mastra-ai/mastra/pull/23889)) - Fixed signals that wake an idle thread so they continue through durable execution when the agent is wrapped by a durable integration such as `@mastra/inngest`, instead of falling back to the wrapped agent's in-process run. Fixes [#23800](https://github.com/mastra-ai/mastra/issues/23800). ([#23868](https://github.com/mastra-ai/mastra/pull/23868)) - Fixed an issue where calling sendSignal() from a processToolResult processor hook could silently drop the just-completed tool call and result from later model inputs and saved history. The in-flight response message is now only sealed when a message id rotation follows, so the next streamed step merges into it instead of replacing it. (#21940) ([#23634](https://github.com/mastra-ai/mastra/pull/23634)) - `ProviderHistoryCompat` now handles signed thinking blocks that cross providers. The new `anthropic-strip-foreign-signed-reasoning` rule drops signed reasoning from the outbound prompt when the turn that produced it was stamped with a different provider (for example Kimi For Coding ↔ `anthropic/claude-sonnet-4-6`), since the receiving provider rejects a foreign signature with ``Invalid `signature` in `thinking` block``. To support provenance-aware rules, `processLLMRequest` args now expose the `messageList` the prompt was built from. Goal scorers created with `createGoalScorer` now include `ProviderHistoryCompat` in their input and error processor lanes by default, since goal judges talk to the same providers as the agent they judge. ([#23695](https://github.com/mastra-ai/mastra/pull/23695)) ```ts import { ProviderHistoryCompat } from '@mastra/core/processors'; export const agent = new Agent({ inputProcessors: [new ProviderHistoryCompat()], errorProcessors: [new ProviderHistoryCompat()], }); ``` - Fix assistant text parts being reordered when merging multiple text parts after tool results. Account for synthetic step-start markers without moving later tool results across text, preserving the order used for stored messages and model history. ([#23840](https://github.com/mastra-ai/mastra/pull/23840)) - Fixed the notification inbox tool leaving notifications pending forever after the agent viewed them. Listing, reading, or searching now marks the returned unread notifications as seen. `list` defaults to unread notifications in pages of 20 and reports `hasMore` and `markedSeen`; pass `status: 'seen'` to list already-viewed notifications or `limit` to change the page size. Internal `metadata` and `payload` fields are no longer included in list and search results. ([#23710](https://github.com/mastra-ai/mastra/pull/23710)) - Simplified core maintenance without changing schema validation, event serialization, or callback behavior. ([#23729](https://github.com/mastra-ai/mastra/pull/23729)) - Improved default goal-judge retries by adding JSON instructions to the latest user message. The first attempt still selects the supported output format automatically. Validation remains strict. Other scorers keep their existing retry behavior. ([#23901](https://github.com/mastra-ai/mastra/pull/23901)) - Add `WidenModelId` and `WidenedMastraModelConfig` type helpers that replace the model-id literal union with `string` for internal plumbing. Public config fields keep `MastraModelConfig` for autocomplete; internal code that merges model values (`??`, ternaries) should widen first so TypeScript does not subtype-reduce the full model registry union. ([#23947](https://github.com/mastra-ai/mastra/pull/23947)) ## 1.67.0-alpha.4 ### Minor Changes - Added the Studio Workflow Builder backend. Configure the editor with the new `workflowBuilder` option to enable a hidden, editor-owned agent that authors persisted workflow definitions: ([#23493](https://github.com/mastra-ai/mastra/pull/23493)) ```ts import { Mastra } from '@mastra/core'; import { MastraEditor } from '@mastra/editor'; const mastra = new Mastra({ editor: new MastraEditor({ workflowBuilder: { enabled: true, model: 'openai/gpt-5.5', // optional, this is the default lastMessages: 100, // optional, raise or lower how much authoring history the agent recalls }, }), }); ``` The server exposes two new endpoints for it: `GET /editor/workflow-builder/settings` reports availability and the admin model policy, and `POST /editor/workflow-builder/stream` streams responses from the builder agent. Access is gated by the `stored-workflows:read` and `stored-workflows:write` permissions, and the `stored:` permission umbrella now also matches `stored-workflows:`, so roles granted `stored` access can use the stored-workflow endpoints. - Exported `validateToolInput` from `@mastra/core/tools` (alongside the existing `validateToolOutput`) for validating a value against a tool's input schema: ([#23493](https://github.com/mastra-ai/mastra/pull/23493)) ```ts import { validateToolInput } from '@mastra/core/tools'; const { data, error } = validateToolInput(myTool.inputSchema, input, myTool.id); if (error) { // error is a ValidationError describing the schema mismatch } ``` ### Patch Changes - Added persisted error parts for failed agent turns so thread history retains terminal failures. ([#23867](https://github.com/mastra-ai/mastra/pull/23867)) ```typescript const memory = await agent.getMemory(); const { messages } = await memory!.recall({ threadId: 'thread-123', perPage: false }); for (const message of messages) { for (const part of message.content.parts ?? []) { if (part.type === 'error') { console.error(part.error.name, part.error.message); } } } ``` - Updated Session follow-ups to share the Agent-owned thread queue and pending count across collaborators. Steering retains its abort-then-send behavior without clearing queued follow-ups. Session cleanup preserves submitted messages while cancelling unfinished local preparation. ([#23235](https://github.com/mastra-ai/mastra/pull/23235)) **Breaking change:** replace `subscribeQueuedMessages({ resourceId, threadId }, listener)` with `subscribeThreadEvents({ resourceId, threadId }, listener)`. The listener now receives a typed event instead of a `{ count }` snapshot: ```typescript const unsubscribe = agent.subscribeThreadEvents({ resourceId, threadId }, event => { if (event.type === 'queue-count-changed') { console.log(event.count); } }); ``` This API currently reports only local pending queue counts, not composite thread state or individual message lifecycle events. Explicit cancellation remains available by signal ID or optional queue owner. Observation and cancellation apply only to local pending messages, not running or remote work. - Reduced TypeScript memory usage for applications that define many tools with Zod schemas. ([#23677](https://github.com/mastra-ai/mastra/pull/23677)) - `summarizeNotifications()` now counts notification sources whose names collide with `Object.prototype` members (for example `__proto__`, `constructor`, `toString`) as ordinary own numeric properties. The per-source and per-priority accumulators are seeded with null-prototype objects, so a `__proto__` source is no longer silently dropped from the summary and `constructor`/`toString` sources no longer produce non-numeric string counts. Fixes #23693. ([#23700](https://github.com/mastra-ai/mastra/pull/23700)) - Honor the configured logger and error strategy when structured output uses a separate model. Handled validation failures now warn through the agent logger or return the configured fallback without misleading error-level console logs. Preserve fallback metadata on structured output results. ([#23849](https://github.com/mastra-ai/mastra/pull/23849)) - Reject non-finite, fractional, and negative numeric pagination inputs while preserving defaults, zero-sized pages, and fetch-all pagination. Throw a clear error when directly creating a subagent tool without any subagent definitions. ([#23846](https://github.com/mastra-ai/mastra/pull/23846)) - Fixed notification delivery policy so a `source` named after a JavaScript `Object.prototype` member (such as `constructor`, `toString`, or `hasOwnProperty`) no longer bypasses your configured `priorities` and `default`. Previously such a source resolved an inherited function and was delivered even when `default: 'discard'` was set. Source and priority lookups now only match keys you explicitly configured. Fixes #23694. ([#23701](https://github.com/mastra-ai/mastra/pull/23701)) - Updated dependencies [[`ffe16f1`](https://github.com/mastra-ai/mastra/commit/ffe16f17447449b7155f1f15992e3c9e5f6511ac)]: - @mastra/schema-compat@1.3.10-alpha.1 ## 1.67.0-alpha.3 ### Minor Changes - Added per-call background execution dispositions, awaited background calls, caller-scoped completion signals, and bounded delegated background tool execution. ([#23026](https://github.com/mastra-ai/mastra/pull/23026)) Background-eligible tools continue to run deferred by default. Use `defaultDisposition: 'foreground'` when eligibility should only give the model the option to background individual calls: ```ts const tool = createTool({ id: 'research', background: { enabled: true, defaultDisposition: 'foreground', }, // ... }); ``` Eligible calls can now override their execution mode with `_background.disposition`: ```json { "topic": "distributed systems", "_background": { "disposition": "awaited" } } ``` Add the background-work signal processor when tools need caller-scoped completion signals: ```ts import { Agent } from '@mastra/core/agent'; import { createBackgroundWorkSignalProcessor } from '@mastra/core/processors'; const agent = new Agent({ // ... inputProcessors: [createBackgroundWorkSignalProcessor()], }); const stream = await agent.stream('Research distributed systems'); for await (const part of stream.fullStream) { if ( part.type === 'data-signal' && (part.data.tagName === 'work-completed' || part.data.tagName === 'work-failed') ) { handleBackgroundSignal(part.data); } } function handleBackgroundSignal(signal: { tagName?: string }) { if (signal.tagName === 'work-completed') { // Handle successful background work. } else if (signal.tagName === 'work-failed') { // Handle failed background work. } } ``` `deferred` returns a task placeholder while the run continues, `awaited` uses durable background execution while holding the current branch for the authoritative result, and `foreground` executes inline. The legacy `_background.enabled` field remains supported. - Added experimental tools for connecting Mastra Code agents and sending prioritized signals between freshly advertised peer threads. Cross-agent communication is off by default. Enable it with the "Experimental cross-agent communication" toggle in `/settings`, then restart Mastra Code. Embedded clients can enable it directly: ([#21986](https://github.com/mastra-ai/mastra/pull/21986)) ```typescript import { createMastraCode } from 'mastracode'; const mastraCode = await createMastraCode({ crossAgentSignals: true, }); ``` Use `agent_connections_list` to discover peers, `agent_connect` to save an exact peer endpoint, `agent_signal_send` to send a correlated message, and `agent_disconnect` to remove the saved connection. Sends require a currently advertised thread owner, return an error when delivery isn't acknowledged, and retain a bounded sender-side history so sequential retries can reuse the same `messageId`. A busy claimed owner acknowledges a safely queued wake immediately and delivers it after the active run finishes. - Added a storage contract for observability adapters to query thread identities using cross-trace predicates. ([#23558](https://github.com/mastra-ai/mastra/pull/23558)) - Added an optional per-file `mode` to `WorkspaceSandbox.writeFiles` inputs so callers can set POSIX permissions (`0o001`–`0o777`) when provisioning files. The Docker sandbox applies the requested mode to each uploaded file, falling back to `0644` when omitted. Sandboxes that cannot honor an explicit mode (Vercel, E2B, Daytona, Cloudflare) reject the request with `SandboxUnsupportedFeatureError` instead of silently ignoring it. Closes #23580. ([#23652](https://github.com/mastra-ai/mastra/pull/23652)) ```ts await sandbox.writeFiles([ { path: 'scripts/setup.sh', content: '#!/bin/sh\necho ready\n', mode: 0o755 }, { path: 'config/private.json', content: JSON.stringify({ token }), mode: 0o600 }, { path: 'README.md', content: 'Sandbox instructions' }, // defaults to 0644 ]); ``` `@mastra/core` now exports `validateSandboxFileMode`, `assertModesUnsupported`, and `SandboxUnsupportedFeatureError` for sandbox providers; the built-in providers' `@mastra/core` peer dependency floor is raised to `1.67.0` accordingly. - Added an optional `abortSignal` to the `WorkspaceSandbox.writeFiles` contract so file uploads can be cancelled. Also added a `SandboxAbortError` (code `ABORTED`) that providers throw when a write is cancelled. ([#23644](https://github.com/mastra-ai/mastra/pull/23644)) ```ts const controller = new AbortController(); await sandbox.writeFiles?.(files, { abortSignal: controller.signal }); ``` Observing the signal is provider-dependent. Providers that do not support cancellation ignore the option and run to completion. ### Patch Changes - Channel adapters can now discard the agent's buffered reply when a run is aborted. A new `onAbort: 'flush' | 'discard'` option on the adapter config controls what the static (non-streaming) driver does with not-yet-posted text on an `abort` chunk: `'flush'` (the default) posts the partial reply as before, and `'discard'` drops it so nothing is posted. This supports human-takeover flows on non-streaming platforms, where an in-flight agent reply should not appear as a truncated message beside the operator's own reply. ([#23655](https://github.com/mastra-ai/mastra/pull/23655)) ```ts const adapter = new MyChannelAdapter({ // ...existing config onAbort: 'discard', // drop buffered text on abort instead of posting it }); ``` Fixes #23640. - Fixed `Mastra.shutdown()` tearing down pub/sub before in-flight workflow runs could finish. Runs that were mid-step when `mastra.shutdown()` was called used to hang forever because the events they needed no longer had a consumer; durable agent runs were drained too late for the drain to help. ([#23168](https://github.com/mastra-ai/mastra/pull/23168)) `shutdown()` now waits for in-flight workflow runs (plain and durable agent) to reach a finished or suspended state before stopping workers, bounded by a new `drainTimeout` option (default 5 seconds). Workers also wait for events they are already processing before tearing down. The timeout is one shared deadline for the whole shutdown: the workflow drain, background task cancellation, and worker teardown all draw from it, so a stuck step can never hold `shutdown()` open for longer than `drainTimeout` before workspace and storage cleanup. A durable agent whose terminal error event cannot be published (for example because the pub/sub client is already closing) now logs a warning instead of surfacing an unhandled rejection. Note: the durable agent wait was previously unbounded. If you rely on `shutdown()` waiting longer than 5 seconds for durable agent runs, pass a larger `drainTimeout`. ```typescript await mastra.shutdown({ drainTimeout: 30_000 }); ``` Fixes https://github.com/mastra-ai/mastra/issues/22863 - Fix AgentController Session turning missing/unknown token counts into false zeros. Step-finish now skips fabricating, persisting, and emitting a usage update when a step reports no usable primary counts (prompt/completion/total all absent), instead of folding a `{0,0,0}` tally into the running total. `loadMetadata()` no longer calls `resetTokenUsage()` when a persisted metadata read fails transiently, so a measured tally is preserved instead of being destroyed by a read error. Fixes #23471. ([#23657](https://github.com/mastra-ai/mastra/pull/23657)) - Fixed background tasks whose initial dispatch is rejected by marking still-pending tasks as failed and freeing local capacity for queued work. Tasks already claimed by a worker are preserved when a transport reports an ambiguous publication failure. ([#23024](https://github.com/mastra-ai/mastra/pull/23024)) - Fixed execute_command to include stderr when commands exit successfully. (#23679) ([#23683](https://github.com/mastra-ai/mastra/pull/23683)) - Fixed durable agents continuing past a tool call that the client is meant to execute. ([#23612](https://github.com/mastra-ai/mastra/pull/23612)) A tool declared without `execute` runs on the client, which answers it on a follow-up request, so the run must end at the call. Durable agents instead recorded an empty result for it and called the model again — the agent answered as though the tool had returned nothing, and the client never received the call. ```ts const agent = new Agent({ model, durable: true, // No `execute`: the client runs this tool and sends the result back. tools: { approveInvoice: createTool({ id: 'approveInvoice', inputSchema }) }, }); // The turn now ends at the call so the client can answer it. const { output } = await agent.stream('Approve invoice 42'); ``` See [#23295](https://github.com/mastra-ai/mastra/issues/23295). - Fixed Unix socket pubsub clients hanging forever on startup when connecting to a broker from an older build that never acknowledges membership requests. Both `subscribe` and `unsubscribe` now proceed best-effort after a configurable timeout when no acknowledgement arrives, so newer clients no longer deadlock against legacy brokers. ([#23699](https://github.com/mastra-ai/mastra/pull/23699)) Configure the timeout via the new `membershipAckTimeoutMs` option (default 5000ms): ```ts const pubsub = new UnixSocketPubSub(socketPath, { membershipAckTimeoutMs: 5000 }); ``` - Improved background task execution across multiple processes. Invocation-bound tasks stay on the process that owns their executor, cancellation reliably reaches local work, and queued tasks continue after dispatch failures. ([#23024](https://github.com/mastra-ai/mastra/pull/23024)) **Stale task recovery** Startup recovery remains enabled by default. Disable it when multiple live managers share storage and task ownership cannot be verified across processes: ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, recoverStaleTasksOnStart: false, }, }); ``` - Fix background sub-agent tool approvals restarting the delegation instead of resuming it. When a sub-agent run as a background task suspends on a tool approval, the nested run id (carried in `suspendOptions.runId`) is now bridged into the background task's persisted suspend data, so `resume` restores it and the sub-agent continues its existing run and executes the approved tool — rather than starting a fresh run and re-suspending on the same approval. Fixes #23626. ([#23659](https://github.com/mastra-ai/mastra/pull/23659)) - Added consumer groups to `UnixSocketPubSub`. ([#23020](https://github.com/mastra-ai/mastra/pull/23020)) **Usage** ```typescript // Every ungrouped subscriber receives each message. await pubsub.subscribe('events', handleAllEvents); // One subscriber in the group receives each message. await pubsub.subscribe('events', handleWorkerEvent, { group: 'workers' }); ``` **Behavior** - Delivers each message once per named group and rotates delivery among available group members. - Restores group membership when a client reconnects. - Retries rejected local deliveries a bounded number of times. - Improved workspace `grep` and `list_files` tool performance on remote filesystems. The `list_files` tree walk now issues directory listings concurrently instead of one at a time, and `WorkspaceFilesystem` providers can implement optional `walk()` and `grep()` methods to run tree walks and content searches natively in a single call. The workspace tools use these capabilities automatically when available and fall back to the existing host-side walk otherwise, so a grep over a remote sandbox filesystem no longer needs one network round trip per directory and file. When a native capability fails and the tools fall back to the host-side walk, the downgrade is logged through the workspace logger (`info` for an unsupported grep pattern, `warn` for any other failure) so unexpected per-file round trips are visible. `Workspace` now exposes a read-only `logger` getter. Fixes https://github.com/mastra-ai/mastra/issues/22285 ([#22317](https://github.com/mastra-ai/mastra/pull/22317)) ```ts import { UnsupportedGrepPatternError } from '@mastra/core/workspace'; import type { WorkspaceFilesystem, WalkEntry, FilesystemGrepResult } from '@mastra/core/workspace'; class RemoteFilesystem implements WorkspaceFilesystem { // ...required methods... // Optional: return every entry under `path` in one call. async walk(path: string, options?: { maxDepth?: number; includeHidden?: boolean }): Promise { return this.api.listTree(path, options); } // Optional: run the search remotely. `column` must be a UTF-16 index. // Throw UnsupportedGrepPatternError to let the tool fall back to the host walk. async grep(options: { pattern: string; path: string; caseSensitive: boolean }): Promise { if (!this.api.supportsRegex(options.pattern)) throw new UnsupportedGrepPatternError(options.pattern); return this.api.search(options); } } ``` ## 1.67.0-alpha.2 ### Minor Changes - Add thread ownership transfer (resourceId reassignment). ([#23533](https://github.com/mastra-ai/mastra/pull/23533)) You can now transfer an existing thread to a different resource, reassigning both the thread and its messages to the new `resourceId` while preserving the thread's original `createdAt` timestamp. This supports scenarios like moving a private thread into a shared workspace without the previous upsert workaround. - `@mastra/core` / `@mastra/memory`: new `Memory.updateThreadResourceId({ threadId, resourceId })` method, backed by a default `MemoryStorage.updateThreadResourceId` implementation. When semantic recall is enabled, the message vectors are migrated to the new `resourceId` so resource-scoped retrieval keeps surfacing the transferred thread. - `@mastra/server`: new `POST /memory/threads/:threadId/transfer` route. The endpoint is restricted to privileged, non-resource-scoped callers and rejects requests made with a resolved resource scope. - `@mastra/client-js`: new `MemoryThread.transfer({ resourceId })` method. - `@mastra/pg`, `@mastra/libsql`, `@mastra/mssql`, `@mastra/dsql`, `@mastra/oracledb`, `@mastra/mysql`, `@mastra/spanner`: atomic, serialized `updateThreadResourceId` overrides. The thread and all of its messages are moved inside a single transaction, so overlapping transfers of the same thread cannot interleave and leave split ownership. Postgres, MySQL, SQL Server and Oracle take a row lock (`SELECT ... FOR UPDATE` / `UPDLOCK, HOLDLOCK`); libSQL and Spanner serialize their write transactions; Aurora DSQL relies on its optimistic concurrency control with automatic retry. Adapters without a transaction primitive fall back to the base best-effort implementation, which fails closed by reverting on error. ```typescript // Server-side, from a privileged (non-resource-scoped) context: const thread = await memory.updateThreadResourceId({ threadId: 'thread-123', resourceId: 'new-resource-456', }); // Client-side: const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); const thread = client.getMemoryThread('thread-123', 'agent-id'); await thread.transfer({ resourceId: 'new-resource-456' }); ``` ### Patch Changes - Fixed browser providers signalling a remote browser's PID on the host machine. When a provider connected to an existing browser over `cdpUrl` (or a Firecrawl/Browserbase cloud session), it captured the remote browser's PID and, on disconnect, ran `process.kill(-pid, 'SIGKILL')` locally. That PID belongs to another host or container, so the signal hit an unrelated local process group — and when the remote Chromium was its container's PID 1, `kill(-1)` broadcast SIGKILL to every process the Mastra user owned. ([#23599](https://github.com/mastra-ai/mastra/pull/23599)) Providers now skip PID capture whenever the browser was reached over CDP, so there is nothing to signal for browsers we do not own. As defense in depth, `killProcessGroup` in `@mastra/core` now refuses any PID that cannot name a killable local process group (non-integer, negative, `0`, or `1`). Locally launched browsers are unaffected and still have their process group cleaned up. - Improved core primitive declarations with concise usage examples and directions to bundled documentation. ([#23489](https://github.com/mastra-ai/mastra/pull/23489)) - Fixed workflow retry counts leaking across concurrent runs and foreach items. ([#23593](https://github.com/mastra-ai/mastra/pull/23593)) - Durable agents attached to a chat channel now render their final answer after a tool call. Previously the first tool step ended channel rendering, so the tool cards posted but the answer that followed was silently dropped — the run still reported success and saved the message to the thread. Any output processor reading `stepResult.isContinued` on `step-finish` chunks now receives it on durable runs too, matching regular agent runs, including when a tool error makes the loop continue past a model `stop`. Fixes #23341. ([#23610](https://github.com/mastra-ai/mastra/pull/23610)) - Scorer judge configuration now accepts an optional `modelSettings` field (temperature, topP, topK, maxOutputTokens, maxRetries, frequencyPenalty, presencePenalty, timeout, etc.), forwarded to the internal judge agent run. It can be set at the scorer level and overridden per step, removing the need for an input-processor workaround. Closes #23458. ([#23637](https://github.com/mastra-ai/mastra/pull/23637)) ```ts const scorer = createScorer({ id: 'answer-relevancy', description: 'Scores answer relevancy', judge: { model: openai('gpt-4o'), instructions: 'Return a relevancy score.', // New: configure the judge model call directly modelSettings: { temperature: 0, maxRetries: 3 }, }, }) .analyze({ description: 'analyze', outputSchema: z.object({ value: z.number() }), createPrompt: () => 'analyze this', // Optional per-step override (replaces the scorer-level value) judge: { modelSettings: { temperature: 0.7 } }, }) .generateScore(({ results }) => results.analyzeStepResult.value); ``` - Add an opt-in `injectCatalog` option to `ToolSearchProcessor`. When enabled, the available-tool catalog (each tool's name and a short description) is injected into the system prompt so the agent can skip the `search_tools` turn and call `load_tool` directly, collapsing the default `search -> load -> use` (3 turns) into `load -> use` (2 turns). `search_tools` stays available as a keyword fallback, injected entries respect the `filter` hook, and the option defaults to `false` so existing behavior is unchanged. Best suited to small/medium tool sets where listing the catalog inline is cheaper than a discovery round-trip. Closes #16463. ([#23578](https://github.com/mastra-ai/mastra/pull/23578)) - Fixed live agent scores being dropped when tracing is enabled. Durable agents now save scorer results, and scores link to the exported agent span instead of a hidden workflow step span. (#23465) ([#23600](https://github.com/mastra-ai/mastra/pull/23600)) ## 1.67.0-alpha.1 ### Minor Changes - Restored reactive and system-reminder signals in live streams by default. Added caller-local `hideSignals` to modern agent streams, resume/until-idle streams, and thread subscriptions. Exclusions leave model context, persistence, transforms, and other subscribers unchanged. ([#23554](https://github.com/mastra-ai/mastra/pull/23554)) ```ts // Before: reminders were hidden from every live consumer. const output = await agent.stream('Continue'); // Now: reminders are visible by default; opt out for this caller only. const filtered = await agent.stream('Continue', { hideSignals: ['reactive', 'system-reminder'], }); const subscription = await agent.subscribeToThread({ threadId: 'thread-1', resourceId: 'user-1', hideSignals: ['reactive'], }); ``` Set `hideSignals: true` to hide all recognized signals, `false` or `[]` to show all, or an array to hide selected types. Stream exclusions normalize legacy aliases. They filter streamed signal chunks after transforms, not aggregate output, and are not a security boundary. Shared execution options also accept `hideSignals` on `generate()` and `resumeGenerate()` without filtering their returned results. HTTP/client-js options are unchanged. Fixed directory-scoped instruction discovery so completed file operations load the nearest `AGENTS.md` into the next model request. Repeated operations in the same directory no longer load duplicate instructions, aliased paths resolve to the same instructions, and operations with multiple path fields can discover instructions for each destination. - Added `hideSignals` to the core memory recall contract so memory implementations and callers can explicitly control which stored signal messages are returned. Omitted exclusions preserve the existing reminder-hidden history default, and explicit visibility settings take precedence over the deprecated `includeSystemReminders` option. ([#23554](https://github.com/mastra-ai/mastra/pull/23554)) ```ts await memory.recall({ threadId: 'thread-1', hideSignals: false }); await memory.recall({ threadId: 'thread-1', hideSignals: true }); await memory.recall({ threadId: 'thread-1', hideSignals: ['reactive', 'system-reminder'], }); ``` Use `true` to hide all recognized signal types, `false` or `[]` to include all, or an array to hide selected exact stored types. Legacy reminder rows without a recognized encoded signal type match `system-reminder`. Returned-message filtering preserves pagination totals, raw storage, ordinary messages, and model context. - Added `delegation.enableResultReferences` so a later subagent delegation can reuse an earlier subagent's result verbatim, instead of the supervisor restating it. When enabled, each successful foreground subagent result ends with a `[ref: ]` line (for example `explorer-1`), and every delegation tool gains a `contextFromRefs` input that inserts the referenced results ahead of the prompt. Rejected, failed, empty, and background-task delegations don't get a reference. Off by default; when it's off the tool schemas and model output are unchanged. ([#22940](https://github.com/mastra-ai/mastra/pull/22940)) ```ts const supervisor = new Agent({ id: 'supervisor', instructions: 'Delegate research to explorer, then hand the findings to implementer.', model: 'openai/gpt-5', agents: { explorer, implementer }, }); await supervisor.generate('Find and fix the token refresh bug', { delegation: { enableResultReferences: true }, }); // 1. agent-explorer → "...expiry check uses `<` instead of `<=`.\n\n[ref: explorer-1]" // 2. agent-implementer({ prompt: 'Fix the bug', contextFromRefs: ['explorer-1'] }) // receives the explorer's text verbatim before the prompt. ``` Referenced text is subagent output. If subagents handle untrusted input, validate or rewrite `resultText` in `onDelegationComplete` (or through processors) before it can be referenced. Closes https://github.com/mastra-ai/mastra/issues/22910 ### Patch Changes - A workflow `.agent()` step that declares `structuredOutput.schema` now fails when the agent finishes without producing an object, instead of silently reporting `success` and returning `{ text }`. The step throws a `MastraError` (`STRUCTURED_OUTPUT_OBJECT_UNDEFINED`) carrying the `finishReason`, matching how the rest of the agent stack guards missing structured output. A validly-parsed falsy object (e.g. `0`) is still treated as produced, and steps without a declared schema are unaffected. Fixes #23403. ([#23559](https://github.com/mastra-ai/mastra/pull/23559)) - Sampling settings that a model does not accept are now left out of the request for models passed in directly and for models hosted on Amazon Bedrock. Previously these settings were only removed for models referenced by name, so directly-passed and Bedrock-hosted models could fail their requests. Fixes #23319. ([#23549](https://github.com/mastra-ai/mastra/pull/23549)) - Avoid loading every message payload into the Node heap when cloning a thread for a forked subagent. ([#23567](https://github.com/mastra-ai/mastra/pull/23567)) `memory.cloneThread()` previously parsed and accumulated all source message payloads into memory, then discarded them on the fork path where only the new thread id is needed. `StorageCloneThreadInput.options` now accepts `hydrateMessages` (default `true`); when `false`, the LibSQL, Postgres, and in-memory adapters copy message rows inside the database via `INSERT … SELECT` and return an empty `clonedMessages` array. `Memory.cloneThread` re-enables hydration when semantic recall is active so embeddings still work, and forked subagents now clone with `hydrateMessages: false`. Fixes #23434. Callers can opt into lazy hydration directly: ```ts const { messageIdMap } = await memory.cloneThread({ sourceThreadId, newThreadId, resourceId, options: { hydrateMessages: false }, }); ``` - Fixed sub-agent delegation so processed supervisor context remains available without exposing observational memory control messages or rerunning observational memory for inherited memory. ([#21929](https://github.com/mastra-ai/mastra/pull/21929)) - Fixed a stuck agent run when a message is sent immediately after aborting. Calling `abort()` (or `steer()`) and sending another message right away now correctly starts a fresh, observable run instead of losing its start/end events and leaving the session stuck in a running state. ([#23565](https://github.com/mastra-ai/mastra/pull/23565)) - Improved streaming performance for output processors by avoiding unnecessary workflow runs for each chunk. ([#23435](https://github.com/mastra-ai/mastra/pull/23435)) - Fixed a fractional `tail` value producing a truncation notice that disagreed with the returned output. The `execute_command` and `get_process_output` tools now reject a non-integer `tail`, and the `[showing last N of M lines]` notice always reports a whole number equal to the lines actually returned. ([#23570](https://github.com/mastra-ai/mastra/pull/23570)) - Fix network completion scoring to honor its timeout as a hard deadline. Previously `runCompletionScorers` awaited every scorer to settle after the deadline, so a slow scorer could return a late passing verdict with `complete: true`, a never-settling scorer could hang the call indefinitely, and the default timeout timer was never cleared. Parallel and sequential scoring now race each scorer against a single shared deadline, return promptly when it elapses, preserve the evidence of scorers that already finished, mark unfinished checks as errored failures, prevent late results from changing the returned verdict, and clear the timer on completion. Non-Error scorer rejections (`null`, `undefined`, strings, plain objects, and objects with throwing `message` getters) are now converted into explicit scorer failures instead of crashing the error handler or losing their reason. Fixes #23449. ([#23564](https://github.com/mastra-ai/mastra/pull/23564)) - Fixed processor step `modelSettings` types so they accept the `timeout` field (`stepMs`, `totalMs`, `firstChunkMs`) that Mastra already applies at runtime. Input processors can now return `modelSettings.timeout` inline without a TypeScript error, and reading `timeout` from step arguments type-checks correctly. ([#23560](https://github.com/mastra-ai/mastra/pull/23560)) - Include token `usage` diagnostics in the `STRUCTURED_OUTPUT_OBJECT_UNDEFINED` error thrown when a workflow agent step declares a structured-output schema but produces no object, so loggers and workflow error consumers retain the finish result's usage data. ([#23563](https://github.com/mastra-ai/mastra/pull/23563)) - Workflow control-flow spans now carry the identity of the graph entry that created them, so observability tools can correlate a span with the authored operation instead of inferring it from structure. ([#23542](https://github.com/mastra-ai/mastra/pull/23542)) - `.parallel()`, `.branch()`, `.dowhile()`, `.dountil()`, and `.foreach()` container spans use the authored entry `id` for their display name (e.g. `parallel: 'check-document'`) and expose the entry `id`, `description`, and `metadata` as span attributes (`entryId`, `entryDescription`, `entryMetadata`). Spans without an authored `id` keep their previous structural name as a fallback. - `.sleep()` and `.sleepUntil()` spans keep their descriptive duration/date names and now expose the same identity attributes. - `.map()` step spans now forward the entry `description` and `metadata` (they already exposed the entry `id`). For example, given: ```ts workflow.parallel([checkSpelling, checkGrammar], { id: 'check-document', metadata: { title: 'Check document' }, }); ``` the container span is now named `parallel: 'check-document'` and its attributes include `entryId: 'check-document'` and `entryMetadata: { title: 'Check document' }`, letting an exporter display "Check document" and distinguish it from other parallel groups with the same branch count. - Updated dependencies [[`80608ed`](https://github.com/mastra-ai/mastra/commit/80608ede1a9e5d7d8488ac511245bf327e8987e3)]: - @mastra/schema-compat@1.3.10-alpha.0 ## 1.66.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`e86be03`](https://github.com/mastra-ai/mastra/commit/e86be034c017fca7deae7d1ebb34d36413928cb8)) - Fixed file attachment names being lost when stored messages are converted to AI SDK v5 UI messages. ([#23523](https://github.com/mastra-ai/mastra/pull/23523)) - Only emit the "logger already wired to another Mastra instance" re-attach warning when `loggerOptions.export` is enabled. With `export: false` there is no observability export target to clobber, so attaching a shared logger to multiple `Mastra` instances no longer prints a spurious warning. ([#23544](https://github.com/mastra-ai/mastra/pull/23544)) - Fixed workspace tool output truncation producing invalid Unicode when cutting through emoji. ([#23431](https://github.com/mastra-ai/mastra/pull/23431)) ## 1.66.0 ### Minor Changes - Added trace filters for span names, model providers, timing, outcomes, identity, and version lineage. ([#23018](https://github.com/mastra-ai/mastra/pull/23018)) ```ts await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { spans: { some: { op: 'eq', left: { path: 'name' }, right: { literal: 'medication_lookup' } } } }, }); ``` - Added deleteFeedback() and deleteScores() to observability storage. Both accept a batch of ids and optional organizationId / resourceId tenant scope, are idempotent, and are implemented by the in-memory store. Storage adapters that don't implement them throw a not-implemented error. ([#22558](https://github.com/mastra-ai/mastra/pull/22558)) ```typescript await observability.deleteFeedback({ feedbackIds: ['feedback-1'] }); await observability.deleteScores({ scoreIds: ['score-1'], organizationId: 'org-1' }); ``` - Added typed `input` and `output` payloads for the spans Mastra records itself: `AGENT_RUN`, `MODEL_GENERATION`, `MODEL_STEP` and `MODEL_INFERENCE`. Every other span type keeps `any`. Stored spans narrow the same way with `isSpanRecordOfType`, which types `attributes`, `input` and `output` without a cast: ([#23141](https://github.com/mastra-ai/mastra/pull/23141)) ```ts import { SpanType, isSpanRecordOfType } from '@mastra/core/observability'; if (isSpanRecordOfType(span, SpanType.MODEL_GENERATION)) { span.attributes?.usage; // UsageStats | undefined span.input?.messages; // MessageListInput } ``` A resumed agent run now always records its resume data as an object on the span input, wrapping a primitive or array under `resumeData` the way it already did when the suspended tool was known. For rendering, `describeSpanInput` and `describeSpanOutput` return the payload tagged by what it holds (`messages`, `agent-run-resume`, `interrupted`, `model-generation-result`, `json`, ...), so a UI can switch on `type` instead of checking shapes. The tag is derived at read time and never stored. `describeSpanError` returns the span's error info, typed. - Added portable top-level string metadata predicates to advanced trace queries. Invalid metadata keys and values are rejected consistently, and valid predicates work inside recursive Boolean expressions. ([#23027](https://github.com/mastra-ai/mastra/pull/23027)) ```ts await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z', }, where: { op: 'eq', left: { path: 'metadata.messageId' }, right: { literal: 'message-123' } }, }); ``` - Added feedback predicates to advanced trace queries. ([#23033](https://github.com/mastra-ai/mastra/pull/23033)) ```typescript await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { feedback: { some: { op: 'lt', left: { path: 'value' }, right: { literal: 0 } } } }, }); ``` - Added `modelSettings.timeout.firstChunkMs` so you can bound how long a streaming model call may take to produce its first content. Stream-start and metadata chunks don't count; the budget is only satisfied by the first text, reasoning, tool call, file or source chunk. Going over the limit fails with a `MastraTimeoutError` whose `timeoutType` is `'firstChunk'`. Like `stepMs`, it's not retried against the same model but does move on to the next entry in `models` when fallback models are configured, and each provider retry attempt gets a fresh budget. Nested `timeout` settings are now merged per key across call-time and per-model `modelSettings`, so a per-model `stepMs` override no longer discards a call-time `firstChunkMs`. Closes #23072 ([#23090](https://github.com/mastra-ai/mastra/pull/23090)) - Added `Mastra.getWorkerConfig()` for reporting the active worker topology and serializable instance-level settings so deployment tools can compare runtime and build-time configuration. ([#22394](https://github.com/mastra-ai/mastra/pull/22394)) ```ts const workerConfig = mastra.getWorkerConfig(); ``` - Added richer `scores.some` and `scores.none` predicates for scorer versions, sources, timestamps, span anchoring, and version lineage. ([#22956](https://github.com/mastra-ai/mastra/pull/22956)) ```ts await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { scores: { some: { op: 'eq', left: { path: 'scorerVersion' }, right: { literal: '2.1.0' } } } }, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7eda39b`](https://github.com/mastra-ai/mastra/commit/7eda39bd17356b9985ae44e663ccde30ff0fedea)) - Improved suspended run discovery to retain fewer workflow snapshots while processing large sets of suspended runs. ([#23506](https://github.com/mastra-ai/mastra/pull/23506)) - Text and reasoning spans now fold into message parts through one shared unit, used by both the live agent-controller message and the persisted message builder. The live message gains the redacted reasoning parts it ignored and the empty reasoning parts OpenAI needs for `item_reference`, no longer draws an empty text part for a text block that never streamed, and keeps provider metadata off its parts since nothing live sends them back to a provider, so a running turn and its stored copy agree on every visible part. ([#23310](https://github.com/mastra-ai/mastra/pull/23310)) - Fixed Windows LocalSandbox sh -c commands losing shell operators and argument boundaries. ([#23145](https://github.com/mastra-ai/mastra/pull/23145)) - Removed redundant type assertions in workflow execution without changing runtime behavior or public types. ([#23502](https://github.com/mastra-ai/mastra/pull/23502)) - Fixed a terminal provider error leaving an unresolved provider-executed tool call in the final message list. When a model stream ends with an error before a provider-executed tool (e.g. Anthropic web_search) returns its result, the abandoned tool call is now reconciled to an `output-error` state instead of remaining a dangling pending `call`. This preserves the original error, keeps successful tool results and surrounding text intact, and prevents observational memory from being deferred forever by an orphaned call. ([#23356](https://github.com/mastra-ai/mastra/pull/23356)) - Removed redundant type assertions in storage and channels without changing runtime behavior or public types. ([#23503](https://github.com/mastra-ai/mastra/pull/23503)) - Fixed live agent trajectory scorers to receive extracted trajectories and save their results. ([#23490](https://github.com/mastra-ai/mastra/pull/23490)) - Added storage-backed pagination, ordering, filtering, and message includes to Agent Controller message listing. Existing Session and numeric client APIs continue to return message arrays for compatibility. ([#22977](https://github.com/mastra-ai/mastra/pull/22977)) ```ts const page = await session.listMessages('thread-id', { perPage: 20, page: 0 }); ``` - Fixed `Agent.stream()` rebuilding the observability logger and metrics contexts for every streamed chunk when tracing is enabled. The context is now resolved once per model step and reused, so tracing overhead no longer grows with the number of chunks in a response. Fixes #23198. ([#23251](https://github.com/mastra-ai/mastra/pull/23251)) - Added a `tags` filter to `dataset.listExperimentResults()` and `GET /api/datasets/:datasetId/experiments/:experimentId/results`. Only results that carry every listed tag are returned; results with extra tags still match. ([#23311](https://github.com/mastra-ai/mastra/pull/23311)) ```ts const { results } = await dataset.listExperimentResults({ experimentId: 'exp-id', tags: ['regression', 'p0'], }); ``` Over HTTP, pass repeated query params: `?tags=regression&tags=p0`. - Added an optional `includeTotal` flag to message listing. Internal message-only memory reads now disable totals so PostgresStore skips counting all matching messages. For paginated reads, it fetches one extra row to determine `hasMore`. The flag defaults to `true`, preserving accurate totals for existing callers and Studio pagination. ([#23389](https://github.com/mastra-ai/mastra/pull/23389)) ```typescript const memoryStore = await storage.getStore('memory'); if (!memoryStore) throw new Error('Memory storage is unavailable'); // Default: include an accurate total. await memoryStore.listMessages({ threadId: 'thread-1', perPage: 20 }); // Skip the total when only messages and hasMore are needed. const { messages, hasMore } = await memoryStore.listMessages({ threadId: 'thread-1', perPage: 20, includeTotal: false, }); ``` - Use the Mastra Platform bot for default coding-agent commit attribution. ([#23418](https://github.com/mastra-ai/mastra/pull/23418)) - Fixed agent trajectories dropping thrown tool calls and using display labels as tool names. Trajectories now keep failed calls with success:false and prefer canonical tool identity from entityId/entityName. (#23462) ([#23511](https://github.com/mastra-ai/mastra/pull/23511)) - Removed redundant type assertions without changing runtime behavior or public types. ([#23501](https://github.com/mastra-ai/mastra/pull/23501)) - Fixed persisted-signal thread broadcasts emitting a `start` chunk without `from` and `payload`. Threads created only from persisted signals (`sendSignal(..., { ifIdle: { behavior: 'persist' } })`) now stream a `start` chunk shaped like every other agent run, so chunk consumers such as Mastra Studio no longer crash when replaying them. Fixes https://github.com/mastra-ai/mastra/issues/23244 ([#23282](https://github.com/mastra-ai/mastra/pull/23282)) ## 1.66.0-alpha.4 ### Minor Changes - Added feedback predicates to advanced trace queries. ([#23033](https://github.com/mastra-ai/mastra/pull/23033)) ```typescript await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { feedback: { some: { op: 'lt', left: { path: 'value' }, right: { literal: 0 } } } }, }); ``` - Added `Mastra.getWorkerConfig()` for reporting the active worker topology and serializable instance-level settings so deployment tools can compare runtime and build-time configuration. ([#22394](https://github.com/mastra-ai/mastra/pull/22394)) ```ts const workerConfig = mastra.getWorkerConfig(); ``` ### Patch Changes - Text and reasoning spans now fold into message parts through one shared unit, used by both the live agent-controller message and the persisted message builder. The live message gains the redacted reasoning parts it ignored and the empty reasoning parts OpenAI needs for `item_reference`, no longer draws an empty text part for a text block that never streamed, and keeps provider metadata off its parts since nothing live sends them back to a provider, so a running turn and its stored copy agree on every visible part. ([#23310](https://github.com/mastra-ai/mastra/pull/23310)) - Added storage-backed pagination, ordering, filtering, and message includes to Agent Controller message listing. Existing Session and numeric client APIs continue to return message arrays for compatibility. ([#22977](https://github.com/mastra-ai/mastra/pull/22977)) ```ts const page = await session.listMessages('thread-id', { perPage: 20, page: 0 }); ``` - Fixed agent trajectories dropping thrown tool calls and using display labels as tool names. Trajectories now keep failed calls with success:false and prefer canonical tool identity from entityId/entityName. (#23462) ([#23511](https://github.com/mastra-ai/mastra/pull/23511)) ## 1.66.0-alpha.3 ### Minor Changes - Added typed `input` and `output` payloads for the spans Mastra records itself: `AGENT_RUN`, `MODEL_GENERATION`, `MODEL_STEP` and `MODEL_INFERENCE`. Every other span type keeps `any`. Stored spans narrow the same way with `isSpanRecordOfType`, which types `attributes`, `input` and `output` without a cast: ([#23141](https://github.com/mastra-ai/mastra/pull/23141)) ```ts import { SpanType, isSpanRecordOfType } from '@mastra/core/observability'; if (isSpanRecordOfType(span, SpanType.MODEL_GENERATION)) { span.attributes?.usage; // UsageStats | undefined span.input?.messages; // MessageListInput } ``` A resumed agent run now always records its resume data as an object on the span input, wrapping a primitive or array under `resumeData` the way it already did when the suspended tool was known. For rendering, `describeSpanInput` and `describeSpanOutput` return the payload tagged by what it holds (`messages`, `agent-run-resume`, `interrupted`, `model-generation-result`, `json`, ...), so a UI can switch on `type` instead of checking shapes. The tag is derived at read time and never stored. `describeSpanError` returns the span's error info, typed. ### Patch Changes - Improved suspended run discovery to retain fewer workflow snapshots while processing large sets of suspended runs. ([#23506](https://github.com/mastra-ai/mastra/pull/23506)) - Removed redundant type assertions in workflow execution without changing runtime behavior or public types. ([#23502](https://github.com/mastra-ai/mastra/pull/23502)) - Removed redundant type assertions in storage and channels without changing runtime behavior or public types. ([#23503](https://github.com/mastra-ai/mastra/pull/23503)) - Fixed live agent trajectory scorers to receive extracted trajectories and save their results. ([#23490](https://github.com/mastra-ai/mastra/pull/23490)) - Removed redundant type assertions without changing runtime behavior or public types. ([#23501](https://github.com/mastra-ai/mastra/pull/23501)) ## 1.66.0-alpha.2 ### Minor Changes - Added deleteFeedback() and deleteScores() to observability storage. Both accept a batch of ids and optional organizationId / resourceId tenant scope, are idempotent, and are implemented by the in-memory store. Storage adapters that don't implement them throw a not-implemented error. ([#22558](https://github.com/mastra-ai/mastra/pull/22558)) ```typescript await observability.deleteFeedback({ feedbackIds: ['feedback-1'] }); await observability.deleteScores({ scoreIds: ['score-1'], organizationId: 'org-1' }); ``` ### Patch Changes - Fixed a terminal provider error leaving an unresolved provider-executed tool call in the final message list. When a model stream ends with an error before a provider-executed tool (e.g. Anthropic web_search) returns its result, the abandoned tool call is now reconciled to an `output-error` state instead of remaining a dangling pending `call`. This preserves the original error, keeps successful tool results and surrounding text intact, and prevents observational memory from being deferred forever by an orphaned call. ([#23356](https://github.com/mastra-ai/mastra/pull/23356)) ## 1.66.0-alpha.1 ### Minor Changes - Added trace filters for span names, model providers, timing, outcomes, identity, and version lineage. ([#23018](https://github.com/mastra-ai/mastra/pull/23018)) ```ts await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { spans: { some: { op: 'eq', left: { path: 'name' }, right: { literal: 'medication_lookup' } } } }, }); ``` - Added portable top-level string metadata predicates to advanced trace queries. Invalid metadata keys and values are rejected consistently, and valid predicates work inside recursive Boolean expressions. ([#23027](https://github.com/mastra-ai/mastra/pull/23027)) ```ts await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z', }, where: { op: 'eq', left: { path: 'metadata.messageId' }, right: { literal: 'message-123' } }, }); ``` - Added richer `scores.some` and `scores.none` predicates for scorer versions, sources, timestamps, span anchoring, and version lineage. ([#22956](https://github.com/mastra-ai/mastra/pull/22956)) ```ts await mastraClient.queryTraces({ timeRange: { from: '2026-08-01T00:00:00.000Z', to: '2026-08-08T00:00:00.000Z' }, where: { scores: { some: { op: 'eq', left: { path: 'scorerVersion' }, right: { literal: '2.1.0' } } } }, }); ``` ### Patch Changes - Added a `tags` filter to `dataset.listExperimentResults()` and `GET /api/datasets/:datasetId/experiments/:experimentId/results`. Only results that carry every listed tag are returned; results with extra tags still match. ([#23311](https://github.com/mastra-ai/mastra/pull/23311)) ```ts const { results } = await dataset.listExperimentResults({ experimentId: 'exp-id', tags: ['regression', 'p0'], }); ``` Over HTTP, pass repeated query params: `?tags=regression&tags=p0`. ## 1.66.0-alpha.0 ### Minor Changes - Added `modelSettings.timeout.firstChunkMs` so you can bound how long a streaming model call may take to produce its first content. Stream-start and metadata chunks don't count; the budget is only satisfied by the first text, reasoning, tool call, file or source chunk. Going over the limit fails with a `MastraTimeoutError` whose `timeoutType` is `'firstChunk'`. Like `stepMs`, it's not retried against the same model but does move on to the next entry in `models` when fallback models are configured, and each provider retry attempt gets a fresh budget. Nested `timeout` settings are now merged per key across call-time and per-model `modelSettings`, so a per-model `stepMs` override no longer discards a call-time `firstChunkMs`. Closes #23072 ([#23090](https://github.com/mastra-ai/mastra/pull/23090)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7eda39b`](https://github.com/mastra-ai/mastra/commit/7eda39bd17356b9985ae44e663ccde30ff0fedea)) - Fixed Windows LocalSandbox sh -c commands losing shell operators and argument boundaries. ([#23145](https://github.com/mastra-ai/mastra/pull/23145)) - Fixed `Agent.stream()` rebuilding the observability logger and metrics contexts for every streamed chunk when tracing is enabled. The context is now resolved once per model step and reused, so tracing overhead no longer grows with the number of chunks in a response. Fixes #23198. ([#23251](https://github.com/mastra-ai/mastra/pull/23251)) - Added an optional `includeTotal` flag to message listing. Internal message-only memory reads now disable totals so PostgresStore skips counting all matching messages. For paginated reads, it fetches one extra row to determine `hasMore`. The flag defaults to `true`, preserving accurate totals for existing callers and Studio pagination. ([#23389](https://github.com/mastra-ai/mastra/pull/23389)) ```typescript const memoryStore = await storage.getStore('memory'); if (!memoryStore) throw new Error('Memory storage is unavailable'); // Default: include an accurate total. await memoryStore.listMessages({ threadId: 'thread-1', perPage: 20 }); // Skip the total when only messages and hasMore are needed. const { messages, hasMore } = await memoryStore.listMessages({ threadId: 'thread-1', perPage: 20, includeTotal: false, }); ``` - Use the Mastra Platform bot for default coding-agent commit attribution. ([#23418](https://github.com/mastra-ai/mastra/pull/23418)) - Fixed persisted-signal thread broadcasts emitting a `start` chunk without `from` and `payload`. Threads created only from persisted signals (`sendSignal(..., { ifIdle: { behavior: 'persist' } })`) now stream a `start` chunk shaped like every other agent run, so chunk consumers such as Mastra Studio no longer crash when replaying them. Fixes https://github.com/mastra-ai/mastra/issues/23244 ([#23282](https://github.com/mastra-ai/mastra/pull/23282)) ## 1.65.0 ### Minor Changes - Added tenant-scoped trace deletion arguments for observability storage with a limit of 1,000 trace IDs per batch. ([#22553](https://github.com/mastra-ai/mastra/pull/22553)) ```typescript await storage.batchDeleteTraces({ traceIds: ['trace-1'], organizationId: 'org-1' }); ``` - Added the core contract for advanced trace queries, including bounded time ranges, recursive trace, span, and score predicates, thread grouping, and deterministic cursor pagination. Invalid or overly complex requests are rejected before a storage adapter executes them. ([#22726](https://github.com/mastra-ai/mastra/pull/22726)) ```ts const request = traceQueryRequestSchema.parse({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, where: { op: 'eq', left: { path: 'environment' }, right: { literal: 'production' } }, }); ``` - Added optional `id`, `description`, and `metadata` to workflow control-flow entries: `.parallel()`, `.branch()`, `.dowhile()`, `.dountil()`, `.foreach()`, `.sleep()`, `.sleepUntil()`, and `.map()`. Executable steps already supported these fields; the entries between them now follow the same model, so visual editors and review tools can label a parallel block or a sleep and address it with a stable id instead of a generated one or a position in the graph. ([#22633](https://github.com/mastra-ai/mastra/pull/22633)) ```typescript workflow .parallel([validateStep, enrichStep], { id: 'independent-enrichment', description: 'Run independent enrichment tasks concurrently', metadata: { title: 'Independent enrichment' }, }) .sleep(5000, { id: 'wait-before-retry', metadata: { title: 'Wait before retry' } }); ``` The fields appear in `serializedStepGraph`, survive storage and rehydration of dynamic workflow definitions, and have no effect on execution. For `.map()`, `.sleep()`, and `.sleepUntil()`, a supplied `id` replaces the generated entry id. - `dataset.deleteExperiment()` now also deletes the observability traces the experiment produced, cascading to their spans and trace-linked scores, feedback, metrics and logs. Experiment traces are excluded from normal trace reads, so leaving them behind kept data that was invisible but still retained. ([#22550](https://github.com/mastra-ai/mastra/pull/22550)) `mastra.datasets.deleteExperiment()` is new and does the same thing without requiring the experiment to still belong to a dataset, so experiments orphaned by dataset deletion can be cleaned up. ```ts // Both delete the experiment, its results, and its traces. await dataset.deleteExperiment({ experimentId }); await mastra.datasets.deleteExperiment({ experimentId }); ``` Stores without an observability domain (or without tenant-scoped trace deletion) log a warning and skip the trace cascade so the experiment is still deleted. - Added `handlers.onAction` to agent channels so apps can handle button clicks and select changes from their own cards. The built-in tool approval handling is available as `defaultHandler`, and `onAction: false` disables it. Fixes #22629 ([#22927](https://github.com/mastra-ai/mastra/pull/22927)) ```ts const agent = new Agent({ // ... channels: { adapters: { slack: createSlackAdapter() }, handlers: { onAction: async (event, defaultHandler) => { if (event.actionId === 'retry') { await event.thread?.post('Retrying...'); return; } await defaultHandler(); }, }, }, }); ``` - Traces now show Mastra's built-in add-ons as the subsystem they came from, instead of anonymous processor runs. ([#22542](https://github.com/mastra-ai/mastra/pull/22542)) Skills, workspace instructions, observational memory and agent state signals all run on the processor pipeline, but you configure `skills`, `workspace`, `memory` and `signals` — not processors. Their spans were named after a pipeline phase you never chose: | Was | Now | | -------------------------------------------------------- | ------------------------------ | | `input step processor: skills-processor` | `skill:inject` | | `input step processor: workspace-instructions-processor` | `workspace:mount:instructions` | | `input step processor: observational-memory` | `memory: recall` | **New span types** - `SKILL_ACTION` covers the whole skill lifecycle — resolve, inject, activate, search, read. `SKILL_RESOLUTION` is deprecated and no longer emitted. - `AGENT_SIGNAL` records each state signal emission as a point-in-time event. A turn where the lane computes no change records nothing. **Tracing your own processors** Any processor can declare how it is traced, and one that declares nothing is unchanged: ```ts import { SpanType } from '@mastra/core/observability'; import type { Processor, ProcessorSpanPhase } from '@mastra/core/processors'; class MyProcessor implements Processor<'my-processor'> { readonly id = 'my-processor' as const; readonly spanType = SpanType.MEMORY_OPERATION; readonly spanName = (phase: ProcessorSpanPhase) => `memory: ${phase === 'inputStep' ? 'recall' : 'save'}`; readonly spanAttributes = { operationType: 'recall' } as const; } ``` **Fixes** - The skills processor reports `skillCount` on every run. A skills path that resolved to nothing previously produced no span at all; it now shows as `skillCount: 0`. - `computeStateSignal` implementations receive the `tracingContext` their argument type always advertised but never passed. - Added `dataset.updateExperiment()` to rename an experiment or change its description and metadata after it has been created. Status and result counters remain managed by the experiment lifecycle. ([#22924](https://github.com/mastra-ai/mastra/pull/22924)) ```typescript const dataset = await mastra.datasets.get({ id: 'dataset-id' }); await dataset.updateExperiment({ experimentId: 'exp-id', name: 'Baseline vs. new prompt', description: 'Run after switching to the shorter system prompt', }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b72c747`](https://github.com/mastra-ai/mastra/commit/b72c747a1a698c829c7c1d42e75f72c6d1808dde)) - Fixed agent run traces leaking open spans when a run ends abnormally. Errors, aborts, suspensions, tripwires, and prepare failures now close the whole span tree, and a span that ends early hands its still-open children to the nearest live ancestor. Exporters that wait for every span to finish (such as Datadog) no longer retain the trace and its payloads in memory forever. ([#22764](https://github.com/mastra-ai/mastra/pull/22764)) Added an `endTree` option to `span.end()` and `span.error()` for terminal points: it also closes any still-open descendant spans, without applying the terminal output or error to them. - The agent controller's live message now closes a text or reasoning span on `text-end` / `reasoning-end`. A later step that reuses the provider's block id opens a new part instead of appending to the earlier one, so the live message keeps the same part order as the persisted one. ([#23271](https://github.com/mastra-ai/mastra/pull/23271)) - Fixed a message history compatibility issue. ([#23093](https://github.com/mastra-ai/mastra/pull/23093)) - Added `MASTRA_MESSAGE_AUTHOR_KEY` to `@mastra/core/request-context`. Set it from your auth middleware to `{ id, name?, avatarUrl? }` and every message an agent-controller session sends on that request (`sendMessage`, `steer`, `followUp`) is stored with that sender under `providerMetadata.mastra.author`, so a thread several people share can show who wrote what. ([#23085](https://github.com/mastra-ai/mastra/pull/23085)) ```typescript import { MASTRA_MESSAGE_AUTHOR_KEY } from '@mastra/core/request-context'; requestContext.setRaw(MASTRA_MESSAGE_AUTHOR_KEY, { id: user.id, name: user.name, avatarUrl: user.avatarUrl }); ``` - Fixed failed dataset experiment agent runs to retain their trace links. ([#22948](https://github.com/mastra-ai/mastra/pull/22948)) - Fixed BrowserViewer connections for Browser Use stdin commands while preserving thread isolation. ([#23142](https://github.com/mastra-ai/mastra/pull/23142)) - Fixed dataset experiments to pass request context when resolving dynamic agent models. ([#23152](https://github.com/mastra-ai/mastra/pull/23152)) - Fixed raw tool inputs being copied into logs and errors. When a tool call's JSON cannot be parsed, only the tool name and input length are logged instead of the full input. The `TOOL_EXECUTION_FAILED` error no longer includes an `argsJson` copy of the arguments, and raw arguments are no longer attached to exception-tracking metadata. Raw arguments are also no longer included in the debug log written at the start of each tool call. Tool inputs remain available on the tool's trace span, where observability redaction applies. The truncated `Provided arguments:` excerpt in schema validation errors is intentionally unchanged, because the model uses it to correct the call. Fixes https://github.com/mastra-ai/mastra/issues/22926 ([#22931](https://github.com/mastra-ai/mastra/pull/22931)) - Added Perplexity integration attribution while preserving custom headers. ([#22498](https://github.com/mastra-ai/mastra/pull/22498)) - Restore the default explore, plan, and execute subagents in Mastra Code while preserving explicit empty and custom subagent configurations. Match delegation prompt guidance to tool availability and permissions. ([#23217](https://github.com/mastra-ai/mastra/pull/23217)) - Added protected `getDatasetForMutation` and `listItemsForMutation` hooks to `DatasetsStorage`. The base `updateDataset`, `updateItem`, `deleteItem`, `batchInsertItems`, and `batchDeleteItems` flows now use these hooks for their pre-write dataset checks, so storage adapters that read from a replica can point those checks at the primary. Defaults are unchanged. ([#23154](https://github.com/mastra-ai/mastra/pull/23154)) - Keep reactive and system-reminder signals out of live thread streams, matching default thread-history visibility while preserving model delivery and persistence in regular and durable agent runs. ([#23214](https://github.com/mastra-ai/mastra/pull/23214)) - Preserve MCP tool descriptions and types in OpenTelemetry spans. Export MCP server names and optional versions as `mastra.mcp_tool_call.server_name` and `mastra.mcp_tool_call.server_version`, retaining `server.address` and preserving server metadata through Arize's OpenInference conversion. ([#23218](https://github.com/mastra-ai/mastra/pull/23218)) - Added `dataset.purgeItem()` to redact item content from existing dataset history and linked experiment results while preserving version history and review status. Purged items reject later dataset updates, later experiment-result writes remain redacted, and MongoDB purges require transaction support. Dataset item writes must not run concurrently with purge. ([#22559](https://github.com/mastra-ai/mastra/pull/22559)) ```typescript await dataset.purgeItem({ itemId: 'item-123' }); ``` - Fixed Cloudflare Workers builds by preventing Node-only runtime dependencies from being bundled. ([#20639](https://github.com/mastra-ai/mastra/pull/20639)) - Fixed dynamic agent models being resolved repeatedly when an agent uses tools from multiple sources. Each generation or stream now consistently uses a single model snapshot for all tools, including per-call model overrides. ([#23080](https://github.com/mastra-ai/mastra/pull/23080)) - Fixed AgentController reply IDs after a suspended tool resumes so streamed replies match their saved messages in Memory. ([#23151](https://github.com/mastra-ai/mastra/pull/23151)) - Fixed tool result metadata being dropped when a UI message comes back from the browser. The AI SDK sends this metadata separately from the call-time metadata, and only the call half was read, so the `toModelOutput` projection stored on a tool result was lost and the raw result was rendered back into the prompt. ([#23290](https://github.com/mastra-ai/mastra/pull/23290)) Fixes [#22012](https://github.com/mastra-ai/mastra/issues/22012) - Fixed durable agent streaming being throttled by the event cache when it lives on a remote server (issue #22477). Every streamed chunk used to wait for two sequential cache round-trips before it could be published; it now waits for one, and cache backends can fuse index allocation and append into a single operation. ([#23161](https://github.com/mastra-ai/mastra/pull/23161)) Added a `shouldCache` option to `createDurableAgent`, `createEventedAgent`, and the `durable` agent config so specific topics can skip the replay cache and publish straight through when resumability is not needed for them. ```ts const durableAgent = createDurableAgent({ agent, cache, // Stream chunks are delivered live only; other topics stay resumable. shouldCache: topic => !topic.startsWith('agent.stream.'), }); ``` - Applied `onDelegationComplete` result text replacements to failed subagent delegations while preserving failed tool result semantics. ([#23000](https://github.com/mastra-ai/mastra/pull/23000)) - Fixed `CompositeAuth` resource mapping validation to reject invalid IDs from the authenticating provider while preserving providers without a mapper, including nested composites. ([#21722](https://github.com/mastra-ai/mastra/pull/21722)) - Fixed AI SDK v6/v7 message conversion throwing on reasoning parts with no text and no details, which could crash message rendering while a reasoning model streamed. ([#23134](https://github.com/mastra-ai/mastra/pull/23134)) - Added inference start timestamps to step-start stream events for accurate time-to-first-token measurement. ([#23094](https://github.com/mastra-ai/mastra/pull/23094)) - Fixed unnecessary streaming overhead for final-only output processors. ([#23147](https://github.com/mastra-ai/mastra/pull/23147)) - Fixed durable agents dropping `scoringData` from `generate()` and `stream()` results when `returnScorerData: true` is set. The flag was serialized into the durable workflow input but never forwarded to the client-side output, so `runEvals` and `startExperiment` scorers silently evaluated `undefined` output. Durable agents now return `scoringData` across generate, stream, resume, and recovery paths, matching non-durable agents. Also fixed tool-calling durable runs replacing the run's message list mid-run, which left resumed runs reading stale messages. Fixes #22743 ([#22878](https://github.com/mastra-ai/mastra/pull/22878)) - Fixed durable agent runs being restarted by the generic boot-time workflow recovery. On server start, `Mastra.restartAllActiveWorkflowRuns()` restarted every active workflow run, including the internal workflows that back durable agents — even when `recovery.durableAgents` was `'off'` (the default), and racing the dedicated recovery path when set to `'auto'`. Durable agent runs are now only recovered through `recovery.durableAgents: 'auto'`. The internal durable agent workflows also no longer appear in `listWorkflows()` or the Studio workflow list; they remain accessible by id. Fixes [#22598](https://github.com/mastra-ai/mastra/issues/22598). ([#22960](https://github.com/mastra-ai/mastra/pull/22960)) **New workflow option `autoRestartActiveRuns`** Any workflow can now opt out of the automatic boot-time restart, for example when its side effects must not be re-driven by a blanket restart: ```typescript const workflow = createWorkflow({ id: 'my-workflow', inputSchema, outputSchema, options: { // Exclude this workflow from Mastra.restartAllActiveWorkflowRuns() autoRestartActiveRuns: false, }, }); ``` - Updated dependencies [[`40f3647`](https://github.com/mastra-ai/mastra/commit/40f36478291d6098f762fc639d545357732b77b4)]: - @mastra/schema-compat@1.3.9 ## 1.65.0-alpha.12 ### Patch Changes - Added Perplexity integration attribution while preserving custom headers. ([#22498](https://github.com/mastra-ai/mastra/pull/22498)) ## 1.65.0-alpha.11 ### Minor Changes - `dataset.deleteExperiment()` now also deletes the observability traces the experiment produced, cascading to their spans and trace-linked scores, feedback, metrics and logs. Experiment traces are excluded from normal trace reads, so leaving them behind kept data that was invisible but still retained. ([#22550](https://github.com/mastra-ai/mastra/pull/22550)) `mastra.datasets.deleteExperiment()` is new and does the same thing without requiring the experiment to still belong to a dataset, so experiments orphaned by dataset deletion can be cleaned up. ```ts // Both delete the experiment, its results, and its traces. await dataset.deleteExperiment({ experimentId }); await mastra.datasets.deleteExperiment({ experimentId }); ``` Stores without an observability domain (or without tenant-scoped trace deletion) log a warning and skip the trace cascade so the experiment is still deleted. ### Patch Changes - Fixed failed dataset experiment agent runs to retain their trace links. ([#22948](https://github.com/mastra-ai/mastra/pull/22948)) - Added `dataset.purgeItem()` to redact item content from existing dataset history and linked experiment results while preserving version history and review status. Purged items reject later dataset updates, later experiment-result writes remain redacted, and MongoDB purges require transaction support. Dataset item writes must not run concurrently with purge. ([#22559](https://github.com/mastra-ai/mastra/pull/22559)) ```typescript await dataset.purgeItem({ itemId: 'item-123' }); ``` - Fixed `CompositeAuth` resource mapping validation to reject invalid IDs from the authenticating provider while preserving providers without a mapper, including nested composites. ([#21722](https://github.com/mastra-ai/mastra/pull/21722)) - Updated dependencies [[`40f3647`](https://github.com/mastra-ai/mastra/commit/40f36478291d6098f762fc639d545357732b77b4)]: - @mastra/schema-compat@1.3.9-alpha.0 ## 1.65.0-alpha.10 ### Patch Changes - The agent controller's live message now closes a text or reasoning span on `text-end` / `reasoning-end`. A later step that reuses the provider's block id opens a new part instead of appending to the earlier one, so the live message keeps the same part order as the persisted one. ([#23271](https://github.com/mastra-ai/mastra/pull/23271)) - Fixed tool result metadata being dropped when a UI message comes back from the browser. The AI SDK sends this metadata separately from the call-time metadata, and only the call half was read, so the `toModelOutput` projection stored on a tool result was lost and the raw result was rendered back into the prompt. ([#23290](https://github.com/mastra-ai/mastra/pull/23290)) Fixes [#22012](https://github.com/mastra-ai/mastra/issues/22012) ## 1.65.0-alpha.9 ### Patch Changes - Restore the default explore, plan, and execute subagents in Mastra Code while preserving explicit empty and custom subagent configurations. Match delegation prompt guidance to tool availability and permissions. ([#23217](https://github.com/mastra-ai/mastra/pull/23217)) - Keep reactive and system-reminder signals out of live thread streams, matching default thread-history visibility while preserving model delivery and persistence in regular and durable agent runs. ([#23214](https://github.com/mastra-ai/mastra/pull/23214)) - Fixed durable agent streaming being throttled by the event cache when it lives on a remote server (issue #22477). Every streamed chunk used to wait for two sequential cache round-trips before it could be published; it now waits for one, and cache backends can fuse index allocation and append into a single operation. ([#23161](https://github.com/mastra-ai/mastra/pull/23161)) Added a `shouldCache` option to `createDurableAgent`, `createEventedAgent`, and the `durable` agent config so specific topics can skip the replay cache and publish straight through when resumability is not needed for them. ```ts const durableAgent = createDurableAgent({ agent, cache, // Stream chunks are delivered live only; other topics stay resumable. shouldCache: topic => !topic.startsWith('agent.stream.'), }); ``` ## 1.65.0-alpha.8 ### Patch Changes - Preserve MCP tool descriptions and types in OpenTelemetry spans. Export MCP server names and optional versions as `mastra.mcp_tool_call.server_name` and `mastra.mcp_tool_call.server_version`, retaining `server.address` and preserving server metadata through Arize's OpenInference conversion. ([#23218](https://github.com/mastra-ai/mastra/pull/23218)) ## 1.65.0-alpha.7 ### Patch Changes - Added `MASTRA_MESSAGE_AUTHOR_KEY` to `@mastra/core/request-context`. Set it from your auth middleware to `{ id, name?, avatarUrl? }` and every message an agent-controller session sends on that request (`sendMessage`, `steer`, `followUp`) is stored with that sender under `providerMetadata.mastra.author`, so a thread several people share can show who wrote what. ([#23085](https://github.com/mastra-ai/mastra/pull/23085)) ```typescript import { MASTRA_MESSAGE_AUTHOR_KEY } from '@mastra/core/request-context'; requestContext.setRaw(MASTRA_MESSAGE_AUTHOR_KEY, { id: user.id, name: user.name, avatarUrl: user.avatarUrl }); ``` - Added protected `getDatasetForMutation` and `listItemsForMutation` hooks to `DatasetsStorage`. The base `updateDataset`, `updateItem`, `deleteItem`, `batchInsertItems`, and `batchDeleteItems` flows now use these hooks for their pre-write dataset checks, so storage adapters that read from a replica can point those checks at the primary. Defaults are unchanged. ([#23154](https://github.com/mastra-ai/mastra/pull/23154)) ## 1.65.0-alpha.6 ### Patch Changes - Fixed BrowserViewer connections for Browser Use stdin commands while preserving thread isolation. ([#23142](https://github.com/mastra-ai/mastra/pull/23142)) - Fixed dataset experiments to pass request context when resolving dynamic agent models. ([#23152](https://github.com/mastra-ai/mastra/pull/23152)) - Fixed AgentController reply IDs after a suspended tool resumes so streamed replies match their saved messages in Memory. ([#23151](https://github.com/mastra-ai/mastra/pull/23151)) - Added inference start timestamps to step-start stream events for accurate time-to-first-token measurement. ([#23094](https://github.com/mastra-ai/mastra/pull/23094)) - Fixed unnecessary streaming overhead for final-only output processors. ([#23147](https://github.com/mastra-ai/mastra/pull/23147)) ## 1.65.0-alpha.5 ### Patch Changes - Fixed a message history compatibility issue. ([#23093](https://github.com/mastra-ai/mastra/pull/23093)) - Fixed AI SDK v6/v7 message conversion throwing on reasoning parts with no text and no details, which could crash message rendering while a reasoning model streamed. ([#23134](https://github.com/mastra-ai/mastra/pull/23134)) ## 1.65.0-alpha.4 ### Minor Changes - Added tenant-scoped trace deletion arguments for observability storage with a limit of 1,000 trace IDs per batch. ([#22553](https://github.com/mastra-ai/mastra/pull/22553)) ```typescript await storage.batchDeleteTraces({ traceIds: ['trace-1'], organizationId: 'org-1' }); ``` ### Patch Changes - Fixed dynamic agent models being resolved repeatedly when an agent uses tools from multiple sources. Each generation or stream now consistently uses a single model snapshot for all tools, including per-call model overrides. ([#23080](https://github.com/mastra-ai/mastra/pull/23080)) ## 1.65.0-alpha.3 ### Minor Changes - Added `handlers.onAction` to agent channels so apps can handle button clicks and select changes from their own cards. The built-in tool approval handling is available as `defaultHandler`, and `onAction: false` disables it. Fixes #22629 ([#22927](https://github.com/mastra-ai/mastra/pull/22927)) ```ts const agent = new Agent({ // ... channels: { adapters: { slack: createSlackAdapter() }, handlers: { onAction: async (event, defaultHandler) => { if (event.actionId === 'retry') { await event.thread?.post('Retrying...'); return; } await defaultHandler(); }, }, }, }); ``` ### Patch Changes - Fixed Cloudflare Workers builds by preventing Node-only runtime dependencies from being bundled. ([#20639](https://github.com/mastra-ai/mastra/pull/20639)) - Applied `onDelegationComplete` result text replacements to failed subagent delegations while preserving failed tool result semantics. ([#23000](https://github.com/mastra-ai/mastra/pull/23000)) ## 1.65.0-alpha.2 ### Minor Changes - Added the core contract for advanced trace queries, including bounded time ranges, recursive trace, span, and score predicates, thread grouping, and deterministic cursor pagination. Invalid or overly complex requests are rejected before a storage adapter executes them. ([#22726](https://github.com/mastra-ai/mastra/pull/22726)) ```ts const request = traceQueryRequestSchema.parse({ timeRange: { from: '2026-08-01T00:00:00Z', to: '2026-09-01T00:00:00Z' }, where: { op: 'eq', left: { path: 'environment' }, right: { literal: 'production' } }, }); ``` ## 1.65.0-alpha.1 ### Minor Changes - Added optional `id`, `description`, and `metadata` to workflow control-flow entries: `.parallel()`, `.branch()`, `.dowhile()`, `.dountil()`, `.foreach()`, `.sleep()`, `.sleepUntil()`, and `.map()`. Executable steps already supported these fields; the entries between them now follow the same model, so visual editors and review tools can label a parallel block or a sleep and address it with a stable id instead of a generated one or a position in the graph. ([#22633](https://github.com/mastra-ai/mastra/pull/22633)) ```typescript workflow .parallel([validateStep, enrichStep], { id: 'independent-enrichment', description: 'Run independent enrichment tasks concurrently', metadata: { title: 'Independent enrichment' }, }) .sleep(5000, { id: 'wait-before-retry', metadata: { title: 'Wait before retry' } }); ``` The fields appear in `serializedStepGraph`, survive storage and rehydration of dynamic workflow definitions, and have no effect on execution. For `.map()`, `.sleep()`, and `.sleepUntil()`, a supplied `id` replaces the generated entry id. - Traces now show Mastra's built-in add-ons as the subsystem they came from, instead of anonymous processor runs. ([#22542](https://github.com/mastra-ai/mastra/pull/22542)) Skills, workspace instructions, observational memory and agent state signals all run on the processor pipeline, but you configure `skills`, `workspace`, `memory` and `signals` — not processors. Their spans were named after a pipeline phase you never chose: | Was | Now | | -------------------------------------------------------- | ------------------------------ | | `input step processor: skills-processor` | `skill:inject` | | `input step processor: workspace-instructions-processor` | `workspace:mount:instructions` | | `input step processor: observational-memory` | `memory: recall` | **New span types** - `SKILL_ACTION` covers the whole skill lifecycle — resolve, inject, activate, search, read. `SKILL_RESOLUTION` is deprecated and no longer emitted. - `AGENT_SIGNAL` records each state signal emission as a point-in-time event. A turn where the lane computes no change records nothing. **Tracing your own processors** Any processor can declare how it is traced, and one that declares nothing is unchanged: ```ts import { SpanType } from '@mastra/core/observability'; import type { Processor, ProcessorSpanPhase } from '@mastra/core/processors'; class MyProcessor implements Processor<'my-processor'> { readonly id = 'my-processor' as const; readonly spanType = SpanType.MEMORY_OPERATION; readonly spanName = (phase: ProcessorSpanPhase) => `memory: ${phase === 'inputStep' ? 'recall' : 'save'}`; readonly spanAttributes = { operationType: 'recall' } as const; } ``` **Fixes** - The skills processor reports `skillCount` on every run. A skills path that resolved to nothing previously produced no span at all; it now shows as `skillCount: 0`. - `computeStateSignal` implementations receive the `tracingContext` their argument type always advertised but never passed. - Added `dataset.updateExperiment()` to rename an experiment or change its description and metadata after it has been created. Status and result counters remain managed by the experiment lifecycle. ([#22924](https://github.com/mastra-ai/mastra/pull/22924)) ```typescript const dataset = await mastra.datasets.get({ id: 'dataset-id' }); await dataset.updateExperiment({ experimentId: 'exp-id', name: 'Baseline vs. new prompt', description: 'Run after switching to the shorter system prompt', }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b72c747`](https://github.com/mastra-ai/mastra/commit/b72c747a1a698c829c7c1d42e75f72c6d1808dde)) - Fixed agent run traces leaking open spans when a run ends abnormally. Errors, aborts, suspensions, tripwires, and prepare failures now close the whole span tree, and a span that ends early hands its still-open children to the nearest live ancestor. Exporters that wait for every span to finish (such as Datadog) no longer retain the trace and its payloads in memory forever. ([#22764](https://github.com/mastra-ai/mastra/pull/22764)) Added an `endTree` option to `span.end()` and `span.error()` for terminal points: it also closes any still-open descendant spans, without applying the terminal output or error to them. - Fixed durable agents dropping `scoringData` from `generate()` and `stream()` results when `returnScorerData: true` is set. The flag was serialized into the durable workflow input but never forwarded to the client-side output, so `runEvals` and `startExperiment` scorers silently evaluated `undefined` output. Durable agents now return `scoringData` across generate, stream, resume, and recovery paths, matching non-durable agents. Also fixed tool-calling durable runs replacing the run's message list mid-run, which left resumed runs reading stale messages. Fixes #22743 ([#22878](https://github.com/mastra-ai/mastra/pull/22878)) - Fixed durable agent runs being restarted by the generic boot-time workflow recovery. On server start, `Mastra.restartAllActiveWorkflowRuns()` restarted every active workflow run, including the internal workflows that back durable agents — even when `recovery.durableAgents` was `'off'` (the default), and racing the dedicated recovery path when set to `'auto'`. Durable agent runs are now only recovered through `recovery.durableAgents: 'auto'`. The internal durable agent workflows also no longer appear in `listWorkflows()` or the Studio workflow list; they remain accessible by id. Fixes [#22598](https://github.com/mastra-ai/mastra/issues/22598). ([#22960](https://github.com/mastra-ai/mastra/pull/22960)) **New workflow option `autoRestartActiveRuns`** Any workflow can now opt out of the automatic boot-time restart, for example when its side effects must not be re-driven by a blanket restart: ```typescript const workflow = createWorkflow({ id: 'my-workflow', inputSchema, outputSchema, options: { // Exclude this workflow from Mastra.restartAllActiveWorkflowRuns() autoRestartActiveRuns: false, }, }); ``` ## 1.64.1-alpha.0 ### Patch Changes - Fixed raw tool inputs being copied into logs and errors. When a tool call's JSON cannot be parsed, only the tool name and input length are logged instead of the full input. The `TOOL_EXECUTION_FAILED` error no longer includes an `argsJson` copy of the arguments, and raw arguments are no longer attached to exception-tracking metadata. Raw arguments are also no longer included in the debug log written at the start of each tool call. Tool inputs remain available on the tool's trace span, where observability redaction applies. The truncated `Provided arguments:` excerpt in schema validation errors is intentionally unchanged, because the model uses it to correct the call. Fixes https://github.com/mastra-ai/mastra/issues/22926 ([#22931](https://github.com/mastra-ai/mastra/pull/22931)) ## 1.64.0 ### Minor Changes - Added a review workflow status to observability feedback. ([#22805](https://github.com/mastra-ai/mastra/pull/22805)) - Feedback records now carry a `reviewStatus` (`needs-review` | `reviewed`), defaulting to `needs-review` and settable at creation; `listFeedback` can filter on it. - New storage method `updateFeedbackReviewStatus` and `PATCH /api/observability/feedback/:feedbackId/review-status` endpoint (requires `observability:write`), exposed on the client as `updateFeedbackReviewStatus`. ```ts const { feedback } = await client.listFeedback({ filters: { reviewStatus: 'needs-review' }, pagination: { page: 0, perPage: 20 }, }); await client.updateFeedbackReviewStatus({ feedbackId: feedback[0].feedbackId, reviewStatus: 'reviewed', }); ``` - Added support for server-defined `toModelOutput` on client-side tools. When a tool without an `execute` function runs in the browser and sends its result back, the server tool definition's `toModelOutput` now transforms that result before the model sees it — matching AI SDK behavior. This lets a client tool return a compact payload (like an uploaded file id or base64 image) and have the server map it into real model content: ([#22437](https://github.com/mastra-ai/mastra/pull/22437)) ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; // No execute: the browser runs this tool and returns { fileId, dataUrl } const takeScreenshot = createTool({ id: 'takeScreenshot', description: 'Captures the screen', inputSchema: z.object({}), outputSchema: z.object({ fileId: z.string(), dataUrl: z.string() }), toModelOutput: output => ({ type: 'content', value: [{ type: 'image-url', url: output.dataUrl }], }), }); ``` Previously the model only ever saw the raw JSON tool result and transforming it required a custom input processor. - **Added a `workingDirectory` option to `MastraSandboxOptions`, honored by every sandbox provider** ([#22697](https://github.com/mastra-ai/mastra/pull/22697)) Every sandbox now accepts one instance-level `workingDirectory` option that sets the default directory for command execution and process spawns. A per-command `cwd` always wins over it, and when neither is provided each provider keeps its previous default (E2B home, docker `/workspace`, Vercel serverless `/tmp`, and so on). The effective value is readable through the new `sandbox.workingDirectory` getter. ```ts const sandbox = new E2BSandbox({ workingDirectory: '/home/user/my-repo' }); await sandbox.executeCommand('pwd'); // /home/user/my-repo await sandbox.executeCommand('pwd', [], { cwd: '/tmp' }); // /tmp ``` Providers that already carried this concept under other names keep those names working as deprecated aliases feeding the same field: `workingDir` on `@mastra/docker` and `@mastra/apple-container`, and `workdir` on `@mastra/modal`. When both the alias and `workingDirectory` are set, `workingDirectory` wins. Use absolute paths: the value is passed to the provider as-is, so `~` and environment variables like `$HOME` are not expanded (except where a provider documents expansion, such as `LocalSandbox` expanding `~`). ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`3910c77`](https://github.com/mastra-ai/mastra/commit/3910c77413a3058ab270c6dbc74a59bc3cdf67ea)) - Fixed `SlashCommandChannelHandler`, `SlashCommandChannelHandlerConfig`, and `SlashCommandEvent` not being exported from `@mastra/core/channels`. Standalone slash-command handlers can now be typed directly instead of reaching through `ChannelHandlers['onSlashCommand']`. ([#22814](https://github.com/mastra-ai/mastra/pull/22814)) ```ts import type { SlashCommandChannelHandler } from '@mastra/core/channels'; const onSlashCommand: SlashCommandChannelHandler = async (event, defaultHandler) => { if (event.command === '/help') { await event.channel.post('Available commands: /help'); return; } await defaultHandler(); }; ``` - Fixed approval event delivery for function-form `toolDisplay` in streaming channels. A function-form `toolDisplay` now receives `approval` events when `streaming: true`, matching static mode. Return `{ kind: 'post', message }` to replace the built-in approval card; `undefined`, blank, or `stream` results fall back to the built-in card so the approval stays actionable. ([#22832](https://github.com/mastra-ai/mastra/pull/22832)) - Exported the validateToolOutput helper from @mastra/core/tools so integrations can validate tool results against a schema and produce the same structured validation error as createTool. ([#22779](https://github.com/mastra-ai/mastra/pull/22779)) - Update README to include accurate, up-to-date information ([#22858](https://github.com/mastra-ai/mastra/pull/22858)) - Fixed per-tool `requireApproval` functions (`needsApprovalFn`) receiving no context on durable agents and `agent.network()`. They now get the same `{ requestContext, workspace }` second argument as `stream()`/`generate()`, so approval logic that reads the request context works consistently. On durable agents the request context is restored from the persisted run snapshot when the check runs in another process or after a resume. Fixes #22491 ([#22841](https://github.com/mastra-ai/mastra/pull/22841)) - Fixed tool observability helpers to emit correlated logs and child spans during agent execution. ([#22887](https://github.com/mastra-ai/mastra/pull/22887)) - Fixed the scheduler polling storage every 10 seconds in apps that never create a schedule, which kept Railway/Neon-style deployments from scaling to zero. The scheduler now runs a single `listSchedules()` check at boot and only starts polling when a schedule exists or is created (or when `scheduler: { enabled: true }` / `MASTRA_WORKERS=scheduler` opts in explicitly), and worker processes learn about schedules created by the API process through the shared PubSub backend instead of constant polling. Also fixed deferred notifications sent from a `workers: false` API process never being dispatched: that process now registers the dispatcher schedule so a standalone worker can run it. ([#22909](https://github.com/mastra-ai/mastra/pull/22909)) - Fixed signals sent after DurableAgent recovery so they are drained by the resumed run. ([#22781](https://github.com/mastra-ai/mastra/pull/22781)) - Fixed durable agent run recovery losing the fallback model list. After a process restart, DurableAgent.recover() now restores the live fallback models with the ids the run was prepared with, so recovered runs keep using custom or dynamically resolved fallback model instances instead of failing or falling back to models rebuilt from serialized config. Fixes [#22594](https://github.com/mastra-ai/mastra/issues/22594). ([#22860](https://github.com/mastra-ai/mastra/pull/22860)) - `runEvals` now honours the trajectory contract in both gate loops. A scorer created with `type: 'trajectory'` is typed as receiving `output: Trajectory`, and the `scorers.trajectory` path already resolved one and threaded `expectedTrajectory`; the top-level `gates` loop and the per-turn `turns[].gates` loop passed the raw target output and no `expectedTrajectory`, so such a gate scored 0 on every item as soon as it read a trajectory field. Workflow targets resolve the trajectory from step results, matching the scorer path, instead of being handed the workflow's own result. Gate failures are also no longer silent: a throwing gate still scores 0, but the cause is logged with the gate id instead of being discarded by a bare `catch`. ([#22903](https://github.com/mastra-ai/mastra/pull/22903)) - Fixed AgentController dropping streamed assistant text and reasoning after page reload. Text and reasoning deltas that arrive without a seeded part (for example after a mid-stream step rotation) are now folded into the message instead of being silently discarded. Fixes #22712 ([#22775](https://github.com/mastra-ai/mastra/pull/22775)) - Fixed LocalSandbox native isolation being unable to open /dev/null, which broke git, ssh, and shell redirections (e.g. `2>/dev/null`) inside the sandbox. On Linux, the Bubblewrap backend now mounts a fresh /dev with standard device nodes, emitted after all configured binds so existing workarounds like `readOnlyPaths: ['/dev']` no longer shadow it. On macOS, the Seatbelt profile now allows writing to the standard device nodes (/dev/null, /dev/zero, /dev/random, /dev/urandom, /dev/tty). Fixes https://github.com/mastra-ai/mastra/issues/22702 ([#22791](https://github.com/mastra-ai/mastra/pull/22791)) - Fixed durable agent traces being polluted by output-stream processor spans. The durable per-chunk processor pipeline ran without a tracing context, so every `output stream processor` span exported with no parent. Span stores that label a trace by its newest root row then showed a processor id instead of the agent. ([#22677](https://github.com/mastra-ai/mastra/pull/22677)) - Output-stream processor spans now nest under the run's `agent run` span. - Tool-call chunks and resumed runs parent their processor spans the same way. - The tool-call pipeline ends its processor spans right after each chunk, so none stay open. - Callers without a tracing context no longer create processor spans, so orphan trace roots can never appear. Fixes #22602 - Fixed plan approvals so mode changes resume the original agent run. ([#22476](https://github.com/mastra-ai/mastra/pull/22476)) - Fixed durable agent output processors receiving an empty request context, including after server restarts. ([#22327](https://github.com/mastra-ai/mastra/pull/22327)) - Fixed durable agents dropping already-streamed assistant text from memory when a run is aborted mid-stream. The partial response was visible in the live stream and in the onAbort callback, but disappeared after a reload or memory recall — only the user message remained. Aborted runs now persist the partial assistant message to memory, matching the regular agent's behavior. Fixes #22593 ([#22872](https://github.com/mastra-ai/mastra/pull/22872)) - Fixed background tasks advertising the `_background` override to every tool. Previously, enabling `backgroundTasks` on the Mastra instance injected the `_background` field into every tool's input schema and listed every tool as background-eligible in the system prompt, even when neither the agent nor the tool opted in. Now only tools that are actually background-eligible — via the agent's `backgroundTasks.tools` config or the tool's own `background: { enabled: true }` — advertise the override, matching the runtime dispatch behavior. This removes roughly 2,000 characters of prompt overhead per ineligible tool and stops the model from being told it can background tools it cannot. Fixes [#22724](https://github.com/mastra-ai/mastra/issues/22724). ([#22777](https://github.com/mastra-ai/mastra/pull/22777)) - Fixed approval resumes when Agent Controller uses in-memory storage. ([#22476](https://github.com/mastra-ai/mastra/pull/22476)) - Fixed workspace skill discovery silently reporting zero skills when the workspace filesystem is mis-wired. Invalid-argument errors (Node `ERR_INVALID_ARG*`, e.g. a non-string path handed to the skill source) now surface from refresh() instead of being logged as an inaccessible skills path warning. Genuine access failures and network errors keep the warn-and-continue behavior. Closes https://github.com/mastra-ai/mastra/issues/22639 ([#22823](https://github.com/mastra-ai/mastra/pull/22823)) - Fixed UnixSocketPubSub accepting unbounded inbound frames. Added a `maxInboundFrameBytes` option (default 64 MiB); a connection that sends a larger frame, or an unterminated partial frame beyond that size, is disconnected, and partial frames are buffered compactly regardless of how they are fragmented, so a single peer can no longer exhaust broker memory. Fixes #22376 ([#22882](https://github.com/mastra-ai/mastra/pull/22882)) ```ts import { UnixSocketPubSub } from '@mastra/core/events'; const pubsub = new UnixSocketPubSub('/tmp/mastra.sock', { maxInboundFrameBytes: 8 * 1024 * 1024, // 8 MiB }); ``` - Removed the vulnerable @ai-sdk/provider-utils@3.x dependency (CVE-2026-8769 / GHSA-866g-f22w-33x8) from @mastra/core. The helpers it supplied are now sourced from the patched provider-utils 4.x line already installed, so security audits no longer flag @mastra/core and its dependents. AI SDK v5 model compatibility is unchanged. Fixes #22592. ([#22790](https://github.com/mastra-ai/mastra/pull/22790)) - Fixed `listResolved()` on versioned storage domains issuing one version query per listed entity. `@mastra/core` adds an overridable `getVersions(ids)` method that `listResolved()` uses to fetch all active versions in a single batch; adapters that don't override it keep their previous per-id behavior. `@mastra/pg` and `@mastra/libsql` override it for the agents and skills domains with a single `WHERE id IN (...)` query. Fixes https://github.com/mastra-ai/mastra/issues/22524 ([#22828](https://github.com/mastra-ai/mastra/pull/22828)) - Fixed observability signals from Studio (mastra dev) being tagged with environment: production. Runs started through mastra dev now resolve to development unless an explicit environment is configured. Fixes #21941 ([#22734](https://github.com/mastra-ai/mastra/pull/22734)) - Record TripWire aborts on workflow-path PROCESSOR_RUN spans as span errors with the structured `tripwireAbort` attribute (reason, retry, metadata), matching the legacy processor-runner path. Previously these spans ended like successful runs with only `output.tripwire`, dropping the retry flag and error info. ([#22350](https://github.com/mastra-ai/mastra/pull/22350)) - Fixed onDelegationComplete reporting successful sub-agent runs when the finish reason is error. ([#22483](https://github.com/mastra-ai/mastra/pull/22483)) - Fixed scheduled workflows disappearing from Studio's Schedules tab for dynamically created workflows. Dynamic workflow definitions now persist their schedule configuration, so schedules are re-declared after a restart instead of being deleted as orphans. Schedules of dynamic workflows that fail to load are also kept instead of being swept. Fixes https://github.com/mastra-ai/mastra/issues/22756 ([#22778](https://github.com/mastra-ai/mastra/pull/22778)) - Fixed span metadata values being silently erased by keys whose value is undefined. Values extracted via requestContextKeys (for example a threadId set on a RequestContext) now reach exported spans even when the agent has no memory configured, so exporters like Arize can group traces into sessions again. Keys passed in tracingOptions.metadata with undefined values no longer remove values the span already has; keys with real values still take precedence. Fixes [#22597](https://github.com/mastra-ai/mastra/issues/22597). ([#22742](https://github.com/mastra-ai/mastra/pull/22742)) - Remove `CHANGELOG.md` from distributed npm files resulting in reduced package size ([#22737](https://github.com/mastra-ai/mastra/pull/22737)) - Updated dependencies [[`e983f74`](https://github.com/mastra-ai/mastra/commit/e983f749873189f767f509eb33d1a3596c0f1c74), [`28ce924`](https://github.com/mastra-ai/mastra/commit/28ce924276eeca492e6a360e5482ed20c2785ef6)]: - @mastra/schema-compat@1.3.8 ## 1.64.0-alpha.9 ### Minor Changes - Added a review workflow status to observability feedback. ([#22805](https://github.com/mastra-ai/mastra/pull/22805)) - Feedback records now carry a `reviewStatus` (`needs-review` | `reviewed`), defaulting to `needs-review` and settable at creation; `listFeedback` can filter on it. - New storage method `updateFeedbackReviewStatus` and `PATCH /api/observability/feedback/:feedbackId/review-status` endpoint (requires `observability:write`), exposed on the client as `updateFeedbackReviewStatus`. ```ts const { feedback } = await client.listFeedback({ filters: { reviewStatus: 'needs-review' }, pagination: { page: 0, perPage: 20 }, }); await client.updateFeedbackReviewStatus({ feedbackId: feedback[0].feedbackId, reviewStatus: 'reviewed', }); ``` ### Patch Changes - Fixed the scheduler polling storage every 10 seconds in apps that never create a schedule, which kept Railway/Neon-style deployments from scaling to zero. The scheduler now runs a single `listSchedules()` check at boot and only starts polling when a schedule exists or is created (or when `scheduler: { enabled: true }` / `MASTRA_WORKERS=scheduler` opts in explicitly), and worker processes learn about schedules created by the API process through the shared PubSub backend instead of constant polling. Also fixed deferred notifications sent from a `workers: false` API process never being dispatched: that process now registers the dispatcher schedule so a standalone worker can run it. ([#22909](https://github.com/mastra-ai/mastra/pull/22909)) - Fixed durable agent run recovery losing the fallback model list. After a process restart, DurableAgent.recover() now restores the live fallback models with the ids the run was prepared with, so recovered runs keep using custom or dynamically resolved fallback model instances instead of failing or falling back to models rebuilt from serialized config. Fixes [#22594](https://github.com/mastra-ai/mastra/issues/22594). ([#22860](https://github.com/mastra-ai/mastra/pull/22860)) - Fixed durable agents dropping already-streamed assistant text from memory when a run is aborted mid-stream. The partial response was visible in the live stream and in the onAbort callback, but disappeared after a reload or memory recall — only the user message remained. Aborted runs now persist the partial assistant message to memory, matching the regular agent's behavior. Fixes #22593 ([#22872](https://github.com/mastra-ai/mastra/pull/22872)) - Fixed UnixSocketPubSub accepting unbounded inbound frames. Added a `maxInboundFrameBytes` option (default 64 MiB); a connection that sends a larger frame, or an unterminated partial frame beyond that size, is disconnected, and partial frames are buffered compactly regardless of how they are fragmented, so a single peer can no longer exhaust broker memory. Fixes #22376 ([#22882](https://github.com/mastra-ai/mastra/pull/22882)) ```ts import { UnixSocketPubSub } from '@mastra/core/events'; const pubsub = new UnixSocketPubSub('/tmp/mastra.sock', { maxInboundFrameBytes: 8 * 1024 * 1024, // 8 MiB }); ``` ## 1.64.0-alpha.8 ### Patch Changes - Fixed tool observability helpers to emit correlated logs and child spans during agent execution. ([#22887](https://github.com/mastra-ai/mastra/pull/22887)) ## 1.64.0-alpha.7 ### Minor Changes - Added support for server-defined `toModelOutput` on client-side tools. When a tool without an `execute` function runs in the browser and sends its result back, the server tool definition's `toModelOutput` now transforms that result before the model sees it — matching AI SDK behavior. This lets a client tool return a compact payload (like an uploaded file id or base64 image) and have the server map it into real model content: ([#22437](https://github.com/mastra-ai/mastra/pull/22437)) ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; // No execute: the browser runs this tool and returns { fileId, dataUrl } const takeScreenshot = createTool({ id: 'takeScreenshot', description: 'Captures the screen', inputSchema: z.object({}), outputSchema: z.object({ fileId: z.string(), dataUrl: z.string() }), toModelOutput: output => ({ type: 'content', value: [{ type: 'image-url', url: output.dataUrl }], }), }); ``` Previously the model only ever saw the raw JSON tool result and transforming it required a custom input processor. ### Patch Changes - Update README to include accurate, up-to-date information ([#22858](https://github.com/mastra-ai/mastra/pull/22858)) - Fixed onDelegationComplete reporting successful sub-agent runs when the finish reason is error. ([#22483](https://github.com/mastra-ai/mastra/pull/22483)) - Updated dependencies [[`e983f74`](https://github.com/mastra-ai/mastra/commit/e983f749873189f767f509eb33d1a3596c0f1c74)]: - @mastra/schema-compat@1.3.8-alpha.1 ## 1.64.0-alpha.6 ### Patch Changes - Fixed approval event delivery for function-form `toolDisplay` in streaming channels. A function-form `toolDisplay` now receives `approval` events when `streaming: true`, matching static mode. Return `{ kind: 'post', message }` to replace the built-in approval card; `undefined`, blank, or `stream` results fall back to the built-in card so the approval stays actionable. ([#22832](https://github.com/mastra-ai/mastra/pull/22832)) - Fixed per-tool `requireApproval` functions (`needsApprovalFn`) receiving no context on durable agents and `agent.network()`. They now get the same `{ requestContext, workspace }` second argument as `stream()`/`generate()`, so approval logic that reads the request context works consistently. On durable agents the request context is restored from the persisted run snapshot when the check runs in another process or after a resume. Fixes #22491 ([#22841](https://github.com/mastra-ai/mastra/pull/22841)) - Fixed workspace skill discovery silently reporting zero skills when the workspace filesystem is mis-wired. Invalid-argument errors (Node `ERR_INVALID_ARG*`, e.g. a non-string path handed to the skill source) now surface from refresh() instead of being logged as an inaccessible skills path warning. Genuine access failures and network errors keep the warn-and-continue behavior. Closes https://github.com/mastra-ai/mastra/issues/22639 ([#22823](https://github.com/mastra-ai/mastra/pull/22823)) - Fixed `listResolved()` on versioned storage domains issuing one version query per listed entity. `@mastra/core` adds an overridable `getVersions(ids)` method that `listResolved()` uses to fetch all active versions in a single batch; adapters that don't override it keep their previous per-id behavior. `@mastra/pg` and `@mastra/libsql` override it for the agents and skills domains with a single `WHERE id IN (...)` query. Fixes https://github.com/mastra-ai/mastra/issues/22524 ([#22828](https://github.com/mastra-ai/mastra/pull/22828)) ## 1.64.0-alpha.5 ### Patch Changes - Fixed `SlashCommandChannelHandler`, `SlashCommandChannelHandlerConfig`, and `SlashCommandEvent` not being exported from `@mastra/core/channels`. Standalone slash-command handlers can now be typed directly instead of reaching through `ChannelHandlers['onSlashCommand']`. ([#22814](https://github.com/mastra-ai/mastra/pull/22814)) ```ts import type { SlashCommandChannelHandler } from '@mastra/core/channels'; const onSlashCommand: SlashCommandChannelHandler = async (event, defaultHandler) => { if (event.command === '/help') { await event.channel.post('Available commands: /help'); return; } await defaultHandler(); }; ``` - Exported the validateToolOutput helper from @mastra/core/tools so integrations can validate tool results against a schema and produce the same structured validation error as createTool. ([#22779](https://github.com/mastra-ai/mastra/pull/22779)) - Fixed signals sent after DurableAgent recovery so they are drained by the resumed run. ([#22781](https://github.com/mastra-ai/mastra/pull/22781)) - Fixed background tasks advertising the `_background` override to every tool. Previously, enabling `backgroundTasks` on the Mastra instance injected the `_background` field into every tool's input schema and listed every tool as background-eligible in the system prompt, even when neither the agent nor the tool opted in. Now only tools that are actually background-eligible — via the agent's `backgroundTasks.tools` config or the tool's own `background: { enabled: true }` — advertise the override, matching the runtime dispatch behavior. This removes roughly 2,000 characters of prompt overhead per ineligible tool and stops the model from being told it can background tools it cannot. Fixes [#22724](https://github.com/mastra-ai/mastra/issues/22724). ([#22777](https://github.com/mastra-ai/mastra/pull/22777)) - Fixed scheduled workflows disappearing from Studio's Schedules tab for dynamically created workflows. Dynamic workflow definitions now persist their schedule configuration, so schedules are re-declared after a restart instead of being deleted as orphans. Schedules of dynamic workflows that fail to load are also kept instead of being swept. Fixes https://github.com/mastra-ai/mastra/issues/22756 ([#22778](https://github.com/mastra-ai/mastra/pull/22778)) ## 1.64.0-alpha.4 ### Patch Changes - Fixed LocalSandbox native isolation being unable to open /dev/null, which broke git, ssh, and shell redirections (e.g. `2>/dev/null`) inside the sandbox. On Linux, the Bubblewrap backend now mounts a fresh /dev with standard device nodes, emitted after all configured binds so existing workarounds like `readOnlyPaths: ['/dev']` no longer shadow it. On macOS, the Seatbelt profile now allows writing to the standard device nodes (/dev/null, /dev/zero, /dev/random, /dev/urandom, /dev/tty). Fixes https://github.com/mastra-ai/mastra/issues/22702 ([#22791](https://github.com/mastra-ai/mastra/pull/22791)) - Removed the vulnerable @ai-sdk/provider-utils@3.x dependency (CVE-2026-8769 / GHSA-866g-f22w-33x8) from @mastra/core. The helpers it supplied are now sourced from the patched provider-utils 4.x line already installed, so security audits no longer flag @mastra/core and its dependents. AI SDK v5 model compatibility is unchanged. Fixes #22592. ([#22790](https://github.com/mastra-ai/mastra/pull/22790)) ## 1.64.0-alpha.3 ### Patch Changes - Fixed AgentController dropping streamed assistant text and reasoning after page reload. Text and reasoning deltas that arrive without a seeded part (for example after a mid-stream step rotation) are now folded into the message instead of being silently discarded. Fixes #22712 ([#22775](https://github.com/mastra-ai/mastra/pull/22775)) ## 1.64.0-alpha.2 ### Minor Changes - **Added a `workingDirectory` option to `MastraSandboxOptions`, honored by every sandbox provider** ([#22697](https://github.com/mastra-ai/mastra/pull/22697)) Every sandbox now accepts one instance-level `workingDirectory` option that sets the default directory for command execution and process spawns. A per-command `cwd` always wins over it, and when neither is provided each provider keeps its previous default (E2B home, docker `/workspace`, Vercel serverless `/tmp`, and so on). The effective value is readable through the new `sandbox.workingDirectory` getter. ```ts const sandbox = new E2BSandbox({ workingDirectory: '/home/user/my-repo' }); await sandbox.executeCommand('pwd'); // /home/user/my-repo await sandbox.executeCommand('pwd', [], { cwd: '/tmp' }); // /tmp ``` Providers that already carried this concept under other names keep those names working as deprecated aliases feeding the same field: `workingDir` on `@mastra/docker` and `@mastra/apple-container`, and `workdir` on `@mastra/modal`. When both the alias and `workingDirectory` are set, `workingDirectory` wins. Use absolute paths: the value is passed to the provider as-is, so `~` and environment variables like `$HOME` are not expanded (except where a provider documents expansion, such as `LocalSandbox` expanding `~`). ### Patch Changes - Fixed durable agent traces being polluted by output-stream processor spans. The durable per-chunk processor pipeline ran without a tracing context, so every `output stream processor` span exported with no parent. Span stores that label a trace by its newest root row then showed a processor id instead of the agent. ([#22677](https://github.com/mastra-ai/mastra/pull/22677)) - Output-stream processor spans now nest under the run's `agent run` span. - Tool-call chunks and resumed runs parent their processor spans the same way. - The tool-call pipeline ends its processor spans right after each chunk, so none stay open. - Callers without a tracing context no longer create processor spans, so orphan trace roots can never appear. Fixes #22602 - Fixed observability signals from Studio (mastra dev) being tagged with environment: production. Runs started through mastra dev now resolve to development unless an explicit environment is configured. Fixes #21941 ([#22734](https://github.com/mastra-ai/mastra/pull/22734)) - Record TripWire aborts on workflow-path PROCESSOR_RUN spans as span errors with the structured `tripwireAbort` attribute (reason, retry, metadata), matching the legacy processor-runner path. Previously these spans ended like successful runs with only `output.tripwire`, dropping the retry flag and error info. ([#22350](https://github.com/mastra-ai/mastra/pull/22350)) - Fixed span metadata values being silently erased by keys whose value is undefined. Values extracted via requestContextKeys (for example a threadId set on a RequestContext) now reach exported spans even when the agent has no memory configured, so exporters like Arize can group traces into sessions again. Keys passed in tracingOptions.metadata with undefined values no longer remove values the span already has; keys with real values still take precedence. Fixes [#22597](https://github.com/mastra-ai/mastra/issues/22597). ([#22742](https://github.com/mastra-ai/mastra/pull/22742)) - Remove `CHANGELOG.md` from distributed npm files resulting in reduced package size ([#22737](https://github.com/mastra-ai/mastra/pull/22737)) - Updated dependencies [[`28ce924`](https://github.com/mastra-ai/mastra/commit/28ce924276eeca492e6a360e5482ed20c2785ef6)]: - @mastra/schema-compat@1.3.8-alpha.0 ## 1.63.3-alpha.1 ### Patch Changes - Fixed plan approvals so mode changes resume the original agent run. ([#22476](https://github.com/mastra-ai/mastra/pull/22476)) - Fixed durable agent output processors receiving an empty request context, including after server restarts. ([#22327](https://github.com/mastra-ai/mastra/pull/22327)) - Fixed approval resumes when Agent Controller uses in-memory storage. ([#22476](https://github.com/mastra-ai/mastra/pull/22476)) ## 1.63.3-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`3910c77`](https://github.com/mastra-ai/mastra/commit/3910c77413a3058ab270c6dbc74a59bc3cdf67ea)) ## 1.63.2 ### Patch Changes - Fixed Anthropic tool continuations after switching providers by removing incompatible reasoning history. ([#22568](https://github.com/mastra-ai/mastra/pull/22568)) ## 1.63.2-alpha.0 ### Patch Changes - Fixed Anthropic tool continuations after switching providers by removing incompatible reasoning history. ([#22568](https://github.com/mastra-ai/mastra/pull/22568)) ## 1.63.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`bae1502`](https://github.com/mastra-ai/mastra/commit/bae150254b06a4da6964d7c137af97f336362359)) - Add an optional bounded description field to knowledge nodes across storage adapters, written through a dedicated curator tool. Part of an unreleased experimental memory feature. ([#21830](https://github.com/mastra-ai/mastra/pull/21830)) - Added anonymous feature usage telemetry that reports once per project when file-based agents are used. ([#22457](https://github.com/mastra-ai/mastra/pull/22457)) - Fixed a spurious "Failed to resolve versioned agent" warning when calling an agent in a project without the editor package configured. Agent calls now fall back to your code-defined agent silently; looking up a specific version ID still reports that the editor is required. ([#22471](https://github.com/mastra-ai/mastra/pull/22471)) - Include LICENSE.md file in the published package. ([#22474](https://github.com/mastra-ai/mastra/pull/22474)) - Fixed output stream processors losing their observability data after the first step of a multi-step agent run. Tripwire aborts from processors like `TokenLimiterProcessor` (`strategy: 'abort'`) that fire in a later step now show up on the `processor_run` span instead of an empty span. ([#22470](https://github.com/mastra-ai/mastra/pull/22470)) ## 1.63.1-alpha.3 ### Patch Changes - Added anonymous feature usage telemetry that reports once per project when file-based agents are used. ([#22457](https://github.com/mastra-ai/mastra/pull/22457)) ## 1.63.1-alpha.2 ### Patch Changes - Add an optional bounded description field to knowledge nodes across storage adapters, written through a dedicated curator tool. Part of an unreleased experimental memory feature. ([#21830](https://github.com/mastra-ai/mastra/pull/21830)) ## 1.63.1-alpha.1 ### Patch Changes - Fixed a spurious "Failed to resolve versioned agent" warning when calling an agent in a project without the editor package configured. Agent calls now fall back to your code-defined agent silently; looking up a specific version ID still reports that the editor is required. ([#22471](https://github.com/mastra-ai/mastra/pull/22471)) - Include LICENSE.md file in the published package. ([#22474](https://github.com/mastra-ai/mastra/pull/22474)) - Fixed output stream processors losing their observability data after the first step of a multi-step agent run. Tripwire aborts from processors like `TokenLimiterProcessor` (`strategy: 'abort'`) that fire in a later step now show up on the `processor_run` span instead of an empty span. ([#22470](https://github.com/mastra-ai/mastra/pull/22470)) ## 1.63.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`bae1502`](https://github.com/mastra-ai/mastra/commit/bae150254b06a4da6964d7c137af97f336362359)) ## 1.63.0 ### Minor Changes - Added a logger adapter contract (`AdaptableLogger` in `@mastra/core/logger`) for trace-correlated log output. Loggers implementing the adapter inject `trace_id` and `span_id` into their native log records during traced operations, and the observability `LogEvent` is derived from that same record. The built-in `ConsoleLogger` implements the contract. A new `loggerOptions` config on `Mastra` controls the behavior: ([#21753](https://github.com/mastra-ai/mastra/pull/21753)) ```typescript import { Mastra } from '@mastra/core/mastra'; export const mastra = new Mastra({ loggerOptions: { correlation: true, // inject trace_id/span_id into native output (default: true) export: true, // forward log records to observability storage (default: true) }, }); ``` Custom `IMastraLogger` implementations without adapter support continue to work through the existing dual-write wrapper, which is now deprecated and will be removed in the next major version. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7176362`](https://github.com/mastra-ai/mastra/commit/717636281a3339911a05ea2cc8ae38afe4fd2cef)) - Fixed worker processes to discover and run schedules created after startup without requiring a restart or explicit scheduler configuration. ([#22420](https://github.com/mastra-ai/mastra/pull/22420)) - Fixed output stream processor traces remaining open when streams close or are canceled before a finish chunk. ([#22397](https://github.com/mastra-ai/mastra/pull/22397)) - Added an optional `getExportedSpanId()` method to the `Span` interface. It returns the span's own id when the span reaches exporters, and the nearest exportable ancestor's id when it does not. ([#22286](https://github.com/mastra-ai/mastra/pull/22286)) Suspending workflow and agent runs now use it, so a resumed run links to a span that was actually exported instead of leaving its child spans orphaned in the trace. - Fixed resuming a tool with a falsy resume payload. ([#22363](https://github.com/mastra-ai/mastra/pull/22363)) A tool whose `resumeSchema` is a primitive can be resumed with `false`, `0` or `""`, and `false` is how a boolean human-in-the-loop tool declines. Those payloads were read as "no resume data". **Background tasks no longer start a second run** Resuming a suspended background task with one of those payloads left the task suspended forever and dispatched a new one. It now resumes the existing task. **Same-run resumes keep their tool call** A reloading client now still receives the replayed tool-call chunk, and the tool call is still recorded to memory when no matching invocation is on the message list. - Fixed stdout log lines carrying a span id that cannot be looked up. ([#22387](https://github.com/mastra-ai/mastra/pull/22387)) When trace correlation is enabled, `trace_id`/`span_id` are injected into structured stdout logs. A log emitted inside an internal span, or one dropped by `excludeSpanTypes`, was tagged with that span's own id — but such spans are never exported, so clicking through from the logs view found nothing, and the stdout line disagreed with the stored log record for the same event. Stdout lines now carry the nearest span id that actually reaches exporters, matching the stored record. When nothing in the chain is exportable, the line keeps `trace_id` and omits `span_id` — the trace is still addressable even though no individual span is. `span_id` is now optional on `TraceFields`, so consumers parsing these lines should treat it as possibly absent. - Added storage documentation links to errors that require persistent storage. ([#22367](https://github.com/mastra-ai/mastra/pull/22367)) ## 1.63.0-alpha.1 ### Patch Changes - Fixed output stream processor traces remaining open when streams close or are canceled before a finish chunk. ([#22397](https://github.com/mastra-ai/mastra/pull/22397)) - Fixed resuming a tool with a falsy resume payload. ([#22363](https://github.com/mastra-ai/mastra/pull/22363)) A tool whose `resumeSchema` is a primitive can be resumed with `false`, `0` or `""`, and `false` is how a boolean human-in-the-loop tool declines. Those payloads were read as "no resume data". **Background tasks no longer start a second run** Resuming a suspended background task with one of those payloads left the task suspended forever and dispatched a new one. It now resumes the existing task. **Same-run resumes keep their tool call** A reloading client now still receives the replayed tool-call chunk, and the tool call is still recorded to memory when no matching invocation is on the message list. - Fixed stdout log lines carrying a span id that cannot be looked up. ([#22387](https://github.com/mastra-ai/mastra/pull/22387)) When trace correlation is enabled, `trace_id`/`span_id` are injected into structured stdout logs. A log emitted inside an internal span, or one dropped by `excludeSpanTypes`, was tagged with that span's own id — but such spans are never exported, so clicking through from the logs view found nothing, and the stdout line disagreed with the stored log record for the same event. Stdout lines now carry the nearest span id that actually reaches exporters, matching the stored record. When nothing in the chain is exportable, the line keeps `trace_id` and omits `span_id` — the trace is still addressable even though no individual span is. `span_id` is now optional on `TraceFields`, so consumers parsing these lines should treat it as possibly absent. ## 1.63.0-alpha.0 ### Minor Changes - Added a logger adapter contract (`AdaptableLogger` in `@mastra/core/logger`) for trace-correlated log output. Loggers implementing the adapter inject `trace_id` and `span_id` into their native log records during traced operations, and the observability `LogEvent` is derived from that same record. The built-in `ConsoleLogger` implements the contract. A new `loggerOptions` config on `Mastra` controls the behavior: ([#21753](https://github.com/mastra-ai/mastra/pull/21753)) ```typescript import { Mastra } from '@mastra/core/mastra'; export const mastra = new Mastra({ loggerOptions: { correlation: true, // inject trace_id/span_id into native output (default: true) export: true, // forward log records to observability storage (default: true) }, }); ``` Custom `IMastraLogger` implementations without adapter support continue to work through the existing dual-write wrapper, which is now deprecated and will be removed in the next major version. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7176362`](https://github.com/mastra-ai/mastra/commit/717636281a3339911a05ea2cc8ae38afe4fd2cef)) - Added an optional `getExportedSpanId()` method to the `Span` interface. It returns the span's own id when the span reaches exporters, and the nearest exportable ancestor's id when it does not. ([#22286](https://github.com/mastra-ai/mastra/pull/22286)) Suspending workflow and agent runs now use it, so a resumed run links to a span that was actually exported instead of leaving its child spans orphaned in the trace. - Added storage documentation links to errors that require persistent storage. ([#22367](https://github.com/mastra-ai/mastra/pull/22367)) ## 1.62.0 ### Minor Changes - Added optional collection row counts for application storage, so totals no longer require loading every matching row. ([#22021](https://github.com/mastra-ai/mastra/pull/22021)) **Before** ```ts const total = (await storage.ops.findMany('jobs', { status: 'failed' })).length; ``` **After** ```ts if (!storage.ops.count) throw new Error('Storage backend does not support counts'); const total = await storage.ops.count('jobs', { status: 'failed' }); ``` - Added metadata filtering to score queries. You can now filter scores by metadata key-value pairs when listing scores: ([#22047](https://github.com/mastra-ai/mastra/pull/22047)) ```typescript const result = await storage.listScores({ filters: { metadata: { env: 'prod' } }, }); ``` - Added `AgentController#generateThreadTitle()` — name a thread on demand from where its conversation went, with the model and instructions `generateTitle` gives the first-turn namer. ([#22156](https://github.com/mastra-ai/mastra/pull/22156)) It reads a bounded window of the thread's recent messages and writes the thread row without constructing a `Session`, so a "rename this conversation" action never spins up a workspace or sandbox for a session that is no longer live. A live session lends its agent, request context and event stream, and hears the resulting `thread_title_updated`. ```ts const title = await controller.generateThreadTitle({ threadId, resourceId, // The caller's identity, so model resolution bills their credentials. requestContext, // Optional: hosts that store the title model themselves pass it here. model: ({ requestContext }) => resolveModel(storedModelId, { requestContext }), }); ``` - Sandboxes now own their runtime environment. `MastraSandbox` accepts an `env` constructor option, and you can read or update the environment at runtime with `getEnv()` and `setEnv(updater)`: ([#22250](https://github.com/mastra-ai/mastra/pull/22250)) ```typescript sandbox.setEnv(env => ({ ...env, GH_TOKEN: token })); ``` The sandbox environment is merged into every process spawn by the base `SandboxProcessManager`, so it reaches `executeCommand()` and `processes.spawn()` on any provider whose execution routes through its process manager, including values installed or rotated after the sandbox was created (for example, refreshed credentials). Per-call `env` options take precedence for that command only. These values apply to commands executed through the sandbox; they are not VM-level environment and are never written into the VM. `WorkspaceSandbox` declares `setEnv` as an optional capability. - **Sandbox setup failures are no longer silent** ([#21984](https://github.com/mastra-ai/mastra/pull/21984)) An error thrown by an `onStart` handler now fails the start: `start()` rejects and the sandbox is marked `error`. Previously the error was logged and swallowed, so a sandbox whose setup never ran still looked healthy, and the failure surfaced later as a confusing command error. **Added optional `find()`, `connect()` and `create()` for sandbox providers** A provider can implement these three methods instead of writing `start()`. Mastra calls them in order, so providers no longer hand-roll "reuse the existing sandbox, otherwise make one", and Mastra knows which one happened. Existing providers that override `start()` keep working unchanged: ```typescript class MySandbox extends MastraSandbox { protected async find() { return (await sdk.list({ id: this.id }))[0]; } protected async connect(handle: MyHandle) { this.vm = await sdk.resume(handle); } protected async create() { this.vm = await sdk.create({ id: this.id }); } } ``` **Added the start outcome, so setup can tell a new sandbox from a resumed one** `onStart` now receives `outcome`, either `'created'` or `'connected'`, which is `undefined` for providers that don't report it: ```typescript new MySandbox({ onStart: async ({ outcome }) => { if (outcome === 'created') await installDependencies(); }, }); ``` **Improved concurrent starts** Two starts at once now share one attempt for every provider rather than only some, and a failed start is never cached, so the next call retries it. Starting a sandbox that implements neither `start()` nor `create()` throws instead of reporting itself `running` having provisioned nothing. **Added `setOnStart()`** For code handed a sandbox it didn't construct, this attaches a start hook after the fact. It takes an updater over the installed hook, so it composes with one already there instead of replacing it: ```typescript sandbox.setOnStart?.(previous => async args => { await previous?.(args); // whatever set the sandbox up runs first await mySetup(args); }); ``` - Added an optional computer-use capability for workspace sandboxes. Providers can expose `SandboxComputer` to give agents screenshot, mouse, keyboard, screen information, and wait tools automatically when the workspace uses a static sandbox. Resolver-backed sandboxes do not register computer tools because their capabilities are unavailable when the tool list is constructed. ([#21700](https://github.com/mastra-ai/mastra/pull/21700)) ```typescript const workspace = new Workspace({ sandbox }); if (supportsComputer(sandbox)) { const screenshot = await sandbox.computer.screenshot(); } ``` Computer action tools return a follow-up screenshot by default and support the existing workspace tool approval and enablement settings. - `Mastra.shutdown()` no longer destroys registered workspaces' sandboxes. Remote sandboxes (E2B, Platform, and other providers) were being killed on every process restart; they are now stopped instead, so providers that support suspension pause the sandbox and resume it later with filesystem and memory intact. ([#22079](https://github.com/mastra-ai/mastra/pull/22079)) - New `Workspace.stop()`: shuts down language servers, closes the browser, and stops the sandbox without destroying anything. The workspace stays usable and the sandbox can start again later. - `LocalSandbox.stop()` now kills background processes (previously only `destroy()` did), so the stop-on-shutdown behavior cannot leak child processes on local sandboxes. Files in the working directory are untouched. - Full teardown remains explicit: `workspace.destroy()` or `mastra.removeWorkspace(id, { destroy: true })`. ```typescript // Suspend live resources; the workspace stays usable await workspace.stop(); // Full teardown is explicit await workspace.destroy(); ``` ### Patch Changes - Fixed DurableAgent runs losing live models and tools while waiting on long-running tool or model calls. ([#22141](https://github.com/mastra-ai/mastra/pull/22141)) - Fixed channel render failures crashing the host process before stream cleanup. ([#22165](https://github.com/mastra-ai/mastra/pull/22165)) - Update provider registry and model documentation with latest models and providers ([`1e47b75`](https://github.com/mastra-ai/mastra/commit/1e47b7520cab4cfaa8daed52f17e2e6d14ff7539)) - Fixed nested workflow steps being omitted from foreach run details. ([#22144](https://github.com/mastra-ai/mastra/pull/22144)) - Fixed the Mastra gateway so custom request headers can no longer replace the internal gateway authorization header (#21780). ([#22134](https://github.com/mastra-ai/mastra/pull/22134)) - Route channel slash commands through AgentChannels handlers ([#22223](https://github.com/mastra-ai/mastra/pull/22223)) - Removed obsolete generated capability files after registry refreshes. ([#21531](https://github.com/mastra-ai/mastra/pull/21531)) - Fixed AI SDK `reasoning-file` and `custom` content parts being silently dropped from agent streams. Providers on the AI SDK v7 spec emit these parts, but Mastra discarded them when converting model output, so they never reached `fullStream` for either `generate` or `stream` calls. They now arrive as `reasoning-file` and `custom` chunks. ([#22111](https://github.com/mastra-ai/mastra/pull/22111)) Content types Mastra does not recognize are no longer discarded either. They are now emitted as `raw` chunks, so you can see them by enabling raw chunks instead of losing the data with no error or warning: ```ts const stream = await agent.stream('Hello', { includeRawChunks: true }); for await (const chunk of stream.fullStream) { if (chunk.type === 'raw') console.log(chunk.payload); } ``` - Fixed missing observability spans for the processLLMRequest and processLLMResponse processor hooks. These hooks now emit processor_run spans like every other processor hook, so they appear in traces and in the automatic processor duration metrics, and tripwire aborts from them are recorded on the span. Fixes #22342. ([#22351](https://github.com/mastra-ai/mastra/pull/22351)) - Fixed channel typing statuses staying pinned to the thread after a run ends without posting a message. When a run terminates on a tool call (for example via a stopWhen predicate), errors, or is aborted before any assistant text is posted, the typing status is now cleared instead of showing the last status indefinitely. Fixes #21880 ([#22264](https://github.com/mastra-ai/mastra/pull/22264)) - Fixed durable agent runs writing the entire conversation into storage on every step. A long run persisted the accumulated message history and step output once per completed step, so the amount written grew with the square of the run length — a 57-step run produced hundreds of megabytes in Postgres and Redis, made saves slow enough to time out, and could fail an approval with a 500. ([#22127](https://github.com/mastra-ai/mastra/pull/22127)) Historical completed steps in a still-running snapshot now keep only the fields the engine needs for routing, while the active step retains the conversation state needed for crash recovery. On a benchmark run this cut total bytes written by around 70% and peak snapshot size from 561 KB to 118 KB. Suspended and finished runs are stored exactly as before. Also fixed approvals that arrived while a large suspend was still being written. Resume read the run once and gave up if it did not yet look suspended; it now briefly re-reads snapshots that are still running or pending, while missing runs and statuses that cannot become suspended still fail immediately. Reported in https://github.com/mastra-ai/mastra/issues/20747 - Fixed suspended agent runs writing snapshots that grew quadratically with step count, which could exhaust memory on human-in-the-loop workflows using tool approval with large payloads. ([#21732](https://github.com/mastra-ai/mastra/pull/21732)) Each buffered step of a run persisted its own full copy of the conversation so far — three times over, as model messages, database messages, and UI messages. A fifteen-step run was observed writing 22 MB of buffered steps over 2 MB of distinct messages, and production runs reached 170 MB per suspended run. Snapshots now record the IDs of the response messages referenced by each step and lazily rebuild those messages from the conversation that is already stored alongside them. This preserves the correct messages even when processors move or remove messages before suspension. Steps still expose the same messages after a run resumes, so no application code needs to change, while persisted snapshot size now grows linearly with step count. Fixes [#17738](https://github.com/mastra-ai/mastra/issues/17738). - Fix `steps[i].content` and `steps[i].toolCalls` being empty for input-step processors (`processInputStep`, `prepareStep`) and in the workflow output payload. Step content was extracted with a 0-indexed step count passed to a 1-indexed extractor and then sliced by message count, so the first step came back empty and later steps were misaligned. Completed steps now carry their real content, including tool results, which are re-read just before the next step's input processors run. ([#22119](https://github.com/mastra-ai/mastra/pull/22119)) - Workspace no longer registers the lsp_inspect tool when LSP is not active, so agents are only offered the tool when it can actually run. ([#22126](https://github.com/mastra-ai/mastra/pull/22126)) - Fixed cross-provider agent conversations leaking provider-hosted tool IDs into incompatible model requests. ([#22148](https://github.com/mastra-ai/mastra/pull/22148)) - Fixed suspended agent runs resuming on a newer published version. When an agent is resolved from a status selector (for example `{ status: 'published' }`), the version the run started on is now recorded when the run suspends and reused when it resumes, so approval and human-in-the-loop flows finish on the same version they started on. New runs still pick up the latest published version. Fixes #21846 ([#22128](https://github.com/mastra-ai/mastra/pull/22128)) - Fixed streaming channels posting blank messages when a response starts with whitespace-only text. ([#22168](https://github.com/mastra-ai/mastra/pull/22168)) - Fixed `session.thread.firstUserMessage()` and `firstUserMessages()` returning nothing for agent-controller sessions. A live session persists a chat message as a `user` signal rather than a `user` row, and the lookup only matched the latter, so it came back empty for every real session. ([#22156](https://github.com/mastra-ai/mastra/pull/22156)) Added `isUserAuthoredMessage()` for the same check anywhere else, and sessions now emit a `thread_title_updated` event when a generated title lands on the thread. - Preserve response message source tracking when processors return unchanged messages in multi-step agent runs ([#22146](https://github.com/mastra-ai/mastra/pull/22146)) - Fixed Claude 4.6 and newer structured output requests that end with an assistant message, including agents without configured input processors. ([#21925](https://github.com/mastra-ai/mastra/pull/21925)) - Improved workspace grep performance on remote filesystems by searching directories and files concurrently. ([#22225](https://github.com/mastra-ai/mastra/pull/22225)) - Fixed semantic recall initialization to fail instead of silently selecting the default vector index when embedding dimensions cannot be determined. ([#22171](https://github.com/mastra-ai/mastra/pull/22171)) - Fixed `timeTravel()` on a failed `.foreach()` step re-running iterations that had already succeeded, which duplicated their side effects (publishing, billing, uploads, notifications). ([#22133](https://github.com/mastra-ai/mastra/pull/22133)) A failed foreach now records which iterations completed, so re-entering the step only runs the ones that did not. ```ts const run = await workflow.createRunAsync(); await run.start({ inputData: { items: [1, 2] } }); // item 2 fails // Previously both items ran again. Now only item 2 runs. await run.timeTravel({ step: 'process-item' }); ``` Fixes #21749 - Fixed slow streaming when a workflow is used as an output processor. The workflow runs once per streamed chunk, and each of those runs was saved to storage with the whole response so far, so a long reply got quadratically slower and heavier the more it streamed. Those per-chunk runs are now transient: they no longer write workflow snapshots or emit public traces, while the processor logic and tripwire behavior are unchanged. Running the same workflow directly still persists and traces as before. Fixes #19605. ([#22124](https://github.com/mastra-ai/mastra/pull/22124)) - Made language-server (LSP) support opt-in in Mastra Code. By default Mastra Code no longer checks for LSP dependencies, starts language servers, or offers the lsp_inspect tool to the agent. Turn it on by adding "lsp": true (or an LSP config object) to your settings.json; "lsp": false is now also accepted and preserved. ([#22126](https://github.com/mastra-ai/mastra/pull/22126)) - Fixed durable agent runs ignoring aborts requested before execution starts. ([#22145](https://github.com/mastra-ai/mastra/pull/22145)) - Fixed workflow imports crashing in environments where the global `fetch` value is not callable. ([#22246](https://github.com/mastra-ai/mastra/pull/22246)) - Fixed workspace search indexing so it can be rebuilt without starting the sandbox. ([#22245](https://github.com/mastra-ai/mastra/pull/22245)) ```typescript import { LocalFilesystem, Workspace } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), bm25: true, autoIndexPaths: ['docs'], }); await workspace.rebuildSearchIndex(); ``` - Fixed `run.cancel()` leaving a workflow's spans open when a step ignores `abortSignal`. ([#22278](https://github.com/mastra-ai/mastra/pull/22278)) A step that never observes the abort signal keeps running, so the execution engine never unwinds and never ends the run's spans. Exporters that only act on span-end events (Langfuse, Mastra Cloud, and any OpenTelemetry-based exporter) therefore never received the run span or any of its ancestors, leaving traces without input/output, tags, or cost, and turning every step that did finish into an orphan. `cancel()` now closes the run's span tree itself, on both the default and evented engines. ```ts const run = await workflow.createRun(); run.start({ inputData: {} }); // The trace is now closed and exported even if the running step never returns await run.cancel(); ``` - Fixed malformed message parts crashing OpenAI-compatible providers when observational memory is enabled. ([#22069](https://github.com/mastra-ai/mastra/pull/22069)) - Fixed EventedAgent leaving finished runs' snapshot rows in storage. Completed runs no longer show up in listActiveRuns() or get re-executed by recoverActiveRuns() (#22209). The evented workflow engine now also deletes a run's snapshot row when it reaches a non-paused terminal status that the workflow's shouldPersistSnapshot option declined to persist, instead of leaving a stale row behind. ([#22266](https://github.com/mastra-ai/mastra/pull/22266)) - Fixed the `onDelegationComplete` hook so its `result` now includes `finishReason` (on both `generate()` and `stream()` paths) and its type declares `subAgentToolResults`. Hooks can now tell whether a sub-agent actually finished (`finishReason: 'stop'`) or was cut off mid tool-call, and can read sub-agent tool results without casting. Fixes #21942. ([#22311](https://github.com/mastra-ai/mastra/pull/22311)) - Add a `/context` command (alias `/ctx`) to Mastra Code that reports what is occupying the context window. ([#22131](https://github.com/mastra-ai/mastra/pull/22131)) The report separates the startup context — system prompt, AGENTS.md/CLAUDE.md instructions with their source paths, the skills catalog, and MCP tool definitions rolled up per server — from context that accumulates during the session, namely the conversation itself and any observation memory injected into it. Each line carries an estimated token count and its share of the audited total, so it is possible to see which server, instruction file, or skill set is worth pruning. The audit reports only sizes and labels, never the audited content, and is printed to the terminal without being added to the conversation, so running it does not enlarge the context it describes. To support exact measurement, `@mastra/core` now exports `formatSkillsCatalog`, the pure formatter behind the skills processor's `` block, and `@mastra/code-sdk` exposes the assembled system prompt as labeled sections. - Experiment results now include isolated metadata snapshots from the dataset items that ran. ([#22005](https://github.com/mastra-ai/mastra/pull/22005)) - Fixed background tasks restarting after reaching a terminal or suspended state. ([#22140](https://github.com/mastra-ai/mastra/pull/22140)) - Fixed evented parallel and conditional workflows losing setState() updates from sibling branches; state changes from every branch are now merged into the workflow state (fixes #22319) ([#22353](https://github.com/mastra-ai/mastra/pull/22353)) - Fixed background tasks failing to resume suspended sub-agent runs. ([#22139](https://github.com/mastra-ai/mastra/pull/22139)) - Fixed nested workflows to preserve non-retryable failures across parent retry boundaries. ([#22138](https://github.com/mastra-ai/mastra/pull/22138)) - Fixed concurrent agent.stream() calls on the same thread racing instead of serializing. A second stream() call on the same agent, thread, and resource now waits for the active run to finish before recalling memory, so each turn sees the previous turn's messages and message history is persisted in turn-complete order. Read-only runs and resumed/suspended runs are not affected. Fixes https://github.com/mastra-ai/mastra/issues/21906 ([#22312](https://github.com/mastra-ai/mastra/pull/22312)) - Fixed suspended run cleanup so cross-instance resumes keep their active thread lease. ([#22150](https://github.com/mastra-ai/mastra/pull/22150)) - Purge deduped transient signals from MessageList source-tracking sets so removed messages no longer leave stale entries behind ([#22107](https://github.com/mastra-ai/mastra/pull/22107)) - Stopped internal agent-loop workflows from logging a `shouldPersistSnapshot excludes the "running" status` warning on every resume. Workflows can now set `options.allowUnclaimedResumes: true` to acknowledge that resume claims cannot be persisted (because `running` snapshots are intentionally not written) and suppress the per-resume warning; the built-in agentic-loop, agentic-execution, and agent-network workflows opt in. User workflows that exclude `running` without acknowledging still get the warning. ([#22262](https://github.com/mastra-ai/mastra/pull/22262)) - Prevent stale-build instances from executing scheduled workflow runs. ([#22125](https://github.com/mastra-ai/mastra/pull/22125)) A scheduled fire carries only a workflow id, so whichever process consumes it resolves the step graph from its _own_ registry. During a rollout a not-yet-cycled instance from the previous deploy could therefore run an outdated step graph — skipping steps the current build had added, including gates meant to stop the workflow from running at all — while HTTP runs of the same workflow on the same deployment executed the current graph (#19169). This is now fenced on both sides: - Declarative schedule rows record a hash of the workflow's serialized step graph. The scheduler refuses to claim a due fire when its local definition doesn't match the row, leaving the fire for an instance running the current build. If no instance claims it for several consecutive ticks, the scheduler escalates to an error and records a failed trigger rather than stalling silently — it never forces a stale fire through. - When the claiming process also runs workflow execution, the fire is published `localOnly` so the instance that verified the definition is the instance that runs it. Scheduler-only deployments, where pinning locally would strand the fire, keep publishing to the shared topic. - Fires that do reach the shared topic now carry the schedule's definition hash, and a consumer whose locally registered workflow hashes differently refuses the run and records a failed trigger instead of executing an outdated graph. Rows without a hash (legacy or imperatively created schedules) and agent schedules fail open and are unaffected. - Add declarative eligibility filters to scorer bindings, evaluated before sampling. ([#22010](https://github.com/mastra-ai/mastra/pull/22010)) A scorer binding can now declare `filter`, a JSON-safe predicate over the scoring context (`requestContext`, `entity`, `entityType`, `source`, `threadId`, `resourceId`, `projectId`) using the shared predicate DSL. Filters run before sampling, so the sampling rate applies to qualifying traffic only — e.g. score escalated conversations at 100% by filtering on `requestContext.escalated` in one binding, and sample the rest at 5% in another. ```ts scorers: { groundedness: { scorer: groundednessScorer, filter: { op: 'eq', left: { path: 'requestContext.protocolVersion' }, right: { literal: 'v3' }, }, sampling: { type: 'ratio', rate: 0.05 }, }, } ``` Details: - Filters are validated at definition time: paths referencing unknown roots throw at agent construction (or when function-based scorer configs resolve), instead of silently skipping all scoring at runtime. - Filters evaluate against the flattened `requestContext` view that is persisted on score rows, so a filter remains answerable against stored records. - Filters are plain JSON and survive durable-agent serialization round-trips unchanged. - Scoring filters use the same predicate format as workflow `conditional`/`loop` conditions; existing workflow predicate behavior is unchanged. - Fixed: an unrecognized `sampling.type` previously fell through to scoring 100% of traffic; it now fails closed and skips scoring. - Make scorer sampling deterministic and tie it to the trace sampling decision ([#22008](https://github.com/mastra-ai/mastra/pull/22008)) Ratio-based scorer sampling previously called `Math.random()` on every scorer invocation. It now hashes the trace ID (falling back to the run ID when observability is not configured), so the decision is reproducible and every scorer at a given rate selects the same traces. Two scorers sampling at 10% now cover the same 10% of traffic instead of overlapping on roughly 1% by chance, which makes their scores comparable on the same population. Scorers also now respect the tracing sampler. If the tracer declined a trace, scorers no longer run against it — those scores referenced a trace that was never stored and could not be drilled into. Two behavior changes when upgrading: - **Score volume drops if your trace sampling rate is lower than your scorer sampling rate.** The scores you lose are ones whose traces were never recorded. To keep the same score volume, raise your trace sampling to cover the traffic you score (or lower your scorer sampling to match — the effective rate is now bounded by the trace rate): ```ts // Before: scorer sampled 50% of invocations, but only 10% had a stored trace, // so up to 40% of scores referenced traces that were never recorded. new Observability({ configs: { default: { sampling: { type: 'ratio', probability: 0.1 }, exporters } }, }); new Agent({ scorers: { myScorer: { scorer: myScorer, sampling: { type: 'ratio', rate: 0.5 } } }, }); // After: raise trace sampling so every sampled score has a stored trace. new Observability({ configs: { default: { sampling: { type: 'ratio', probability: 0.5 }, exporters } }, }); new Agent({ scorers: { myScorer: { scorer: myScorer, sampling: { type: 'ratio', rate: 0.5 } } }, }); ``` - **Which traces get scored changes, even at an unchanged rate.** Score counts before and after the upgrade are drawn from a different set of traces, not just a different number. Scoring is unaffected when observability is not configured: with no tracing set up there is no trace decision to inherit, so scorers run as before, now keyed deterministically on run ID. - Fixed experiments dropping a dataset item's expected trajectory. `dataset.startExperiment` and `runExperiment` now pass each item's `expectedTrajectory` to trajectory scorers, so a trajectory that matches the expectation scores correctly instead of always scoring 0. Inline experiment data can now supply `expectedTrajectory` too. Fixes #21743 ([#22122](https://github.com/mastra-ai/mastra/pull/22122)) - Fixed cleared AgentController session preferences so they are removed from thread metadata and stay cleared after restart. ([#22198](https://github.com/mastra-ai/mastra/pull/22198)) - Fixed recursive tool preparation for cyclic sub-agent graphs when background tasks are disabled. ([#22244](https://github.com/mastra-ai/mastra/pull/22244)) - Fixed agent model calls to honor `modelSettings.maxRetries` when no retry count is explicitly configured on the agent or fallback model. Agents with no retry configuration continue to make a single attempt by default, while explicit agent or fallback settings—including `maxRetries: 0`—continue to override call-time settings. ([#21947](https://github.com/mastra-ai/mastra/pull/21947)) - Fix Workspace tool output-validation errors reaching the model as untagged objects. `sandboxToModelOutput` now converts Mastra validation-error envelopes to AI SDK `{ type: 'error-json', value }` tool results, so OpenAI-compatible providers serialize a tool message with valid `content` instead of rejecting the request. ([#22306](https://github.com/mastra-ai/mastra/pull/22306)) - Fixed delegated (supervisor) resume dropping a sub-agent leaf tool's writer.custom() data frames from the parent stream. Resumed delegations now continue on the delegation thread persisted by the suspended run instead of generating a new one, so approval-gated tools that emit custom data frames keep streaming them after approval. Also, a failure to save a custom data frame to memory no longer removes it from the stream. Fixes https://github.com/mastra-ai/mastra/issues/22217 ([#22277](https://github.com/mastra-ai/mastra/pull/22277)) - Fixed transient signals duplicating within a turn. A processor that re-sends a transient reminder (e.g. via sendSignal with transient: true in processInputStep) now keeps a single fresh copy near the latest message instead of accumulating one copy per model call. Fixes https://github.com/mastra-ai/mastra/issues/22060 ([#22104](https://github.com/mastra-ai/mastra/pull/22104)) - Reduced the size of stored agent run snapshots. Completed steps of an agent run no longer keep a copy of the whole conversation, which previously made snapshots grow with every step of every turn and could push long conversations past a storage provider's document size limit. Resuming a suspended run, tool approvals, and run recovery are unchanged. ([#22123](https://github.com/mastra-ai/mastra/pull/22123)) - Improved agent turn latency by reusing run-scoped memory reads ([#22076](https://github.com/mastra-ai/mastra/pull/22076)) - Documented that `server.middleware` handlers use Hono's signature and which serving paths run them. ([#22161](https://github.com/mastra-ai/mastra/pull/22161)) - Fixed built-in memory processors so canceled agent runs retain transcript history through normal output processing. Removed the accidental partial-abort persistence option because cancellation persistence is no longer opt-in. ([#22243](https://github.com/mastra-ai/mastra/pull/22243)) ```ts // Before await agent.stream('Hello', { abortSignal, persistPartialOnAbort: true }); // Now await agent.stream('Hello', { abortSignal }); ``` - Fixed `Agent.listSuspendedRuns()` and `DurableAgent.listActiveRuns()` ignoring the `resourceId` filter at the storage level. The filter is now pushed down to the workflow storage query instead of being applied in JavaScript after fetching every snapshot in the date range, so scoped polling no longer deserializes unrelated runs. Durable and evented agents now also record the `resourceId` on their workflow runs when they are created. See https://github.com/mastra-ai/mastra/issues/21844 ([#22105](https://github.com/mastra-ai/mastra/pull/22105)) - Cancelling a durable agent run that is already executing now works through the agent APIs. `agent.abortThreadStream({ resourceId, threadId })` and `agent.abortRunStream(runId)` only ever reached the abort controller a regular run prepares, which a durable run does not have, so they recorded a cancellation nothing acted on while the run streamed on. The server route `POST /agents/:agentId/threads/abort` goes through the same call. Both now publish the durable abort request as well, the same one the `abort()` on a stream result publishes, so the run stops in whichever process is executing it. ([#22255](https://github.com/mastra-ai/mastra/pull/22255)) ```ts const { runId } = await durableAgent.stream('...', { memory: { thread, resource } }); // before: the cancellation was recorded and the run streamed on to completion // after: the run stops and onAbort fires durableAgent.abortRunStream(runId); ``` - Added a `denied` flag to the agent controller `tool_end` event. Approval-denied tool calls and tools aborted while parked at an approval gate already emit `tool_end` with `isError: false` (the tool didn't fail — it never ran), which made them indistinguishable from a real successful completion. Subscribers that need to know whether the tool actually did work can now gate on `denied !== true`. ([#22213](https://github.com/mastra-ai/mastra/pull/22213)) ```ts session.subscribe(event => { if (event.type !== 'tool_end') return; if (event.isError) return; // tool ran and failed if (event.denied) return; // tool was approval-denied or aborted before it ran // ...tool actually executed successfully }); ``` - Fixed agent skill search to rank relevant instructions and references instead of relying on literal substring matches. ([#22174](https://github.com/mastra-ai/mastra/pull/22174)) - Fixed resumed sub-agent delegations failing thread-ownership validation. The resume path backfilled the thread from the suspended run's snapshot but still passed a freshly generated resource ID, so resuming a delegated run threw "A thread can only be used by the resource that owns it". Both the thread and resource are now restored from the snapshot on resume. Also fixed model stream transport handles (e.g. WebSocket routing) being dropped when `modelSettings.timeout` wraps the model stream. ([#22287](https://github.com/mastra-ai/mastra/pull/22287)) - Prevent background task dispatch from overwriting concurrent cancellation ([#22228](https://github.com/mastra-ai/mastra/pull/22228)) - Enforce atomic conditional background task state updates so cancellation cannot be overwritten during dispatch. Background task storage is no longer exposed by Cloudflare KV or ClickHouse, which cannot provide the required compare-and-set semantics. ([#22228](https://github.com/mastra-ai/mastra/pull/22228)) - Fixed grouped channel streams closing between agent steps. ([#22173](https://github.com/mastra-ai/mastra/pull/22173)) - Fixed Anthropic 400 errors ("thinking or redacted_thinking blocks in the latest assistant message cannot be modified") during tool-use turns with extended thinking. ProviderHistoryCompat no longer strips reasoning parts from the trailing assistant message of an active tool-use continuation, which Anthropic requires to be replayed exactly as it was generated. Historical assistant messages are still sanitized. ([#22099](https://github.com/mastra-ai/mastra/pull/22099)) - Fixed an infinite agent loop when a provider stream ends with finish reason "other" without producing any output. The agentic loop previously re-issued the identical request until maxSteps was reached, silently burning tokens. Such zero-output streams are now treated as a stream error so error processors can retry a bounded number of times before failing loudly. Streams that finish with reason "other" after producing output continue as before. Fixes #21897 ([#22273](https://github.com/mastra-ai/mastra/pull/22273)) ## 1.62.0-alpha.12 ### Minor Changes - Added an optional computer-use capability for workspace sandboxes. Providers can expose `SandboxComputer` to give agents screenshot, mouse, keyboard, screen information, and wait tools automatically when the workspace uses a static sandbox. Resolver-backed sandboxes do not register computer tools because their capabilities are unavailable when the tool list is constructed. ([#21700](https://github.com/mastra-ai/mastra/pull/21700)) ```typescript const workspace = new Workspace({ sandbox }); if (supportsComputer(sandbox)) { const screenshot = await sandbox.computer.screenshot(); } ``` Computer action tools return a follow-up screenshot by default and support the existing workspace tool approval and enablement settings. ### Patch Changes - Fixed missing observability spans for the processLLMRequest and processLLMResponse processor hooks. These hooks now emit processor_run spans like every other processor hook, so they appear in traces and in the automatic processor duration metrics, and tripwire aborts from them are recorded on the span. Fixes #22342. ([#22351](https://github.com/mastra-ai/mastra/pull/22351)) - Fixed evented parallel and conditional workflows losing setState() updates from sibling branches; state changes from every branch are now merged into the workflow state (fixes #22319) ([#22353](https://github.com/mastra-ai/mastra/pull/22353)) ## 1.62.0-alpha.11 ### Minor Changes - **Sandbox setup failures are no longer silent** ([#21984](https://github.com/mastra-ai/mastra/pull/21984)) An error thrown by an `onStart` handler now fails the start: `start()` rejects and the sandbox is marked `error`. Previously the error was logged and swallowed, so a sandbox whose setup never ran still looked healthy, and the failure surfaced later as a confusing command error. **Added optional `find()`, `connect()` and `create()` for sandbox providers** A provider can implement these three methods instead of writing `start()`. Mastra calls them in order, so providers no longer hand-roll "reuse the existing sandbox, otherwise make one", and Mastra knows which one happened. Existing providers that override `start()` keep working unchanged: ```typescript class MySandbox extends MastraSandbox { protected async find() { return (await sdk.list({ id: this.id }))[0]; } protected async connect(handle: MyHandle) { this.vm = await sdk.resume(handle); } protected async create() { this.vm = await sdk.create({ id: this.id }); } } ``` **Added the start outcome, so setup can tell a new sandbox from a resumed one** `onStart` now receives `outcome`, either `'created'` or `'connected'`, which is `undefined` for providers that don't report it: ```typescript new MySandbox({ onStart: async ({ outcome }) => { if (outcome === 'created') await installDependencies(); }, }); ``` **Improved concurrent starts** Two starts at once now share one attempt for every provider rather than only some, and a failed start is never cached, so the next call retries it. Starting a sandbox that implements neither `start()` nor `create()` throws instead of reporting itself `running` having provisioned nothing. **Added `setOnStart()`** For code handed a sandbox it didn't construct, this attaches a start hook after the fact. It takes an updater over the installed hook, so it composes with one already there instead of replacing it: ```typescript sandbox.setOnStart?.(previous => async args => { await previous?.(args); // whatever set the sandbox up runs first await mySetup(args); }); ``` ### Patch Changes - Fixed the `onDelegationComplete` hook so its `result` now includes `finishReason` (on both `generate()` and `stream()` paths) and its type declares `subAgentToolResults`. Hooks can now tell whether a sub-agent actually finished (`finishReason: 'stop'`) or was cut off mid tool-call, and can read sub-agent tool results without casting. Fixes #21942. ([#22311](https://github.com/mastra-ai/mastra/pull/22311)) - Fixed concurrent agent.stream() calls on the same thread racing instead of serializing. A second stream() call on the same agent, thread, and resource now waits for the active run to finish before recalling memory, so each turn sees the previous turn's messages and message history is persisted in turn-complete order. Read-only runs and resumed/suspended runs are not affected. Fixes https://github.com/mastra-ai/mastra/issues/21906 ([#22312](https://github.com/mastra-ai/mastra/pull/22312)) - Fix Workspace tool output-validation errors reaching the model as untagged objects. `sandboxToModelOutput` now converts Mastra validation-error envelopes to AI SDK `{ type: 'error-json', value }` tool results, so OpenAI-compatible providers serialize a tool message with valid `content` instead of rejecting the request. ([#22306](https://github.com/mastra-ai/mastra/pull/22306)) - Fixed resumed sub-agent delegations failing thread-ownership validation. The resume path backfilled the thread from the suspended run's snapshot but still passed a freshly generated resource ID, so resuming a delegated run threw "A thread can only be used by the resource that owns it". Both the thread and resource are now restored from the snapshot on resume. Also fixed model stream transport handles (e.g. WebSocket routing) being dropped when `modelSettings.timeout` wraps the model stream. ([#22287](https://github.com/mastra-ai/mastra/pull/22287)) ## 1.62.0-alpha.10 ### Patch Changes - Fixed `run.cancel()` leaving a workflow's spans open when a step ignores `abortSignal`. ([#22278](https://github.com/mastra-ai/mastra/pull/22278)) A step that never observes the abort signal keeps running, so the execution engine never unwinds and never ends the run's spans. Exporters that only act on span-end events (Langfuse, Mastra Cloud, and any OpenTelemetry-based exporter) therefore never received the run span or any of its ancestors, leaving traces without input/output, tags, or cost, and turning every step that did finish into an orphan. `cancel()` now closes the run's span tree itself, on both the default and evented engines. ```ts const run = await workflow.createRun(); run.start({ inputData: {} }); // The trace is now closed and exported even if the running step never returns await run.cancel(); ``` - Fixed delegated (supervisor) resume dropping a sub-agent leaf tool's writer.custom() data frames from the parent stream. Resumed delegations now continue on the delegation thread persisted by the suspended run instead of generating a new one, so approval-gated tools that emit custom data frames keep streaming them after approval. Also, a failure to save a custom data frame to memory no longer removes it from the stream. Fixes https://github.com/mastra-ai/mastra/issues/22217 ([#22277](https://github.com/mastra-ai/mastra/pull/22277)) ## 1.62.0-alpha.9 ### Patch Changes - Fixed EventedAgent leaving finished runs' snapshot rows in storage. Completed runs no longer show up in listActiveRuns() or get re-executed by recoverActiveRuns() (#22209). The evented workflow engine now also deletes a run's snapshot row when it reaches a non-paused terminal status that the workflow's shouldPersistSnapshot option declined to persist, instead of leaving a stale row behind. ([#22266](https://github.com/mastra-ai/mastra/pull/22266)) - Fixed an infinite agent loop when a provider stream ends with finish reason "other" without producing any output. The agentic loop previously re-issued the identical request until maxSteps was reached, silently burning tokens. Such zero-output streams are now treated as a stream error so error processors can retry a bounded number of times before failing loudly. Streams that finish with reason "other" after producing output continue as before. Fixes #21897 ([#22273](https://github.com/mastra-ai/mastra/pull/22273)) ## 1.62.0-alpha.8 ### Minor Changes - Added `AgentController#generateThreadTitle()` — name a thread on demand from where its conversation went, with the model and instructions `generateTitle` gives the first-turn namer. ([#22156](https://github.com/mastra-ai/mastra/pull/22156)) It reads a bounded window of the thread's recent messages and writes the thread row without constructing a `Session`, so a "rename this conversation" action never spins up a workspace or sandbox for a session that is no longer live. A live session lends its agent, request context and event stream, and hears the resulting `thread_title_updated`. ```ts const title = await controller.generateThreadTitle({ threadId, resourceId, // The caller's identity, so model resolution bills their credentials. requestContext, // Optional: hosts that store the title model themselves pass it here. model: ({ requestContext }) => resolveModel(storedModelId, { requestContext }), }); ``` - Sandboxes now own their runtime environment. `MastraSandbox` accepts an `env` constructor option, and you can read or update the environment at runtime with `getEnv()` and `setEnv(updater)`: ([#22250](https://github.com/mastra-ai/mastra/pull/22250)) ```typescript sandbox.setEnv(env => ({ ...env, GH_TOKEN: token })); ``` The sandbox environment is merged into every process spawn by the base `SandboxProcessManager`, so it reaches `executeCommand()` and `processes.spawn()` on any provider whose execution routes through its process manager, including values installed or rotated after the sandbox was created (for example, refreshed credentials). Per-call `env` options take precedence for that command only. These values apply to commands executed through the sandbox; they are not VM-level environment and are never written into the VM. `WorkspaceSandbox` declares `setEnv` as an optional capability. ### Patch Changes - Fixed channel typing statuses staying pinned to the thread after a run ends without posting a message. When a run terminates on a tool call (for example via a stopWhen predicate), errors, or is aborted before any assistant text is posted, the typing status is now cleared instead of showing the last status indefinitely. Fixes #21880 ([#22264](https://github.com/mastra-ai/mastra/pull/22264)) - Fixed durable agent runs writing the entire conversation into storage on every step. A long run persisted the accumulated message history and step output once per completed step, so the amount written grew with the square of the run length — a 57-step run produced hundreds of megabytes in Postgres and Redis, made saves slow enough to time out, and could fail an approval with a 500. ([#22127](https://github.com/mastra-ai/mastra/pull/22127)) Historical completed steps in a still-running snapshot now keep only the fields the engine needs for routing, while the active step retains the conversation state needed for crash recovery. On a benchmark run this cut total bytes written by around 70% and peak snapshot size from 561 KB to 118 KB. Suspended and finished runs are stored exactly as before. Also fixed approvals that arrived while a large suspend was still being written. Resume read the run once and gave up if it did not yet look suspended; it now briefly re-reads snapshots that are still running or pending, while missing runs and statuses that cannot become suspended still fail immediately. Reported in https://github.com/mastra-ai/mastra/issues/20747 - Fixed suspended agent runs writing snapshots that grew quadratically with step count, which could exhaust memory on human-in-the-loop workflows using tool approval with large payloads. ([#21732](https://github.com/mastra-ai/mastra/pull/21732)) Each buffered step of a run persisted its own full copy of the conversation so far — three times over, as model messages, database messages, and UI messages. A fifteen-step run was observed writing 22 MB of buffered steps over 2 MB of distinct messages, and production runs reached 170 MB per suspended run. Snapshots now record the IDs of the response messages referenced by each step and lazily rebuild those messages from the conversation that is already stored alongside them. This preserves the correct messages even when processors move or remove messages before suspension. Steps still expose the same messages after a run resumes, so no application code needs to change, while persisted snapshot size now grows linearly with step count. Fixes [#17738](https://github.com/mastra-ai/mastra/issues/17738). - Fixed streaming channels posting blank messages when a response starts with whitespace-only text. ([#22168](https://github.com/mastra-ai/mastra/pull/22168)) - Fixed `session.thread.firstUserMessage()` and `firstUserMessages()` returning nothing for agent-controller sessions. A live session persists a chat message as a `user` signal rather than a `user` row, and the lookup only matched the latter, so it came back empty for every real session. ([#22156](https://github.com/mastra-ai/mastra/pull/22156)) Added `isUserAuthoredMessage()` for the same check anywhere else, and sessions now emit a `thread_title_updated` event when a generated title lands on the thread. - Improved workspace grep performance on remote filesystems by searching directories and files concurrently. ([#22225](https://github.com/mastra-ai/mastra/pull/22225)) - Fixed workflow imports crashing in environments where the global `fetch` value is not callable. ([#22246](https://github.com/mastra-ai/mastra/pull/22246)) - Fixed workspace search indexing so it can be rebuilt without starting the sandbox. ([#22245](https://github.com/mastra-ai/mastra/pull/22245)) ```typescript import { LocalFilesystem, Workspace } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), bm25: true, autoIndexPaths: ['docs'], }); await workspace.rebuildSearchIndex(); ``` - Fixed malformed message parts crashing OpenAI-compatible providers when observational memory is enabled. ([#22069](https://github.com/mastra-ai/mastra/pull/22069)) - Experiment results now include isolated metadata snapshots from the dataset items that ran. ([#22005](https://github.com/mastra-ai/mastra/pull/22005)) - Stopped internal agent-loop workflows from logging a `shouldPersistSnapshot excludes the "running" status` warning on every resume. Workflows can now set `options.allowUnclaimedResumes: true` to acknowledge that resume claims cannot be persisted (because `running` snapshots are intentionally not written) and suppress the per-resume warning; the built-in agentic-loop, agentic-execution, and agent-network workflows opt in. User workflows that exclude `running` without acknowledging still get the warning. ([#22262](https://github.com/mastra-ai/mastra/pull/22262)) - Fixed recursive tool preparation for cyclic sub-agent graphs when background tasks are disabled. ([#22244](https://github.com/mastra-ai/mastra/pull/22244)) - Fixed built-in memory processors so canceled agent runs retain transcript history through normal output processing. Removed the accidental partial-abort persistence option because cancellation persistence is no longer opt-in. ([#22243](https://github.com/mastra-ai/mastra/pull/22243)) ```ts // Before await agent.stream('Hello', { abortSignal, persistPartialOnAbort: true }); // Now await agent.stream('Hello', { abortSignal }); ``` - Cancelling a durable agent run that is already executing now works through the agent APIs. `agent.abortThreadStream({ resourceId, threadId })` and `agent.abortRunStream(runId)` only ever reached the abort controller a regular run prepares, which a durable run does not have, so they recorded a cancellation nothing acted on while the run streamed on. The server route `POST /agents/:agentId/threads/abort` goes through the same call. Both now publish the durable abort request as well, the same one the `abort()` on a stream result publishes, so the run stops in whichever process is executing it. ([#22255](https://github.com/mastra-ai/mastra/pull/22255)) ```ts const { runId } = await durableAgent.stream('...', { memory: { thread, resource } }); // before: the cancellation was recorded and the run streamed on to completion // after: the run stops and onAbort fires durableAgent.abortRunStream(runId); ``` - Fixed agent skill search to rank relevant instructions and references instead of relying on literal substring matches. ([#22174](https://github.com/mastra-ai/mastra/pull/22174)) - Prevent background task dispatch from overwriting concurrent cancellation ([#22228](https://github.com/mastra-ai/mastra/pull/22228)) - Enforce atomic conditional background task state updates so cancellation cannot be overwritten during dispatch. Background task storage is no longer exposed by Cloudflare KV or ClickHouse, which cannot provide the required compare-and-set semantics. ([#22228](https://github.com/mastra-ai/mastra/pull/22228)) - Fixed grouped channel streams closing between agent steps. ([#22173](https://github.com/mastra-ai/mastra/pull/22173)) ## 1.62.0-alpha.7 ### Patch Changes - Route channel slash commands through AgentChannels handlers ([#22223](https://github.com/mastra-ai/mastra/pull/22223)) - Fixed cleared AgentController session preferences so they are removed from thread metadata and stay cleared after restart. ([#22198](https://github.com/mastra-ai/mastra/pull/22198)) - Added a `denied` flag to the agent controller `tool_end` event. Approval-denied tool calls and tools aborted while parked at an approval gate already emit `tool_end` with `isError: false` (the tool didn't fail — it never ran), which made them indistinguishable from a real successful completion. Subscribers that need to know whether the tool actually did work can now gate on `denied !== true`. ([#22213](https://github.com/mastra-ai/mastra/pull/22213)) ```ts session.subscribe(event => { if (event.type !== 'tool_end') return; if (event.isError) return; // tool ran and failed if (event.denied) return; // tool was approval-denied or aborted before it ran // ...tool actually executed successfully }); ``` ## 1.62.0-alpha.6 ### Patch Changes - Fixed semantic recall initialization to fail instead of silently selecting the default vector index when embedding dimensions cannot be determined. ([#22171](https://github.com/mastra-ai/mastra/pull/22171)) - Fixed `timeTravel()` on a failed `.foreach()` step re-running iterations that had already succeeded, which duplicated their side effects (publishing, billing, uploads, notifications). ([#22133](https://github.com/mastra-ai/mastra/pull/22133)) A failed foreach now records which iterations completed, so re-entering the step only runs the ones that did not. ```ts const run = await workflow.createRunAsync(); await run.start({ inputData: { items: [1, 2] } }); // item 2 fails // Previously both items ran again. Now only item 2 runs. await run.timeTravel({ step: 'process-item' }); ``` Fixes #21749 - Prevent stale-build instances from executing scheduled workflow runs. ([#22125](https://github.com/mastra-ai/mastra/pull/22125)) A scheduled fire carries only a workflow id, so whichever process consumes it resolves the step graph from its _own_ registry. During a rollout a not-yet-cycled instance from the previous deploy could therefore run an outdated step graph — skipping steps the current build had added, including gates meant to stop the workflow from running at all — while HTTP runs of the same workflow on the same deployment executed the current graph (#19169). This is now fenced on both sides: - Declarative schedule rows record a hash of the workflow's serialized step graph. The scheduler refuses to claim a due fire when its local definition doesn't match the row, leaving the fire for an instance running the current build. If no instance claims it for several consecutive ticks, the scheduler escalates to an error and records a failed trigger rather than stalling silently — it never forces a stale fire through. - When the claiming process also runs workflow execution, the fire is published `localOnly` so the instance that verified the definition is the instance that runs it. Scheduler-only deployments, where pinning locally would strand the fire, keep publishing to the shared topic. - Fires that do reach the shared topic now carry the schedule's definition hash, and a consumer whose locally registered workflow hashes differently refuses the run and records a failed trigger instead of executing an outdated graph. Rows without a hash (legacy or imperatively created schedules) and agent schedules fail open and are unaffected. - Documented that `server.middleware` handlers use Hono's signature and which serving paths run them. ([#22161](https://github.com/mastra-ai/mastra/pull/22161)) ## 1.62.0-alpha.5 ### Patch Changes - Fixed channel render failures crashing the host process before stream cleanup. ([#22165](https://github.com/mastra-ai/mastra/pull/22165)) - Fixed nested workflow steps being omitted from foreach run details. ([#22144](https://github.com/mastra-ai/mastra/pull/22144)) - Fixed cross-provider agent conversations leaking provider-hosted tool IDs into incompatible model requests. ([#22148](https://github.com/mastra-ai/mastra/pull/22148)) - Preserve response message source tracking when processors return unchanged messages in multi-step agent runs ([#22146](https://github.com/mastra-ai/mastra/pull/22146)) - Fixed durable agent runs ignoring aborts requested before execution starts. ([#22145](https://github.com/mastra-ai/mastra/pull/22145)) - Add a `/context` command (alias `/ctx`) to Mastra Code that reports what is occupying the context window. ([#22131](https://github.com/mastra-ai/mastra/pull/22131)) The report separates the startup context — system prompt, AGENTS.md/CLAUDE.md instructions with their source paths, the skills catalog, and MCP tool definitions rolled up per server — from context that accumulates during the session, namely the conversation itself and any observation memory injected into it. Each line carries an estimated token count and its share of the audited total, so it is possible to see which server, instruction file, or skill set is worth pruning. The audit reports only sizes and labels, never the audited content, and is printed to the terminal without being added to the conversation, so running it does not enlarge the context it describes. To support exact measurement, `@mastra/core` now exports `formatSkillsCatalog`, the pure formatter behind the skills processor's `` block, and `@mastra/code-sdk` exposes the assembled system prompt as labeled sections. - Fixed suspended run cleanup so cross-instance resumes keep their active thread lease. ([#22150](https://github.com/mastra-ai/mastra/pull/22150)) - Fixed agent model calls to honor `modelSettings.maxRetries` when no retry count is explicitly configured on the agent or fallback model. Agents with no retry configuration continue to make a single attempt by default, while explicit agent or fallback settings—including `maxRetries: 0`—continue to override call-time settings. ([#21947](https://github.com/mastra-ai/mastra/pull/21947)) ## 1.62.0-alpha.4 ### Patch Changes - Fixed DurableAgent runs losing live models and tools while waiting on long-running tool or model calls. ([#22141](https://github.com/mastra-ai/mastra/pull/22141)) - Fixed the Mastra gateway so custom request headers can no longer replace the internal gateway authorization header (#21780). ([#22134](https://github.com/mastra-ai/mastra/pull/22134)) - Fixed background tasks restarting after reaching a terminal or suspended state. ([#22140](https://github.com/mastra-ai/mastra/pull/22140)) - Fixed background tasks failing to resume suspended sub-agent runs. ([#22139](https://github.com/mastra-ai/mastra/pull/22139)) - Fixed nested workflows to preserve non-retryable failures across parent retry boundaries. ([#22138](https://github.com/mastra-ai/mastra/pull/22138)) ## 1.62.0-alpha.3 ### Minor Changes - Added optional collection row counts for application storage, so totals no longer require loading every matching row. ([#22021](https://github.com/mastra-ai/mastra/pull/22021)) **Before** ```ts const total = (await storage.ops.findMany('jobs', { status: 'failed' })).length; ``` **After** ```ts if (!storage.ops.count) throw new Error('Storage backend does not support counts'); const total = await storage.ops.count('jobs', { status: 'failed' }); ``` ### Patch Changes - Removed obsolete generated capability files after registry refreshes. ([#21531](https://github.com/mastra-ai/mastra/pull/21531)) - Fixed AI SDK `reasoning-file` and `custom` content parts being silently dropped from agent streams. Providers on the AI SDK v7 spec emit these parts, but Mastra discarded them when converting model output, so they never reached `fullStream` for either `generate` or `stream` calls. They now arrive as `reasoning-file` and `custom` chunks. ([#22111](https://github.com/mastra-ai/mastra/pull/22111)) Content types Mastra does not recognize are no longer discarded either. They are now emitted as `raw` chunks, so you can see them by enabling raw chunks instead of losing the data with no error or warning: ```ts const stream = await agent.stream('Hello', { includeRawChunks: true }); for await (const chunk of stream.fullStream) { if (chunk.type === 'raw') console.log(chunk.payload); } ``` - Fix `steps[i].content` and `steps[i].toolCalls` being empty for input-step processors (`processInputStep`, `prepareStep`) and in the workflow output payload. Step content was extracted with a 0-indexed step count passed to a 1-indexed extractor and then sliced by message count, so the first step came back empty and later steps were misaligned. Completed steps now carry their real content, including tool results, which are re-read just before the next step's input processors run. ([#22119](https://github.com/mastra-ai/mastra/pull/22119)) - Workspace no longer registers the lsp_inspect tool when LSP is not active, so agents are only offered the tool when it can actually run. ([#22126](https://github.com/mastra-ai/mastra/pull/22126)) - Fixed suspended agent runs resuming on a newer published version. When an agent is resolved from a status selector (for example `{ status: 'published' }`), the version the run started on is now recorded when the run suspends and reused when it resumes, so approval and human-in-the-loop flows finish on the same version they started on. New runs still pick up the latest published version. Fixes #21846 ([#22128](https://github.com/mastra-ai/mastra/pull/22128)) - Fixed Claude 4.6 and newer structured output requests that end with an assistant message, including agents without configured input processors. ([#21925](https://github.com/mastra-ai/mastra/pull/21925)) - Fixed slow streaming when a workflow is used as an output processor. The workflow runs once per streamed chunk, and each of those runs was saved to storage with the whole response so far, so a long reply got quadratically slower and heavier the more it streamed. Those per-chunk runs are now transient: they no longer write workflow snapshots or emit public traces, while the processor logic and tripwire behavior are unchanged. Running the same workflow directly still persists and traces as before. Fixes #19605. ([#22124](https://github.com/mastra-ai/mastra/pull/22124)) - Made language-server (LSP) support opt-in in Mastra Code. By default Mastra Code no longer checks for LSP dependencies, starts language servers, or offers the lsp_inspect tool to the agent. Turn it on by adding "lsp": true (or an LSP config object) to your settings.json; "lsp": false is now also accepted and preserved. ([#22126](https://github.com/mastra-ai/mastra/pull/22126)) - Purge deduped transient signals from MessageList source-tracking sets so removed messages no longer leave stale entries behind ([#22107](https://github.com/mastra-ai/mastra/pull/22107)) - Fixed experiments dropping a dataset item's expected trajectory. `dataset.startExperiment` and `runExperiment` now pass each item's `expectedTrajectory` to trajectory scorers, so a trajectory that matches the expectation scores correctly instead of always scoring 0. Inline experiment data can now supply `expectedTrajectory` too. Fixes #21743 ([#22122](https://github.com/mastra-ai/mastra/pull/22122)) - Fixed transient signals duplicating within a turn. A processor that re-sends a transient reminder (e.g. via sendSignal with transient: true in processInputStep) now keeps a single fresh copy near the latest message instead of accumulating one copy per model call. Fixes https://github.com/mastra-ai/mastra/issues/22060 ([#22104](https://github.com/mastra-ai/mastra/pull/22104)) - Reduced the size of stored agent run snapshots. Completed steps of an agent run no longer keep a copy of the whole conversation, which previously made snapshots grow with every step of every turn and could push long conversations past a storage provider's document size limit. Resuming a suspended run, tool approvals, and run recovery are unchanged. ([#22123](https://github.com/mastra-ai/mastra/pull/22123)) - Improved agent turn latency by reusing run-scoped memory reads ([#22076](https://github.com/mastra-ai/mastra/pull/22076)) - Fixed `Agent.listSuspendedRuns()` and `DurableAgent.listActiveRuns()` ignoring the `resourceId` filter at the storage level. The filter is now pushed down to the workflow storage query instead of being applied in JavaScript after fetching every snapshot in the date range, so scoped polling no longer deserializes unrelated runs. Durable and evented agents now also record the `resourceId` on their workflow runs when they are created. See https://github.com/mastra-ai/mastra/issues/21844 ([#22105](https://github.com/mastra-ai/mastra/pull/22105)) - Fixed Anthropic 400 errors ("thinking or redacted_thinking blocks in the latest assistant message cannot be modified") during tool-use turns with extended thinking. ProviderHistoryCompat no longer strips reasoning parts from the trailing assistant message of an active tool-use continuation, which Anthropic requires to be replayed exactly as it was generated. Historical assistant messages are still sanitized. ([#22099](https://github.com/mastra-ai/mastra/pull/22099)) ## 1.62.0-alpha.2 ### Minor Changes - Added metadata filtering to score queries. You can now filter scores by metadata key-value pairs when listing scores: ([#22047](https://github.com/mastra-ai/mastra/pull/22047)) ```typescript const result = await storage.listScores({ filters: { metadata: { env: 'prod' } }, }); ``` - `Mastra.shutdown()` no longer destroys registered workspaces' sandboxes. Remote sandboxes (E2B, Platform, and other providers) were being killed on every process restart; they are now stopped instead, so providers that support suspension pause the sandbox and resume it later with filesystem and memory intact. ([#22079](https://github.com/mastra-ai/mastra/pull/22079)) - New `Workspace.stop()`: shuts down language servers, closes the browser, and stops the sandbox without destroying anything. The workspace stays usable and the sandbox can start again later. - `LocalSandbox.stop()` now kills background processes (previously only `destroy()` did), so the stop-on-shutdown behavior cannot leak child processes on local sandboxes. Files in the working directory are untouched. - Full teardown remains explicit: `workspace.destroy()` or `mastra.removeWorkspace(id, { destroy: true })`. ```typescript // Suspend live resources; the workspace stays usable await workspace.stop(); // Full teardown is explicit await workspace.destroy(); ``` ### Patch Changes - Add declarative eligibility filters to scorer bindings, evaluated before sampling. ([#22010](https://github.com/mastra-ai/mastra/pull/22010)) A scorer binding can now declare `filter`, a JSON-safe predicate over the scoring context (`requestContext`, `entity`, `entityType`, `source`, `threadId`, `resourceId`, `projectId`) using the shared predicate DSL. Filters run before sampling, so the sampling rate applies to qualifying traffic only — e.g. score escalated conversations at 100% by filtering on `requestContext.escalated` in one binding, and sample the rest at 5% in another. ```ts scorers: { groundedness: { scorer: groundednessScorer, filter: { op: 'eq', left: { path: 'requestContext.protocolVersion' }, right: { literal: 'v3' }, }, sampling: { type: 'ratio', rate: 0.05 }, }, } ``` Details: - Filters are validated at definition time: paths referencing unknown roots throw at agent construction (or when function-based scorer configs resolve), instead of silently skipping all scoring at runtime. - Filters evaluate against the flattened `requestContext` view that is persisted on score rows, so a filter remains answerable against stored records. - Filters are plain JSON and survive durable-agent serialization round-trips unchanged. - Scoring filters use the same predicate format as workflow `conditional`/`loop` conditions; existing workflow predicate behavior is unchanged. - Fixed: an unrecognized `sampling.type` previously fell through to scoring 100% of traffic; it now fails closed and skips scoring. ## 1.61.1-alpha.1 ### Patch Changes - Make scorer sampling deterministic and tie it to the trace sampling decision ([#22008](https://github.com/mastra-ai/mastra/pull/22008)) Ratio-based scorer sampling previously called `Math.random()` on every scorer invocation. It now hashes the trace ID (falling back to the run ID when observability is not configured), so the decision is reproducible and every scorer at a given rate selects the same traces. Two scorers sampling at 10% now cover the same 10% of traffic instead of overlapping on roughly 1% by chance, which makes their scores comparable on the same population. Scorers also now respect the tracing sampler. If the tracer declined a trace, scorers no longer run against it — those scores referenced a trace that was never stored and could not be drilled into. Two behavior changes when upgrading: - **Score volume drops if your trace sampling rate is lower than your scorer sampling rate.** The scores you lose are ones whose traces were never recorded. To keep the same score volume, raise your trace sampling to cover the traffic you score (or lower your scorer sampling to match — the effective rate is now bounded by the trace rate): ```ts // Before: scorer sampled 50% of invocations, but only 10% had a stored trace, // so up to 40% of scores referenced traces that were never recorded. new Observability({ configs: { default: { sampling: { type: 'ratio', probability: 0.1 }, exporters } }, }); new Agent({ scorers: { myScorer: { scorer: myScorer, sampling: { type: 'ratio', rate: 0.5 } } }, }); // After: raise trace sampling so every sampled score has a stored trace. new Observability({ configs: { default: { sampling: { type: 'ratio', probability: 0.5 }, exporters } }, }); new Agent({ scorers: { myScorer: { scorer: myScorer, sampling: { type: 'ratio', rate: 0.5 } } }, }); ``` - **Which traces get scored changes, even at an unchanged rate.** Score counts before and after the upgrade are drawn from a different set of traces, not just a different number. Scoring is unaffected when observability is not configured: with no tracing set up there is no trace decision to inherit, so scorers run as before, now keyed deterministically on run ID. ## 1.61.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`1e47b75`](https://github.com/mastra-ai/mastra/commit/1e47b7520cab4cfaa8daed52f17e2e6d14ff7539)) ## 1.61.0 ### Minor Changes - Added graceful shutdown options for generated servers ([#20678](https://github.com/mastra-ai/mastra/issues/20678)). Configure how long in-flight HTTP requests may drain, or disable Mastra's built-in signal handlers when managing the server lifecycle yourself. ([#21990](https://github.com/mastra-ai/mastra/pull/21990)) ```ts export const mastra = new Mastra({ server: { drainTimeout: 600_000, handleShutdownSignals: false, }, }); ``` - The session now marks a message you send while the agent is working, and every client gets it for free. Any user message submitted with a run in flight — including the one `session.steer()` sends after interrupting a run — carries `delivery: 'while-active'`, so the agent reads it as context for the work in progress and a reloaded transcript can still tell a steer apart from a normal message. A message that opens a new turn is unmarked, as before. ([#21842](https://github.com/mastra-ai/mastra/pull/21842)) The attribute used to be resolved from the run state at dispatch time, which reads idle for a steer (a steer aborts its own run before sending), so each client had to describe both delivery routes itself. ```ts // before session.sendSignal({ content, ifActive: { attributes: { delivery: 'while-active' } }, ifIdle: { attributes: { delivery: 'message' } }, }); // now session.sendSignal({ content }); ``` A `delivery` the caller sets on the signal still wins. - Added caller-driven experiments so an external orchestrator (for example Temporal workers) can own the experiment loop while Mastra stays the system of record. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) Create an experiment with `dataset.createExperiment()` (idempotent when you pass your own id). With a target, Mastra runs each item for you: call `dataset.runExperimentItem()` per item and Mastra executes the registered agent or workflow, resolves scorers (experiment `scorers`, falling back to item `scorerIds`, then dataset `scorerIds`), and upserts the result. Without a target, run everything yourself and report per-item results with `dataset.submitExperimentResult()` (upsert semantics on `(experimentId, itemId, attempt)` so retried workers converge on a single row). Either way, close the run with `dataset.finalizeExperiment()` and Mastra computes per-item succeeded/failed/skipped counts from the persisted rows. Results go into the same storage as native runs, so Studio views, comparisons, and review summaries work unchanged. ```typescript // Caller drives the loop, Mastra runs each item const { experimentId } = await dataset.createExperiment({ id: workflowRunId, targetType: 'agent', targetId: 'support-agent', scorers: ['accuracy'], }); await dataset.runExperimentItem({ experimentId, itemId }); // Or: caller runs everything, Mastra ingests results const ingest = await dataset.createExperiment({ id: workflowRunId }); await dataset.submitExperimentResult({ experimentId: ingest.experimentId, itemId, output, scores: [{ scorerId: 'accuracy', score: 0.92 }], }); const experiment = await dataset.finalizeExperiment({ experimentId }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`88d14ca`](https://github.com/mastra-ai/mastra/commit/88d14cac008582a618fecc3d5c7fd3bdf4f6ddc3)) - Fixed the coding agent's instructions for a message that arrives while it is already working. They described that message as ``, a tag the runtime never emits, so the rule and the wrapper the agent actually receives (``) never matched by name. ([#21879](https://github.com/mastra-ai/mastra/pull/21879)) No API change, and no change to how a message is delivered: one that lands mid-work is still marked `while-active` and still meant as context for the work in flight. The instructions now name it the way it arrives. - Added HTTP endpoints for caller-driven experiments: `POST /datasets/:datasetId/experiments` now accepts `start: false` to create an experiment without spawning the runner (with an optional target and run-level `scorerIds`), and new routes `POST /datasets/:datasetId/experiments/:experimentId/items/:itemId/run`, `POST /datasets/:datasetId/experiments/:experimentId/results`, and `POST /datasets/:datasetId/experiments/:experimentId/finalize` let external orchestrators run one item server-side, submit externally computed per-item results (idempotent upsert on retries), and finalize the run with server-computed counts. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) - Fixed versioned dataset item lookups to return the item visible in the requested dataset snapshot. ([#21979](https://github.com/mastra-ai/mastra/pull/21979)) - Fixed concurrent resume() calls on the same suspended workflow run executing downstream steps more than once. A resume now atomically claims the run before executing anything, so only one caller continues a given suspension. Losing callers throw WORKFLOW_RESUME_ALREADY_CLAIMED without running any steps. Fixes #20443 ([#21725](https://github.com/mastra-ai/mastra/pull/21725)) - Added `createDatasetExperiment()`, `runExperimentItem()`, `submitExperimentResult()`, and `finalizeExperiment()` methods so a caller-owned orchestrator (for example Temporal) can drive an experiment loop while Mastra either executes each item server-side or ingests externally computed results. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) - Fixed Code Mode instructions to reference custom tool IDs. ([#21920](https://github.com/mastra-ai/mastra/pull/21920)) - Reduced agent-controller memory usage during long streamed responses by reusing the live accumulated message across `message_start`, `message_update`, and `message_end` events, including events queued for server-sent event delivery. `display_state_changed.currentMessage` references the same live message. Consumers that require point-in-time values should copy or serialize before deferring event processing. ([#21813](https://github.com/mastra-ai/mastra/pull/21813)) - Removed a catch that hid a broken durable run. When a durable run's state could not be read between iterations, the loop kept going and crashed one step later with an error that pointed at the wrong place. It now fails where the unreadable state is found, carrying the real cause. ([#21889](https://github.com/mastra-ai/mastra/pull/21889)) - Workflow state updates now support an optional expectedStatus guard, so a status change is only applied when the stored run is in an expected state. This is what makes concurrent workflow resumes safe. ([#21725](https://github.com/mastra-ai/mastra/pull/21725)) - Preserved plan revision feedback in submit_plan results so hosts can replay resolved approvals. ([#21891](https://github.com/mastra-ai/mastra/pull/21891)) - Fixed a security issue where the framework-managed auth bearer token (`mastra__authToken`) was persisted in cleartext in workflow snapshots, score rows, and durable agent workflow inputs. The token is now excluded from all durable persistence; resumed authenticated requests use their own fresh live token. Fixes https://github.com/mastra-ai/mastra/issues/21975 ([#21996](https://github.com/mastra-ai/mastra/pull/21996)) - Added `upsertExperimentResult()` to the experiments storage domain plus an `attempt` column on experiment results and a nullable target with an optional `scorerIds` column on experiments, enabling retry-safe result writes for caller-driven experiments (retried submissions with the same `(experimentId, itemId, attempt)` key converge on a single row). `saveScore()` now accepts an optional caller-supplied `id` and upserts on it, so retried experiment submissions replace their previous score rows (latest wins) instead of accumulating duplicates. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) - Resume conflicts now return 409 Conflict. When a suspended workflow run has already been resumed by another caller, the resume endpoints respond with 409 instead of a generic error. ([#21725](https://github.com/mastra-ai/mastra/pull/21725)) - Fixed a reply coming back duplicated after an interrupted turn. When an error-retry processor or the durable loop moved the response message id without sealing the stored response, the streamed message split where the stored one did not, so the second half reappeared under its own id on reload. Rotating a response message id now seals the response it leaves behind, so the two can no longer drift apart. ([#21868](https://github.com/mastra-ai/mastra/pull/21868)) Durable runs now honour the same error-retry hooks as regular runs: `processAPIError` receives `messageId` and a working `rotateResponseMessageId`, so a processor can close the failed response and answer the retry in a message of its own instead of appending to it. The same handler was also working on a throwaway copy of the conversation, so anything it added there was dropped: a signal sent from `processAPIError` never reached the retried request. It does now. ```ts const agent = new Agent({ name: 'support', model: 'openai/gpt-5-nano', maxProcessorRetries: 1, errorProcessors: [ { id: 'retry-in-a-new-message', processAPIError: async ({ rotateResponseMessageId }) => { rotateResponseMessageId?.(); return { retry: true }; }, }, ], }); ``` Message ids minted during a durable run also honour a custom `generateId` configured on `Mastra`, which some paths silently ignored. - Fixed Code Mode sandbox resolution for resolver-backed workspaces. ([#21922](https://github.com/mastra-ai/mastra/pull/21922)) - Improved Agent Controller session startup by initializing workspaces only when used. ([#21874](https://github.com/mastra-ai/mastra/pull/21874)) Agent Controller no longer initializes configured workspaces during controller or session creation. Before, session creation implicitly initialized the configured workspace: ```ts const session = await controller.createSession({ id: 'session-id' }); ``` After, applications that require eager initialization must request it explicitly: ```ts const session = await controller.createSession({ id: 'session-id' }); await session.getWorkspace()?.init(); ``` Workspace operations otherwise initialize their resources lazily. - Fixed shared threads running with a stale model in multi-server deployments. The model selected for a mode is now re-read from the thread's persisted settings at the start of every run, so a model switch made in one browser session or server replica is picked up by all others instead of silently diverging until the next mode switch. ([#21899](https://github.com/mastra-ai/mastra/pull/21899)) - Fixed streaming output processors reacting to errors that the agent recovered from. Previously, an error raised by a single model call (for example a transient rate limit delivered in the response stream) was passed to every output processor immediately, before error processor retries or fallback models had a chance to recover. Processors now only see an error once the run has actually failed on it, and they see it exactly once. ([#21738](https://github.com/mastra-ai/mastra/pull/21738)) - Fixed the streamed reply splitting differently from the stored one. When the agent's turn was interrupted mid-run — a message sent while it was working, a resumed tool approval, a state signal — the live stream kept everything in one assistant message while storage had already sealed it and opened a new one. Coming back to the thread (tab switch, reload) replayed the second half as an extra copy. The stream now closes its message at the same boundary the run loop does. ([#21841](https://github.com/mastra-ai/mastra/pull/21841)) - Removed a catch that hid a broken durable run. When a durable run's state could not be read between iterations, the loop kept going and crashed one step later with an error that pointed at the wrong place. It now fails where the unreadable state is found, carrying the real cause. ([#21889](https://github.com/mastra-ai/mastra/pull/21889)) ## 1.61.0-alpha.5 ### Patch Changes - Fixed a security issue where the framework-managed auth bearer token (`mastra__authToken`) was persisted in cleartext in workflow snapshots, score rows, and durable agent workflow inputs. The token is now excluded from all durable persistence; resumed authenticated requests use their own fresh live token. Fixes https://github.com/mastra-ai/mastra/issues/21975 ([#21996](https://github.com/mastra-ai/mastra/pull/21996)) ## 1.61.0-alpha.4 ## 1.61.0-alpha.3 ### Minor Changes - Added graceful shutdown options for generated servers ([#20678](https://github.com/mastra-ai/mastra/issues/20678)). Configure how long in-flight HTTP requests may drain, or disable Mastra's built-in signal handlers when managing the server lifecycle yourself. ([#21990](https://github.com/mastra-ai/mastra/pull/21990)) ```ts export const mastra = new Mastra({ server: { drainTimeout: 600_000, handleShutdownSignals: false, }, }); ``` - Added caller-driven experiments so an external orchestrator (for example Temporal workers) can own the experiment loop while Mastra stays the system of record. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) Create an experiment with `dataset.createExperiment()` (idempotent when you pass your own id). With a target, Mastra runs each item for you: call `dataset.runExperimentItem()` per item and Mastra executes the registered agent or workflow, resolves scorers (experiment `scorers`, falling back to item `scorerIds`, then dataset `scorerIds`), and upserts the result. Without a target, run everything yourself and report per-item results with `dataset.submitExperimentResult()` (upsert semantics on `(experimentId, itemId, attempt)` so retried workers converge on a single row). Either way, close the run with `dataset.finalizeExperiment()` and Mastra computes per-item succeeded/failed/skipped counts from the persisted rows. Results go into the same storage as native runs, so Studio views, comparisons, and review summaries work unchanged. ```typescript // Caller drives the loop, Mastra runs each item const { experimentId } = await dataset.createExperiment({ id: workflowRunId, targetType: 'agent', targetId: 'support-agent', scorers: ['accuracy'], }); await dataset.runExperimentItem({ experimentId, itemId }); // Or: caller runs everything, Mastra ingests results const ingest = await dataset.createExperiment({ id: workflowRunId }); await dataset.submitExperimentResult({ experimentId: ingest.experimentId, itemId, output, scores: [{ scorerId: 'accuracy', score: 0.92 }], }); const experiment = await dataset.finalizeExperiment({ experimentId }); ``` ### Patch Changes - Added HTTP endpoints for caller-driven experiments: `POST /datasets/:datasetId/experiments` now accepts `start: false` to create an experiment without spawning the runner (with an optional target and run-level `scorerIds`), and new routes `POST /datasets/:datasetId/experiments/:experimentId/items/:itemId/run`, `POST /datasets/:datasetId/experiments/:experimentId/results`, and `POST /datasets/:datasetId/experiments/:experimentId/finalize` let external orchestrators run one item server-side, submit externally computed per-item results (idempotent upsert on retries), and finalize the run with server-computed counts. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) - Fixed versioned dataset item lookups to return the item visible in the requested dataset snapshot. ([#21979](https://github.com/mastra-ai/mastra/pull/21979)) - Added `createDatasetExperiment()`, `runExperimentItem()`, `submitExperimentResult()`, and `finalizeExperiment()` methods so a caller-owned orchestrator (for example Temporal) can drive an experiment loop while Mastra either executes each item server-side or ingests externally computed results. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) - Preserved plan revision feedback in submit_plan results so hosts can replay resolved approvals. ([#21891](https://github.com/mastra-ai/mastra/pull/21891)) - Added `upsertExperimentResult()` to the experiments storage domain plus an `attempt` column on experiment results and a nullable target with an optional `scorerIds` column on experiments, enabling retry-safe result writes for caller-driven experiments (retried submissions with the same `(experimentId, itemId, attempt)` key converge on a single row). `saveScore()` now accepts an optional caller-supplied `id` and upserts on it, so retried experiment submissions replace their previous score rows (latest wins) instead of accumulating duplicates. ([#21888](https://github.com/mastra-ai/mastra/pull/21888)) - Fixed shared threads running with a stale model in multi-server deployments. The model selected for a mode is now re-read from the thread's persisted settings at the start of every run, so a model switch made in one browser session or server replica is picked up by all others instead of silently diverging until the next mode switch. ([#21899](https://github.com/mastra-ai/mastra/pull/21899)) ## 1.61.0-alpha.2 ### Patch Changes - Fixed the coding agent's instructions for a message that arrives while it is already working. They described that message as ``, a tag the runtime never emits, so the rule and the wrapper the agent actually receives (``) never matched by name. ([#21879](https://github.com/mastra-ai/mastra/pull/21879)) No API change, and no change to how a message is delivered: one that lands mid-work is still marked `while-active` and still meant as context for the work in flight. The instructions now name it the way it arrives. - Fixed Code Mode instructions to reference custom tool IDs. ([#21920](https://github.com/mastra-ai/mastra/pull/21920)) - Removed a catch that hid a broken durable run. When a durable run's state could not be read between iterations, the loop kept going and crashed one step later with an error that pointed at the wrong place. It now fails where the unreadable state is found, carrying the real cause. ([#21889](https://github.com/mastra-ai/mastra/pull/21889)) - Fixed Code Mode sandbox resolution for resolver-backed workspaces. ([#21922](https://github.com/mastra-ai/mastra/pull/21922)) - Removed a catch that hid a broken durable run. When a durable run's state could not be read between iterations, the loop kept going and crashed one step later with an error that pointed at the wrong place. It now fails where the unreadable state is found, carrying the real cause. ([#21889](https://github.com/mastra-ai/mastra/pull/21889)) ## 1.61.0-alpha.1 ### Minor Changes - The session now marks a message you send while the agent is working, and every client gets it for free. Any user message submitted with a run in flight — including the one `session.steer()` sends after interrupting a run — carries `delivery: 'while-active'`, so the agent reads it as context for the work in progress and a reloaded transcript can still tell a steer apart from a normal message. A message that opens a new turn is unmarked, as before. ([#21842](https://github.com/mastra-ai/mastra/pull/21842)) The attribute used to be resolved from the run state at dispatch time, which reads idle for a steer (a steer aborts its own run before sending), so each client had to describe both delivery routes itself. ```ts // before session.sendSignal({ content, ifActive: { attributes: { delivery: 'while-active' } }, ifIdle: { attributes: { delivery: 'message' } }, }); // now session.sendSignal({ content }); ``` A `delivery` the caller sets on the signal still wins. ### Patch Changes - Reduced agent-controller memory usage during long streamed responses by reusing the live accumulated message across `message_start`, `message_update`, and `message_end` events, including events queued for server-sent event delivery. `display_state_changed.currentMessage` references the same live message. Consumers that require point-in-time values should copy or serialize before deferring event processing. ([#21813](https://github.com/mastra-ai/mastra/pull/21813)) - Fixed a reply coming back duplicated after an interrupted turn. When an error-retry processor or the durable loop moved the response message id without sealing the stored response, the streamed message split where the stored one did not, so the second half reappeared under its own id on reload. Rotating a response message id now seals the response it leaves behind, so the two can no longer drift apart. ([#21868](https://github.com/mastra-ai/mastra/pull/21868)) Durable runs now honour the same error-retry hooks as regular runs: `processAPIError` receives `messageId` and a working `rotateResponseMessageId`, so a processor can close the failed response and answer the retry in a message of its own instead of appending to it. The same handler was also working on a throwaway copy of the conversation, so anything it added there was dropped: a signal sent from `processAPIError` never reached the retried request. It does now. ```ts const agent = new Agent({ name: 'support', model: 'openai/gpt-5-nano', maxProcessorRetries: 1, errorProcessors: [ { id: 'retry-in-a-new-message', processAPIError: async ({ rotateResponseMessageId }) => { rotateResponseMessageId?.(); return { retry: true }; }, }, ], }); ``` Message ids minted during a durable run also honour a custom `generateId` configured on `Mastra`, which some paths silently ignored. - Improved Agent Controller session startup by initializing workspaces only when used. ([#21874](https://github.com/mastra-ai/mastra/pull/21874)) Agent Controller no longer initializes configured workspaces during controller or session creation. Before, session creation implicitly initialized the configured workspace: ```ts const session = await controller.createSession({ id: 'session-id' }); ``` After, applications that require eager initialization must request it explicitly: ```ts const session = await controller.createSession({ id: 'session-id' }); await session.getWorkspace()?.init(); ``` Workspace operations otherwise initialize their resources lazily. ## 1.60.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`88d14ca`](https://github.com/mastra-ai/mastra/commit/88d14cac008582a618fecc3d5c7fd3bdf4f6ddc3)) - Fixed concurrent resume() calls on the same suspended workflow run executing downstream steps more than once. A resume now atomically claims the run before executing anything, so only one caller continues a given suspension. Losing callers throw WORKFLOW_RESUME_ALREADY_CLAIMED without running any steps. Fixes #20443 ([#21725](https://github.com/mastra-ai/mastra/pull/21725)) - Workflow state updates now support an optional expectedStatus guard, so a status change is only applied when the stored run is in an expected state. This is what makes concurrent workflow resumes safe. ([#21725](https://github.com/mastra-ai/mastra/pull/21725)) - Resume conflicts now return 409 Conflict. When a suspended workflow run has already been resumed by another caller, the resume endpoints respond with 409 instead of a generic error. ([#21725](https://github.com/mastra-ai/mastra/pull/21725)) - Fixed streaming output processors reacting to errors that the agent recovered from. Previously, an error raised by a single model call (for example a transient rate limit delivered in the response stream) was passed to every output processor immediately, before error processor retries or fallback models had a chance to recover. Processors now only see an error once the run has actually failed on it, and they see it exactly once. ([#21738](https://github.com/mastra-ai/mastra/pull/21738)) - Fixed the streamed reply splitting differently from the stored one. When the agent's turn was interrupted mid-run — a message sent while it was working, a resumed tool approval, a state signal — the live stream kept everything in one assistant message while storage had already sealed it and opened a new one. Coming back to the thread (tab switch, reload) replayed the second half as an extra copy. The stream now closes its message at the same boundary the run loop does. ([#21841](https://github.com/mastra-ai/mastra/pull/21841)) ## 1.60.0 ### Minor Changes - Added `ProcessHandle.closeStdin()` to signal end-of-file to background processes. Local and Docker sandboxes support closing stdin, while providers without an available stdin-close API return a provider-specific unsupported-operation error. Providers signal the unsupported case with the new `UnsupportedStdinCloseError`, and the base class supplies that behavior by default so existing `ProcessHandle` subclasses keep compiling. Calling `handle.writer.end()` also closes stdin, and finishes without an error when the provider cannot close stdin. ([#21606](https://github.com/mastra-ai/mastra/pull/21606)) - Added live-tail PubSub subscriptions with `startFrom: "latest"` and an explicit `supportsOffsets` capability. ([#21535](https://github.com/mastra-ai/mastra/pull/21535)) ```typescript await pubsub.subscribe(topic, callback, { startFrom: 'latest' }); ``` The default remains `"earliest"`, and existing consumer groups keep their checkpoint. - Added optional `filename` property to `FilePayload`. When an agent emits a file chunk with a filename, the Channel layer now preserves it through to the Chat SDK adapter instead of generating a name from the MIME type. Existing file chunks without a filename continue using the `generated.` fallback. ([#21135](https://github.com/mastra-ai/mastra/pull/21135)) ```ts const chunk: FilePayload = { data: fileBuffer, mimeType: 'text/plain', filename: 'report.txt', // preserved through to the channel adapter }; ``` - Client-executed tools (tools without a server-side `execute`) now fire their `onOutput` lifecycle hook when the browser returns the tool result on a follow-up request. Previously `onOutput` only fired after a server-side `execute`, so client tools never reported their output. ([#21377](https://github.com/mastra-ai/mastra/pull/21377)) ```ts const agent = new Agent({ // ... tools: { browserTool: createTool({ id: 'browserTool', description: 'Runs in the browser', inputSchema: z.object({ query: z.string() }), // No execute — the client runs the tool. This hook now fires when // the client sends the result back. onOutput: async ({ toolCallId, output }) => { console.log('client tool resolved', toolCallId, output); }, }), }, }); ``` The hook fires for trailing tool results that match a raw tool call from the preceding assistant message, so the follow-up request must include both (as `@mastra/client-js` does automatically). This works with standard, legacy, and durable agents, including requests with a same-name serialized `clientTools` entry. The callback receives `{ toolCallId, toolName, output, abortSignal }`. Because the input is client-provided, treat the output as untrusted data. Delivery is at least once, and the hook does not fire when an input processor rejects the request. Keep hooks idempotent. - Added `AgentControllerWireEvent`, `JsonReadyAgentControllerEvent`, `ErrorCarryingAgentControllerEvent` and `WireDisplayState` to `@mastra/core/agent-controller`. They describe an agent controller event as it crosses an HTTP boundary — display-state Maps as records, errors as `{ name, message }`, dates as ISO strings — and are derived from `AgentControllerEvent` itself, so a client can type what it receives without redeclaring the controller's events by hand. ([#21761](https://github.com/mastra-ai/mastra/pull/21761)) ```ts import type { AgentControllerWireEvent } from '@mastra/core/agent-controller'; function onEvent(event: AgentControllerWireEvent) { if (event.type === 'display_state_changed') { // a record after JSON, where the controller holds a Map Object.values(event.displayState.activeTools); } if (event.type === 'error') { event.error.message; } } ``` - MCP tools served over HTTP now see the authenticated caller. When an MCP server runs behind a Mastra server with `server.auth` configured, the resolved user is bridged into `extra.authInfo` automatically, on both the streamable HTTP and SSE transports. Previously `extra.authInfo` was always undefined because the request handed to the MCP transport was rebuilt without the auth data. ([#21689](https://github.com/mastra-ai/mastra/pull/21689)) **Custom verification** If your own middleware verifies the caller, build the auth info yourself with the new `server.mcpOptions.setRequestAuth` hook: ```ts export const mastra = new Mastra({ mcpServers: { myServer }, server: { middleware: [verifyBearerToken], mcpOptions: { setRequestAuth: (req, requestContext) => { const payload = requestContext.get('bearerPayload'); req.auth = { token: payload.token, clientId: payload.sub, scopes: payload.scope.split(' ') }; }, }, }, }); ``` Fixes #17291 - Added an opt-in `propagate` flag on the FGA actor signal. Set `actor: { actorKind: 'system', propagate: true }` when starting a workflow run and the actor is forwarded into the agent and tool calls made for declarative `.then(agent)` and `.then(tool)` steps, so system and scheduled runs no longer fail membership resolution. Propagation stays opt-in, skips custom step `execute` functions, and can be overridden per step. (#19064) ([#21713](https://github.com/mastra-ai/mastra/pull/21713)) - Added per-message Signal metadata to Channel handlers. Use `ctx.signalMetadata` to attach serializable, non-sensitive context that follows both idle and active message delivery: ([#21330](https://github.com/mastra-ai/mastra/pull/21330)) ```typescript handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.signalMetadata.attachmentIds = ['file-1'] await defaultHandler(thread, message) }, } ``` - Added `delegation.hookErrorStrategy` so a failing delegation hook no longer passes silently. ([#21695](https://github.com/mastra-ai/mastra/pull/21695)) Previously, if `onDelegationStart`, `messageFilter`, or `onDelegationComplete` threw, the error was only logged and the delegation carried on. A throwing `onDelegationStart` could not block a subagent, and a throwing `onDelegationComplete` lost whatever the hook was responsible for with no programmatic signal. **Detecting hook failures** Hook failures are now always recorded on the run's request context, whatever strategy you choose: ```ts const requestContext = new RequestContext(); await parentAgent.generate('Research AI trends', { requestContext }); const hookErrors = requestContext.get('__mastra_delegationHookErrors') ?? []; ``` **Failing the delegation instead** Opt in to fail-closed behavior. A throwing `onDelegationStart` then blocks the subagent, and a throwing `messageFilter` or `onDelegationComplete` surfaces to the parent as a failed tool call: ```ts await parentAgent.generate('Research AI trends', { delegation: { hookErrorStrategy: 'throw', onDelegationComplete }, }); ``` The default remains `'warn'`, so existing behavior is unchanged. Fixes #21624. - Added checkpoint support to LocalSandbox and a checkpoint capability signal to sandboxes. Sandboxes now expose `supportsCheckpoints` so features can detect whether `snapshot()` persists real state. LocalSandbox gained filesystem-backed checkpoints: pass `checkpointName` to seed the working directory on `start()` and persist it on `snapshot()`, and `seedCheckpointName` as a boot-only fallback (for example a shared warm base image) that never gets overwritten by later snapshots. ([#21798](https://github.com/mastra-ai/mastra/pull/21798)) ```ts import { LocalSandbox } from '@mastra/core/workspace'; const sandbox = new LocalSandbox({ workingDirectory: './workspace', checkpointName: 'session-123', seedCheckpointName: 'repo-base', }); await sandbox.start(); await sandbox.snapshot(); ``` - Added `modelSettings.timeout` so you can put a time limit on agent runs. Set `totalMs` to cap how long a whole run may take, including every reasoning step, tool call and retry, and `stepMs` to cap a single model call. Going over either limit fails with a `MastraTimeoutError`. A `totalMs` timeout ends the run outright, while a `stepMs` timeout moves on to the next model when you have configured fallback models, so a slow provider no longer stalls the run. Closes #15667 ([#21724](https://github.com/mastra-ai/mastra/pull/21724)) - Added foundational support for an upcoming experimental memory capability across storage, runtime, and developer tooling. ([#19538](https://github.com/mastra-ai/mastra/pull/19538)) - Added a `durable` option to stored agents so agents created through the Agents API can run with durable execution — no code deployment required. ([#21715](https://github.com/mastra-ai/mastra/pull/21715)) ```typescript await mastraClient.createStoredAgent({ id: 'helper', name: 'Helper', instructions: 'You are a helpful assistant.', model: { provider: 'openai', name: 'gpt-5' }, durable: true, }); ``` Pass `true` for defaults, or `{ maxSteps, cleanupTimeoutMs }` to tune the durable loop. Cache and pubsub are inherited from the server's Mastra instance, so configure distributed backends there for durability across replicas. Automatic recovery is still configured in code via `recovery.durableAgents`. - Added an opt-in `persistPartialOnAbort` stream option that saves the assistant text streamed before a cancellation. Only the pre-abort snapshot is persisted, so output a provider keeps producing after cancellation is discarded, and nothing is saved when no text was streamed. Aborted streams still persist nothing by default. Also fixed thread creation tracking so a thread created during step persistence is no longer re-created on finish. Fixes #17510. ([#21600](https://github.com/mastra-ai/mastra/pull/21600)) - Added invoker-bound tool provider connections. Providers now receive the connection kind, toolkit, and live RequestContext so they can execute as the authenticated user without coupling provider identity to the Memory resource. The stored connection ID continues to select the exact provider account. ([#21783](https://github.com/mastra-ai/mastra/pull/21783)) ```typescript import type { ToolProviders } from '@mastra/core/tool-provider'; const toolProviders: ToolProviders = { crm: { tools: { CREATE_LEAD: { toolkit: 'salesforce' }, }, connections: { salesforce: [ { kind: 'invoker', toolkit: 'salesforce', connectionId: 'connected-account-id', }, ], }, }, }; ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`587f6ef`](https://github.com/mastra-ai/mastra/commit/587f6efcfc25880b93760a8607d1cd381ec612fe)) - Fixed workspace skill resolvers so concurrent requests keep their own resolved skill set throughout execution. ([#21524](https://github.com/mastra-ai/mastra/pull/21524)) - Fixed routed models so tool results can include images and files. (#21183) ([#21569](https://github.com/mastra-ai/mastra/pull/21569)) - Fixed model-backed processors (language detector, prompt injection detector, PII detector, system prompt scrubber, and moderation) dropping the request context. Their internal detection agents now receive the caller's RequestContext, so dynamic model resolvers and gateways can select models per request. ([#21709](https://github.com/mastra-ai/mastra/pull/21709)) - Fixed the `tail` parameter of the workspace `execute_command` and `get_process_output` tools rejecting numeric strings. Models sometimes send number parameters as strings, for example `"10"` instead of `10`. The `timeout` parameter already tolerated this, but `tail` failed validation and the command never ran, wasting a turn. In observed cases the model then fabricated a success result instead of retrying. Numeric strings are now coerced to numbers for `tail` in both tools. ([#21493](https://github.com/mastra-ai/mastra/pull/21493)) - Fixed generated streams to preserve provider metadata on text and file events. ([#21441](https://github.com/mastra-ai/mastra/pull/21441)) - Added `settled()` to the base memory class. Memory implementations can do work in the background after an agent run returns, and this gives callers a way to wait for it before closing a storage connection they own. The default implementation does nothing; `@mastra/memory` overrides it. ([#21708](https://github.com/mastra-ai/mastra/pull/21708)) - Fixed workflow cancellation so sleep() and sleepUntil() stop promptly without overwriting the canceled run status. ([#21570](https://github.com/mastra-ai/mastra/pull/21570)) - Fixed evented workflow restarts so conditional branches resume at the correct execution path and aggregate their results. ([#21603](https://github.com/mastra-ai/mastra/pull/21603)) - Improved observational memory progress restoration by rebuilding UI status from the durable memory record instead of polluted message history. Restored progress now also reports the memory thresholds you configured, where it previously always showed the built-in defaults. ([#21604](https://github.com/mastra-ai/mastra/pull/21604)) - Refactor MastraCompositeStore domain wiring to be data driven. The constructor resolve block and the init roll call previously enumerated every storage domain by hand, so a domain registered in the stores map but missing from either list was silently skipped: init reported success while the domain's tables were never created. Both lists are now replaced by iteration over a typed DOMAIN_KEYS constant with a compile time exhaustiveness guard, and a conformance test proves a newly registered domain cannot dodge init. DOMAIN_KEYS is a new exported constant, following the existing EDITOR_DOMAINS pattern. No behavior change for the existing domains, with two narrow carve outs: a stores map entry without an init function is now skipped during init instead of throwing a TypeError, and an entry present in the stores map that the old roll call never named now gets initialized. ([#21672](https://github.com/mastra-ai/mastra/pull/21672)) - Fixed agent memory so that calls now fail immediately when the given thread belongs to a different resource. Previously agent.stream() and agent.generate() would silently run the model and drop the turn instead of reporting the ownership mismatch (#21641). ([#21691](https://github.com/mastra-ai/mastra/pull/21691)) - Fixed durable agents pausing between model steps by suppressing unused internal workflow step events that repeatedly serialized cumulative conversation state. ([#21529](https://github.com/mastra-ai/mastra/pull/21529)) - Fixed durable step-start events publishing full model requests. ([#21572](https://github.com/mastra-ai/mastra/pull/21572)) - Fixed replay streams so disconnecting during history replay also stops the live stream. ([#21556](https://github.com/mastra-ai/mastra/pull/21556)) - Fixed dataset experiments with memory-enabled agents when no resource or thread IDs are provided. ([#21552](https://github.com/mastra-ai/mastra/pull/21552)) - Fixed tool request context schemas to pass transformed values, coercions, and defaults to execute while retaining input-form values for nested validation. Explicit mutations now preserve schema input encoding and reject unencodable transformed writes without corrupting the shared context. ([#21653](https://github.com/mastra-ai/mastra/pull/21653)) - Skills discovery no longer blocks agent turns: the skills processors serve the cached catalog and revalidate in the background, and refresh swaps the catalog atomically. Mid-session skill changes now appear one turn later (plus a staleness cooldown of up to 30 seconds); pass blockingRefresh: true to SkillsProcessor or SkillSearchProcessor to restore same-turn freshness by awaiting the refresh before the first step. ([#21555](https://github.com/mastra-ai/mastra/pull/21555)) - Restored the Studio memory bar token counts after a reload by reading them from the stored observational memory record. ([#21604](https://github.com/mastra-ai/mastra/pull/21604)) - Retry transient Windows file locks when atomically replacing provider registry cache files. ([#21425](https://github.com/mastra-ai/mastra/pull/21425)) - Fixed `DurableAgent.listActiveRuns()` and `recoverActiveRuns()` loading every running run's full workflow snapshot into memory at once. Candidate runs are now fetched from storage in bounded batches of 100 rows, so discovering or recovering runs against a large backlog no longer risks exhausting process memory. Fixes [#21501](https://github.com/mastra-ai/mastra/issues/21501). ([#21518](https://github.com/mastra-ai/mastra/pull/21518)) - Fixed array textStream output so it always stays valid JSON, even when the first streamed chunk already includes elements ([#21559](https://github.com/mastra-ai/mastra/pull/21559)) - Exported `ReservedThreadMetadataKey`, the list of thread-metadata keys an agent controller session owns for its own bookkeeping (selected model and mode, observer/reflector config, token usage, persisted preferences). Packages that cannot import the list as a value can now pin their copy of it to the real one: ([#21739](https://github.com/mastra-ai/mastra/pull/21739)) ```ts import type { ReservedThreadMetadataKey } from '@mastra/core/agent-controller'; const RESERVED = { currentModelId: true /* … */ } satisfies Record; ``` The list itself is unchanged — only its keys are now nameable, so a package that mirrors it fails to compile the moment the two fall out of step. - Added regression coverage keeping memory-sourced messages exempt from the thread ID check, so resource-scoped observational memory can pull in messages from a resource's other threads without failing the turn. ([#21702](https://github.com/mastra-ai/mastra/pull/21702)) - Honor stored agent version overrides on direct programmatic agent calls. Version overrides supplied via `MASTRA_VERSIONS_KEY` on the requestContext, the `versions` call option, or Mastra-level `versions` defaults now resolve the called agent itself to its stored version (previously they only applied to sub-agent delegation and HTTP routes), fixing the inconsistency where published overrides were served over HTTP but silently ignored in programmatic paths. ([#21383](https://github.com/mastra-ai/mastra/pull/21383)) - LocalSandbox now replaces an existing checkpoint atomically. A concurrent boot never observes a missing or half-written checkpoint while a snapshot is being saved. ([#21798](https://github.com/mastra-ai/mastra/pull/21798)) - Fixed tool search and skill search losing loaded state for HTTP requests that pass a thread via memory options. Processors now resolve the thread ID from the memory request context, and single-name load_tool results are recognized when reconstructing loaded tools from conversation messages with storage: 'context'. Fixes #21508 ([#21521](https://github.com/mastra-ai/mastra/pull/21521)) - Fixed durable run recovery so a reconnecting thread subscriber receives the remaining output and terminal event of a recovered run. ([#21526](https://github.com/mastra-ai/mastra/pull/21526)) Only one recovery of a run can be active at a time. A second concurrent `durableAgent.recover(runId)` call now fails fast with the error `DURABLE_AGENT_RECOVER_ALREADY_IN_PROGRESS` instead of restarting the run twice. - Fixed durable agent runs failing during shutdown by waiting for in-flight workflow persistence before closing storage. ([#21532](https://github.com/mastra-ai/mastra/pull/21532)) - Rewrite system-reminder tags in Azure-bound user text and system instructions to avoid content moderation refusals while preserving stored history. ([#21599](https://github.com/mastra-ai/mastra/pull/21599)) - Fixed `MockMemory` so its working memory merge keeps parity with `@mastra/memory`: a `null` now deletes a field on the first write and inside newly created nested objects, instead of being stored literally. ([#21687](https://github.com/mastra-ai/mastra/pull/21687)) - Fixed stale tool suspension metadata when resuming with `agent.resumeStream()`. ([#21319](https://github.com/mastra-ai/mastra/pull/21319)) A tool suspended with `suspendSchema` left its `suspendedTools` entry on the saved assistant message after it was resumed via `agent.resumeStream(resumeData, { runId, toolCallId })`. A client reloading the thread read the already-resolved tool as still waiting for input, and could send the next resume to the wrong tool call. The entry is now cleared for both resume conventions. **Falsy resume payloads are handled correctly** A tool whose `resumeSchema` is a primitive can legitimately be resumed with `false`, `0` or `""`, which is how a boolean human-in-the-loop tool declines. Those payloads now clear the suspension entry, and a delegated `agent-*` or `workflow-*` tool resumed with one keeps resuming its existing sub-run instead of silently starting a fresh one. **Approving one tool call no longer clears another's pending state** When two calls of the same tool were waiting and one of them was approved after its approval requirement had changed, the other call's pending state could be removed, leaving it stuck and unresumable. Approving one call now leaves the other waiting as expected. Fixes [#19083](https://github.com/mastra-ai/mastra/issues/19083). - Sub-agent delegation no longer attempts to resume when the model supplies `resumeData` without a suspended run. Previously the delegation step chose the resume path on `resumeData` alone and called `resumeGenerate`/`resumeStream` with an undefined run id, throwing `AGENT_RESUME_NO_SNAPSHOT_FOUND` before the sub-agent executed. It now starts a fresh delegation in that case. ([#21729](https://github.com/mastra-ai/mastra/pull/21729)) - Stop writing run-local workflow events to the server cache. ([#21675](https://github.com/mastra-ai/mastra/pull/21675)) Durable agents wrap `mastra.pubsub` in a `CachingPubSub`, so every publish was mirrored into the shared cache for replay. Per-run `workflow.events.v2.` watch events are only ever consumed in the publishing process, and their payloads accumulate step results — mirroring them filled shared stores (e.g. Redis) with lists no other instance could read. `CachingPubSub` already skipped caching for publishes marked `localOnly`, but the caching layer sits above the proxy that sets that flag, so it never saw it. `CachingPubSubOptions` now accepts an optional `shouldCache?: (topic: string) => boolean` predicate, and the durable agent uses it to keep run-local topics out of the cache. Those events are still delivered live to subscribers; agent stream topics are unaffected and still replay as before. - Estimate media token cost in TokenLimiterProcessor instead of serializing base64 payloads as text. File parts and media-shaped tool results (`{ data, mediaType }`) previously fell through to `JSON.stringify`, so a single image could add thousands of phantom tokens and truncate history unnecessarily (#21731). ([#21737](https://github.com/mastra-ai/mastra/pull/21737)) - Fixed getWorkflowRunById and getWorkflowRunSteps so nested workflows report the correct single suspended leaf step. Obsolete suspension details are removed after the step resumes or completes. (#21229) ([#21565](https://github.com/mastra-ai/mastra/pull/21565)) - Fix per-step `reasoningText` and `reasoning` accumulating across steps for reasoning models. Each step in a multi-step run now reports only the reasoning produced during that step, matching the existing behavior of the per-step `text` field. Run-level `reasoningText` and `reasoning` remain the full concatenation across all steps. ([#21711](https://github.com/mastra-ai/mastra/pull/21711)) - Fixed `createStep(tool)` sometimes misinterpreting a tool as a custom step. Copied or renamed tools — such as tools resolved through a tool provider — are now correctly detected and build a proper tool step. This applies to both the default `createStep` and the one exported from `@mastra/core/workflows/evented`. ([#21512](https://github.com/mastra-ai/mastra/pull/21512)) - Restore pending tool approval state when reloading v6 UI messages. ([#17948](https://github.com/mastra-ai/mastra/pull/17948)) - Fix AgentController subscriptions hanging when remote runs lose their lease ([#21540](https://github.com/mastra-ai/mastra/pull/21540)) - Fixed output processors missing response messages after a mid-run memory save (#21204). ([#21533](https://github.com/mastra-ai/mastra/pull/21533)) - Preserve channel approval cards when tool output is hidden or suppressed by a custom renderer. ([#21402](https://github.com/mastra-ai/mastra/pull/21402)) Resolves https://github.com/mastra-ai/mastra/issues/21162 Custom renderers can reuse the built-in approval and tool-event formatting: ```ts import { formatToolApproval, renderBuiltInToolEvent, type ToolDisplayFn } from '@mastra/core/channels'; const renderTool: ToolDisplayFn = event => event.kind === 'approval' ? formatToolApproval(event.displayName, event.argsSummary, event.toolCallId, true) : renderBuiltInToolEvent(event, 'cards'); ``` - Stop evicting long-running agent runs at the suspended-run TTL. `MASTRA_SUSPENDED_RUN_TTL_MS` now bounds how long a run-scoped internal workflow may sit **idle**, not how long a run may take. Previously the lazy sweep measured wall-clock age from registration, so any run that legitimately executed past the TTL (30 minutes by default) had its workflow registration and run scope dropped mid-flight and went silent. Abandoned or suspended-and-never-resumed runs are still released on the same bound, so the memory protection is unchanged. Operators who raised the knob to work around this can return it to the default. ([#21693](https://github.com/mastra-ai/mastra/pull/21693)) - onAbort now receives the partial assistant text streamed before a mid-generation abort, alongside the completed steps ([#21718](https://github.com/mastra-ai/mastra/pull/21718)) - Added public read-only thread query methods on `AgentController` and a `initStorage()` method that initializes storage without provisioning a workspace. Use these to read threads or messages without paying the workspace/sandbox startup cost that `createSession` incurs. ([#21474](https://github.com/mastra-ai/mastra/pull/21474)) ```ts // Before: had to create a session (which called Workspace.init() -> sandbox.start()) const session = await controller.createSession({ resourceId }); const threads = await session.thread.list(); // After: read directly from storage, no session, no workspace const threads = await controller.queryThreads({ resourceId }); const messages = await controller.queryThreadMessages({ threadId, limit: 50 }); const thread = await controller.queryThreadById({ threadId }); ``` `queryThreads`, `queryThreadById`, and `queryThreadMessages` were already used internally; they are now part of the public `AgentController` API. Each lazily calls `initStorage()`, so callers don't need to pre-init. - Added stable submit_plan identity to suspension payloads and resumed results. ([#21658](https://github.com/mastra-ai/mastra/pull/21658)) - Fixed tool results being lost when an agent calls a server-side tool and a client-side tool in the same step. The server-side tool's result is now streamed to the client and saved to history before the agent hands control back for the pending client-side tool, so clients no longer wait forever on a tool call that already finished. ([#21688](https://github.com/mastra-ai/mastra/pull/21688)) - Fixed model configuration validation so configs with non-string routing fields (`id`, `providerId`, or `modelId`) are rejected with the standard "Invalid model configuration provided" error instead of failing later inside the model router. Fixes #21588 ([#21664](https://github.com/mastra-ai/mastra/pull/21664)) - Require workflow execution authorization when resuming runs through core APIs ([#21602](https://github.com/mastra-ai/mastra/pull/21602)) - Fixed `ToolCallFilter` destroying stored tool calls. The filter used to rewrite the shared message list, so its filtered history was written back to memory and the original tool calls and results were permanently replaced with text. It now filters only the prompt sent to the model, leaving stored messages, memory, and UI history untouched. ([#21631](https://github.com/mastra-ai/mastra/issues/21631)) ([#21669](https://github.com/mastra-ai/mastra/pull/21669)) **Behavior change:** tool calls made during the current agent loop are no longer filtered by default, so the agent can still act on results it just produced. Use `filterAfterToolSteps` to filter during the loop: ```typescript // Keeps only the two most recent tool-producing steps new ToolCallFilter({ filterAfterToolSteps: 2 }); ``` - Fixed automatic tool resumption so approval-gated tools require an explicit trusted approval decision and remain suspended for ambiguous input. ([#21539](https://github.com/mastra-ai/mastra/pull/21539)) - Fixed `requestContextSchema` typing so it matches the open-map runtime without losing declared-key safety, and restored assignability of schema-typed agents to bare `Agent`. ([#21525](https://github.com/mastra-ai/mastra/pull/21525)) Declared keys on `get`/`set`/`has`/`delete` stay strictly typed (typos still fail). Runtime-only keys — including reserved middleware keys like `mastra__resourceId` — are available through `getRaw`/`setRaw`/`hasRaw`/`deleteRaw`, which return/accept `unknown` instead of forcing `as never` casts. Also fixed generic helpers typed as `(agent: Agent) => ...` rejecting agents that declare a `requestContextSchema` (`TRequestContext` now defaults to `any` on `Agent`/`SubAgent` so the invariant generic remains assignable). ```ts const ctx = new RequestContext<{ tenantTier?: 'free' | 'pro' }>(); ctx.set('tenantTier', 'pro'); ctx.setRaw('session.cache', { hits: 0 }); const cache = ctx.getRaw('session.cache'); // unknown const schemaAgent = new Agent({ id: 'repro', name: 'repro', instructions: 'hi', model: 'openai/gpt-5.6-sol', requestContextSchema: z.object({ tenantTier: z.enum(['free', 'pro']).optional() }), }); declare function driveAgent(agent: Agent): Promise; void driveAgent(schemaAgent); // ok ``` Fixes #21286 - Fixed Anthropic cache-write usage to preserve 5-minute and 1-hour token counts across streams and observability. ([#21563](https://github.com/mastra-ai/mastra/pull/21563)) - Added a `blocking` option to `AgentController.onSessionCreated`. Blocking listeners are awaited before `createSession()` resolves, so hosts can seed session state (for example observational-memory settings loaded from storage) before the caller can start a run. Blocking listeners run sequentially in registration order before fire-and-forget listeners are notified. Listener failures remain isolated and logged; default (non-blocking) listeners keep their fire-and-forget behavior. ([#21423](https://github.com/mastra-ai/mastra/pull/21423)) ```ts controller.onSessionCreated( async session => { // Runs before createSession() resolves for a newly materialized session. const settings = await loadSettings(session.identity.getResourceId()); if (settings) await session.state.set(settings); }, { blocking: true }, ); // Default listeners stay fire-and-forget. controller.onSessionCreated(session => audit(session)); ``` - Fixed workflow snapshot persistence emitting duplicate durable operation IDs. Running a workflow on a durable engine such as `@mastra/inngest` logged `AUTOMATIC_PARALLEL_INDEXING` warnings whenever a step suspended, because two snapshot writes on the same execution path shared one operation ID. Each snapshot write now uses a distinct ID, so suspend, resume, cancel, pause and sleep runs no longer produce the warning. Fixes #21639. ([#21696](https://github.com/mastra-ai/mastra/pull/21696)) - Fixed DurableAgent snapshots to prune duplicated foreach suspension data so long-running agents do not exceed storage document limits. ([#21537](https://github.com/mastra-ai/mastra/pull/21537)) - Fix sub-agent delegation permanently grafting the supervisor's memory onto a memory-less sub-agent instance. Sub-agents are usually long-lived singletons, so the first supervisor to delegate would set its memory on the shared instance and every later supervisor — and every direct invocation of that sub-agent — would keep reading and writing that first supervisor's memory. The supervisor's memory is now passed through the delegated run's request context, so inheritance applies for that invocation only and the sub-agent instance is never mutated. ([#21692](https://github.com/mastra-ai/mastra/pull/21692)) Inherited memory is scoped to a single in-process delegation: it applies to the sub-agent the supervisor delegated to and not to agents that sub-agent delegates to in turn, and it is not carried across a durable run's suspend/resume boundary. A sub-agent that needs memory in its own right should declare it, as before. - Fixed observed durable run cancellation so calling `cleanup()` immediately after `abort()` no longer removes run state before terminal events and lifecycle callbacks are delivered. ([#21527](https://github.com/mastra-ai/mastra/pull/21527)) Related to https://github.com/mastra-ai/mastra/issues/21522 - Tightened `AgentConfig.tools` typing so each entry must be an actual tool object. Previously a plain function such as `tools: { myTool: () => realTool }` passed type checking and then threw `TOOL_INVALID_FORMAT` at runtime, because the provider-defined tool member of the union is all-optional with an index signature. Provider-defined tools now require an `id` when used as a tools-map entry, mirroring the existing runtime check. Setting `tools` itself to a resolver function is still supported. ([#21705](https://github.com/mastra-ai/mastra/pull/21705)) - Fixed MCP-exposed agents so nested tools receive elicitation and other MCP context. (#20549) ([#21548](https://github.com/mastra-ai/mastra/pull/21548)) - Normalize instruction-file paths (AGENTS.md/CLAUDE.md/CONTEXT.md) to forward slashes in dynamic `system-reminder` injection. On Windows, `node:path` produced backslash-separated paths that leaked into the prompt reminders and the metadata used to avoid re-injection; paths are now identical across platforms and match the paths tool calls report. Windows filesystem APIs accept forward slashes, so file reads are unaffected. ([#21071](https://github.com/mastra-ai/mastra/pull/21071)) ## 1.60.0-alpha.14 ### Minor Changes - Added `AgentControllerWireEvent`, `JsonReadyAgentControllerEvent`, `ErrorCarryingAgentControllerEvent` and `WireDisplayState` to `@mastra/core/agent-controller`. They describe an agent controller event as it crosses an HTTP boundary — display-state Maps as records, errors as `{ name, message }`, dates as ISO strings — and are derived from `AgentControllerEvent` itself, so a client can type what it receives without redeclaring the controller's events by hand. ([#21761](https://github.com/mastra-ai/mastra/pull/21761)) ```ts import type { AgentControllerWireEvent } from '@mastra/core/agent-controller'; function onEvent(event: AgentControllerWireEvent) { if (event.type === 'display_state_changed') { // a record after JSON, where the controller holds a Map Object.values(event.displayState.activeTools); } if (event.type === 'error') { event.error.message; } } ``` ## 1.60.0-alpha.13 ### Minor Changes - Added checkpoint support to LocalSandbox and a checkpoint capability signal to sandboxes. Sandboxes now expose `supportsCheckpoints` so features can detect whether `snapshot()` persists real state. LocalSandbox gained filesystem-backed checkpoints: pass `checkpointName` to seed the working directory on `start()` and persist it on `snapshot()`, and `seedCheckpointName` as a boot-only fallback (for example a shared warm base image) that never gets overwritten by later snapshots. ([#21798](https://github.com/mastra-ai/mastra/pull/21798)) ```ts import { LocalSandbox } from '@mastra/core/workspace'; const sandbox = new LocalSandbox({ workingDirectory: './workspace', checkpointName: 'session-123', seedCheckpointName: 'repo-base', }); await sandbox.start(); await sandbox.snapshot(); ``` - Added foundational support for an upcoming experimental memory capability across storage, runtime, and developer tooling. ([#19538](https://github.com/mastra-ai/mastra/pull/19538)) - Added invoker-bound tool provider connections. Providers now receive the connection kind, toolkit, and live RequestContext so they can execute as the authenticated user without coupling provider identity to the Memory resource. The stored connection ID continues to select the exact provider account. ([#21783](https://github.com/mastra-ai/mastra/pull/21783)) ```typescript import type { ToolProviders } from '@mastra/core/tool-provider'; const toolProviders: ToolProviders = { crm: { tools: { CREATE_LEAD: { toolkit: 'salesforce' }, }, connections: { salesforce: [ { kind: 'invoker', toolkit: 'salesforce', connectionId: 'connected-account-id', }, ], }, }, }; ``` ### Patch Changes - LocalSandbox now replaces an existing checkpoint atomically. A concurrent boot never observes a missing or half-written checkpoint while a snapshot is being saved. ([#21798](https://github.com/mastra-ai/mastra/pull/21798)) ## 1.60.0-alpha.12 ### Patch Changes - Skills discovery no longer blocks agent turns: the skills processors serve the cached catalog and revalidate in the background, and refresh swaps the catalog atomically. Mid-session skill changes now appear one turn later (plus a staleness cooldown of up to 30 seconds); pass blockingRefresh: true to SkillsProcessor or SkillSearchProcessor to restore same-turn freshness by awaiting the refresh before the first step. ([#21555](https://github.com/mastra-ai/mastra/pull/21555)) - Normalize instruction-file paths (AGENTS.md/CLAUDE.md/CONTEXT.md) to forward slashes in dynamic `system-reminder` injection. On Windows, `node:path` produced backslash-separated paths that leaked into the prompt reminders and the metadata used to avoid re-injection; paths are now identical across platforms and match the paths tool calls report. Windows filesystem APIs accept forward slashes, so file reads are unaffected. ([#21071](https://github.com/mastra-ai/mastra/pull/21071)) ## 1.60.0-alpha.11 ### Minor Changes - Added a `durable` option to stored agents so agents created through the Agents API can run with durable execution — no code deployment required. ([#21715](https://github.com/mastra-ai/mastra/pull/21715)) ```typescript await mastraClient.createStoredAgent({ id: 'helper', name: 'Helper', instructions: 'You are a helpful assistant.', model: { provider: 'openai', name: 'gpt-5' }, durable: true, }); ``` Pass `true` for defaults, or `{ maxSteps, cleanupTimeoutMs }` to tune the durable loop. Cache and pubsub are inherited from the server's Mastra instance, so configure distributed backends there for durability across replicas. Automatic recovery is still configured in code via `recovery.durableAgents`. ### Patch Changes - Fixed model-backed processors (language detector, prompt injection detector, PII detector, system prompt scrubber, and moderation) dropping the request context. Their internal detection agents now receive the caller's RequestContext, so dynamic model resolvers and gateways can select models per request. ([#21709](https://github.com/mastra-ai/mastra/pull/21709)) - Retry transient Windows file locks when atomically replacing provider registry cache files. ([#21425](https://github.com/mastra-ai/mastra/pull/21425)) - Exported `ReservedThreadMetadataKey`, the list of thread-metadata keys an agent controller session owns for its own bookkeeping (selected model and mode, observer/reflector config, token usage, persisted preferences). Packages that cannot import the list as a value can now pin their copy of it to the real one: ([#21739](https://github.com/mastra-ai/mastra/pull/21739)) ```ts import type { ReservedThreadMetadataKey } from '@mastra/core/agent-controller'; const RESERVED = { currentModelId: true /* … */ } satisfies Record; ``` The list itself is unchanged — only its keys are now nameable, so a package that mirrors it fails to compile the moment the two fall out of step. - Sub-agent delegation no longer attempts to resume when the model supplies `resumeData` without a suspended run. Previously the delegation step chose the resume path on `resumeData` alone and called `resumeGenerate`/`resumeStream` with an undefined run id, throwing `AGENT_RESUME_NO_SNAPSHOT_FOUND` before the sub-agent executed. It now starts a fresh delegation in that case. ([#21729](https://github.com/mastra-ai/mastra/pull/21729)) - Estimate media token cost in TokenLimiterProcessor instead of serializing base64 payloads as text. File parts and media-shaped tool results (`{ data, mediaType }`) previously fell through to `JSON.stringify`, so a single image could add thousands of phantom tokens and truncate history unnecessarily (#21731). ([#21737](https://github.com/mastra-ai/mastra/pull/21737)) ## 1.60.0-alpha.10 ### Minor Changes - Added `modelSettings.timeout` so you can put a time limit on agent runs. Set `totalMs` to cap how long a whole run may take, including every reasoning step, tool call and retry, and `stepMs` to cap a single model call. Going over either limit fails with a `MastraTimeoutError`. A `totalMs` timeout ends the run outright, while a `stepMs` timeout moves on to the next model when you have configured fallback models, so a slow provider no longer stalls the run. Closes #15667 ([#21724](https://github.com/mastra-ai/mastra/pull/21724)) ## 1.60.0-alpha.9 ### Minor Changes - Added an opt-in `propagate` flag on the FGA actor signal. Set `actor: { actorKind: 'system', propagate: true }` when starting a workflow run and the actor is forwarded into the agent and tool calls made for declarative `.then(agent)` and `.then(tool)` steps, so system and scheduled runs no longer fail membership resolution. Propagation stays opt-in, skips custom step `execute` functions, and can be overridden per step. (#19064) ([#21713](https://github.com/mastra-ai/mastra/pull/21713)) ### Patch Changes - Added `settled()` to the base memory class. Memory implementations can do work in the background after an agent run returns, and this gives callers a way to wait for it before closing a storage connection they own. The default implementation does nothing; `@mastra/memory` overrides it. ([#21708](https://github.com/mastra-ai/mastra/pull/21708)) - Fix per-step `reasoningText` and `reasoning` accumulating across steps for reasoning models. Each step in a multi-step run now reports only the reasoning produced during that step, matching the existing behavior of the per-step `text` field. Run-level `reasoningText` and `reasoning` remain the full concatenation across all steps. ([#21711](https://github.com/mastra-ai/mastra/pull/21711)) - onAbort now receives the partial assistant text streamed before a mid-generation abort, alongside the completed steps ([#21718](https://github.com/mastra-ai/mastra/pull/21718)) - Tightened `AgentConfig.tools` typing so each entry must be an actual tool object. Previously a plain function such as `tools: { myTool: () => realTool }` passed type checking and then threw `TOOL_INVALID_FORMAT` at runtime, because the provider-defined tool member of the union is all-optional with an index signature. Provider-defined tools now require an `id` when used as a tools-map entry, mirroring the existing runtime check. Setting `tools` itself to a resolver function is still supported. ([#21705](https://github.com/mastra-ai/mastra/pull/21705)) ## 1.60.0-alpha.8 ### Minor Changes - Added `ProcessHandle.closeStdin()` to signal end-of-file to background processes. Local and Docker sandboxes support closing stdin, while providers without an available stdin-close API return a provider-specific unsupported-operation error. Providers signal the unsupported case with the new `UnsupportedStdinCloseError`, and the base class supplies that behavior by default so existing `ProcessHandle` subclasses keep compiling. Calling `handle.writer.end()` also closes stdin, and finishes without an error when the provider cannot close stdin. ([#21606](https://github.com/mastra-ai/mastra/pull/21606)) - MCP tools served over HTTP now see the authenticated caller. When an MCP server runs behind a Mastra server with `server.auth` configured, the resolved user is bridged into `extra.authInfo` automatically, on both the streamable HTTP and SSE transports. Previously `extra.authInfo` was always undefined because the request handed to the MCP transport was rebuilt without the auth data. ([#21689](https://github.com/mastra-ai/mastra/pull/21689)) **Custom verification** If your own middleware verifies the caller, build the auth info yourself with the new `server.mcpOptions.setRequestAuth` hook: ```ts export const mastra = new Mastra({ mcpServers: { myServer }, server: { middleware: [verifyBearerToken], mcpOptions: { setRequestAuth: (req, requestContext) => { const payload = requestContext.get('bearerPayload'); req.auth = { token: payload.token, clientId: payload.sub, scopes: payload.scope.split(' ') }; }, }, }, }); ``` Fixes #17291 - Added `delegation.hookErrorStrategy` so a failing delegation hook no longer passes silently. ([#21695](https://github.com/mastra-ai/mastra/pull/21695)) Previously, if `onDelegationStart`, `messageFilter`, or `onDelegationComplete` threw, the error was only logged and the delegation carried on. A throwing `onDelegationStart` could not block a subagent, and a throwing `onDelegationComplete` lost whatever the hook was responsible for with no programmatic signal. **Detecting hook failures** Hook failures are now always recorded on the run's request context, whatever strategy you choose: ```ts const requestContext = new RequestContext(); await parentAgent.generate('Research AI trends', { requestContext }); const hookErrors = requestContext.get('__mastra_delegationHookErrors') ?? []; ``` **Failing the delegation instead** Opt in to fail-closed behavior. A throwing `onDelegationStart` then blocks the subagent, and a throwing `messageFilter` or `onDelegationComplete` surfaces to the parent as a failed tool call: ```ts await parentAgent.generate('Research AI trends', { delegation: { hookErrorStrategy: 'throw', onDelegationComplete }, }); ``` The default remains `'warn'`, so existing behavior is unchanged. Fixes #21624. - Added an opt-in `persistPartialOnAbort` stream option that saves the assistant text streamed before a cancellation. Only the pre-abort snapshot is persisted, so output a provider keeps producing after cancellation is discarded, and nothing is saved when no text was streamed. Aborted streams still persist nothing by default. Also fixed thread creation tracking so a thread created during step persistence is no longer re-created on finish. Fixes #17510. ([#21600](https://github.com/mastra-ai/mastra/pull/21600)) ### Patch Changes - Fixed evented workflow restarts so conditional branches resume at the correct execution path and aggregate their results. ([#21603](https://github.com/mastra-ai/mastra/pull/21603)) - Improved observational memory progress restoration by rebuilding UI status from the durable memory record instead of polluted message history. Restored progress now also reports the memory thresholds you configured, where it previously always showed the built-in defaults. ([#21604](https://github.com/mastra-ai/mastra/pull/21604)) - Refactor MastraCompositeStore domain wiring to be data driven. The constructor resolve block and the init roll call previously enumerated every storage domain by hand, so a domain registered in the stores map but missing from either list was silently skipped: init reported success while the domain's tables were never created. Both lists are now replaced by iteration over a typed DOMAIN_KEYS constant with a compile time exhaustiveness guard, and a conformance test proves a newly registered domain cannot dodge init. DOMAIN_KEYS is a new exported constant, following the existing EDITOR_DOMAINS pattern. No behavior change for the existing domains, with two narrow carve outs: a stores map entry without an init function is now skipped during init instead of throwing a TypeError, and an entry present in the stores map that the old roll call never named now gets initialized. ([#21672](https://github.com/mastra-ai/mastra/pull/21672)) - Fixed agent memory so that calls now fail immediately when the given thread belongs to a different resource. Previously agent.stream() and agent.generate() would silently run the model and drop the turn instead of reporting the ownership mismatch (#21641). ([#21691](https://github.com/mastra-ai/mastra/pull/21691)) - Fixed tool request context schemas to pass transformed values, coercions, and defaults to execute while retaining input-form values for nested validation. Explicit mutations now preserve schema input encoding and reject unencodable transformed writes without corrupting the shared context. ([#21653](https://github.com/mastra-ai/mastra/pull/21653)) - Restored the Studio memory bar token counts after a reload by reading them from the stored observational memory record. ([#21604](https://github.com/mastra-ai/mastra/pull/21604)) - Added regression coverage keeping memory-sourced messages exempt from the thread ID check, so resource-scoped observational memory can pull in messages from a resource's other threads without failing the turn. ([#21702](https://github.com/mastra-ai/mastra/pull/21702)) - Fixed `MockMemory` so its working memory merge keeps parity with `@mastra/memory`: a `null` now deletes a field on the first write and inside newly created nested objects, instead of being stored literally. ([#21687](https://github.com/mastra-ai/mastra/pull/21687)) - Fixed stale tool suspension metadata when resuming with `agent.resumeStream()`. ([#21319](https://github.com/mastra-ai/mastra/pull/21319)) A tool suspended with `suspendSchema` left its `suspendedTools` entry on the saved assistant message after it was resumed via `agent.resumeStream(resumeData, { runId, toolCallId })`. A client reloading the thread read the already-resolved tool as still waiting for input, and could send the next resume to the wrong tool call. The entry is now cleared for both resume conventions. **Falsy resume payloads are handled correctly** A tool whose `resumeSchema` is a primitive can legitimately be resumed with `false`, `0` or `""`, which is how a boolean human-in-the-loop tool declines. Those payloads now clear the suspension entry, and a delegated `agent-*` or `workflow-*` tool resumed with one keeps resuming its existing sub-run instead of silently starting a fresh one. **Approving one tool call no longer clears another's pending state** When two calls of the same tool were waiting and one of them was approved after its approval requirement had changed, the other call's pending state could be removed, leaving it stuck and unresumable. Approving one call now leaves the other waiting as expected. Fixes [#19083](https://github.com/mastra-ai/mastra/issues/19083). - Stop writing run-local workflow events to the server cache. ([#21675](https://github.com/mastra-ai/mastra/pull/21675)) Durable agents wrap `mastra.pubsub` in a `CachingPubSub`, so every publish was mirrored into the shared cache for replay. Per-run `workflow.events.v2.` watch events are only ever consumed in the publishing process, and their payloads accumulate step results — mirroring them filled shared stores (e.g. Redis) with lists no other instance could read. `CachingPubSub` already skipped caching for publishes marked `localOnly`, but the caching layer sits above the proxy that sets that flag, so it never saw it. `CachingPubSubOptions` now accepts an optional `shouldCache?: (topic: string) => boolean` predicate, and the durable agent uses it to keep run-local topics out of the cache. Those events are still delivered live to subscribers; agent stream topics are unaffected and still replay as before. - Fixed `createStep(tool)` sometimes misinterpreting a tool as a custom step. Copied or renamed tools — such as tools resolved through a tool provider — are now correctly detected and build a proper tool step. This applies to both the default `createStep` and the one exported from `@mastra/core/workflows/evented`. ([#21512](https://github.com/mastra-ai/mastra/pull/21512)) - Stop evicting long-running agent runs at the suspended-run TTL. `MASTRA_SUSPENDED_RUN_TTL_MS` now bounds how long a run-scoped internal workflow may sit **idle**, not how long a run may take. Previously the lazy sweep measured wall-clock age from registration, so any run that legitimately executed past the TTL (30 minutes by default) had its workflow registration and run scope dropped mid-flight and went silent. Abandoned or suspended-and-never-resumed runs are still released on the same bound, so the memory protection is unchanged. Operators who raised the knob to work around this can return it to the default. ([#21693](https://github.com/mastra-ai/mastra/pull/21693)) - Added stable submit_plan identity to suspension payloads and resumed results. ([#21658](https://github.com/mastra-ai/mastra/pull/21658)) - Fixed tool results being lost when an agent calls a server-side tool and a client-side tool in the same step. The server-side tool's result is now streamed to the client and saved to history before the agent hands control back for the pending client-side tool, so clients no longer wait forever on a tool call that already finished. ([#21688](https://github.com/mastra-ai/mastra/pull/21688)) - Fixed model configuration validation so configs with non-string routing fields (`id`, `providerId`, or `modelId`) are rejected with the standard "Invalid model configuration provided" error instead of failing later inside the model router. Fixes #21588 ([#21664](https://github.com/mastra-ai/mastra/pull/21664)) - Require workflow execution authorization when resuming runs through core APIs ([#21602](https://github.com/mastra-ai/mastra/pull/21602)) - Fixed `ToolCallFilter` destroying stored tool calls. The filter used to rewrite the shared message list, so its filtered history was written back to memory and the original tool calls and results were permanently replaced with text. It now filters only the prompt sent to the model, leaving stored messages, memory, and UI history untouched. ([#21631](https://github.com/mastra-ai/mastra/issues/21631)) ([#21669](https://github.com/mastra-ai/mastra/pull/21669)) **Behavior change:** tool calls made during the current agent loop are no longer filtered by default, so the agent can still act on results it just produced. Use `filterAfterToolSteps` to filter during the loop: ```typescript // Keeps only the two most recent tool-producing steps new ToolCallFilter({ filterAfterToolSteps: 2 }); ``` - Fixed workflow snapshot persistence emitting duplicate durable operation IDs. Running a workflow on a durable engine such as `@mastra/inngest` logged `AUTOMATIC_PARALLEL_INDEXING` warnings whenever a step suspended, because two snapshot writes on the same execution path shared one operation ID. Each snapshot write now uses a distinct ID, so suspend, resume, cancel, pause and sleep runs no longer produce the warning. Fixes #21639. ([#21696](https://github.com/mastra-ai/mastra/pull/21696)) - Fix sub-agent delegation permanently grafting the supervisor's memory onto a memory-less sub-agent instance. Sub-agents are usually long-lived singletons, so the first supervisor to delegate would set its memory on the shared instance and every later supervisor — and every direct invocation of that sub-agent — would keep reading and writing that first supervisor's memory. The supervisor's memory is now passed through the delegated run's request context, so inheritance applies for that invocation only and the sub-agent instance is never mutated. ([#21692](https://github.com/mastra-ai/mastra/pull/21692)) Inherited memory is scoped to a single in-process delegation: it applies to the sub-agent the supervisor delegated to and not to agents that sub-agent delegates to in turn, and it is not carried across a durable run's suspend/resume boundary. A sub-agent that needs memory in its own right should declare it, as before. ## 1.60.0-alpha.7 ### Patch Changes - Honor stored agent version overrides on direct programmatic agent calls. Version overrides supplied via `MASTRA_VERSIONS_KEY` on the requestContext, the `versions` call option, or Mastra-level `versions` defaults now resolve the called agent itself to its stored version (previously they only applied to sub-agent delegation and HTTP routes), fixing the inconsistency where published overrides were served over HTTP but silently ignored in programmatic paths. ([#21383](https://github.com/mastra-ai/mastra/pull/21383)) ## 1.60.0-alpha.6 ### Patch Changes - Fixed durable step-start events publishing full model requests. ([#21572](https://github.com/mastra-ai/mastra/pull/21572)) - Rewrite system-reminder tags in Azure-bound user text and system instructions to avoid content moderation refusals while preserving stored history. ([#21599](https://github.com/mastra-ai/mastra/pull/21599)) - Fixed getWorkflowRunById and getWorkflowRunSteps so nested workflows report the correct single suspended leaf step. Obsolete suspension details are removed after the step resumes or completes. (#21229) ([#21565](https://github.com/mastra-ai/mastra/pull/21565)) ## 1.60.0-alpha.5 ### Patch Changes - Fixed workflow cancellation so sleep() and sleepUntil() stop promptly without overwriting the canceled run status. ([#21570](https://github.com/mastra-ai/mastra/pull/21570)) ## 1.60.0-alpha.4 ### Minor Changes - Added optional `filename` property to `FilePayload`. When an agent emits a file chunk with a filename, the Channel layer now preserves it through to the Chat SDK adapter instead of generating a name from the MIME type. Existing file chunks without a filename continue using the `generated.` fallback. ([#21135](https://github.com/mastra-ai/mastra/pull/21135)) ```ts const chunk: FilePayload = { data: fileBuffer, mimeType: 'text/plain', filename: 'report.txt', // preserved through to the channel adapter }; ``` ### Patch Changes - Fixed routed models so tool results can include images and files. (#21183) ([#21569](https://github.com/mastra-ai/mastra/pull/21569)) - Fixed replay streams so disconnecting during history replay also stops the live stream. ([#21556](https://github.com/mastra-ai/mastra/pull/21556)) - Fixed dataset experiments with memory-enabled agents when no resource or thread IDs are provided. ([#21552](https://github.com/mastra-ai/mastra/pull/21552)) - Fixed array textStream output so it always stays valid JSON, even when the first streamed chunk already includes elements ([#21559](https://github.com/mastra-ai/mastra/pull/21559)) - Restore pending tool approval state when reloading v6 UI messages. ([#17948](https://github.com/mastra-ai/mastra/pull/17948)) - Fix AgentController subscriptions hanging when remote runs lose their lease ([#21540](https://github.com/mastra-ai/mastra/pull/21540)) - Fixed Anthropic cache-write usage to preserve 5-minute and 1-hour token counts across streams and observability. ([#21563](https://github.com/mastra-ai/mastra/pull/21563)) ## 1.60.0-alpha.3 ### Minor Changes - Added live-tail PubSub subscriptions with `startFrom: "latest"` and an explicit `supportsOffsets` capability. ([#21535](https://github.com/mastra-ai/mastra/pull/21535)) ```typescript await pubsub.subscribe(topic, callback, { startFrom: 'latest' }); ``` The default remains `"earliest"`, and existing consumer groups keep their checkpoint. ### Patch Changes - Fixed automatic tool resumption so approval-gated tools require an explicit trusted approval decision and remain suspended for ambiguous input. ([#21539](https://github.com/mastra-ai/mastra/pull/21539)) - Fixed MCP-exposed agents so nested tools receive elicitation and other MCP context. (#20549) ([#21548](https://github.com/mastra-ai/mastra/pull/21548)) ## 1.60.0-alpha.2 ### Minor Changes - Added per-message Signal metadata to Channel handlers. Use `ctx.signalMetadata` to attach serializable, non-sensitive context that follows both idle and active message delivery: ([#21330](https://github.com/mastra-ai/mastra/pull/21330)) ```typescript handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.signalMetadata.attachmentIds = ['file-1'] await defaultHandler(thread, message) }, } ``` ### Patch Changes - Fixed workspace skill resolvers so concurrent requests keep their own resolved skill set throughout execution. ([#21524](https://github.com/mastra-ai/mastra/pull/21524)) - Fixed durable agents pausing between model steps by suppressing unused internal workflow step events that repeatedly serialized cumulative conversation state. ([#21529](https://github.com/mastra-ai/mastra/pull/21529)) - Fixed `DurableAgent.listActiveRuns()` and `recoverActiveRuns()` loading every running run's full workflow snapshot into memory at once. Candidate runs are now fetched from storage in bounded batches of 100 rows, so discovering or recovering runs against a large backlog no longer risks exhausting process memory. Fixes [#21501](https://github.com/mastra-ai/mastra/issues/21501). ([#21518](https://github.com/mastra-ai/mastra/pull/21518)) - Fixed tool search and skill search losing loaded state for HTTP requests that pass a thread via memory options. Processors now resolve the thread ID from the memory request context, and single-name load_tool results are recognized when reconstructing loaded tools from conversation messages with storage: 'context'. Fixes #21508 ([#21521](https://github.com/mastra-ai/mastra/pull/21521)) - Fixed durable run recovery so a reconnecting thread subscriber receives the remaining output and terminal event of a recovered run. ([#21526](https://github.com/mastra-ai/mastra/pull/21526)) Only one recovery of a run can be active at a time. A second concurrent `durableAgent.recover(runId)` call now fails fast with the error `DURABLE_AGENT_RECOVER_ALREADY_IN_PROGRESS` instead of restarting the run twice. - Fixed durable agent runs failing during shutdown by waiting for in-flight workflow persistence before closing storage. ([#21532](https://github.com/mastra-ai/mastra/pull/21532)) - Fixed output processors missing response messages after a mid-run memory save (#21204). ([#21533](https://github.com/mastra-ai/mastra/pull/21533)) - Preserve channel approval cards when tool output is hidden or suppressed by a custom renderer. ([#21402](https://github.com/mastra-ai/mastra/pull/21402)) Resolves https://github.com/mastra-ai/mastra/issues/21162 Custom renderers can reuse the built-in approval and tool-event formatting: ```ts import { formatToolApproval, renderBuiltInToolEvent, type ToolDisplayFn } from '@mastra/core/channels'; const renderTool: ToolDisplayFn = event => event.kind === 'approval' ? formatToolApproval(event.displayName, event.argsSummary, event.toolCallId, true) : renderBuiltInToolEvent(event, 'cards'); ``` - Fixed `requestContextSchema` typing so it matches the open-map runtime without losing declared-key safety, and restored assignability of schema-typed agents to bare `Agent`. ([#21525](https://github.com/mastra-ai/mastra/pull/21525)) Declared keys on `get`/`set`/`has`/`delete` stay strictly typed (typos still fail). Runtime-only keys — including reserved middleware keys like `mastra__resourceId` — are available through `getRaw`/`setRaw`/`hasRaw`/`deleteRaw`, which return/accept `unknown` instead of forcing `as never` casts. Also fixed generic helpers typed as `(agent: Agent) => ...` rejecting agents that declare a `requestContextSchema` (`TRequestContext` now defaults to `any` on `Agent`/`SubAgent` so the invariant generic remains assignable). ```ts const ctx = new RequestContext<{ tenantTier?: 'free' | 'pro' }>(); ctx.set('tenantTier', 'pro'); ctx.setRaw('session.cache', { hits: 0 }); const cache = ctx.getRaw('session.cache'); // unknown const schemaAgent = new Agent({ id: 'repro', name: 'repro', instructions: 'hi', model: 'openai/gpt-5.6-sol', requestContextSchema: z.object({ tenantTier: z.enum(['free', 'pro']).optional() }), }); declare function driveAgent(agent: Agent): Promise; void driveAgent(schemaAgent); // ok ``` Fixes #21286 - Fixed DurableAgent snapshots to prune duplicated foreach suspension data so long-running agents do not exceed storage document limits. ([#21537](https://github.com/mastra-ai/mastra/pull/21537)) - Fixed observed durable run cancellation so calling `cleanup()` immediately after `abort()` no longer removes run state before terminal events and lifecycle callbacks are delivered. ([#21527](https://github.com/mastra-ai/mastra/pull/21527)) Related to https://github.com/mastra-ai/mastra/issues/21522 ## 1.60.0-alpha.1 ### Minor Changes - Client-executed tools (tools without a server-side `execute`) now fire their `onOutput` lifecycle hook when the browser returns the tool result on a follow-up request. Previously `onOutput` only fired after a server-side `execute`, so client tools never reported their output. ([#21377](https://github.com/mastra-ai/mastra/pull/21377)) ```ts const agent = new Agent({ // ... tools: { browserTool: createTool({ id: 'browserTool', description: 'Runs in the browser', inputSchema: z.object({ query: z.string() }), // No execute — the client runs the tool. This hook now fires when // the client sends the result back. onOutput: async ({ toolCallId, output }) => { console.log('client tool resolved', toolCallId, output); }, }), }, }); ``` The hook fires for trailing tool results that match a raw tool call from the preceding assistant message, so the follow-up request must include both (as `@mastra/client-js` does automatically). This works with standard, legacy, and durable agents, including requests with a same-name serialized `clientTools` entry. The callback receives `{ toolCallId, toolName, output, abortSignal }`. Because the input is client-provided, treat the output as untrusted data. Delivery is at least once, and the hook does not fire when an input processor rejects the request. Keep hooks idempotent. ### Patch Changes - Fixed the `tail` parameter of the workspace `execute_command` and `get_process_output` tools rejecting numeric strings. Models sometimes send number parameters as strings, for example `"10"` instead of `10`. The `timeout` parameter already tolerated this, but `tail` failed validation and the command never ran, wasting a turn. In observed cases the model then fabricated a success result instead of retrying. Numeric strings are now coerced to numbers for `tail` in both tools. ([#21493](https://github.com/mastra-ai/mastra/pull/21493)) - Fixed generated streams to preserve provider metadata on text and file events. ([#21441](https://github.com/mastra-ai/mastra/pull/21441)) - Added public read-only thread query methods on `AgentController` and a `initStorage()` method that initializes storage without provisioning a workspace. Use these to read threads or messages without paying the workspace/sandbox startup cost that `createSession` incurs. ([#21474](https://github.com/mastra-ai/mastra/pull/21474)) ```ts // Before: had to create a session (which called Workspace.init() -> sandbox.start()) const session = await controller.createSession({ resourceId }); const threads = await session.thread.list(); // After: read directly from storage, no session, no workspace const threads = await controller.queryThreads({ resourceId }); const messages = await controller.queryThreadMessages({ threadId, limit: 50 }); const thread = await controller.queryThreadById({ threadId }); ``` `queryThreads`, `queryThreadById`, and `queryThreadMessages` were already used internally; they are now part of the public `AgentController` API. Each lazily calls `initStorage()`, so callers don't need to pre-init. - Added a `blocking` option to `AgentController.onSessionCreated`. Blocking listeners are awaited before `createSession()` resolves, so hosts can seed session state (for example observational-memory settings loaded from storage) before the caller can start a run. Blocking listeners run sequentially in registration order before fire-and-forget listeners are notified. Listener failures remain isolated and logged; default (non-blocking) listeners keep their fire-and-forget behavior. ([#21423](https://github.com/mastra-ai/mastra/pull/21423)) ```ts controller.onSessionCreated( async session => { // Runs before createSession() resolves for a newly materialized session. const settings = await loadSettings(session.identity.getResourceId()); if (settings) await session.state.set(settings); }, { blocking: true }, ); // Default listeners stay fire-and-forget. controller.onSessionCreated(session => audit(session)); ``` ## 1.59.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`587f6ef`](https://github.com/mastra-ai/mastra/commit/587f6efcfc25880b93760a8607d1cd381ec612fe)) ## 1.59.0 ### Minor Changes - Fixed traces that start under an external parent span not appearing in Mastra Studio. Resumed agent and workflow runs keep their link to the suspended run's trace. ([#20499](https://github.com/mastra-ai/mastra/pull/20499)) - Renamed CostGuardProcessor to TokenCostControl, improved its reliability and diagnostics, and added new budgeting options. ([#21372](https://github.com/mastra-ai/mastra/pull/21372)) **Rename** - `CostGuardProcessor` is now `TokenCostControl` with processor id `'token-cost-control'`. The `CostGuardProcessor` export (and the `CostGuardOptions`, `CostGuardUsage`, `CostGuardBreakdownEntry`, `CostGuardTripwireMetadata`, and `CostGuardViolationDetail` types) remains available as a deprecated alias for the same class and will be removed in a future major version. **Improvements** - Each cost check now issues fewer queries against observability storage. - Diagnostics now go through the Mastra logger, and failed cost queries now log diagnostics and allow the request to continue instead of failing silently. - With the warn strategy, warnings and the onViolation callback now fire at most once per request instead of on every step. - Violation messages no longer contain float precision artifacts (e.g. 0.30000000000000004 now renders as 0.3). **New options** - `warnAtPercent`: soft threshold that warns (without blocking) when cost reaches a percentage of the limit. - `maxCost` now also accepts a function of RequestContext for per-tier or per-user budgets. - New scopes `user`, `organization`, and `session` track cumulative cost per userId, organizationId, and sessionId (read from the matching RequestContext keys; traces must carry the matching span metadata). - `includeBreakdown`: attaches a per-provider/model cost breakdown to violations. ```typescript const tokenCostControl = new TokenCostControl({ maxCost: requestContext => (requestContext?.get('tier') === 'pro' ? 10.0 : 1.0), scope: 'user', warnAtPercent: 80, includeBreakdown: true, }); ``` - Added `Agent.listActiveThreadRuns()` and `AgentController.listActiveThreadRuns()`. They list every run currently in flight across resources and threads, from the same in-process tracking as `getActiveThreadRunId()`. ([#21353](https://github.com/mastra-ai/mastra/pull/21353)) ```ts const runs = agent.listActiveThreadRuns(); // [{ runId: 'run-1', resourceId: 'workspace-a', threadId: 'thread-1' }] ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`088e41e`](https://github.com/mastra-ai/mastra/commit/088e41e434ed05f2c674b254f1034ec46a57a7be)) - Fixed session-deleted listeners missing their notification when a session teardown failed while releasing its thread lock. The controller deregisters the session either way, so `onSessionDeleted` now always fires and listeners no longer hold on to a dead session. ([#21358](https://github.com/mastra-ai/mastra/pull/21358)) - Fixed model routing to use rotated gateway API keys. ([#21364](https://github.com/mastra-ai/mastra/pull/21364)) - Fixed CoreToolBuilder dropping the `~standard.jsonSchema` adapter when injecting background/resume fields onto Zod v4 tool input schemas. Invalid tool calls now return structured validation errors instead of crashing during JSON Schema conversion. ([#21187](https://github.com/mastra-ai/mastra/pull/21187)) - Fixed workflow watch events re-publishing stale step state, which could grow to megabytes per event. `workflow-step-start` events spread the step's previous result into their payload, so on loops (including durable agent runs) every start event shipped the previous iteration's `output` next to a byte-identical input `payload`. On Cloudflare Workers this made the request streaming a durable agent run exceed the 128 MB isolate memory limit and fail, even though the run itself kept executing. Watch events now only carry the fields describing the current transition: the input (`payload` or `resumePayload`), timestamps, and `status` on start events, plus the fresh result on `workflow-step-result` and `workflow-step-suspended` events. A step's prior `output`, `error`, and suspend state are no longer re-published on later events. Persisted run snapshots are unchanged. ([#20661](https://github.com/mastra-ai/mastra/pull/20661)) - Fixed memory growth from completed foreground workspace commands retaining process handles and their output. ([#21438](https://github.com/mastra-ai/mastra/pull/21438)) - Added an option to limit language servers retained by a workspace. Workspaces remain unlimited when the option is omitted. ([#21186](https://github.com/mastra-ai/mastra/pull/21186)) ```ts const workspace = new Workspace({ lsp: { maxOpenClients: 4 }, }); ``` When using `workspace.lsp.prepareQuery()`, call `release()` on the returned query after closing the file. - Deprecated `translationQuality` on `LanguageDetector`. The option previously selected prompt-level "Quality Level" guidance, but that behavior was removed when the language detection and translation prompts were streamlined. The option currently has no effect. ([#21199](https://github.com/mastra-ai/mastra/pull/21199)) Existing configurations keep working and keep type-checking. The option no longer appears in the processor provider's configuration schema, so configuration UIs stop offering a control that does nothing, and the reference docs now mark it as deprecated. For model-specific speed and quality controls, use `providerOptions` when your provider supports them: ```ts new LanguageDetector({ model, targetLanguages: ['English'], strategy: 'translate', providerOptions: { openai: { reasoningEffort: 'low' } }, }); ``` - Send opaque acting-user subjects with Platform sandbox requests, including Factory creation and reattachment flows. ([#20754](https://github.com/mastra-ai/mastra/pull/20754)) ```typescript import { PlatformSandbox } from '@mastra/platform-workspace'; const sandbox = new PlatformSandbox({ environmentId: 'env_abc', actingUserId: auth.user.id, }); ``` - Corrected the documentation for `observation.blockAfter` on the docs pages and in the editor TSDoc. Above the threshold, buffered activation may overshoot the retention target instead of activating fewer chunks; it does not force a synchronous observation. The docs also give the correct value ranges: values from 1 up to (but not including) 100 are multipliers of `messageTokens`, and values of 100 or more are absolute token counts. No runtime behavior changed. ([#21215](https://github.com/mastra-ai/mastra/pull/21215)) - Fixed output processors being skipped when the model provider throws an error. Output processors now run on failed streams with finishReason set to 'error', restoring the behavior from 1.55.0, so custom processors can observe and react to error terminals. Message history still avoids saving a user message when the provider fails before producing any output, so failed turns don't leave orphaned input in the conversation history. Cancelled (aborted) streams continue to skip output processors. Fixes #21292. ([#21370](https://github.com/mastra-ai/mastra/pull/21370)) - Couple the blocking wait in get_process_output to the run's abortSignal: ProcessHandle.wait() now accepts abortSignal and kills the process on abort (the same convention the process manager applies at spawn time), and the workspace tool forwards context.abortSignal, so aborting a run no longer leaves the tool blocking on a background process. ([#21388](https://github.com/mastra-ai/mastra/pull/21388)) ```ts const controller = new AbortController(); const result = await handle.wait({ abortSignal: controller.signal, }); // controller.abort() kills the process and wait() resolves with its exit result ``` - Fixed an issue where multiple agents sharing one storage instance would all respond in a subscribed channel thread instead of just the agent that was mentioned. Each agent now tracks its own channel threads and subscriptions. ([#21288](https://github.com/mastra-ai/mastra/pull/21288)) - Reduced persisted agent-loop snapshot size by no longer storing duplicated provider request data (measured at 24% of all persisted snapshot bytes in production). Resume behavior and step routing data are unchanged. ([#21390](https://github.com/mastra-ai/mastra/pull/21390)) - Fixed runEvals TypeScript overloads for Workflow targets so they accept gates and threshold-bearing scorer entries, matching what the runtime already supports. Workflow eval runs can now produce a verdict without type errors. Fixes #21290 ([#21380](https://github.com/mastra-ai/mastra/pull/21380)) - Fixed file-based agents so setting `workspace` to `undefined` disables the default workspace and its automatic file and shell tools. ([#21378](https://github.com/mastra-ai/mastra/pull/21378)) - **Fixed** legacy Anthropic history that contains a thinking signature without its original thinking text is sanitized before replay, preventing invalid empty signed thinking blocks from being forwarded. ([#17602](https://github.com/mastra-ai/mastra/pull/17602)) Fixes #17457. - Fixed schedule errors to preserve actionable HTTP statuses and retained structured agent errors after model fallback exhaustion. ([#21449](https://github.com/mastra-ai/mastra/pull/21449)) - Fixed gateway authentication so empty header objects fall back to API keys and are not reported as valid credentials. ([#21266](https://github.com/mastra-ai/mastra/pull/21266)) - Fixed missing TypeScript declarations for the `@mastra/core/test-utils/llm-mock` entrypoint. `MastraLanguageModelV2Mock`, `createMockModel`, and `simulateReadableStream` are now fully typed when imported in consumer projects — no need for a local ambient `declare module` shim. ([#21427](https://github.com/mastra-ai/mastra/pull/21427)) ```ts import { MastraLanguageModelV2Mock } from '@mastra/core/test-utils/llm-mock'; const mockModel = new MastraLanguageModelV2Mock({ doGenerate: async () => ({ content: [{ type: 'text', text: 'stubbed response' }], finishReason: 'stop', usage: { inputTokens: 1, outputTokens: 1, totalTokens: 2 }, warnings: [], }), }); // Spy arrays are typed as LanguageModelV2CallOptions[] mockModel.doGenerateCalls; ``` - Updated dependencies [[`cf418b6`](https://github.com/mastra-ai/mastra/commit/cf418b65efb81997e9b8dc7638eee363c5d96c96)]: - @mastra/schema-compat@1.3.7 ## 1.59.0-alpha.5 ### Patch Changes - Fixed file-based agents so setting `workspace` to `undefined` disables the default workspace and its automatic file and shell tools. ([#21378](https://github.com/mastra-ai/mastra/pull/21378)) - Fixed schedule errors to preserve actionable HTTP statuses and retained structured agent errors after model fallback exhaustion. ([#21449](https://github.com/mastra-ai/mastra/pull/21449)) ## 1.59.0-alpha.4 ### Minor Changes - Added `Agent.listActiveThreadRuns()` and `AgentController.listActiveThreadRuns()`. They list every run currently in flight across resources and threads, from the same in-process tracking as `getActiveThreadRunId()`. ([#21353](https://github.com/mastra-ai/mastra/pull/21353)) ```ts const runs = agent.listActiveThreadRuns(); // [{ runId: 'run-1', resourceId: 'workspace-a', threadId: 'thread-1' }] ``` ### Patch Changes - Fixed memory growth from completed foreground workspace commands retaining process handles and their output. ([#21438](https://github.com/mastra-ai/mastra/pull/21438)) - Send opaque acting-user subjects with Platform sandbox requests, including Factory creation and reattachment flows. ([#20754](https://github.com/mastra-ai/mastra/pull/20754)) ```typescript import { PlatformSandbox } from '@mastra/platform-workspace'; const sandbox = new PlatformSandbox({ environmentId: 'env_abc', actingUserId: auth.user.id, }); ``` ## 1.59.0-alpha.3 ### Minor Changes - Renamed CostGuardProcessor to TokenCostControl, improved its reliability and diagnostics, and added new budgeting options. ([#21372](https://github.com/mastra-ai/mastra/pull/21372)) **Rename** - `CostGuardProcessor` is now `TokenCostControl` with processor id `'token-cost-control'`. The `CostGuardProcessor` export (and the `CostGuardOptions`, `CostGuardUsage`, `CostGuardBreakdownEntry`, `CostGuardTripwireMetadata`, and `CostGuardViolationDetail` types) remains available as a deprecated alias for the same class and will be removed in a future major version. **Improvements** - Each cost check now issues fewer queries against observability storage. - Diagnostics now go through the Mastra logger, and failed cost queries now log diagnostics and allow the request to continue instead of failing silently. - With the warn strategy, warnings and the onViolation callback now fire at most once per request instead of on every step. - Violation messages no longer contain float precision artifacts (e.g. 0.30000000000000004 now renders as 0.3). **New options** - `warnAtPercent`: soft threshold that warns (without blocking) when cost reaches a percentage of the limit. - `maxCost` now also accepts a function of RequestContext for per-tier or per-user budgets. - New scopes `user`, `organization`, and `session` track cumulative cost per userId, organizationId, and sessionId (read from the matching RequestContext keys; traces must carry the matching span metadata). - `includeBreakdown`: attaches a per-provider/model cost breakdown to violations. ```typescript const tokenCostControl = new TokenCostControl({ maxCost: requestContext => (requestContext?.get('tier') === 'pro' ? 10.0 : 1.0), scope: 'user', warnAtPercent: 80, includeBreakdown: true, }); ``` ### Patch Changes - Fixed model routing to use rotated gateway API keys. ([#21364](https://github.com/mastra-ai/mastra/pull/21364)) - Reduced persisted agent-loop snapshot size by no longer storing duplicated provider request data (measured at 24% of all persisted snapshot bytes in production). Resume behavior and step routing data are unchanged. ([#21390](https://github.com/mastra-ai/mastra/pull/21390)) ## 1.59.0-alpha.2 ### Patch Changes - Deprecated `translationQuality` on `LanguageDetector`. The option previously selected prompt-level "Quality Level" guidance, but that behavior was removed when the language detection and translation prompts were streamlined. The option currently has no effect. ([#21199](https://github.com/mastra-ai/mastra/pull/21199)) Existing configurations keep working and keep type-checking. The option no longer appears in the processor provider's configuration schema, so configuration UIs stop offering a control that does nothing, and the reference docs now mark it as deprecated. For model-specific speed and quality controls, use `providerOptions` when your provider supports them: ```ts new LanguageDetector({ model, targetLanguages: ['English'], strategy: 'translate', providerOptions: { openai: { reasoningEffort: 'low' } }, }); ``` - Corrected the documentation for `observation.blockAfter` on the docs pages and in the editor TSDoc. Above the threshold, buffered activation may overshoot the retention target instead of activating fewer chunks; it does not force a synchronous observation. The docs also give the correct value ranges: values from 1 up to (but not including) 100 are multipliers of `messageTokens`, and values of 100 or more are absolute token counts. No runtime behavior changed. ([#21215](https://github.com/mastra-ai/mastra/pull/21215)) - Couple the blocking wait in get_process_output to the run's abortSignal: ProcessHandle.wait() now accepts abortSignal and kills the process on abort (the same convention the process manager applies at spawn time), and the workspace tool forwards context.abortSignal, so aborting a run no longer leaves the tool blocking on a background process. ([#21388](https://github.com/mastra-ai/mastra/pull/21388)) ```ts const controller = new AbortController(); const result = await handle.wait({ abortSignal: controller.signal, }); // controller.abort() kills the process and wait() resolves with its exit result ``` - Fixed runEvals TypeScript overloads for Workflow targets so they accept gates and threshold-bearing scorer entries, matching what the runtime already supports. Workflow eval runs can now produce a verdict without type errors. Fixes #21290 ([#21380](https://github.com/mastra-ai/mastra/pull/21380)) ## 1.59.0-alpha.1 ### Patch Changes - Fixed session-deleted listeners missing their notification when a session teardown failed while releasing its thread lock. The controller deregisters the session either way, so `onSessionDeleted` now always fires and listeners no longer hold on to a dead session. ([#21358](https://github.com/mastra-ai/mastra/pull/21358)) - Added an option to limit language servers retained by a workspace. Workspaces remain unlimited when the option is omitted. ([#21186](https://github.com/mastra-ai/mastra/pull/21186)) ```ts const workspace = new Workspace({ lsp: { maxOpenClients: 4 }, }); ``` When using `workspace.lsp.prepareQuery()`, call `release()` on the returned query after closing the file. - Fixed output processors being skipped when the model provider throws an error. Output processors now run on failed streams with finishReason set to 'error', restoring the behavior from 1.55.0, so custom processors can observe and react to error terminals. Message history still avoids saving a user message when the provider fails before producing any output, so failed turns don't leave orphaned input in the conversation history. Cancelled (aborted) streams continue to skip output processors. Fixes #21292. ([#21370](https://github.com/mastra-ai/mastra/pull/21370)) - Fixed an issue where multiple agents sharing one storage instance would all respond in a subscribed channel thread instead of just the agent that was mentioned. Each agent now tracks its own channel threads and subscriptions. ([#21288](https://github.com/mastra-ai/mastra/pull/21288)) - Fixed gateway authentication so empty header objects fall back to API keys and are not reported as valid credentials. ([#21266](https://github.com/mastra-ai/mastra/pull/21266)) - Updated dependencies [[`cf418b6`](https://github.com/mastra-ai/mastra/commit/cf418b65efb81997e9b8dc7638eee363c5d96c96)]: - @mastra/schema-compat@1.3.7-alpha.0 ## 1.59.0-alpha.0 ### Minor Changes - Fixed traces that start under an external parent span not appearing in Mastra Studio. Resumed agent and workflow runs keep their link to the suspended run's trace. ([#20499](https://github.com/mastra-ai/mastra/pull/20499)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`088e41e`](https://github.com/mastra-ai/mastra/commit/088e41e434ed05f2c674b254f1034ec46a57a7be)) - Fixed CoreToolBuilder dropping the `~standard.jsonSchema` adapter when injecting background/resume fields onto Zod v4 tool input schemas. Invalid tool calls now return structured validation errors instead of crashing during JSON Schema conversion. ([#21187](https://github.com/mastra-ai/mastra/pull/21187)) - Fixed workflow watch events re-publishing stale step state, which could grow to megabytes per event. `workflow-step-start` events spread the step's previous result into their payload, so on loops (including durable agent runs) every start event shipped the previous iteration's `output` next to a byte-identical input `payload`. On Cloudflare Workers this made the request streaming a durable agent run exceed the 128 MB isolate memory limit and fail, even though the run itself kept executing. Watch events now only carry the fields describing the current transition: the input (`payload` or `resumePayload`), timestamps, and `status` on start events, plus the fresh result on `workflow-step-result` and `workflow-step-suspended` events. A step's prior `output`, `error`, and suspend state are no longer re-published on later events. Persisted run snapshots are unchanged. ([#20661](https://github.com/mastra-ai/mastra/pull/20661)) - **Fixed** legacy Anthropic history that contains a thinking signature without its original thinking text is sanitized before replay, preventing invalid empty signed thinking blocks from being forwarded. ([#17602](https://github.com/mastra-ai/mastra/pull/17602)) Fixes #17457. ## 1.58.0 ### Minor Changes - Add `resolveSession` and `onStaleToolApproval` to agent controller channels. ([#21094](https://github.com/mastra-ai/mastra/pull/21094)) `resolveSession` creates the session for a mapped channel thread in place of the built-in call. It runs before any session exists, so a host can refuse a request before a session, a model call, or any output happens, which is something `onSessionStart` cannot do, because it runs after the session is created and swallows errors. A refusal is silent: the chat thread gets nothing, so the host's authorization message never lands in a shared channel. Other failures still post an error to the thread as before, and a refusal is distinguishable as `ChannelSessionRejectedError` with the original error as its `cause`. Approval actions now resolve their session with the action's own request context, so a shared install can revalidate the person answering an approval instead of trusting the request that opened the session. `onStaleToolApproval` reports approval actions that have no matching parked gate, which is every approval answered after a restart. Mastra still refuses to run the tool; the hook lets a durable host settle that attempt rather than dropping the answer. It receives the `runId` the approval card was rendered for: the attempt the user answered, plus the session's `currentRunId`, which after a restart is usually null or a different run. - Added typed experiment provenance and grouping fields that persist across synchronous and asynchronous runs and can be filtered when listing experiments. ([#20645](https://github.com/mastra-ai/mastra/pull/20645)) ```ts await dataset.startExperiment({ task, scorers, provenance: { source: 'github', sourceVersion: 'abc123' }, grouping: { experimentSetId: 'benchmark-1', variantId: 'candidate', trialIndex: 0 }, }); ``` - Added `instructions.ts` as a code alternative to `instructions.md` for file-based agents. Use it when the prompt needs TypeScript, for example when it's built from shared constants or resolved per request. ([#20847](https://github.com/mastra-ai/mastra/pull/20847)) Before, a computed prompt had to move into `config.ts`, splitting it away from the agent's other instructions: ```typescript // src/mastra/agents/support/config.ts import { agentConfig } from '@mastra/core/agent'; export default agentConfig({ model: 'openai/gpt-5.6-sol', instructions: ({ requestContext }) => { const tier = requestContext.get('tier') ?? 'standard'; return `You are a support agent. Treat this as a ${tier}-tier customer.`; }, }); ``` Now it lives in its own file, next to `config.ts`: ```typescript // src/mastra/agents/support/instructions.ts import { agentInstructions } from '@mastra/core/agent'; export default agentInstructions(({ requestContext }) => { const tier = requestContext.get('tier') ?? 'standard'; return `You are a support agent. Treat this as a ${tier}-tier customer.`; }); ``` The file default-exports a string, a system message, or a function returning one. `agentInstructions()` is an identity helper that only adds editor types. **Precedence** A function `config.instructions` still wins over both files, then `instructions.ts` wins over `instructions.md`, which still wins over a static `config.instructions`. Defining instructions in more than one place logs a warning naming both sources and which one wins. One upgrade case needs a rename. If an agent directory already holds an unrelated `instructions.ts`, for example a helper that `config.ts` imports, Mastra now reads that file as the agent's instructions instead of its old source, and the build fails if the file has no default export. - Added AgentController live-session deletion with a process-local listener. Deletion is runtime-only: persisted threads and messages remain in storage and can be resumed by a future session. ([#21174](https://github.com/mastra-ai/mastra/pull/21174)) ```ts controller.onSessionDeleted(session => { console.log(session.identity.getResourceId()); }); await controller.deleteSession({ resourceId: 'project-42' }); ``` - Add `beforeAll`, `afterAll`, `beforeEach`, and `afterEach` lifecycle hooks to `runExperiment` for setting up and tearing down test data around an experiment run. `beforeAll` failures fail the experiment, `beforeEach` failures mark the item failed and skip execution (with `afterEach` still running), and `afterEach` failures are logged as warnings. ([#21082](https://github.com/mastra-ai/mastra/pull/21082)) - Added an `openIfEmpty` option to streamed tool display results. Set it to ([#20909](https://github.com/mastra-ai/mastra/pull/20909)) `false` when a tool lifecycle chunk should update only an active streaming session: ```typescript toolDisplay: event => ({ kind: 'stream', chunk: createTaskUpdate(event), openIfEmpty: false, }); ``` By default, stream results continue to open a session when needed. Static channels continue to use plain-text fallback rendering. - Added support for schema-aware routes created with `createRoute()` in `server.apiRoutes`. ([#21184](https://github.com/mastra-ai/mastra/pull/21184)) ```ts const route = createRoute({ method: 'POST', path: '/items', responseType: 'json', bodySchema: z.object({ name: z.string() }), handler: async ({ name }) => ({ name }), }); const mastra = new Mastra({ server: { apiRoutes: [route] }, }); ``` - **Added file-based schedules for agents** ([#20711](https://github.com/mastra-ai/mastra/pull/20711)) File-based agents can now declare recurring tasks in a `schedules/` directory next to their tools and skills. Mastra registers them into schedule storage at startup, so a scheduled agent no longer needs any runtime registration code. Each file is one schedule: a cron expression plus exactly one execution mode. Prompt mode runs the owning agent with a fixed message. ```typescript // src/mastra/agents/support/schedules/heartbeat.ts import { defineSchedule } from '@mastra/core/agent'; export default defineSchedule({ cron: '*/5 * * * *', prompt: 'Check system health and report any failures.', }); ``` Handler mode computes the fire when it triggers, and returning `null` skips it. ```typescript // src/mastra/agents/support/schedules/billing/sweep.ts import { defineSchedule } from '@mastra/core/agent'; export default defineSchedule({ cron: '0 3 * * *', handler: async () => { const overdue = await findOverdueInvoices(); if (overdue.length === 0) return null; return { prompt: `Chase ${overdue.length} overdue invoices.` }; }, }); ``` A schedule's id is its path under `schedules/` with the extension stripped, so `billing/sweep.ts` becomes `billing/sweep` and stays stable across builds. Editing a cron patches the stored schedule and recomputes its next fire time, deleting the file deletes the schedule, and pausing a schedule through the API survives a redeploy. Schedules created with `mastra.schedules.create(...)` are in a separate namespace and are never touched by this sync. A schedule can also be a Markdown file, using cron frontmatter with the document body as the prompt. ```text // src/mastra/agents/support/schedules/cleanup.md --- cron: "0 3 * * *" --- Review tickets untouched for 30 days and close the ones that are resolved. ``` Declaring a schedule is enough to start the scheduler. Schedules are supported on root agents only; a `schedules/` directory under `subagents/` fails the build, because the scheduler cannot resolve a subagent as a run target. `defineSchedule` is exported from both `@mastra/core/agent` and `@mastra/core/schedules`, so authoring a file-based agent needs a single import path. Build and dev support the same convention: schedules are discovered at build time, Markdown schedules fail the build with a message naming the file when the cron is missing, the body is empty, the frontmatter has an unknown field, or the YAML is unparseable. That last case has its own message because a leading `*` is a YAML alias, so `cron: */5 * * * *` needs quoting. The dev server rebuilds when a Markdown schedule changes. - Added a `command_exit` session event to the agent controller. Subscribers now receive the exit code and success flag of each foreground `execute_command` tool call, alongside the existing `shell_output` stream: ([#21211](https://github.com/mastra-ai/mastra/pull/21211)) ```typescript session.subscribe(event => { if (event.type === 'command_exit') { console.log(event.toolCallId, event.exitCode, event.success); } }); ``` Previously the exit outcome was only visible inside the tool result text, so observers could stream a command's output but never tell whether it succeeded. - Made the workspace optional when creating an agent controller session. Previously `createSession()` threw `A session requires a valid workspace instance` unless a workspace was configured, which blocked chat-style sessions that only need threads, state, and agent runs. ([#21084](https://github.com/mastra-ai/mastra/pull/21084)) ```ts // Now works — no workspace configured anywhere const controller = new AgentController({ id: 'chat', storage, modes }); const session = await controller.createSession({ resourceId: 'user-1' }); session.getWorkspace(); // undefined ``` `Session.getWorkspace()` now returns `Workspace | undefined`, so check the result before using it. Passing a value that is not a `Workspace` instance is still rejected. Sessions that do configure a workspace are unchanged, including workspace initialization and the `workspace_ready` / `workspace_error` events. Closes #20594 - Renamed the beta stored workflows feature to dynamic workflows. `mastra.addStoredWorkflow()` is now `mastra.addDynamicWorkflow()`, `addStoredWorkflows()` is now `addDynamicWorkflows()`, the `StoredWorkflow*` types are now `DynamicWorkflow*`, and `workflow.origin` reports `'dynamic'` instead of `'stored'`. ([#20938](https://github.com/mastra-ai/mastra/pull/20938)) - Added a reusable Workflow Builder authoring API for discovering registered primitives, validating complete workflow definitions, and creating strict-provider-safe builder agents. Built-in workspace tools now expose explicit output schemas so workflow authors can infer tool compatibility. ([#21210](https://github.com/mastra-ai/mastra/pull/21210)) ```ts import { createWorkflowBuilderAgent } from '@mastra/core/workflows/builder'; const workflowBuilder = createWorkflowBuilderAgent({ id: 'workflow-builder', name: 'Workflow Builder', tools: discoveryAndSaveTools, model, }); ``` - Added a `requestContextKeys` option to scorer runs that controls which request-context values are recorded on the eval's span input for repeatability. ([#20808](https://github.com/mastra-ai/mastra/pull/20808)) Previously the entire request context was recorded on every scorer-run span. Because request context is an arbitrary, app-controlled bag, that could persist secrets or PII stored under any key into exported traces and datasets. Scorer runs now record **nothing** from the request context by default — you opt in per key. **What changed** - Default (no `requestContextKeys`): nothing from the request context is recorded. - Specific keys: only those keys are recorded, with dot notation for nested values. - `['*']`: the whole context is recorded (the framework-managed auth token stays redacted). This is separate from the observability config's `requestContextKeys`, which controls live span metadata. Recording a run so it can be reproduced later and surfacing keys on every live span are different concerns, so they are controlled independently. **Example** ```ts await scorer.run({ input, output, requestContext: { userId: 'u_123', tenant: { id: 't_1', apiKey: 'secret' } }, // Persist only what you need to reproduce the run — `apiKey` is never stored. requestContextKeys: ['userId', 'tenant.id'], }); ``` - Added a delegated request context to `onDelegationStart`. Each subagent run receives a context map derived from its parent, so hooks can add values before dynamic agent configuration resolves without changing the parent context. ([#20853](https://github.com/mastra-ai/mastra/pull/20853)) ```typescript await supervisor.stream('Research AI trends', { delegation: { onDelegationStart: context => { context.requestContext.set('specialty', context.primitiveId); }, }, }); ``` Subagent request contexts inherit caller entries but exclude parent memory, thread, and resource identity. Setting or deleting entries during a subagent run no longer changes the parent context map or another concurrent delegation's map. - Added an opt-in `toolCallConcurrency` strategy that parallelizes safe tool calls when an approval or suspending tool is registered but not actually called in a step. ([#21106](https://github.com/mastra-ai/mastra/pull/21106)) Previously, registering any tool that requires approval or can suspend forced every tool-call batch to run one at a time, even when the model never called that tool. You can now opt in to resolving concurrency from the tools the model actually called: **Before** ```ts // Any registered approval/suspend tool forced sequential execution const stream = await agent.stream('...', { toolCallConcurrency: 8 }); ``` **After** ```ts // A pure-safe batch runs in parallel; a batch that calls an approval/suspend tool still serializes const stream = await agent.stream('...', { toolCallConcurrency: { limit: 8, strategy: 'called' }, }); ``` The default strategy ('available') keeps the existing conservative behavior. This works across both the standard loop and the durable engine. Closes #20100. - Added tracing to dynamic agent skills resolvers. The resolver now runs inside a `resolve-skills` span, so skills fetched from an external service show up in the agent's trace instead of disappearing, and it receives `tracingContext` for creating child spans of its own — the same pattern tools already use. ([#20949](https://github.com/mastra-ai/mastra/pull/20949)) ```typescript const agent = new Agent({ skills: async ({ requestContext, tracingContext }) => { const span = tracingContext?.currentSpan?.createChildSpan({ type: 'generic', name: 'entitlements-lookup', }); const skills = await fetchSkillsFor(requestContext.get('userId')); span?.end(); return skills; }, }); ``` On metadata reads such as `agent.listSkills()` no agent is running, so `tracingContext.currentSpan` is `undefined` — guard span usage with `?.` as shown above. - Fixed agent streams closing silently when a provider ends the stream with `finishReason: 'error'` but sends no error payload (for example, Google models reporting `MALFORMED_FUNCTION_CALL`). Previously the stream closed with no error part and `onError` never fired, so a failed run looked identical to a turn that produced no text. The stream now emits an `error` chunk and calls `onError`, and error processors can intercept and retry the failure. ([#20302](https://github.com/mastra-ai/mastra/pull/20302)) Added `stepResult.rawReason` to `step-finish` and `finish` chunks. It preserves the provider's own finish reason instead of collapsing it to `'error'`, so you can tell distinct provider failures apart: ```ts for await (const chunk of stream.fullStream) { if (chunk.type === 'step-finish') { chunk.payload.stepResult.reason; // 'error' chunk.payload.stepResult.rawReason; // 'MALFORMED_FUNCTION_CALL' } } ``` These runs previously resolved as if they had succeeded with empty output. They now fail the same way a provider-reported error already did: `agent.generate()` rejects, and so do awaited stream promises such as `stream.text`. Iterating `fullStream` still completes normally, with an `error` chunk included. - Add `includeResolvedTools` to `ToolSearchProcessor`, making per-request tools (MCP tools that need the caller's credentials, or anything returned by a dynamic `tools` function) searchable and withholding them from the prompt until the agent loads them. Previously only tools listed at construction could be searched, so dynamically resolved tools were always sent in full. ([#21016](https://github.com/mastra-ai/mastra/pull/21016)) ```ts const toolSearch = new ToolSearchProcessor({ tools: staticTools, includeResolvedTools: true, }); const agent = new Agent({ id: 'mcp-agent', name: 'mcp-agent', instructions: 'Search for a tool when you need a capability you do not have.', model: 'openai/gpt-5.6-sol', // Resolved per request, then searchable alongside staticTools tools: async ({ requestContext }) => mcpClient.getTools(requestContext.get('userToken')), inputProcessors: [toolSearch], }); ``` - Added an explicit A2A Protocol v1.0 SDK export while preserving the existing v0.3 export. ([#20811](https://github.com/mastra-ai/mastra/pull/20811)) ```typescript import { ListTasksRequest } from '@mastra/core/a2a/v1'; const request = ListTasksRequest.fromJSON({ pageSize: 20 }); ``` - Set `tools.writeLockTimeoutMs` when a remote or cold-starting filesystem needs more than 30 seconds to accept a write. The write tool waits for the configured timeout before returning a `write-lock timeout` error. ([#20809](https://github.com/mastra-ai/mastra/pull/20809)) ```typescript const workspace = new Workspace({ filesystem: mySandboxFilesystem, tools: { // allow a cold-starting sandbox time to accept its first write writeLockTimeoutMs: 210_000, }, }); ``` The default is unchanged at 30 000 ms. - Channel agent replies now post as markdown by default, so Slack renders bold text, links, and tables natively and other chat platforms convert the reply to their own format. Previously the final reply was posted as literal plain text, which made standard markdown show up as raw `**bold**` and `[title](url)` characters in Slack while the same reply rendered correctly in Studio. ([#20971](https://github.com/mastra-ai/mastra/pull/20971)) This is a behavior change for every channel agent. If your agent was prompted to emit a platform dialect such as Slack mrkdwn to work around the old behavior, either remove those prompt instructions (recommended) or set the new `textFormat: 'plain'` option on the channel adapter config to keep posting literal plain text: ```typescript channels: { adapters: { slack: { adapter: createSlackAdapter(), textFormat: 'plain', }, }, }, ``` `textFormat` applies to final reply text only. Tool cards, error messages, tripwire notices, and native streaming (which was already markdown) are unchanged. Postable channel messages now also accept a `{ markdown: string }` object alongside strings and card elements. - Lightweight trace lists now work on every storage backend. Their rows carry an `inputPreview` so a list can render its preview column without transferring the whole prompt, plus a computed `status` and the span `metadata` so Studio's configurable trace columns work unchanged on the lightweight list. ([#20677](https://github.com/mastra-ai/mastra/pull/20677)) ```ts const { spans } = await storage.listTracesLight({ pagination: { page: 0, perPage: 25 } }); spans[0].inputPreview; // short preview text — no input/output/attributes blobs ``` `ObservabilityStorage.listTracesLight()` previously threw on any backend that did not implement it — which was every backend except ClickHouse, DuckDB and the in-memory store. It now defaults to `listTraces()` with each row projected down, so all backends serve the same response shape. Backends that can push the projection into the query should still override it; that is what keeps the blob columns off the read path. `lightSpanRecordSchema` gains optional `inputPreview`, `status` and `metadata` fields, and `buildInputPreview()` / `toLightSpanRecord()` are exported for stores that derive the preview at read time. `listTracesLightResponseSchema` also gained the `delta` and `deltaCursor` fields already present on `listTracesResponseSchema`, so lightweight lists can be live-tailed. Note that `pagination` on `ListTracesLightResponse` is now optional, because delta-mode responses return a cursor instead of a page. Code that reads it directly needs a guard: ```ts // Before const total = response.pagination.total; // After const total = response.pagination?.total ?? 0; ``` - Added `WorkspaceSandbox.snapshot()` for persisting sandbox state when supported. ([#21221](https://github.com/mastra-ai/mastra/pull/21221)) ```ts await sandbox.snapshot(); ``` - Added an optional `reason` when declining a tool call, so the model and your UI can see _why_ a tool call was rejected instead of always showing "Tool call was not approved by the user". ([#21085](https://github.com/mastra-ai/mastra/pull/21085)) ```ts // Before: the model only learned the call was declined await agent.declineToolCall({ runId, toolCallId }); // After: give the model context it can act on await agent.declineToolCall({ runId, toolCallId, reason: 'Reading other users personal data is not allowed, ask the user for their own email instead', }); ``` The reason is retained with the tool call, so it is still there when the conversation is recalled later. It is supported by `declineToolCall`, `declineToolCallGenerate` and `declineNetworkToolCall`, on both regular and durable agents. Omitting `reason` keeps the previous default message. Closes #20495 - Added an `onDetection` callback to `PIIDetector` and `PromptInjectionDetector` so you can observe detection results and build your own metrics. ([#21123](https://github.com/mastra-ai/mastra/pull/21123)) The callback receives the raw detection result, the analyzed input, whether it crossed the threshold, and which strategy was applied. ```ts new PIIDetector({ model: 'openai/gpt-4o-mini', onDetection: ({ detectionResult, input, flagged, strategyApplied }) => { metrics.increment('pii.detections', { flagged, strategyApplied }); }, }); ``` Errors thrown from the callback are logged and never interrupt processing. Closes #13336 ### Patch Changes - dependencies updates: ([#19783](https://github.com/mastra-ai/mastra/pull/19783)) - Updated dependency [`posthog-node@^5.46.1` ↗︎](https://www.npmjs.com/package/posthog-node/v/5.46.1) (from `^5.37.0`, in `dependencies`) - Stop AgentController sessions from re-sending the whole conversation state on every streamed chunk. ([#21095](https://github.com/mastra-ai/mastra/pull/21095)) A session emits one `message_update` per streamed delta, and the session bus fanned out a full `display_state_changed` snapshot alongside each one. Every snapshot carried the growing message plus each finished tool's arguments and result, so a long turn with a large tool result re-sent that result thousands of times. A turn with 500 deltas and a 100 KB tool result put roughly 50 MB of snapshots on the wire; subscribers holding the event log could exhaust memory. Snapshots for high-frequency events are now coalesced, so a burst of deltas produces one snapshot per frame instead of one per delta. A snapshot always describes the complete current state, so no information is lost: any dropped intermediate snapshot is immediately superseded, and a pending snapshot is flushed before the next non-coalesced event so ordering and final state are unchanged. The engine also skips its per-delta message clone when a session has no subscribers. Event types, payload shapes, and delivery order are unchanged. - Fixed `AgentController.resolveWorkspace` handing one session's workspace to every session created after it. On a controller configured with a dynamic workspace factory, the first call cached its result over the factory itself, so later sessions skipped resolution and ran against the first session's workspace instead of their own. ([#21144](https://github.com/mastra-ai/mastra/pull/21144)) `resolveWorkspace` now returns the workspace a session already resolved at creation, and caches nothing on the controller. Two smaller fixes come with it: `isWorkspaceReady()` no longer flips to `false` for factory configs, and slash commands read the same workspace instance the session's runs use. - Update provider registry and model documentation with latest models and providers ([`45a9147`](https://github.com/mastra-ai/mastra/commit/45a914741f578754d79d8b7de7b4e4f304d8e14a)) - Fixed thread title generation being dropped on serverless runtimes by accepting an optional `serverless.waitUntil` on `generate()`/`stream()` so title persistence stays alive after the response without blocking the run. (#20682) ([#20996](https://github.com/mastra-ai/mastra/pull/20996)) ```ts import { waitUntil } from '@vercel/functions'; await agent.generate('Name this conversation', { serverless: { waitUntil }, memory: { thread: 'thread-1', resource: 'user-1', options: { generateTitle: true }, }, }); ``` - Made the workspace LSP manager's per-file lock hand out access in arrival order. Diagnostics requests for the same file were already serialized; a request that arrived at the moment the previous one finished could jump ahead of requests that had been waiting longer. Requests for different files still run in parallel. ([#21101](https://github.com/mastra-ai/mastra/pull/21101)) - Fixed deferred notifications accumulating dead workflow records forever. The internal dispatcher runs on a schedule (every minute by default) and left a completed snapshot row behind on every run, so `mastra_workflow_snapshot` grew unboundedly — tens of thousands of rows that were never read again. These runs no longer persist snapshots. Fixes #20254 ([#20970](https://github.com/mastra-ai/mastra/pull/20970)) - Add a workflow `onStart` lifecycle hook. It is awaited before a run executes, receives the run context, and fires only on initial start — not on resume, restart, or time travel. Unlike `onFinish`/`onError`, errors thrown in `onStart` reject the `start()`/`stream()` call so it can act as a pre-flight gate, for example a quota check. ([#21063](https://github.com/mastra-ai/mastra/pull/21063)) - Add `bundler.minify` to minify `mastra build` output ([#21032](https://github.com/mastra-ai/mastra/pull/21032)) `mastra build` always emitted unminified code, which is larger than necessary when packaging for production — a container image or an on-prem deployment. Set `bundler.minify: true` to minify the emitted bundle. Minification runs over whole chunks, so comments and whitespace are dropped and local identifiers are shortened while exported names are preserved. ```typescript export const mastra = new Mastra({ bundler: { minify: true, }, }); ``` Defaults to `false`, so existing builds are unchanged. `mastra dev` is never minified. - Fixed dynamic workflows so object-form mapping configs keep working. Passing `mapConfig` as an object to `addDynamicWorkflow` previously failed at run time with `"[object Object]" is not valid JSON`; it now stays intact when the workflow is registered, saved, and loaded. ([#21287](https://github.com/mastra-ai/mastra/pull/21287)) - Allow the browser `viewport` to be set to `'window'` so the page matches the real browser window instead of a fixed size. Adds `resolveViewportSize` and `resolveLaunchViewport` helpers plus a `DEFAULT_BROWSER_VIEWPORT` constant for providers to resolve the setting consistently. ([#21010](https://github.com/mastra-ai/mastra/pull/21010)) - Prevent `streamLegacy()` cleanup from hanging when an observer stream has queued events that have not been consumed. ([#19921](https://github.com/mastra-ai/mastra/pull/19921)) - Fixed `Mastra.shutdown()` leaving database connections open when storage is a `MastraCompositeStore`. The composite had no `close()` of its own, so shutdown silently skipped storage cleanup and any composed adapter (Redis, LibSQL, Postgres, ...) kept its client connected — leaving processes that wait for a graceful drain, such as test runners and Kubernetes pods handling SIGTERM, hanging until they were killed. ([#20629](https://github.com/mastra-ai/mastra/pull/20629)) A composite now closes everything it was built from: the `default` and `editor` stores, plus any domain that owns its own connection. Each store is closed once even when it backs several domains, and a store that fails to close is logged and skipped so the remaining handles are still released. See [#20621](https://github.com/mastra-ai/mastra/issues/20621). - Fixed Azure OpenAI routing to reject empty deployment names. ([#21105](https://github.com/mastra-ai/mastra/pull/21105)) - Fixed `Mastra.startWorkers()` so it no longer reads the schedules store on boot when the scheduler cannot start. Boot now skips that read if you set `scheduler: { enabled: false }` or `workers: false`. Storage adapters that need request or tenant context no longer warn on every boot. Automatic detection of persisted agent schedules and deferred notifications is unchanged when you do not opt out. ([#20982](https://github.com/mastra-ai/mastra/pull/20982)) - Fixed the response cache serving one image's answer for a different image. `ResponseCache` derives its key from the resolved prompt, but URL-valued image and file parts were serialized as `{}`, so two requests that differed only in which image they pointed at collided on the same cache entry. URLs now contribute their full href, and inline binary data contributes a digest of its bytes instead of being expanded one property per byte (which turned a 1 MiB image into ~11 MiB of intermediate JSON, hashed synchronously before every model call). ([#20656](https://github.com/mastra-ai/mastra/pull/20656)) - Fixed durable agent completion on remote workers so processed messages persist, thread titles generate, and prior turns can be recalled. ([#20926](https://github.com/mastra-ai/mastra/pull/20926)) - Fixed sub-agent delegation failing when an LLM sends `maxSteps` as a numeric string (e.g. `"10"` instead of `10`). Delegation now accepts valid numeric strings and continues to reject invalid values such as non-integers or numbers below 3. ([#20793](https://github.com/mastra-ai/mastra/pull/20793)) - Fixed `include` in `listMessages` and `listMessagesByResourceId` so it can no longer return a message that belongs to a different resource. When you pass a `resourceId`, the target message and its surrounding context messages now stay inside that resource. Includes that cross threads inside the same resource keep working, so semantic recall with `scope: 'resource'` is unchanged. ([#20984](https://github.com/mastra-ai/mastra/pull/20984)) **Behaviour change in the in-memory store** The in-memory store read the context window from the thread you queried. It now reads the window from the thread that owns the target message, which is what the SQL stores already did. This only changes the result when an `include` entry names a message from another thread. The in-memory store also ignored `include` in `listMessagesByResourceId`. It now returns the included messages, like `@mastra/libsql` and `@mastra/pg` do. Fixes #20604. - Added a `firstMeaningfulExecAt` timestamp to source-control sessions, recording when the session's agent completed its first successful sandbox command. Together with `firstMessageAt` this measures time-to-first-meaningful-exec: how long a user waits between sending their first message and the agent actually doing work in a live sandbox. The value is written once per session and is available on all session read APIs; setup commands run by the platform itself (skill loading, repo checkout) do not count. ([#21211](https://github.com/mastra-ai/mastra/pull/21211)) - Fixed agent schedules targeting stored agents being permanently deleted after a server restart. Both deletion paths are covered: the scheduler tick loop no longer counts an unhydrated stored agent as a missing target (it confirms absence against the editor before reclaiming the schedule row), and the fire path resolves stored agents through the editor before self-cleaning. Schedules are never deleted when the editor lookup fails transiently — only a confirmed miss from both the registry and the editor reclaims the row. ([#19791](https://github.com/mastra-ai/mastra/pull/19791)) - Fix `deepEqual` returning `true` for values of mismatched shapes. A `Date` compared against a plain object (e.g. `deepEqual(new Date(), {})`) fell through to the generic object-key comparison — and since a `Date` has no own enumerable keys, it matched `{}`. Likewise an array compared against an object with matching index keys (e.g. `deepEqual([1, 2], { '0': 1, '1': 2 })`) returned `true`. Added guards so an array only equals an array and a `Date` only equals a `Date`; the checks apply recursively to nested values. ([#21146](https://github.com/mastra-ai/mastra/pull/21146)) - Improved A2A v0.3 remote task continuation. A2AAgent now resumes input and authentication requests using the original task ID and surfaces protocol errors returned by remote agents. ([#20708](https://github.com/mastra-ai/mastra/pull/20708)) ```typescript const resumedResult = await remoteAgent.resumeGenerate({ approved: true }, { runId }); console.log(resumedResult.text); ``` - Fixed DurableAgent terminal cleanup so it also clears `workflow.events.v2.`, preventing orphaned no-TTL counter keys on persistent caches (#20786). ([#20961](https://github.com/mastra-ai/mastra/pull/20961)) - Fixed durable agent streams crashing when consumed by `@mastra/ai-sdk` and other chunk converters. The `step-start` stream chunks now use the canonical shape, matching the regular engine and preventing destructuring errors when reading the chunk payload. ([#19575](https://github.com/mastra-ai/mastra/pull/19575)) - Fixed Workspace search indexing sending one embedding request per document. ([#21067](https://github.com/mastra-ai/mastra/pull/21067)) A batch-capable embedder (one branded with `batch: true`) was only used when the search engine ran in lazy mode, which `Workspace` never enables. Indexing a directory therefore cost one embedding round trip per file no matter what the embedder supported — indexing 500 files made 500 requests. `indexMany` now groups documents into batched embedder calls whenever the configured embedder is batch-capable, so those 500 files take 2 requests with `maxBatchSize: 256`. Vector writes are bounded as well: a single `upsert` now carries at most `min(maxBatchSize, 100)` vectors instead of the whole batch, since each document's metadata carries its full text and several vector stores reject oversized write requests. Lazy-mode rebuilds that previously issued one large `upsert` per flush now issue several bounded ones. An embedder declaring an unusable `maxBatchSize` (`0`, negative, or `NaN`) now falls back to the default batch size instead of hanging or silently indexing nothing. - Enqueue a `tool-output-denied` chunk when a `requireApproval` tool is declined so live AI SDK clients resolve the pending tool call instead of hanging. Persistence as `output-denied` already worked; only the stream path was missing. ([#20886](https://github.com/mastra-ai/mastra/pull/20886)) ```ts for await (const part of toAISdkStream(result.fullStream, { from: 'agent' })) { if (part.type === 'tool-output-denied') { // Clears the pending requireApproval tool call on the client console.log('denied', part.toolCallId); } } ``` - Fixed dataset experiment runs failing with a missing-thread memory error when the target agent has memory configured and the request context provides only a resource id (for example from auth middleware or Studio). Each dataset item now runs in its own fresh memory thread; explicitly supplied thread ids are still respected. Fixes #20663 ([#20844](https://github.com/mastra-ai/mastra/pull/20844)) - Fixed nested agent-as-tool approvals so users see the inner tool and arguments while resumes retain the parent delegation identity (#20934). ([#20948](https://github.com/mastra-ai/mastra/pull/20948)) - Fix `tryStreamWithJsonFallback` treating a valid falsy structured-output value as undefined. The first-attempt check used `!object`, so a schema resolving to a falsy-but-defined value (e.g. `z.boolean()` -> `false`, `z.number()` -> `0`) was wrongly rejected and triggered an unnecessary JSON-prompt fallback stream. It now checks `object === undefined`, matching the generate path and the stream fallback path. ([#20628](https://github.com/mastra-ai/mastra/pull/20628)) - Fixed an issue where observer cleanup failures no longer prevent legacy workflow streams from completing cleanly. ([#20652](https://github.com/mastra-ai/mastra/pull/20652)) - Fixed workflow `.map()` step arrays so they preserve branch results of `{}`, `0`, `false`, and empty strings instead of returning `null`. ([#20896](https://github.com/mastra-ai/mastra/pull/20896)) - Fixed a crash where updating a thread without a title (for example during observational memory buffering) could write a null title and violate the database's not-null constraint when running a newer @mastra/memory against an older storage package. Memory now checks whether the connected storage adapter supports partial thread updates and backfills the existing title for older adapters, so mixed-version deployments keep working. See #21041 for the original title-clobbering fix this makes backward compatible. ([#21257](https://github.com/mastra-ai/mastra/pull/21257)) - Fixed durable agents losing custom model-facing tool output between workflow steps. ([#20176](https://github.com/mastra-ai/mastra/pull/20176)) - Hardened `runEvals` threshold checks: non-finite scores (for example `NaN`) now fail `min`/`max` range thresholds instead of passing, and invalid threshold shapes passed from JavaScript (strings, `null`, arrays) are rejected with a clear `INVALID_SCORER_THRESHOLD` error instead of silently passing every score. ([#20145](https://github.com/mastra-ai/mastra/pull/20145)) - Fixed concurrent workflow tool approvals so each suspended call resumes the correct workflow run. ([#20347](https://github.com/mastra-ai/mastra/pull/20347)) - Storage adapters now declare support for partial thread updates, letting newer @mastra/memory preserve existing thread titles instead of overwriting them, while remaining safe against older versions. ([#21257](https://github.com/mastra-ai/mastra/pull/21257)) - Fixed experiment runs reporting a failed outcome instead of cancelled when cancellation interrupted an in-flight item. Cancelled experiment runs now consistently finish with a cancelled outcome, so standalone experiment workers exit with the cancellation exit code instead of a failure code. ([#20719](https://github.com/mastra-ai/mastra/pull/20719)) - Fixed an active goal being reported as cancelled when its objective was written while a run was already in flight. The objective a goal state projection sees is now read from storage whenever the run-start read found nothing, so a goal started or restarted mid-run is no longer projected as having no objective. ([#21255](https://github.com/mastra-ai/mastra/pull/21255)) - Fixed subscribed agent-controller runs so dynamic workspaces use the identity from the request that started the run. ([#20658](https://github.com/mastra-ai/mastra/pull/20658)) - Fixed sendStreamResume() to resume suspended agent runs from storage after a server restart or when requests reach another instance. ([#20602](https://github.com/mastra-ai/mastra/pull/20602)) - Fixed an agent reply loop on the iMessage channel. iPhone read receipts arrive as inbound messages with no text and no attachments, and each agent reply triggered another receipt. Channel messages with neither text nor attachments no longer start an agent run. Custom channel handlers still receive them. ([#21240](https://github.com/mastra-ai/mastra/pull/21240)) - Fixed `session.abort()` when a tool call is waiting on approval or parked in a suspension. ([#20972](https://github.com/mastra-ai/mastra/pull/20972)) Aborting from a `tool_approval_required` subscriber raised an error and ended the run with reason `error`. It now completes with reason `aborted`, and the gated call settles as `output-denied` instead of rendering as still in flight. Aborting while tool suspensions were parked (`ask_user`, `request_access`) dropped them silently, leaving prompts on screen whose answers could never be delivered. Each dropped suspension now emits `tool_suspension_cancelled`. Fixes #20592 - Multi-step model generations now retain provider metadata from every completed step. ([#19921](https://github.com/mastra-ai/mastra/pull/19921)) - Fixed a hang when two streams were started for the same workflow run at once. Resuming or time-traveling a suspended run twice concurrently (a double-clicked approval, a client retry, or two open tabs) left one of the two streams open forever, so the matching resume-stream or time-travel-stream HTTP request never ended. Each stream now closes itself independently. ([#21126](https://github.com/mastra-ai/mastra/pull/21126)) - Agent scorers now run on the Inngest durable engine. ([#21038](https://github.com/mastra-ai/mastra/pull/21038)) An agent configured with `scorers` never had them executed when running via `createInngestAgent()` — no scorer ran, no spans, no persisted scores, and no error. Core's durable workflow gained an `execute-scorers` step that the Inngest workflow builder, a copy of it, never picked up. Scorer execution now lives in the durable workflow's shared module and is used by both engines, so scorers behave identically on either one. - Fixed output processors not being able to clear the final agent text. An output processor that redacts all assistant text to an empty string now correctly results in an empty `result.text` from `generate()` and `stream()`, instead of falling back to the original unprocessed model output. Fixes #19240 ([#20998](https://github.com/mastra-ai/mastra/pull/20998)) - Fix durable agents dropping `requestContext` when a step is rehydrated on another process (for example an Inngest worker delegating to a subagent). Tool, memory, and workspace resolution now fall back to the run-level request context when the step input carries no request-context snapshot, so request-scoped configuration reaches subagents instead of resolving with an empty context. ([#21015](https://github.com/mastra-ai/mastra/pull/21015)) - Stamp the run's traceId into persisted assistant message metadata so a stored message can be correlated back to its trace. ([#20928](https://github.com/mastra-ai/mastra/pull/20928)) Previously a caller holding only a `messageId` had no supported way to find the trace that produced it: message rows carry no `traceId` column and span records carry no `messageId`. The traceId now rides along in the metadata that already carried `modelId` and `provider`, on both the regular and the durable agent path. ```typescript const { messages } = await memory.recall({ threadId, perPage: false }); const traceId = messages.find(m => m.id === messageId)?.content.metadata?.traceId; ``` This is forward-looking — messages persisted before this change have no traceId. - Fixed deferred notification deliveries failing with "No model selected" when the woken thread had no request context. The notification dispatch workflow re-sends deferred and summarized notifications long after the originating send, so any stream options attached to the original signal are gone; waking an idle thread then had no way to resolve a model. The notification delivery policy now drives the fix: `NotificationDeliveryDecision` accepts `streamOptions`, and the dispatcher re-runs the agent's delivery policy at delivery time (via the new `agent.resolveNotificationDeliveryDecision()`) to attach freshly resolved stream options to both individual and summary deliveries. Only `streamOptions` is honored at dispatch time; the record's persisted schedule still governs when and how it is delivered. Receipt-time sends also honor the policy's `streamOptions` now: an immediate deliver or summarize-now wake attaches them too, with caller-supplied stream options taking precedence. Mastra Code wires its session-based stream options resolver through the Code Agent's `deliveryPolicy.decide`, layered on the default decision logic. This fix applies to threads whose session is live in the current process; deliveries with no resolvable session fall back to a bare wake, and the "No model selected" error now distinguishes the case where a run started without any controller session context. This re-lands the capability removed by the #18637 revert in the policy-driven shape that revert called for, and the `@mastra/github-signals` bump only widens its `getNotificationStreamOptions` callback return type to allow `undefined`. ([#21113](https://github.com/mastra-ai/mastra/pull/21113)) - Fix DurableAgent inference crashes and error reporting (#21138): ([#21224](https://github.com/mastra-ai/mastra/pull/21224)) - DurableAgent inference no longer crashes with "Cannot read properties of undefined (reading 'type')" on malformed message content. - Durable LLM errors now keep their original message, name, stack, and cause when reported to callers and `onError`. - Let `onDelegationComplete` correct a delegation result within the current run ([#21042](https://github.com/mastra-ai/mastra/pull/21042)) A subagent that stops on a tool-calls step returns empty text, which the parent model reads as a successful but empty delegation and narrates around ("I'll report back once it returns"). The `feedback` returned from `onDelegationComplete` is persisted to the parent's memory, so it only reaches the model on the next turn — after the parent has already answered. The hook can now return `resultText`, which replaces the tool result text the parent model sees for that delegation in the run that is still executing. - Added an `onSessionStart` hook to `AgentControllerChannels` config, called once per session after it is bound to its mapped chat thread and before the first message dispatches. Messages arriving while the hook is still running wait for it instead of dispatching on an unconfigured session. ([#20832](https://github.com/mastra-ai/mastra/pull/20832)) ```typescript const controller = new AgentController({ id: 'support-controller', agent, channels: { adapters: { slack: createSlackAdapter() }, onSessionStart: async ({ session, thread }) => { await session.model.switch({ modelId: 'anthropic/claude-sonnet-4-6' }); }, }, }); ``` A host can already name a channel session through `resolveResourceId`, but it never receives the `Session` object, which is created inside the channel machinery. Without a seam there, a host could only configure sessions it created itself, and channel-created sessions silently ran on the built-in default models. Hook errors are logged and swallowed so a session that cannot be configured still answers the message. - Make `abort()` stop durable agent runs that are executing in another process ([#21009](https://github.com/mastra-ai/mastra/pull/21009)) A durable agent's steps often run somewhere other than the process that started them — another replica behind a load balancer, or an Inngest step worker. `abort()` only flipped an in-memory `AbortController`, which those processes never see, so aborting a durable or Inngest agent run silently did nothing in exactly the deployments durable agents exist for. Abort intent now travels over pubsub. The executing process picks the request up and flips its own controller, so the run unwinds the same way an in-process abort does and still emits its terminal stream event — consumers waiting on the stream are released instead of hanging. `abort()` now returns a promise so callers can await dispatch; ignoring it preserves the previous fire-and-forget behavior. - Fixed suspended runs that could fail to save after repeated tool calls. ([#21002](https://github.com/mastra-ai/mastra/pull/21002)) A run's snapshot no longer grows with every step it took before suspending, which keeps agents that make many tool calls before their first approval prompt under MongoDB's 16 MB per-document limit — past it, the snapshot was never written and the resume reported the run as missing. Runs suspended by earlier versions still load. - Fixed background tool calls so the model sees the completed result instead of the dispatch placeholder. ([#21001](https://github.com/mastra-ai/mastra/pull/21001)) An agent that dispatched a tool to the background kept reading "Background task started..." on every later turn, so it would re-run the tool or answer without the result. Unrelated provider metadata on the tool call is now preserved when the completed result arrives. - Fixed AgentController losing the second auto-approved tool result when sequential tool calls resume from a stale suspended snapshot (#19814) ([#19940](https://github.com/mastra-ai/mastra/pull/19940)) - Fixed `convertFullStreamChunkToUIMessageStream` from `@mastra/core/stream` dropping the finish reason. The terminal `finish` chunk now carries `finishReason`, so a UI message stream built on this export can tell `stop`, `length`, `content-filter`, `tool-calls` and `other` apart. Fixes #20562. ([#20983](https://github.com/mastra-ai/mastra/pull/20983)) - Fixed assistant message history so provider-executed tool failures are preserved instead of remaining as pending calls. Matching tool invocations now use `state: "output-error"`, and the normalized provider message is stored in `errorText` with a fallback when no usable message is available. Fixes https://github.com/mastra-ai/mastra/issues/20715 ([#20716](https://github.com/mastra-ai/mastra/pull/20716)) - Resolve dynamic models once when preparing assigned tools. ([#21281](https://github.com/mastra-ai/mastra/pull/21281)) - Fixed input processors receiving an undefined agent in their context when a durable agent run was resumed by a signal or schedule. The running agent is now passed through the processor workflow, so processors that rely on it (such as working memory and semantic recall) work correctly on wake. ([#21102](https://github.com/mastra-ai/mastra/pull/21102)) - Fixed parallel sub-agent approvals and suspensions so every persisted target remains resumable after refresh. ([#20700](https://github.com/mastra-ai/mastra/pull/20700)) - Fixed failed workspace commands to label standard output and standard error. ([#20774](https://github.com/mastra-ai/mastra/pull/20774)) - Fixed `result.object` being stale after an output processor rejected a structured-output attempt with `abort(reason, { retry: true })`. The retried attempt object is now returned, matching `result.text`, instead of the rejected attempt object. Fixes #20570 ([#20999](https://github.com/mastra-ai/mastra/pull/20999)) - Fixed RegexFilterProcessor redact strategy to redact matches split across stream chunks, and added a `streamCarryoverSize` option for custom rules whose matches can be longer than the default 128-char window. Fixes #21049 ([#21050](https://github.com/mastra-ai/mastra/pull/21050)) - `runEvals` now accepts `gates` together with a categorized scorer config (`AgentScorerConfig` / `WorkflowScorerConfig`) without a TypeScript error. Previously a call like `runEvals({ target, data, gates, scorers: { trajectory: [...] } })` failed to compile with TS2769 because the categorized-config overloads didn't declare `gates`, even though the runtime already ran gates independently of the scorer shape. The optional `gates` property was added to both the agent and workflow categorized-config overloads. ([#21143](https://github.com/mastra-ai/mastra/pull/21143)) - Fixed model configuration validation to safely reject null values. ([#21192](https://github.com/mastra-ai/mastra/pull/21192)) - Fixed AgentController session preferences (thinking level, notifications) reverting to defaults after a server restart. These preferences now survive restarts and follow the conversation: reopening a thread restores the values that were active in it, including on self-hosted Factory deployments. ([#20901](https://github.com/mastra-ai/mastra/pull/20901)) - Fixed live scorer execution across multiple Mastra instances so only the instance that emitted a scorer run handles it. ([#20840](https://github.com/mastra-ai/mastra/pull/20840)) - Fixed two thread-stream broadcast issues when subscribing to agent threads: ([#21224](https://github.com/mastra-ai/mastra/pull/21224)) - **Broadcast payload sanitization (#21219):** broadcast copies of `step-start`, `step-finish`, and `finish` parts no longer embed the raw model request body or duplicated step history, preventing multi-gigabyte pubsub streams and out-of-memory crashes when threads contain large media. - **Phantom replay prevention (#21223):** runs that failed before persisting any messages no longer replay as phantom partial messages to new thread subscribers on retained backends like Redis Streams. - Stop the browser-safe `@mastra/core/a2a/client` entry from pulling in the Node `module` builtin, which broke `@mastra/client-js` in browser bundles (`Module not found: Can't resolve 'module'` in Next.js). ([#21053](https://github.com/mastra-ai/mastra/pull/21053)) - Fixed a bug where a server-side tool that was still running when a request was aborted (for example, when a turn parked waiting on a client-side tool and the response closed, or the user hit Stop) was persisted as a _completed_ tool call whose result was the abort message. On resume, the cancelled — and possibly half-finished — operation read as a successful tool call. ([#18034](https://github.com/mastra-ai/mastra/pull/18034)) Tool executions interrupted by an aborted request are now left as incomplete calls instead of being recorded as fabricated successful results, so they are no longer mistaken for completed work when a conversation resumes. Genuine tool errors on a live request still surface to the model as before. - Fixed stopping an agent-controller run so it always takes effect. When the model stream hung and never reacted to the abort signal, the session stayed stuck in a running state until the server was restarted; the run now finalizes as aborted a few seconds after the stop request. ([#20835](https://github.com/mastra-ai/mastra/pull/20835)) - Added a dedicated `SKILL_RESOLUTION` span type for dynamic agent skills resolvers, replacing the `GENERIC` type the `resolve-skills` span used before. The span now reports `agentId` and `skillCount` as typed span attributes. If you filter or query traces by span type, the resolver span's type value changed from `generic` to `skill_resolution`. ([#21232](https://github.com/mastra-ai/mastra/pull/21232)) - Pass `formatLocation` to `SkillsProcessor` when skill files are not at `${skill.path}/SKILL.md` from the model's point of view, such as when the agent's filesystem tools run against a sandbox that mounts them elsewhere. Key the override on `skill.path` so skills that share a name still render distinct locations. ([#20893](https://github.com/mastra-ai/mastra/pull/20893)) ```ts new SkillsProcessor({ workspace, formatLocation: skill => `/mnt/skills${skill.path}/SKILL.md`, }); ``` Remapped locations remain valid skill identifiers: the processor registers each rendered location as an alias with the skills registry, so the `skill` and `skill_read` tools resolve it back to the underlying skill. The skill-tool instruction now also tells the model that `location` may not exist on its filesystem, so it reads skill files with `skill_read` instead of filesystem tools. If a custom `WorkspaceSkills` implementation does not support alias registration, the instruction falls back to directing the model to refer to skills by name. - Fixed dynamic `skills` resolvers running four times per agent request. Skills are needed in several places during one execution, and each of them called your resolver again — so a resolver that fetches over the network made four calls per request instead of one. ([#20921](https://github.com/mastra-ai/mastra/pull/20921)) ```typescript const agent = new Agent({ name: 'support', instructions: 'Help the customer.', model: 'openai/gpt-5-mini', skills: async ({ requestContext }) => { // Called four times per generate()/stream() before this change, once now. const res = await fetch(`https://internal/skills?user=${requestContext.get('userId')}`); return (await res.json()).skills; }, }); ``` The resolution is now shared across a request, keyed on its `RequestContext`. A new request resolves again, and a failed resolution is not cached so a retry still reaches your resolver. If you reuse a single `RequestContext` across several executions, those executions now share one resolution. - Fixed Mastra shutdown so background task resources are released before storage closes without blocking indefinitely. Retryable running tasks remain recoverable after a process restart, while local executors are aborted and durable dispatches are left for surviving workers during shutdown. ([#20194](https://github.com/mastra-ai/mastra/pull/20194)) - Fix dedicated scheduler deployments never starting the scheduler. When `MASTRA_WORKERS=scheduler` (or any worker filter naming the `scheduler` role) is set, `startWorkers()` now always injects the SchedulerWorker instead of relying on boot-time heuristics (declarative workflow schedules or persisted agent-schedule rows) that a standalone scheduler process cannot see — it exists to fire schedule rows created by other processes. `workers: false` and `scheduler: { enabled: false }` still take precedence. ([#21224](https://github.com/mastra-ai/mastra/pull/21224)) - Messages sent while an agent run is active are no longer silently lost when the run fails. ([#21128](https://github.com/mastra-ai/mastra/pull/21128)) **Failed runs now finish cleanly.** A run that dies on a provider error previously never settled its completion watcher, so queued messages were never delivered, the thread stayed locked, and no error surfaced. **Queued messages survive delivery failures.** If starting the follow-up run for a queued message fails, the message is put back at the head of the queue and the failure renders as an error. The message delivers on the next turn. - Fixed tool calls that fail during execution staying stuck as running in agent-controller session streams. A tool error now updates the streamed message part to a terminal errored result and emits the corresponding message update, exactly like a successful tool result. The `isError` flag on persisted tool invocations is now part of the public type instead of an undeclared runtime field. ([#20805](https://github.com/mastra-ai/mastra/pull/20805)) - Fixed `@mastra/core` crashing when a project loads Mastra as CommonJS. Several ESM-only dependencies were read as a module object instead of a function, so the calls threw. ([#20980](https://github.com/mastra-ai/mastra/pull/20980)) **What failed before** - `new MCPServer(...)` threw `slugify is not a function`. - `mastra.schedules.create()` threw the same error. - Workspace search, workspace file indexing, and batch trace scoring threw `p_map.default is not a function`. All of these paths now run under CommonJS and ESM. See [#20354](https://github.com/mastra-ai/mastra/issues/20354). - Fixed observational memory stalling agent loops after a tool fails. ([#20788](https://github.com/mastra-ai/mastra/pull/20788)) - Fixed streamed assistant messages carrying a different id than their persisted copy. Chat UIs that reconcile a live stream with refetched history could show the same assistant reply twice (for example after returning to a hidden tab); with a shared id, deduplication by message id now works for every turn, including text-only replies without tool calls. ([#21185](https://github.com/mastra-ai/mastra/pull/21185)) - Acknowledge pubsub deliveries so persistent backends stop accumulating pending entries. Every fan-out subscribe creates a private consumer group, and Redis keeps each delivered entry pending until it is acked, so subscribers that never acknowledged grew an unbounded pending list for as long as they stayed attached. ([#21081](https://github.com/mastra-ai/mastra/pull/21081)) The following subscribers now ack every event they inspect (including ones they filter out) and nack when processing throws: - the agent thread-stream subscriber and the cross-agent remote-run waiter, which also waits for the terminal event's ack before unsubscribing - workflow run watchers, including the shared `nested-watch` topic - durable agent abort-request listeners - user-defined topic listeners registered through `events` or `addTopicListener` `events` listeners are typed as receiving only the event; acknowledgement is handled for them. - Fix schema-based working memory losing stored data on partial updates. When a model updated one section of working memory, unrelated sections could be wiped out. Tools can now set `strict: false` to opt out of strict structured-output schema rewriting, which previously forced every field to be required and left models no way to signal "leave this field alone". ([#20992](https://github.com/mastra-ai/mastra/pull/20992)) - Fixed unbounded memory growth in the shared event cache when a caching pub/sub is backed by a persistent store such as Redis. Workflow watch events and other instance-local events were still being copied into the shared replay cache even though no other instance could read them. Because those events carry cumulative step results, a single workflow run could add tens of megabytes of cache entries that nothing ever consumed, and each topic left behind an index counter that was never cleaned up. Instance-local events are now delivered live only and skip the cache entirely; replay for agent streams is unchanged. Fixes https://github.com/mastra-ai/mastra/issues/20646 ([#20685](https://github.com/mastra-ai/mastra/pull/20685)) - Fixed generated thread titles being clobbered during a turn ([#21041](https://github.com/mastra-ai/mastra/pull/21041)) `updateThread` required both `title` and `metadata`, so callers that only needed to change metadata (message persistence, working memory, observational memory, channel subscriptions) had to read the thread and pass its title back. When title generation finished between that read and the write, the freshly generated title was overwritten with the stale one. `title` and `metadata` are now independently optional: omitting one leaves that column untouched. Callers that only change metadata no longer send a title, and message persistence no longer rewrites a thread row it just read. - Make `ProviderModelsMap` augmentable so custom gateways can register their providers and models. It is now declared as an exported interface on `@mastra/core/llm`, so `declare module '@mastra/core/llm'` augmentation flows through to `Provider`, `ModelForProvider` and `ModelRouterModelId`. ([#21043](https://github.com/mastra-ai/mastra/pull/21043)) - Fixed dataset item saving for traces with failed or suspended tool calls that have no recorded results. These dataset items now save successfully; missing tool results are stored as `null` instead of being omitted. ([#20569](https://github.com/mastra-ai/mastra/pull/20569)) - Stop timeTravel from destroying recorded workflow snapshots. Two changes: ([#21222](https://github.com/mastra-ai/mastra/pull/21222)) 1. timeTravel now fails with a descriptive error and leaves the recorded snapshot unchanged when the workflow graph has changed since the run was recorded. Steps inside preceding foreach or loop entries are not inspected by this check. 2. Unnamed .map() steps now get deterministic ids, so timeTravel works across process restarts for unchanged workflow code. Removing a .map() call is not detected, so re-run rather than time travel after deleting a mapping step. - Fixed tool execute-time input validation for Zod tools on Anthropic Claude 3.5 Haiku. The compat layer now skips string min/max checks that were removed from the model-facing JSON Schema, while preserving refinements, defaults, and other validation semantics. ([#19701](https://github.com/mastra-ai/mastra/pull/19701)) - Exported the `RunEvalsResult` type so the return type of `runEvals` can be imported from `@mastra/core/evals`. This also fixes a type error introduced in #21143 where the type-level tests imported a type that was not exported. ([#21196](https://github.com/mastra-ai/mastra/pull/21196)) - Fixed workflow run streams (`WorkflowRunOutput`) swallowing stream pipeline errors, which could hang callers forever. ([#18571](https://github.com/mastra-ai/mastra/pull/18571)) When the underlying stream errored (for example a provider/transport failure mid-run), the error was swallowed and the run never finalized — `await output.result` / `output.usage` and any `fullStream` consumers waited forever. These now reject with the error, the run is marked `failed`, and consumers receive a terminal `workflow-finish` event and close. ```ts const result = await run.stream(input).result; // before: hung forever on a stream error // after: rejects with the error ``` - Fixed `LocalSandbox` replacing your custom seatbelt profile. When you point `nativeSandbox.seatbeltProfilePath` at a profile file you wrote, that profile now stays active after a mount or an unmount. ([#20978](https://github.com/mastra-ai/mastra/pull/20978)) Mastra uses your profile exactly as written and does not add mount paths to it, so the profile must already allow every path you mount. If no file exists at that path, Mastra still generates a default profile, and that generated profile keeps allowing the paths you mount. Generated profiles now carry a marker comment, so a later run regenerates them and keeps allowing mount paths instead of reading them back as your own profile. To edit a generated profile and keep your edits, delete that marker comment: the file then counts as yours. - Fix durable agents losing the caller's request context after the first iteration. ([#21125](https://github.com/mastra-ai/mastra/pull/21125)) `prepareForDurableExecution` snapshots the caller's `RequestContext` onto the workflow input as `requestContextEntries`, and steps that rebuild the model and tools from the Mastra instance restore the context from that snapshot. The snapshot was dropped when iteration state was rebuilt and was never forwarded to the LLM step, so from the second iteration on, dynamic `model`, `tools`, `memory` and `workspace` resolvers ran against an empty context and silently fell back to defaults. This affected every path that cannot use the in-process run registry: Inngest workers, recovered runs, and evicted registry entries. The snapshot is now declared on the iteration state and LLM step input schemas, carried forward by `createBaseIterationStateUpdate`, and forwarded by the `map-to-llm-input` step in both the core and Inngest durable agentic workflows. - Added MCP server context compatibility for tools using the MCP 2.0 packages. ([#18683](https://github.com/mastra-ai/mastra/pull/18683)) - Fixed destroyed workspaces holding on to their indexed documents and loaded skills. Long-running apps that create a workspace per session no longer grow in memory as sessions come and go. ([#20617](https://github.com/mastra-ai/mastra/pull/20617)) **Also in this change** - Cleanup now runs even when another part of the workspace fails to shut down. - Indexing or accessing skills after workspace teardown begins now throws `WorkspaceNotReadyError` instead of quietly repopulating released content. - Updated dependencies [[`6bff877`](https://github.com/mastra-ai/mastra/commit/6bff877e214695ff8d9c84b06c13a6e6bcf9f1ed), [`f5a17d9`](https://github.com/mastra-ai/mastra/commit/f5a17d95c19e7d4149996932bd8d1905089f031d), [`5dba2a4`](https://github.com/mastra-ai/mastra/commit/5dba2a41600385751f5aace79878904e1972609d), [`9be8878`](https://github.com/mastra-ai/mastra/commit/9be8878dcf0388e84fc4873e0eec27bd49b881a4)]: - @mastra/schema-compat@1.3.6 ## 1.58.0-alpha.16 ### Minor Changes - Added a reusable Workflow Builder authoring API for discovering registered primitives, validating complete workflow definitions, and creating strict-provider-safe builder agents. Built-in workspace tools now expose explicit output schemas so workflow authors can infer tool compatibility. ([#21210](https://github.com/mastra-ai/mastra/pull/21210)) ```ts import { createWorkflowBuilderAgent } from '@mastra/core/workflows/builder'; const workflowBuilder = createWorkflowBuilderAgent({ id: 'workflow-builder', name: 'Workflow Builder', tools: discoveryAndSaveTools, model, }); ``` ### Patch Changes - Fixed dynamic workflows so object-form mapping configs keep working. Passing `mapConfig` as an object to `addDynamicWorkflow` previously failed at run time with `"[object Object]" is not valid JSON`; it now stays intact when the workflow is registered, saved, and loaded. ([#21287](https://github.com/mastra-ai/mastra/pull/21287)) - Fixed an active goal being reported as cancelled when its objective was written while a run was already in flight. The objective a goal state projection sees is now read from storage whenever the run-start read found nothing, so a goal started or restarted mid-run is no longer projected as having no objective. ([#21255](https://github.com/mastra-ai/mastra/pull/21255)) - Fixed an agent reply loop on the iMessage channel. iPhone read receipts arrive as inbound messages with no text and no attachments, and each agent reply triggered another receipt. Channel messages with neither text nor attachments no longer start an agent run. Custom channel handlers still receive them. ([#21240](https://github.com/mastra-ai/mastra/pull/21240)) - Resolve dynamic models once when preparing assigned tools. ([#21281](https://github.com/mastra-ai/mastra/pull/21281)) ## 1.58.0-alpha.15 ### Patch Changes - Fixed a crash where updating a thread without a title (for example during observational memory buffering) could write a null title and violate the database's not-null constraint when running a newer @mastra/memory against an older storage package. Memory now checks whether the connected storage adapter supports partial thread updates and backfills the existing title for older adapters, so mixed-version deployments keep working. See #21041 for the original title-clobbering fix this makes backward compatible. ([#21257](https://github.com/mastra-ai/mastra/pull/21257)) - Storage adapters now declare support for partial thread updates, letting newer @mastra/memory preserve existing thread titles instead of overwriting them, while remaining safe against older versions. ([#21257](https://github.com/mastra-ai/mastra/pull/21257)) ## 1.58.0-alpha.14 ### Minor Changes - Added AgentController live-session deletion with a process-local listener. Deletion is runtime-only: persisted threads and messages remain in storage and can be resumed by a future session. ([#21174](https://github.com/mastra-ai/mastra/pull/21174)) ```ts controller.onSessionDeleted(session => { console.log(session.identity.getResourceId()); }); await controller.deleteSession({ resourceId: 'project-42' }); ``` - Fixed agent streams closing silently when a provider ends the stream with `finishReason: 'error'` but sends no error payload (for example, Google models reporting `MALFORMED_FUNCTION_CALL`). Previously the stream closed with no error part and `onError` never fired, so a failed run looked identical to a turn that produced no text. The stream now emits an `error` chunk and calls `onError`, and error processors can intercept and retry the failure. ([#20302](https://github.com/mastra-ai/mastra/pull/20302)) Added `stepResult.rawReason` to `step-finish` and `finish` chunks. It preserves the provider's own finish reason instead of collapsing it to `'error'`, so you can tell distinct provider failures apart: ```ts for await (const chunk of stream.fullStream) { if (chunk.type === 'step-finish') { chunk.payload.stepResult.reason; // 'error' chunk.payload.stepResult.rawReason; // 'MALFORMED_FUNCTION_CALL' } } ``` These runs previously resolved as if they had succeeded with empty output. They now fail the same way a provider-reported error already did: `agent.generate()` rejects, and so do awaited stream promises such as `stream.text`. Iterating `fullStream` still completes normally, with an `error` chunk included. ### Patch Changes - Fixed subscribed agent-controller runs so dynamic workspaces use the identity from the request that started the run. ([#20658](https://github.com/mastra-ai/mastra/pull/20658)) - Fix DurableAgent inference crashes and error reporting (#21138): ([#21224](https://github.com/mastra-ai/mastra/pull/21224)) - DurableAgent inference no longer crashes with "Cannot read properties of undefined (reading 'type')" on malformed message content. - Durable LLM errors now keep their original message, name, stack, and cause when reported to callers and `onError`. - Fixed two thread-stream broadcast issues when subscribing to agent threads: ([#21224](https://github.com/mastra-ai/mastra/pull/21224)) - **Broadcast payload sanitization (#21219):** broadcast copies of `step-start`, `step-finish`, and `finish` parts no longer embed the raw model request body or duplicated step history, preventing multi-gigabyte pubsub streams and out-of-memory crashes when threads contain large media. - **Phantom replay prevention (#21223):** runs that failed before persisting any messages no longer replay as phantom partial messages to new thread subscribers on retained backends like Redis Streams. - Added a dedicated `SKILL_RESOLUTION` span type for dynamic agent skills resolvers, replacing the `GENERIC` type the `resolve-skills` span used before. The span now reports `agentId` and `skillCount` as typed span attributes. If you filter or query traces by span type, the resolver span's type value changed from `generic` to `skill_resolution`. ([#21232](https://github.com/mastra-ai/mastra/pull/21232)) - Fix dedicated scheduler deployments never starting the scheduler. When `MASTRA_WORKERS=scheduler` (or any worker filter naming the `scheduler` role) is set, `startWorkers()` now always injects the SchedulerWorker instead of relying on boot-time heuristics (declarative workflow schedules or persisted agent-schedule rows) that a standalone scheduler process cannot see — it exists to fire schedule rows created by other processes. `workers: false` and `scheduler: { enabled: false }` still take precedence. ([#21224](https://github.com/mastra-ai/mastra/pull/21224)) ## 1.58.0-alpha.13 ### Minor Changes - Added support for schema-aware routes created with `createRoute()` in `server.apiRoutes`. ([#21184](https://github.com/mastra-ai/mastra/pull/21184)) ```ts const route = createRoute({ method: 'POST', path: '/items', responseType: 'json', bodySchema: z.object({ name: z.string() }), handler: async ({ name }) => ({ name }), }); const mastra = new Mastra({ server: { apiRoutes: [route] }, }); ``` - Added a `command_exit` session event to the agent controller. Subscribers now receive the exit code and success flag of each foreground `execute_command` tool call, alongside the existing `shell_output` stream: ([#21211](https://github.com/mastra-ai/mastra/pull/21211)) ```typescript session.subscribe(event => { if (event.type === 'command_exit') { console.log(event.toolCallId, event.exitCode, event.success); } }); ``` Previously the exit outcome was only visible inside the tool result text, so observers could stream a command's output but never tell whether it succeeded. - Added `WorkspaceSandbox.snapshot()` for persisting sandbox state when supported. ([#21221](https://github.com/mastra-ai/mastra/pull/21221)) ```ts await sandbox.snapshot(); ``` ### Patch Changes - Added a `firstMeaningfulExecAt` timestamp to source-control sessions, recording when the session's agent completed its first successful sandbox command. Together with `firstMessageAt` this measures time-to-first-meaningful-exec: how long a user waits between sending their first message and the agent actually doing work in a live sandbox. The value is written once per session and is available on all session read APIs; setup commands run by the platform itself (skill loading, repo checkout) do not count. ([#21211](https://github.com/mastra-ai/mastra/pull/21211)) - Fixed model configuration validation to safely reject null values. ([#21192](https://github.com/mastra-ai/mastra/pull/21192)) - Fixed streamed assistant messages carrying a different id than their persisted copy. Chat UIs that reconcile a live stream with refetched history could show the same assistant reply twice (for example after returning to a hidden tab); with a shared id, deduplication by message id now works for every turn, including text-only replies without tool calls. ([#21185](https://github.com/mastra-ai/mastra/pull/21185)) - Stop timeTravel from destroying recorded workflow snapshots. Two changes: ([#21222](https://github.com/mastra-ai/mastra/pull/21222)) 1. timeTravel now fails with a descriptive error and leaves the recorded snapshot unchanged when the workflow graph has changed since the run was recorded. Steps inside preceding foreach or loop entries are not inspected by this check. 2. Unnamed .map() steps now get deterministic ids, so timeTravel works across process restarts for unchanged workflow code. Removing a .map() call is not detected, so re-run rather than time travel after deleting a mapping step. - Exported the `RunEvalsResult` type so the return type of `runEvals` can be imported from `@mastra/core/evals`. This also fixes a type error introduced in #21143 where the type-level tests imported a type that was not exported. ([#21196](https://github.com/mastra-ai/mastra/pull/21196)) ## 1.58.0-alpha.12 ### Patch Changes - Fixed `AgentController.resolveWorkspace` handing one session's workspace to every session created after it. On a controller configured with a dynamic workspace factory, the first call cached its result over the factory itself, so later sessions skipped resolution and ran against the first session's workspace instead of their own. ([#21144](https://github.com/mastra-ai/mastra/pull/21144)) `resolveWorkspace` now returns the workspace a session already resolved at creation, and caches nothing on the controller. Two smaller fixes come with it: `isWorkspaceReady()` no longer flips to `false` for factory configs, and slash commands read the same workspace instance the session's runs use. - Fix `deepEqual` returning `true` for values of mismatched shapes. A `Date` compared against a plain object (e.g. `deepEqual(new Date(), {})`) fell through to the generic object-key comparison — and since a `Date` has no own enumerable keys, it matched `{}`. Likewise an array compared against an object with matching index keys (e.g. `deepEqual([1, 2], { '0': 1, '1': 2 })`) returned `true`. Added guards so an array only equals an array and a `Date` only equals a `Date`; the checks apply recursively to nested values. ([#21146](https://github.com/mastra-ai/mastra/pull/21146)) - Fixed a hang when two streams were started for the same workflow run at once. Resuming or time-traveling a suspended run twice concurrently (a double-clicked approval, a client retry, or two open tabs) left one of the two streams open forever, so the matching resume-stream or time-travel-stream HTTP request never ended. Each stream now closes itself independently. ([#21126](https://github.com/mastra-ai/mastra/pull/21126)) - `runEvals` now accepts `gates` together with a categorized scorer config (`AgentScorerConfig` / `WorkflowScorerConfig`) without a TypeScript error. Previously a call like `runEvals({ target, data, gates, scorers: { trajectory: [...] } })` failed to compile with TS2769 because the categorized-config overloads didn't declare `gates`, even though the runtime already ran gates independently of the scorer shape. The optional `gates` property was added to both the agent and workflow categorized-config overloads. ([#21143](https://github.com/mastra-ai/mastra/pull/21143)) - Messages sent while an agent run is active are no longer silently lost when the run fails. ([#21128](https://github.com/mastra-ai/mastra/pull/21128)) **Failed runs now finish cleanly.** A run that dies on a provider error previously never settled its completion watcher, so queued messages were never delivered, the thread stayed locked, and no error surfaced. **Queued messages survive delivery failures.** If starting the follow-up run for a queued message fails, the message is put back at the head of the queue and the failure renders as an error. The message delivers on the next turn. - Updated dependencies [[`5dba2a4`](https://github.com/mastra-ai/mastra/commit/5dba2a41600385751f5aace79878904e1972609d)]: - @mastra/schema-compat@1.3.6-alpha.3 ## 1.58.0-alpha.11 ### Minor Changes - Add `resolveSession` and `onStaleToolApproval` to agent controller channels. ([#21094](https://github.com/mastra-ai/mastra/pull/21094)) `resolveSession` creates the session for a mapped channel thread in place of the built-in call. It runs before any session exists, so a host can refuse a request before a session, a model call, or any output happens, which is something `onSessionStart` cannot do, because it runs after the session is created and swallows errors. A refusal is silent: the chat thread gets nothing, so the host's authorization message never lands in a shared channel. Other failures still post an error to the thread as before, and a refusal is distinguishable as `ChannelSessionRejectedError` with the original error as its `cause`. Approval actions now resolve their session with the action's own request context, so a shared install can revalidate the person answering an approval instead of trusting the request that opened the session. `onStaleToolApproval` reports approval actions that have no matching parked gate, which is every approval answered after a restart. Mastra still refuses to run the tool; the hook lets a durable host settle that attempt rather than dropping the answer. It receives the `runId` the approval card was rendered for: the attempt the user answered, plus the session's `currentRunId`, which after a restart is usually null or a different run. - Added an opt-in `toolCallConcurrency` strategy that parallelizes safe tool calls when an approval or suspending tool is registered but not actually called in a step. ([#21106](https://github.com/mastra-ai/mastra/pull/21106)) Previously, registering any tool that requires approval or can suspend forced every tool-call batch to run one at a time, even when the model never called that tool. You can now opt in to resolving concurrency from the tools the model actually called: **Before** ```ts // Any registered approval/suspend tool forced sequential execution const stream = await agent.stream('...', { toolCallConcurrency: 8 }); ``` **After** ```ts // A pure-safe batch runs in parallel; a batch that calls an approval/suspend tool still serializes const stream = await agent.stream('...', { toolCallConcurrency: { limit: 8, strategy: 'called' }, }); ``` The default strategy ('available') keeps the existing conservative behavior. This works across both the standard loop and the durable engine. Closes #20100. - Added an `onDetection` callback to `PIIDetector` and `PromptInjectionDetector` so you can observe detection results and build your own metrics. ([#21123](https://github.com/mastra-ai/mastra/pull/21123)) The callback receives the raw detection result, the analyzed input, whether it crossed the threshold, and which strategy was applied. ```ts new PIIDetector({ model: 'openai/gpt-4o-mini', onDetection: ({ detectionResult, input, flagged, strategyApplied }) => { metrics.increment('pii.detections', { flagged, strategyApplied }); }, }); ``` Errors thrown from the callback are logged and never interrupt processing. Closes #13336 ### Patch Changes - Stop AgentController sessions from re-sending the whole conversation state on every streamed chunk. ([#21095](https://github.com/mastra-ai/mastra/pull/21095)) A session emits one `message_update` per streamed delta, and the session bus fanned out a full `display_state_changed` snapshot alongside each one. Every snapshot carried the growing message plus each finished tool's arguments and result, so a long turn with a large tool result re-sent that result thousands of times. A turn with 500 deltas and a 100 KB tool result put roughly 50 MB of snapshots on the wire; subscribers holding the event log could exhaust memory. Snapshots for high-frequency events are now coalesced, so a burst of deltas produces one snapshot per frame instead of one per delta. A snapshot always describes the complete current state, so no information is lost: any dropped intermediate snapshot is immediately superseded, and a pending snapshot is flushed before the next non-coalesced event so ordering and final state are unchanged. The engine also skips its per-delta message clone when a session has no subscribers. Event types, payload shapes, and delivery order are unchanged. - Fixed thread title generation being dropped on serverless runtimes by accepting an optional `serverless.waitUntil` on `generate()`/`stream()` so title persistence stays alive after the response without blocking the run. (#20682) ([#20996](https://github.com/mastra-ai/mastra/pull/20996)) ```ts import { waitUntil } from '@vercel/functions'; await agent.generate('Name this conversation', { serverless: { waitUntil }, memory: { thread: 'thread-1', resource: 'user-1', options: { generateTitle: true }, }, }); ``` - Made the workspace LSP manager's per-file lock hand out access in arrival order. Diagnostics requests for the same file were already serialized; a request that arrived at the moment the previous one finished could jump ahead of requests that had been waiting longer. Requests for different files still run in parallel. ([#21101](https://github.com/mastra-ai/mastra/pull/21101)) - Fixed Azure OpenAI routing to reject empty deployment names. ([#21105](https://github.com/mastra-ai/mastra/pull/21105)) - Fixed deferred notification deliveries failing with "No model selected" when the woken thread had no request context. The notification dispatch workflow re-sends deferred and summarized notifications long after the originating send, so any stream options attached to the original signal are gone; waking an idle thread then had no way to resolve a model. The notification delivery policy now drives the fix: `NotificationDeliveryDecision` accepts `streamOptions`, and the dispatcher re-runs the agent's delivery policy at delivery time (via the new `agent.resolveNotificationDeliveryDecision()`) to attach freshly resolved stream options to both individual and summary deliveries. Only `streamOptions` is honored at dispatch time; the record's persisted schedule still governs when and how it is delivered. Receipt-time sends also honor the policy's `streamOptions` now: an immediate deliver or summarize-now wake attaches them too, with caller-supplied stream options taking precedence. Mastra Code wires its session-based stream options resolver through the Code Agent's `deliveryPolicy.decide`, layered on the default decision logic. This fix applies to threads whose session is live in the current process; deliveries with no resolvable session fall back to a bare wake, and the "No model selected" error now distinguishes the case where a run started without any controller session context. This re-lands the capability removed by the #18637 revert in the policy-driven shape that revert called for, and the `@mastra/github-signals` bump only widens its `getNotificationStreamOptions` callback return type to allow `undefined`. ([#21113](https://github.com/mastra-ai/mastra/pull/21113)) - Fixed input processors receiving an undefined agent in their context when a durable agent run was resumed by a signal or schedule. The running agent is now passed through the processor workflow, so processors that rely on it (such as working memory and semantic recall) work correctly on wake. ([#21102](https://github.com/mastra-ai/mastra/pull/21102)) - Fix durable agents losing the caller's request context after the first iteration. ([#21125](https://github.com/mastra-ai/mastra/pull/21125)) `prepareForDurableExecution` snapshots the caller's `RequestContext` onto the workflow input as `requestContextEntries`, and steps that rebuild the model and tools from the Mastra instance restore the context from that snapshot. The snapshot was dropped when iteration state was rebuilt and was never forwarded to the LLM step, so from the second iteration on, dynamic `model`, `tools`, `memory` and `workspace` resolvers ran against an empty context and silently fell back to defaults. This affected every path that cannot use the in-process run registry: Inngest workers, recovered runs, and evicted registry entries. The snapshot is now declared on the iteration state and LLM step input schemas, carried forward by `createBaseIterationStateUpdate`, and forwarded by the `map-to-llm-input` step in both the core and Inngest durable agentic workflows. ## 1.58.0-alpha.10 ### Patch Changes - Fixed concurrent workflow tool approvals so each suspended call resumes the correct workflow run. ([#20347](https://github.com/mastra-ai/mastra/pull/20347)) ## 1.58.0-alpha.9 ### Minor Changes - Add `beforeAll`, `afterAll`, `beforeEach`, and `afterEach` lifecycle hooks to `runExperiment` for setting up and tearing down test data around an experiment run. `beforeAll` failures fail the experiment, `beforeEach` failures mark the item failed and skip execution (with `afterEach` still running), and `afterEach` failures are logged as warnings. ([#21082](https://github.com/mastra-ai/mastra/pull/21082)) - Made the workspace optional when creating an agent controller session. Previously `createSession()` threw `A session requires a valid workspace instance` unless a workspace was configured, which blocked chat-style sessions that only need threads, state, and agent runs. ([#21084](https://github.com/mastra-ai/mastra/pull/21084)) ```ts // Now works — no workspace configured anywhere const controller = new AgentController({ id: 'chat', storage, modes }); const session = await controller.createSession({ resourceId: 'user-1' }); session.getWorkspace(); // undefined ``` `Session.getWorkspace()` now returns `Workspace | undefined`, so check the result before using it. Passing a value that is not a `Workspace` instance is still rejected. Sessions that do configure a workspace are unchanged, including workspace initialization and the `workspace_ready` / `workspace_error` events. Closes #20594 - Added an optional `reason` when declining a tool call, so the model and your UI can see _why_ a tool call was rejected instead of always showing "Tool call was not approved by the user". ([#21085](https://github.com/mastra-ai/mastra/pull/21085)) ```ts // Before: the model only learned the call was declined await agent.declineToolCall({ runId, toolCallId }); // After: give the model context it can act on await agent.declineToolCall({ runId, toolCallId, reason: 'Reading other users personal data is not allowed, ask the user for their own email instead', }); ``` The reason is retained with the tool call, so it is still there when the conversation is recalled later. It is supported by `declineToolCall`, `declineToolCallGenerate` and `declineNetworkToolCall`, on both regular and durable agents. Omitting `reason` keeps the previous default message. Closes #20495 ### Patch Changes - Add a workflow `onStart` lifecycle hook. It is awaited before a run executes, receives the run context, and fires only on initial start — not on resume, restart, or time travel. Unlike `onFinish`/`onError`, errors thrown in `onStart` reject the `start()`/`stream()` call so it can act as a pre-flight gate, for example a quota check. ([#21063](https://github.com/mastra-ai/mastra/pull/21063)) - Fixed Workspace search indexing sending one embedding request per document. ([#21067](https://github.com/mastra-ai/mastra/pull/21067)) A batch-capable embedder (one branded with `batch: true`) was only used when the search engine ran in lazy mode, which `Workspace` never enables. Indexing a directory therefore cost one embedding round trip per file no matter what the embedder supported — indexing 500 files made 500 requests. `indexMany` now groups documents into batched embedder calls whenever the configured embedder is batch-capable, so those 500 files take 2 requests with `maxBatchSize: 256`. Vector writes are bounded as well: a single `upsert` now carries at most `min(maxBatchSize, 100)` vectors instead of the whole batch, since each document's metadata carries its full text and several vector stores reject oversized write requests. Lazy-mode rebuilds that previously issued one large `upsert` per flush now issue several bounded ones. An embedder declaring an unusable `maxBatchSize` (`0`, negative, or `NaN`) now falls back to the default batch size instead of hanging or silently indexing nothing. - Let `onDelegationComplete` correct a delegation result within the current run ([#21042](https://github.com/mastra-ai/mastra/pull/21042)) A subagent that stops on a tool-calls step returns empty text, which the parent model reads as a successful but empty delegation and narrates around ("I'll report back once it returns"). The `feedback` returned from `onDelegationComplete` is persisted to the parent's memory, so it only reaches the model on the next turn — after the parent has already answered. The hook can now return `resultText`, which replaces the tool result text the parent model sees for that delegation in the run that is still executing. - Acknowledge pubsub deliveries so persistent backends stop accumulating pending entries. Every fan-out subscribe creates a private consumer group, and Redis keeps each delivered entry pending until it is acked, so subscribers that never acknowledged grew an unbounded pending list for as long as they stayed attached. ([#21081](https://github.com/mastra-ai/mastra/pull/21081)) The following subscribers now ack every event they inspect (including ones they filter out) and nack when processing throws: - the agent thread-stream subscriber and the cross-agent remote-run waiter, which also waits for the terminal event's ack before unsubscribing - workflow run watchers, including the shared `nested-watch` topic - durable agent abort-request listeners - user-defined topic listeners registered through `events` or `addTopicListener` `events` listeners are typed as receiving only the event; acknowledgement is handled for them. ## 1.58.0-alpha.8 ### Minor Changes - Add `includeResolvedTools` to `ToolSearchProcessor`, making per-request tools (MCP tools that need the caller's credentials, or anything returned by a dynamic `tools` function) searchable and withholding them from the prompt until the agent loads them. Previously only tools listed at construction could be searched, so dynamically resolved tools were always sent in full. ([#21016](https://github.com/mastra-ai/mastra/pull/21016)) ```ts const toolSearch = new ToolSearchProcessor({ tools: staticTools, includeResolvedTools: true, }); const agent = new Agent({ id: 'mcp-agent', name: 'mcp-agent', instructions: 'Search for a tool when you need a capability you do not have.', model: 'openai/gpt-5.6-sol', // Resolved per request, then searchable alongside staticTools tools: async ({ requestContext }) => mcpClient.getTools(requestContext.get('userToken')), inputProcessors: [toolSearch], }); ``` ### Patch Changes - Add `bundler.minify` to minify `mastra build` output ([#21032](https://github.com/mastra-ai/mastra/pull/21032)) `mastra build` always emitted unminified code, which is larger than necessary when packaging for production — a container image or an on-prem deployment. Set `bundler.minify: true` to minify the emitted bundle. Minification runs over whole chunks, so comments and whitespace are dropped and local identifiers are shortened while exported names are preserved. ```typescript export const mastra = new Mastra({ bundler: { minify: true, }, }); ``` Defaults to `false`, so existing builds are unchanged. `mastra dev` is never minified. - Fixed durable agent completion on remote workers so processed messages persist, thread titles generate, and prior turns can be recalled. ([#20926](https://github.com/mastra-ai/mastra/pull/20926)) - Agent scorers now run on the Inngest durable engine. ([#21038](https://github.com/mastra-ai/mastra/pull/21038)) An agent configured with `scorers` never had them executed when running via `createInngestAgent()` — no scorer ran, no spans, no persisted scores, and no error. Core's durable workflow gained an `execute-scorers` step that the Inngest workflow builder, a copy of it, never picked up. Scorer execution now lives in the durable workflow's shared module and is used by both engines, so scorers behave identically on either one. - Make `abort()` stop durable agent runs that are executing in another process ([#21009](https://github.com/mastra-ai/mastra/pull/21009)) A durable agent's steps often run somewhere other than the process that started them — another replica behind a load balancer, or an Inngest step worker. `abort()` only flipped an in-memory `AbortController`, which those processes never see, so aborting a durable or Inngest agent run silently did nothing in exactly the deployments durable agents exist for. Abort intent now travels over pubsub. The executing process picks the request up and flips its own controller, so the run unwinds the same way an in-process abort does and still emits its terminal stream event — consumers waiting on the stream are released instead of hanging. `abort()` now returns a promise so callers can await dispatch; ignoring it preserves the previous fire-and-forget behavior. - Fixed suspended runs that could fail to save after repeated tool calls. ([#21002](https://github.com/mastra-ai/mastra/pull/21002)) A run's snapshot no longer grows with every step it took before suspending, which keeps agents that make many tool calls before their first approval prompt under MongoDB's 16 MB per-document limit — past it, the snapshot was never written and the resume reported the run as missing. Runs suspended by earlier versions still load. - Fixed `result.object` being stale after an output processor rejected a structured-output attempt with `abort(reason, { retry: true })`. The retried attempt object is now returned, matching `result.text`, instead of the rejected attempt object. Fixes #20570 ([#20999](https://github.com/mastra-ai/mastra/pull/20999)) - Fixed RegexFilterProcessor redact strategy to redact matches split across stream chunks, and added a `streamCarryoverSize` option for custom rules whose matches can be longer than the default 128-char window. Fixes #21049 ([#21050](https://github.com/mastra-ai/mastra/pull/21050)) - Fixed generated thread titles being clobbered during a turn ([#21041](https://github.com/mastra-ai/mastra/pull/21041)) `updateThread` required both `title` and `metadata`, so callers that only needed to change metadata (message persistence, working memory, observational memory, channel subscriptions) had to read the thread and pass its title back. When title generation finished between that read and the write, the freshly generated title was overwritten with the stale one. `title` and `metadata` are now independently optional: omitting one leaves that column untouched. Callers that only change metadata no longer send a title, and message persistence no longer rewrites a thread row it just read. - Make `ProviderModelsMap` augmentable so custom gateways can register their providers and models. It is now declared as an exported interface on `@mastra/core/llm`, so `declare module '@mastra/core/llm'` augmentation flows through to `Provider`, `ModelForProvider` and `ModelRouterModelId`. ([#21043](https://github.com/mastra-ai/mastra/pull/21043)) ## 1.58.0-alpha.7 ### Patch Changes - Fix durable agents dropping `requestContext` when a step is rehydrated on another process (for example an Inngest worker delegating to a subagent). Tool, memory, and workspace resolution now fall back to the run-level request context when the step input carries no request-context snapshot, so request-scoped configuration reaches subagents instead of resolving with an empty context. ([#21015](https://github.com/mastra-ai/mastra/pull/21015)) - Fixed background tool calls so the model sees the completed result instead of the dispatch placeholder. ([#21001](https://github.com/mastra-ai/mastra/pull/21001)) An agent that dispatched a tool to the background kept reading "Background task started..." on every later turn, so it would re-run the tool or answer without the result. Unrelated provider metadata on the tool call is now preserved when the completed result arrives. - Stop the browser-safe `@mastra/core/a2a/client` entry from pulling in the Node `module` builtin, which broke `@mastra/client-js` in browser bundles (`Module not found: Can't resolve 'module'` in Next.js). ([#21053](https://github.com/mastra-ai/mastra/pull/21053)) - Fixed `LocalSandbox` replacing your custom seatbelt profile. When you point `nativeSandbox.seatbeltProfilePath` at a profile file you wrote, that profile now stays active after a mount or an unmount. ([#20978](https://github.com/mastra-ai/mastra/pull/20978)) Mastra uses your profile exactly as written and does not add mount paths to it, so the profile must already allow every path you mount. If no file exists at that path, Mastra still generates a default profile, and that generated profile keeps allowing the paths you mount. Generated profiles now carry a marker comment, so a later run regenerates them and keeps allowing mount paths instead of reading them back as your own profile. To edit a generated profile and keep your edits, delete that marker comment: the file then counts as yours. ## 1.58.0-alpha.6 ### Patch Changes - Allow the browser `viewport` to be set to `'window'` so the page matches the real browser window instead of a fixed size. Adds `resolveViewportSize` and `resolveLaunchViewport` helpers plus a `DEFAULT_BROWSER_VIEWPORT` constant for providers to resolve the setting consistently. ([#21010](https://github.com/mastra-ai/mastra/pull/21010)) - Fixed output processors not being able to clear the final agent text. An output processor that redacts all assistant text to an empty string now correctly results in an empty `result.text` from `generate()` and `stream()`, instead of falling back to the original unprocessed model output. Fixes #19240 ([#20998](https://github.com/mastra-ai/mastra/pull/20998)) ## 1.58.0-alpha.5 ### Minor Changes - Renamed the beta stored workflows feature to dynamic workflows. `mastra.addStoredWorkflow()` is now `mastra.addDynamicWorkflow()`, `addStoredWorkflows()` is now `addDynamicWorkflows()`, the `StoredWorkflow*` types are now `DynamicWorkflow*`, and `workflow.origin` reports `'dynamic'` instead of `'stored'`. ([#20938](https://github.com/mastra-ai/mastra/pull/20938)) ### Patch Changes - Fixed deferred notifications accumulating dead workflow records forever. The internal dispatcher runs on a schedule (every minute by default) and left a completed snapshot row behind on every run, so `mastra_workflow_snapshot` grew unboundedly — tens of thousands of rows that were never read again. These runs no longer persist snapshots. Fixes #20254 ([#20970](https://github.com/mastra-ai/mastra/pull/20970)) - Fixed `include` in `listMessages` and `listMessagesByResourceId` so it can no longer return a message that belongs to a different resource. When you pass a `resourceId`, the target message and its surrounding context messages now stay inside that resource. Includes that cross threads inside the same resource keep working, so semantic recall with `scope: 'resource'` is unchanged. ([#20984](https://github.com/mastra-ai/mastra/pull/20984)) **Behaviour change in the in-memory store** The in-memory store read the context window from the thread you queried. It now reads the window from the thread that owns the target message, which is what the SQL stores already did. This only changes the result when an `include` entry names a message from another thread. The in-memory store also ignored `include` in `listMessagesByResourceId`. It now returns the included messages, like `@mastra/libsql` and `@mastra/pg` do. Fixes #20604. - Fix schema-based working memory losing stored data on partial updates. When a model updated one section of working memory, unrelated sections could be wiped out. Tools can now set `strict: false` to opt out of strict structured-output schema rewriting, which previously forced every field to be required and left models no way to signal "leave this field alone". ([#20992](https://github.com/mastra-ai/mastra/pull/20992)) ## 1.58.0-alpha.4 ### Minor Changes - Added the `bundler.entries` config option, a map of output name to source path for extra process entries that `mastra build` should emit alongside the server bundle. ([#20850](https://github.com/mastra-ai/mastra/pull/20850)) ```typescript title="src/mastra/index.ts" export const mastra = new Mastra({ bundler: { entries: { 'voice-worker': './voice-worker.ts' }, }, }); ``` This emits `.mastra/output/voice-worker.mjs` next to `.mastra/output/index.mjs`. Entry names cannot be `index`, `tools`, or start with `tools/`. See the `@mastra/deployer` changelog for details. - Channel agent replies now post as markdown by default, so Slack renders bold text, links, and tables natively and other chat platforms convert the reply to their own format. Previously the final reply was posted as literal plain text, which made standard markdown show up as raw `**bold**` and `[title](url)` characters in Slack while the same reply rendered correctly in Studio. ([#20971](https://github.com/mastra-ai/mastra/pull/20971)) This is a behavior change for every channel agent. If your agent was prompted to emit a platform dialect such as Slack mrkdwn to work around the old behavior, either remove those prompt instructions (recommended) or set the new `textFormat: 'plain'` option on the channel adapter config to keep posting literal plain text: ```typescript channels: { adapters: { slack: { adapter: createSlackAdapter(), textFormat: 'plain', }, }, }, ``` `textFormat` applies to final reply text only. Tool cards, error messages, tripwire notices, and native streaming (which was already markdown) are unchanged. Postable channel messages now also accept a `{ markdown: string }` object alongside strings and card elements. ## 1.58.0-alpha.3 ### Minor Changes - Added an `openIfEmpty` option to streamed tool display results. Set it to ([#20909](https://github.com/mastra-ai/mastra/pull/20909)) `false` when a tool lifecycle chunk should update only an active streaming session: ```typescript toolDisplay: event => ({ kind: 'stream', chunk: createTaskUpdate(event), openIfEmpty: false, }); ``` By default, stream results continue to open a session when needed. Static channels continue to use plain-text fallback rendering. - Added tracing to dynamic agent skills resolvers. The resolver now runs inside a `resolve-skills` span, so skills fetched from an external service show up in the agent's trace instead of disappearing, and it receives `tracingContext` for creating child spans of its own — the same pattern tools already use. ([#20949](https://github.com/mastra-ai/mastra/pull/20949)) ```typescript const agent = new Agent({ skills: async ({ requestContext, tracingContext }) => { const span = tracingContext?.currentSpan?.createChildSpan({ type: 'generic', name: 'entitlements-lookup', }); const skills = await fetchSkillsFor(requestContext.get('userId')); span?.end(); return skills; }, }); ``` On metadata reads such as `agent.listSkills()` no agent is running, so `tracingContext.currentSpan` is `undefined` — guard span usage with `?.` as shown above. - Added an explicit A2A Protocol v1.0 SDK export while preserving the existing v0.3 export. ([#20811](https://github.com/mastra-ai/mastra/pull/20811)) ```typescript import { ListTasksRequest } from '@mastra/core/a2a/v1'; const request = ListTasksRequest.fromJSON({ pageSize: 20 }); ``` ### Patch Changes - Prevent `streamLegacy()` cleanup from hanging when an observer stream has queued events that have not been consumed. ([#19921](https://github.com/mastra-ai/mastra/pull/19921)) - Fixed `Mastra.shutdown()` leaving database connections open when storage is a `MastraCompositeStore`. The composite had no `close()` of its own, so shutdown silently skipped storage cleanup and any composed adapter (Redis, LibSQL, Postgres, ...) kept its client connected — leaving processes that wait for a graceful drain, such as test runners and Kubernetes pods handling SIGTERM, hanging until they were killed. ([#20629](https://github.com/mastra-ai/mastra/pull/20629)) A composite now closes everything it was built from: the `default` and `editor` stores, plus any domain that owns its own connection. Each store is closed once even when it backs several domains, and a store that fails to close is logged and skipped so the remaining handles are still released. See [#20621](https://github.com/mastra-ai/mastra/issues/20621). - Fixed `Mastra.startWorkers()` so it no longer reads the schedules store on boot when the scheduler cannot start. Boot now skips that read if you set `scheduler: { enabled: false }` or `workers: false`. Storage adapters that need request or tenant context no longer warn on every boot. Automatic detection of persisted agent schedules and deferred notifications is unchanged when you do not opt out. ([#20982](https://github.com/mastra-ai/mastra/pull/20982)) - Fixed sub-agent delegation failing when an LLM sends `maxSteps` as a numeric string (e.g. `"10"` instead of `10`). Delegation now accepts valid numeric strings and continues to reject invalid values such as non-integers or numbers below 3. ([#20793](https://github.com/mastra-ai/mastra/pull/20793)) - Fixed agent schedules targeting stored agents being permanently deleted after a server restart. Both deletion paths are covered: the scheduler tick loop no longer counts an unhydrated stored agent as a missing target (it confirms absence against the editor before reclaiming the schedule row), and the fire path resolves stored agents through the editor before self-cleaning. Schedules are never deleted when the editor lookup fails transiently — only a confirmed miss from both the registry and the editor reclaims the row. ([#19791](https://github.com/mastra-ai/mastra/pull/19791)) - Fixed DurableAgent terminal cleanup so it also clears `workflow.events.v2.`, preventing orphaned no-TTL counter keys on persistent caches (#20786). ([#20961](https://github.com/mastra-ai/mastra/pull/20961)) - Fixed nested agent-as-tool approvals so users see the inner tool and arguments while resumes retain the parent delegation identity (#20934). ([#20948](https://github.com/mastra-ai/mastra/pull/20948)) - Fixed an issue where observer cleanup failures no longer prevent legacy workflow streams from completing cleanly. ([#20652](https://github.com/mastra-ai/mastra/pull/20652)) - Fixed durable agents losing custom model-facing tool output between workflow steps. ([#20176](https://github.com/mastra-ai/mastra/pull/20176)) - Hardened `runEvals` threshold checks: non-finite scores (for example `NaN`) now fail `min`/`max` range thresholds instead of passing, and invalid threshold shapes passed from JavaScript (strings, `null`, arrays) are rejected with a clear `INVALID_SCORER_THRESHOLD` error instead of silently passing every score. ([#20145](https://github.com/mastra-ai/mastra/pull/20145)) - Fixed sendStreamResume() to resume suspended agent runs from storage after a server restart or when requests reach another instance. ([#20602](https://github.com/mastra-ai/mastra/pull/20602)) - Fixed `session.abort()` when a tool call is waiting on approval or parked in a suspension. ([#20972](https://github.com/mastra-ai/mastra/pull/20972)) Aborting from a `tool_approval_required` subscriber raised an error and ended the run with reason `error`. It now completes with reason `aborted`, and the gated call settles as `output-denied` instead of rendering as still in flight. Aborting while tool suspensions were parked (`ask_user`, `request_access`) dropped them silently, leaving prompts on screen whose answers could never be delivered. Each dropped suspension now emits `tool_suspension_cancelled`. Fixes #20592 - Multi-step model generations now retain provider metadata from every completed step. ([#19921](https://github.com/mastra-ai/mastra/pull/19921)) - Fixed `convertFullStreamChunkToUIMessageStream` from `@mastra/core/stream` dropping the finish reason. The terminal `finish` chunk now carries `finishReason`, so a UI message stream built on this export can tell `stop`, `length`, `content-filter`, `tool-calls` and `other` apart. Fixes #20562. ([#20983](https://github.com/mastra-ai/mastra/pull/20983)) - Fixed AgentController session preferences (thinking level, notifications) reverting to defaults after a server restart. These preferences now survive restarts and follow the conversation: reopening a thread restores the values that were active in it, including on self-hosted Factory deployments. ([#20901](https://github.com/mastra-ai/mastra/pull/20901)) - Fixed live scorer execution across multiple Mastra instances so only the instance that emitted a scorer run handles it. ([#20840](https://github.com/mastra-ai/mastra/pull/20840)) - Fixed `@mastra/core` crashing when a project loads Mastra as CommonJS. Several ESM-only dependencies were read as a module object instead of a function, so the calls threw. ([#20980](https://github.com/mastra-ai/mastra/pull/20980)) **What failed before** - `new MCPServer(...)` threw `slugify is not a function`. - `mastra.schedules.create()` threw the same error. - Workspace search, workspace file indexing, and batch trace scoring threw `p_map.default is not a function`. All of these paths now run under CommonJS and ESM. See [#20354](https://github.com/mastra-ai/mastra/issues/20354). - Updated dependencies [[`6bff877`](https://github.com/mastra-ai/mastra/commit/6bff877e214695ff8d9c84b06c13a6e6bcf9f1ed)]: - @mastra/schema-compat@1.3.6-alpha.2 ## 1.58.0-alpha.2 ### Minor Changes - Added `instructions.ts` as a code alternative to `instructions.md` for file-based agents. Use it when the prompt needs TypeScript, for example when it's built from shared constants or resolved per request. ([#20847](https://github.com/mastra-ai/mastra/pull/20847)) Before, a computed prompt had to move into `config.ts`, splitting it away from the agent's other instructions: ```typescript // src/mastra/agents/support/config.ts import { agentConfig } from '@mastra/core/agent'; export default agentConfig({ model: 'openai/gpt-5.6-sol', instructions: ({ requestContext }) => { const tier = requestContext.get('tier') ?? 'standard'; return `You are a support agent. Treat this as a ${tier}-tier customer.`; }, }); ``` Now it lives in its own file, next to `config.ts`: ```typescript // src/mastra/agents/support/instructions.ts import { agentInstructions } from '@mastra/core/agent'; export default agentInstructions(({ requestContext }) => { const tier = requestContext.get('tier') ?? 'standard'; return `You are a support agent. Treat this as a ${tier}-tier customer.`; }); ``` The file default-exports a string, a system message, or a function returning one. `agentInstructions()` is an identity helper that only adds editor types. **Precedence** A function `config.instructions` still wins over both files, then `instructions.ts` wins over `instructions.md`, which still wins over a static `config.instructions`. Defining instructions in more than one place logs a warning naming both sources and which one wins. One upgrade case needs a rename. If an agent directory already holds an unrelated `instructions.ts`, for example a helper that `config.ts` imports, Mastra now reads that file as the agent's instructions instead of its old source, and the build fails if the file has no default export. - Added a `requestContextKeys` option to scorer runs that controls which request-context values are recorded on the eval's span input for repeatability. ([#20808](https://github.com/mastra-ai/mastra/pull/20808)) Previously the entire request context was recorded on every scorer-run span. Because request context is an arbitrary, app-controlled bag, that could persist secrets or PII stored under any key into exported traces and datasets. Scorer runs now record **nothing** from the request context by default — you opt in per key. **What changed** - Default (no `requestContextKeys`): nothing from the request context is recorded. - Specific keys: only those keys are recorded, with dot notation for nested values. - `['*']`: the whole context is recorded (the framework-managed auth token stays redacted). This is separate from the observability config's `requestContextKeys`, which controls live span metadata. Recording a run so it can be reproduced later and surfacing keys on every live span are different concerns, so they are controlled independently. **Example** ```ts await scorer.run({ input, output, requestContext: { userId: 'u_123', tenant: { id: 't_1', apiKey: 'secret' } }, // Persist only what you need to reproduce the run — `apiKey` is never stored. requestContextKeys: ['userId', 'tenant.id'], }); ``` ### Patch Changes - Enqueue a `tool-output-denied` chunk when a `requireApproval` tool is declined so live AI SDK clients resolve the pending tool call instead of hanging. Persistence as `output-denied` already worked; only the stream path was missing. ([#20886](https://github.com/mastra-ai/mastra/pull/20886)) ```ts for await (const part of toAISdkStream(result.fullStream, { from: 'agent' })) { if (part.type === 'tool-output-denied') { // Clears the pending requireApproval tool call on the client console.log('denied', part.toolCallId); } } ``` - Fixed workflow `.map()` step arrays so they preserve branch results of `{}`, `0`, `false`, and empty strings instead of returning `null`. ([#20896](https://github.com/mastra-ai/mastra/pull/20896)) - Stamp the run's traceId into persisted assistant message metadata so a stored message can be correlated back to its trace. ([#20928](https://github.com/mastra-ai/mastra/pull/20928)) Previously a caller holding only a `messageId` had no supported way to find the trace that produced it: message rows carry no `traceId` column and span records carry no `messageId`. The traceId now rides along in the metadata that already carried `modelId` and `provider`, on both the regular and the durable agent path. ```typescript const { messages } = await memory.recall({ threadId, perPage: false }); const traceId = messages.find(m => m.id === messageId)?.content.metadata?.traceId; ``` This is forward-looking — messages persisted before this change have no traceId. - Fixed AgentController losing the second auto-approved tool result when sequential tool calls resume from a stale suspended snapshot (#19814) ([#19940](https://github.com/mastra-ai/mastra/pull/19940)) - Pass `formatLocation` to `SkillsProcessor` when skill files are not at `${skill.path}/SKILL.md` from the model's point of view, such as when the agent's filesystem tools run against a sandbox that mounts them elsewhere. Key the override on `skill.path` so skills that share a name still render distinct locations. ([#20893](https://github.com/mastra-ai/mastra/pull/20893)) ```ts new SkillsProcessor({ workspace, formatLocation: skill => `/mnt/skills${skill.path}/SKILL.md`, }); ``` Remapped locations remain valid skill identifiers: the processor registers each rendered location as an alias with the skills registry, so the `skill` and `skill_read` tools resolve it back to the underlying skill. The skill-tool instruction now also tells the model that `location` may not exist on its filesystem, so it reads skill files with `skill_read` instead of filesystem tools. If a custom `WorkspaceSkills` implementation does not support alias registration, the instruction falls back to directing the model to refer to skills by name. - Fixed dynamic `skills` resolvers running four times per agent request. Skills are needed in several places during one execution, and each of them called your resolver again — so a resolver that fetches over the network made four calls per request instead of one. ([#20921](https://github.com/mastra-ai/mastra/pull/20921)) ```typescript const agent = new Agent({ name: 'support', instructions: 'Help the customer.', model: 'openai/gpt-5-mini', skills: async ({ requestContext }) => { // Called four times per generate()/stream() before this change, once now. const res = await fetch(`https://internal/skills?user=${requestContext.get('userId')}`); return (await res.json()).skills; }, }); ``` The resolution is now shared across a request, keyed on its `RequestContext`. A new request resolves again, and a failed resolution is not cached so a retry still reaches your resolver. If you reuse a single `RequestContext` across several executions, those executions now share one resolution. - Fixed Mastra shutdown so background task resources are released before storage closes without blocking indefinitely. Retryable running tasks remain recoverable after a process restart, while local executors are aborted and durable dispatches are left for surviving workers during shutdown. ([#20194](https://github.com/mastra-ai/mastra/pull/20194)) - Fixed tool calls that fail during execution staying stuck as running in agent-controller session streams. A tool error now updates the streamed message part to a terminal errored result and emits the corresponding message update, exactly like a successful tool result. The `isError` flag on persisted tool invocations is now part of the public type instead of an undeclared runtime field. ([#20805](https://github.com/mastra-ai/mastra/pull/20805)) - Fixed tool execute-time input validation for Zod tools on Anthropic Claude 3.5 Haiku. The compat layer now skips string min/max checks that were removed from the model-facing JSON Schema, while preserving refinements, defaults, and other validation semantics. ([#19701](https://github.com/mastra-ai/mastra/pull/19701)) - Updated dependencies [[`9be8878`](https://github.com/mastra-ai/mastra/commit/9be8878dcf0388e84fc4873e0eec27bd49b881a4)]: - @mastra/schema-compat@1.3.6-alpha.1 ## 1.58.0-alpha.1 ### Minor Changes - Added typed experiment provenance and grouping fields that persist across synchronous and asynchronous runs and can be filtered when listing experiments. ([#20645](https://github.com/mastra-ai/mastra/pull/20645)) ```ts await dataset.startExperiment({ task, scorers, provenance: { source: 'github', sourceVersion: 'abc123' }, grouping: { experimentSetId: 'benchmark-1', variantId: 'candidate', trialIndex: 0 }, }); ``` - Added a delegated request context to `onDelegationStart`. Each subagent run receives a context map derived from its parent, so hooks can add values before dynamic agent configuration resolves without changing the parent context. ([#20853](https://github.com/mastra-ai/mastra/pull/20853)) ```typescript await supervisor.stream('Research AI trends', { delegation: { onDelegationStart: context => { context.requestContext.set('specialty', context.primitiveId); }, }, }); ``` Subagent request contexts inherit caller entries but exclude parent memory, thread, and resource identity. Setting or deleting entries during a subagent run no longer changes the parent context map or another concurrent delegation's map. - Set `tools.writeLockTimeoutMs` when a remote or cold-starting filesystem needs more than 30 seconds to accept a write. The write tool waits for the configured timeout before returning a `write-lock timeout` error. ([#20809](https://github.com/mastra-ai/mastra/pull/20809)) ```typescript const workspace = new Workspace({ filesystem: mySandboxFilesystem, tools: { // allow a cold-starting sandbox time to accept its first write writeLockTimeoutMs: 210_000, }, }); ``` The default is unchanged at 30 000 ms. - Lightweight trace lists now work on every storage backend. Their rows carry an `inputPreview` so a list can render its preview column without transferring the whole prompt, plus a computed `status` and the span `metadata` so Studio's configurable trace columns work unchanged on the lightweight list. ([#20677](https://github.com/mastra-ai/mastra/pull/20677)) ```ts const { spans } = await storage.listTracesLight({ pagination: { page: 0, perPage: 25 } }); spans[0].inputPreview; // short preview text — no input/output/attributes blobs ``` `ObservabilityStorage.listTracesLight()` previously threw on any backend that did not implement it — which was every backend except ClickHouse, DuckDB and the in-memory store. It now defaults to `listTraces()` with each row projected down, so all backends serve the same response shape. Backends that can push the projection into the query should still override it; that is what keeps the blob columns off the read path. `lightSpanRecordSchema` gains optional `inputPreview`, `status` and `metadata` fields, and `buildInputPreview()` / `toLightSpanRecord()` are exported for stores that derive the preview at read time. `listTracesLightResponseSchema` also gained the `delta` and `deltaCursor` fields already present on `listTracesResponseSchema`, so lightweight lists can be live-tailed. Note that `pagination` on `ListTracesLightResponse` is now optional, because delta-mode responses return a cursor instead of a page. Code that reads it directly needs a guard: ```ts // Before const total = response.pagination.total; // After const total = response.pagination?.total ?? 0; ``` ### Patch Changes - dependencies updates: ([#19783](https://github.com/mastra-ai/mastra/pull/19783)) - Updated dependency [`posthog-node@^5.46.1` ↗︎](https://www.npmjs.com/package/posthog-node/v/5.46.1) (from `^5.37.0`, in `dependencies`) - Fixed the response cache serving one image's answer for a different image. `ResponseCache` derives its key from the resolved prompt, but URL-valued image and file parts were serialized as `{}`, so two requests that differed only in which image they pointed at collided on the same cache entry. URLs now contribute their full href, and inline binary data contributes a digest of its bytes instead of being expanded one property per byte (which turned a 1 MiB image into ~11 MiB of intermediate JSON, hashed synchronously before every model call). ([#20656](https://github.com/mastra-ai/mastra/pull/20656)) - Improved A2A v0.3 remote task continuation. A2AAgent now resumes input and authentication requests using the original task ID and surfaces protocol errors returned by remote agents. ([#20708](https://github.com/mastra-ai/mastra/pull/20708)) ```typescript const resumedResult = await remoteAgent.resumeGenerate({ approved: true }, { runId }); console.log(resumedResult.text); ``` - Fixed durable agent streams crashing when consumed by `@mastra/ai-sdk` and other chunk converters. The `step-start` stream chunks now use the canonical shape, matching the regular engine and preventing destructuring errors when reading the chunk payload. ([#19575](https://github.com/mastra-ai/mastra/pull/19575)) - Fixed dataset experiment runs failing with a missing-thread memory error when the target agent has memory configured and the request context provides only a resource id (for example from auth middleware or Studio). Each dataset item now runs in its own fresh memory thread; explicitly supplied thread ids are still respected. Fixes #20663 ([#20844](https://github.com/mastra-ai/mastra/pull/20844)) - Fixed experiment runs reporting a failed outcome instead of cancelled when cancellation interrupted an in-flight item. Cancelled experiment runs now consistently finish with a cancelled outcome, so standalone experiment workers exit with the cancellation exit code instead of a failure code. ([#20719](https://github.com/mastra-ai/mastra/pull/20719)) - Added an `onSessionStart` hook to `AgentControllerChannels` config, called once per session after it is bound to its mapped chat thread and before the first message dispatches. Messages arriving while the hook is still running wait for it instead of dispatching on an unconfigured session. ([#20832](https://github.com/mastra-ai/mastra/pull/20832)) ```typescript const controller = new AgentController({ id: 'support-controller', agent, channels: { adapters: { slack: createSlackAdapter() }, onSessionStart: async ({ session, thread }) => { await session.model.switch({ modelId: 'anthropic/claude-sonnet-4-6' }); }, }, }); ``` A host can already name a channel session through `resolveResourceId`, but it never receives the `Session` object, which is created inside the channel machinery. Without a seam there, a host could only configure sessions it created itself, and channel-created sessions silently ran on the built-in default models. Hook errors are logged and swallowed so a session that cannot be configured still answers the message. - Fixed assistant message history so provider-executed tool failures are preserved instead of remaining as pending calls. Matching tool invocations now use `state: "output-error"`, and the normalized provider message is stored in `errorText` with a fallback when no usable message is available. Fixes https://github.com/mastra-ai/mastra/issues/20715 ([#20716](https://github.com/mastra-ai/mastra/pull/20716)) - Fixed parallel sub-agent approvals and suspensions so every persisted target remains resumable after refresh. ([#20700](https://github.com/mastra-ai/mastra/pull/20700)) - Fixed failed workspace commands to label standard output and standard error. ([#20774](https://github.com/mastra-ai/mastra/pull/20774)) - Fixed a bug where a server-side tool that was still running when a request was aborted (for example, when a turn parked waiting on a client-side tool and the response closed, or the user hit Stop) was persisted as a _completed_ tool call whose result was the abort message. On resume, the cancelled — and possibly half-finished — operation read as a successful tool call. ([#18034](https://github.com/mastra-ai/mastra/pull/18034)) Tool executions interrupted by an aborted request are now left as incomplete calls instead of being recorded as fabricated successful results, so they are no longer mistaken for completed work when a conversation resumes. Genuine tool errors on a live request still surface to the model as before. - Fixed stopping an agent-controller run so it always takes effect. When the model stream hung and never reacted to the abort signal, the session stayed stuck in a running state until the server was restarted; the run now finalizes as aborted a few seconds after the stop request. ([#20835](https://github.com/mastra-ai/mastra/pull/20835)) - Fixed observational memory stalling agent loops after a tool fails. ([#20788](https://github.com/mastra-ai/mastra/pull/20788)) - Fixed unbounded memory growth in the shared event cache when a caching pub/sub is backed by a persistent store such as Redis. Workflow watch events and other instance-local events were still being copied into the shared replay cache even though no other instance could read them. Because those events carry cumulative step results, a single workflow run could add tens of megabytes of cache entries that nothing ever consumed, and each topic left behind an index counter that was never cleaned up. Instance-local events are now delivered live only and skip the cache entirely; replay for agent streams is unchanged. Fixes https://github.com/mastra-ai/mastra/issues/20646 ([#20685](https://github.com/mastra-ai/mastra/pull/20685)) - Fixed dataset item saving for traces with failed or suspended tool calls that have no recorded results. These dataset items now save successfully; missing tool results are stored as `null` instead of being omitted. ([#20569](https://github.com/mastra-ai/mastra/pull/20569)) - Fixed workflow run streams (`WorkflowRunOutput`) swallowing stream pipeline errors, which could hang callers forever. ([#18571](https://github.com/mastra-ai/mastra/pull/18571)) When the underlying stream errored (for example a provider/transport failure mid-run), the error was swallowed and the run never finalized — `await output.result` / `output.usage` and any `fullStream` consumers waited forever. These now reject with the error, the run is marked `failed`, and consumers receive a terminal `workflow-finish` event and close. ```ts const result = await run.stream(input).result; // before: hung forever on a stream error // after: rejects with the error ``` - Added MCP server context compatibility for tools using the MCP 2.0 packages. ([#18683](https://github.com/mastra-ai/mastra/pull/18683)) - Fixed destroyed workspaces holding on to their indexed documents and loaded skills. Long-running apps that create a workspace per session no longer grow in memory as sessions come and go. ([#20617](https://github.com/mastra-ai/mastra/pull/20617)) **Also in this change** - Cleanup now runs even when another part of the workspace fails to shut down. - Indexing or accessing skills after workspace teardown begins now throws `WorkspaceNotReadyError` instead of quietly repopulating released content. - Updated dependencies [[`f5a17d9`](https://github.com/mastra-ai/mastra/commit/f5a17d95c19e7d4149996932bd8d1905089f031d)]: - @mastra/schema-compat@1.3.6-alpha.0 ## 1.58.0-alpha.0 ### Minor Changes - **Added file-based schedules for agents** ([#20711](https://github.com/mastra-ai/mastra/pull/20711)) File-based agents can now declare recurring tasks in a `schedules/` directory next to their tools and skills. Mastra registers them into schedule storage at startup, so a scheduled agent no longer needs any runtime registration code. Each file is one schedule: a cron expression plus exactly one execution mode. Prompt mode runs the owning agent with a fixed message. ```typescript // src/mastra/agents/support/schedules/heartbeat.ts import { defineSchedule } from '@mastra/core/agent'; export default defineSchedule({ cron: '*/5 * * * *', prompt: 'Check system health and report any failures.', }); ``` Handler mode computes the fire when it triggers, and returning `null` skips it. ```typescript // src/mastra/agents/support/schedules/billing/sweep.ts import { defineSchedule } from '@mastra/core/agent'; export default defineSchedule({ cron: '0 3 * * *', handler: async () => { const overdue = await findOverdueInvoices(); if (overdue.length === 0) return null; return { prompt: `Chase ${overdue.length} overdue invoices.` }; }, }); ``` A schedule's id is its path under `schedules/` with the extension stripped, so `billing/sweep.ts` becomes `billing/sweep` and stays stable across builds. Editing a cron patches the stored schedule and recomputes its next fire time, deleting the file deletes the schedule, and pausing a schedule through the API survives a redeploy. Schedules created with `mastra.schedules.create(...)` are in a separate namespace and are never touched by this sync. A schedule can also be a Markdown file, using cron frontmatter with the document body as the prompt. ```text // src/mastra/agents/support/schedules/cleanup.md --- cron: "0 3 * * *" --- Review tickets untouched for 30 days and close the ones that are resolved. ``` Declaring a schedule is enough to start the scheduler. Schedules are supported on root agents only; a `schedules/` directory under `subagents/` fails the build, because the scheduler cannot resolve a subagent as a run target. `defineSchedule` is exported from both `@mastra/core/agent` and `@mastra/core/schedules`, so authoring a file-based agent needs a single import path. Build and dev support the same convention: schedules are discovered at build time, Markdown schedules fail the build with a message naming the file when the cron is missing, the body is empty, the frontmatter has an unknown field, or the YAML is unparseable. That last case has its own message because a leading `*` is a YAML alias, so `cron: */5 * * * *` needs quoting. The dev server rebuilds when a Markdown schedule changes. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`45a9147`](https://github.com/mastra-ai/mastra/commit/45a914741f578754d79d8b7de7b4e4f304d8e14a)) - Fix `tryStreamWithJsonFallback` treating a valid falsy structured-output value as undefined. The first-attempt check used `!object`, so a schema resolving to a falsy-but-defined value (e.g. `z.boolean()` -> `false`, `z.number()` -> `0`) was wrongly rejected and triggered an unnecessary JSON-prompt fallback stream. It now checks `object === undefined`, matching the generate path and the stream fallback path. ([#20628](https://github.com/mastra-ai/mastra/pull/20628)) ## 1.57.0 ### Minor Changes - Added `transient` to non-state agent signals. Set `transient: true` when a processor should deliver a reminder to the current model call without retaining it in conversation history. ([#18909](https://github.com/mastra-ai/mastra/pull/18909)) ```typescript await sendSignal?.({ type: 'reactive', contents: 'Stay on the current task.', transient: true, }); ``` - Added tool definitions to MODEL_GENERATION span attributes. The tools made available to the model (name, description, and JSON-schema parameters) are now captured once per generation as the `tools` attribute, so observability exporters can surface which tool schemas the model ran with. Per-step tool names (after `activeTools` filtering) remain on MODEL_INFERENCE spans as `availableTools`. Related: #20242 ([#20243](https://github.com/mastra-ai/mastra/pull/20243)) ```typescript // Any exporter reading MODEL_GENERATION spans now receives: span.attributes.tools; // [ // { // type: 'function', // name: 'get_weather', // description: 'Get the weather for a city', // parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] }, // }, // ] ``` - Added `processToolResult` processor lifecycle method that fires after each tool execution and before the result is added to message history. Symmetric with `processOutputStep`, this hook lets processors scan tool returns for prompt injection or sensitive data, transform them, or abort the run before the LLM sees the result. ([#16012](https://github.com/mastra-ai/mastra/pull/16012)) ```ts class ToolResultGuard implements Processor { readonly id = 'tool-result-guard'; async processToolResult({ toolName, result, abort, messageList, toolCallId, args }) { if (containsPromptInjection(result)) { abort('blocked by tool-result-guard'); } } } ``` ### Patch Changes - dependencies updates: ([#20149](https://github.com/mastra-ai/mastra/pull/20149)) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.13` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.13) (from `npm:@ai-sdk/provider-utils@5.0.11`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.4` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.4) (from `npm:@ai-sdk/provider@4.0.3`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`c8002da`](https://github.com/mastra-ai/mastra/commit/c8002da7775c468e2965b6ff5f82045450fa8cb9)) - Browser applications can now import `toAISdkMessages` from `@mastra/ai-sdk/ui` without crashing with `TypeError: createRequire is not a function`. ([#20760](https://github.com/mastra-ai/mastra/pull/20760)) - Fixed a memory leak where suspended agent runs were never released from memory. Every suspend kept its full in-memory transcript retained for the life of the process, so long-running servers with many suspend/resume cycles grew unbounded and could eventually exhaust the heap. ([#20273](https://github.com/mastra-ai/mastra/pull/20273)) Suspended runs are now kept warm only for a bounded window (30 minutes by default) and then evicted. A same-instance resume within that window still reattaches to the warm state exactly as before; a resume after the TTL expires falls back to the durable snapshot. Set `MASTRA_SUSPENDED_RUN_TTL_MS` (in milliseconds) to tune the window — lower it on multi-instance deployments where a resume rarely lands on the origin instance: ```sh # keep suspended runs warm for 10 minutes instead of the default 30 MASTRA_SUSPENDED_RUN_TTL_MS=600000 ``` - Fixed sessions opening a new empty thread on every start instead of resuming their conversation. This affected setups that give each session its own working directory: the conversation stayed in storage but the session never reopened it, leaving an unused thread behind each time. ([#20743](https://github.com/mastra-ai/mastra/pull/20743)) - Fixed boot-time recovery so nested workflows that already finished inside a parent `.parallel()` are reused instead of restarted. Parent `activeStepsPath` can still list completed children after a crash; restarting those terminal snapshots no longer throws "This workflow run was not active". As part of this, calling `restart()` on a run whose snapshot is already `success`, `failed`, or `tripwire` now returns the stored result without re-executing any steps (it previously threw). Fixes https://github.com/mastra-ai/mastra/issues/20225 ([#20518](https://github.com/mastra-ai/mastra/pull/20518)) - Fixed invalid workflow input responses to return HTTP 400 instead of HTTP 500 while preserving schema validation details. ([#20722](https://github.com/mastra-ai/mastra/pull/20722)) - Fixed `DurableAgent` still writing messages to the thread during tool-call suspension (approval / in-execution suspend) and background-task completion when `memory.options.readOnly` was set. Follow-up to the readOnly fix for the durable finish path (#18921) — these mid-run flush paths in `steps/tool-call.ts` had the same missing guard. ([#18856](https://github.com/mastra-ai/mastra/pull/18856)) - Fixed durable agents losing a pending approval on page refresh. The suspended tool's metadata is now persisted to the assistant message, so a reloading client re-renders the approval instead of showing none while the run sits parked and resumable. ([#19713](https://github.com/mastra-ai/mastra/pull/19713)) This applies whenever the agentic loop executes in a different process than the one that called `stream()` — for example the `@mastra/inngest` `connect()` worker topology. - Fixed agent controller approvals losing the current caller identity while processing subscribed thread streams. ([#20741](https://github.com/mastra-ai/mastra/pull/20741)) - Fixed `filterIncompleteToolCalls: false` producing prompts that providers reject with a 400. ([#20636](https://github.com/mastra-ai/mastra/pull/20636)) A tool call that suspends for approval is stored without a result. With filtering disabled that call was sent to the provider unpaired, and since every provider requires a tool call to have a matching tool result, the request failed — and kept failing on every later turn, leaving the thread unusable. Suspended tool calls are now paired with a placeholder result instead of being sent alone, so the agent can see its pending calls and the prompt stays valid: ```typescript const agent = new Agent({ name: 'approvals', model: 'openai/gpt-5-mini', memory, }); // Before: this turn failed with 'No tool output found for function call ...' // After: the agent answers and can see the pending approval await agent.stream('Do I have anything waiting on my approval?', { memory: { thread: 'thread-1', resource: 'user-1', options: { filterIncompleteToolCalls: false }, }, }); ``` The default (`true`) is unchanged — suspended calls are still dropped from the prompt. - Fixed SignalProvider webhook documentation to explain how applications should expose verified HTTP endpoints. ([#20749](https://github.com/mastra-ai/mastra/pull/20749)) - Fixed agent model-call retries ignoring the provider's `Retry-After` response header. ([#19906](https://github.com/mastra-ai/mastra/pull/19906)) An agent that retries a failed model call (via `maxRetries`) backed off on a fixed exponential schedule. A provider replying `429` with `Retry-After: 30` was retried after 1s, 2s and 4s, so every attempt landed inside the window the provider was still throttling. Retries now wait for the delay the provider asks for, reading either `Retry-After` or `Retry-After-Ms`. Waits are limited to 30 seconds, so an unusually large `Retry-After` cannot stall a run. When a provider sends no retry delay, the existing exponential backoff is unchanged. Fixes #19885 - Fixed the in-memory storage fallback warning when file-based storage is registered during startup. ([#20696](https://github.com/mastra-ai/mastra/pull/20696)) - Prevented tool results and durable request-context snapshots from hanging the event loop when they contain deeply shared object graphs. Serialization now uses a bounded check, so an acyclic value with layered shared references (which `JSON.stringify` would expand exponentially) is handled in milliseconds instead of blocking for minutes. Over-budget tool results are still returned — repeated references are collapsed to `[Circular]` — rather than dropped. ([#20727](https://github.com/mastra-ai/mastra/pull/20727)) - Exempt memory-sourced messages from the resourceId guard in inputToMastraDBMessage, matching the existing threadId exemption. Memory messages can carry a system resourceId (e.g. observational-memory continuation messages arrive with the observer's resourceId), and the mismatch previously threw inside input processing and hard-aborted the turn. ([#19153](https://github.com/mastra-ai/mastra/pull/19153)) - Fixed how tool errors are stored in message history. When a tool throws (or a background task fails), the tool call is now recorded with an error state and its message in an `errorText` field, instead of being stored as a successful result. This keeps failed tool calls distinguishable from real results when messages are recalled or replayed to the model. ([#20705](https://github.com/mastra-ai/mastra/pull/20705)) - Fixed non-durable tool-call suspensions writing messages when memory is read-only. ([#20346](https://github.com/mastra-ai/mastra/pull/20346)) - Fixed OpenAI Files API file IDs (e.g. `file-abc123`) being corrupted into invalid base64 data URIs, which caused `MastraError: Failed to download asset`. File IDs are now classified as provider file references and passed through untouched on every conversion path — v5 UI messages, v4 attachments, and v1 prompt messages — so `@ai-sdk/openai` can forward them as `{ file_id: "file-..." }` to the API. ([#16448](https://github.com/mastra-ai/mastra/pull/16448)) - Improved observability traces: RequestContext objects and arrays now preserve their nested structure instead of appearing as `[object]`. ([#20520](https://github.com/mastra-ai/mastra/pull/20520)) - Fixed Google provider-executed tool results (e.g. `file_search`) being dropped when the tool ran alongside another tool. Some providers assign the tool result a different `toolCallId` than the original tool call, so the result never merged into the stored call and the next request to the model failed with a "Corrupted tool call context" error. The tool call now matches by tool name as a fallback when the id differs, so the result is recorded correctly. ([#18604](https://github.com/mastra-ai/mastra/pull/18604)) - Fixed `RequestContext.toJSON()` so nested contexts reached through shared-reference graphs no longer block the event loop. The serialization safety budget is now shared across nested probes within one serialization, so such values are handled in bounded time — and filtered when they exceed the budget — instead of blocking for seconds. ([#20730](https://github.com/mastra-ai/mastra/pull/20730)) - Fixed durable agents dropping tool results when a tool's `toModelOutput` returns `undefined`. ([#20404](https://github.com/mastra-ai/mastra/pull/20404)) Tools that only map some of their results — including the built-in workspace `read_file` tool and the sandbox tools — return `undefined` from `toModelOutput` to mean "send the raw result as-is". Durable runs stored that `undefined` as the tool's model output, so the next request to the provider carried a tool message with no `output` field and the run failed with `Cannot read properties of undefined (reading 'type')`. Any multi-step durable task that read a file hit this. Regular agents were never affected. - Fixed AI SDK v5 streams so provider metadata is preserved when a text delta is empty. This keeps Google Gemini thought signatures and other provider continuity metadata available to downstream consumers. Empty deltas without provider metadata remain omitted. Relates to https://github.com/mastra-ai/mastra/issues/20469 ([#20488](https://github.com/mastra-ai/mastra/pull/20488)) - Updated dependencies [[`14562d6`](https://github.com/mastra-ai/mastra/commit/14562d6ea724ed4ccb9fb079d016ec7ab1bd92a4)]: - @mastra/schema-compat@1.3.5 ## 1.57.0-alpha.2 ### Patch Changes - Fixed boot-time recovery so nested workflows that already finished inside a parent `.parallel()` are reused instead of restarted. Parent `activeStepsPath` can still list completed children after a crash; restarting those terminal snapshots no longer throws "This workflow run was not active". As part of this, calling `restart()` on a run whose snapshot is already `success`, `failed`, or `tripwire` now returns the stored result without re-executing any steps (it previously threw). Fixes https://github.com/mastra-ai/mastra/issues/20225 ([#20518](https://github.com/mastra-ai/mastra/pull/20518)) - Fixed `DurableAgent` still writing messages to the thread during tool-call suspension (approval / in-execution suspend) and background-task completion when `memory.options.readOnly` was set. Follow-up to the readOnly fix for the durable finish path (#18921) — these mid-run flush paths in `steps/tool-call.ts` had the same missing guard. ([#18856](https://github.com/mastra-ai/mastra/pull/18856)) - Fixed durable agents losing a pending approval on page refresh. The suspended tool's metadata is now persisted to the assistant message, so a reloading client re-renders the approval instead of showing none while the run sits parked and resumable. ([#19713](https://github.com/mastra-ai/mastra/pull/19713)) This applies whenever the agentic loop executes in a different process than the one that called `stream()` — for example the `@mastra/inngest` `connect()` worker topology. - Fixed agent model-call retries ignoring the provider's `Retry-After` response header. ([#19906](https://github.com/mastra-ai/mastra/pull/19906)) An agent that retries a failed model call (via `maxRetries`) backed off on a fixed exponential schedule. A provider replying `429` with `Retry-After: 30` was retried after 1s, 2s and 4s, so every attempt landed inside the window the provider was still throttling. Retries now wait for the delay the provider asks for, reading either `Retry-After` or `Retry-After-Ms`. Waits are limited to 30 seconds, so an unusually large `Retry-After` cannot stall a run. When a provider sends no retry delay, the existing exponential backoff is unchanged. Fixes #19885 - Exempt memory-sourced messages from the resourceId guard in inputToMastraDBMessage, matching the existing threadId exemption. Memory messages can carry a system resourceId (e.g. observational-memory continuation messages arrive with the observer's resourceId), and the mismatch previously threw inside input processing and hard-aborted the turn. ([#19153](https://github.com/mastra-ai/mastra/pull/19153)) - Fixed non-durable tool-call suspensions writing messages when memory is read-only. ([#20346](https://github.com/mastra-ai/mastra/pull/20346)) - Fixed OpenAI Files API file IDs (e.g. `file-abc123`) being corrupted into invalid base64 data URIs, which caused `MastraError: Failed to download asset`. File IDs are now classified as provider file references and passed through untouched on every conversion path — v5 UI messages, v4 attachments, and v1 prompt messages — so `@ai-sdk/openai` can forward them as `{ file_id: "file-..." }` to the API. ([#16448](https://github.com/mastra-ai/mastra/pull/16448)) - Fixed `RequestContext.toJSON()` so nested contexts reached through shared-reference graphs no longer block the event loop. The serialization safety budget is now shared across nested probes within one serialization, so such values are handled in bounded time — and filtered when they exceed the budget — instead of blocking for seconds. ([#20730](https://github.com/mastra-ai/mastra/pull/20730)) - Fixed AI SDK v5 streams so provider metadata is preserved when a text delta is empty. This keeps Google Gemini thought signatures and other provider continuity metadata available to downstream consumers. Empty deltas without provider metadata remain omitted. Relates to https://github.com/mastra-ai/mastra/issues/20469 ([#20488](https://github.com/mastra-ai/mastra/pull/20488)) ## 1.57.0-alpha.1 ### Minor Changes - Added tool definitions to MODEL_GENERATION span attributes. The tools made available to the model (name, description, and JSON-schema parameters) are now captured once per generation as the `tools` attribute, so observability exporters can surface which tool schemas the model ran with. Per-step tool names (after `activeTools` filtering) remain on MODEL_INFERENCE spans as `availableTools`. Related: #20242 ([#20243](https://github.com/mastra-ai/mastra/pull/20243)) ```typescript // Any exporter reading MODEL_GENERATION spans now receives: span.attributes.tools; // [ // { // type: 'function', // name: 'get_weather', // description: 'Get the weather for a city', // parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] }, // }, // ] ``` ### Patch Changes - Fixed a memory leak where suspended agent runs were never released from memory. Every suspend kept its full in-memory transcript retained for the life of the process, so long-running servers with many suspend/resume cycles grew unbounded and could eventually exhaust the heap. ([#20273](https://github.com/mastra-ai/mastra/pull/20273)) Suspended runs are now kept warm only for a bounded window (30 minutes by default) and then evicted. A same-instance resume within that window still reattaches to the warm state exactly as before; a resume after the TTL expires falls back to the durable snapshot. Set `MASTRA_SUSPENDED_RUN_TTL_MS` (in milliseconds) to tune the window — lower it on multi-instance deployments where a resume rarely lands on the origin instance: ```sh # keep suspended runs warm for 10 minutes instead of the default 30 MASTRA_SUSPENDED_RUN_TTL_MS=600000 ``` - Fixed sessions opening a new empty thread on every start instead of resuming their conversation. This affected setups that give each session its own working directory: the conversation stayed in storage but the session never reopened it, leaving an unused thread behind each time. ([#20743](https://github.com/mastra-ai/mastra/pull/20743)) - Fixed invalid workflow input responses to return HTTP 400 instead of HTTP 500 while preserving schema validation details. ([#20722](https://github.com/mastra-ai/mastra/pull/20722)) - Fixed agent controller approvals losing the current caller identity while processing subscribed thread streams. ([#20741](https://github.com/mastra-ai/mastra/pull/20741)) - Fixed `filterIncompleteToolCalls: false` producing prompts that providers reject with a 400. ([#20636](https://github.com/mastra-ai/mastra/pull/20636)) A tool call that suspends for approval is stored without a result. With filtering disabled that call was sent to the provider unpaired, and since every provider requires a tool call to have a matching tool result, the request failed — and kept failing on every later turn, leaving the thread unusable. Suspended tool calls are now paired with a placeholder result instead of being sent alone, so the agent can see its pending calls and the prompt stays valid: ```typescript const agent = new Agent({ name: 'approvals', model: 'openai/gpt-5-mini', memory, }); // Before: this turn failed with 'No tool output found for function call ...' // After: the agent answers and can see the pending approval await agent.stream('Do I have anything waiting on my approval?', { memory: { thread: 'thread-1', resource: 'user-1', options: { filterIncompleteToolCalls: false }, }, }); ``` The default (`true`) is unchanged — suspended calls are still dropped from the prompt. - Fixed SignalProvider webhook documentation to explain how applications should expose verified HTTP endpoints. ([#20749](https://github.com/mastra-ai/mastra/pull/20749)) - Fixed the in-memory storage fallback warning when file-based storage is registered during startup. ([#20696](https://github.com/mastra-ai/mastra/pull/20696)) - Prevented tool results and durable request-context snapshots from hanging the event loop when they contain deeply shared object graphs. Serialization now uses a bounded check, so an acyclic value with layered shared references (which `JSON.stringify` would expand exponentially) is handled in milliseconds instead of blocking for minutes. Over-budget tool results are still returned — repeated references are collapsed to `[Circular]` — rather than dropped. ([#20727](https://github.com/mastra-ai/mastra/pull/20727)) - Fixed how tool errors are stored in message history. When a tool throws (or a background task fails), the tool call is now recorded with an error state and its message in an `errorText` field, instead of being stored as a successful result. This keeps failed tool calls distinguishable from real results when messages are recalled or replayed to the model. ([#20705](https://github.com/mastra-ai/mastra/pull/20705)) - Improved observability traces: RequestContext objects and arrays now preserve their nested structure instead of appearing as `[object]`. ([#20520](https://github.com/mastra-ai/mastra/pull/20520)) - Fixed durable agents dropping tool results when a tool's `toModelOutput` returns `undefined`. ([#20404](https://github.com/mastra-ai/mastra/pull/20404)) Tools that only map some of their results — including the built-in workspace `read_file` tool and the sandbox tools — return `undefined` from `toModelOutput` to mean "send the raw result as-is". Durable runs stored that `undefined` as the tool's model output, so the next request to the provider carried a tool message with no `output` field and the run failed with `Cannot read properties of undefined (reading 'type')`. Any multi-step durable task that read a file hit this. Regular agents were never affected. - Updated dependencies [[`14562d6`](https://github.com/mastra-ai/mastra/commit/14562d6ea724ed4ccb9fb079d016ec7ab1bd92a4)]: - @mastra/schema-compat@1.3.5-alpha.0 ## 1.56.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`c8002da`](https://github.com/mastra-ai/mastra/commit/c8002da7775c468e2965b6ff5f82045450fa8cb9)) ## 1.56.0 ### Minor Changes - Added item-level scorer selection for dataset experiments. ([#20190](https://github.com/mastra-ai/mastra/pull/20190)) Dataset items now accept `scorerIds`. Experiments select one scorer source in this order: explicitly provided run-level scorers, item scorer IDs, then dataset scorer IDs. Use `[]` at the run or item level to select no scorers. ```typescript await dataset.addItem({ input: 'Evaluate this response', scorerIds: ['accuracy'], }); await dataset.updateItem({ itemId: 'item-id', scorerIds: null, }); ``` Omit `scorerIds` to inherit or preserve the current override, use `[]` to run no scorers for an item, and update with `null` to restore dataset inheritance. Missing item-level scorer IDs fail only the affected item before target execution. Run-level scorers now replace dataset-attached defaults instead of merging with them. Previously, this configuration ran both `latency` and the dataset's `accuracy` scorer: ```typescript await dataset.startExperiment({ targetType: 'agent', targetId: 'support-agent', scorers: ['latency'], }); ``` It now runs only `latency`. To keep both scorers, provide both in the run-level list: ```typescript await dataset.startExperiment({ targetType: 'agent', targetId: 'support-agent', scorers: ['latency', 'accuracy'], }); ``` - Declarative, persistable workflow graphs. ([#20471](https://github.com/mastra-ai/mastra/pull/20471)) Workflows can now be authored as data — a UI, an LLM, or an operator can construct a workflow and have it survive process restarts. The step graph carries dedicated `agent` / `tool` / `mapping` entries (plus static `parallel` / `foreach` / `sleep` / `sleepUntil`) that round-trip through JSON, persist through the new stored-workflow endpoints, rehydrate, and run. **New declarative entry types in the step graph.** `.agent(agentOrId)`, `.tool(toolOrId)`, and `.map(...)` now emit dedicated `type: 'agent' | 'tool' | 'mapping'` entries into both `stepFlow` (live) and `serializedStepFlow` (JSON-safe), instead of collapsing into an opaque generic `step`. Existing `.then(createStep(agent))` / `.then(createStep(tool))` / `.map()` calls keep working and are auto-migrated to the new entries. `SingleStepEntry` (a new union of `step | agent | tool | mapping`) is now the shape used inside `parallel` and `conditional` `steps` arrays as well. Both engines interpret declarative entries per kind at the invoke point (via internal `step-entry` accessors and per-kind entry executors — `getEntryId` / `getEntryWorkflow` are exported for integrations) instead of materializing them into synthetic `Step` objects. The internal deep-import module `@mastra/core/dist/workflows/inner-step` (`getInnerStepId` / `materializeInnerStep`, never part of the public barrel) has been removed. **New builder ergonomics.** ```ts // Before: agents/tools were wrapped via createStep and lost their identity in the graph workflow.then(createStep(myAgent)).then(createStep(myTool)); // After: dedicated builders (createStep still works) workflow .agent(myAgent) // output inferred as { text: string } .agent(myAgent, { structuredOutput: { schema } }) // output inferred from the schema .tool(myTool) // output inferred from the tool's outputSchema .agent(myAgent, undefined, { id: 'reviewer' }) // reuse the same agent under a distinct step id .agent('my-registered-agent-id'); // resolved against the Mastra instance at run time ``` `.tool()` and `.agent()` enforce input/output schema chaining the same way `.then()` does — mismatched chains are compile-time errors. Agent steps type their input as `{ prompt: string }`. **New workflow-definitions storage domain.** `WorkflowDefinitionsStorage` (`upsert` / `get` / `list` / `delete` on JSON-safe `WorkflowDefinition`s) plus `Mastra.addStoredWorkflow(definition)` for persisting and live-registering a workflow. An in-memory implementation ships in core; database-backed stores provide their own implementations of the same domain interface. **New (de)serialization helpers.** `toStorableGraph(stepFlow)` turns a live workflow into a JSON-safe graph; `rehydrateWorkflow(def, mastra, opts?)` reconstructs the live workflow (including top-level workflow `metadata`). Referenced agents/tools must be registered on the target `Mastra` at rehydration time — otherwise rehydration hard-crashes rather than silently dropping. **Two-sided contract for unsupported JSON Schema keywords.** The MVP `jsonSchemaToZod` doesn't support `oneOf` / `anyOf` / `allOf` / `not` / `$ref` / `patternProperties` / `discriminator` (or unknown `type`s): - **Save path** (`Mastra.addStoredWorkflow`) is strict: the author is right there, so it throws before touching storage or registry, naming the offending schema (`inputSchema`, `outputSchema`, `stateSchema`, `requestContextSchema`, or `step "" outputSchema` reached through `parallel` / `foreach` / `conditional` / `loop`). Simplify the schema or extend the converter before saving. - **Load path** (boot-time `#loadStoredWorkflows`) is lenient: `jsonSchemaToZod` accepts an `{ onUnsupportedSchema: 'warn', onUnsupported }` option that degrades the unsupported subtree to `z.any()` and emits a warning through the Mastra logger. One bad pre-existing row (e.g. a definition written by an older version) can't take down startup for every other workflow. **Agent-step `structuredOutput` and JSON-safe options now round-trip.** The serialized `agent` entry carries an `outputSchema` field (JSON Schema Draft 2020-12) and rehydration reconstructs the equivalent `structuredOutput` wiring. `retries` and `metadata` round-trip on both `agent` and `tool` entries. Closure-valued options (`onFinish`, `onChunk`, `onError`, `onStepFinish`, `onAbort`, function-valued `scorers` / `toolChoice`) hard-crash at `toStorableGraph` time instead of silently dropping. This is what makes patterns like `tool → agent-with-array-outputSchema → foreach(agent)` persistable end-to-end. **`foreach` / `dowhile` / `dountil` inner steps are now `SingleStepEntry`.** Both at the live `stepFlow` level and, for `foreach`, in the serialized graph. Fixes the previous round-trip bug where an agent-bodied `foreach` was persisted as an id-only descriptor and rehydrated as the wrong kind of step (looked up in the tool registry). `foreach.step` preserves the stored step id (which can differ from the underlying agent/tool id), and the agent/tool `outputSchema` + JSON-safe options round-trip through the foreach body. `loop.step` is typed as `SerializedSingleStepEntry` in the serialized graph as well, matching `foreach` and matching the shape the builder actually emits. Mapping entries are rejected inside `foreach` / `parallel` at serialize and rehydrate time — mappings project data, they don't execute per item. **Mapping templates now accept `${stepResults.}` with no subpath, and stringify objects/arrays as JSON.** Primitive step outputs render via `String(v)`; object and array outputs render via `JSON.stringify` and are inlined into the template. This makes `foreach(agent) → mapping → synthesis-agent` work naturally — the mapping hands the full `{ text: string }[]` output to a downstream agent as one JSON blob, instead of forcing callers to fake indexed access (`${stepResults..0.text}`, `.1.text`, …) up to a fixed slot count. A step whose whole result is nullish is reported as missing (the template throws with the offending placeholder); nullish values resolved from a subpath inside a present result render as empty strings. Unrepresentable values (circular references, `BigInt`) throw with a hint pointing at the placeholder. **Workflow streams now publish the final workflow result before closing.** Successful runs include the canonical result on the closing `workflow-finish` chunk (`payload.finalWorkflowResult`), so stream-only consumers can read the result without a race-prone second fetch. Non-success and tripwire payloads are unchanged. **New `Mastra.removeWorkflow(keyOrId)` public API** mirroring `removeAgent` / `removeTool`. `Mastra.addStoredWorkflow(def)` now unregisters any existing live workflow with the same id before rehydrating and re-registering, so re-saving a stored workflow surfaces the new graph immediately instead of being silently no-op'd by `addWorkflow`'s first-write-wins guard. Fixes the stale-workflow bug where `deleteWorkflow` + `addStoredWorkflow` served the previous graph until the process restarted. **`Mastra.addStoredWorkflow` now performs a registry pre-flight before rehydrating.** Every `agentId` in the graph must resolve via `listAgents()` (and must not collide with a tool id), and every `toolId` must resolve via `listTools()` (and must not collide with an agent id). Previously, invalid or mis-classified ids failed deep inside `rehydrateWorkflow` with a less-actionable error (`Tool with name X not found`, or a silent lookup of an agent id in the tool registry). HTTP callers and direct `addStoredWorkflow` consumers now share one contract with the same actionable error messages. **One validation domain for stored workflow definitions.** All stored-definition checks now live in `@mastra/core`'s internal `workflows/stored/validate/` modules as a single issue-collecting core (`validateStoredWorkflow(def, registryIndex) → { code, path, message }[]`, plus a throwing `assertValidStoredWorkflow` used by the save path). Structure rules (ids, duplicates, mapping placement, nested-workflow identity, self-reference, declarative-predicate arity), reference checks (with mis-classification swap hints; mappings and declarative predicates may reference any runtime-visible step — including steps inside `parallel` / `conditional` / `foreach` / `loop` containers — and references to unknown steps are rejected before publication), JSON-Schema keyword checks, and the schema-flow type-checker (each step's input checked against the preceding output, foreach item schemas, loop feedback, final output vs. `outputSchema`) all run from the same walker over the serialized graph union. The builder authoring types (`WorkflowBuilderGraphEntry` and friends) from `@mastra/core/workflows/builder` are derived from `SerializedStepFlowEntry` instead of hand-duplicated, so the two can no longer drift. **Behavior change:** `Mastra.addStoredWorkflow` (and therefore `POST /stored/workflows`) now runs the schema-flow analysis with the live registries' schemas at save time — schema-incompatible graphs that previously saved silently are rejected with `incompatible-schema` issues. Boot-time loading of existing stored rows stays lenient. **New declarative predicate DSL for `.branch()` / `.dowhile()` / `.dountil()`.** Conditional branches and loop conditions can now be authored as a small structural JSON expression instead of (or alongside) a JS closure — the shape that finally lets `conditional` and `loop` step entries round-trip through storage. Nothing about existing closure-based conditions changes: the previous `(ctx) => boolean` overloads still work, still evaluate exactly the way they did, and their `serializedCondition.fn` string is still serialized unchanged. Opt in by passing `{ predicate }` in place of the closure: ```ts import type { Predicate } from '@mastra/core/workflows'; workflow .then(loadUser) .branch([ [{ predicate: { op: 'eq', left: { path: 'inputData.role' }, right: 'admin' } }, adminStep], [{ predicate: { op: 'truthy', value: { path: 'inputData.isGuest' } } }, guestStep], ]) .commit(); workflow .then(tick) .dountil(tick, { predicate: { op: 'gte', left: { path: 'inputData.count' }, right: 3 } }) .commit(); ``` The DSL supports `eq` / `ne` / `lt` / `lte` / `gt` / `gte` / `in` / `notIn` / `exists` / `notExists` / `truthy` / `falsy` and the logical combinators `and` / `or` / `not`. Values are either literals or `{ path: '....' }` references — `inputData.*` for the previous step's output, `initData.*` for the workflow's initial input, `stepResults.[.]` for a named earlier step's output (scalar step results resolve with no subpath), and `state.*` for the workflow state slot. Missing paths resolve to `undefined` rather than throwing, so `exists` / `notExists` do what you'd expect. `evaluatePredicate(predicate, context)` and `derivePredicateLabel(predicate)` are exported from `@mastra/core/workflows` for callers that want to reuse the evaluator or render the human-readable summary. The declarative form is what unlocks persistence for `conditional` and `loop` step entries: their serialized shape now carries a `predicates: Predicate[]` (conditional) / `predicate: Predicate` (loop) field that survives `toStorableGraph` and `rehydrateWorkflow`. Closure-only `.branch()` / `.dowhile()` / `.dountil()` calls remain live-only and continue to throw at `toStorableGraph` time with a message pointing at the predicate DSL. Stored `conditional` / `loop` entries also carry the derived human-readable condition labels (`serializedConditions` / `serializedCondition`) generated from the predicate, so UIs render the same labels for stored and code-authored workflows. Rehydrated `parallel` / `conditional` inner agent steps now preserve `outputSchema` (structured output), `retries`, and `metadata` — previously these were silently dropped on load — and serialize → rehydrate → serialize is idempotent. **Nested workflows as a first-class serialized step type.** `SerializedSingleStepEntry` and `SerializedStepFlowEntry` gain a new `{ type: 'workflow', id, workflowId, description? }` variant. Any `.then(subWorkflow)` (or nesting inside `parallel` / `conditional` / `foreach` / `dowhile` / `dountil`) now serializes to this variant instead of a generic `type: 'step'` entry, and stored (JSON) workflows can reference other registered workflows by id. The live `stepFlow` is unchanged — `SingleStepEntry` / `StepFlowEntry` still use `type: 'step'` for nested workflows at runtime, so all existing engine code, `component === 'WORKFLOW'` checks, and execution paths continue to work. Rehydration resolves `workflowId` against `mastra.listWorkflows()` and hard-crashes with an actionable error if the reference is missing. `Mastra.addStoredWorkflow`'s pre-flight `collectRefs` and boot-time `#loadStoredWorkflows` both understand the new variant. Cross-workflow references between stored workflows are supported and load-ordered via a two-pass topological sort with Kahn's algorithm; cycles (including self-reference) are detected and rejected with a "detected cycle: A → B → A" error rather than infinite-looping the rehydrator. This is what makes patterns like `parent-workflow → conditional → { child-workflow-A, child-workflow-B }` seedable end-to-end from JSON. **Backward compatibility.** Existing `.then(createStep(agent))`, `.then(createStep(tool))`, `.map()`, `.parallel()`, and `.branch()` usages keep working and now emit the new declarative entries automatically. Closure-based `.branch()` / `.dowhile()` / `.dountil()` continue to evaluate exactly as before. Adopt the declarative predicate form only if you want the condition to survive `toStorableGraph` / `rehydrateWorkflow`. - Added batch trace ID filtering to observability metric queries. ([#20535](https://github.com/mastra-ai/mastra/pull/20535)) ```ts const result = await observability.getMetricBreakdown({ name: ['mastra_model_total_input_tokens'], aggregation: 'sum', groupBy: ['traceId'], filters: { traceIds: ['trace-1', 'trace-2'] }, }); ``` - `RegexFilterProcessor` now reports what the `redact` strategy rewrote, through the existing `Processor.onViolation` callback. ([#20445](https://github.com/mastra-ai/mastra/pull/20445)) Redaction used to be silent. The processor found its matches, replaced the text, and dropped the match list, leaving nothing downstream to audit. It now reports once per redacted message, message part, or stream chunk, with offsets relative to that piece of text. ```ts const filter = new RegexFilterProcessor({ presets: ['pii'], strategy: 'redact', }); filter.onViolation = async ({ detail }) => { const redaction = detail as RegexRedactionDetail; for (const entry of redaction.redactions) { await auditLog.write({ phase: redaction.phase, // processInput | processOutputStream | processOutputResult messageId: redaction.messageId, rule: entry.rule, // 'credit-card' offset: entry.index, length: entry.length, }); } }; ``` Async callbacks are awaited. When no callback is attached the redact path stays synchronous, so nothing changes for existing callers. When two rules match the same span, the winning rule is reported as `rule` and every rule that matched is listed in `overlappingRules`. **Values are withheld by default** Reports carry offsets and rule names, not the matched text. An audit trail that copies the data it protects widens the exposure it was added to narrow, which is why the `block` strategy already withholds matched text from its `TripWire` metadata. Set `includeRedactedValues: true` to add a `value` field when the destination is as protected as the original. - Added per-run controls for experiment and score persistence. Targets and scorers continue to run, while callers can keep results in memory without writing selected record types to storage. ([#20643](https://github.com/mastra-ai/mastra/pull/20643)) ```typescript const summary = await dataset.startExperiment({ targetType: 'agent', targetId: 'my-agent', scorers: ['accuracy'], persistence: { experiments: 'none', scores: 'none' }, }); ``` - Added scorer failure results so callers can inspect completed stages and `status: 'failed'` judge executions in `error.result.judge` after `scorer.run()` rejects. ([#20195](https://github.com/mastra-ai/mastra/pull/20195)) ```typescript import { ScorerRunError } from '@mastra/core/evals'; try { await scorer.run(input); } catch (error) { if (error instanceof ScorerRunError) { console.log(error.failedStep); console.log(error.completedSteps); console.log(error.result); } } ``` - Added `unmockedToolPolicy` to experiments and dataset items so undeclared agent tool calls can be blocked before execution. ([#19643](https://github.com/mastra-ai/mastra/pull/19643)) ```typescript await dataset.startExperiment({ targetType: 'agent', targetId: 'weather-agent', unmockedToolPolicy: 'deny', }); ``` - Added an awaited semantic event observer for experiments. ([#20644](https://github.com/mastra-ai/mastra/pull/20644)) Use `onEvent` to consume versioned, JSON-safe run and item lifecycle events in strict sequence order: ```ts await runExperiment(mastra, { task: async ({ input }) => processItem(input), data: items, onEvent: async event => { await publish(event); }, }); ``` Terminal events are delivered before final experiment status persistence, allowing external workers to treat the ordered event stream as authoritative. Observer failures stop the run with an `EXPERIMENT_EVENT_OBSERVER_FAILED` error so workers can distinguish delivery failures from partial experiment results. - Add optional `idleTimeoutMs` and `isAlive` options to `DurableAgent.observe()`. ([#20442](https://github.com/mastra-ai/mastra/pull/20442)) When the process running a durable agent stops unexpectedly, the run stops producing updates but never emits a completion event — so a client that reconnects with `observe()` previously waited forever, with no way to tell the run was gone. With `idleTimeoutMs` set, the observed stream ends after that many milliseconds of silence. An optional `isAlive` check is consulted first: if it reports the run is still being worked on (for example a long-running tool call, or a run paused waiting for human input), the stream keeps waiting instead of ending. Fully backward-compatible — with neither option set, `observe()` behaves exactly as before. ```ts // Reconnect to an in-flight run, but stop waiting if the run is no longer running. const { output } = await agent.observe(runId, { idleTimeoutMs: 30_000, // Consulted only after idleTimeoutMs of silence. Return true while the run is // still being worked on to keep waiting; false (or omitted) ends the stream. isAlive: () => runHeartbeat.isFresh(runId), }); // Omit both options for the previous behavior (wait indefinitely): const { output: legacy } = await agent.observe(runId); ``` ### Patch Changes - dependencies updates: ([#20501](https://github.com/mastra-ai/mastra/pull/20501)) - Updated dependency [`@isaacs/ttlcache@^2.1.5` ↗︎](https://www.npmjs.com/package/@isaacs/ttlcache/v/2.1.5) (from `^2.1.4`, in `dependencies`) - Fixed review comments on experiment results not being saved. Experiment results now have a persisted comment field, and updateExperimentResult accepts a comment alongside status and tags. Fixes https://github.com/mastra-ai/mastra/issues/19857 ([#19865](https://github.com/mastra-ai/mastra/pull/19865)) ```ts const experimentsStore = await storage.getStore('experiments'); await experimentsStore.updateExperimentResult({ id: resultId, experimentId, comment: 'Agent hallucinated an API that does not exist', }); ``` - Fixed conversations becoming permanently stuck after approving or declining a `requireApproval` tool call with Anthropic extended thinking enabled. Resuming now saves the model's continuation in a new assistant message, so the paused response stays intact and later turns keep working. ([#19486](https://github.com/mastra-ai/mastra/pull/19486)) - Update provider registry and model documentation with latest models and providers ([`7f4e26d`](https://github.com/mastra-ai/mastra/commit/7f4e26dd57bd9b23c278ea21235ab823a3810a6c)) - Fixed dropped non-text stream parts when `emitOnNonText: false`. ([#18561](https://github.com/mastra-ai/mastra/pull/18561)) Tool calls, tool results, objects, and reasoning parts now stay in order with the text output instead of being silently lost during batching. - Fixed a crash in tool input validation when using Zod v4 compatibility schemas that don't provide a native JSON Schema. ([#19030](https://github.com/mastra-ai/mastra/pull/19030)) - Fixed TokenLimiter silently dropping agent output when used as an output processor. It counted every stream part — including lifecycle parts like `step-start` (which embeds the full serialized request) and reasoning deltas — so a realistic limit could be exhausted before any answer text arrived. Once the limit was hit, all later parts were withheld, including `tool-call` parts, so agents returned empty text or stopped executing tools with no error. ([#20256](https://github.com/mastra-ai/mastra/pull/20256)) Now only generated output counts against the limit (text and object parts), tool and lifecycle parts always pass through, and the first time output is withheld the processor emits a transient `data-token-limit-reached` part so truncation is visible: ```typescript for await (const part of stream.fullStream) { if (part.type === 'data-token-limit-reached') { console.log('output truncated at', part.data.limit, 'tokens'); } } ``` Fixes #20250 - Fixed delegated tool approvals not resuming after a page refresh or server restart. Approvals saved in conversation metadata previously pointed at a sub-agent run that could not be resumed. They now point at the supervisor run, so the saved approval works directly with `resumeStream()` and `approveToolCall()`. Approvals saved before this fix keep working. ([#19645](https://github.com/mastra-ai/mastra/pull/19645)) - Added an optional `releaseSpan` method to the observability bridge interface. ([#20463](https://github.com/mastra-ai/mastra/pull/20463)) Bridges hold per-span state from `createSpan()` until the span ends, but span-end events are only delivered for spans that survive export filtering, so a bridge had no way to learn that a filtered span had finished. `releaseSpan(spanId, traceId)` is now called for those spans. If you maintain a custom bridge, implement it to drop whatever `createSpan()` allocated. Do not end or send the span — it was filtered out on purpose. ```typescript class MyBridge implements ObservabilityBridge { private spans = new Map(); createSpan(options) { const span = myTracer.start(options.name); this.spans.set(span.id, span); return { spanId: span.id, traceId: span.traceId }; } // Called when a span ends but is dropped by excludeSpanTypes, // a spanFilter, or a span output processor. releaseSpan(spanId: string, _traceId: string) { this.spans.delete(spanId); } } ``` The method is optional, so bridges that omit it keep working unchanged. Fixes [#20368](https://github.com/mastra-ai/mastra/issues/20368). - Use the configured ID generator for message rows persisted through agent signal APIs. ([#17857](https://github.com/mastra-ai/mastra/pull/17857)) - Fixed an issue in the agentic loop where an aborted or failed LLM stream would still trigger output processors and improperly persist the user's input message as an orphaned record. ([#19716](https://github.com/mastra-ai/mastra/pull/19716)) - Fixed durable agents losing track of parallel sub-agent approvals. When a durable supervisor delegated to multiple sub-agents in parallel and more than one required approval, only the first approval was persisted — the rest disappeared from the conversation metadata. Also fixed listSuspendedRuns() never returning suspended durable agent runs. Both parallel approvals now persist, the suspended run is discoverable, and the approvals can be resumed in any order. ([#20529](https://github.com/mastra-ai/mastra/pull/20529)) - Fixed sub-agent delegation prompts being ordered before forwarded supervisor context. ([#20535](https://github.com/mastra-ai/mastra/pull/20535)) - Fixed a bug where disconnecting one consumer of a workflow's `fullStream` (or a model's evented stream) would silently stop every other concurrent consumer of the same run from receiving further chunks. This affected cases like two `/stream` requests for the same `runId`, or `/stream` combined with `/observe` — one client disconnecting no longer breaks the others, which now keep receiving chunks and close normally. ([#19745](https://github.com/mastra-ai/mastra/pull/19745)) - Fixed tool executions silently losing request context when a bundler or monorepo loads more than one copy of @mastra/core. Previously, a request context created by a different copy of the package was not recognized, so the tool received an empty context or the entries passed at execution time were dropped from the merge. Request context values now reach the tool regardless of which copy of the package created them. Closes #19772. ([#19863](https://github.com/mastra-ai/mastra/pull/19863)) - Added toolCallId to TOOL_CALL and MCP_TOOL_CALL span attributes so observability exporters can pair tool results with their calls. Previously the tool call ID was available at the call site but never forwarded to the span, causing downstream exporters to lose the association between a tool call and its result. ([#19405](https://github.com/mastra-ai/mastra/pull/19405)) - Fixed provider-executed tool spans appearing outside the model step in traces. `PROVIDER_TOOL_CALL` spans now nest under the model step that delivered the tool result — matching how regular tool calls are traced — with their start time backdated to the tool call, so tools like OpenAI-hosted web search show up in the right place in the timeline. Tool input is now also captured whenever the provider supplies arguments. Calls whose result never arrives stay anchored to the agent run span. Fixes #20335. ([#20522](https://github.com/mastra-ai/mastra/pull/20522)) - Fixed custom data chunks emitted by processors so they are saved with thread messages unless marked transient. ([#19375](https://github.com/mastra-ai/mastra/pull/19375)) - Fixed agent thread subscriptions so every instance in a multi-instance deployment sees the same conversation: ([#19806](https://github.com/mastra-ai/mastra/pull/19806)) - Subscribers on any instance now replay completed runs identically instead of diverging from the instance that ran them. - Reconnecting to a thread no longer wedges `agent.stream()` behind a stale run left by a crashed or finished process. - Aborting a thread now works from any instance — the request is routed to the process that owns the run. - A `stream()`/`generate()` call started while another instance is mid-run on the same thread now waits its turn instead of interleaving output. - Fixed `declineToolCall` and `declineToolCallGenerate` so declined approval-gated tools no longer execute or cause side effects after resume. Fixes https://github.com/mastra-ai/mastra/issues/20470 ([#20487](https://github.com/mastra-ai/mastra/pull/20487)) - Fixed durable delegated tool approvals so persisted approvals resume the suspended sub-agent, including after a server restart. ([#20492](https://github.com/mastra-ai/mastra/pull/20492)) - Fixed thread title generation using messages from other threads when memory is resource-scoped. Titles for new threads are now derived only from the messages of the thread being titled, instead of the full message list which can include recalled messages from the user's other conversations. ([#19856](https://github.com/mastra-ai/mastra/pull/19856)) - Added an optional `instructions` field to the Observational Memory `retrieval` config type. It appends application-specific recall guidance after Mastra's built-in retrieval instructions. ([#20160](https://github.com/mastra-ai/mastra/pull/20160)) - Fixed `RegexFilterProcessor` leaving part of a matched value in the output when two rules match overlapping text. ([#20445](https://github.com/mastra-ai/mastra/pull/20445)) **What was wrong** Rules were applied one at a time with `String.replace`, so an earlier rule could consume the start of a longer match and leave the rest in the clear. With the `pii` preset, `phone` runs before `credit-card`, so a card number written without separators lost only its first ten digits: ```ts const filter = new RegexFilterProcessor({ presets: ['pii'], strategy: 'redact' }); // Before: "card [PHONE]111111" // After: "card [CREDIT_CARD]" ``` Overlapping matches are now combined into one region and replaced once, using the replacement of the longest match. **Two smaller changes to redaction** - A replacement that references capture groups (`$1`, `$&`) now falls back to the replacement string as written when the rule cannot match the text it matched in isolation, which happens with a lookbehind or lookahead. The region is still redacted. - A rule that only matches empty strings no longer inserts its replacement between every character. - Fixed suspended DurableAgent tools to receive primitive resume data such as native ask_user answers. ([#19750](https://github.com/mastra-ai/mastra/pull/19750)) - Fixed `RequestContext.toJSON()` so deeply shared object graphs no longer block the event loop during serialization. Values that exceed the serialization safety limit are filtered instead. ([#20375](https://github.com/mastra-ai/mastra/pull/20375)) - Added a typed `model` override to `approveToolCall`, `declineToolCall`, `approveToolCallGenerate`, and `declineToolCallGenerate`. The resume path already honored `model` (via `resumeStream`/`resumeGenerate`), but the public approve/decline signatures omitted it, so agents whose model is resolved per run had no typed way to pick the model for the resumed segment without casting past the signature. ([#20212](https://github.com/mastra-ai/mastra/pull/20212)) ```ts // Now type-checks — previously required casting past the public type await agent.approveToolCall({ runId, model: myRunModel }); ``` - Fixed durable agents failing to resume delegated sub-agent or workflow tools that suspend mid-execution without an approval. The suspended inner run is now persisted with the tool call, so resumeStream() continues that run instead of restarting the delegate — including after a server restart. See #20496 ([#20502](https://github.com/mastra-ai/mastra/pull/20502)) ## 1.56.0-alpha.7 ### Minor Changes - Added an awaited semantic event observer for experiments. ([#20644](https://github.com/mastra-ai/mastra/pull/20644)) Use `onEvent` to consume versioned, JSON-safe run and item lifecycle events in strict sequence order: ```ts await runExperiment(mastra, { task: async ({ input }) => processItem(input), data: items, onEvent: async event => { await publish(event); }, }); ``` Terminal events are delivered before final experiment status persistence, allowing external workers to treat the ordered event stream as authoritative. Observer failures stop the run with an `EXPERIMENT_EVENT_OBSERVER_FAILED` error so workers can distinguish delivery failures from partial experiment results. ## 1.56.0-alpha.6 ### Minor Changes - Added batch trace ID filtering to observability metric queries. ([#20535](https://github.com/mastra-ai/mastra/pull/20535)) ```ts const result = await observability.getMetricBreakdown({ name: ['mastra_model_total_input_tokens'], aggregation: 'sum', groupBy: ['traceId'], filters: { traceIds: ['trace-1', 'trace-2'] }, }); ``` - Added per-run controls for experiment and score persistence. Targets and scorers continue to run, while callers can keep results in memory without writing selected record types to storage. ([#20643](https://github.com/mastra-ai/mastra/pull/20643)) ```typescript const summary = await dataset.startExperiment({ targetType: 'agent', targetId: 'my-agent', scorers: ['accuracy'], persistence: { experiments: 'none', scores: 'none' }, }); ``` - Added scorer failure results so callers can inspect completed stages and `status: 'failed'` judge executions in `error.result.judge` after `scorer.run()` rejects. ([#20195](https://github.com/mastra-ai/mastra/pull/20195)) ```typescript import { ScorerRunError } from '@mastra/core/evals'; try { await scorer.run(input); } catch (error) { if (error instanceof ScorerRunError) { console.log(error.failedStep); console.log(error.completedSteps); console.log(error.result); } } ``` ### Patch Changes - Fixed TokenLimiter silently dropping agent output when used as an output processor. It counted every stream part — including lifecycle parts like `step-start` (which embeds the full serialized request) and reasoning deltas — so a realistic limit could be exhausted before any answer text arrived. Once the limit was hit, all later parts were withheld, including `tool-call` parts, so agents returned empty text or stopped executing tools with no error. ([#20256](https://github.com/mastra-ai/mastra/pull/20256)) Now only generated output counts against the limit (text and object parts), tool and lifecycle parts always pass through, and the first time output is withheld the processor emits a transient `data-token-limit-reached` part so truncation is visible: ```typescript for await (const part of stream.fullStream) { if (part.type === 'data-token-limit-reached') { console.log('output truncated at', part.data.limit, 'tokens'); } } ``` Fixes #20250 - Fixed sub-agent delegation prompts being ordered before forwarded supervisor context. ([#20535](https://github.com/mastra-ai/mastra/pull/20535)) - Fixed provider-executed tool spans appearing outside the model step in traces. `PROVIDER_TOOL_CALL` spans now nest under the model step that delivered the tool result — matching how regular tool calls are traced — with their start time backdated to the tool call, so tools like OpenAI-hosted web search show up in the right place in the timeline. Tool input is now also captured whenever the provider supplies arguments. Calls whose result never arrives stay anchored to the agent run span. Fixes #20335. ([#20522](https://github.com/mastra-ai/mastra/pull/20522)) - Added an optional `instructions` field to the Observational Memory `retrieval` config type. It appends application-specific recall guidance after Mastra's built-in retrieval instructions. ([#20160](https://github.com/mastra-ai/mastra/pull/20160)) ## 1.56.0-alpha.5 ### Minor Changes - Added item-level scorer selection for dataset experiments. ([#20190](https://github.com/mastra-ai/mastra/pull/20190)) Dataset items now accept `scorerIds`. Experiments select one scorer source in this order: explicitly provided run-level scorers, item scorer IDs, then dataset scorer IDs. Use `[]` at the run or item level to select no scorers. ```typescript await dataset.addItem({ input: 'Evaluate this response', scorerIds: ['accuracy'], }); await dataset.updateItem({ itemId: 'item-id', scorerIds: null, }); ``` Omit `scorerIds` to inherit or preserve the current override, use `[]` to run no scorers for an item, and update with `null` to restore dataset inheritance. Missing item-level scorer IDs fail only the affected item before target execution. Run-level scorers now replace dataset-attached defaults instead of merging with them. Previously, this configuration ran both `latency` and the dataset's `accuracy` scorer: ```typescript await dataset.startExperiment({ targetType: 'agent', targetId: 'support-agent', scorers: ['latency'], }); ``` It now runs only `latency`. To keep both scorers, provide both in the run-level list: ```typescript await dataset.startExperiment({ targetType: 'agent', targetId: 'support-agent', scorers: ['latency', 'accuracy'], }); ``` ### Patch Changes - Use the configured ID generator for message rows persisted through agent signal APIs. ([#17857](https://github.com/mastra-ai/mastra/pull/17857)) ## 1.56.0-alpha.4 ### Minor Changes - Declarative, persistable workflow graphs. ([#20471](https://github.com/mastra-ai/mastra/pull/20471)) Workflows can now be authored as data — a UI, an LLM, or an operator can construct a workflow and have it survive process restarts. The step graph carries dedicated `agent` / `tool` / `mapping` entries (plus static `parallel` / `foreach` / `sleep` / `sleepUntil`) that round-trip through JSON, persist through the new stored-workflow endpoints, rehydrate, and run. **New declarative entry types in the step graph.** `.agent(agentOrId)`, `.tool(toolOrId)`, and `.map(...)` now emit dedicated `type: 'agent' | 'tool' | 'mapping'` entries into both `stepFlow` (live) and `serializedStepFlow` (JSON-safe), instead of collapsing into an opaque generic `step`. Existing `.then(createStep(agent))` / `.then(createStep(tool))` / `.map()` calls keep working and are auto-migrated to the new entries. `SingleStepEntry` (a new union of `step | agent | tool | mapping`) is now the shape used inside `parallel` and `conditional` `steps` arrays as well. Both engines interpret declarative entries per kind at the invoke point (via internal `step-entry` accessors and per-kind entry executors — `getEntryId` / `getEntryWorkflow` are exported for integrations) instead of materializing them into synthetic `Step` objects. The internal deep-import module `@mastra/core/dist/workflows/inner-step` (`getInnerStepId` / `materializeInnerStep`, never part of the public barrel) has been removed. **New builder ergonomics.** ```ts // Before: agents/tools were wrapped via createStep and lost their identity in the graph workflow.then(createStep(myAgent)).then(createStep(myTool)); // After: dedicated builders (createStep still works) workflow .agent(myAgent) // output inferred as { text: string } .agent(myAgent, { structuredOutput: { schema } }) // output inferred from the schema .tool(myTool) // output inferred from the tool's outputSchema .agent(myAgent, undefined, { id: 'reviewer' }) // reuse the same agent under a distinct step id .agent('my-registered-agent-id'); // resolved against the Mastra instance at run time ``` `.tool()` and `.agent()` enforce input/output schema chaining the same way `.then()` does — mismatched chains are compile-time errors. Agent steps type their input as `{ prompt: string }`. **New workflow-definitions storage domain.** `WorkflowDefinitionsStorage` (`upsert` / `get` / `list` / `delete` on JSON-safe `WorkflowDefinition`s) plus `Mastra.addStoredWorkflow(definition)` for persisting and live-registering a workflow. An in-memory implementation ships in core; database-backed stores provide their own implementations of the same domain interface. **New (de)serialization helpers.** `toStorableGraph(stepFlow)` turns a live workflow into a JSON-safe graph; `rehydrateWorkflow(def, mastra, opts?)` reconstructs the live workflow (including top-level workflow `metadata`). Referenced agents/tools must be registered on the target `Mastra` at rehydration time — otherwise rehydration hard-crashes rather than silently dropping. **Two-sided contract for unsupported JSON Schema keywords.** The MVP `jsonSchemaToZod` doesn't support `oneOf` / `anyOf` / `allOf` / `not` / `$ref` / `patternProperties` / `discriminator` (or unknown `type`s): - **Save path** (`Mastra.addStoredWorkflow`) is strict: the author is right there, so it throws before touching storage or registry, naming the offending schema (`inputSchema`, `outputSchema`, `stateSchema`, `requestContextSchema`, or `step "" outputSchema` reached through `parallel` / `foreach` / `conditional` / `loop`). Simplify the schema or extend the converter before saving. - **Load path** (boot-time `#loadStoredWorkflows`) is lenient: `jsonSchemaToZod` accepts an `{ onUnsupportedSchema: 'warn', onUnsupported }` option that degrades the unsupported subtree to `z.any()` and emits a warning through the Mastra logger. One bad pre-existing row (e.g. a definition written by an older version) can't take down startup for every other workflow. **Agent-step `structuredOutput` and JSON-safe options now round-trip.** The serialized `agent` entry carries an `outputSchema` field (JSON Schema Draft 2020-12) and rehydration reconstructs the equivalent `structuredOutput` wiring. `retries` and `metadata` round-trip on both `agent` and `tool` entries. Closure-valued options (`onFinish`, `onChunk`, `onError`, `onStepFinish`, `onAbort`, function-valued `scorers` / `toolChoice`) hard-crash at `toStorableGraph` time instead of silently dropping. This is what makes patterns like `tool → agent-with-array-outputSchema → foreach(agent)` persistable end-to-end. **`foreach` / `dowhile` / `dountil` inner steps are now `SingleStepEntry`.** Both at the live `stepFlow` level and, for `foreach`, in the serialized graph. Fixes the previous round-trip bug where an agent-bodied `foreach` was persisted as an id-only descriptor and rehydrated as the wrong kind of step (looked up in the tool registry). `foreach.step` preserves the stored step id (which can differ from the underlying agent/tool id), and the agent/tool `outputSchema` + JSON-safe options round-trip through the foreach body. `loop.step` is typed as `SerializedSingleStepEntry` in the serialized graph as well, matching `foreach` and matching the shape the builder actually emits. Mapping entries are rejected inside `foreach` / `parallel` at serialize and rehydrate time — mappings project data, they don't execute per item. **Mapping templates now accept `${stepResults.}` with no subpath, and stringify objects/arrays as JSON.** Primitive step outputs render via `String(v)`; object and array outputs render via `JSON.stringify` and are inlined into the template. This makes `foreach(agent) → mapping → synthesis-agent` work naturally — the mapping hands the full `{ text: string }[]` output to a downstream agent as one JSON blob, instead of forcing callers to fake indexed access (`${stepResults..0.text}`, `.1.text`, …) up to a fixed slot count. A step whose whole result is nullish is reported as missing (the template throws with the offending placeholder); nullish values resolved from a subpath inside a present result render as empty strings. Unrepresentable values (circular references, `BigInt`) throw with a hint pointing at the placeholder. **Workflow streams now publish the final workflow result before closing.** Successful runs include the canonical result on the closing `workflow-finish` chunk (`payload.finalWorkflowResult`), so stream-only consumers can read the result without a race-prone second fetch. Non-success and tripwire payloads are unchanged. **New `Mastra.removeWorkflow(keyOrId)` public API** mirroring `removeAgent` / `removeTool`. `Mastra.addStoredWorkflow(def)` now unregisters any existing live workflow with the same id before rehydrating and re-registering, so re-saving a stored workflow surfaces the new graph immediately instead of being silently no-op'd by `addWorkflow`'s first-write-wins guard. Fixes the stale-workflow bug where `deleteWorkflow` + `addStoredWorkflow` served the previous graph until the process restarted. **`Mastra.addStoredWorkflow` now performs a registry pre-flight before rehydrating.** Every `agentId` in the graph must resolve via `listAgents()` (and must not collide with a tool id), and every `toolId` must resolve via `listTools()` (and must not collide with an agent id). Previously, invalid or mis-classified ids failed deep inside `rehydrateWorkflow` with a less-actionable error (`Tool with name X not found`, or a silent lookup of an agent id in the tool registry). HTTP callers and direct `addStoredWorkflow` consumers now share one contract with the same actionable error messages. **One validation domain for stored workflow definitions.** All stored-definition checks now live in `@mastra/core`'s internal `workflows/stored/validate/` modules as a single issue-collecting core (`validateStoredWorkflow(def, registryIndex) → { code, path, message }[]`, plus a throwing `assertValidStoredWorkflow` used by the save path). Structure rules (ids, duplicates, mapping placement, nested-workflow identity, self-reference, declarative-predicate arity), reference checks (with mis-classification swap hints; mappings and declarative predicates may reference any runtime-visible step — including steps inside `parallel` / `conditional` / `foreach` / `loop` containers — and references to unknown steps are rejected before publication), JSON-Schema keyword checks, and the schema-flow type-checker (each step's input checked against the preceding output, foreach item schemas, loop feedback, final output vs. `outputSchema`) all run from the same walker over the serialized graph union. The builder authoring types (`WorkflowBuilderGraphEntry` and friends) from `@mastra/core/workflows/builder` are derived from `SerializedStepFlowEntry` instead of hand-duplicated, so the two can no longer drift. **Behavior change:** `Mastra.addStoredWorkflow` (and therefore `POST /stored/workflows`) now runs the schema-flow analysis with the live registries' schemas at save time — schema-incompatible graphs that previously saved silently are rejected with `incompatible-schema` issues. Boot-time loading of existing stored rows stays lenient. **New declarative predicate DSL for `.branch()` / `.dowhile()` / `.dountil()`.** Conditional branches and loop conditions can now be authored as a small structural JSON expression instead of (or alongside) a JS closure — the shape that finally lets `conditional` and `loop` step entries round-trip through storage. Nothing about existing closure-based conditions changes: the previous `(ctx) => boolean` overloads still work, still evaluate exactly the way they did, and their `serializedCondition.fn` string is still serialized unchanged. Opt in by passing `{ predicate }` in place of the closure: ```ts import type { Predicate } from '@mastra/core/workflows'; workflow .then(loadUser) .branch([ [{ predicate: { op: 'eq', left: { path: 'inputData.role' }, right: 'admin' } }, adminStep], [{ predicate: { op: 'truthy', value: { path: 'inputData.isGuest' } } }, guestStep], ]) .commit(); workflow .then(tick) .dountil(tick, { predicate: { op: 'gte', left: { path: 'inputData.count' }, right: 3 } }) .commit(); ``` The DSL supports `eq` / `ne` / `lt` / `lte` / `gt` / `gte` / `in` / `notIn` / `exists` / `notExists` / `truthy` / `falsy` and the logical combinators `and` / `or` / `not`. Values are either literals or `{ path: '....' }` references — `inputData.*` for the previous step's output, `initData.*` for the workflow's initial input, `stepResults.[.]` for a named earlier step's output (scalar step results resolve with no subpath), and `state.*` for the workflow state slot. Missing paths resolve to `undefined` rather than throwing, so `exists` / `notExists` do what you'd expect. `evaluatePredicate(predicate, context)` and `derivePredicateLabel(predicate)` are exported from `@mastra/core/workflows` for callers that want to reuse the evaluator or render the human-readable summary. The declarative form is what unlocks persistence for `conditional` and `loop` step entries: their serialized shape now carries a `predicates: Predicate[]` (conditional) / `predicate: Predicate` (loop) field that survives `toStorableGraph` and `rehydrateWorkflow`. Closure-only `.branch()` / `.dowhile()` / `.dountil()` calls remain live-only and continue to throw at `toStorableGraph` time with a message pointing at the predicate DSL. Stored `conditional` / `loop` entries also carry the derived human-readable condition labels (`serializedConditions` / `serializedCondition`) generated from the predicate, so UIs render the same labels for stored and code-authored workflows. Rehydrated `parallel` / `conditional` inner agent steps now preserve `outputSchema` (structured output), `retries`, and `metadata` — previously these were silently dropped on load — and serialize → rehydrate → serialize is idempotent. **Nested workflows as a first-class serialized step type.** `SerializedSingleStepEntry` and `SerializedStepFlowEntry` gain a new `{ type: 'workflow', id, workflowId, description? }` variant. Any `.then(subWorkflow)` (or nesting inside `parallel` / `conditional` / `foreach` / `dowhile` / `dountil`) now serializes to this variant instead of a generic `type: 'step'` entry, and stored (JSON) workflows can reference other registered workflows by id. The live `stepFlow` is unchanged — `SingleStepEntry` / `StepFlowEntry` still use `type: 'step'` for nested workflows at runtime, so all existing engine code, `component === 'WORKFLOW'` checks, and execution paths continue to work. Rehydration resolves `workflowId` against `mastra.listWorkflows()` and hard-crashes with an actionable error if the reference is missing. `Mastra.addStoredWorkflow`'s pre-flight `collectRefs` and boot-time `#loadStoredWorkflows` both understand the new variant. Cross-workflow references between stored workflows are supported and load-ordered via a two-pass topological sort with Kahn's algorithm; cycles (including self-reference) are detected and rejected with a "detected cycle: A → B → A" error rather than infinite-looping the rehydrator. This is what makes patterns like `parent-workflow → conditional → { child-workflow-A, child-workflow-B }` seedable end-to-end from JSON. **Backward compatibility.** Existing `.then(createStep(agent))`, `.then(createStep(tool))`, `.map()`, `.parallel()`, and `.branch()` usages keep working and now emit the new declarative entries automatically. Closure-based `.branch()` / `.dowhile()` / `.dountil()` continue to evaluate exactly as before. Adopt the declarative predicate form only if you want the condition to survive `toStorableGraph` / `rehydrateWorkflow`. ### Patch Changes - dependencies updates: ([#20501](https://github.com/mastra-ai/mastra/pull/20501)) - Updated dependency [`@isaacs/ttlcache@^2.1.5` ↗︎](https://www.npmjs.com/package/@isaacs/ttlcache/v/2.1.5) (from `^2.1.4`, in `dependencies`) - Fixed durable agents losing track of parallel sub-agent approvals. When a durable supervisor delegated to multiple sub-agents in parallel and more than one required approval, only the first approval was persisted — the rest disappeared from the conversation metadata. Also fixed listSuspendedRuns() never returning suspended durable agent runs. Both parallel approvals now persist, the suspended run is discoverable, and the approvals can be resumed in any order. ([#20529](https://github.com/mastra-ai/mastra/pull/20529)) - Fixed `RequestContext.toJSON()` so deeply shared object graphs no longer block the event loop during serialization. Values that exceed the serialization safety limit are filtered instead. ([#20375](https://github.com/mastra-ai/mastra/pull/20375)) - Added a typed `model` override to `approveToolCall`, `declineToolCall`, `approveToolCallGenerate`, and `declineToolCallGenerate`. The resume path already honored `model` (via `resumeStream`/`resumeGenerate`), but the public approve/decline signatures omitted it, so agents whose model is resolved per run had no typed way to pick the model for the resumed segment without casting past the signature. ([#20212](https://github.com/mastra-ai/mastra/pull/20212)) ```ts // Now type-checks — previously required casting past the public type await agent.approveToolCall({ runId, model: myRunModel }); ``` - Fixed durable agents failing to resume delegated sub-agent or workflow tools that suspend mid-execution without an approval. The suspended inner run is now persisted with the tool call, so resumeStream() continues that run instead of restarting the delegate — including after a server restart. See #20496 ([#20502](https://github.com/mastra-ai/mastra/pull/20502)) ## 1.56.0-alpha.3 ### Minor Changes - Add optional `idleTimeoutMs` and `isAlive` options to `DurableAgent.observe()`. ([#20442](https://github.com/mastra-ai/mastra/pull/20442)) When the process running a durable agent stops unexpectedly, the run stops producing updates but never emits a completion event — so a client that reconnects with `observe()` previously waited forever, with no way to tell the run was gone. With `idleTimeoutMs` set, the observed stream ends after that many milliseconds of silence. An optional `isAlive` check is consulted first: if it reports the run is still being worked on (for example a long-running tool call, or a run paused waiting for human input), the stream keeps waiting instead of ending. Fully backward-compatible — with neither option set, `observe()` behaves exactly as before. ```ts // Reconnect to an in-flight run, but stop waiting if the run is no longer running. const { output } = await agent.observe(runId, { idleTimeoutMs: 30_000, // Consulted only after idleTimeoutMs of silence. Return true while the run is // still being worked on to keep waiting; false (or omitted) ends the stream. isAlive: () => runHeartbeat.isFresh(runId), }); // Omit both options for the previous behavior (wait indefinitely): const { output: legacy } = await agent.observe(runId); ``` ### Patch Changes - Fixed conversations becoming permanently stuck after approving or declining a `requireApproval` tool call with Anthropic extended thinking enabled. Resuming now saves the model's continuation in a new assistant message, so the paused response stays intact and later turns keep working. ([#19486](https://github.com/mastra-ai/mastra/pull/19486)) - Fixed dropped non-text stream parts when `emitOnNonText: false`. ([#18561](https://github.com/mastra-ai/mastra/pull/18561)) Tool calls, tool results, objects, and reasoning parts now stay in order with the text output instead of being silently lost during batching. - Added an optional `releaseSpan` method to the observability bridge interface. ([#20463](https://github.com/mastra-ai/mastra/pull/20463)) Bridges hold per-span state from `createSpan()` until the span ends, but span-end events are only delivered for spans that survive export filtering, so a bridge had no way to learn that a filtered span had finished. `releaseSpan(spanId, traceId)` is now called for those spans. If you maintain a custom bridge, implement it to drop whatever `createSpan()` allocated. Do not end or send the span — it was filtered out on purpose. ```typescript class MyBridge implements ObservabilityBridge { private spans = new Map(); createSpan(options) { const span = myTracer.start(options.name); this.spans.set(span.id, span); return { spanId: span.id, traceId: span.traceId }; } // Called when a span ends but is dropped by excludeSpanTypes, // a spanFilter, or a span output processor. releaseSpan(spanId: string, _traceId: string) { this.spans.delete(spanId); } } ``` The method is optional, so bridges that omit it keep working unchanged. Fixes [#20368](https://github.com/mastra-ai/mastra/issues/20368). - Fixed an issue in the agentic loop where an aborted or failed LLM stream would still trigger output processors and improperly persist the user's input message as an orphaned record. ([#19716](https://github.com/mastra-ai/mastra/pull/19716)) - Fixed a bug where disconnecting one consumer of a workflow's `fullStream` (or a model's evented stream) would silently stop every other concurrent consumer of the same run from receiving further chunks. This affected cases like two `/stream` requests for the same `runId`, or `/stream` combined with `/observe` — one client disconnecting no longer breaks the others, which now keep receiving chunks and close normally. ([#19745](https://github.com/mastra-ai/mastra/pull/19745)) - Fixed tool executions silently losing request context when a bundler or monorepo loads more than one copy of @mastra/core. Previously, a request context created by a different copy of the package was not recognized, so the tool received an empty context or the entries passed at execution time were dropped from the merge. Request context values now reach the tool regardless of which copy of the package created them. Closes #19772. ([#19863](https://github.com/mastra-ai/mastra/pull/19863)) - Added toolCallId to TOOL_CALL and MCP_TOOL_CALL span attributes so observability exporters can pair tool results with their calls. Previously the tool call ID was available at the call site but never forwarded to the span, causing downstream exporters to lose the association between a tool call and its result. ([#19405](https://github.com/mastra-ai/mastra/pull/19405)) - Fixed agent thread subscriptions so every instance in a multi-instance deployment sees the same conversation: ([#19806](https://github.com/mastra-ai/mastra/pull/19806)) - Subscribers on any instance now replay completed runs identically instead of diverging from the instance that ran them. - Reconnecting to a thread no longer wedges `agent.stream()` behind a stale run left by a crashed or finished process. - Aborting a thread now works from any instance — the request is routed to the process that owns the run. - A `stream()`/`generate()` call started while another instance is mid-run on the same thread now waits its turn instead of interleaving output. - Fixed `declineToolCall` and `declineToolCallGenerate` so declined approval-gated tools no longer execute or cause side effects after resume. Fixes https://github.com/mastra-ai/mastra/issues/20470 ([#20487](https://github.com/mastra-ai/mastra/pull/20487)) - Fixed durable delegated tool approvals so persisted approvals resume the suspended sub-agent, including after a server restart. ([#20492](https://github.com/mastra-ai/mastra/pull/20492)) - Fixed suspended DurableAgent tools to receive primitive resume data such as native ask_user answers. ([#19750](https://github.com/mastra-ai/mastra/pull/19750)) ## 1.56.0-alpha.2 ### Patch Changes - Fixed a crash in tool input validation when using Zod v4 compatibility schemas that don't provide a native JSON Schema. ([#19030](https://github.com/mastra-ai/mastra/pull/19030)) - Fixed delegated tool approvals not resuming after a page refresh or server restart. Approvals saved in conversation metadata previously pointed at a sub-agent run that could not be resumed. They now point at the supervisor run, so the saved approval works directly with `resumeStream()` and `approveToolCall()`. Approvals saved before this fix keep working. ([#19645](https://github.com/mastra-ai/mastra/pull/19645)) - Fixed custom data chunks emitted by processors so they are saved with thread messages unless marked transient. ([#19375](https://github.com/mastra-ai/mastra/pull/19375)) - Fixed thread title generation using messages from other threads when memory is resource-scoped. Titles for new threads are now derived only from the messages of the thread being titled, instead of the full message list which can include recalled messages from the user's other conversations. ([#19856](https://github.com/mastra-ai/mastra/pull/19856)) ## 1.56.0-alpha.1 ### Minor Changes - Added `unmockedToolPolicy` to experiments and dataset items so undeclared agent tool calls can be blocked before execution. ([#19643](https://github.com/mastra-ai/mastra/pull/19643)) ```typescript await dataset.startExperiment({ targetType: 'agent', targetId: 'weather-agent', unmockedToolPolicy: 'deny', }); ``` ### Patch Changes - Fixed review comments on experiment results not being saved. Experiment results now have a persisted comment field, and updateExperimentResult accepts a comment alongside status and tags. Fixes https://github.com/mastra-ai/mastra/issues/19857 ([#19865](https://github.com/mastra-ai/mastra/pull/19865)) ```ts const experimentsStore = await storage.getStore('experiments'); await experimentsStore.updateExperimentResult({ id: resultId, experimentId, comment: 'Agent hallucinated an API that does not exist', }); ``` ## 1.56.0-alpha.0 ### Minor Changes - `RegexFilterProcessor` now reports what the `redact` strategy rewrote, through the existing `Processor.onViolation` callback. ([#20445](https://github.com/mastra-ai/mastra/pull/20445)) Redaction used to be silent. The processor found its matches, replaced the text, and dropped the match list, leaving nothing downstream to audit. It now reports once per redacted message, message part, or stream chunk, with offsets relative to that piece of text. ```ts const filter = new RegexFilterProcessor({ presets: ['pii'], strategy: 'redact', }); filter.onViolation = async ({ detail }) => { const redaction = detail as RegexRedactionDetail; for (const entry of redaction.redactions) { await auditLog.write({ phase: redaction.phase, // processInput | processOutputStream | processOutputResult messageId: redaction.messageId, rule: entry.rule, // 'credit-card' offset: entry.index, length: entry.length, }); } }; ``` Async callbacks are awaited. When no callback is attached the redact path stays synchronous, so nothing changes for existing callers. When two rules match the same span, the winning rule is reported as `rule` and every rule that matched is listed in `overlappingRules`. **Values are withheld by default** Reports carry offsets and rule names, not the matched text. An audit trail that copies the data it protects widens the exposure it was added to narrow, which is why the `block` strategy already withholds matched text from its `TripWire` metadata. Set `includeRedactedValues: true` to add a `value` field when the destination is as protected as the original. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7f4e26d`](https://github.com/mastra-ai/mastra/commit/7f4e26dd57bd9b23c278ea21235ab823a3810a6c)) - Fixed `RegexFilterProcessor` leaving part of a matched value in the output when two rules match overlapping text. ([#20445](https://github.com/mastra-ai/mastra/pull/20445)) **What was wrong** Rules were applied one at a time with `String.replace`, so an earlier rule could consume the start of a longer match and leave the rest in the clear. With the `pii` preset, `phone` runs before `credit-card`, so a card number written without separators lost only its first ten digits: ```ts const filter = new RegexFilterProcessor({ presets: ['pii'], strategy: 'redact' }); // Before: "card [PHONE]111111" // After: "card [CREDIT_CARD]" ``` Overlapping matches are now combined into one region and replaced once, using the replacement of the longest match. **Two smaller changes to redaction** - A replacement that references capture groups (`$1`, `$&`) now falls back to the replacement string as written when the rule cannot match the text it matched in isolation, which happens with a lookbehind or lookahead. The region is still redacted. - A rule that only matches empty strings no longer inserts its replacement between every character. ## 1.55.0 ### Minor Changes - Added support for Code Mode transports that provide their own execution boundary. A transport can now declare `requiresSandbox: false` and `createCodeMode()` will run it without a workspace sandbox, which enables in-process transports such as `IsolatedVmCodeModeTransport` from `@mastra/isolated-vm`: ([#20359](https://github.com/mastra-ai/mastra/pull/20359)) ```typescript import { createCodeMode } from '@mastra/core/tools'; import { IsolatedVmCodeModeTransport } from '@mastra/isolated-vm'; // No sandbox needed — the V8 isolate is the execution boundary const { tool, instructions } = createCodeMode({ tools }, new IsolatedVmCodeModeTransport()); ``` Also fixed the generated Code Mode instructions to describe isolation accurately instead of always claiming the program runs fully sandboxed, since the actual boundary depends on the configured sandbox and transport. The `sanitizeToolId` helper used for `external_*` naming is now exported from `@mastra/core/tools` so transports can reuse it instead of duplicating it. - Added retry callbacks for stream error policies and ensured explicit matcher policies override provider retry metadata. ([#19724](https://github.com/mastra-ai/mastra/pull/19724)) - Channel handlers can now contribute to the request context of the run they start. ([#20060](https://github.com/mastra-ai/mastra/pull/20060)) `ChannelHandlerContext` gains a `requestContext` field holding the `RequestContext` for the run the inbound message is about to start. It is constructed fresh for every message, and a handler may write to it before calling `defaultHandler`. Core then adds its own channel and render-context entries and dispatches with the same instance, so anything the handler wrote reaches the run. ```ts import { AgentControllerChannels } from '@mastra/core/channels'; const channels = new AgentControllerChannels({ adapters, handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.requestContext.set('locale', 'en-GB'); await defaultHandler(thread, message); }, }, }); ``` Anything a run reads from its request context can now be decided per inbound message — for example resolving which user a platform sender maps to, so the run uses that user's stored credentials. **Contract change:** `ChannelHandler`'s 4th `ctx` parameter is now non-optional (`ctx: ChannelHandlerContext`, previously `ctx?: ChannelHandlerContext`). Core has always passed it, and requiring it means a handler writing `ctx.requestContext.set(...)` needs neither a non-null assertion nor a guard that would silently skip the write. Handler _implementations_ are unaffected: TypeScript lets a function declaring fewer parameters satisfy a type declaring more, so existing three-parameter handlers — and anyone who wrote `ctx?.mastra` — keep compiling. Code that _calls_ a `ChannelHandler`-typed value with three arguments does need updating, and will fail with `Expected 4 arguments, but got 3` until the context is passed. - Added a built-in web search tool that resolves to provider-native search for supported models. ([#20345](https://github.com/mastra-ai/mastra/pull/20345)) ```ts import { Agent } from '@mastra/core/agent'; import { webSearchTool } from '@mastra/core/tools'; export const agent = new Agent({ name: 'web-search-agent', instructions: 'Use web search for current information.', model: 'openai/gpt-5-mini', tools: { webSearch: webSearchTool }, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`3f472b4`](https://github.com/mastra-ai/mastra/commit/3f472b468892a1ff14ccb43cc0343b86f7d8fd7d)) - Fixed Zod 4 installations reporting peer dependency warnings. ([#20313](https://github.com/mastra-ai/mastra/pull/20313)) - Fixed channel tool approval actions so missing thread mappings cannot create conversations under the button clicker's identity. ([#20374](https://github.com/mastra-ai/mastra/pull/20374)) - Fixed duplicate delegation prompts in sub-agent threads. When a supervisor agent delegated to a sub-agent without its own memory and a memory processor persisted messages during the run (such as observational memory), the delegation prompt was saved twice to the sub-agent's thread (once by the mid-run persistence and once by the delegation transcript save). The prompt now keeps a stable message ID across both writes so it is persisted exactly once. Delegation prompt rows are now stored with the default `v2` message type instead of `text`, consistent with other persisted messages. ([#20174](https://github.com/mastra-ai/mastra/pull/20174)) - Review sessions now load project AGENTS.md/CLAUDE.md from the pull request's trusted base branch instead of skipping them entirely. The working-tree copies on an untrusted checkout remain excluded from the system prompt and reminder injection; content is served from the base ref via git, and sessions without a known base ref still skip project instruction files. ([#20372](https://github.com/mastra-ai/mastra/pull/20372)) - Improved workflow and MCP tool-result formatting to avoid serializing large values during equality checks while preserving explicit model outputs. ([#20340](https://github.com/mastra-ai/mastra/pull/20340)) - Review sessions no longer ingest AGENTS.md or CLAUDE.md from the checked-out pull request branch. A PR branch is third-party content, so its instruction files are treated as content under review instead of trusted configuration — closing a prompt-injection path into the reviewer agent. The reviewer also runs the PR's install/build/test commands with GitHub tokens stripped from the environment. ([#20372](https://github.com/mastra-ai/mastra/pull/20372)) - Added an option to the instruction-file reminder processor that lets hosts disable injection entirely for a request, so instruction files from untrusted checkouts are never surfaced as reminders. ([#20372](https://github.com/mastra-ai/mastra/pull/20372)) - Fixed the default workflow execution engine serializing the full RequestContext on every step and entry and then discarding the result. `serializeRequestContext` probes every stored value with `JSON.stringify` via `RequestContext.toJSON()`, but on the default engine the serialized object was never read — only engines with `requiresDurableContextSerialization()` (e.g. Inngest) restore context from serialized results. The step/entry return paths now skip serialization on the default engine; snapshot persistence is unaffected. ([#20369](https://github.com/mastra-ai/mastra/pull/20369)) ## 1.55.0-alpha.3 ### Patch Changes - Review sessions now load project AGENTS.md/CLAUDE.md from the pull request's trusted base branch instead of skipping them entirely. The working-tree copies on an untrusted checkout remain excluded from the system prompt and reminder injection; content is served from the base ref via git, and sessions without a known base ref still skip project instruction files. ([#20372](https://github.com/mastra-ai/mastra/pull/20372)) - Review sessions no longer ingest AGENTS.md or CLAUDE.md from the checked-out pull request branch. A PR branch is third-party content, so its instruction files are treated as content under review instead of trusted configuration — closing a prompt-injection path into the reviewer agent. The reviewer also runs the PR's install/build/test commands with GitHub tokens stripped from the environment. ([#20372](https://github.com/mastra-ai/mastra/pull/20372)) - Added an option to the instruction-file reminder processor that lets hosts disable injection entirely for a request, so instruction files from untrusted checkouts are never surfaced as reminders. ([#20372](https://github.com/mastra-ai/mastra/pull/20372)) ## 1.55.0-alpha.2 ### Minor Changes - Channel handlers can now contribute to the request context of the run they start. ([#20060](https://github.com/mastra-ai/mastra/pull/20060)) `ChannelHandlerContext` gains a `requestContext` field holding the `RequestContext` for the run the inbound message is about to start. It is constructed fresh for every message, and a handler may write to it before calling `defaultHandler`. Core then adds its own channel and render-context entries and dispatches with the same instance, so anything the handler wrote reaches the run. ```ts import { AgentControllerChannels } from '@mastra/core/channels'; const channels = new AgentControllerChannels({ adapters, handlers: { onDirectMessage: async (thread, message, defaultHandler, ctx) => { ctx.requestContext.set('locale', 'en-GB'); await defaultHandler(thread, message); }, }, }); ``` Anything a run reads from its request context can now be decided per inbound message — for example resolving which user a platform sender maps to, so the run uses that user's stored credentials. **Contract change:** `ChannelHandler`'s 4th `ctx` parameter is now non-optional (`ctx: ChannelHandlerContext`, previously `ctx?: ChannelHandlerContext`). Core has always passed it, and requiring it means a handler writing `ctx.requestContext.set(...)` needs neither a non-null assertion nor a guard that would silently skip the write. Handler _implementations_ are unaffected: TypeScript lets a function declaring fewer parameters satisfy a type declaring more, so existing three-parameter handlers — and anyone who wrote `ctx?.mastra` — keep compiling. Code that _calls_ a `ChannelHandler`-typed value with three arguments does need updating, and will fail with `Expected 4 arguments, but got 3` until the context is passed. ### Patch Changes - Improved workflow and MCP tool-result formatting to avoid serializing large values during equality checks while preserving explicit model outputs. ([#20340](https://github.com/mastra-ai/mastra/pull/20340)) ## 1.55.0-alpha.1 ### Minor Changes - Added support for Code Mode transports that provide their own execution boundary. A transport can now declare `requiresSandbox: false` and `createCodeMode()` will run it without a workspace sandbox, which enables in-process transports such as `IsolatedVmCodeModeTransport` from `@mastra/isolated-vm`: ([#20359](https://github.com/mastra-ai/mastra/pull/20359)) ```typescript import { createCodeMode } from '@mastra/core/tools'; import { IsolatedVmCodeModeTransport } from '@mastra/isolated-vm'; // No sandbox needed — the V8 isolate is the execution boundary const { tool, instructions } = createCodeMode({ tools }, new IsolatedVmCodeModeTransport()); ``` Also fixed the generated Code Mode instructions to describe isolation accurately instead of always claiming the program runs fully sandboxed, since the actual boundary depends on the configured sandbox and transport. The `sanitizeToolId` helper used for `external_*` naming is now exported from `@mastra/core/tools` so transports can reuse it instead of duplicating it. ### Patch Changes - Fixed Zod 4 installations reporting peer dependency warnings. ([#20313](https://github.com/mastra-ai/mastra/pull/20313)) - Fixed channel tool approval actions so missing thread mappings cannot create conversations under the button clicker's identity. ([#20374](https://github.com/mastra-ai/mastra/pull/20374)) - Fixed the default workflow execution engine serializing the full RequestContext on every step and entry and then discarding the result. `serializeRequestContext` probes every stored value with `JSON.stringify` via `RequestContext.toJSON()`, but on the default engine the serialized object was never read — only engines with `requiresDurableContextSerialization()` (e.g. Inngest) restore context from serialized results. The step/entry return paths now skip serialization on the default engine; snapshot persistence is unaffected. ([#20369](https://github.com/mastra-ai/mastra/pull/20369)) ## 1.55.0-alpha.0 ### Minor Changes - Added retry callbacks for stream error policies and ensured explicit matcher policies override provider retry metadata. ([#19724](https://github.com/mastra-ai/mastra/pull/19724)) - Added a built-in web search tool that resolves to provider-native search for supported models. ([#20345](https://github.com/mastra-ai/mastra/pull/20345)) ```ts import { Agent } from '@mastra/core/agent'; import { webSearchTool } from '@mastra/core/tools'; export const agent = new Agent({ name: 'web-search-agent', instructions: 'Use web search for current information.', model: 'openai/gpt-5-mini', tools: { webSearch: webSearchTool }, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`3f472b4`](https://github.com/mastra-ai/mastra/commit/3f472b468892a1ff14ccb43cc0343b86f7d8fd7d)) ## 1.54.0 ### Minor Changes - Channel handlers now receive a 4th argument: a `ChannelHandlerContext` carrying the resolved `mastra` instance. Custom handlers can read `ctx.mastra`. ([#19289](https://github.com/mastra-ai/mastra/pull/19289)) - Added exact metadata filtering to message history queries across Memory APIs and supported storage providers. ([#19991](https://github.com/mastra-ai/mastra/pull/19991)) ```ts const messages = await memory.recall({ threadId: 'thread-1', filter: { metadata: { status: 'done', priority: 'high', }, }, }); ``` Multiple fields use AND semantics. Supported values are strings, finite numbers, booleans, and `null`. - Added status-bearing judge execution details to scorer results. ([#20022](https://github.com/mastra-ai/mastra/pull/20022)) ```typescript const result = await scorer.run(input); const execution = result.judge?.generateScore?.executions[0]; if (execution?.status === 'success') { console.log(execution.output, execution.usage); } ``` Prompt-based scorer steps now expose successful logical executions with `status: 'success'`, their prompt, structured output, judge model identity, normalized token usage, attempt and model-call counts, and duration. A structured-output fallback that eventually succeeds remains one execution with its attempts aggregated. Use this data to interpret one scorer run without reconstructing it from traces. - Added chat channel support to `AgentController`. Configure `channels` with Chat SDK adapters (Slack, Discord, Telegram, and more) to run a controller-backed agent session inside a messaging thread: each chat thread maps to one durable controller session, the agent's streamed output renders back to the platform with native streaming, tool approval cards, and typing status, and tool approvals resolve through the session's approval gate. ([#19289](https://github.com/mastra-ai/mastra/pull/19289)) ```typescript import { AgentController } from '@mastra/core/agent-controller'; import { createSlackAdapter } from '@chat-adapter/slack'; const agentController = new AgentController({ id: 'my-agent-controller', agent, modes, channels: { adapters: { slack: createSlackAdapter(), }, }, }); ``` Registering the controller on a `Mastra` instance exposes a webhook route per adapter at `/api/agent-controllers//channels//webhook`. Adapters can be constructed manually here; provider-managed connect flows (such as Slack's controller-owned installations) are also supported. Controller channels need a long-lived server. Controller channels attach to the backing agent instance (via `setChannels`, propagated to every backing agent including per-mode agents) rather than being stamped onto each run's request context. Only agents explicitly given channels attach the channel output processor, so child runs such as observational-memory observers and forked subagents no longer render to the chat platform. Inbound channel messages routed into a controller session keep their platform metadata: the message's `providerOptions` (author name, user ID, message ID, and so on under `mastra.channels.`) are stamped onto the persisted user message as `content.providerMetadata`, matching the plain agent channel path. - Added a built-in web fetch tool export for agents to retrieve HTTP and HTTPS page content. ([#20232](https://github.com/mastra-ai/mastra/pull/20232)) ```ts import { Agent } from '@mastra/core/agent'; import { webFetchTool } from '@mastra/core/tools'; export const agent = new Agent({ name: 'Research agent', instructions: 'Use web_fetch when you need page content.', model, tools: { web_fetch: webFetchTool }, }); ``` - Added `smoothStream()` and experimental agent stream transforms for buffering text and reasoning deltas into consistent, delayed chunks. ([#19792](https://github.com/mastra-ai/mastra/pull/19792)) ```typescript const stream = result.fullStream.pipeThrough(smoothStream()); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ce93a3c`](https://github.com/mastra-ai/mastra/commit/ce93a3c114ea1cbfbd576f3db41d7c26c9844f5b)) - Fixed `skill_read` returning an empty string when `startLine` is past the end of a file, which left agents unable to tell end-of-file from a failed read so they kept paginating. It now returns the total line count with an explicit end-of-file message, and prefixes ranged reads with a `(lines 350-428 of 428)` header. Full-file reads are unchanged. ([#20287](https://github.com/mastra-ai/mastra/pull/20287)) - Added a `requireDelivery` option to agent-controller session signals. When set, `session.sendSignal` waits for the agent to accept the wake signal and rejects if delivery fails, instead of resolving optimistically. This lets callers that need guaranteed delivery (like the Factory rule dispatcher) detect and retry failed kickoffs. ([#20294](https://github.com/mastra-ai/mastra/pull/20294)) ```ts const result = session.sendSignal( { id, type: 'user', tagName: 'user', contents: message }, { requestContext, requireDelivery: true }, ); // Resolves only once the agent has accepted the signal (`action` is // 'wake' or 'deliver'); rejects if the wake never reaches an agent. const { action, runId } = await result.accepted; ``` - Observational-memory settings no longer fail with "No session for resourceId" on the settings page: OM config routes now treat the live session as best-effort sync and fall back to the durably stored per-user settings when no agent-controller session exists for the resource (e.g. after a server restart), so settings load and save instead of 404ing ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - Fixed four bugs affecting split API and worker deployments: ([#19652](https://github.com/mastra-ai/mastra/pull/19652)) **Workflow resolution in distributed step execution** — Workers now correctly resolve workflows by their internal ID, fixing silent failures when workflow IDs differ from their registration keys. **Background task dispatch without worker competition** — Added a `mode` option (`'full'` | `'producer'` | `'worker'`) to `BackgroundTaskManager` so the API tier can dispatch tasks without consuming them. Mastra automatically selects `'producer'` mode when workers are disabled or filtered. **Background task execution engine** — Background tasks now execute in-process on the worker that picks them up, instead of routing through the distributed orchestration pipeline. **Agent tool registration for background tasks** — Tools attached to an agent are now registered with the background task executor registry, so dedicated workers can resolve and execute them. - Fixed session creation ignoring an exact thread id when the session was already live. Requesting a session with a threadId now resumes or creates that exact thread even when another request (like an event subscription or message listing) created the session first, preventing 'Thread not found' errors for workspace threads. ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - Fixed Factory sessions failing to start their kickoff run. Workspaces now recover automatically when the sandbox provider changes or a sandbox is wiped (the repository is re-cloned instead of failing), thread pages surface workspace preparation errors with a Retry button instead of hanging, and kickoff messages are now delivered to the session thread instead of silently failing with a permissions error. ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - The factory-review skill now publishes its verdict on the pull request itself (gh pr review --approve / --request-changes with the full handoff body, falling back to a PR comment when GitHub rejects the review) instead of only posting the verdict in the Factory thread ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - Fixed Mastra construction so failed storage initialization no longer causes unhandled promise rejections. ([#20181](https://github.com/mastra-ai/mastra/pull/20181)) - Fixed xAI model router strings to use the Responses API so provider tools like web search work without manually creating an xAI model instance. ([#20306](https://github.com/mastra-ai/mastra/pull/20306)) - Notifications now stop retrying after a delivery failure that will never succeed. ([#20290](https://github.com/mastra-ai/mastra/pull/20290)) A notification whose delivery deterministically fails — a missing model, a rejected request context — used to be retried on every dispatch tick forever, because a failed delivery only incremented a counter and left the record `pending`. One production notification reached 288 delivery attempts against the same error. Delivery attempts are now capped. After 5 failures the notification is marked `failed` and is no longer picked up by the dispatcher: ```ts const [notification] = await storage.listNotifications({ threadId, status: 'failed' }); notification.deliveryAttempts; // 5 notification.lastDeliveryError; // 'No model selected. Use /models to select a model first.' ``` `failed` is a new `NotificationStatus`, so notification inbox queries can filter for delivery failures that need attention. Transient failures are unaffected — they still retry, just no longer without bound. ## 1.54.0-alpha.4 ### Minor Changes - Added `smoothStream()` and experimental agent stream transforms for buffering text and reasoning deltas into consistent, delayed chunks. ([#19792](https://github.com/mastra-ai/mastra/pull/19792)) ```typescript const stream = result.fullStream.pipeThrough(smoothStream()); ``` ### Patch Changes - Added a `requireDelivery` option to agent-controller session signals. When set, `session.sendSignal` waits for the agent to accept the wake signal and rejects if delivery fails, instead of resolving optimistically. This lets callers that need guaranteed delivery (like the Factory rule dispatcher) detect and retry failed kickoffs. ([#20294](https://github.com/mastra-ai/mastra/pull/20294)) ```ts const result = session.sendSignal( { id, type: 'user', tagName: 'user', contents: message }, { requestContext, requireDelivery: true }, ); // Resolves only once the agent has accepted the signal (`action` is // 'wake' or 'deliver'); rejects if the wake never reaches an agent. const { action, runId } = await result.accepted; ``` ## 1.54.0-alpha.3 ### Patch Changes - Fixed xAI model router strings to use the Responses API so provider tools like web search work without manually creating an xAI model instance. ([#20306](https://github.com/mastra-ai/mastra/pull/20306)) ## 1.54.0-alpha.2 ### Patch Changes - Fixed `skill_read` returning an empty string when `startLine` is past the end of a file, which left agents unable to tell end-of-file from a failed read so they kept paginating. It now returns the total line count with an explicit end-of-file message, and prefixes ranged reads with a `(lines 350-428 of 428)` header. Full-file reads are unchanged. ([#20287](https://github.com/mastra-ai/mastra/pull/20287)) - Fixed four bugs affecting split API and worker deployments: ([#19652](https://github.com/mastra-ai/mastra/pull/19652)) **Workflow resolution in distributed step execution** — Workers now correctly resolve workflows by their internal ID, fixing silent failures when workflow IDs differ from their registration keys. **Background task dispatch without worker competition** — Added a `mode` option (`'full'` | `'producer'` | `'worker'`) to `BackgroundTaskManager` so the API tier can dispatch tasks without consuming them. Mastra automatically selects `'producer'` mode when workers are disabled or filtered. **Background task execution engine** — Background tasks now execute in-process on the worker that picks them up, instead of routing through the distributed orchestration pipeline. **Agent tool registration for background tasks** — Tools attached to an agent are now registered with the background task executor registry, so dedicated workers can resolve and execute them. - Notifications now stop retrying after a delivery failure that will never succeed. ([#20290](https://github.com/mastra-ai/mastra/pull/20290)) A notification whose delivery deterministically fails — a missing model, a rejected request context — used to be retried on every dispatch tick forever, because a failed delivery only incremented a counter and left the record `pending`. One production notification reached 288 delivery attempts against the same error. Delivery attempts are now capped. After 5 failures the notification is marked `failed` and is no longer picked up by the dispatcher: ```ts const [notification] = await storage.listNotifications({ threadId, status: 'failed' }); notification.deliveryAttempts; // 5 notification.lastDeliveryError; // 'No model selected. Use /models to select a model first.' ``` `failed` is a new `NotificationStatus`, so notification inbox queries can filter for delivery failures that need attention. Transient failures are unaffected — they still retry, just no longer without bound. ## 1.54.0-alpha.1 ### Minor Changes - Channel handlers now receive a 4th argument: a `ChannelHandlerContext` carrying the resolved `mastra` instance. Custom handlers can read `ctx.mastra`. ([#19289](https://github.com/mastra-ai/mastra/pull/19289)) - Added structured judge execution details to scorer results. ([#20022](https://github.com/mastra-ai/mastra/pull/20022)) ```typescript const result = await scorer.run(input); const usage = result.judge?.generateScore?.executions[0]?.usage; ``` Prompt-based scorer steps now expose their prompt, structured output, judge model identity, normalized token usage, attempt and model-call counts, and duration. Use this data to interpret one scorer run without reconstructing it from traces. - Added chat channel support to `AgentController`. Configure `channels` with Chat SDK adapters (Slack, Discord, Telegram, and more) to run a controller-backed agent session inside a messaging thread: each chat thread maps to one durable controller session, the agent's streamed output renders back to the platform with native streaming, tool approval cards, and typing status, and tool approvals resolve through the session's approval gate. ([#19289](https://github.com/mastra-ai/mastra/pull/19289)) ```typescript import { AgentController } from '@mastra/core/agent-controller'; import { createSlackAdapter } from '@chat-adapter/slack'; const agentController = new AgentController({ id: 'my-agent-controller', agent, modes, channels: { adapters: { slack: createSlackAdapter(), }, }, }); ``` Registering the controller on a `Mastra` instance exposes a webhook route per adapter at `/api/agent-controllers//channels//webhook`. Adapters can be constructed manually here; provider-managed connect flows (such as Slack's controller-owned installations) are also supported. Controller channels need a long-lived server. Controller channels attach to the backing agent instance (via `setChannels`, propagated to every backing agent including per-mode agents) rather than being stamped onto each run's request context. Only agents explicitly given channels attach the channel output processor, so child runs such as observational-memory observers and forked subagents no longer render to the chat platform. Inbound channel messages routed into a controller session keep their platform metadata: the message's `providerOptions` (author name, user ID, message ID, and so on under `mastra.channels.`) are stamped onto the persisted user message as `content.providerMetadata`, matching the plain agent channel path. - Added a built-in web fetch tool export for agents to retrieve HTTP and HTTPS page content. ([#20232](https://github.com/mastra-ai/mastra/pull/20232)) ```ts import { Agent } from '@mastra/core/agent'; import { webFetchTool } from '@mastra/core/tools'; export const agent = new Agent({ name: 'Research agent', instructions: 'Use web_fetch when you need page content.', model, tools: { web_fetch: webFetchTool }, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ce93a3c`](https://github.com/mastra-ai/mastra/commit/ce93a3c114ea1cbfbd576f3db41d7c26c9844f5b)) - Observational-memory settings no longer fail with "No session for resourceId" on the settings page: OM config routes now treat the live session as best-effort sync and fall back to the durably stored per-user settings when no agent-controller session exists for the resource (e.g. after a server restart), so settings load and save instead of 404ing ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - Fixed session creation ignoring an exact thread id when the session was already live. Requesting a session with a threadId now resumes or creates that exact thread even when another request (like an event subscription or message listing) created the session first, preventing 'Thread not found' errors for workspace threads. ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - Fixed Factory sessions failing to start their kickoff run. Workspaces now recover automatically when the sandbox provider changes or a sandbox is wiped (the repository is re-cloned instead of failing), thread pages surface workspace preparation errors with a Retry button instead of hanging, and kickoff messages are now delivered to the session thread instead of silently failing with a permissions error. ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - The factory-review skill now publishes its verdict on the pull request itself (gh pr review --approve / --request-changes with the full handoff body, falling back to a PR comment when GitHub rejects the review) instead of only posting the verdict in the Factory thread ([#20265](https://github.com/mastra-ai/mastra/pull/20265)) - Fixed Mastra construction so failed storage initialization no longer causes unhandled promise rejections. ([#20181](https://github.com/mastra-ai/mastra/pull/20181)) ## 1.54.0-alpha.0 ### Minor Changes - Added exact metadata filtering to message history queries across Memory APIs and supported storage providers. ([#19991](https://github.com/mastra-ai/mastra/pull/19991)) ```ts const messages = await memory.recall({ threadId: 'thread-1', filter: { metadata: { status: 'done', priority: 'high', }, }, }); ``` Multiple fields use AND semantics. Supported values are strings, finite numbers, booleans, and `null`. ## 1.53.0 ### Minor Changes - Added a `readOnly` option to `NativeSandboxConfig` to restrict the local sandbox's working directory to read-only access on macOS (Seatbelt) and Linux (Bubblewrap). ([#18999](https://github.com/mastra-ai/mastra/pull/18999)) **Usage Example:** ```typescript import { Workspace, LocalFilesystem, LocalSandbox } from '@mastra/core'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace', readOnly: true }), sandbox: new LocalSandbox({ workingDirectory: './workspace', isolation: 'bwrap', // or 'seatbelt' nativeSandbox: { readOnly: true, }, }), }); ``` - Added `resolveThreadId` to `ChannelConfig`: resolve the internal Mastra thread id before a channel thread is created, mirroring `resolveResourceId`. The hook runs after `resolveResourceId` (the resolved owner is on the context) and only for newly-created threads — existing threads keep their stored id. This lets a host align channel thread ids with ids it controls, e.g. give the thread the same id as the session it belongs to: ([#20147](https://github.com/mastra-ai/mastra/pull/20147)) ```ts const channels = new AgentChannels({ adapters, resolveResourceId: async ctx => resolveSessionId(ctx), resolveThreadId: ({ resourceId, defaultThreadId }) => (isSessionId(resourceId) ? resourceId : defaultThreadId), }); ``` - Channel reaction tools (`add_reaction`, `remove_reaction`) are no longer injected into a channel-bearing agent's toolset automatically. Replies are unaffected — they stream back to the channel without a tool. If you want your agent to react to channel messages, add the tools explicitly: ([#20152](https://github.com/mastra-ai/mastra/pull/20152)) ```ts const channels = new AgentChannels({ adapters: { slack: createSlackAdapter() }, }); const agent = new Agent({ name: 'assistant', model: 'openai/gpt-5', channels, tools: { ...channels.getTools() }, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`c8d8a01`](https://github.com/mastra-ai/mastra/commit/c8d8a010ee2efe2b7bf4d07707382c34c87b14e4)) - Fixed sub-agent tool approvals resuming the wrong run. When a sub-agent suspends for approval and the bot restarts, the approval handler now correctly resumes the parent agent's run instead of the sub-agent's. ([#19769](https://github.com/mastra-ai/mastra/pull/19769)) - Removed redundant non-null assertions from core control flow. ([#20140](https://github.com/mastra-ai/mastra/pull/20140)) - Fixed thread title generation receiving each conversation message twice. The duplicated transcript confused small title models into replying to the conversation instead of producing a title, so threads could get refusal text as their title. ([#20141](https://github.com/mastra-ai/mastra/pull/20141)) - Improved Factory work-item concurrency by replacing distributed advisory locks with atomic claims, idempotent replay, and serializable relationship transactions. ([#20135](https://github.com/mastra-ai/mastra/pull/20135)) - Keep deprecated provider models in the model registry instead of dropping them ([#20220](https://github.com/mastra-ai/mastra/pull/20220)) The models.dev gateway filtered out every model marked `status: 'deprecated'` upstream. That status means "still served, scheduled for retirement" rather than "removed", so filtering it silently degraded models that still work. A dropped model lost its capability data: `modelSupportsAttachments`, `modelSupportsTemperature`, and `modelSupportsStructuredOutput` return `false` for a model that is missing from a known provider's capability file, rather than `undefined`. A model that genuinely supports attachments therefore reported that it does not. Dropped models also lost their per-model endpoint/shape/SDK overrides, so a model that needs a non-default endpoint or SDK routed with provider defaults instead. They also disappeared from the generated model-id union used for editor autocomplete, and from any surface that lists a provider's models. Deprecated models are now retained in `models`, in the generated types, and in the capability and override data. They are additionally reported through a new optional `deprecatedModels` field on `ProviderConfig`, so surfaces that offer models for new selection can hide or mark them. - Fixed `isTaskComplete` completion checks leaking the internal scorer report (`#### Completion Check Results`) into the agent's final answer and saved conversation history. ([#19951](https://github.com/mastra-ai/mastra/pull/19951)) When completion scorers passed on the first check, the report was appended after the model's answer and became the last response message. Agents with memory (or any output processors) then resolved `stream.text` and the persisted assistant message to the report instead of the real answer. ```ts const stream = await agent.stream('What is the capital of France?', { memory: { thread, resource }, isTaskComplete: { scorers: [myScorer] }, }); // Before: '#### Completion Check Results ...' // After: 'The capital of France is Paris.' console.log(await stream.text); ``` Scorer feedback is now only added to the conversation when a check fails, where it steers the next iteration. Failing checks still retry with feedback exactly as before. Fixes [#19875](https://github.com/mastra-ai/mastra/issues/19875). - Fixed AI SDK v7 providers receiving screenshot and file tool results in an older content format. Tool result media is now sent in the file content shape expected by v7 providers such as Bedrock. ([#19755](https://github.com/mastra-ai/mastra/pull/19755)) ## 1.53.0-alpha.4 ### Minor Changes - Added a `readOnly` option to `NativeSandboxConfig` to restrict the local sandbox's working directory to read-only access on macOS (Seatbelt) and Linux (Bubblewrap). ([#18999](https://github.com/mastra-ai/mastra/pull/18999)) **Usage Example:** ```typescript import { Workspace, LocalFilesystem, LocalSandbox } from '@mastra/core'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace', readOnly: true }), sandbox: new LocalSandbox({ workingDirectory: './workspace', isolation: 'bwrap', // or 'seatbelt' nativeSandbox: { readOnly: true, }, }), }); ``` ### Patch Changes - Removed redundant non-null assertions from core control flow. ([#20140](https://github.com/mastra-ai/mastra/pull/20140)) - Improved Factory work-item concurrency by replacing distributed advisory locks with atomic claims, idempotent replay, and serializable relationship transactions. ([#20135](https://github.com/mastra-ai/mastra/pull/20135)) - Keep deprecated provider models in the model registry instead of dropping them ([#20220](https://github.com/mastra-ai/mastra/pull/20220)) The models.dev gateway filtered out every model marked `status: 'deprecated'` upstream. That status means "still served, scheduled for retirement" rather than "removed", so filtering it silently degraded models that still work. A dropped model lost its capability data: `modelSupportsAttachments`, `modelSupportsTemperature`, and `modelSupportsStructuredOutput` return `false` for a model that is missing from a known provider's capability file, rather than `undefined`. A model that genuinely supports attachments therefore reported that it does not. Dropped models also lost their per-model endpoint/shape/SDK overrides, so a model that needs a non-default endpoint or SDK routed with provider defaults instead. They also disappeared from the generated model-id union used for editor autocomplete, and from any surface that lists a provider's models. Deprecated models are now retained in `models`, in the generated types, and in the capability and override data. They are additionally reported through a new optional `deprecatedModels` field on `ProviderConfig`, so surfaces that offer models for new selection can hide or mark them. - Fixed `isTaskComplete` completion checks leaking the internal scorer report (`#### Completion Check Results`) into the agent's final answer and saved conversation history. ([#19951](https://github.com/mastra-ai/mastra/pull/19951)) When completion scorers passed on the first check, the report was appended after the model's answer and became the last response message. Agents with memory (or any output processors) then resolved `stream.text` and the persisted assistant message to the report instead of the real answer. ```ts const stream = await agent.stream('What is the capital of France?', { memory: { thread, resource }, isTaskComplete: { scorers: [myScorer] }, }); // Before: '#### Completion Check Results ...' // After: 'The capital of France is Paris.' console.log(await stream.text); ``` Scorer feedback is now only added to the conversation when a check fails, where it steers the next iteration. Failing checks still retry with feedback exactly as before. Fixes [#19875](https://github.com/mastra-ai/mastra/issues/19875). - Fixed AI SDK v7 providers receiving screenshot and file tool results in an older content format. Tool result media is now sent in the file content shape expected by v7 providers such as Bedrock. ([#19755](https://github.com/mastra-ai/mastra/pull/19755)) ## 1.53.0-alpha.3 ## 1.53.0-alpha.2 ### Minor Changes - Channel reaction tools (`add_reaction`, `remove_reaction`) are no longer injected into a channel-bearing agent's toolset automatically. Replies are unaffected — they stream back to the channel without a tool. If you want your agent to react to channel messages, add the tools explicitly: ([#20152](https://github.com/mastra-ai/mastra/pull/20152)) ```ts const channels = new AgentChannels({ adapters: { slack: createSlackAdapter() }, }); const agent = new Agent({ name: 'assistant', model: 'openai/gpt-5', channels, tools: { ...channels.getTools() }, }); ``` ## 1.53.0-alpha.1 ### Minor Changes - Added `resolveThreadId` to `ChannelConfig`: resolve the internal Mastra thread id before a channel thread is created, mirroring `resolveResourceId`. The hook runs after `resolveResourceId` (the resolved owner is on the context) and only for newly-created threads — existing threads keep their stored id. This lets a host align channel thread ids with ids it controls, e.g. give the thread the same id as the session it belongs to: ([#20147](https://github.com/mastra-ai/mastra/pull/20147)) ```ts const channels = new AgentChannels({ adapters, resolveResourceId: async ctx => resolveSessionId(ctx), resolveThreadId: ({ resourceId, defaultThreadId }) => (isSessionId(resourceId) ? resourceId : defaultThreadId), }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`c8d8a01`](https://github.com/mastra-ai/mastra/commit/c8d8a010ee2efe2b7bf4d07707382c34c87b14e4)) - Fixed thread title generation receiving each conversation message twice. The duplicated transcript confused small title models into replying to the conversation instead of producing a title, so threads could get refusal text as their title. ([#20141](https://github.com/mastra-ai/mastra/pull/20141)) ## 1.52.2-alpha.0 ### Patch Changes - Fixed sub-agent tool approvals resuming the wrong run. When a sub-agent suspends for approval and the bot restarts, the approval handler now correctly resumes the parent agent's run instead of the sub-agent's. ([#19769](https://github.com/mastra-ai/mastra/pull/19769)) ## 1.52.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`55adddf`](https://github.com/mastra-ai/mastra/commit/55adddfda2a170b00c112bf37d677e8ce5b65d5a)) ## 1.52.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`55adddf`](https://github.com/mastra-ai/mastra/commit/55adddfda2a170b00c112bf37d677e8ce5b65d5a)) ## 1.52.0 ### Minor Changes - `mastra build` now deploys in one step for push-style deployers. Deployers can opt in with the new `deployOnBuild` flag on the deployer contract, and the build runs their `deploy()` right after bundling. `SandboxDeployer` from `@mastra/deployer-sandbox` opts in, so configuring it means `mastra build` bundles your project, deploys it into the sandbox, and prints the live URL. Existing platform deployers are unchanged. ([#19577](https://github.com/mastra-ai/mastra/pull/19577)) ```typescript import { Mastra } from '@mastra/core/mastra'; import { SandboxDeployer } from '@mastra/deployer-sandbox'; import { VercelSandbox } from '@mastra/vercel'; export const mastra = new Mastra({ deployer: new SandboxDeployer({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), }), }); ``` ```bash mastra build # bundles AND deploys — prints the live URL ``` - The 'workers' option on the Mastra class now merges with the default workers instead of replacing them. Passing custom workers (e.g. a polling worker for an integration) no longer silently drops the built-in orchestration and background-task workers — a custom worker only replaces a default when it shares its name, and 'workers: false' still disables all workers. ([#19766](https://github.com/mastra-ai/mastra/pull/19766)) ```ts const mastra = new Mastra({ // before: only myPoller ran — orchestration was dropped // after: myPoller runs alongside the default workers workers: [myPoller], }); ``` - Added optional provider-driven authorization for system (non-user) actors, enabling per-agent least privilege after tenant scope has been validated. ([#19338](https://github.com/mastra-ai/mastra/pull/19338)) System actors (`actor: { actorKind: 'system' }` or `actor: true`) skip the user-centric FGA path. Previously the only boundary for them was tenant scope — there was no way to constrain which agent could do what, and no deny path. FGA providers can now opt in to enforce least privilege for those actors. **What's new** - `IFGAProvider` gains an optional `requireActor(actor, params)` method that throws `FGADeniedError` to deny. - `ActorSignal` gains optional `agentId`, `permissions`, and `scope` so a provider can identify and constrain the acting agent. `permissions` reuses the `MastraFGAPermissionInput` vocabulary — the actor analog of a user's resolved permissions. It is a self-asserted claim: a provider enforcing real least privilege should resolve authoritative grants from a trusted source keyed by `agentId` rather than trusting it directly. - When a provider does not implement `requireActor`, the existing trusted-actor bypass is preserved exactly — this change is fully backward compatible. **Before** — system actors had tenant scoping but no provider-defined per-agent authorization: ```ts // Cron/system run: tenant scope is required, but no per-agent limits are possible. await agent.generate('run nightly report', { actor: { actorKind: 'system', sourceWorkflow: 'nightly' }, }); ``` **After** — a provider enforces least privilege on the acting agent: ```ts import type { IFGAProvider } from '@mastra/core/auth/ee'; import { FGADeniedError } from '@mastra/core/auth/ee'; class MyFga implements IFGAProvider { // ...existing check / require / filterAccessible... async requireActor(actor, { resource, permission }) { const agentId = actor === true ? undefined : actor.agentId; // Resolve authoritative grants from a trusted source keyed by agentId. const granted = await this.grantsForAgent(agentId); // `permission` may be a single value or an array (needs ANY one of them). const required = Array.isArray(permission) ? permission : [permission]; if (!required.some(p => granted.includes(p))) { throw new FGADeniedError(null, resource, permission, 'actor lacks required permission'); } } } // The acting agent is now identified and constrained by its granted permissions. await agent.generate('run nightly report', { actor: { actorKind: 'system', agentId: 'nightly-agent', permissions: ['agents:execute', 'tools:execute'], }, }); ``` - Added an optional `networking` capability and `writeFiles()` method to the `WorkspaceSandbox` interface. Sandbox providers that expose public port URLs can now implement `networking.getPortUrl(port)`, which enables preview URLs and sandbox deploys (see the new `@mastra/deployer-sandbox` package). Use the new `supportsNetworking()` type guard to detect the capability at runtime. ([#19577](https://github.com/mastra-ai/mastra/pull/19577)) ```typescript import { supportsNetworking } from '@mastra/core/workspace'; if (supportsNetworking(sandbox)) { const url = await sandbox.networking.getPortUrl(4111); } ``` - Added `onTitleGenerated` callback to memory options. When `generateTitle` is enabled, pass `onTitleGenerated` in the `memory` property of `agent.generate()` or `agent.stream()` to be notified when the thread title has been generated and persisted to storage. This is a per-request callback, so each request handler can use its own callback with direct access to its response stream — no storage adapter wrapping needed. ([#18998](https://github.com/mastra-ai/mastra/pull/18998)) **Example:** ```ts await agent.stream('Hello', { memory: { thread: threadId, resource: userId, onTitleGenerated: title => { res.write(`event: title\ndata: ${JSON.stringify({ title })}\n\n`); }, }, }); ``` - Update the experimental AgentController message surface to use the canonical `MastraDBMessage` shape. ([#18783](https://github.com/mastra-ai/mastra/pull/18783)) The AgentController now emits, persists, and returns DB-native messages where message parts live under `content.parts`, terminal status lives under `content.metadata`, and completed tool invocations retain their explicit error state. Signals such as system reminders and notifications now arrive as separate messages with `role: 'signal'` instead of being flattened into assistant message content. This affects the experimental AgentController event and session APIs, including `message_start`, `message_update`, `message_end`, `currentMessage`, `listMessages`, `listActiveMessages`, `firstUserMessage`, and `firstUserMessages`. ```ts agentController.subscribe(event => { if (event.type === 'message_end' && event.message.role === 'assistant') { // Before: parts were flattened directly onto the message // After: read parts and terminal status from the DB-native shape const text = event.message.content.parts .filter(part => part.type === 'text') .map(part => part.text) .join(''); const stopReason = event.message.content.metadata?.stopReason; } }); // System reminders and notifications are now separate messages const messages = await agentController.session.thread.listActiveMessages(); const signals = messages.filter(message => message.role === 'signal'); ``` - Added a FactoryStorage contract that owns application storage domains, initializes them with the shared backend, and reports per-domain readiness and initialization errors. ([#19681](https://github.com/mastra-ai/mastra/pull/19681)) ```ts import { FactoryStorageDomain } from '@mastra/core/storage'; import { LibSQLFactoryStorage } from '@mastra/libsql'; class TasksStorage extends FactoryStorageDomain { constructor() { super('tasks'); } async init() { await this.ensureCollections([{ name: 'tasks', columns: { id: { type: 'uuid-pk' } } }]); } async dangerouslyClearAll() { await this.ops.deleteMany('tasks', {}); } } const storage = new LibSQLFactoryStorage({ url: 'file:mastra.db' }); storage.registerDomain(new TasksStorage()); await storage.init(); ``` - Added request-scoped model overrides to agent execution and approvals, and fixed Studio model selection so each tab can use a different model without changing the agent's configured default. ([#19749](https://github.com/mastra-ai/mastra/pull/19749)) ```ts await agent.stream(messages, { model: 'google/gemini-2.5-flash' }); ``` - Added access to the workspace resolved for an AgentController session. ([#19547](https://github.com/mastra-ai/mastra/pull/19547)) Use the session-owned workspace when an operation must remain isolated to that session: ```ts const session = await controller.createSession({ resourceId, scope }); const workspace = session.getWorkspace(); ``` Mastra Code workspace resolvers can now accept an isolated read-only skill extension: ```ts const workspace = await getDynamicWorkspace({ requestContext, skillExtension: { id: 'review-skills', paths: ['/__review_skills__'], createSource: fallback => new ReviewSkillSource(fallback), }, }); ``` This lets SDK consumers compose additional read-only skill roots into selected workspaces without changing the default workspace skill set. - Added an optional `clone()` method to the `WorkspaceSandbox` contract, along with the new `SandboxCloneOptions` type. `clone()` constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides — without performing any I/O. This lets one configured sandbox act as a template for a fleet of sandbox clones (for example, one per project). ([#19616](https://github.com/mastra-ai/mastra/pull/19616)) ```ts const template = new RailwaySandbox({ token, environmentId }); // Fresh sandbox clone for a project — provisions on start() const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start(); ``` `LocalSandbox` implements `clone()` out of the box. - Added an optional `threadId` parameter to AgentController session creation so hosts can create or resume a session bound to an exact thread ID. ([#19758](https://github.com/mastra-ai/mastra/pull/19758)) - Added multi-turn support to `runEvals`. Data items can now include an `inputs: string[]` array — each entry is sent sequentially to the agent on the same thread, and scorers see the accumulated output from all turns. ([#18395](https://github.com/mastra-ai/mastra/pull/18395)) **What changed** - `RunEvalsDataItem` accepts an optional `inputs` array for multi-turn conversations - Each turn runs `agent.generate()` with the same `threadId`, preserving conversation history - `runEvals` injects both a shared `threadId` and a `resourceId` (Mastra memory scopes messages by resource + thread, so both are needed for recall); the resource defaults to the generated thread and a caller-provided `targetOptions.memory.resource` is preserved. `thread` is now optional in `targetOptions.memory` since `runEvals` owns it. - Scorers receive the accumulated output messages from all turns - Works with gates, thresholds, and all existing scorer configurations - Validation rejects empty `inputs` arrays with a `MastraError` Also added a `turns` API for **per-turn assertions**. Instead of a single holistic score over the accumulated conversation (which can hide a broken follow-up turn), each turn colocates its `input` with `gates`/`scorers` that evaluate only that turn's input and output. Per-turn outcomes are reported in `result.turnResults` and folded into the overall `verdict`. When the agent has storage configured, per-turn scorer/gate results are persisted like top-level scores. `turns` is Agent-only and mutually exclusive with `input`/`inputs`. **Example — holistic multi-turn (`inputs`)** ```ts import { runEvals } from '@mastra/core/evals'; import { checks } from '@mastra/evals/checks'; const result = await runEvals({ target: weatherAgent, data: [ { inputs: ['What is the weather in Brooklyn?', 'What about tomorrow?', 'Compare the two forecasts.'], }, ], scorers: [checks.calledTool('get_weather', { times: 2 }), checks.includes('Brooklyn')], }); ``` **Example — per-turn assertions (`turns`)** ```ts const result = await runEvals({ target: weatherAgent, data: [ { turns: [ { input: 'What is the weather in Brooklyn?', gates: [checks.calledTool('get_weather')], }, { input: 'What about tomorrow?', gates: [checks.calledTool('get_weather')], // must call again this turn scorers: [{ scorer: checks.similarity('tomorrow forecast'), threshold: 0.5 }], }, ], }, ], }); result.verdict; // folds in per-turn gate/threshold outcomes result.turnResults; // [{ index, gateResults, thresholdResults, scores }] ``` ### Patch Changes - dependencies updates: ([#19611](https://github.com/mastra-ai/mastra/pull/19611)) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.9` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.9) (from `npm:@ai-sdk/provider-utils@5.0.7`, in `dependencies`) - dependencies updates: ([#19776](https://github.com/mastra-ai/mastra/pull/19776)) - Updated dependency [`chat@^4.34.0` ↗︎](https://www.npmjs.com/package/chat/v/4.34.0) (from `^4.29.0`, in `dependencies`) - dependencies updates: ([#19813](https://github.com/mastra-ai/mastra/pull/19813)) - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.30` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.30) (from `npm:@ai-sdk/provider-utils@3.0.28`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.40` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.40) (from `npm:@ai-sdk/provider-utils@4.0.38`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.11` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.11) (from `npm:@ai-sdk/provider-utils@5.0.9`, in `dependencies`) - Added a public ensureReady() method to FactoryStorageDomain so consumers can initialize a storage domain without going through a global registry. ([#19866](https://github.com/mastra-ai/mastra/pull/19866)) - Added a `durable` field to `AgentConfig` so an agent can opt into durable execution without a separate `createDurableAgent` call. Setting `durable: true` (or `durable: { cache, pubsub, maxSteps, cleanupTimeoutMs }`) auto-wraps the agent with `createDurableAgent` when it is attached to a `Mastra` instance; the factory function remains available for advanced use. ([#19371](https://github.com/mastra-ai/mastra/pull/19371)) ```ts const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model, durable: true, // or: { maxSteps: 10, cleanupTimeoutMs: 60_000 } }); export const mastra = new Mastra({ agents: { agent } }); ``` - Fixed `new Agent({ durable: true })` to actually use the durable execution path when used standalone (without being registered on a `Mastra` instance). ([#19632](https://github.com/mastra-ai/mastra/pull/19632)) Previously, `durable: true` only took effect when the agent was attached to a `Mastra`. Constructing an agent with `durable: true` and calling `agent.stream(...)` directly silently ran through the non-durable path. Now `stream`, `generate`, `resumeStream`, `resumeGenerate`, `approveToolCall`, `declineToolCall`, `streamUntilIdle`, `resume`, `recover`, `listActiveRuns`, `recoverActiveRuns`, `observe`, and `prepare` all run through the durable execution path on a standalone `new Agent({ durable: true })`. ```ts const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model, durable: true, }); // Now runs through the durable execution path. await agent.stream('hi'); ``` - Update provider registry and model documentation with latest models and providers ([`8a0d145`](https://github.com/mastra-ai/mastra/commit/8a0d145aadbdf7278665aceaaec364b35dd9bd94)) - Fixed model router requests through OpenRouter failing with a 400 "thinking blocks cannot be modified" error when an Anthropic model (e.g. openrouter/anthropic/claude-sonnet-4-6) used extended thinking together with parallel tool calls. The bundled OpenRouter provider was updated from 1.5.4 to 2.10.0, which no longer duplicates reasoning details on each tool call when sending conversation history back to the API. Fixes #19436 ([#19493](https://github.com/mastra-ai/mastra/pull/19493)) - Fixed bounded process output stalling other requests while high-volume commands continue streaming. ([#19600](https://github.com/mastra-ai/mastra/pull/19600)) - Fixed goal judge results rendering more than once during live goal runs. ([#19613](https://github.com/mastra-ai/mastra/pull/19613)) - Ensured that canceled workflow results correctly format and deduplicate their payloads before returning. ([#19225](https://github.com/mastra-ai/mastra/pull/19225)) - Fixed fine-grained authorization (FGA) checks for `DurableAgent` execution and tool approvals. ([#19338](https://github.com/mastra-ai/mastra/pull/19338)) **Durable agents now enforce `agents:execute`** `DurableAgent` overrides `stream()` and `generate()` to run a workflow instead of the base agent path. Those methods now resolve default and per-call options once, then authorize the same effective actor, request context, and memory resource used for execution. Behavior change: with an FGA provider configured, durable runs that were previously allowed through are now checked and can be denied. **Durable resumes reauthorize each call** Durable approval and decline methods now forward the trusted request context and selected tool-call ID into resume authorization and workflow resume. The actor is resolved for each call and forwarded to both agent authorization and tool execution. An explicit resume actor replaces the initial actor, while a newly resolved default actor can still apply. If neither exists, the resume uses normal user authorization and fails closed when no user is available. - Added optional auth provider capability interfaces and type guards to `@mastra/core/server`: `IOrganizationsProvider` (personal organization bootstrap and admin checks), `IAuthInit` (host-driven initialization with database, public URL, and allowed origins), and `IAuthHttpHandler` (mounting a provider's own HTTP auth endpoints). Server hosts can use the `isOrganizationsProvider`, `isAuthHttpHandler`, and `hasAuthInit` guards to detect what an auth provider supports and wire routes accordingly. ([#19765](https://github.com/mastra-ai/mastra/pull/19765)) - Fixed Babel 8 compatibility for build-time transforms. ([#19393](https://github.com/mastra-ai/mastra/pull/19393)) - Fixed caller-supplied tool providers so selected connectionless tools can initialize and manage user connections. ([#19872](https://github.com/mastra-ai/mastra/pull/19872)) - Updated the unsupported-adapter error for resource-scoped message listing to include convex in the list of storage adapters that support Observational Memory. ([#19474](https://github.com/mastra-ai/mastra/pull/19474)) - Added native structured-output support lookup through the model provider registry. ([#19440](https://github.com/mastra-ai/mastra/pull/19440)) ```ts import { modelSupportsStructuredOutput } from '@mastra/core/llm'; const supportsStructuredOutput = modelSupportsStructuredOutput('openai/gpt-5.5'); ``` - Cap the LLM-provided delegation `maxSteps` at the sub-agent's own `defaultOptions.maxSteps`. Previously a supervisor's model could silently raise a sub-agent's step budget past its configured default via the delegation tool's optional `maxSteps` argument. The supervisor's model can still reduce the budget, and `onDelegationStart`'s `modifiedMaxSteps` (developer code) still overrides the cap. A warning is logged when a value is capped. ([#19529](https://github.com/mastra-ai/mastra/pull/19529)) - Fixed `requestContext` typing inside a tool's `execute` callback. It is now non-optional, matching runtime behavior: Mastra always provides a `RequestContext` (creating an empty one when the caller passed none), and when `requestContextSchema` is defined the context is validated before `execute` runs — on failure the tool returns a validation error and `execute` is never called. No more null-checks, throws, or tool factories needed to satisfy the compiler. ([#19680](https://github.com/mastra-ai/mastra/pull/19680)) ```typescript const tool = createTool({ id: 'fetch-doc', requestContextSchema: z.object({ documentId: z.string(), userId: z.string() }), execute: async (input, context) => { // Before: context.requestContext was RequestContext<...> | undefined, // forcing ?. / ! / throw even though the runtime guarantees it exists // After: typed as RequestContext<{ documentId: string; userId: string }> const documentId = context.requestContext.get('documentId'); // string }, }); ``` Callers of `tool.execute(...)` are unaffected — passing a context remains optional there. Fixes [#19480](https://github.com/mastra-ai/mastra/issues/19480) - Fixed DurableAgent throwing ToolNotFoundError when a ToolSearchProcessor meta-tool (search_tools / load_tool) was called. The durable tool-call step now resolves processor-injected per-step tools, matching the regular Agent. ([#19578](https://github.com/mastra-ai/mastra/pull/19578)) - Improve stored `runEvals` per-turn scores (follow-up to multi-turn `turns`). Each persisted per-turn scorer/gate result is now labeled with its turn index (`metadata.turnIndex`), carries the conversation's shared `threadId`, and links to that turn's own trace span instead of the item-level span. This lets the scores UI group and label per-turn scores by conversation and turn, and resolve each score to the correct trace. ([#19491](https://github.com/mastra-ai/mastra/pull/19491)) - Fixed unbounded memory growth during long goal runs. A goal run chains many agent turns inside one stream, and the stream previously kept every chunk, step, tool result, and text delta of the entire run in memory (and in suspend snapshots). Goal evaluations now carry the goal gate's explicit `shouldContinue` decision, and the stream clears its run-lifetime buffers at each continuing evaluation — the judged turn's messages are already persisted by then. Terminal evaluations (completion, waiting for user, judge failure, budget exhaustion) do not truncate, so the final turn is preserved. ([#19663](https://github.com/mastra-ai/mastra/pull/19663)) **Behavior change for goal runs:** run-end results now cover the final iteration of the run (everything after the last continuing evaluation) instead of every turn. Streamed chunks, token usage totals, and persisted messages are unaffected — only the aggregates resolved at the end of the run change. Agents without a `goal` config are unaffected. ```ts const agent = new Agent({ name: 'coder', model: 'openai/gpt-5.5', goal: { judge: 'openai/gpt-5-mini' }, }); const stream = await agent.stream('Implement feature X'); const output = await stream.getFullOutput(); // Before: output.text, output.steps, output.toolCalls, and output.toolResults // aggregated every turn of the goal run (e.g. 50 turns concatenated). // After: they cover the final iteration — the turn(s) judged by the terminal // evaluation, i.e. the goal's final answer. Use output.messages (or memory) // for the full conversation; output.totalUsage still spans the entire run. ``` - Fixed native Agent goals continuing to invoke the judge after an unretried API error. ([#19662](https://github.com/mastra-ai/mastra/pull/19662)) - Fixed `onIterationComplete` callbacks receiving empty `toolResults` after successful tool execution. Tool results now include the matching call ID, tool name, and output from the completed iteration. Fixes [#19453](https://github.com/mastra-ai/mastra/issues/19453). ([#19454](https://github.com/mastra-ai/mastra/pull/19454)) - Fixed generated files from AI SDK v7 models (e.g. gpt-image-1 image output) being corrupted in stream output and saved message history. The tagged V4 file data is now converted to the flat shape Mastra's stream pipeline and message storage expect. This covers both `file` and `reasoning-file` response parts. ([#19430](https://github.com/mastra-ai/mastra/pull/19430)) Also fixed handling of URL-backed generated files: the URL is no longer mislabeled as base64 in file chunks, UI message streams now emit the URL directly instead of a broken data URI, and reading `.uint8Array` on a URL-backed generated file now throws a descriptive error instead of returning garbage. - Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. ([#19849](https://github.com/mastra-ai/mastra/pull/19849)) Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option. **Before** ```ts const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir }; ``` **After** ```ts const state = { factoryProjectId: 'factory-project-1', projectRepositoryId: 'project-repository-1', sandboxId, sandboxWorkdir, }; ``` - Fixed goal judges changing the parent agent's observational-memory model state. ([#19710](https://github.com/mastra-ai/mastra/pull/19710)) - Fixed durable agent runs continuing after a tool call was denied by authorization. The run now fails immediately instead of letting the model retry the denied tool. ([#19886](https://github.com/mastra-ai/mastra/pull/19886)) - Fixed answering an ask_user question failing with a misleading "could not find a suspended run" error when the suspended run died before its state was saved (for example when persisting the suspended snapshot failed). The unresumable question is now retracted, a late answer is ignored, and the original error is surfaced instead. ([#19444](https://github.com/mastra-ai/mastra/pull/19444)) - Fixed prebuilt agent signals so callers can supply per-run request context. ([#19702](https://github.com/mastra-ai/mastra/pull/19702)) - Fixed Code Mode executions failing when programs return large results or error messages. ([#19878](https://github.com/mastra-ai/mastra/pull/19878)) - Aligned A2AAgent streams with the regular Agent stream contract. A2A streams now emit a leading start chunk on fresh runs (skipped when resuming, matching Agent behavior) and the finish chunk now carries the Agent-shaped payload (stepResult.reason, output.usage) so downstream consumers can treat sub-agent streams uniformly. The previous flat finishReason and usage fields are still included for backward compatibility but are deprecated. ([#19517](https://github.com/mastra-ai/mastra/pull/19517)) - Updated the bundled provider SDKs used by the model router for Cerebras, DeepInfra, DeepSeek, Perplexity, Together AI, and OpenAI-compatible endpoints (including custom provider URLs and the Netlify gateway) to their AI SDK v6-compatible versions. This aligns them with the other providers in the model router (OpenAI, Anthropic, Google, Groq, Mistral, xAI, OpenRouter) which already use v6-compatible SDKs, and picks up upstream provider fixes. No changes to the public API: model strings like cerebras/llama-3.3-70b or deepseek/deepseek-chat keep working as before. Unused v5-track provider SDK bundles (Groq, Mistral, xAI, and the migrated providers above) were removed from the bundle. ([#19495](https://github.com/mastra-ai/mastra/pull/19495)) - Updated embedding model routing to use the AI SDK v6 provider SDKs (OpenAI, Google, and OpenAI-compatible) while preserving the existing embedding interface. Existing embedding model configurations continue to work without code changes. ([#19500](https://github.com/mastra-ai/mastra/pull/19500)) - Fixed workflow cancellation for tool-wrapped steps. Cooperative tools can now stop early when `run.cancel()` is called. Fixes #19599. ([#19602](https://github.com/mastra-ai/mastra/pull/19602)) - Added a working directory override when deriving local sandboxes so callers can isolate each derived checkout. ([#19907](https://github.com/mastra-ai/mastra/pull/19907)) - Add `jsonPromptInjection: 'auto'` to select native structured output when model capability data confirms support and inline JSON prompt injection otherwise. Scorer judges use this automatic path by default while preserving explicit `jsonPromptInjection` overrides and fallback retries for unexpected provider failures. ([#19442](https://github.com/mastra-ai/mastra/pull/19442)) - Added `checkpointName` to sandbox derive options so providers can attach durable checkpoint identities to derived sandboxes. ([#19907](https://github.com/mastra-ai/mastra/pull/19907)) - Fixed dataset item writes to reject circular payloads with a clear validation error before storage, instead of failing with database-specific serialization errors. Silently lossy JSON values (nested `undefined`, functions, symbols, bigints, and non-finite numbers) and non-plain objects (`Date`, `Map`, `Set`, class instances, and custom `toJSON()` objects) are now also rejected with the offending path, so identical `externalId` retries can no longer conflict with the persisted payload. Added a public `safeStringify` utility for serializing values that may contain circular references. ([#19603](https://github.com/mastra-ai/mastra/pull/19603)) ```ts import { safeStringify, ensureSerializable } from '@mastra/core/utils/safe-stringify'; const value: Record = { prompt: 'hello' }; value.self = value; safeStringify(value); // '{"prompt":"hello","self":"[Circular]"}' ensureSerializable(value); // { prompt: 'hello', self: '[Circular]' } ``` - Remove leftover branches that selected the evented workflow engine inside the regular `Agent` loop. The agentic-execution workflow now always uses the in-process workflow engine, and `Agent` no longer maintains an ephemeral `Mastra` (with its own pubsub and `startWorkers()` lifecycle) just to host the evented path. This removes a class of subtle behavior differences between the two engines from `Agent.stream()` / `Agent.generate()` and simplifies the suspend/resume scope lifecycle. ([#18666](https://github.com/mastra-ai/mastra/pull/18666)) No public API changes. The evented workflow infrastructure itself is unchanged and continues to be used by background tasks, notifications, the score-traces workflow, and other subsystems that explicitly opt into it. - Fixed `requestContext` typing in the dynamic `skills` agent option. When an agent defines `requestContextSchema`, the `skills` callback now receives a typed `RequestContext` (with key autocomplete and typo checking), matching `instructions`, `tools`, `memory`, and the other dynamic options. Previously it was always `RequestContext`. ([#19554](https://github.com/mastra-ai/mastra/pull/19554)) ```typescript const agent = new Agent({ requestContextSchema: z.object({ documentId: z.string(), userId: z.string() }), // Before: requestContext was RequestContext — any key compiled // After: requestContext is RequestContext<{ documentId: string; userId: string }> skills: ({ requestContext }) => { requestContext.get('documentId'); // typed as string return ['./skills/basic']; }, }); ``` Fixes [#19553](https://github.com/mastra-ai/mastra/issues/19553) - Fixed thread cloning to preserve configured memory state, including observational memory progress. ([#19917](https://github.com/mastra-ai/mastra/pull/19917)) - Fix the `createDurableAgent` JSDoc examples to construct `RedisServerCache` with a connected Redis client (`{ client }`) instead of a `{ url }` config the class does not accept. ([#19569](https://github.com/mastra-ai/mastra/pull/19569)) - Added stable metrics and logs capability reporting for observability storage. The system packages response now includes `observabilityStorageCapabilities` with `metrics` and `logs` flags, enabling capability-based detection that is resilient to bundler-generated constructor name changes. ([#19305](https://github.com/mastra-ai/mastra/pull/19305)) ```typescript const packages = await client.getSystemPackages(); console.log(packages.observabilityStorageCapabilities?.metrics); // true console.log(packages.observabilityStorageCapabilities?.logs); // true ``` Studio now uses the capability response instead of relying on constructor names, with a fallback for older servers. - Fixed background tasks never executing when Mastra is used as a library without a Mastra server. Previously, background-eligible tool calls (including background subagent delegations) were dispatched but stayed in the `running` state forever because the task workers only started with a Mastra server (`mastra dev` or a deployed server). Now the workers start automatically on the first background task dispatch or resume, so background tasks complete in apps that embed Mastra directly (for example Express or Next.js servers). Fixes [#19339](https://github.com/mastra-ai/mastra/issues/19339). ([#19476](https://github.com/mastra-ai/mastra/pull/19476)) - Fixed durable goal objectives so active execution time is preserved across runs and excludes tool approval waits. ([#19837](https://github.com/mastra-ai/mastra/pull/19837)) - Fixed a crash on Cloudflare Workers where calling generate() on an Agent not registered to a Mastra instance threw `TypeError: this.#intervalHandle.unref is not a function`. The scheduler now only calls unref() on its polling interval when the runtime provides it (Node.js); on runtimes where setInterval returns a number (workerd) the call is skipped. Fixes [#19462](https://github.com/mastra-ai/mastra/issues/19462). ([#19471](https://github.com/mastra-ai/mastra/pull/19471)) - Fixed composite storage initialization so transient failures can retry without restarting the process. ([#19697](https://github.com/mastra-ai/mastra/pull/19697)) - Moved the server config routes and provider credential helpers into @mastra/factory as a reusable ConfigRoutes class. Route handlers now receive their auth checks through an injected RouteAuth seam and storage domains through constructor options, so hosts other than the Mastra Code web app can mount the same routes. ([#19866](https://github.com/mastra-ai/mastra/pull/19866)) - Fixed workspace LSP support for TypeScript 7 projects. Code inspection (diagnostics, hover) now works in workspaces using TypeScript 7, including hoisted monorepo and pnpm installations. No configuration changes are needed — existing `lsp: true` setups keep working, and TypeScript 6 and earlier behavior is unchanged. ([#19618](https://github.com/mastra-ai/mastra/pull/19618)) Fixes [#19601](https://github.com/mastra-ai/mastra/issues/19601) - Fixed agents with channels failing on Cloudflare Workers with `No such module "chat"`. ([#19570](https://github.com/mastra-ai/mastra/pull/19570)) Agents with Slack, Discord, or Telegram channels now deploy to Cloudflare Workers and other serverless targets without extra configuration — channels initialize correctly and webhooks work out of the box. The `bundler.dynamicPackages: ['chat']` workaround is no longer needed for Node deploys. Projects that don't use channels still load the Chat SDK lazily. Fixes #19254 ## 1.52.0-alpha.13 ## 1.52.0-alpha.12 ### Patch Changes - dependencies updates: ([#19776](https://github.com/mastra-ai/mastra/pull/19776)) - Updated dependency [`chat@^4.34.0` ↗︎](https://www.npmjs.com/package/chat/v/4.34.0) (from `^4.29.0`, in `dependencies`) - Added a public ensureReady() method to FactoryStorageDomain so consumers can initialize a storage domain without going through a global registry. ([#19866](https://github.com/mastra-ai/mastra/pull/19866)) - Fixed durable agent runs continuing after a tool call was denied by authorization. The run now fails immediately instead of letting the model retry the denied tool. ([#19886](https://github.com/mastra-ai/mastra/pull/19886)) - Fixed Code Mode executions failing when programs return large results or error messages. ([#19878](https://github.com/mastra-ai/mastra/pull/19878)) - Moved the server config routes and provider credential helpers into @mastra/factory as a reusable ConfigRoutes class. Route handlers now receive their auth checks through an injected RouteAuth seam and storage domains through constructor options, so hosts other than the Mastra Code web app can mount the same routes. ([#19866](https://github.com/mastra-ai/mastra/pull/19866)) ## 1.52.0-alpha.11 ### Minor Changes - Added an optional `threadId` parameter to AgentController session creation so hosts can create or resume a session bound to an exact thread ID. ([#19758](https://github.com/mastra-ai/mastra/pull/19758)) ### Patch Changes - Fixed caller-supplied tool providers so selected connectionless tools can initialize and manage user connections. ([#19872](https://github.com/mastra-ai/mastra/pull/19872)) - Fixed prebuilt agent signals so callers can supply per-run request context. ([#19702](https://github.com/mastra-ai/mastra/pull/19702)) - Added a working directory override when deriving local sandboxes so callers can isolate each derived checkout. ([#19907](https://github.com/mastra-ai/mastra/pull/19907)) - Added `checkpointName` to sandbox derive options so providers can attach durable checkpoint identities to derived sandboxes. ([#19907](https://github.com/mastra-ai/mastra/pull/19907)) - Fixed thread cloning to preserve configured memory state, including observational memory progress. ([#19917](https://github.com/mastra-ai/mastra/pull/19917)) ## 1.52.0-alpha.10 ### Minor Changes - Added optional provider-driven authorization for system (non-user) actors, enabling per-agent least privilege after tenant scope has been validated. ([#19338](https://github.com/mastra-ai/mastra/pull/19338)) System actors (`actor: { actorKind: 'system' }` or `actor: true`) skip the user-centric FGA path. Previously the only boundary for them was tenant scope — there was no way to constrain which agent could do what, and no deny path. FGA providers can now opt in to enforce least privilege for those actors. **What's new** - `IFGAProvider` gains an optional `requireActor(actor, params)` method that throws `FGADeniedError` to deny. - `ActorSignal` gains optional `agentId`, `permissions`, and `scope` so a provider can identify and constrain the acting agent. `permissions` reuses the `MastraFGAPermissionInput` vocabulary — the actor analog of a user's resolved permissions. It is a self-asserted claim: a provider enforcing real least privilege should resolve authoritative grants from a trusted source keyed by `agentId` rather than trusting it directly. - When a provider does not implement `requireActor`, the existing trusted-actor bypass is preserved exactly — this change is fully backward compatible. **Before** — system actors had tenant scoping but no provider-defined per-agent authorization: ```ts // Cron/system run: tenant scope is required, but no per-agent limits are possible. await agent.generate('run nightly report', { actor: { actorKind: 'system', sourceWorkflow: 'nightly' }, }); ``` **After** — a provider enforces least privilege on the acting agent: ```ts import type { IFGAProvider } from '@mastra/core/auth/ee'; import { FGADeniedError } from '@mastra/core/auth/ee'; class MyFga implements IFGAProvider { // ...existing check / require / filterAccessible... async requireActor(actor, { resource, permission }) { const agentId = actor === true ? undefined : actor.agentId; // Resolve authoritative grants from a trusted source keyed by agentId. const granted = await this.grantsForAgent(agentId); // `permission` may be a single value or an array (needs ANY one of them). const required = Array.isArray(permission) ? permission : [permission]; if (!required.some(p => granted.includes(p))) { throw new FGADeniedError(null, resource, permission, 'actor lacks required permission'); } } } // The acting agent is now identified and constrained by its granted permissions. await agent.generate('run nightly report', { actor: { actorKind: 'system', agentId: 'nightly-agent', permissions: ['agents:execute', 'tools:execute'], }, }); ``` - Added request-scoped model overrides to agent execution and approvals, and fixed Studio model selection so each tab can use a different model without changing the agent's configured default. ([#19749](https://github.com/mastra-ai/mastra/pull/19749)) ```ts await agent.stream(messages, { model: 'google/gemini-2.5-flash' }); ``` ### Patch Changes - dependencies updates: ([#19813](https://github.com/mastra-ai/mastra/pull/19813)) - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.30` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.30) (from `npm:@ai-sdk/provider-utils@3.0.28`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.40` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.40) (from `npm:@ai-sdk/provider-utils@4.0.38`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.11` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.11) (from `npm:@ai-sdk/provider-utils@5.0.9`, in `dependencies`) - Fixed fine-grained authorization (FGA) checks for `DurableAgent` execution and tool approvals. ([#19338](https://github.com/mastra-ai/mastra/pull/19338)) **Durable agents now enforce `agents:execute`** `DurableAgent` overrides `stream()` and `generate()` to run a workflow instead of the base agent path. Those methods now resolve default and per-call options once, then authorize the same effective actor, request context, and memory resource used for execution. Behavior change: with an FGA provider configured, durable runs that were previously allowed through are now checked and can be denied. **Durable resumes reauthorize each call** Durable approval and decline methods now forward the trusted request context and selected tool-call ID into resume authorization and workflow resume. The actor is resolved for each call and forwarded to both agent authorization and tool execution. An explicit resume actor replaces the initial actor, while a newly resolved default actor can still apply. If neither exists, the resume uses normal user authorization and fails closed when no user is available. - Added optional auth provider capability interfaces and type guards to `@mastra/core/server`: `IOrganizationsProvider` (personal organization bootstrap and admin checks), `IAuthInit` (host-driven initialization with database, public URL, and allowed origins), and `IAuthHttpHandler` (mounting a provider's own HTTP auth endpoints). Server hosts can use the `isOrganizationsProvider`, `isAuthHttpHandler`, and `hasAuthInit` guards to detect what an auth provider supports and wire routes accordingly. ([#19765](https://github.com/mastra-ai/mastra/pull/19765)) - Fixed DurableAgent throwing ToolNotFoundError when a ToolSearchProcessor meta-tool (search_tools / load_tool) was called. The durable tool-call step now resolves processor-injected per-step tools, matching the regular Agent. ([#19578](https://github.com/mastra-ai/mastra/pull/19578)) - Replaced GitHub-specific Mastra Code session state with Factory project and linked-repository identities. This lets SDK consumers represent sessions independently of a source-control provider and select a repository explicitly when sandbox execution is required. ([#19849](https://github.com/mastra-ai/mastra/pull/19849)) Updated Mastra Code onboarding to be Factory-first: create a Factory by name, then link repositories from your connected source-control installations in a separate step. A Factory is valid with zero linked repositories, and the Board, Metrics, and Audit pages stay available for any server-backed Factory. Factory pages keep project-scoped data separate from repository-scoped intake and provide a repository selector when a Factory has multiple linked repositories. Creating a Factory from a local folder remains available as a secondary option. **Before** ```ts const state = { githubProjectId: 'project-1', sandboxId, sandboxWorkdir }; ``` **After** ```ts const state = { factoryProjectId: 'factory-project-1', projectRepositoryId: 'project-repository-1', sandboxId, sandboxWorkdir, }; ``` - Fixed durable goal objectives so active execution time is preserved across runs and excludes tool approval waits. ([#19837](https://github.com/mastra-ai/mastra/pull/19837)) - Fixed agents with channels failing on Cloudflare Workers with `No such module "chat"`. ([#19570](https://github.com/mastra-ai/mastra/pull/19570)) Agents with Slack, Discord, or Telegram channels now deploy to Cloudflare Workers and other serverless targets without extra configuration — channels initialize correctly and webhooks work out of the box. The `bundler.dynamicPackages: ['chat']` workaround is no longer needed for Node deploys. Projects that don't use channels still load the Chat SDK lazily. Fixes #19254 ## 1.52.0-alpha.9 ### Minor Changes - The 'workers' option on the Mastra class now merges with the default workers instead of replacing them. Passing custom workers (e.g. a polling worker for an integration) no longer silently drops the built-in orchestration and background-task workers — a custom worker only replaces a default when it shares its name, and 'workers: false' still disables all workers. ([#19766](https://github.com/mastra-ai/mastra/pull/19766)) ```ts const mastra = new Mastra({ // before: only myPoller ran — orchestration was dropped // after: myPoller runs alongside the default workers workers: [myPoller], }); ``` ### Patch Changes - Fixed goal judges changing the parent agent's observational-memory model state. ([#19710](https://github.com/mastra-ai/mastra/pull/19710)) ## 1.52.0-alpha.8 ### Minor Changes - Added a FactoryStorage contract that owns application storage domains, initializes them with the shared backend, and reports per-domain readiness and initialization errors. ([#19681](https://github.com/mastra-ai/mastra/pull/19681)) ```ts import { FactoryStorageDomain } from '@mastra/core/storage'; import { LibSQLFactoryStorage } from '@mastra/libsql'; class TasksStorage extends FactoryStorageDomain { constructor() { super('tasks'); } async init() { await this.ensureCollections([{ name: 'tasks', columns: { id: { type: 'uuid-pk' } } }]); } async dangerouslyClearAll() { await this.ops.deleteMany('tasks', {}); } } const storage = new LibSQLFactoryStorage({ url: 'file:mastra.db' }); storage.registerDomain(new TasksStorage()); await storage.init(); ``` ### Patch Changes - Fixed Babel 8 compatibility for build-time transforms. ([#19393](https://github.com/mastra-ai/mastra/pull/19393)) - Fixed `requestContext` typing inside a tool's `execute` callback. It is now non-optional, matching runtime behavior: Mastra always provides a `RequestContext` (creating an empty one when the caller passed none), and when `requestContextSchema` is defined the context is validated before `execute` runs — on failure the tool returns a validation error and `execute` is never called. No more null-checks, throws, or tool factories needed to satisfy the compiler. ([#19680](https://github.com/mastra-ai/mastra/pull/19680)) ```typescript const tool = createTool({ id: 'fetch-doc', requestContextSchema: z.object({ documentId: z.string(), userId: z.string() }), execute: async (input, context) => { // Before: context.requestContext was RequestContext<...> | undefined, // forcing ?. / ! / throw even though the runtime guarantees it exists // After: typed as RequestContext<{ documentId: string; userId: string }> const documentId = context.requestContext.get('documentId'); // string }, }); ``` Callers of `tool.execute(...)` are unaffected — passing a context remains optional there. Fixes [#19480](https://github.com/mastra-ai/mastra/issues/19480) ## 1.52.0-alpha.7 ### Patch Changes - Ensured that canceled workflow results correctly format and deduplicate their payloads before returning. ([#19225](https://github.com/mastra-ai/mastra/pull/19225)) - Fixed unbounded memory growth during long goal runs. A goal run chains many agent turns inside one stream, and the stream previously kept every chunk, step, tool result, and text delta of the entire run in memory (and in suspend snapshots). Goal evaluations now carry the goal gate's explicit `shouldContinue` decision, and the stream clears its run-lifetime buffers at each continuing evaluation — the judged turn's messages are already persisted by then. Terminal evaluations (completion, waiting for user, judge failure, budget exhaustion) do not truncate, so the final turn is preserved. ([#19663](https://github.com/mastra-ai/mastra/pull/19663)) **Behavior change for goal runs:** run-end results now cover the final iteration of the run (everything after the last continuing evaluation) instead of every turn. Streamed chunks, token usage totals, and persisted messages are unaffected — only the aggregates resolved at the end of the run change. Agents without a `goal` config are unaffected. ```ts const agent = new Agent({ name: 'coder', model: 'openai/gpt-5.5', goal: { judge: 'openai/gpt-5-mini' }, }); const stream = await agent.stream('Implement feature X'); const output = await stream.getFullOutput(); // Before: output.text, output.steps, output.toolCalls, and output.toolResults // aggregated every turn of the goal run (e.g. 50 turns concatenated). // After: they cover the final iteration — the turn(s) judged by the terminal // evaluation, i.e. the goal's final answer. Use output.messages (or memory) // for the full conversation; output.totalUsage still spans the entire run. ``` - Fixed composite storage initialization so transient failures can retry without restarting the process. ([#19697](https://github.com/mastra-ai/mastra/pull/19697)) ## 1.52.0-alpha.6 ### Patch Changes - Fixed `new Agent({ durable: true })` to actually use the durable execution path when used standalone (without being registered on a `Mastra` instance). ([#19632](https://github.com/mastra-ai/mastra/pull/19632)) Previously, `durable: true` only took effect when the agent was attached to a `Mastra`. Constructing an agent with `durable: true` and calling `agent.stream(...)` directly silently ran through the non-durable path. Now `stream`, `generate`, `resumeStream`, `resumeGenerate`, `approveToolCall`, `declineToolCall`, `streamUntilIdle`, `resume`, `recover`, `listActiveRuns`, `recoverActiveRuns`, `observe`, and `prepare` all run through the durable execution path on a standalone `new Agent({ durable: true })`. ```ts const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model, durable: true, }); // Now runs through the durable execution path. await agent.stream('hi'); ``` - Fixed native Agent goals continuing to invoke the judge after an unretried API error. ([#19662](https://github.com/mastra-ai/mastra/pull/19662)) - Fixed dataset item writes to reject circular payloads with a clear validation error before storage, instead of failing with database-specific serialization errors. Silently lossy JSON values (nested `undefined`, functions, symbols, bigints, and non-finite numbers) and non-plain objects (`Date`, `Map`, `Set`, class instances, and custom `toJSON()` objects) are now also rejected with the offending path, so identical `externalId` retries can no longer conflict with the persisted payload. Added a public `safeStringify` utility for serializing values that may contain circular references. ([#19603](https://github.com/mastra-ai/mastra/pull/19603)) ```ts import { safeStringify, ensureSerializable } from '@mastra/core/utils/safe-stringify'; const value: Record = { prompt: 'hello' }; value.self = value; safeStringify(value); // '{"prompt":"hello","self":"[Circular]"}' ensureSerializable(value); // { prompt: 'hello', self: '[Circular]' } ``` ## 1.52.0-alpha.5 ### Minor Changes - `mastra build` now deploys in one step for push-style deployers. Deployers can opt in with the new `deployOnBuild` flag on the deployer contract, and the build runs their `deploy()` right after bundling. `SandboxDeployer` from `@mastra/deployer-sandbox` opts in, so configuring it means `mastra build` bundles your project, deploys it into the sandbox, and prints the live URL. Existing platform deployers are unchanged. ([#19577](https://github.com/mastra-ai/mastra/pull/19577)) ```typescript import { Mastra } from '@mastra/core/mastra'; import { SandboxDeployer } from '@mastra/deployer-sandbox'; import { VercelSandbox } from '@mastra/vercel'; export const mastra = new Mastra({ deployer: new SandboxDeployer({ sandbox: new VercelSandbox({ sandboxName: 'my-preview', ports: [4111] }), }), }); ``` ```bash mastra build # bundles AND deploys — prints the live URL ``` - Added an optional `networking` capability and `writeFiles()` method to the `WorkspaceSandbox` interface. Sandbox providers that expose public port URLs can now implement `networking.getPortUrl(port)`, which enables preview URLs and sandbox deploys (see the new `@mastra/deployer-sandbox` package). Use the new `supportsNetworking()` type guard to detect the capability at runtime. ([#19577](https://github.com/mastra-ai/mastra/pull/19577)) ```typescript import { supportsNetworking } from '@mastra/core/workspace'; if (supportsNetworking(sandbox)) { const url = await sandbox.networking.getPortUrl(4111); } ``` - Added `onTitleGenerated` callback to memory options. When `generateTitle` is enabled, pass `onTitleGenerated` in the `memory` property of `agent.generate()` or `agent.stream()` to be notified when the thread title has been generated and persisted to storage. This is a per-request callback, so each request handler can use its own callback with direct access to its response stream — no storage adapter wrapping needed. ([#18998](https://github.com/mastra-ai/mastra/pull/18998)) **Example:** ```ts await agent.stream('Hello', { memory: { thread: threadId, resource: userId, onTitleGenerated: title => { res.write(`event: title\ndata: ${JSON.stringify({ title })}\n\n`); }, }, }); ``` - Added access to the workspace resolved for an AgentController session. ([#19547](https://github.com/mastra-ai/mastra/pull/19547)) Use the session-owned workspace when an operation must remain isolated to that session: ```ts const session = await controller.createSession({ resourceId, scope }); const workspace = session.getWorkspace(); ``` Mastra Code workspace resolvers can now accept an isolated read-only skill extension: ```ts const workspace = await getDynamicWorkspace({ requestContext, skillExtension: { id: 'review-skills', paths: ['/__review_skills__'], createSource: fallback => new ReviewSkillSource(fallback), }, }); ``` This lets SDK consumers compose additional read-only skill roots into selected workspaces without changing the default workspace skill set. - Added an optional `clone()` method to the `WorkspaceSandbox` contract, along with the new `SandboxCloneOptions` type. `clone()` constructs an unstarted sibling sandbox that inherits the template's configuration (credentials, image, resources) with per-instance overrides — without performing any I/O. This lets one configured sandbox act as a template for a fleet of sandbox clones (for example, one per project). ([#19616](https://github.com/mastra-ai/mastra/pull/19616)) ```ts const template = new RailwaySandbox({ token, environmentId }); // Fresh sandbox clone for a project — provisions on start() const projectSandbox = template.clone({ id: 'mc-project-42', env: { GITHUB_TOKEN: token }, idleTimeoutMinutes: 30, }); await projectSandbox.start(); ``` `LocalSandbox` implements `clone()` out of the box. ### Patch Changes - dependencies updates: ([#19611](https://github.com/mastra-ai/mastra/pull/19611)) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.9` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.9) (from `npm:@ai-sdk/provider-utils@5.0.7`, in `dependencies`) - Fixed bounded process output stalling other requests while high-volume commands continue streaming. ([#19600](https://github.com/mastra-ai/mastra/pull/19600)) - Fixed goal judge results rendering more than once during live goal runs. ([#19613](https://github.com/mastra-ai/mastra/pull/19613)) - Fixed workflow cancellation for tool-wrapped steps. Cooperative tools can now stop early when `run.cancel()` is called. Fixes #19599. ([#19602](https://github.com/mastra-ai/mastra/pull/19602)) - Fixed workspace LSP support for TypeScript 7 projects. Code inspection (diagnostics, hover) now works in workspaces using TypeScript 7, including hoisted monorepo and pnpm installations. No configuration changes are needed — existing `lsp: true` setups keep working, and TypeScript 6 and earlier behavior is unchanged. ([#19618](https://github.com/mastra-ai/mastra/pull/19618)) Fixes [#19601](https://github.com/mastra-ai/mastra/issues/19601) ## 1.52.0-alpha.4 ### Patch Changes - Improve stored `runEvals` per-turn scores (follow-up to multi-turn `turns`). Each persisted per-turn scorer/gate result is now labeled with its turn index (`metadata.turnIndex`), carries the conversation's shared `threadId`, and links to that turn's own trace span instead of the item-level span. This lets the scores UI group and label per-turn scores by conversation and turn, and resolve each score to the correct trace. ([#19491](https://github.com/mastra-ai/mastra/pull/19491)) ## 1.52.0-alpha.3 ### Minor Changes - Update the experimental AgentController message surface to use the canonical `MastraDBMessage` shape. ([#18783](https://github.com/mastra-ai/mastra/pull/18783)) The AgentController now emits, persists, and returns DB-native messages where message parts live under `content.parts`, terminal status lives under `content.metadata`, and completed tool invocations retain their explicit error state. Signals such as system reminders and notifications now arrive as separate messages with `role: 'signal'` instead of being flattened into assistant message content. This affects the experimental AgentController event and session APIs, including `message_start`, `message_update`, `message_end`, `currentMessage`, `listMessages`, `listActiveMessages`, `firstUserMessage`, and `firstUserMessages`. ```ts agentController.subscribe(event => { if (event.type === 'message_end' && event.message.role === 'assistant') { // Before: parts were flattened directly onto the message // After: read parts and terminal status from the DB-native shape const text = event.message.content.parts .filter(part => part.type === 'text') .map(part => part.text) .join(''); const stopReason = event.message.content.metadata?.stopReason; } }); // System reminders and notifications are now separate messages const messages = await agentController.session.thread.listActiveMessages(); const signals = messages.filter(message => message.role === 'signal'); ``` ### Patch Changes - Added a `durable` field to `AgentConfig` so an agent can opt into durable execution without a separate `createDurableAgent` call. Setting `durable: true` (or `durable: { cache, pubsub, maxSteps, cleanupTimeoutMs }`) auto-wraps the agent with `createDurableAgent` when it is attached to a `Mastra` instance; the factory function remains available for advanced use. ([#19371](https://github.com/mastra-ai/mastra/pull/19371)) ```ts const agent = new Agent({ id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model, durable: true, // or: { maxSteps: 10, cleanupTimeoutMs: 60_000 } }); export const mastra = new Mastra({ agents: { agent } }); ``` - Cap the LLM-provided delegation `maxSteps` at the sub-agent's own `defaultOptions.maxSteps`. Previously a supervisor's model could silently raise a sub-agent's step budget past its configured default via the delegation tool's optional `maxSteps` argument. The supervisor's model can still reduce the budget, and `onDelegationStart`'s `modifiedMaxSteps` (developer code) still overrides the cap. A warning is logged when a value is capped. ([#19529](https://github.com/mastra-ai/mastra/pull/19529)) - Remove leftover branches that selected the evented workflow engine inside the regular `Agent` loop. The agentic-execution workflow now always uses the in-process workflow engine, and `Agent` no longer maintains an ephemeral `Mastra` (with its own pubsub and `startWorkers()` lifecycle) just to host the evented path. This removes a class of subtle behavior differences between the two engines from `Agent.stream()` / `Agent.generate()` and simplifies the suspend/resume scope lifecycle. ([#18666](https://github.com/mastra-ai/mastra/pull/18666)) No public API changes. The evented workflow infrastructure itself is unchanged and continues to be used by background tasks, notifications, the score-traces workflow, and other subsystems that explicitly opt into it. - Fixed `requestContext` typing in the dynamic `skills` agent option. When an agent defines `requestContextSchema`, the `skills` callback now receives a typed `RequestContext` (with key autocomplete and typo checking), matching `instructions`, `tools`, `memory`, and the other dynamic options. Previously it was always `RequestContext`. ([#19554](https://github.com/mastra-ai/mastra/pull/19554)) ```typescript const agent = new Agent({ requestContextSchema: z.object({ documentId: z.string(), userId: z.string() }), // Before: requestContext was RequestContext — any key compiled // After: requestContext is RequestContext<{ documentId: string; userId: string }> skills: ({ requestContext }) => { requestContext.get('documentId'); // typed as string return ['./skills/basic']; }, }); ``` Fixes [#19553](https://github.com/mastra-ai/mastra/issues/19553) - Fix the `createDurableAgent` JSDoc examples to construct `RedisServerCache` with a connected Redis client (`{ client }`) instead of a `{ url }` config the class does not accept. ([#19569](https://github.com/mastra-ai/mastra/pull/19569)) - Added stable metrics and logs capability reporting for observability storage. The system packages response now includes `observabilityStorageCapabilities` with `metrics` and `logs` flags, enabling capability-based detection that is resilient to bundler-generated constructor name changes. ([#19305](https://github.com/mastra-ai/mastra/pull/19305)) ```typescript const packages = await client.getSystemPackages(); console.log(packages.observabilityStorageCapabilities?.metrics); // true console.log(packages.observabilityStorageCapabilities?.logs); // true ``` Studio now uses the capability response instead of relying on constructor names, with a fallback for older servers. ## 1.52.0-alpha.2 ### Minor Changes - Added multi-turn support to `runEvals`. Data items can now include an `inputs: string[]` array — each entry is sent sequentially to the agent on the same thread, and scorers see the accumulated output from all turns. ([#18395](https://github.com/mastra-ai/mastra/pull/18395)) **What changed** - `RunEvalsDataItem` accepts an optional `inputs` array for multi-turn conversations - Each turn runs `agent.generate()` with the same `threadId`, preserving conversation history - `runEvals` injects both a shared `threadId` and a `resourceId` (Mastra memory scopes messages by resource + thread, so both are needed for recall); the resource defaults to the generated thread and a caller-provided `targetOptions.memory.resource` is preserved. `thread` is now optional in `targetOptions.memory` since `runEvals` owns it. - Scorers receive the accumulated output messages from all turns - Works with gates, thresholds, and all existing scorer configurations - Validation rejects empty `inputs` arrays with a `MastraError` Also added a `turns` API for **per-turn assertions**. Instead of a single holistic score over the accumulated conversation (which can hide a broken follow-up turn), each turn colocates its `input` with `gates`/`scorers` that evaluate only that turn's input and output. Per-turn outcomes are reported in `result.turnResults` and folded into the overall `verdict`. When the agent has storage configured, per-turn scorer/gate results are persisted like top-level scores. `turns` is Agent-only and mutually exclusive with `input`/`inputs`. **Example — holistic multi-turn (`inputs`)** ```ts import { runEvals } from '@mastra/core/evals'; import { checks } from '@mastra/evals/checks'; const result = await runEvals({ target: weatherAgent, data: [ { inputs: ['What is the weather in Brooklyn?', 'What about tomorrow?', 'Compare the two forecasts.'], }, ], scorers: [checks.calledTool('get_weather', { times: 2 }), checks.includes('Brooklyn')], }); ``` **Example — per-turn assertions (`turns`)** ```ts const result = await runEvals({ target: weatherAgent, data: [ { turns: [ { input: 'What is the weather in Brooklyn?', gates: [checks.calledTool('get_weather')], }, { input: 'What about tomorrow?', gates: [checks.calledTool('get_weather')], // must call again this turn scorers: [{ scorer: checks.similarity('tomorrow forecast'), threshold: 0.5 }], }, ], }, ], }); result.verdict; // folds in per-turn gate/threshold outcomes result.turnResults; // [{ index, gateResults, thresholdResults, scores }] ``` ### Patch Changes - Added native structured-output support lookup through the model provider registry. ([#19440](https://github.com/mastra-ai/mastra/pull/19440)) ```ts import { modelSupportsStructuredOutput } from '@mastra/core/llm'; const supportsStructuredOutput = modelSupportsStructuredOutput('openai/gpt-5.5'); ``` - Fixed answering an ask_user question failing with a misleading "could not find a suspended run" error when the suspended run died before its state was saved (for example when persisting the suspended snapshot failed). The unresumable question is now retracted, a late answer is ignored, and the original error is surfaced instead. ([#19444](https://github.com/mastra-ai/mastra/pull/19444)) - Aligned A2AAgent streams with the regular Agent stream contract. A2A streams now emit a leading start chunk on fresh runs (skipped when resuming, matching Agent behavior) and the finish chunk now carries the Agent-shaped payload (stepResult.reason, output.usage) so downstream consumers can treat sub-agent streams uniformly. The previous flat finishReason and usage fields are still included for backward compatibility but are deprecated. ([#19517](https://github.com/mastra-ai/mastra/pull/19517)) - Updated the bundled provider SDKs used by the model router for Cerebras, DeepInfra, DeepSeek, Perplexity, Together AI, and OpenAI-compatible endpoints (including custom provider URLs and the Netlify gateway) to their AI SDK v6-compatible versions. This aligns them with the other providers in the model router (OpenAI, Anthropic, Google, Groq, Mistral, xAI, OpenRouter) which already use v6-compatible SDKs, and picks up upstream provider fixes. No changes to the public API: model strings like cerebras/llama-3.3-70b or deepseek/deepseek-chat keep working as before. Unused v5-track provider SDK bundles (Groq, Mistral, xAI, and the migrated providers above) were removed from the bundle. ([#19495](https://github.com/mastra-ai/mastra/pull/19495)) - Updated embedding model routing to use the AI SDK v6 provider SDKs (OpenAI, Google, and OpenAI-compatible) while preserving the existing embedding interface. Existing embedding model configurations continue to work without code changes. ([#19500](https://github.com/mastra-ai/mastra/pull/19500)) - Add `jsonPromptInjection: 'auto'` to select native structured output when model capability data confirms support and inline JSON prompt injection otherwise. Scorer judges use this automatic path by default while preserving explicit `jsonPromptInjection` overrides and fallback retries for unexpected provider failures. ([#19442](https://github.com/mastra-ai/mastra/pull/19442)) ## 1.51.1-alpha.1 ## 1.51.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`8a0d145`](https://github.com/mastra-ai/mastra/commit/8a0d145aadbdf7278665aceaaec364b35dd9bd94)) - Fixed model router requests through OpenRouter failing with a 400 "thinking blocks cannot be modified" error when an Anthropic model (e.g. openrouter/anthropic/claude-sonnet-4-6) used extended thinking together with parallel tool calls. The bundled OpenRouter provider was updated from 1.5.4 to 2.10.0, which no longer duplicates reasoning details on each tool call when sending conversation history back to the API. Fixes #19436 ([#19493](https://github.com/mastra-ai/mastra/pull/19493)) - Updated the unsupported-adapter error for resource-scoped message listing to include convex in the list of storage adapters that support Observational Memory. ([#19474](https://github.com/mastra-ai/mastra/pull/19474)) - Fixed `onIterationComplete` callbacks receiving empty `toolResults` after successful tool execution. Tool results now include the matching call ID, tool name, and output from the completed iteration. Fixes [#19453](https://github.com/mastra-ai/mastra/issues/19453). ([#19454](https://github.com/mastra-ai/mastra/pull/19454)) - Fixed generated files from AI SDK v7 models (e.g. gpt-image-1 image output) being corrupted in stream output and saved message history. The tagged V4 file data is now converted to the flat shape Mastra's stream pipeline and message storage expect. This covers both `file` and `reasoning-file` response parts. ([#19430](https://github.com/mastra-ai/mastra/pull/19430)) Also fixed handling of URL-backed generated files: the URL is no longer mislabeled as base64 in file chunks, UI message streams now emit the URL directly instead of a broken data URI, and reading `.uint8Array` on a URL-backed generated file now throws a descriptive error instead of returning garbage. - Fixed background tasks never executing when Mastra is used as a library without a Mastra server. Previously, background-eligible tool calls (including background subagent delegations) were dispatched but stayed in the `running` state forever because the task workers only started with a Mastra server (`mastra dev` or a deployed server). Now the workers start automatically on the first background task dispatch or resume, so background tasks complete in apps that embed Mastra directly (for example Express or Next.js servers). Fixes [#19339](https://github.com/mastra-ai/mastra/issues/19339). ([#19476](https://github.com/mastra-ai/mastra/pull/19476)) - Fixed a crash on Cloudflare Workers where calling generate() on an Agent not registered to a Mastra instance threw `TypeError: this.#intervalHandle.unref is not a function`. The scheduler now only calls unref() on its polling interval when the runtime provides it (Node.js); on runtimes where setInterval returns a number (workerd) the call is skipped. Fixes [#19462](https://github.com/mastra-ai/mastra/issues/19462). ([#19471](https://github.com/mastra-ai/mastra/pull/19471)) ## 1.51.0 ### Minor Changes - Added session scoping to `AgentController` so independent sessions can run in parallel over the same resource (for example one session per git worktree). ([#19357](https://github.com/mastra-ai/mastra/pull/19357)) Previously `createSession()` was get-or-create by `resourceId` alone, so two callers sharing a resource always resolved to the same session — with one run loop, one thread binding, and shared mode/model/state. Passing the new `scope` option creates an independent session per scope: ```ts // Two independent sessions over the same resource: const a = await controller.createSession({ resourceId: 'repo-123', scope: '/worktrees/feature-a', tags: { projectPath: '/worktrees/feature-a' }, }); const b = await controller.createSession({ resourceId: 'repo-123', scope: '/worktrees/feature-b', tags: { projectPath: '/worktrees/feature-b' }, }); // Look up a scoped session later: const session = await controller.getSessionByResource('repo-123', '/worktrees/feature-a'); ``` Calls with the same `resourceId` and `scope` still resume the same session (get-or-create), and unscoped sessions behave exactly as before. - Added support for custom processors on `createScorer` judges. You can now pass `inputProcessors`, `outputProcessors`, `errorProcessors`, and `maxProcessorRetries` in a scorer's `judge` config (or per-step judge config) to apply processors to the internal judge agent — for example wiring up `StreamErrorRetryProcessor` to retry transient LLM errors while scoring. ([#19195](https://github.com/mastra-ai/mastra/pull/19195)) ```typescript import { createScorer } from '@mastra/core/evals'; import { StreamErrorRetryProcessor } from '@mastra/core/processors'; const scorer = createScorer({ id: 'my-scorer', description: 'Scores responses', judge: { model: myModel, instructions: 'You are an expert evaluator...', errorProcessors: [new StreamErrorRetryProcessor()], maxProcessorRetries: 3, }, }); ``` - Added support for resolving `foreach` concurrency at execution time. `concurrency` can now be a function that receives the foreach input and the workflow's init data and returns a number, in addition to a static number: ([#19329](https://github.com/mastra-ai/mastra/pull/19329)) ```ts workflow.foreach(step, { concurrency: ({ inputData, getInitData }) => (getInitData().fast ? 10 : 1), }); ``` Durable agents use this to honor `toolCallConcurrency`: parallel tool calls now run concurrently (default 10) instead of always sequentially, while runs that require tool approval or use tools that can suspend still execute tool calls one at a time. - Added an option to retry unknown stream errors while allowing known authorization failures to surface immediately, and enabled resilient retries for coding agents. ([#19290](https://github.com/mastra-ai/mastra/pull/19290)) ```typescript import { StreamErrorRetryProcessor } from '@mastra/core/processors'; const processor = new StreamErrorRetryProcessor({ retryUnknownErrors: true, maxRetries: 2, delayMs: 3000, }); ``` - Added: `runEvals()` now supports gate-only runs. `scorers` is optional when at least one gate is provided. ([#19348](https://github.com/mastra-ai/mastra/pull/19348)) - Added `maxRetryAfterMs` to `StreamErrorRetryProcessor`, with a default of 30 seconds, so provider `Retry-After` waits can't exceed a configured limit. Aborting during the wait now stops the processor retry before another model request. ([#19383](https://github.com/mastra-ai/mastra/pull/19383)) Improved structured-output recovery so transport and provider failures don't trigger JSON prompt injection. Scorer judges that use Mastra's current generation API can use existing error processors for a coordinated retry budget. Legacy model adapters keep their separate `generateLegacy()` retry settings. **Before** ```ts import { StreamErrorRetryProcessor } from '@mastra/core/processors'; new StreamErrorRetryProcessor({ maxRetries: 2, }); ``` **After** ```ts import { StreamErrorRetryProcessor } from '@mastra/core/processors'; new StreamErrorRetryProcessor({ maxRetries: 2, maxRetryAfterMs: 30_000, }); ``` - Workflow run event topics are now cleaned up automatically. The evented workflow engine deletes each run's `workflow.events.v2.` pub/sub topic shortly after the run reaches a terminal state (success, failure, or cancellation), so persistent transports like Redis Streams no longer accumulate streams from finished workflow runs (#19123). ([#19418](https://github.com/mastra-ai/mastra/pull/19418)) `clearTopic` is now part of the `PubSub` base class with a default no-op implementation. Custom transports that retain messages per topic should override it to delete that state: ```typescript import { PubSub } from '@mastra/core/events'; class CustomPubSub extends PubSub { async clearTopic(topic: string): Promise { // delete retained state for the topic } } ``` Callers no longer need to probe for the method before calling it — `CachingPubSub` and the durable-agent runtime now forward `clearTopic` unconditionally. - Added anonymous feature usage telemetry for server startup surface counts and a `trackFeatureUsage()` API. ([#19159](https://github.com/mastra-ai/mastra/pull/19159)) - Added atomic caller-defined dataset IDs for idempotent dataset creation across built-in storage adapters. Supplying `id` to `mastra.datasets.create()` now creates the dataset once and resolves compatible retries to the persisted record; incompatible immutable identity fields throw `DATASET_ID_CONFLICT`. ([#19370](https://github.com/mastra-ai/mastra/pull/19370)) - Added PROVIDER_TOOL_CALL observability spans for provider-executed tools (e.g. Anthropic code execution, server-side web search). Provider tool input and output are now visible in traces and Studio, with spans anchored to the AGENT_RUN parent. ([#19261](https://github.com/mastra-ai/mastra/pull/19261)) - Added the ability to explicitly disable a storage domain on `MastraCompositeStore` by setting it to `false` in the `domains` config. A disabled domain no longer falls back to the `editor` or `default` store, so writes for that domain are dropped instead of silently landing in the fallback database. ([#19059](https://github.com/mastra-ai/mastra/pull/19059)) ```ts const storage = new MastraCompositeStore({ id: 'my-storage', default: libsqlStore, domains: { // don't persist traces/metrics when observability is turned off observability: false, }, }); ``` `prune()` also accepts a per-call `retention` option that replaces the configured retention policies for that call only — for example to skip a domain (keep chat history) or prune more aggressively without reconstructing the store: ```ts // prune everything except the memory domain, one time await storage.prune({ retention: { observability: { spans: { maxAge: '14d' } }, }, }); ``` - Added the authoritative session scope to agent controller request context for scoped session integrations. ([#19446](https://github.com/mastra-ai/mastra/pull/19446)) ```ts const controllerContext = requestContext.get('controller'); console.log(controllerContext?.scope); ``` - Added caller-defined dataset item identities for safe retries across all dataset storage adapters. ([#19384](https://github.com/mastra-ai/mastra/pull/19384)) Dataset items can now include an `externalId` when calling `addItem` or `addItems`: ```ts await dataset.addItem({ externalId: 'source-item-123', input: { prompt: 'Hello' }, }); ``` Retrying with the same identity and payload returns the existing item. Reusing an identity with different content returns a typed conflict, including during concurrent writes. Updates and deletes preserve the identity, Spanner retries transactions without changing the outcome, and MySQL batch writes now preserve every supported dataset item field. - Added optional `log` and `progress` functions to the MCP tool execution context type so tools running in an MCP server can send log and progress notifications to the calling client. ([#19193](https://github.com/mastra-ai/mastra/pull/19193)) Added `mastra.removeTool(key)` to remove a dynamically registered tool from the Mastra instance, the counterpart to `mastra.addTool()`: ```typescript mastra.addTool(calculatorTool); mastra.removeTool('calculator-tool'); // returns true if a tool was removed ``` - Added file-system routing for a Mastra logger and per-agent scorers. ([#19262](https://github.com/mastra-ai/mastra/pull/19262)) Define a logger in `src/mastra/logger.ts` (default export) and it is auto-registered as the Mastra logger, just like `storage.ts` and `observability.ts`. A code-registered logger still wins. Register scorers per agent by adding an `agents//scorers/` folder. Each module's default export (a `MastraScorer`, or a `{ scorer, sampling }` entry) is wired into that agent, keyed by filename. `config.scorers` wins on collision. ```text src/mastra/ logger.ts # export default new PinoLogger({ name: 'App' }) agents/weather/ config.ts scorers/ relevance.ts # export default myRelevanceScorer ``` ### Patch Changes - dependencies updates: ([#16699](https://github.com/mastra-ai/mastra/pull/16699)) - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.28` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.28) (from `npm:@ai-sdk/provider-utils@3.0.25`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.35` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.35) (from `npm:@ai-sdk/provider-utils@4.0.27`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.5` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.5) (from `npm:@ai-sdk/provider-utils@5.0.0`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.13` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.13) (from `npm:@ai-sdk/provider@3.0.10`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.2` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.2) (from `npm:@ai-sdk/provider@4.0.0`, in `dependencies`) - dependencies updates: ([#19328](https://github.com/mastra-ai/mastra/pull/19328)) - Updated dependency [`@a2a-js/sdk@~0.3.14` ↗︎](https://www.npmjs.com/package/@a2a-js/sdk/v/0.3.14) (from `~0.3.13`, in `dependencies`) - dependencies updates: ([#19385](https://github.com/mastra-ai/mastra/pull/19385)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.38` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.38) (from `npm:@ai-sdk/provider-utils@4.0.35`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.7` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.7) (from `npm:@ai-sdk/provider-utils@5.0.5`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.14` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.14) (from `npm:@ai-sdk/provider@3.0.13`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.3` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.3) (from `npm:@ai-sdk/provider@4.0.2`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`fe1bda0`](https://github.com/mastra-ai/mastra/commit/fe1bda06f6af92a694a51712db747cda1e7185f0)) - Fixed background-task cancellation so cancelled tasks no longer look completed to the agent, terminal workflow events still surface a valid result when cancellation happens before one is produced, and completed, failed, cancelled, and suspended background tasks each get clearer continuation instructions. ([#19255](https://github.com/mastra-ai/mastra/pull/19255)) - Added an optional `scope` field to `ResolveToolsOpts` so tool providers can see a connection's identity bucketing (`per-author`, `shared`, or `caller-supplied`) when resolving tools. Providers can use this to let the backend auto-resolve an account within a caller's bucket instead of pinning a specific one. The field is optional and defaults to previous behavior when absent. ([#19144](https://github.com/mastra-ai/mastra/pull/19144)) Also added an optional `defaultScope` to `BaseToolProviderOptions` (surfaced on the `ToolProvider` interface as `defaultScope`). This lets an app author set a tool provider's connection scope at config time — for example `defaultScope: 'caller-supplied'` for multi-tenant OAuth — so every connection authorized against the provider is bucketed correctly without any per-connection UI control. Defaults to `'per-author'` when absent. ```ts import { BaseToolProvider, type ResolveToolsOpts } from '@mastra/core/tool-provider'; class MyToolProvider extends BaseToolProvider { // ...required members like `info` and `capabilities` elided constructor() { // Config-level tenancy decision: bucket connections per caller. super({ defaultScope: 'caller-supplied' }); } async resolveToolsVNext(opts: ResolveToolsOpts) { if (opts.scope === 'caller-supplied') { // Let the backend auto-resolve an account within the caller's // bucket instead of pinning a specific connected account. } // ... } } ``` - **Fixed** — `CachingPubSub.clearTopic` now forwards to the wrapped transport. Because the durable-agent runtime wraps every pubsub in `CachingPubSub`, clearing a topic previously dropped only the in-memory cache and never told a persistent backend (e.g. Redis Streams) to delete its stream — so finished runs' streams leaked. It now also calls `clearTopic` on the inner transport when the inner implements it. ([#19138](https://github.com/mastra-ai/mastra/pull/19138)) - Fixed `Memory` `generateTitle` never firing for durable agents created with `createEventedAgent` (including Inngest). Thread titles now generate and persist on the durable path when `generateTitle` is configured, including when Observational Memory is enabled. Existing thread titles are preserved. ([#19315](https://github.com/mastra-ai/mastra/pull/19315)) - Fix durable agents orphaning per-step output processor spans. In `createDurableLLMExecutionStep`, the `runProcessOutputStep(...)` call omitted `tracingContext` (unlike the sibling `runProcessInputStep`/`runProcessLLMRequest` calls), so `output step processor` `processor_run` spans were created without a parent and appeared as their own root traces — one per LLM step — instead of nesting under `MODEL_STEP` → `AGENT_RUN`. Passing `tracingContext: modelSpanTracker?.getTracingContext() ?? tracingContext` restores parity with the non-durable path. Fixes #19312. ([#19313](https://github.com/mastra-ai/mastra/pull/19313)) - Fix `ToolNotFoundError` for workspace/skill tools (`skill`, `skill_read`, `skill_search`, `mastra_workspace_*`) when a durable agent's steps execute on a cross-process engine (e.g. the `@mastra/inngest` `connect()` worker). ([#19331](https://github.com/mastra-ai/mastra/pull/19331)) The durable tool-call step resolved tools only from the per-process `globalRunRegistry` plus Mastra-instance-level tools, while the sibling LLM-execution step already rebuilds the full toolset from the agent via `resolveRuntimeDependencies`/`getToolsForExecution`. On a worker process the registry is empty, so the model could _call_ `skill` (the LLM step saw it) but the tool-call step rejected it with `ToolNotFoundError`. The tool-call step now falls back to rebuilding the toolset from the agent (`rebuildRunToolsFromMastra`) when the registry misses, resolving workspace/skill tools symmetrically cross-process. `resolveRuntimeDependencies` also now rebuilds `inputProcessors`/`outputProcessors` (and writes the rebuilt tools + processors back into `globalRunRegistry`) when the registry entry is a cross-process placeholder, so the `SkillsProcessor` and `WorkspaceInstructionsProcessor` run cross-process too — restoring the available-skills list and workspace instructions in the system prompt on the worker. Placeholder registry entries are detected via a new explicit `RunRegistryEntry.isPlaceholder` flag (set by `@mastra/inngest` when seeding resume-segment entries) or the absence of a live model instance — never by an empty `tools` map, which is a legitimate state for agents configured without tools. Fixes #19330. - Pass tracingContext through to runProcessAPIError so error-processor runs show up as processor_run spans in observability exports ([#19188](https://github.com/mastra-ai/mastra/pull/19188)) - Fixed `iterationCount` always being 1 for `dountil` and `dowhile` loops on the evented workflow engine. The count was never carried forward between iterations, so any loop whose condition depended on `iterationCount` never advanced and ran forever. ([#19233](https://github.com/mastra-ai/mastra/pull/19233)) **Before** ```ts // On the evented engine this loop never terminated: iterationCount stayed 1. workflow.dountil(step, async ({ iterationCount }) => iterationCount >= 3); ``` **After** The condition now receives an incrementing count (1, 2, 3, ...) exactly as it does on the default engine, so the loop stops as expected. Loops whose conditions read step output (`inputData`) were unaffected. - Fix the `createHandler` option type on `registerApiRoute`. It's called with `{ mastra }` at runtime but was typed as `(c: Context)`; it's now `(opts: { mastra: Mastra }) => Promise`, matching the runtime and the internal `ApiRoute` type. ([#19320](https://github.com/mastra-ai/mastra/pull/19320)) - Fixed `DurableAgent` losing reasoning items on turns that include tool calls, which caused OpenAI reasoning models like `gpt-5-mini` to fail on the next turn. Reasoning is now preserved and replayed correctly on subsequent turns. ([#19408](https://github.com/mastra-ai/mastra/pull/19408)) Fixes #19365. - Fix heap OOM when a workflow uses `.map({ key: mapVariable({ initData: }) })`. The map reducer kept the live `Workflow` instance by reference and `JSON.stringify`'d it into the map step's `mapConfig`, deep-walking the whole workflow (its logger, nested step graph, …) into a multi-hundred-MB string — and the length-guard truncation only ran after the full string was built, so `.commit()` could OOM at module load. The `initData` mapping is now serialized as a slim `{ initData: , path }` reference. Runtime behavior is unchanged (the execute path only reads `initData` for truthiness). ([#19033](https://github.com/mastra-ai/mastra/pull/19033)) - Fixed file attachments with AI SDK v7 models. ([#19316](https://github.com/mastra-ai/mastra/pull/19316)) - fix: handle PathSegment objects in validation error messages ([#19125](https://github.com/mastra-ai/mastra/pull/19125)) - Durable agents now stop cleanly when cancelled via an abort signal, reporting the correct `abort` finish reason instead of crashing. The `sendMessage` and `sendSignal` wake flows also work reliably for durable agents, so thread subscribers receive streamed chunks as expected. ([#19046](https://github.com/mastra-ai/mastra/pull/19046)) - Fixed typo in agent-network e2e test description ([#19162](https://github.com/mastra-ai/mastra/pull/19162)) - Fixed `stream.text` so it resolves only to the final step's answer and excludes interim commentary between tool calls when no memory or output processors are configured. All text still streams in full through `fullStream`, and per-step text remains available on `steps`. Fixes [#17986](https://github.com/mastra-ai/mastra/issues/17986). ([#18644](https://github.com/mastra-ai/mastra/pull/18644)) - Fixed an infinite retry loop in evented workflows when workflow storage is unavailable (for example when the workflows table does not exist). A failing workflow.fail event no longer republishes another workflow.fail after exhausting its retry budget, which previously flooded logs with endless "error processing event" entries. ([#19194](https://github.com/mastra-ai/mastra/pull/19194)) - Fixed the tool payload transform policy being dropped on non-durable agent streams. When an agent was configured with a `transform` policy, `loop()` rebuilt its internal state bag but did not carry the policy forward, so it never reached the run scope and silently no-opped for the whole run. The policy is now forwarded, so tool call, tool result, and tool input delta payloads are transformed as configured. Closes #19102. ([#19103](https://github.com/mastra-ai/mastra/pull/19103)) - Fixed a crash that could happen when a language server process exited or stopped responding while a request was being sent to it. The request now fails with a clean timeout error instead of crashing the host process. ([#19322](https://github.com/mastra-ai/mastra/pull/19322)) - Renamed the built-in gateway's display name from "Memory Gateway" to "Gateway" so it reads as a plain model gateway alongside the other providers. No behavior change. ([#18691](https://github.com/mastra-ai/mastra/pull/18691)) - Add MastraNonRetryableError for workflow steps to signal permanent failures and skip retries ([#19321](https://github.com/mastra-ai/mastra/pull/19321)) ```ts import { MastraNonRetryableError } from '@mastra/core/error'; throw new MastraNonRetryableError('Invalid template ID'); ``` - Fixed parallel sub-agent approvals so they can be handled in any order. listSuspendedRuns() now returns each pending sub-agent call, and approving one resumes that specific call instead of using another call’s suspended state. ([#19450](https://github.com/mastra-ai/mastra/pull/19450)) - Fixed background task start chunks reaching agent stream onChunk callbacks. ([#18628](https://github.com/mastra-ai/mastra/pull/18628)) - Fixed a memory leak where every discarded standalone agent (an `Agent` used directly without being registered on a `Mastra` instance) stayed reachable for the lifetime of the process. The internal Mastra instance created for standalone execution no longer registers a module-level scorer hook it can never use, so standalone agents are garbage-collected once discarded. Applications that create one agent per request no longer see linear heap growth. ([#19413](https://github.com/mastra-ai/mastra/pull/19413)) Fixes [#19404](https://github.com/mastra-ai/mastra/issues/19404). - - Fixed DurableAgent returning stale/empty values for Studio metadata, processors, skills, workflows, voice, and other inherited Agent accessors by adding delegation overrides to the wrapped agent ([#19076](https://github.com/mastra-ai/mastra/pull/19076)) - Fixed missing traces in Studio: span `entityId` now uses the durable agent's ID instead of the wrapped agent's ID - Fixed dropped `background-task-completed` events in `streamUntilIdle` caused by the same agent ID mismatch - `\_\_setMemory`, `\_\_setPubSub`, and `\_\_setWorkspace` now propagate to both the DurableAgent base class and the wrapped agent - Fixed file attachments carrying AI SDK v5 metadata failing to persist and keep their media type. File parts using the v5 shape are now read wherever stored messages are converted, so they save correctly and retain their content type instead of erroring out. Also fixed distinct file attachments being wrongly deduplicated when they carried this metadata. ([#19021](https://github.com/mastra-ai/mastra/pull/19021)) - Extract text from DB-shaped user messages in goal judge prompts instead of stringifying them as `[object Object]`. Goal judge prompts now also skip malformed, empty, or synthetic reminder messages when selecting the latest user context. ([#19070](https://github.com/mastra-ai/mastra/pull/19070)) - Fixed durable agents resuming persisted runs after a process restart. ([#19363](https://github.com/mastra-ai/mastra/pull/19363)) - Added durable-agent recovery for orphaned RUNNING runs after process restart. ([#19191](https://github.com/mastra-ai/mastra/pull/19191)) **What changed** - `DurableAgent.listActiveRuns()` — discover in-flight durable runs for an agent from persistent storage, filtered by agentId, threadId, and resourceId (mirrors `listSuspendedRuns` but for `running` status). - `DurableAgent.recover(runId, options?)` — rehydrate a single orphaned run's non-serializable state (`MessageList`, model, tools, memory, `SaveQueueManager`, processors, request context, agent span, `BackgroundTaskManager` + agent background-tasks config) from its persisted workflow snapshot, re-subscribe to the pubsub topic, and re-drive the workflow in the background. Returns the same `{ output, fullStream, runId, threadId, resourceId, cleanup, abort }` shape as `stream()`/`resume()`, so callers can stream the recovered response or attach later via `observe(runId)`. Memory writes flow through the rebuilt `SaveQueueManager` exactly like a fresh run. - `DurableAgent.recoverActiveRuns(options?)` — bulk recovery hook that delegates to `recover(runId)` for each in-flight run discovered via `listActiveRuns()` (or a caller-supplied `runId`), awaits each workflow settlement, and returns `{ recovered, succeeded, failed }` counts. Use this on boot to drain the backlog; use `recover()` when you want to stream a specific run. - The default workflow engine now persists `running` snapshots for the durable agentic loop, with a guard that prevents a `running` write from overwriting an already-`suspended` snapshot for the same run. Without this, `listActiveRuns()` would never see a live durable run in storage. - Background-task state is re-wired on recovery so the `bg-task-check` step waits for pre-crash in-flight tasks (still tracked in `BackgroundTaskManager` storage), the `tool-call` step can still dispatch new tools as background tasks, and `llm-execution` still injects the background-task system prompt. Without this, the recovered segment would silently run with background-tasks disabled and could end before storage-backed tasks delivered their tool-result chunks. - Snapshot rows are now deleted after a durable run reaches any non-suspended terminal status — this applies to `stream`/`generate` (the initial `run.start()`), `resume()`, `recover()`, and `recoverActiveRuns()`. Suspended terminals still keep their snapshots so a later resume/recover can find them. Mirrors the existing loop-stream cleanup so snapshot storage doesn't grow one stale row per completed durable run. - `Mastra.recoverAllDurableAgents()` — new server-level fan-out that walks every registered agent, filters those exposing `recoverActiveRuns()` (default-engine `DurableAgent` only — Inngest and other externally-executed durable wrappers run their own recovery), and aggregates `{ agents, recovered, succeeded, failed }` counts. A per-agent failure is logged and skipped so one bad agent doesn't stop the rest. - New `MastraConfig.recovery` option: `recovery?: { durableAgents?: 'auto' | 'off' }` (default `'off'`). When set to `'auto'`, the deployer's `/__restart-active-workflow-runs` boot handler will invoke `mastra.recoverAllDurableAgents()` right after `restartAllActiveWorkflowRuns()`, so both user workflows and durable-agent runs are re-driven from the same boot hook the CLI/deployer already call. Also exposed as `mastra.recoveryConfig` for callers who want to gate their own recovery pipelines on it. - Recovery is opt-in on purpose. Auto-recovery re-runs the agentic loop from the last persisted snapshot, so it re-issues LLM calls (real cost) and re-executes tool calls (must be idempotent); in multi-instance deploys every replica will race to recover the same runs until a lease/lock is added. Leave `'off'` and call `mastra.recoverAllDurableAgents()` (or the per-agent APIs) from a cron, a leader-elected worker, or an admin endpoint if you need finer control. **Why** Previously, the durable agent's agentic loop was an awaited in-process Promise and `globalRunRegistry` was an in-memory TTLCache, so any RUNNING run silently died on process restart with no boot-time recovery or re-drive API (see issue #19056). Suspended runs already had `prepare`/`resume`/`listSuspendedRuns`; RUNNING runs now have the equivalent discover-and-recover pair. **Usage** ```ts // Opt into boot-time recovery for every durable agent on this Mastra instance. // The deployer will call `mastra.recoverAllDurableAgents()` automatically // after restarting active workflow runs. const mastra = new Mastra({ agents: { support: supportDurableAgent }, storage, recovery: { durableAgents: 'auto' }, }); // Or drive it yourself (cron, leader election, admin endpoint, etc.): const { agents, recovered, succeeded, failed } = await mastra.recoverAllDurableAgents(); // Per-agent: drain all orphaned RUNNING runs on one agent. const agent = mastra.getAgent('support') as DurableAgent; await agent.recoverActiveRuns(); // Per-run: recover a specific run and stream its output to the caller. const { fullStream, runId, cleanup } = await agent.recover('run-abc123'); for await (const chunk of fullStream) { // forward chunks to the client, log them, etc. } cleanup(); ``` - Fixed targeted agent resumes so an approval for a tool call that is not suspended fails without executing a different tool call. ([#19377](https://github.com/mastra-ai/mastra/pull/19377)) - Updated dependencies [[`bc1121a`](https://github.com/mastra-ai/mastra/commit/bc1121a7bb98f7cd73e82e3a7913a667a9fa9911), [`171c3a2`](https://github.com/mastra-ai/mastra/commit/171c3a23f36199ad1354166fb515b22b57f310c2), [`6789ab4`](https://github.com/mastra-ai/mastra/commit/6789ab4191ddcd32a932898b360b191e80cee1a9)]: - @mastra/schema-compat@1.3.4 ## 1.51.0-alpha.13 ### Minor Changes - Added atomic caller-defined dataset IDs for idempotent dataset creation across built-in storage adapters. Supplying `id` to `mastra.datasets.create()` now creates the dataset once and resolves compatible retries to the persisted record; incompatible immutable identity fields throw `DATASET_ID_CONFLICT`. ([#19370](https://github.com/mastra-ai/mastra/pull/19370)) - Added the authoritative session scope to agent controller request context for scoped session integrations. ([#19446](https://github.com/mastra-ai/mastra/pull/19446)) ```ts const controllerContext = requestContext.get('controller'); console.log(controllerContext?.scope); ``` - Added caller-defined dataset item identities for safe retries across all dataset storage adapters. ([#19384](https://github.com/mastra-ai/mastra/pull/19384)) Dataset items can now include an `externalId` when calling `addItem` or `addItems`: ```ts await dataset.addItem({ externalId: 'source-item-123', input: { prompt: 'Hello' }, }); ``` Retrying with the same identity and payload returns the existing item. Reusing an identity with different content returns a typed conflict, including during concurrent writes. Updates and deletes preserve the identity, Spanner retries transactions without changing the outcome, and MySQL batch writes now preserve every supported dataset item field. ### Patch Changes - Fixed parallel sub-agent approvals so they can be handled in any order. listSuspendedRuns() now returns each pending sub-agent call, and approving one resumes that specific call instead of using another call’s suspended state. ([#19450](https://github.com/mastra-ai/mastra/pull/19450)) ## 1.51.0-alpha.12 ### Minor Changes - Added `maxRetryAfterMs` to `StreamErrorRetryProcessor`, with a default of 30 seconds, so provider `Retry-After` waits can't exceed a configured limit. Aborting during the wait now stops the processor retry before another model request. ([#19383](https://github.com/mastra-ai/mastra/pull/19383)) Improved structured-output recovery so transport and provider failures don't trigger JSON prompt injection. Scorer judges that use Mastra's current generation API can use existing error processors for a coordinated retry budget. Legacy model adapters keep their separate `generateLegacy()` retry settings. **Before** ```ts import { StreamErrorRetryProcessor } from '@mastra/core/processors'; new StreamErrorRetryProcessor({ maxRetries: 2, }); ``` **After** ```ts import { StreamErrorRetryProcessor } from '@mastra/core/processors'; new StreamErrorRetryProcessor({ maxRetries: 2, maxRetryAfterMs: 30_000, }); ``` ### Patch Changes - Fixed file attachments with AI SDK v7 models. ([#19316](https://github.com/mastra-ai/mastra/pull/19316)) - Fixed targeted agent resumes so an approval for a tool call that is not suspended fails without executing a different tool call. ([#19377](https://github.com/mastra-ai/mastra/pull/19377)) ## 1.51.0-alpha.11 ### Minor Changes - Workflow run event topics are now cleaned up automatically. The evented workflow engine deletes each run's `workflow.events.v2.` pub/sub topic shortly after the run reaches a terminal state (success, failure, or cancellation), so persistent transports like Redis Streams no longer accumulate streams from finished workflow runs (#19123). ([#19418](https://github.com/mastra-ai/mastra/pull/19418)) `clearTopic` is now part of the `PubSub` base class with a default no-op implementation. Custom transports that retain messages per topic should override it to delete that state: ```typescript import { PubSub } from '@mastra/core/events'; class CustomPubSub extends PubSub { async clearTopic(topic: string): Promise { // delete retained state for the topic } } ``` Callers no longer need to probe for the method before calling it — `CachingPubSub` and the durable-agent runtime now forward `clearTopic` unconditionally. ### Patch Changes - Fixed `DurableAgent` losing reasoning items on turns that include tool calls, which caused OpenAI reasoning models like `gpt-5-mini` to fail on the next turn. Reasoning is now preserved and replayed correctly on subsequent turns. ([#19408](https://github.com/mastra-ai/mastra/pull/19408)) Fixes #19365. - Fixed a memory leak where every discarded standalone agent (an `Agent` used directly without being registered on a `Mastra` instance) stayed reachable for the lifetime of the process. The internal Mastra instance created for standalone execution no longer registers a module-level scorer hook it can never use, so standalone agents are garbage-collected once discarded. Applications that create one agent per request no longer see linear heap growth. ([#19413](https://github.com/mastra-ai/mastra/pull/19413)) Fixes [#19404](https://github.com/mastra-ai/mastra/issues/19404). ## 1.51.0-alpha.10 ### Patch Changes - **Fixed** — `CachingPubSub.clearTopic` now forwards to the wrapped transport. Because the durable-agent runtime wraps every pubsub in `CachingPubSub`, clearing a topic previously dropped only the in-memory cache and never told a persistent backend (e.g. Redis Streams) to delete its stream — so finished runs' streams leaked. It now also calls `clearTopic` on the inner transport when the inner implements it. ([#19138](https://github.com/mastra-ai/mastra/pull/19138)) - Fixed file attachments carrying AI SDK v5 metadata failing to persist and keep their media type. File parts using the v5 shape are now read wherever stored messages are converted, so they save correctly and retain their content type instead of erroring out. Also fixed distinct file attachments being wrongly deduplicated when they carried this metadata. ([#19021](https://github.com/mastra-ai/mastra/pull/19021)) - Updated dependencies [[`171c3a2`](https://github.com/mastra-ai/mastra/commit/171c3a23f36199ad1354166fb515b22b57f310c2)]: - @mastra/schema-compat@1.3.4-alpha.2 ## 1.51.0-alpha.9 ### Patch Changes - Fixed `stream.text` so it resolves only to the final step's answer and excludes interim commentary between tool calls when no memory or output processors are configured. All text still streams in full through `fullStream`, and per-step text remains available on `steps`. Fixes [#17986](https://github.com/mastra-ai/mastra/issues/17986). ([#18644](https://github.com/mastra-ai/mastra/pull/18644)) ## 1.51.0-alpha.8 ### Patch Changes - dependencies updates: ([#16699](https://github.com/mastra-ai/mastra/pull/16699)) - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.28` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.28) (from `npm:@ai-sdk/provider-utils@3.0.25`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.35` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.35) (from `npm:@ai-sdk/provider-utils@4.0.27`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.5` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.5) (from `npm:@ai-sdk/provider-utils@5.0.0`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.13` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.13) (from `npm:@ai-sdk/provider@3.0.10`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.2` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.2) (from `npm:@ai-sdk/provider@4.0.0`, in `dependencies`) - dependencies updates: ([#19328](https://github.com/mastra-ai/mastra/pull/19328)) - Updated dependency [`@a2a-js/sdk@~0.3.14` ↗︎](https://www.npmjs.com/package/@a2a-js/sdk/v/0.3.14) (from `~0.3.13`, in `dependencies`) - dependencies updates: ([#19385](https://github.com/mastra-ai/mastra/pull/19385)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.38` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.38) (from `npm:@ai-sdk/provider-utils@4.0.35`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v7@npm:@ai-sdk/provider-utils@5.0.7` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v7/v/5.0.7) (from `npm:@ai-sdk/provider-utils@5.0.5`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.14` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.14) (from `npm:@ai-sdk/provider@3.0.13`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v7@npm:@ai-sdk/provider@4.0.3` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v7/v/4.0.3) (from `npm:@ai-sdk/provider@4.0.2`, in `dependencies`) - Fixed background task start chunks reaching agent stream onChunk callbacks. ([#18628](https://github.com/mastra-ai/mastra/pull/18628)) - Fixed durable agents resuming persisted runs after a process restart. ([#19363](https://github.com/mastra-ai/mastra/pull/19363)) ## 1.51.0-alpha.7 ### Minor Changes - Added session scoping to `AgentController` so independent sessions can run in parallel over the same resource (for example one session per git worktree). ([#19357](https://github.com/mastra-ai/mastra/pull/19357)) Previously `createSession()` was get-or-create by `resourceId` alone, so two callers sharing a resource always resolved to the same session — with one run loop, one thread binding, and shared mode/model/state. Passing the new `scope` option creates an independent session per scope: ```ts // Two independent sessions over the same resource: const a = await controller.createSession({ resourceId: 'repo-123', scope: '/worktrees/feature-a', tags: { projectPath: '/worktrees/feature-a' }, }); const b = await controller.createSession({ resourceId: 'repo-123', scope: '/worktrees/feature-b', tags: { projectPath: '/worktrees/feature-b' }, }); // Look up a scoped session later: const session = await controller.getSessionByResource('repo-123', '/worktrees/feature-a'); ``` Calls with the same `resourceId` and `scope` still resume the same session (get-or-create), and unscoped sessions behave exactly as before. - Added support for resolving `foreach` concurrency at execution time. `concurrency` can now be a function that receives the foreach input and the workflow's init data and returns a number, in addition to a static number: ([#19329](https://github.com/mastra-ai/mastra/pull/19329)) ```ts workflow.foreach(step, { concurrency: ({ inputData, getInitData }) => (getInitData().fast ? 10 : 1), }); ``` Durable agents use this to honor `toolCallConcurrency`: parallel tool calls now run concurrently (default 10) instead of always sequentially, while runs that require tool approval or use tools that can suspend still execute tool calls one at a time. - Added an option to retry unknown stream errors while allowing known authorization failures to surface immediately, and enabled resilient retries for coding agents. ([#19290](https://github.com/mastra-ai/mastra/pull/19290)) ```typescript import { StreamErrorRetryProcessor } from '@mastra/core/processors'; const processor = new StreamErrorRetryProcessor({ retryUnknownErrors: true, maxRetries: 2, delayMs: 3000, }); ``` - Added: `runEvals()` now supports gate-only runs. `scorers` is optional when at least one gate is provided. ([#19348](https://github.com/mastra-ai/mastra/pull/19348)) - Added PROVIDER_TOOL_CALL observability spans for provider-executed tools (e.g. Anthropic code execution, server-side web search). Provider tool input and output are now visible in traces and Studio, with spans anchored to the AGENT_RUN parent. ([#19261](https://github.com/mastra-ai/mastra/pull/19261)) ### Patch Changes - Fixed background-task cancellation so cancelled tasks no longer look completed to the agent, terminal workflow events still surface a valid result when cancellation happens before one is produced, and completed, failed, cancelled, and suspended background tasks each get clearer continuation instructions. ([#19255](https://github.com/mastra-ai/mastra/pull/19255)) - Fixed `Memory` `generateTitle` never firing for durable agents created with `createEventedAgent` (including Inngest). Thread titles now generate and persist on the durable path when `generateTitle` is configured, including when Observational Memory is enabled. Existing thread titles are preserved. ([#19315](https://github.com/mastra-ai/mastra/pull/19315)) - Fix durable agents orphaning per-step output processor spans. In `createDurableLLMExecutionStep`, the `runProcessOutputStep(...)` call omitted `tracingContext` (unlike the sibling `runProcessInputStep`/`runProcessLLMRequest` calls), so `output step processor` `processor_run` spans were created without a parent and appeared as their own root traces — one per LLM step — instead of nesting under `MODEL_STEP` → `AGENT_RUN`. Passing `tracingContext: modelSpanTracker?.getTracingContext() ?? tracingContext` restores parity with the non-durable path. Fixes #19312. ([#19313](https://github.com/mastra-ai/mastra/pull/19313)) - Fix `ToolNotFoundError` for workspace/skill tools (`skill`, `skill_read`, `skill_search`, `mastra_workspace_*`) when a durable agent's steps execute on a cross-process engine (e.g. the `@mastra/inngest` `connect()` worker). ([#19331](https://github.com/mastra-ai/mastra/pull/19331)) The durable tool-call step resolved tools only from the per-process `globalRunRegistry` plus Mastra-instance-level tools, while the sibling LLM-execution step already rebuilds the full toolset from the agent via `resolveRuntimeDependencies`/`getToolsForExecution`. On a worker process the registry is empty, so the model could _call_ `skill` (the LLM step saw it) but the tool-call step rejected it with `ToolNotFoundError`. The tool-call step now falls back to rebuilding the toolset from the agent (`rebuildRunToolsFromMastra`) when the registry misses, resolving workspace/skill tools symmetrically cross-process. `resolveRuntimeDependencies` also now rebuilds `inputProcessors`/`outputProcessors` (and writes the rebuilt tools + processors back into `globalRunRegistry`) when the registry entry is a cross-process placeholder, so the `SkillsProcessor` and `WorkspaceInstructionsProcessor` run cross-process too — restoring the available-skills list and workspace instructions in the system prompt on the worker. Placeholder registry entries are detected via a new explicit `RunRegistryEntry.isPlaceholder` flag (set by `@mastra/inngest` when seeding resume-segment entries) or the absence of a live model instance — never by an empty `tools` map, which is a legitimate state for agents configured without tools. Fixes #19330. - Fix the `createHandler` option type on `registerApiRoute`. It's called with `{ mastra }` at runtime but was typed as `(c: Context)`; it's now `(opts: { mastra: Mastra }) => Promise`, matching the runtime and the internal `ApiRoute` type. ([#19320](https://github.com/mastra-ai/mastra/pull/19320)) - fix: handle PathSegment objects in validation error messages ([#19125](https://github.com/mastra-ai/mastra/pull/19125)) - Fixed a crash that could happen when a language server process exited or stopped responding while a request was being sent to it. The request now fails with a clean timeout error instead of crashing the host process. ([#19322](https://github.com/mastra-ai/mastra/pull/19322)) - Renamed the built-in gateway's display name from "Memory Gateway" to "Gateway" so it reads as a plain model gateway alongside the other providers. No behavior change. ([#18691](https://github.com/mastra-ai/mastra/pull/18691)) - Add MastraNonRetryableError for workflow steps to signal permanent failures and skip retries ([#19321](https://github.com/mastra-ai/mastra/pull/19321)) ```ts import { MastraNonRetryableError } from '@mastra/core/error'; throw new MastraNonRetryableError('Invalid template ID'); ``` ## 1.51.0-alpha.6 ### Patch Changes - Added durable-agent recovery for orphaned RUNNING runs after process restart. ([#19191](https://github.com/mastra-ai/mastra/pull/19191)) **What changed** - `DurableAgent.listActiveRuns()` — discover in-flight durable runs for an agent from persistent storage, filtered by agentId, threadId, and resourceId (mirrors `listSuspendedRuns` but for `running` status). - `DurableAgent.recover(runId, options?)` — rehydrate a single orphaned run's non-serializable state (`MessageList`, model, tools, memory, `SaveQueueManager`, processors, request context, agent span, `BackgroundTaskManager` + agent background-tasks config) from its persisted workflow snapshot, re-subscribe to the pubsub topic, and re-drive the workflow in the background. Returns the same `{ output, fullStream, runId, threadId, resourceId, cleanup, abort }` shape as `stream()`/`resume()`, so callers can stream the recovered response or attach later via `observe(runId)`. Memory writes flow through the rebuilt `SaveQueueManager` exactly like a fresh run. - `DurableAgent.recoverActiveRuns(options?)` — bulk recovery hook that delegates to `recover(runId)` for each in-flight run discovered via `listActiveRuns()` (or a caller-supplied `runId`), awaits each workflow settlement, and returns `{ recovered, succeeded, failed }` counts. Use this on boot to drain the backlog; use `recover()` when you want to stream a specific run. - The default workflow engine now persists `running` snapshots for the durable agentic loop, with a guard that prevents a `running` write from overwriting an already-`suspended` snapshot for the same run. Without this, `listActiveRuns()` would never see a live durable run in storage. - Background-task state is re-wired on recovery so the `bg-task-check` step waits for pre-crash in-flight tasks (still tracked in `BackgroundTaskManager` storage), the `tool-call` step can still dispatch new tools as background tasks, and `llm-execution` still injects the background-task system prompt. Without this, the recovered segment would silently run with background-tasks disabled and could end before storage-backed tasks delivered their tool-result chunks. - Snapshot rows are now deleted after a durable run reaches any non-suspended terminal status — this applies to `stream`/`generate` (the initial `run.start()`), `resume()`, `recover()`, and `recoverActiveRuns()`. Suspended terminals still keep their snapshots so a later resume/recover can find them. Mirrors the existing loop-stream cleanup so snapshot storage doesn't grow one stale row per completed durable run. - `Mastra.recoverAllDurableAgents()` — new server-level fan-out that walks every registered agent, filters those exposing `recoverActiveRuns()` (default-engine `DurableAgent` only — Inngest and other externally-executed durable wrappers run their own recovery), and aggregates `{ agents, recovered, succeeded, failed }` counts. A per-agent failure is logged and skipped so one bad agent doesn't stop the rest. - New `MastraConfig.recovery` option: `recovery?: { durableAgents?: 'auto' | 'off' }` (default `'off'`). When set to `'auto'`, the deployer's `/__restart-active-workflow-runs` boot handler will invoke `mastra.recoverAllDurableAgents()` right after `restartAllActiveWorkflowRuns()`, so both user workflows and durable-agent runs are re-driven from the same boot hook the CLI/deployer already call. Also exposed as `mastra.recoveryConfig` for callers who want to gate their own recovery pipelines on it. - Recovery is opt-in on purpose. Auto-recovery re-runs the agentic loop from the last persisted snapshot, so it re-issues LLM calls (real cost) and re-executes tool calls (must be idempotent); in multi-instance deploys every replica will race to recover the same runs until a lease/lock is added. Leave `'off'` and call `mastra.recoverAllDurableAgents()` (or the per-agent APIs) from a cron, a leader-elected worker, or an admin endpoint if you need finer control. **Why** Previously, the durable agent's agentic loop was an awaited in-process Promise and `globalRunRegistry` was an in-memory TTLCache, so any RUNNING run silently died on process restart with no boot-time recovery or re-drive API (see issue #19056). Suspended runs already had `prepare`/`resume`/`listSuspendedRuns`; RUNNING runs now have the equivalent discover-and-recover pair. **Usage** ```ts // Opt into boot-time recovery for every durable agent on this Mastra instance. // The deployer will call `mastra.recoverAllDurableAgents()` automatically // after restarting active workflow runs. const mastra = new Mastra({ agents: { support: supportDurableAgent }, storage, recovery: { durableAgents: 'auto' }, }); // Or drive it yourself (cron, leader election, admin endpoint, etc.): const { agents, recovered, succeeded, failed } = await mastra.recoverAllDurableAgents(); // Per-agent: drain all orphaned RUNNING runs on one agent. const agent = mastra.getAgent('support') as DurableAgent; await agent.recoverActiveRuns(); // Per-run: recover a specific run and stream its output to the caller. const { fullStream, runId, cleanup } = await agent.recover('run-abc123'); for await (const chunk of fullStream) { // forward chunks to the client, log them, etc. } cleanup(); ``` ## 1.51.0-alpha.5 ### Minor Changes - Added file-system routing for a Mastra logger and per-agent scorers. ([#19262](https://github.com/mastra-ai/mastra/pull/19262)) Define a logger in `src/mastra/logger.ts` (default export) and it is auto-registered as the Mastra logger, just like `storage.ts` and `observability.ts`. A code-registered logger still wins. Register scorers per agent by adding an `agents//scorers/` folder. Each module's default export (a `MastraScorer`, or a `{ scorer, sampling }` entry) is wired into that agent, keyed by filename. `config.scorers` wins on collision. ```text src/mastra/ logger.ts # export default new PinoLogger({ name: 'App' }) agents/weather/ config.ts scorers/ relevance.ts # export default myRelevanceScorer ``` ### Patch Changes - Added an optional `scope` field to `ResolveToolsOpts` so tool providers can see a connection's identity bucketing (`per-author`, `shared`, or `caller-supplied`) when resolving tools. Providers can use this to let the backend auto-resolve an account within a caller's bucket instead of pinning a specific one. The field is optional and defaults to previous behavior when absent. ([#19144](https://github.com/mastra-ai/mastra/pull/19144)) Also added an optional `defaultScope` to `BaseToolProviderOptions` (surfaced on the `ToolProvider` interface as `defaultScope`). This lets an app author set a tool provider's connection scope at config time — for example `defaultScope: 'caller-supplied'` for multi-tenant OAuth — so every connection authorized against the provider is bucketed correctly without any per-connection UI control. Defaults to `'per-author'` when absent. ```ts import { BaseToolProvider, type ResolveToolsOpts } from '@mastra/core/tool-provider'; class MyToolProvider extends BaseToolProvider { // ...required members like `info` and `capabilities` elided constructor() { // Config-level tenancy decision: bucket connections per caller. super({ defaultScope: 'caller-supplied' }); } async resolveToolsVNext(opts: ResolveToolsOpts) { if (opts.scope === 'caller-supplied') { // Let the backend auto-resolve an account within the caller's // bucket instead of pinning a specific connected account. } // ... } } ``` ## 1.51.0-alpha.4 ### Minor Changes - Added support for custom processors on `createScorer` judges. You can now pass `inputProcessors`, `outputProcessors`, `errorProcessors`, and `maxProcessorRetries` in a scorer's `judge` config (or per-step judge config) to apply processors to the internal judge agent — for example wiring up `StreamErrorRetryProcessor` to retry transient LLM errors while scoring. ([#19195](https://github.com/mastra-ai/mastra/pull/19195)) ```typescript import { createScorer } from '@mastra/core/evals'; import { StreamErrorRetryProcessor } from '@mastra/core/processors'; const scorer = createScorer({ id: 'my-scorer', description: 'Scores responses', judge: { model: myModel, instructions: 'You are an expert evaluator...', errorProcessors: [new StreamErrorRetryProcessor()], maxProcessorRetries: 3, }, }); ``` - Added anonymous feature usage telemetry for server startup surface counts and a `trackFeatureUsage()` API. ([#19159](https://github.com/mastra-ai/mastra/pull/19159)) - Added optional `log` and `progress` functions to the MCP tool execution context type so tools running in an MCP server can send log and progress notifications to the calling client. ([#19193](https://github.com/mastra-ai/mastra/pull/19193)) Added `mastra.removeTool(key)` to remove a dynamically registered tool from the Mastra instance, the counterpart to `mastra.addTool()`: ```typescript mastra.addTool(calculatorTool); mastra.removeTool('calculator-tool'); // returns true if a tool was removed ``` ### Patch Changes - Pass tracingContext through to runProcessAPIError so error-processor runs show up as processor_run spans in observability exports ([#19188](https://github.com/mastra-ai/mastra/pull/19188)) - Fixed `iterationCount` always being 1 for `dountil` and `dowhile` loops on the evented workflow engine. The count was never carried forward between iterations, so any loop whose condition depended on `iterationCount` never advanced and ran forever. ([#19233](https://github.com/mastra-ai/mastra/pull/19233)) **Before** ```ts // On the evented engine this loop never terminated: iterationCount stayed 1. workflow.dountil(step, async ({ iterationCount }) => iterationCount >= 3); ``` **After** The condition now receives an incrementing count (1, 2, 3, ...) exactly as it does on the default engine, so the loop stops as expected. Loops whose conditions read step output (`inputData`) were unaffected. - Fixed an infinite retry loop in evented workflows when workflow storage is unavailable (for example when the workflows table does not exist). A failing workflow.fail event no longer republishes another workflow.fail after exhausting its retry budget, which previously flooded logs with endless "error processing event" entries. ([#19194](https://github.com/mastra-ai/mastra/pull/19194)) ## 1.51.0-alpha.3 ### Patch Changes - - Fixed DurableAgent returning stale/empty values for Studio metadata, processors, skills, workflows, voice, and other inherited Agent accessors by adding delegation overrides to the wrapped agent ([#19076](https://github.com/mastra-ai/mastra/pull/19076)) - Fixed missing traces in Studio: span `entityId` now uses the durable agent's ID instead of the wrapped agent's ID - Fixed dropped `background-task-completed` events in `streamUntilIdle` caused by the same agent ID mismatch - `\_\_setMemory`, `\_\_setPubSub`, and `\_\_setWorkspace` now propagate to both the DurableAgent base class and the wrapped agent - Extract text from DB-shaped user messages in goal judge prompts instead of stringifying them as `[object Object]`. Goal judge prompts now also skip malformed, empty, or synthetic reminder messages when selecting the latest user context. ([#19070](https://github.com/mastra-ai/mastra/pull/19070)) ## 1.51.0-alpha.2 ### Minor Changes - Added the ability to explicitly disable a storage domain on `MastraCompositeStore` by setting it to `false` in the `domains` config. A disabled domain no longer falls back to the `editor` or `default` store, so writes for that domain are dropped instead of silently landing in the fallback database. ([#19059](https://github.com/mastra-ai/mastra/pull/19059)) ```ts const storage = new MastraCompositeStore({ id: 'my-storage', default: libsqlStore, domains: { // don't persist traces/metrics when observability is turned off observability: false, }, }); ``` `prune()` also accepts a per-call `retention` option that replaces the configured retention policies for that call only — for example to skip a domain (keep chat history) or prune more aggressively without reconstructing the store: ```ts // prune everything except the memory domain, one time await storage.prune({ retention: { observability: { spans: { maxAge: '14d' } }, }, }); ``` ### Patch Changes - Fix heap OOM when a workflow uses `.map({ key: mapVariable({ initData: }) })`. The map reducer kept the live `Workflow` instance by reference and `JSON.stringify`'d it into the map step's `mapConfig`, deep-walking the whole workflow (its logger, nested step graph, …) into a multi-hundred-MB string — and the length-guard truncation only ran after the full string was built, so `.commit()` could OOM at module load. The `initData` mapping is now serialized as a slim `{ initData: , path }` reference. Runtime behavior is unchanged (the execute path only reads `initData` for truthiness). ([#19033](https://github.com/mastra-ai/mastra/pull/19033)) - Durable agents now stop cleanly when cancelled via an abort signal, reporting the correct `abort` finish reason instead of crashing. The `sendMessage` and `sendSignal` wake flows also work reliably for durable agents, so thread subscribers receive streamed chunks as expected. ([#19046](https://github.com/mastra-ai/mastra/pull/19046)) - Fixed typo in agent-network e2e test description ([#19162](https://github.com/mastra-ai/mastra/pull/19162)) - Fixed the tool payload transform policy being dropped on non-durable agent streams. When an agent was configured with a `transform` policy, `loop()` rebuilt its internal state bag but did not carry the policy forward, so it never reached the run scope and silently no-opped for the whole run. The policy is now forwarded, so tool call, tool result, and tool input delta payloads are transformed as configured. Closes #19102. ([#19103](https://github.com/mastra-ai/mastra/pull/19103)) - Updated dependencies [[`bc1121a`](https://github.com/mastra-ai/mastra/commit/bc1121a7bb98f7cd73e82e3a7913a667a9fa9911)]: - @mastra/schema-compat@1.3.4-alpha.1 ## 1.50.2-alpha.1 ### Patch Changes - Updated dependencies [[`6789ab4`](https://github.com/mastra-ai/mastra/commit/6789ab4191ddcd32a932898b360b191e80cee1a9)]: - @mastra/schema-compat@1.3.4-alpha.0 ## 1.50.2-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`fe1bda0`](https://github.com/mastra-ai/mastra/commit/fe1bda06f6af92a694a51712db747cda1e7185f0)) ## 1.50.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`e900f25`](https://github.com/mastra-ai/mastra/commit/e900f25dfe2c9237f15b26cb109ac55aa9de3000)) - Fixed slow agent streaming with remote storage: output processors run an internal workflow once per streamed chunk, and each run performed a storage read that could never hit (the run id is freshly generated and these transient workflows never persist a snapshot). On high-latency databases this throttled token delivery to roughly one storage round-trip per token. The guaranteed-miss read is now skipped for freshly-created transient runs. (#19015) ([#19028](https://github.com/mastra-ai/mastra/pull/19028)) - Fix a custom `stopWhen` being ignored when `maxSteps` is also set. Previously, setting `maxSteps` replaced the user's `stopWhen` with `stepCountIs(maxSteps)`, so the agent could not stop early and ran to the `maxSteps` cap. The two are now composed, so `stopWhen` still fires while `maxSteps` acts as an upper safety cap. Closes #19007. ([#19009](https://github.com/mastra-ai/mastra/pull/19009)) - Fixed durable agent tool error recovery to always continue the agentic loop when tool errors occur, matching the regular agent's behavior. This allows the model to self-correct after tool failures instead of stopping the loop prematurely. ([#18881](https://github.com/mastra-ai/mastra/pull/18881)) - Fixed the internal notification dispatcher starting the workflow scheduler in every app. Since 1.39.0 every Mastra instance registered the notification dispatch cron at boot, which kept the scheduler polling storage every 10 seconds — generating constant network traffic to remote databases (like Turso) and preventing serverless containers from ever scaling to zero. The dispatch schedule now activates lazily on the first deferred or summarized notification, so apps that never defer notifications no longer run a scheduler at all. Stale dispatcher schedule rows left behind by earlier versions are cleaned up automatically the next time a scheduler runs. Fixes #18864. ([#18907](https://github.com/mastra-ai/mastra/pull/18907)) ## 1.50.1-alpha.2 ### Patch Changes - Fixed the internal notification dispatcher starting the workflow scheduler in every app. Since 1.39.0 every Mastra instance registered the notification dispatch cron at boot, which kept the scheduler polling storage every 10 seconds — generating constant network traffic to remote databases (like Turso) and preventing serverless containers from ever scaling to zero. The dispatch schedule now activates lazily on the first deferred or summarized notification, so apps that never defer notifications no longer run a scheduler at all. Stale dispatcher schedule rows left behind by earlier versions are cleaned up automatically the next time a scheduler runs. Fixes #18864. ([#18907](https://github.com/mastra-ai/mastra/pull/18907)) ## 1.50.1-alpha.1 ### Patch Changes - Fixed slow agent streaming with remote storage: output processors run an internal workflow once per streamed chunk, and each run performed a storage read that could never hit (the run id is freshly generated and these transient workflows never persist a snapshot). On high-latency databases this throttled token delivery to roughly one storage round-trip per token. The guaranteed-miss read is now skipped for freshly-created transient runs. (#19015) ([#19028](https://github.com/mastra-ai/mastra/pull/19028)) - Fix a custom `stopWhen` being ignored when `maxSteps` is also set. Previously, setting `maxSteps` replaced the user's `stopWhen` with `stepCountIs(maxSteps)`, so the agent could not stop early and ran to the `maxSteps` cap. The two are now composed, so `stopWhen` still fires while `maxSteps` acts as an upper safety cap. Closes #19007. ([#19009](https://github.com/mastra-ai/mastra/pull/19009)) - Fixed durable agent tool error recovery to always continue the agentic loop when tool errors occur, matching the regular agent's behavior. This allows the model to self-correct after tool failures instead of stopping the loop prematurely. ([#18881](https://github.com/mastra-ai/mastra/pull/18881)) ## 1.50.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`e900f25`](https://github.com/mastra-ai/mastra/commit/e900f25dfe2c9237f15b26cb109ac55aa9de3000)) ## 1.50.0 ### Minor Changes - Added file-system-routed observability singleton. Place an `observability.ts` file in your mastra directory that default-exports an `ObservabilityEntrypoint`, and it will be auto-discovered and registered when running `mastra dev` or `mastra build`. Code-registered observability takes precedence if both are present. ([#18887](https://github.com/mastra-ai/mastra/pull/18887)) ```ts // src/mastra/observability.ts import { Observability, MastraStorageExporter } from '@mastra/observability'; export default new Observability({ configs: { default: { serviceName: 'mastra', exporters: [new MastraStorageExporter()] } }, }); ``` - Added file-system routed storage support. A `storage.ts` file under the mastra directory is now auto-discovered and registered during `mastra dev` / `mastra build`. The default export replaces the InMemoryStore fallback. Code-registered storage (passed to `new Mastra({storage})`) wins on collision. ([#18885](https://github.com/mastra-ai/mastra/pull/18885)) ```ts // src/mastra/storage.ts import { LibSQLStore } from '@mastra/libsql'; export default new LibSQLStore({ url: 'file:local.db' }); ``` - Added file-system-routed workflows support. Workflows placed in `workflows/*.ts` under the mastra directory are now auto-discovered and registered during `mastra dev` / `mastra build`, matching the existing file-based agents convention. Code-registered workflows win on name collisions. ([#18883](https://github.com/mastra-ai/mastra/pull/18883)) ```ts // src/mastra/workflows/onboarding.ts import { createWorkflow } from '@mastra/core/workflows'; export default createWorkflow({ id: 'onboarding' /* ...steps */ }); ``` - Added workspace-level provider registry to MastraEditor. You can now register WorkspaceProvider factories that build complete Workspace instances as a single unit, instead of composing from separate filesystem and sandbox providers. Stored agents can reference a workspace provider via `{ type: 'provider', provider: 'my-cloud', config: { ... } }` and the editor will call the registered factory during agent hydration. ([#18781](https://github.com/mastra-ai/mastra/pull/18781)) ```ts import { MastraEditor } from '@mastra/editor'; import { Workspace } from '@mastra/core/workspace'; const editor = new MastraEditor({ workspaces: { 'my-cloud': { id: 'my-cloud', name: 'My Cloud Workspace', createWorkspace: config => new Workspace({ id: 'cloud-ws', name: 'Cloud WS', filesystem: new MyCloudFilesystem(config), sandbox: new MyCloudSandbox(config), }), }, }, }); // Stored agent workspace reference using the provider: // { type: 'provider', provider: 'my-cloud', config: { region: 'us-east-1' } } ``` - models.dev gateway: honor per-model `provider` overrides (endpoint, request shape, SDK). ([`10959d5`](https://github.com/mastra-ai/mastra/commit/10959d509d824f682d40ff96e05ee044aec3b0e5)) A provider can now serve individual models over a different base URL / request shape than the provider default — e.g. a model served over the OpenAI **Responses** API while the provider default is chat-completions. The models.dev gateway now reads each model's `provider` block (`api`, `shape`, `npm`), so `resolveLanguageModel` routes `shape: "responses"` models via the OpenAI Responses API and `buildUrl` prefers a per-model `api` when present. Providers without per-model overrides are unaffected. - Added file-system routed server config singleton. Place a server.ts file in your mastra directory that default-exports a ServerConfig object, and it will be auto-discovered and registered when running mastra dev or mastra build. Code-registered server config takes precedence if both are present. ([#18888](https://github.com/mastra-ai/mastra/pull/18888)) - Added file-system-routed agent processors. Place input and output processor files under `agents//processors/input/` and `agents//processors/output/`. Each file default-exports a processor, and they are auto-discovered and merged with config-defined processors when running `mastra dev` or `mastra build`. Config-defined processors run first, and a dynamic (function) `inputProcessors`/`outputProcessors` in `config.ts` takes precedence over discovered files. ([#18890](https://github.com/mastra-ai/mastra/pull/18890)) ``` src/mastra/agents/support/ ├── config.ts ├── instructions.md └── processors/ ├── input/ │ └── moderation.ts └── output/ └── redact-pii.ts ``` ```ts // src/mastra/agents/support/processors/input/moderation.ts import { ModerationProcessor } from '@mastra/core/processors'; export default new ModerationProcessor({ model: 'openai/gpt-5-nano' }); ``` - **Renamed heartbeats to schedules.** Agent heartbeats and workflow schedules are now one unified Schedules API: `mastra.schedules` manages both. The name "heartbeat" implied a liveness check; these are cron-based agent schedules, so they are now simply called schedules. ([#18874](https://github.com/mastra-ai/mastra/pull/18874)) **Before** ```ts const hb = await mastra.heartbeats.create({ agentId: 'chef', cron: '0 9 * * *', prompt: 'Suggest a dish of the day', }); await mastra.heartbeats.pause(hb.id); ``` **After** ```ts // Schedule an agent (was a heartbeat) const schedule = await mastra.schedules.create({ agentId: 'chef', cron: '0 9 * * *', prompt: 'Suggest a dish of the day', }); // Schedule a workflow with the same API await mastra.schedules.create({ workflowId: 'daily-report', cron: '0 6 * * *', inputData: { region: 'us' }, }); await mastra.schedules.pause(schedule.id); ``` What changed: - `mastra.heartbeats` is now `mastra.schedules` and also creates, lists, updates, pauses, resumes, runs, and deletes workflow schedules. Results are discriminated by `agentId` vs `workflowId`. - The Mastra config option `heartbeat: { ... }` (lifecycle hooks) is now `schedules: { ... }`, and hook types were renamed (`HeartbeatHooks` → `ScheduleHooks`, `HeartbeatPrepareContext` → `SchedulePrepareContext`, and so on). - New agent schedule ids use the `agent_` prefix instead of `hb_`. Existing `hb_` ids keep working. - The default signal tag an agent receives on a fire is now `` instead of ``. - Types renamed: `Heartbeat` → `AgentSchedule`, `CreateHeartbeatInput` → `CreateAgentScheduleInput`, `HeartbeatScheduleTarget` → `AgentScheduleTarget` (persisted `target.type` is now `'agent'` instead of `'heartbeat'`). Existing schedules stored in your database keep working: rows persisted with the old `target.type: 'heartbeat'` are read as `'agent'` automatically and keep firing. - Added file-system routed studio config singleton. Place a studio.ts file in your mastra directory that default-exports a StudioConfig object, and it will be auto-discovered and registered when running mastra dev or mastra build. Code-registered studio config takes precedence if both are present. ([#18889](https://github.com/mastra-ai/mastra/pull/18889)) ### Patch Changes - Fix workflow snapshot bloat on agent HITL tool-approval suspensions (#18647). Agent-run snapshots previously grew with thread length × number of historical suspensions because completed steps retained stale `suspendPayload`s (each embedding a full serialized message list) and duplicated message arrays across step payloads and outputs. Snapshots could balloon to 5MB+ on long threads and hit storage row-size limits. ([#18862](https://github.com/mastra-ai/mastra/pull/18862)) Agent-loop snapshots are now pruned to minimal resume artifacts before persist: completed steps drop suspension payloads and heavy message/step data, while suspended steps keep their full resume state intact. Snapshot size now stays flat across sequential approvals — O(thread) instead of O(suspensions × thread). This also adds an optional `pruneSnapshot` workflow option (alongside `shouldPersistSnapshot`) that transforms a snapshot immediately before it is persisted. User-authored workflows are unaffected and persist full snapshots by default. - Add unit test coverage for agent utility functions in `packages/core/src/agent/utils.ts`. ([#18896](https://github.com/mastra-ai/mastra/pull/18896)) - Update provider registry and model documentation with latest models and providers ([`6ef59fe`](https://github.com/mastra-ai/mastra/commit/6ef59fef1da52ed8da5fbb2a892c71cf4fb6c739)) - Fixed cross-pod signal routing to dead runIds in multi-pod deployments without sticky sessions ([#18614](https://github.com/mastra-ai/mastra/pull/18614)) - Fix `DurableAgent` persisting messages when `memory.options.readOnly` is `true`. The durable finish path saved via the save queue directly, bypassing the `MessageHistory` processor that enforces `readOnly` in the non-durable path, so messages were written against the caller's explicit "read but don't save" instruction. The durable path now honors `readOnly` and skips persistence, matching the non-durable agent. Closes #18771. ([#18921](https://github.com/mastra-ai/mastra/pull/18921)) - Added `flush()` to `ObservabilityEntrypoint` so `mastra.observability.flush()` works directly in serverless environments. ([#18873](https://github.com/mastra-ai/mastra/pull/18873)) Previously, `flush()` only existed on individual `ObservabilityInstance` objects, requiring users to call `mastra.observability.getDefaultInstance()?.flush()`. The entrypoint-level `flush()` delegates to all registered instances, matching the existing `shutdown()` pattern. ```ts // Before (broken — getObservability() didn't exist, flush() wasn't on the entrypoint) const observability = mastra.getObservability(); await observability.flush(); // After await mastra.observability.flush(); ``` Fixed the serverless flush docs in the observability config guide and Vercel deployment guide to use the correct API. - Fixed durable agent parity gaps for structured output, output processors, callbacks, error processors, suspend/resume, and tool lifecycle hooks. Durable agents now support structured output schemas, per-chunk output processor streaming, onStepFinish/onFinish/onError callbacks, error processor retry loops, tool context.writer chunks, and suspend/resume with proper data flow. These fixes bring durable agent scenario test coverage from 43% to over 90%. ([#18857](https://github.com/mastra-ai/mastra/pull/18857)) - Add unit test coverage for storage utility functions in `packages/core/src/storage/utils.ts`. ([#18895](https://github.com/mastra-ai/mastra/pull/18895)) - Fixed `listSuspendedRuns()` reporting `toolCallId: undefined` for tool calls parked via `suspend()`. The id was only stored as the workflow resume label, so discovery dropped it and `sendToolApproval({ toolCallId })` could never match the run once it had to be resolved from storage. The suspend payload now carries the id (agentic and durable loops), and discovery recovers it from resume labels for snapshots persisted before this change. ([#18940](https://github.com/mastra-ai/mastra/pull/18940)) - Fixed `agent.generate()` and `agent.stream()` rejecting AI SDK v7 messages under strict TypeScript. AI SDK v7 `ModelMessage` and `UIMessage` inputs are now accepted, matching the existing v4-v6 support. (#18956) ([#18997](https://github.com/mastra-ai/mastra/pull/18997)) - Fixed message hydration to backfill resource IDs when thread IDs are already present. ([#18931](https://github.com/mastra-ai/mastra/pull/18931)) - Fixed an issue where writes to a shared RequestContext inside tools were lost because the tool received a cloned context instead of the original. Tool writes are now preserved by reusing the shared context instance. ([#18872](https://github.com/mastra-ai/mastra/pull/18872)) - Hardened several string-parsing code paths against regular-expression denial of service (ReDoS). Path normalization, URL trimming, LLM token stripping, and observation parsing now use linear-time string scanning instead of regexes that could back-track polynomially on adversarial input. No behavior changes. ([#18801](https://github.com/mastra-ai/mastra/pull/18801)) - Fixed a TypeScript build error caused by the regenerated provider registry adding per-model shape overrides ([#18995](https://github.com/mastra-ai/mastra/pull/18995)) ## 1.50.0-alpha.5 ### Minor Changes - Added file-system-routed agent processors. Place input and output processor files under `agents//processors/input/` and `agents//processors/output/`. Each file default-exports a processor, and they are auto-discovered and merged with config-defined processors when running `mastra dev` or `mastra build`. Config-defined processors run first, and a dynamic (function) `inputProcessors`/`outputProcessors` in `config.ts` takes precedence over discovered files. ([#18890](https://github.com/mastra-ai/mastra/pull/18890)) ``` src/mastra/agents/support/ ├── config.ts ├── instructions.md └── processors/ ├── input/ │ └── moderation.ts └── output/ └── redact-pii.ts ``` ```ts // src/mastra/agents/support/processors/input/moderation.ts import { ModerationProcessor } from '@mastra/core/processors'; export default new ModerationProcessor({ model: 'openai/gpt-5-nano' }); ``` ## 1.50.0-alpha.4 ### Minor Changes - Added file-system-routed observability singleton. Place an `observability.ts` file in your mastra directory that default-exports an `ObservabilityEntrypoint`, and it will be auto-discovered and registered when running `mastra dev` or `mastra build`. Code-registered observability takes precedence if both are present. ([#18887](https://github.com/mastra-ai/mastra/pull/18887)) ```ts // src/mastra/observability.ts import { Observability, MastraStorageExporter } from '@mastra/observability'; export default new Observability({ configs: { default: { serviceName: 'mastra', exporters: [new MastraStorageExporter()] } }, }); ``` - Added file-system routed server config singleton. Place a server.ts file in your mastra directory that default-exports a ServerConfig object, and it will be auto-discovered and registered when running mastra dev or mastra build. Code-registered server config takes precedence if both are present. ([#18888](https://github.com/mastra-ai/mastra/pull/18888)) - Added file-system routed studio config singleton. Place a studio.ts file in your mastra directory that default-exports a StudioConfig object, and it will be auto-discovered and registered when running mastra dev or mastra build. Code-registered studio config takes precedence if both are present. ([#18889](https://github.com/mastra-ai/mastra/pull/18889)) ### Patch Changes - Fixed cross-pod signal routing to dead runIds in multi-pod deployments without sticky sessions ([#18614](https://github.com/mastra-ai/mastra/pull/18614)) - Fix `DurableAgent` persisting messages when `memory.options.readOnly` is `true`. The durable finish path saved via the save queue directly, bypassing the `MessageHistory` processor that enforces `readOnly` in the non-durable path, so messages were written against the caller's explicit "read but don't save" instruction. The durable path now honors `readOnly` and skips persistence, matching the non-durable agent. Closes #18771. ([#18921](https://github.com/mastra-ai/mastra/pull/18921)) - Fixed `agent.generate()` and `agent.stream()` rejecting AI SDK v7 messages under strict TypeScript. AI SDK v7 `ModelMessage` and `UIMessage` inputs are now accepted, matching the existing v4-v6 support. (#18956) ([#18997](https://github.com/mastra-ai/mastra/pull/18997)) - Fixed a TypeScript build error caused by the regenerated provider registry adding per-model shape overrides ([#18995](https://github.com/mastra-ai/mastra/pull/18995)) ## 1.50.0-alpha.3 ### Minor Changes - Added file-system routed storage support. A `storage.ts` file under the mastra directory is now auto-discovered and registered during `mastra dev` / `mastra build`. The default export replaces the InMemoryStore fallback. Code-registered storage (passed to `new Mastra({storage})`) wins on collision. ([#18885](https://github.com/mastra-ai/mastra/pull/18885)) ```ts // src/mastra/storage.ts import { LibSQLStore } from '@mastra/libsql'; export default new LibSQLStore({ url: 'file:local.db' }); ``` - models.dev gateway: honor per-model `provider` overrides (endpoint, request shape, SDK). ([`10959d5`](https://github.com/mastra-ai/mastra/commit/10959d509d824f682d40ff96e05ee044aec3b0e5)) A provider can now serve individual models over a different base URL / request shape than the provider default — e.g. a model served over the OpenAI **Responses** API while the provider default is chat-completions. The models.dev gateway now reads each model's `provider` block (`api`, `shape`, `npm`), so `resolveLanguageModel` routes `shape: "responses"` models via the OpenAI Responses API and `buildUrl` prefers a per-model `api` when present. Providers without per-model overrides are unaffected. - **Renamed heartbeats to schedules.** Agent heartbeats and workflow schedules are now one unified Schedules API: `mastra.schedules` manages both. The name "heartbeat" implied a liveness check; these are cron-based agent schedules, so they are now simply called schedules. ([#18874](https://github.com/mastra-ai/mastra/pull/18874)) **Before** ```ts const hb = await mastra.heartbeats.create({ agentId: 'chef', cron: '0 9 * * *', prompt: 'Suggest a dish of the day', }); await mastra.heartbeats.pause(hb.id); ``` **After** ```ts // Schedule an agent (was a heartbeat) const schedule = await mastra.schedules.create({ agentId: 'chef', cron: '0 9 * * *', prompt: 'Suggest a dish of the day', }); // Schedule a workflow with the same API await mastra.schedules.create({ workflowId: 'daily-report', cron: '0 6 * * *', inputData: { region: 'us' }, }); await mastra.schedules.pause(schedule.id); ``` What changed: - `mastra.heartbeats` is now `mastra.schedules` and also creates, lists, updates, pauses, resumes, runs, and deletes workflow schedules. Results are discriminated by `agentId` vs `workflowId`. - The Mastra config option `heartbeat: { ... }` (lifecycle hooks) is now `schedules: { ... }`, and hook types were renamed (`HeartbeatHooks` → `ScheduleHooks`, `HeartbeatPrepareContext` → `SchedulePrepareContext`, and so on). - New agent schedule ids use the `agent_` prefix instead of `hb_`. Existing `hb_` ids keep working. - The default signal tag an agent receives on a fire is now `` instead of ``. - Types renamed: `Heartbeat` → `AgentSchedule`, `CreateHeartbeatInput` → `CreateAgentScheduleInput`, `HeartbeatScheduleTarget` → `AgentScheduleTarget` (persisted `target.type` is now `'agent'` instead of `'heartbeat'`). Existing schedules stored in your database keep working: rows persisted with the old `target.type: 'heartbeat'` are read as `'agent'` automatically and keep firing. ### Patch Changes - Fix workflow snapshot bloat on agent HITL tool-approval suspensions (#18647). Agent-run snapshots previously grew with thread length × number of historical suspensions because completed steps retained stale `suspendPayload`s (each embedding a full serialized message list) and duplicated message arrays across step payloads and outputs. Snapshots could balloon to 5MB+ on long threads and hit storage row-size limits. ([#18862](https://github.com/mastra-ai/mastra/pull/18862)) Agent-loop snapshots are now pruned to minimal resume artifacts before persist: completed steps drop suspension payloads and heavy message/step data, while suspended steps keep their full resume state intact. Snapshot size now stays flat across sequential approvals — O(thread) instead of O(suspensions × thread). This also adds an optional `pruneSnapshot` workflow option (alongside `shouldPersistSnapshot`) that transforms a snapshot immediately before it is persisted. User-authored workflows are unaffected and persist full snapshots by default. - Fixed `listSuspendedRuns()` reporting `toolCallId: undefined` for tool calls parked via `suspend()`. The id was only stored as the workflow resume label, so discovery dropped it and `sendToolApproval({ toolCallId })` could never match the run once it had to be resolved from storage. The suspend payload now carries the id (agentic and durable loops), and discovery recovers it from resume labels for snapshots persisted before this change. ([#18940](https://github.com/mastra-ai/mastra/pull/18940)) ## 1.50.0-alpha.2 ### Minor Changes - Added file-system-routed workflows support. Workflows placed in `workflows/*.ts` under the mastra directory are now auto-discovered and registered during `mastra dev` / `mastra build`, matching the existing file-based agents convention. Code-registered workflows win on name collisions. ([#18883](https://github.com/mastra-ai/mastra/pull/18883)) ```ts // src/mastra/workflows/onboarding.ts import { createWorkflow } from '@mastra/core/workflows'; export default createWorkflow({ id: 'onboarding' /* ...steps */ }); ``` ### Patch Changes - Fixed message hydration to backfill resource IDs when thread IDs are already present. ([#18931](https://github.com/mastra-ai/mastra/pull/18931)) ## 1.50.0-alpha.1 ### Patch Changes - Add unit test coverage for agent utility functions in `packages/core/src/agent/utils.ts`. ([#18896](https://github.com/mastra-ai/mastra/pull/18896)) - Added `flush()` to `ObservabilityEntrypoint` so `mastra.observability.flush()` works directly in serverless environments. ([#18873](https://github.com/mastra-ai/mastra/pull/18873)) Previously, `flush()` only existed on individual `ObservabilityInstance` objects, requiring users to call `mastra.observability.getDefaultInstance()?.flush()`. The entrypoint-level `flush()` delegates to all registered instances, matching the existing `shutdown()` pattern. ```ts // Before (broken — getObservability() didn't exist, flush() wasn't on the entrypoint) const observability = mastra.getObservability(); await observability.flush(); // After await mastra.observability.flush(); ``` Fixed the serverless flush docs in the observability config guide and Vercel deployment guide to use the correct API. - Fixed durable agent parity gaps for structured output, output processors, callbacks, error processors, suspend/resume, and tool lifecycle hooks. Durable agents now support structured output schemas, per-chunk output processor streaming, onStepFinish/onFinish/onError callbacks, error processor retry loops, tool context.writer chunks, and suspend/resume with proper data flow. These fixes bring durable agent scenario test coverage from 43% to over 90%. ([#18857](https://github.com/mastra-ai/mastra/pull/18857)) - Add unit test coverage for storage utility functions in `packages/core/src/storage/utils.ts`. ([#18895](https://github.com/mastra-ai/mastra/pull/18895)) - Fixed an issue where writes to a shared RequestContext inside tools were lost because the tool received a cloned context instead of the original. Tool writes are now preserved by reusing the shared context instance. ([#18872](https://github.com/mastra-ai/mastra/pull/18872)) - Hardened several string-parsing code paths against regular-expression denial of service (ReDoS). Path normalization, URL trimming, LLM token stripping, and observation parsing now use linear-time string scanning instead of regexes that could back-track polynomially on adversarial input. No behavior changes. ([#18801](https://github.com/mastra-ai/mastra/pull/18801)) ## 1.50.0-alpha.0 ### Minor Changes - Added workspace-level provider registry to MastraEditor. You can now register WorkspaceProvider factories that build complete Workspace instances as a single unit, instead of composing from separate filesystem and sandbox providers. Stored agents can reference a workspace provider via `{ type: 'provider', provider: 'my-cloud', config: { ... } }` and the editor will call the registered factory during agent hydration. ([#18781](https://github.com/mastra-ai/mastra/pull/18781)) ```ts import { MastraEditor } from '@mastra/editor'; import { Workspace } from '@mastra/core/workspace'; const editor = new MastraEditor({ workspaces: { 'my-cloud': { id: 'my-cloud', name: 'My Cloud Workspace', createWorkspace: config => new Workspace({ id: 'cloud-ws', name: 'Cloud WS', filesystem: new MyCloudFilesystem(config), sandbox: new MyCloudSandbox(config), }), }, }, }); // Stored agent workspace reference using the provider: // { type: 'provider', provider: 'my-cloud', config: { region: 'us-east-1' } } ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`6ef59fe`](https://github.com/mastra-ai/mastra/commit/6ef59fef1da52ed8da5fbb2a892c71cf4fb6c739)) ## 1.49.0 ### Minor Changes - Added opt-in storage retention. Declare per-table `maxAge` policies in the `retention` config, then call `storage.prune()` to delete rows older than their age. Anything you don't configure is kept forever, so there is no change until you opt in. ([#18733](https://github.com/mastra-ai/mastra/pull/18733)) Retention covers growth tables across ten domains — `memory` (threads, messages, resources), `threadState`, `observability` (spans), `scores`, `workflows` (run snapshots), `backgroundTasks`, `experiments`, `notifications`, `harness` (sessions), and `schedules` (fire history). Anchors are chosen so `maxAge` is honest: creation time for append-only logs, last activity for workflow snapshots and thread state, and completion time for background tasks and experiments (in-flight work is never pruned). User-authored artifacts and config (agents, skills, workspaces, datasets, schedule definitions, and so on) are not prunable. `prune()` is safe on large tables: it deletes in bounded, batched, resumable, cancellable chunks and never locks the database for long. Call it from your own scheduler; when a result reports `done: false`, eligible rows remain and the next run continues. `prune()` only deletes rows — reclaiming disk to the OS is left to the underlying database and the operator. ```typescript const storage = new MastraCompositeStore({ id: 'composite', retention: { memory: { messages: { maxAge: '30d' }, threads: { maxAge: '90d' } }, observability: { spans: { maxAge: '7d' } }, }, domains: {/* ... */}, }); // Wire this to your own cron — Mastra never runs it for you. const results = await storage.prune(); ``` - Added maxDurationMs, maxWidth, and maxHeight options to BrowserRecordingOptions. These can now be set on the recording config object to provide defaults for every recording, instead of relying on agent instructions to pass them to the tool at start time. ([#18814](https://github.com/mastra-ai/mastra/pull/18814)) ```ts const browser = new AgentBrowser({ recording: { outputDir: './recordings', maxDurationMs: 60_000, maxWidth: 1280, maxHeight: 720, }, }); ``` Per-recording overrides via the browser_record tool still take precedence. - File-based agents can now nest subagents up to three levels deep. A subagent directory can declare its own `subagents/`, and each level is assembled and wired into its parent as a delegation tool. Levels deeper than the cap are ignored with a warning. ([#18780](https://github.com/mastra-ai/mastra/pull/18780)) ```text src/mastra/agents/ supervisor/ # depth 0 subagents/ researcher/ # depth 1 subagents/ summarizer/ # depth 2 ``` - Fixed a cross-tenant data-access issue on datasets by scoping `DatasetsManager.get` and `DatasetsManager.delete` to tenancy filters. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) Previously `get({ id })` and `delete({ id })` looked up a dataset by its primary key alone. Any caller who knew a dataset id could read or delete it regardless of which `organizationId` / `projectId` it belonged to. This is now closed at the storage layer via a scoped SQL predicate (option (a) — no fetch-then-assert). **What changed** - `DatasetsManager.get` and `DatasetsManager.delete` accept optional `organizationId` and `projectId`. - The tenancy is stashed on the returned `Dataset` handle and forwarded to every downstream storage call (`getDetails`, `update`, `addItem`, item batch ops, `startExperimentAsync`). - The abstract storage contract (`getDatasetById`, `deleteDataset`) gained an optional `filters?: DatasetTenancyFilters` arg. - Item-mutation inputs (`AddDatasetItemInput`, `UpdateDatasetItemInput`, `BatchInsertItemsInput`, `BatchDeleteItemsInput`) and `UpdateDatasetInput` accept optional `filters` for the internal existence check. **Behavior** - Omitting tenancy preserves the existing behavior (no predicate added) — fully backwards compatible. - On tenancy mismatch, `get` throws NOT_FOUND (returns null at the storage layer) and `delete` is a silent no-op — matching how a missing id already behaves, so existence does not leak through error timing or messages. **Example** ```ts // Before const ds = await mastra.datasets.get({ id }); await mastra.datasets.delete({ id }); // After — scope to a tenant const ds = await mastra.datasets.get({ id, organizationId, projectId }); await mastra.datasets.delete({ id, organizationId, projectId }); ``` - Added `scoreTrace()` and `scoreTraceBatch()` to `@mastra/core/evals/scoreTraces` for scoring stored traces without re-running the agent. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) - `scoreTrace()` can score either a stored trace reference or a preloaded `TraceRecord`, and it returns the persisted `ScoreRowData` after the write. - `scoreTraceBatch()` runs one scorer instance across multiple stored traces with bounded concurrency and returns per-target success and failure results. **Why** This gives baseline-style callers a small public API for persisted trace scoring when they already have a scorer instance, without widening the existing workflow-based `scoreTraces()` API. **Before** ```ts await scoreTraces({ mastra, scorerId: 'helpfulness', targets: [{ traceId, spanId }], }); ``` **After** ```ts import { scoreTrace, scoreTraceBatch } from '@mastra/core/evals/scoreTraces'; const savedScore = await scoreTrace({ storage, scorer, target: { trace: preloadedTrace, spanId }, batchId, datasetId, datasetItemId, }); const result = await scoreTraceBatch({ storage, scorer, targets, batchId, datasetId, }); ``` - Add optional `batchId`, `datasetId`, and `datasetItemId` fields to persisted scores so saved baseline scores can be grouped as one scoring pass and joined back to the dataset items they came from. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) - `scoreTrace()` accepts top-level `batchId`, `datasetId`, and `datasetItemId` when persisting a score for a stored trace. - `ScoreRowData` and score save payloads now include nullable `batchId`, `datasetId`, and `datasetItemId`. - Built-in stores with explicit score schema or attribute mappings now persist these provenance fields on saved scores. - D1, DSQL, MSSQL, and Upstash score stores now apply additive provenance migrations or deterministic score ordering for persisted score reads. ```ts await scoreTrace({ storage, scorer, target: { traceId }, batchId: 'baseline-batch-1', datasetId, datasetItemId, }); ``` - Added multi-tenant scoping to stored scorer definitions. Stored scorers now persist optional `organizationId` and `projectId` on the definition record, and `list`/`listResolved` accept matching filters to scope results by tenant. The Postgres adapter backfills the new columns and applies the scoped filters; tenancy lives on the record while version snapshots stay pure config. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) ```ts await storage.create({ scorerDefinition: { id, organizationId: 'org-a', projectId: 'proj-1', ...config }, }); const { scorerDefinitions } = await storage.list({ status: 'draft', organizationId: 'org-a', projectId: 'proj-1', }); ``` - Added optional `organizationId` and `projectId` fields to scores for multi-tenant isolation. Scores can now be saved with tenancy metadata and the `listScoresBy*` methods accept a `filters` option to scope results by organization and project. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) ```ts await storage.saveScore({ ...score, organizationId: 'org-a', projectId: 'proj-1' }); const result = await storage.listScoresByScorerId({ scorerId, filters: { organizationId: 'org-a', projectId: 'proj-1' }, }); ``` `projectId` identifies the project scope, separate from `resourceId` which continues to mean the agent memory resource. - Agents using models that dropped support for `temperature`, `topP`, or `topK` (such as `claude-opus-4-7` or `gpt-5-pro`) no longer crash with a 400 error. The model router now automatically strips unsupported sampling parameters before the request is sent — no configuration or processors needed. ([#18622](https://github.com/mastra-ai/mastra/pull/18622)) ```ts const agent = new Agent({ model: 'anthropic/claude-opus-4-7', instructions: 'You are a helpful assistant.', }); // temperature is stripped automatically — no 400 error await agent.generate('hello', { modelSettings: { temperature: 0.7 } }); ``` ### Patch Changes - Fixed signal drain parity for durable agents. Signals sent to a running durable agent (via sendSignal or sendMessage) are now consumed and echoed to the client stream, matching the behavior of regular agents. This includes initial signal echoes on the first model request, pre-run signal drain before the first LLM call, within-iteration signal drain between tool execution and task completion, and inter-iteration signal drain in the loop predicate that forces continuation so the LLM sees newly arrived signals. ([#18732](https://github.com/mastra-ai/mastra/pull/18732)) - Update provider registry and model documentation with latest models and providers ([`0f69865`](https://github.com/mastra-ai/mastra/commit/0f69865aced225d98eac812e22699dc445ee18cb)) - Surface persistence failures in experiment runs. Previously, when `addExperimentResult` threw during `runExperiment`, the failure was silently logged with `console.warn` and the run continued. The item was still counted as succeeded or failed based on the agent run outcome, so `ExperimentSummary.succeededCount` could report more rows than actually existed in `mastra_experiment_results` — silent data loss with no signal to the caller. ([#18716](https://github.com/mastra-ai/mastra/pull/18716)) Now each item result carries an optional `persistenceError: { message } | null` field, and the summary exposes an optional `persistenceFailures: number` counter. The raw error (including stack) is logged internally via the Mastra logger and intentionally omitted from the returned object so the summary can safely cross trust boundaries (e.g. UIs, API responses) without leaking internal paths. Target-run counters (`succeededCount` / `failedCount`) still reflect what the target did, and callers can inspect `persistenceFailures` to detect when the DB is out of sync with the returned summary and decide whether to retry or alert. The persistence failure is also logged via the Mastra logger at error level instead of `console.warn`. Both fields are optional on the types so external mocks / wrappers don't need to hand-construct them; the runner always populates them (`null` / `0` on the happy path). ```ts const summary = await runExperiment(mastra, { datasetId, targetType: 'agent', targetId: 'my-agent' }); if ((summary.persistenceFailures ?? 0) > 0) { const dropped = summary.results.filter(r => r.persistenceError != null); for (const item of dropped) { console.error(`item ${item.itemId} did not persist:`, item.persistenceError?.message); } } ``` - Fixed durable agent parity gaps: emit `start` chunk for correct stream ordering, handle TripWire from input processors during preparation, and port `onInputAvailable`/`onOutput` tool lifecycle hooks to the durable tool execution path. Removed stale test harness guards that were preventing `isTaskComplete`, `actor`, `savePerStep`, and `providerOptions` from reaching durable agent runs. These fixes enable 20+ scenario tests to run on the durable engine. ([#18806](https://github.com/mastra-ai/mastra/pull/18806)) - Fixed replay ordering on pull-mode transports (e.g. Redis Streams): history events are now delivered before live events, offsets are enforced on the live path, suppressed duplicates are acknowledged so persistent transports stop redelivering them, and ack/nack handles are preserved for buffered events. ([#18479](https://github.com/mastra-ai/mastra/pull/18479)) - Added optional filter arguments to `Dataset.listExperiments()` and `Dataset.listExperimentResults()`. The storage layer already accepted these filters — they are now reachable from the `Dataset` handle. All new parameters are optional and existing callers are unaffected. ([#18769](https://github.com/mastra-ai/mastra/pull/18769)) **Before:** ```typescript const { experiments } = await dataset.listExperiments({ page: 0, perPage: 10 }); const baselineOnly = experiments.filter(e => e.agentVersion === 'v1'); ``` **After:** ```typescript const { experiments } = await dataset.listExperiments({ targetType: 'agent', targetId: 'my-agent', agentVersion: 'v1', status: 'completed', page: 0, perPage: 10, }); ``` `listExperiments` accepts: `targetType`, `targetId`, `agentVersion`, `status`, tenancy `filters`. `listExperimentResults` accepts: `traceId`, `status`, tenancy `filters`. Enables baseline vs variant read patterns without client-side filtering or bypassing `Dataset`. - Added optional tenancy arguments to `getDataset`, `updateDataset`, and `deleteDataset`. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) You can now pass `organizationId` and `projectId` to scope dataset reads, updates, and deletes to a specific tenant. Reads and updates against a dataset in a different tenant throw `DATASET_NOT_FOUND` (surfaced as a 404 over HTTP). Deletes silently no-op on a tenancy mismatch — matching the existing "delete non-existent id is a no-op" semantics so cross-tenant existence is never leaked via error timing or status. **Example** ```ts // Before await client.getDataset('abc123'); await client.deleteDataset('abc123'); await client.updateDataset({ id: 'abc123', name: 'renamed' }); // After — scope to a tenant await client.getDataset('abc123', { organizationId: 'org_a', projectId: 'proj_1' }); await client.deleteDataset('abc123', { organizationId: 'org_a' }); await client.updateDataset({ id: 'abc123', name: 'renamed', organizationId: 'org_a' }); ``` - Fixed RequestContext leaking auth tokens onto scorer_run span input. Added serializeForSpan() to RequestContext so deepClean uses a safe snapshot instead of walking the internal registry Map. Also fixed the MastraScorer.run() call site to pass the serialized snapshot into the span input. ([#18776](https://github.com/mastra-ai/mastra/pull/18776)) - Pushed remaining dataset read filters and pagination down to storage. ([#18710](https://github.com/mastra-ai/mastra/pull/18710)) `DatasetsManager.list({ filters })` now accepts `targetType`, `targetIds` (overlap/union semantics), and `name` (substring, case-insensitive) in addition to the existing tenancy and candidate filters. Filtering is pushed down to the storage layer so callers no longer have to post-filter results. Storage adapters must also be upgraded to the versions listed below to honor the new filters. If a caller is on this version of `@mastra/core` but on an older storage adapter, the new `targetType`/`targetIds`/`name` filter keys are silently ignored by the adapter — no runtime error, but the filter has no effect and every dataset in the tenancy is returned. `Dataset.listItems({ version, search, page, perPage })` now applies `search` and pagination at the storage layer when `version` is provided alongside any of those. Previously they were silently dropped whenever `version` was set. The return shape is unchanged: passing only `version` still returns a bare `DatasetItem[]` snapshot; passing `search`, `page`, or `perPage` (with or without `version`) returns the paginated `{ items, pagination }` shape. The bare-array branch is marked `@deprecated`; prefer passing `page` / `perPage` to always receive the paginated shape. - Fixed SystemPromptScrubber `processOutputStream` swallowing TripWire errors when strategy is `block`. The abort call now correctly propagates the TripWire to halt the agent stream, matching the existing behavior in `processOutputResult`. ([#18794](https://github.com/mastra-ai/mastra/pull/18794)) - Fixed five DurableAgent behavioral parity gaps with the regular Agent loop: ([#18712](https://github.com/mastra-ai/mastra/pull/18712)) - **Goal step**: Durable agents now honor goal-aware stop semantics. The `goalStep` has been ported into the durable workflow, reading goal config, running completion scorers, and emitting goal chunks — matching the regular agent's behavior. - **Output processors for tool chunks**: Tool-result and tool-error chunks on durable agents now pass through output processors before emission, enabling content moderation and redaction workflows. - **Cached response replay**: Input processors that return a cached response via `processLLMRequest` now work on durable agents, short-circuiting the model call and replaying cached chunks. - **toModelOutput normalization**: Durable agents now call `toModelOutput` on successful tool results under a MAPPING observability span and normalize the output to AI SDK format, matching the regular agent's behavior. - **Client-tool observability**: `onInputStart` and `onInputDelta` callbacks on tool definitions are now invoked during durable agent streaming, and client-tool observability spans are created for tool input streaming. - `DurableAgent` now matches `Agent` for several per-step behaviors that were silently degraded on the durable path: ([#18693](https://github.com/mastra-ai/mastra/pull/18693)) - Tools suspended for human-in-the-loop now receive the same auto-resume system-message rewrite when `autoResumeSuspendedTools` is enabled. - Agents wired to a `BackgroundTaskManager` get the background-task guidance prompt injected before each LLM call. - Model `supportedUrls` (including async resolvers) is honored consistently for both regular and durable runs. - HTTP headers attached to LLM calls (memory routing, model-config, call-time `modelSettings.headers`) merge in a single documented order and are case-normalized so call-time values reliably override. - `prepareStep` and input-processor overrides — including `model`, `tools`, `activeTools`, `providerOptions`, `modelSettings`, `structuredOutput`, and `workspace` — apply identically on both paths. - Durable agents now honor `onIterationComplete` callback return values and delegation bail signals in the loop predicate, closing three behavioral parity gaps with the regular agent: ([#18707](https://github.com/mastra-ai/mastra/pull/18707)) - **Delegation bail** — When an `onDelegationComplete` hook calls `ctx.bail()`, the durable loop now stops at the next predicate evaluation instead of continuing indefinitely. The `delegationBailed` flag propagates through `DurableAgenticExecutionOutput` and `baseIterationStateSchema`. - **`onIterationComplete` callback dispatch** — The durable predicate now calls `onIterationComplete` directly (read from `globalRunRegistry`) and honors its return value: `{ continue: false }` stops the loop, `{ continue: true }` forces continuation when `maxSteps` allows, and `{ feedback }` injects a user message for the next LLM turn. - **Two-phase stop (`pendingFeedbackStop`)** — `onIterationComplete` returning `{ continue: false, feedback: '...' }` now schedules exactly one more LLM turn before stopping, matching the regular agent's behavior. The `pendingFeedbackStop` flag is persisted in `baseIterationStateSchema` across iterations. Signal drain (bugs 5 and 11) is deferred — `DurableAgent` does not yet participate in `agentThreadStreamRuntime` and has no `sendMessage` / signal infrastructure. Scenario tests `delegation-complete-bail` and `stop-condition-long-loop` now run on the durable engine. The `aimock-scenario` harness no longer drops `stopWhen`, `delegation`, or `onIterationComplete` for durable runs. - Background-dispatched sub-agent delegations no longer send null tool-message content ([#17791](https://github.com/mastra-ai/mastra/pull/17791)) When a sub-agent invocation (an `agent-` tool) is dispatched as a background task, the agentic loop hands the sub-agent tool's `toModelOutput` the placeholder string from `tool-call-step.ts` ("Background task started...") instead of the `agentOutputSchema` object. `toModelOutput` read `output.text`, which is undefined for that string, so the supervisor's next request carried a `role: "tool"` message with null content. Providers that validate tool content (e.g. Anthropic) reject that with a 500, breaking the supervisor turn whenever it backgrounds a sub-agent (`backgroundTasks.tools: { someSubAgent: { enabled: true } }`). `toModelOutput` now uses the placeholder string directly when the output is a string, so the tool message always carries non-empty content and the supervisor can acknowledge the dispatch and continue while the sub-agent runs in the background. - Fix evented workflow parallel steps re-running the wrong branch on restart. The parallel processor built each branch's execution path from its position in the filtered (active-only) list instead of its real index, so restarting a parallel step whose active branches were not a contiguous prefix routed to the wrong branch (and skipped the intended one). Branches are now addressed by their real index. Closes #18754. ([#18755](https://github.com/mastra-ai/mastra/pull/18755)) - `createCodingAgent` now only includes the default `TaskSignalProvider` when `memory` is configured. Previously it always wired `TaskSignalProvider`, whose `TaskStateProcessor` requires a memory-backed thread — causing a hard error in memoryless contexts. The provider is merged into caller-provided signals when memory is present, so custom signal providers don't drop task tracking. ([#18728](https://github.com/mastra-ai/mastra/pull/18728)) - Fixed approved and declined tool approvals not round-tripping on recall. ([#18583](https://github.com/mastra-ai/mastra/pull/18583)) After a `requireApproval` tool call was approved or declined, `memory.recall()` lost the decision: a decline was stored as a normal successful result (`state: 'result'` with the rejection string) and an approval dropped the approval entirely. Now: - **Declined** calls persist as `state: 'output-denied'` with `approval: { id, approved: false, reason }`, so recalled AI SDK v6 UI parts render as `output-denied`. In v4 and v5 (which have no denied state) the call downgrades to a single `output-available` (v5) / result (v4) part whose output is the decline reason — so the agent's onFinish memory save no longer throws `ToolInvocation must have a result`. - **Approved** calls keep `approval: { id, approved: true }` alongside the result, so v6 UI parts carry the approval. Approved and declined decisions now round-trip on recall consistently for both standard agents and durable agents (`DurableAgent`, including the Inngest durable agent). Live approve/decline already worked; this was a write-path persistence gap. Fixes #17218. - Fixed reasoning text being lost in AIV4Adapter and stream-chunk assembly at the end of the turn ([#18534](https://github.com/mastra-ai/mastra/pull/18534)) - Tenancy-scope experiments `getById` and `delete*` on `ExperimentsStorage`. ([#18770](https://github.com/mastra-ai/mastra/pull/18770)) `ExperimentsStorage.getExperimentById`, `getExperimentResultById`, `deleteExperiment`, and `deleteExperimentResults` used to key on the primary id alone, so any caller who knew the id could read or delete the row regardless of tenant. All four now accept an optional `filters: { organizationId?, projectId? }` argument that is enforced on every adapter (inmemory, libsql, pg, mysql, mongodb, spanner): - On tenancy mismatch, `get*` returns `null` at the storage layer. - On tenancy mismatch, `delete*` is a silent no-op. - The tenancy predicate is folded into the destructive DML itself (scoped `WHERE` on the DELETE, an atomic gate + delete inside a transaction, or a scoped subquery for the results cascade). A concurrent tenant swap of the same id between a pre-check and the DELETE cannot let a scoped delete hit another tenant's row. Both behaviors match how a missing id already responds, so existence does not leak through error timing or messages. The same atomic-DML pattern is also applied to `DatasetsStorage.deleteDataset` across all 5 store adapters, closing a TOCTOU window between the pre-check and the parent DELETE that was introduced when tenancy filters were originally added. `Dataset.getExperiment` and the shared experiment-ownership gate on `Dataset` now forward the dataset's tenancy scope to storage, so experiment reads and downstream mutations (list results, update result, delete experiment) reached through a dataset handle are automatically scoped to the owning tenant. Legacy calls that omit `filters` are unchanged, so this is fully backwards-compatible. ```ts // Before: any caller who knew the id could read/delete across tenants. await store.experiments.getExperimentById({ id: experimentId }); await store.experiments.deleteExperiment({ id: experimentId }); // After: pass the caller's scope; wrong tenant gets null / silent no-op. await store.experiments.getExperimentById({ id: experimentId, filters: { organizationId, projectId }, }); await store.experiments.deleteExperiment({ id: experimentId, filters: { organizationId, projectId }, }); ``` - Fixed a TypeScript error where auth provider instances (for example `new MastraAuthWorkos()`) could not be assigned to `server.auth` or `studio.auth`, failing with `Property '#private' is missing` (#18682). ([#18796](https://github.com/mastra-ai/mastra/pull/18796)) Auth providers are now typed with a new structural `IMastraAuthProvider` interface (exported from `@mastra/core/server` and `@mastra/auth`), so provider packages no longer need a shared class identity with `@mastra/core`. `CompositeAuth` also accepts any `IMastraAuthProvider` implementation. No code changes are required: ```typescript import { Mastra } from '@mastra/core'; import { MastraAuthWorkos } from '@mastra/auth-workos'; // Previously failed to compile with TS2322, now works without casts export const mastra = new Mastra({ server: { auth: new MastraAuthWorkos(), }, }); ``` - Updated the ws dependency to ^8.21.0 to pull in fixes for an uninitialized memory disclosure (GHSA-58qx-3vcg-4xpx) and a memory exhaustion denial-of-service (GHSA-96hv-2xvq-fx4p) in the WebSocket server. ([#18789](https://github.com/mastra-ai/mastra/pull/18789)) - Fixed an internal Mastra instance leaking a scorer hook onto a shared, process-wide emitter. Agents that use a Workspace (or any unwired agent run) built a throwaway internal Mastra whose scorer hook was never removed, so it kept firing on every scorer run and flooded logs with "scorer not found" errors. The scorer still ran correctly on the real Mastra — the noise came from the orphaned handler, which is now released on teardown. ([#18763](https://github.com/mastra-ai/mastra/pull/18763)) - Scoped `getDatasetById` and `deleteDataset` to tenancy filters when the caller passes `organizationId` / `projectId`. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) The adapters now push the tenancy predicate into the SQL/query when the new optional `filters` argument is present. Legacy calls that omit tenancy are unchanged. On mismatch, `getDatasetById` returns `null` and `deleteDataset` is a silent no-op — the cascade delete (dataset items and versions) is gated by a scoped parent pre-check, so cross-tenant data is never touched. - Added optional `organizationId` and `projectId` query parameters to the dataset routes. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) `GET /datasets/:datasetId`, `PATCH /datasets/:datasetId`, and `DELETE /datasets/:datasetId` now accept optional tenancy query parameters. When provided, they are forwarded to `mastra.datasets.get` / `.delete` and the operation returns 404 if the dataset does not belong to the requested tenant. Requests that omit the query parameters keep their existing behavior. **Example** ``` GET /datasets/abc123?organizationId=org_a&projectId=proj_1 DELETE /datasets/abc123?organizationId=org_a ``` - Fixed durable agents to no longer persist `modelSettings.headers` to durable storage. Headers (which may contain sensitive API keys or auth tokens) are now stripped during serialization and kept in-process on the `RunRegistryEntry`, then merged back at LLM execution time. ([#18751](https://github.com/mastra-ai/mastra/pull/18751)) Also fixed missing model-config-level headers in the durable header merge pipeline. - Fixed subagent delegations wasting an LLM call on title generation. When a supervisor agent's Memory has generateTitle enabled and delegates to a subagent with no memory of its own, the subagent inherited the supervisor's Memory instance and its generateTitle setting, firing an extra title-generation call for every ephemeral delegation thread that no one ever sees. Title generation is now treated as a top-level thread concern and is suppressed for these ephemeral subagent threads. To generate titles for a subagent's own threads, give that subagent its own memory configuration. ([#18761](https://github.com/mastra-ai/mastra/pull/18761)) - Updated dependencies [[`1042cb4`](https://github.com/mastra-ai/mastra/commit/1042cb4da227c0a1315a6362262be3058866c5f8)]: - @mastra/schema-compat@1.3.3 ## 1.49.0-alpha.5 ### Minor Changes - Added maxDurationMs, maxWidth, and maxHeight options to BrowserRecordingOptions. These can now be set on the recording config object to provide defaults for every recording, instead of relying on agent instructions to pass them to the tool at start time. ([#18814](https://github.com/mastra-ai/mastra/pull/18814)) ```ts const browser = new AgentBrowser({ recording: { outputDir: './recordings', maxDurationMs: 60_000, maxWidth: 1280, maxHeight: 720, }, }); ``` Per-recording overrides via the browser_record tool still take precedence. - Fixed a cross-tenant data-access issue on datasets by scoping `DatasetsManager.get` and `DatasetsManager.delete` to tenancy filters. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) Previously `get({ id })` and `delete({ id })` looked up a dataset by its primary key alone. Any caller who knew a dataset id could read or delete it regardless of which `organizationId` / `projectId` it belonged to. This is now closed at the storage layer via a scoped SQL predicate (option (a) — no fetch-then-assert). **What changed** - `DatasetsManager.get` and `DatasetsManager.delete` accept optional `organizationId` and `projectId`. - The tenancy is stashed on the returned `Dataset` handle and forwarded to every downstream storage call (`getDetails`, `update`, `addItem`, item batch ops, `startExperimentAsync`). - The abstract storage contract (`getDatasetById`, `deleteDataset`) gained an optional `filters?: DatasetTenancyFilters` arg. - Item-mutation inputs (`AddDatasetItemInput`, `UpdateDatasetItemInput`, `BatchInsertItemsInput`, `BatchDeleteItemsInput`) and `UpdateDatasetInput` accept optional `filters` for the internal existence check. **Behavior** - Omitting tenancy preserves the existing behavior (no predicate added) — fully backwards compatible. - On tenancy mismatch, `get` throws NOT_FOUND (returns null at the storage layer) and `delete` is a silent no-op — matching how a missing id already behaves, so existence does not leak through error timing or messages. **Example** ```ts // Before const ds = await mastra.datasets.get({ id }); await mastra.datasets.delete({ id }); // After — scope to a tenant const ds = await mastra.datasets.get({ id, organizationId, projectId }); await mastra.datasets.delete({ id, organizationId, projectId }); ``` - Added `scoreTrace()` and `scoreTraceBatch()` to `@mastra/core/evals/scoreTraces` for scoring stored traces without re-running the agent. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) - `scoreTrace()` can score either a stored trace reference or a preloaded `TraceRecord`, and it returns the persisted `ScoreRowData` after the write. - `scoreTraceBatch()` runs one scorer instance across multiple stored traces with bounded concurrency and returns per-target success and failure results. **Why** This gives baseline-style callers a small public API for persisted trace scoring when they already have a scorer instance, without widening the existing workflow-based `scoreTraces()` API. **Before** ```ts await scoreTraces({ mastra, scorerId: 'helpfulness', targets: [{ traceId, spanId }], }); ``` **After** ```ts import { scoreTrace, scoreTraceBatch } from '@mastra/core/evals/scoreTraces'; const savedScore = await scoreTrace({ storage, scorer, target: { trace: preloadedTrace, spanId }, batchId, datasetId, datasetItemId, }); const result = await scoreTraceBatch({ storage, scorer, targets, batchId, datasetId, }); ``` - Add optional `batchId`, `datasetId`, and `datasetItemId` fields to persisted scores so saved baseline scores can be grouped as one scoring pass and joined back to the dataset items they came from. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) - `scoreTrace()` accepts top-level `batchId`, `datasetId`, and `datasetItemId` when persisting a score for a stored trace. - `ScoreRowData` and score save payloads now include nullable `batchId`, `datasetId`, and `datasetItemId`. - Built-in stores with explicit score schema or attribute mappings now persist these provenance fields on saved scores. - D1, DSQL, MSSQL, and Upstash score stores now apply additive provenance migrations or deterministic score ordering for persisted score reads. ```ts await scoreTrace({ storage, scorer, target: { traceId }, batchId: 'baseline-batch-1', datasetId, datasetItemId, }); ``` - Added multi-tenant scoping to stored scorer definitions. Stored scorers now persist optional `organizationId` and `projectId` on the definition record, and `list`/`listResolved` accept matching filters to scope results by tenant. The Postgres adapter backfills the new columns and applies the scoped filters; tenancy lives on the record while version snapshots stay pure config. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) ```ts await storage.create({ scorerDefinition: { id, organizationId: 'org-a', projectId: 'proj-1', ...config }, }); const { scorerDefinitions } = await storage.list({ status: 'draft', organizationId: 'org-a', projectId: 'proj-1', }); ``` - Added optional `organizationId` and `projectId` fields to scores for multi-tenant isolation. Scores can now be saved with tenancy metadata and the `listScoresBy*` methods accept a `filters` option to scope results by organization and project. ([#18331](https://github.com/mastra-ai/mastra/pull/18331)) ```ts await storage.saveScore({ ...score, organizationId: 'org-a', projectId: 'proj-1' }); const result = await storage.listScoresByScorerId({ scorerId, filters: { organizationId: 'org-a', projectId: 'proj-1' }, }); ``` `projectId` identifies the project scope, separate from `resourceId` which continues to mean the agent memory resource. ### Patch Changes - Surface persistence failures in experiment runs. Previously, when `addExperimentResult` threw during `runExperiment`, the failure was silently logged with `console.warn` and the run continued. The item was still counted as succeeded or failed based on the agent run outcome, so `ExperimentSummary.succeededCount` could report more rows than actually existed in `mastra_experiment_results` — silent data loss with no signal to the caller. ([#18716](https://github.com/mastra-ai/mastra/pull/18716)) Now each item result carries an optional `persistenceError: { message } | null` field, and the summary exposes an optional `persistenceFailures: number` counter. The raw error (including stack) is logged internally via the Mastra logger and intentionally omitted from the returned object so the summary can safely cross trust boundaries (e.g. UIs, API responses) without leaking internal paths. Target-run counters (`succeededCount` / `failedCount`) still reflect what the target did, and callers can inspect `persistenceFailures` to detect when the DB is out of sync with the returned summary and decide whether to retry or alert. The persistence failure is also logged via the Mastra logger at error level instead of `console.warn`. Both fields are optional on the types so external mocks / wrappers don't need to hand-construct them; the runner always populates them (`null` / `0` on the happy path). ```ts const summary = await runExperiment(mastra, { datasetId, targetType: 'agent', targetId: 'my-agent' }); if ((summary.persistenceFailures ?? 0) > 0) { const dropped = summary.results.filter(r => r.persistenceError != null); for (const item of dropped) { console.error(`item ${item.itemId} did not persist:`, item.persistenceError?.message); } } ``` - Added optional tenancy arguments to `getDataset`, `updateDataset`, and `deleteDataset`. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) You can now pass `organizationId` and `projectId` to scope dataset reads, updates, and deletes to a specific tenant. Reads and updates against a dataset in a different tenant throw `DATASET_NOT_FOUND` (surfaced as a 404 over HTTP). Deletes silently no-op on a tenancy mismatch — matching the existing "delete non-existent id is a no-op" semantics so cross-tenant existence is never leaked via error timing or status. **Example** ```ts // Before await client.getDataset('abc123'); await client.deleteDataset('abc123'); await client.updateDataset({ id: 'abc123', name: 'renamed' }); // After — scope to a tenant await client.getDataset('abc123', { organizationId: 'org_a', projectId: 'proj_1' }); await client.deleteDataset('abc123', { organizationId: 'org_a' }); await client.updateDataset({ id: 'abc123', name: 'renamed', organizationId: 'org_a' }); ``` - Pushed remaining dataset read filters and pagination down to storage. ([#18710](https://github.com/mastra-ai/mastra/pull/18710)) `DatasetsManager.list({ filters })` now accepts `targetType`, `targetIds` (overlap/union semantics), and `name` (substring, case-insensitive) in addition to the existing tenancy and candidate filters. Filtering is pushed down to the storage layer so callers no longer have to post-filter results. Storage adapters must also be upgraded to the versions listed below to honor the new filters. If a caller is on this version of `@mastra/core` but on an older storage adapter, the new `targetType`/`targetIds`/`name` filter keys are silently ignored by the adapter — no runtime error, but the filter has no effect and every dataset in the tenancy is returned. `Dataset.listItems({ version, search, page, perPage })` now applies `search` and pagination at the storage layer when `version` is provided alongside any of those. Previously they were silently dropped whenever `version` was set. The return shape is unchanged: passing only `version` still returns a bare `DatasetItem[]` snapshot; passing `search`, `page`, or `perPage` (with or without `version`) returns the paginated `{ items, pagination }` shape. The bare-array branch is marked `@deprecated`; prefer passing `page` / `perPage` to always receive the paginated shape. - Tenancy-scope experiments `getById` and `delete*` on `ExperimentsStorage`. ([#18770](https://github.com/mastra-ai/mastra/pull/18770)) `ExperimentsStorage.getExperimentById`, `getExperimentResultById`, `deleteExperiment`, and `deleteExperimentResults` used to key on the primary id alone, so any caller who knew the id could read or delete the row regardless of tenant. All four now accept an optional `filters: { organizationId?, projectId? }` argument that is enforced on every adapter (inmemory, libsql, pg, mysql, mongodb, spanner): - On tenancy mismatch, `get*` returns `null` at the storage layer. - On tenancy mismatch, `delete*` is a silent no-op. - The tenancy predicate is folded into the destructive DML itself (scoped `WHERE` on the DELETE, an atomic gate + delete inside a transaction, or a scoped subquery for the results cascade). A concurrent tenant swap of the same id between a pre-check and the DELETE cannot let a scoped delete hit another tenant's row. Both behaviors match how a missing id already responds, so existence does not leak through error timing or messages. The same atomic-DML pattern is also applied to `DatasetsStorage.deleteDataset` across all 5 store adapters, closing a TOCTOU window between the pre-check and the parent DELETE that was introduced when tenancy filters were originally added. `Dataset.getExperiment` and the shared experiment-ownership gate on `Dataset` now forward the dataset's tenancy scope to storage, so experiment reads and downstream mutations (list results, update result, delete experiment) reached through a dataset handle are automatically scoped to the owning tenant. Legacy calls that omit `filters` are unchanged, so this is fully backwards-compatible. ```ts // Before: any caller who knew the id could read/delete across tenants. await store.experiments.getExperimentById({ id: experimentId }); await store.experiments.deleteExperiment({ id: experimentId }); // After: pass the caller's scope; wrong tenant gets null / silent no-op. await store.experiments.getExperimentById({ id: experimentId, filters: { organizationId, projectId }, }); await store.experiments.deleteExperiment({ id: experimentId, filters: { organizationId, projectId }, }); ``` - Scoped `getDatasetById` and `deleteDataset` to tenancy filters when the caller passes `organizationId` / `projectId`. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) The adapters now push the tenancy predicate into the SQL/query when the new optional `filters` argument is present. Legacy calls that omit tenancy are unchanged. On mismatch, `getDatasetById` returns `null` and `deleteDataset` is a silent no-op — the cascade delete (dataset items and versions) is gated by a scoped parent pre-check, so cross-tenant data is never touched. - Added optional `organizationId` and `projectId` query parameters to the dataset routes. ([#18750](https://github.com/mastra-ai/mastra/pull/18750)) `GET /datasets/:datasetId`, `PATCH /datasets/:datasetId`, and `DELETE /datasets/:datasetId` now accept optional tenancy query parameters. When provided, they are forwarded to `mastra.datasets.get` / `.delete` and the operation returns 404 if the dataset does not belong to the requested tenant. Requests that omit the query parameters keep their existing behavior. **Example** ``` GET /datasets/abc123?organizationId=org_a&projectId=proj_1 DELETE /datasets/abc123?organizationId=org_a ``` ## 1.49.0-alpha.4 ### Patch Changes - Added optional filter arguments to `Dataset.listExperiments()` and `Dataset.listExperimentResults()`. The storage layer already accepted these filters — they are now reachable from the `Dataset` handle. All new parameters are optional and existing callers are unaffected. ([#18769](https://github.com/mastra-ai/mastra/pull/18769)) **Before:** ```typescript const { experiments } = await dataset.listExperiments({ page: 0, perPage: 10 }); const baselineOnly = experiments.filter(e => e.agentVersion === 'v1'); ``` **After:** ```typescript const { experiments } = await dataset.listExperiments({ targetType: 'agent', targetId: 'my-agent', agentVersion: 'v1', status: 'completed', page: 0, perPage: 10, }); ``` `listExperiments` accepts: `targetType`, `targetId`, `agentVersion`, `status`, tenancy `filters`. `listExperimentResults` accepts: `traceId`, `status`, tenancy `filters`. Enables baseline vs variant read patterns without client-side filtering or bypassing `Dataset`. ## 1.49.0-alpha.3 ### Minor Changes - Added opt-in storage retention. Declare per-table `maxAge` policies in the `retention` config, then call `storage.prune()` to delete rows older than their age. Anything you don't configure is kept forever, so there is no change until you opt in. ([#18733](https://github.com/mastra-ai/mastra/pull/18733)) Retention covers growth tables across ten domains — `memory` (threads, messages, resources), `threadState`, `observability` (spans), `scores`, `workflows` (run snapshots), `backgroundTasks`, `experiments`, `notifications`, `harness` (sessions), and `schedules` (fire history). Anchors are chosen so `maxAge` is honest: creation time for append-only logs, last activity for workflow snapshots and thread state, and completion time for background tasks and experiments (in-flight work is never pruned). User-authored artifacts and config (agents, skills, workspaces, datasets, schedule definitions, and so on) are not prunable. `prune()` is safe on large tables: it deletes in bounded, batched, resumable, cancellable chunks and never locks the database for long. Call it from your own scheduler; when a result reports `done: false`, eligible rows remain and the next run continues. `prune()` only deletes rows — reclaiming disk to the OS is left to the underlying database and the operator. ```typescript const storage = new MastraCompositeStore({ id: 'composite', retention: { memory: { messages: { maxAge: '30d' }, threads: { maxAge: '90d' } }, observability: { spans: { maxAge: '7d' } }, }, domains: {/* ... */}, }); // Wire this to your own cron — Mastra never runs it for you. const results = await storage.prune(); ``` - File-based agents can now nest subagents up to three levels deep. A subagent directory can declare its own `subagents/`, and each level is assembled and wired into its parent as a delegation tool. Levels deeper than the cap are ignored with a warning. ([#18780](https://github.com/mastra-ai/mastra/pull/18780)) ```text src/mastra/agents/ supervisor/ # depth 0 subagents/ researcher/ # depth 1 subagents/ summarizer/ # depth 2 ``` ### Patch Changes - Fixed signal drain parity for durable agents. Signals sent to a running durable agent (via sendSignal or sendMessage) are now consumed and echoed to the client stream, matching the behavior of regular agents. This includes initial signal echoes on the first model request, pre-run signal drain before the first LLM call, within-iteration signal drain between tool execution and task completion, and inter-iteration signal drain in the loop predicate that forces continuation so the LLM sees newly arrived signals. ([#18732](https://github.com/mastra-ai/mastra/pull/18732)) - Fixed durable agent parity gaps: emit `start` chunk for correct stream ordering, handle TripWire from input processors during preparation, and port `onInputAvailable`/`onOutput` tool lifecycle hooks to the durable tool execution path. Removed stale test harness guards that were preventing `isTaskComplete`, `actor`, `savePerStep`, and `providerOptions` from reaching durable agent runs. These fixes enable 20+ scenario tests to run on the durable engine. ([#18806](https://github.com/mastra-ai/mastra/pull/18806)) - Fixed RequestContext leaking auth tokens onto scorer_run span input. Added serializeForSpan() to RequestContext so deepClean uses a safe snapshot instead of walking the internal registry Map. Also fixed the MastraScorer.run() call site to pass the serialized snapshot into the span input. ([#18776](https://github.com/mastra-ai/mastra/pull/18776)) - Fixed SystemPromptScrubber `processOutputStream` swallowing TripWire errors when strategy is `block`. The abort call now correctly propagates the TripWire to halt the agent stream, matching the existing behavior in `processOutputResult`. ([#18794](https://github.com/mastra-ai/mastra/pull/18794)) - Fix evented workflow parallel steps re-running the wrong branch on restart. The parallel processor built each branch's execution path from its position in the filtered (active-only) list instead of its real index, so restarting a parallel step whose active branches were not a contiguous prefix routed to the wrong branch (and skipped the intended one). Branches are now addressed by their real index. Closes #18754. ([#18755](https://github.com/mastra-ai/mastra/pull/18755)) - Fixed approved and declined tool approvals not round-tripping on recall. ([#18583](https://github.com/mastra-ai/mastra/pull/18583)) After a `requireApproval` tool call was approved or declined, `memory.recall()` lost the decision: a decline was stored as a normal successful result (`state: 'result'` with the rejection string) and an approval dropped the approval entirely. Now: - **Declined** calls persist as `state: 'output-denied'` with `approval: { id, approved: false, reason }`, so recalled AI SDK v6 UI parts render as `output-denied`. In v4 and v5 (which have no denied state) the call downgrades to a single `output-available` (v5) / result (v4) part whose output is the decline reason — so the agent's onFinish memory save no longer throws `ToolInvocation must have a result`. - **Approved** calls keep `approval: { id, approved: true }` alongside the result, so v6 UI parts carry the approval. Approved and declined decisions now round-trip on recall consistently for both standard agents and durable agents (`DurableAgent`, including the Inngest durable agent). Live approve/decline already worked; this was a write-path persistence gap. Fixes #17218. - Fixed a TypeScript error where auth provider instances (for example `new MastraAuthWorkos()`) could not be assigned to `server.auth` or `studio.auth`, failing with `Property '#private' is missing` (#18682). ([#18796](https://github.com/mastra-ai/mastra/pull/18796)) Auth providers are now typed with a new structural `IMastraAuthProvider` interface (exported from `@mastra/core/server` and `@mastra/auth`), so provider packages no longer need a shared class identity with `@mastra/core`. `CompositeAuth` also accepts any `IMastraAuthProvider` implementation. No code changes are required: ```typescript import { Mastra } from '@mastra/core'; import { MastraAuthWorkos } from '@mastra/auth-workos'; // Previously failed to compile with TS2322, now works without casts export const mastra = new Mastra({ server: { auth: new MastraAuthWorkos(), }, }); ``` - Updated the ws dependency to ^8.21.0 to pull in fixes for an uninitialized memory disclosure (GHSA-58qx-3vcg-4xpx) and a memory exhaustion denial-of-service (GHSA-96hv-2xvq-fx4p) in the WebSocket server. ([#18789](https://github.com/mastra-ai/mastra/pull/18789)) - Fixed an internal Mastra instance leaking a scorer hook onto a shared, process-wide emitter. Agents that use a Workspace (or any unwired agent run) built a throwaway internal Mastra whose scorer hook was never removed, so it kept firing on every scorer run and flooded logs with "scorer not found" errors. The scorer still ran correctly on the real Mastra — the noise came from the orphaned handler, which is now released on teardown. ([#18763](https://github.com/mastra-ai/mastra/pull/18763)) - Fixed durable agents to no longer persist `modelSettings.headers` to durable storage. Headers (which may contain sensitive API keys or auth tokens) are now stripped during serialization and kept in-process on the `RunRegistryEntry`, then merged back at LLM execution time. ([#18751](https://github.com/mastra-ai/mastra/pull/18751)) Also fixed missing model-config-level headers in the durable header merge pipeline. ## 1.49.0-alpha.2 ### Patch Changes - Fixed subagent delegations wasting an LLM call on title generation. When a supervisor agent's Memory has generateTitle enabled and delegates to a subagent with no memory of its own, the subagent inherited the supervisor's Memory instance and its generateTitle setting, firing an extra title-generation call for every ephemeral delegation thread that no one ever sees. Title generation is now treated as a top-level thread concern and is suppressed for these ephemeral subagent threads. To generate titles for a subagent's own threads, give that subagent its own memory configuration. ([#18761](https://github.com/mastra-ai/mastra/pull/18761)) ## 1.49.0-alpha.1 ### Minor Changes - Agents using models that dropped support for `temperature`, `topP`, or `topK` (such as `claude-opus-4-7` or `gpt-5-pro`) no longer crash with a 400 error. The model router now automatically strips unsupported sampling parameters before the request is sent — no configuration or processors needed. ([#18622](https://github.com/mastra-ai/mastra/pull/18622)) ```ts const agent = new Agent({ model: 'anthropic/claude-opus-4-7', instructions: 'You are a helpful assistant.', }); // temperature is stripped automatically — no 400 error await agent.generate('hello', { modelSettings: { temperature: 0.7 } }); ``` ### Patch Changes - Fixed replay ordering on pull-mode transports (e.g. Redis Streams): history events are now delivered before live events, offsets are enforced on the live path, suppressed duplicates are acknowledged so persistent transports stop redelivering them, and ack/nack handles are preserved for buffered events. ([#18479](https://github.com/mastra-ai/mastra/pull/18479)) - Fixed five DurableAgent behavioral parity gaps with the regular Agent loop: ([#18712](https://github.com/mastra-ai/mastra/pull/18712)) - **Goal step**: Durable agents now honor goal-aware stop semantics. The `goalStep` has been ported into the durable workflow, reading goal config, running completion scorers, and emitting goal chunks — matching the regular agent's behavior. - **Output processors for tool chunks**: Tool-result and tool-error chunks on durable agents now pass through output processors before emission, enabling content moderation and redaction workflows. - **Cached response replay**: Input processors that return a cached response via `processLLMRequest` now work on durable agents, short-circuiting the model call and replaying cached chunks. - **toModelOutput normalization**: Durable agents now call `toModelOutput` on successful tool results under a MAPPING observability span and normalize the output to AI SDK format, matching the regular agent's behavior. - **Client-tool observability**: `onInputStart` and `onInputDelta` callbacks on tool definitions are now invoked during durable agent streaming, and client-tool observability spans are created for tool input streaming. - `DurableAgent` now matches `Agent` for several per-step behaviors that were silently degraded on the durable path: ([#18693](https://github.com/mastra-ai/mastra/pull/18693)) - Tools suspended for human-in-the-loop now receive the same auto-resume system-message rewrite when `autoResumeSuspendedTools` is enabled. - Agents wired to a `BackgroundTaskManager` get the background-task guidance prompt injected before each LLM call. - Model `supportedUrls` (including async resolvers) is honored consistently for both regular and durable runs. - HTTP headers attached to LLM calls (memory routing, model-config, call-time `modelSettings.headers`) merge in a single documented order and are case-normalized so call-time values reliably override. - `prepareStep` and input-processor overrides — including `model`, `tools`, `activeTools`, `providerOptions`, `modelSettings`, `structuredOutput`, and `workspace` — apply identically on both paths. - Durable agents now honor `onIterationComplete` callback return values and delegation bail signals in the loop predicate, closing three behavioral parity gaps with the regular agent: ([#18707](https://github.com/mastra-ai/mastra/pull/18707)) - **Delegation bail** — When an `onDelegationComplete` hook calls `ctx.bail()`, the durable loop now stops at the next predicate evaluation instead of continuing indefinitely. The `delegationBailed` flag propagates through `DurableAgenticExecutionOutput` and `baseIterationStateSchema`. - **`onIterationComplete` callback dispatch** — The durable predicate now calls `onIterationComplete` directly (read from `globalRunRegistry`) and honors its return value: `{ continue: false }` stops the loop, `{ continue: true }` forces continuation when `maxSteps` allows, and `{ feedback }` injects a user message for the next LLM turn. - **Two-phase stop (`pendingFeedbackStop`)** — `onIterationComplete` returning `{ continue: false, feedback: '...' }` now schedules exactly one more LLM turn before stopping, matching the regular agent's behavior. The `pendingFeedbackStop` flag is persisted in `baseIterationStateSchema` across iterations. Signal drain (bugs 5 and 11) is deferred — `DurableAgent` does not yet participate in `agentThreadStreamRuntime` and has no `sendMessage` / signal infrastructure. Scenario tests `delegation-complete-bail` and `stop-condition-long-loop` now run on the durable engine. The `aimock-scenario` harness no longer drops `stopWhen`, `delegation`, or `onIterationComplete` for durable runs. - Background-dispatched sub-agent delegations no longer send null tool-message content ([#17791](https://github.com/mastra-ai/mastra/pull/17791)) When a sub-agent invocation (an `agent-` tool) is dispatched as a background task, the agentic loop hands the sub-agent tool's `toModelOutput` the placeholder string from `tool-call-step.ts` ("Background task started...") instead of the `agentOutputSchema` object. `toModelOutput` read `output.text`, which is undefined for that string, so the supervisor's next request carried a `role: "tool"` message with null content. Providers that validate tool content (e.g. Anthropic) reject that with a 500, breaking the supervisor turn whenever it backgrounds a sub-agent (`backgroundTasks.tools: { someSubAgent: { enabled: true } }`). `toModelOutput` now uses the placeholder string directly when the output is a string, so the tool message always carries non-empty content and the supervisor can acknowledge the dispatch and continue while the sub-agent runs in the background. - `createCodingAgent` now only includes the default `TaskSignalProvider` when `memory` is configured. Previously it always wired `TaskSignalProvider`, whose `TaskStateProcessor` requires a memory-backed thread — causing a hard error in memoryless contexts. The provider is merged into caller-provided signals when memory is present, so custom signal providers don't drop task tracking. ([#18728](https://github.com/mastra-ai/mastra/pull/18728)) - Fixed reasoning text being lost in AIV4Adapter and stream-chunk assembly at the end of the turn ([#18534](https://github.com/mastra-ai/mastra/pull/18534)) - Updated dependencies [[`1042cb4`](https://github.com/mastra-ai/mastra/commit/1042cb4da227c0a1315a6362262be3058866c5f8)]: - @mastra/schema-compat@1.3.3-alpha.0 ## 1.48.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`0f69865`](https://github.com/mastra-ai/mastra/commit/0f69865aced225d98eac812e22699dc445ee18cb)) ## 1.48.0 ### Minor Changes - Renamed the AgentController interval API. `heartbeatHandlers` is now `intervalHandlers`, the `HeartbeatHandler` type is now `IntervalHandler`, and the `removeHeartbeat()`/`stopHeartbeats()` methods are now `removeInterval()`/`stopIntervals()`. This better reflects that these are fixed-interval background tasks, not liveness pings, and is distinct from the unrelated `mastra.heartbeats` scheduled-agent feature. ([#18665](https://github.com/mastra-ai/mastra/pull/18665)) **Before** ```ts const { controller } = await createMastraCode({ heartbeatHandlers: [{ id: 'sync', intervalMs: 60_000, handler: async () => {} }], }); await controller.removeHeartbeat({ id: 'sync' }); await controller.stopHeartbeats(); ``` **After** ```ts const { controller } = await createMastraCode({ intervalHandlers: [{ id: 'sync', intervalMs: 60_000, handler: async () => {} }], }); await controller.removeInterval({ id: 'sync' }); await controller.stopIntervals(); ``` - Added optional `maxSteps` to `ScorerJudgeConfig` and `GoalConfig`, letting callers control the internal judge agent's agentic-loop iteration limit instead of relying on the implicit default of 5. Also fixed judge agent not receiving Mastra registration, which caused the observer's API key resolution to fail silently. ([#18544](https://github.com/mastra-ai/mastra/pull/18544)) - Added optional `maxSteps` to `ScorerJudgeConfig`, letting callers control the internal judge agent's agentic-loop iteration limit instead of relying on the implicit default of 5. ([#18544](https://github.com/mastra-ai/mastra/pull/18544)) - Added `createCodingAgent` factory and a reusable `buildBasePrompt` so other projects can build a coding agent on top of the same defaults MastraCode uses. ([#18695](https://github.com/mastra-ai/mastra/pull/18695)) The factory wires sensible, portable defaults that you can override per field: - **Workspace** — a local filesystem + sandbox rooted at `process.cwd()` (set `basePath`, pass your own `workspace`, or pass `workspace: undefined` to opt out entirely). - **Task signals** — `TaskSignalProvider` so a task list persists across turns. - **Error handling** — retries on `ECONNRESET` and bad-request errors, plus prefill and provider-history compatibility processors. - **Goal judging** — the default goal judge prompt. `buildBasePrompt` is parameterized with `productName`, `coAuthorName` (both default to "Mastra Code"), and `coAuthorEmail` (defaults to "noreply@mastra.ai"), so you can brand the system prompt and commit trailer without forking it. ```ts import { createCodingAgent } from '@mastra/core/coding-agent'; const agent = createCodingAgent({ id: 'my-coding-agent', name: 'My Coding Agent', model: 'openai/gpt-5', instructions: 'You help with my project.', tools: {}, basePath: '/path/to/repo', }); ``` - **Added** heartbeats: schedule an agent to run on a recurring cron, either inside an existing conversation thread or on its own. ([#18184](https://github.com/mastra-ai/mastra/pull/18184)) A heartbeat fires a prompt to an agent on a schedule. When it has a thread, the run is delivered into that thread as a normal agent signal, so anything watching the thread sees it like any other message; without a thread, the agent just runs in isolation. Each heartbeat has its own id and an optional `name`, so one agent or thread can have several heartbeats with different schedules and prompts. The id is generated for you, or you can pass your own `id` to `create` for a stable handle (it's normalized to `hb_`). Heartbeats are persisted, so they keep firing across process restarts with no extra setup. ```ts const hb = await mastra.heartbeats.create({ agentId: 'chef', name: 'morning-checkin', threadId, resourceId, cron: '*/5 * * * *', prompt: 'Check in on the user', ifActive: { behavior: 'discard' }, // skip if the user is mid-conversation ifIdle: { behavior: 'wake' }, // wake the agent if the thread is idle }); // Threadless: run the agent on a cron with no conversation. await mastra.heartbeats.create({ agentId: 'chef', cron: '0 * * * *', prompt: 'Run the hourly summary', }); await mastra.heartbeats.list({ agentId: 'chef' }); await mastra.heartbeats.get(hb.id); await mastra.heartbeats.update(hb.id, { prompt: 'check in gently' }); await mastra.heartbeats.pause(hb.id); await mastra.heartbeats.resume(hb.id); await mastra.heartbeats.run(hb.id); // fire once now await mastra.heartbeats.delete(hb.id); ``` The same CRUD is available over HTTP through `@mastra/server` (under `/api/heartbeats`) and as top-level methods on the `@mastra/client-js` client (`client.createHeartbeat`, `client.getHeartbeat`, `client.listHeartbeats`, etc.). **Lifecycle hooks** React to heartbeat runs via `heartbeat` on the `Mastra` constructor. It's a single hook bundle that runs for every agent's heartbeats; each hook receives the firing `agentId` so you can branch on it. `prepare` resolves fire-time parameters (for example, creating a fresh thread per fire), and `onFinish` / `onError` / `onAbort` mirror `agent.stream`. ```ts new Mastra({ // ... heartbeat: { // Return overrides, `null` to skip this fire, or `undefined` to use defaults. prepare: async ({ agentId, heartbeat }) => { if (agentId === 'chef' && heartbeat.name === 'daily-digest') { return { threadId: await createDailyThread(), resourceId: 'slack:U095PUH0FKL' }; } }, onFinish: ({ agentId, outcome, result, heartbeat }) => { metrics.record({ agentId, heartbeat: heartbeat.name, outcome }); }, onError: ({ agentId, error, phase, heartbeat }) => { alerts.send(`heartbeat ${agentId}/${heartbeat.name} failed in ${phase}: ${error.message}`); }, }, }); ``` **Signal shaping** A heartbeat fire surfaces to the agent as a signal. By default it uses the `notification` type and renders as `…`; override `signalType` and `tagName` to change either. `ifActive` and `ifIdle` mirror the `agent.sendSignal` options shape (`{ behavior, attributes }`, plus `streamOptions` on `ifIdle`) and stay JSON-serializable so they persist with the schedule. `ifIdle.streamOptions` currently accepts `requestContext`, which is rehydrated onto the woken run. Top-level `attributes` are rendered on the signal tag, and top-level `providerOptions` are merged into the signal payload on every fire. ```ts await mastra.heartbeats.create({ agentId: 'chef', threadId, resourceId, cron: '*/5 * * * *', prompt: 'Check in on the user', tagName: 'check-in', // renders as … attributes: { source: 'cron' }, providerOptions: { openai: { store: false } }, ifIdle: { behavior: 'wake', streamOptions: { requestContext: { locale: 'en-US' } }, }, }); ``` - add OM-managed working memory ([#18654](https://github.com/mastra-ai/mastra/pull/18654)) Adds `observationalMemory.observation.manageWorkingMemory` so the Observer can update working memory automatically instead of requiring the main agent to call the working memory tool. ```ts new Memory({ options: { workingMemory: { enabled: true }, observationalMemory: { enabled: true, observation: { manageWorkingMemory: true }, }, }, }); ``` This option adds `WorkingMemoryExtractor`, defaults `workingMemory.agentManaged` to `false`, and defaults `workingMemory.useStateSignals` to `true` when working memory is enabled. Set `workingMemory.agentManaged: true` to keep the main agent's working memory tool and instructions enabled. - Added file-based agents: define an agent by file convention under `src/mastra/agents//` alongside agents created with `new Agent()`. ([#18609](https://github.com/mastra-ai/mastra/pull/18609)) A directory becomes an agent when it has a `config.ts` or `instructions.md`. The directory name is the agent name. `instructions.md` supplies the instructions, `tools/*.ts` supply tools, `skills/` supplies skills (a `createSkill()` module, a packaged `SKILL.md` directory, or a flat `.md`), and a `memory.ts` default export supplies the agent's `memory` (`config.memory` wins if both are set). Each file-based agent also gets a workspace by default (contained filesystem + shell sandbox rooted at a per-agent `workspace/` dir); customize it with a `workspace.ts` default export or `config.workspace`. Both styles register into the same Mastra instance and show up together in Studio, the server, and the bundler. **Before** ```ts import { Agent } from '@mastra/core/agent'; export const weather = new Agent({ id: 'weather', name: 'weather', instructions: 'You are a weather assistant.', model: 'openai/gpt-4o', }); ``` **After (file-based, optional)** ```ts // src/mastra/agents/weather/config.ts import { agentConfig } from '@mastra/core/agent'; export default agentConfig({ model: 'openai/gpt-4o', // instructions taken from instructions.md, tools from tools/*.ts }); // src/mastra/agents/weather/memory.ts import { Memory } from '@mastra/memory'; export default new Memory(); // wired in as the agent's memory ``` A file-based agent can also declare **subagents** under `agents//subagents//`, using the same directory layout as an agent (`config.ts`, `instructions.md`, `tools/`, `skills/`, `workspace.ts` / `workspace/`). Each subagent is assembled independently and wired into the parent's `agents` map, so the loop exposes it as a delegation tool named after the directory. A subagent's `config.ts` must set a non-empty `description` (build error otherwise), subagents inherit nothing from the parent, and they are one level deep (a nested `subagents/` directory is ignored with a warning). A subagent id colliding with a parent tool key or another subagent id is a build error; an id also present in `config.agents` keeps the `config.agents` entry with a warning. Code-registered agents win on name collisions, and a `config.ts` that exports `new Agent()` is used as-is (its sibling `instructions.md`, `tools/`, and `subagents/` are ignored with a warning), so existing projects are unaffected. The core API surface is `agentConfig()` plus the `assembleAgentFromFsEntry()` / `Mastra.__registerFsAgents()` helpers that turn a discovered directory into a registered agent. Directory discovery itself is performed by the build pipeline; importing the `mastra` instance directly as a library does not scan `agents//` directories, so register those agents in code if you need them outside the build pipeline. - support inline JSON prompt injection ([#18652](https://github.com/mastra-ai/mastra/pull/18652)) Added `structuredOutput.jsonPromptInjection: 'inline'` to append JSON schema instructions to the latest user message instead of the system prompt. This helps keep the system prompt stable on providers that cache prompt prefixes. ```ts await agent.generate('Summarize this text', { structuredOutput: { schema, jsonPromptInjection: 'inline', }, }); ``` - Added storage-backed discovery of suspended agent runs, so human-in-the-loop approval UIs can recover a pending run after a page refresh or server restart. ([#17898](https://github.com/mastra-ai/mastra/pull/17898)) `agent.listSuspendedRuns()` lists runs waiting on a tool-call approval or on a tool that called `suspend()`. Unlike the in-memory `getActiveThreadRunId()`, it reads from storage, so it works after a restart and across multiple server instances: ```ts const { runs, total } = await agent.listSuspendedRuns({ threadId, resourceId }); if (runs[0]) { // runs[0].toolCalls -> [{ toolCallId, toolName, args, requiresApproval }] await agent.approveToolCall({ runId: runs[0].runId, toolCallId: runs[0].toolCalls[0].toolCallId }); } ``` Supports `threadId`/`resourceId`/date filters and pagination, mirroring `listWorkflowRuns()`. The same surface is exposed over HTTP as `GET /agents/:agentId/suspended-runs` and on the client SDK as `agent.listSuspendedRuns()`; server-enforced request-context values take precedence over client query parameters, so clients cannot list runs outside their scope. `sendToolApproval()` now falls back to this storage-backed discovery when no active run is found in memory for the thread, so approvals keep working after a restart. If several suspended runs match, it throws an error asking for a `toolCallId` to disambiguate. **Why:** approval UIs previously had no public way to recover a suspended run after a refresh or restart, forcing apps to parse internal workflow snapshots. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b9a2961`](https://github.com/mastra-ai/mastra/commit/b9a2961c1be81e3639c0879e58588c26dd0ae866)) - Fixed thread metadata being lost when a processor or working memory writes to it during an agent run. The thread is re-saved when the run finishes, and it was using a stale in-memory snapshot that overwrote any metadata written mid-run via updateThread. The agent now re-reads the latest persisted thread before that save, so mid-run metadata is preserved. Affects all storage backends (Postgres, LibSQL, and others). Fixes #16216. ([#18152](https://github.com/mastra-ai/mastra/pull/18152)) - Fixed find_files and grep tools to always exclude .git directory contents — the .git directory is now skipped at the traversal boundary in both tools since its internals are never useful and waste tokens. Also fixed pipe-separated exclude patterns in find_files (e.g. ".git|node_modules") to work correctly, matching tree's -I flag behavior. ([#18548](https://github.com/mastra-ai/mastra/pull/18548)) - `DurableAgent` now matches `Agent` behavior in three places where the durable loop previously diverged: ([#18677](https://github.com/mastra-ai/mastra/pull/18677)) - `isTaskComplete` scorers receive `requestContext` as `customContext`, so the same scorer code works on both agents. Only JSON-serializable entries from `requestContext` are forwarded; non-serializable values are dropped. Do not store secrets in `RequestContext` if you persist durable agent snapshots. - Provider-defined tools (e.g. OpenAI `web_search`) resolve and execute when invoked by the model, instead of surfacing as `ToolNotFoundError`. - Each iteration of a multi-step durable run produces a distinct assistant `messageId`, matching the non-durable loop and unblocking downstream consumers (signal drains, audit logs, replay) that key off message identity. - add agent reference to processor execution context ([#18651](https://github.com/mastra-ai/mastra/pull/18651)) - Fix in-memory workflow storage `getWorkflowRunById` returning `null` when `workflowName` is omitted. `workflowName` is optional in the storage contract and the pg/libsql adapters match by `runId` alone when it is not provided, but the in-memory store always compared `workflow_name === workflowName`, which never matched for an undefined name. It now matches by `runId`, only filters by `workflowName` when provided, and returns the most recent run for parity with the persistent adapters. Closes #18585. ([#18586](https://github.com/mastra-ai/mastra/pull/18586)) - Fix in-memory observability `listTraces` ignoring the `startExclusive` and `endExclusive` flags on `startedAt`/`endedAt` filters. Exclusive date-range bounds now drop a trace that sits exactly on the boundary, matching the pg/libsql adapters (and the in-memory log/metric filters). Closes #18635. ([#18675](https://github.com/mastra-ai/mastra/pull/18675)) - Fix in-memory scores store `listScoresByScorerId` returning scores in insertion order instead of newest first. The pg and libsql adapters order by `createdAt DESC`, and the sibling `listScoresBySpan` already does, so the in-memory store now sorts the same way before paginating. Closes #18618. ([#18619](https://github.com/mastra-ai/mastra/pull/18619)) - add observational memory extractors ([#18653](https://github.com/mastra-ai/mastra/pull/18653)) Introduces a public Extractor API for Observational Memory with inline XML extraction and structured follow-up modes. Includes built-in extractors for current task, suggested response, and thread title. Persists extracted values into thread OM metadata with key-level merging and carry-forward into future observer/reflector prompts. - Fix a polynomial ReDoS in the model gateway error matcher. The `Missing .+ environment variable` pattern used to classify expected missing-auth errors could backtrack catastrophically on adversarial error messages; it now uses `Missing [^ ]+ environment variable`, which matches the same real messages without the ambiguous overlap. ([#18680](https://github.com/mastra-ai/mastra/pull/18680)) - Bring `InngestAgent` (Inngest-backed durable agent) to parity with `DurableAgent` for per-call execution options, abort handling, idle-aware resume, and `generate()`. ([#18615](https://github.com/mastra-ai/mastra/pull/18615)) `InngestAgent.stream()` and `resume()` now accept the same execution-option surface as `DurableAgent`, including `stopWhen`, `activeTools`, `structuredOutput`, `versions`, `system`, `disableBackgroundTasks`, `tracingOptions`, `actor`, `transform`, `prepareStep`, `isTaskComplete`, `delegation`, function-form `requireToolApproval`, and the lifecycle callbacks `onAbort` / `onIterationComplete`. Closure-shaped options (`prepareStep`, `transform`, function-form `isTaskComplete` / `requireToolApproval`, `stopWhen` callbacks) continue to work in-process; they degrade after a worker hop the same way they do for in-memory `DurableAgent`. ```ts const result = await inngestAgent.stream(messages, { runId: 'run-1', abortSignal: controller.signal, stopWhen: stepCountIs(5), onIterationComplete: ({ iteration }) => console.log('done', iteration), }); // Cancel a live run from the caller result.abort(); // Resume and drive the run to completion in a single call await inngestAgent.resume({ runId: 'run-1', resumeData, untilIdle: true }); // Durable equivalents of Agent.generate / resumeGenerate const out = await inngestAgent.generate(messages, { runId: 'run-2' }); const resumed = await inngestAgent.resumeGenerate({ runId: 'run-2', resumeData }); ``` `@mastra/core` re-exports `globalRunRegistry` and `runResumeDurableStreamUntilIdle` from `@mastra/core/agent/durable` so durable-agent integrations can share the same registry and idle-wrapper plumbing. - fix: prevent partial gateway sync from corrupting provider registry ([#18545](https://github.com/mastra-ai/mastra/pull/18545)) - Amazon Bedrock models now appear under their own `amazon-bedrock/` provider in the model picker instead of the `mastracode/amazon-bedrock/` namespace. Bedrock is resolved through a dedicated Amazon Bedrock gateway that authenticates with the AWS credential chain (SigV4) and surfaces models from the public models.dev catalog. Saved model selections using the previous `mastracode/amazon-bedrock/...` IDs are still resolved at runtime, so existing config keeps working. ([#17937](https://github.com/mastra-ai/mastra/pull/17937)) - Fixed gs:// and s3:// file/image references being downloaded and corrupted into data: URIs during durable agent execution. The durable LLM step now forwards the model's supportedUrls (matching standard execution), so URLs a provider fetches natively (e.g. Vertex gs://) pass through as references instead of failing with "Failed to download asset" or being base64-wrapped. ([#18649](https://github.com/mastra-ai/mastra/pull/18649)) - Fixed background task execution metadata updates so they no longer rewrite the model-visible tool invocation state. ([#18556](https://github.com/mastra-ai/mastra/pull/18556)) - Fixed 'Type instantiation is excessively deep' (TS2589) errors that occurred when defining workflows with Zod schemas. Workflow and step type inference is now significantly faster and no longer causes TypeScript to crash or report depth errors. ([#18608](https://github.com/mastra-ai/mastra/pull/18608)) - Reduce repeated schema work during sub-agent tool conversion for more stable memory usage. ([#18566](https://github.com/mastra-ai/mastra/pull/18566)) - Scripts using Mastra no longer hang after completing their work. The scheduler timer that polls for due schedules previously kept the Node.js event loop alive, preventing process exit even when all work was done. The timer now allows the process to exit naturally. ([#18713](https://github.com/mastra-ai/mastra/pull/18713)) - Fixed buffered observation extraction metadata so stored OM chunks keep extracted values and extraction failures across memory storage adapters. ([#18655](https://github.com/mastra-ai/mastra/pull/18655)) - Fixed custom model gateways being overridden by default gateways. GatewayManager now deduplicates gateways by ID (first-wins) so custom gateways take precedence over defaults. Narrowed the auth-availability check to only swallow expected missing-credential errors instead of all errors, so real gateway failures surface during debugging. ([#18602](https://github.com/mastra-ai/mastra/pull/18602)) - Fixed channel broadcasting so agent runs on a channel-backed thread post back to the channel even when they did not start from an inbound platform message. Previously only runs triggered by an incoming Slack/Discord/etc. message would render to the channel; heartbeat, Studio, and custom UI runs were silently dropped. The channels output processor now reconstructs the channel destination from the thread itself, so any run on a channel-backed thread delivers its output. ([#18630](https://github.com/mastra-ai/mastra/pull/18630)) - Updated dependencies [[`d0702ee`](https://github.com/mastra-ai/mastra/commit/d0702eedc1594cb2d0d83476440cfc2ec8820adb), [`9feeaa0`](https://github.com/mastra-ai/mastra/commit/9feeaa0f9a1af07039e5b4f22b932b0cb18617e8), [`213feb8`](https://github.com/mastra-ai/mastra/commit/213feb87bfdd1d8ec00ea660e218f9bcfcb34e7b)]: - @mastra/schema-compat@1.3.2 ## 1.48.0-alpha.10 ### Minor Changes - add OM-managed working memory ([#18654](https://github.com/mastra-ai/mastra/pull/18654)) Adds `observationalMemory.observation.manageWorkingMemory` so the Observer can update working memory automatically instead of requiring the main agent to call the working memory tool. ```ts new Memory({ options: { workingMemory: { enabled: true }, observationalMemory: { enabled: true, observation: { manageWorkingMemory: true }, }, }, }); ``` This option adds `WorkingMemoryExtractor`, defaults `workingMemory.agentManaged` to `false`, and defaults `workingMemory.useStateSignals` to `true` when working memory is enabled. Set `workingMemory.agentManaged: true` to keep the main agent's working memory tool and instructions enabled. ### Patch Changes - add observational memory extractors ([#18653](https://github.com/mastra-ai/mastra/pull/18653)) Introduces a public Extractor API for Observational Memory with inline XML extraction and structured follow-up modes. Includes built-in extractors for current task, suggested response, and thread title. Persists extracted values into thread OM metadata with key-level merging and carry-forward into future observer/reflector prompts. - Scripts using Mastra no longer hang after completing their work. The scheduler timer that polls for due schedules previously kept the Node.js event loop alive, preventing process exit even when all work was done. The timer now allows the process to exit naturally. ([#18713](https://github.com/mastra-ai/mastra/pull/18713)) - Fixed buffered observation extraction metadata so stored OM chunks keep extracted values and extraction failures across memory storage adapters. ([#18655](https://github.com/mastra-ai/mastra/pull/18655)) - Fixed channel broadcasting so agent runs on a channel-backed thread post back to the channel even when they did not start from an inbound platform message. Previously only runs triggered by an incoming Slack/Discord/etc. message would render to the channel; heartbeat, Studio, and custom UI runs were silently dropped. The channels output processor now reconstructs the channel destination from the thread itself, so any run on a channel-backed thread delivers its output. ([#18630](https://github.com/mastra-ai/mastra/pull/18630)) ## 1.48.0-alpha.9 ### Minor Changes - support inline JSON prompt injection ([#18652](https://github.com/mastra-ai/mastra/pull/18652)) Added `structuredOutput.jsonPromptInjection: 'inline'` to append JSON schema instructions to the latest user message instead of the system prompt. This helps keep the system prompt stable on providers that cache prompt prefixes. ```ts await agent.generate('Summarize this text', { structuredOutput: { schema, jsonPromptInjection: 'inline', }, }); ``` ## 1.48.0-alpha.8 ### Minor Changes - Added `createCodingAgent` factory and a reusable `buildBasePrompt` so other projects can build a coding agent on top of the same defaults MastraCode uses. ([#18695](https://github.com/mastra-ai/mastra/pull/18695)) The factory wires sensible, portable defaults that you can override per field: - **Workspace** — a local filesystem + sandbox rooted at `process.cwd()` (set `basePath`, pass your own `workspace`, or pass `workspace: undefined` to opt out entirely). - **Task signals** — `TaskSignalProvider` so a task list persists across turns. - **Error handling** — retries on `ECONNRESET` and bad-request errors, plus prefill and provider-history compatibility processors. - **Goal judging** — the default goal judge prompt. `buildBasePrompt` is parameterized with `productName`, `coAuthorName` (both default to "Mastra Code"), and `coAuthorEmail` (defaults to "noreply@mastra.ai"), so you can brand the system prompt and commit trailer without forking it. ```ts import { createCodingAgent } from '@mastra/core/coding-agent'; const agent = createCodingAgent({ id: 'my-coding-agent', name: 'My Coding Agent', model: 'openai/gpt-5', instructions: 'You help with my project.', tools: {}, basePath: '/path/to/repo', }); ``` ### Patch Changes - Fixed background task execution metadata updates so they no longer rewrite the model-visible tool invocation state. ([#18556](https://github.com/mastra-ai/mastra/pull/18556)) ## 1.48.0-alpha.7 ### Minor Changes - Added file-based agents: define an agent by file convention under `src/mastra/agents//` alongside agents created with `new Agent()`. ([#18609](https://github.com/mastra-ai/mastra/pull/18609)) A directory becomes an agent when it has a `config.ts` or `instructions.md`. The directory name is the agent name. `instructions.md` supplies the instructions, `tools/*.ts` supply tools, and `skills/` supplies skills (a `createSkill()` module, a packaged `SKILL.md` directory, or a flat `.md`). Each file-based agent also gets a workspace by default (contained filesystem + shell sandbox rooted at a per-agent `workspace/` dir); customize it with a `workspace.ts` default export or `config.workspace`. Both styles register into the same Mastra instance and show up together in Studio, the server, and the bundler. **Before** ```ts import { Agent } from '@mastra/core/agent'; export const weather = new Agent({ id: 'weather', name: 'weather', instructions: 'You are a weather assistant.', model: 'openai/gpt-4o', }); ``` **After (file-based, optional)** ```ts // src/mastra/agents/weather/config.ts import { agentConfig } from '@mastra/core/agent'; export default agentConfig({ model: 'openai/gpt-4o', // instructions taken from instructions.md, tools from tools/*.ts }); ``` A file-based agent can also declare **subagents** under `agents//subagents//`, using the same directory layout as an agent (`config.ts`, `instructions.md`, `tools/`, `skills/`, `workspace.ts` / `workspace/`). Each subagent is assembled independently and wired into the parent's `agents` map, so the loop exposes it as a delegation tool named after the directory. A subagent's `config.ts` must set a non-empty `description` (build error otherwise), subagents inherit nothing from the parent, and they are one level deep (a nested `subagents/` directory is ignored with a warning). A subagent id colliding with a parent tool key or another subagent id is a build error; an id also present in `config.agents` keeps the `config.agents` entry with a warning. Code-registered agents win on name collisions, and a `config.ts` that exports `new Agent()` is used as-is (its sibling `instructions.md`, `tools/`, and `subagents/` are ignored with a warning), so existing projects are unaffected. The core API surface is `agentConfig()` plus the `assembleAgentFromFsEntry()` / `Mastra.__registerFsAgents()` helpers that turn a discovered directory into a registered agent. Directory discovery itself is performed by the build pipeline; importing the `mastra` instance directly as a library does not scan `agents//` directories, so register those agents in code if you need them outside the build pipeline. ### Patch Changes - `DurableAgent` now matches `Agent` behavior in three places where the durable loop previously diverged: ([#18677](https://github.com/mastra-ai/mastra/pull/18677)) - `isTaskComplete` scorers receive `requestContext` as `customContext`, so the same scorer code works on both agents. Only JSON-serializable entries from `requestContext` are forwarded; non-serializable values are dropped. Do not store secrets in `RequestContext` if you persist durable agent snapshots. - Provider-defined tools (e.g. OpenAI `web_search`) resolve and execute when invoked by the model, instead of surfacing as `ToolNotFoundError`. - Each iteration of a multi-step durable run produces a distinct assistant `messageId`, matching the non-durable loop and unblocking downstream consumers (signal drains, audit logs, replay) that key off message identity. - Fix a polynomial ReDoS in the model gateway error matcher. The `Missing .+ environment variable` pattern used to classify expected missing-auth errors could backtrack catastrophically on adversarial error messages; it now uses `Missing [^ ]+ environment variable`, which matches the same real messages without the ambiguous overlap. ([#18680](https://github.com/mastra-ai/mastra/pull/18680)) ## 1.48.0-alpha.6 ### Minor Changes - Renamed the AgentController interval API. `heartbeatHandlers` is now `intervalHandlers`, the `HeartbeatHandler` type is now `IntervalHandler`, and the `removeHeartbeat()`/`stopHeartbeats()` methods are now `removeInterval()`/`stopIntervals()`. This better reflects that these are fixed-interval background tasks, not liveness pings, and is distinct from the unrelated `mastra.heartbeats` scheduled-agent feature. ([#18665](https://github.com/mastra-ai/mastra/pull/18665)) **Before** ```ts const { controller } = await createMastraCode({ heartbeatHandlers: [{ id: 'sync', intervalMs: 60_000, handler: async () => {} }], }); await controller.removeHeartbeat({ id: 'sync' }); await controller.stopHeartbeats(); ``` **After** ```ts const { controller } = await createMastraCode({ intervalHandlers: [{ id: 'sync', intervalMs: 60_000, handler: async () => {} }], }); await controller.removeInterval({ id: 'sync' }); await controller.stopIntervals(); ``` ### Patch Changes - add agent reference to processor execution context ([#18651](https://github.com/mastra-ai/mastra/pull/18651)) - Fix in-memory observability `listTraces` ignoring the `startExclusive` and `endExclusive` flags on `startedAt`/`endedAt` filters. Exclusive date-range bounds now drop a trace that sits exactly on the boundary, matching the pg/libsql adapters (and the in-memory log/metric filters). Closes #18635. ([#18675](https://github.com/mastra-ai/mastra/pull/18675)) - Fix in-memory scores store `listScoresByScorerId` returning scores in insertion order instead of newest first. The pg and libsql adapters order by `createdAt DESC`, and the sibling `listScoresBySpan` already does, so the in-memory store now sorts the same way before paginating. Closes #18618. ([#18619](https://github.com/mastra-ai/mastra/pull/18619)) - Fixed gs:// and s3:// file/image references being downloaded and corrupted into data: URIs during durable agent execution. The durable LLM step now forwards the model's supportedUrls (matching standard execution), so URLs a provider fetches natively (e.g. Vertex gs://) pass through as references instead of failing with "Failed to download asset" or being base64-wrapped. ([#18649](https://github.com/mastra-ai/mastra/pull/18649)) - Updated dependencies [[`d0702ee`](https://github.com/mastra-ai/mastra/commit/d0702eedc1594cb2d0d83476440cfc2ec8820adb)]: - @mastra/schema-compat@1.3.2-alpha.1 ## 1.48.0-alpha.5 ### Minor Changes - **Added** heartbeats: schedule an agent to run on a recurring cron, either inside an existing conversation thread or on its own. ([#18184](https://github.com/mastra-ai/mastra/pull/18184)) A heartbeat fires a prompt to an agent on a schedule. When it has a thread, the run is delivered into that thread as a normal agent signal, so anything watching the thread sees it like any other message; without a thread, the agent just runs in isolation. Each heartbeat has its own id and an optional `name`, so one agent or thread can have several heartbeats with different schedules and prompts. The id is generated for you, or you can pass your own `id` to `create` for a stable handle (it's normalized to `hb_`). Heartbeats are persisted, so they keep firing across process restarts with no extra setup. ```ts const hb = await mastra.heartbeats.create({ agentId: 'chef', name: 'morning-checkin', threadId, resourceId, cron: '*/5 * * * *', prompt: 'Check in on the user', ifActive: { behavior: 'discard' }, // skip if the user is mid-conversation ifIdle: { behavior: 'wake' }, // wake the agent if the thread is idle }); // Threadless: run the agent on a cron with no conversation. await mastra.heartbeats.create({ agentId: 'chef', cron: '0 * * * *', prompt: 'Run the hourly summary', }); await mastra.heartbeats.list({ agentId: 'chef' }); await mastra.heartbeats.get(hb.id); await mastra.heartbeats.update(hb.id, { prompt: 'check in gently' }); await mastra.heartbeats.pause(hb.id); await mastra.heartbeats.resume(hb.id); await mastra.heartbeats.run(hb.id); // fire once now await mastra.heartbeats.delete(hb.id); ``` The same CRUD is available over HTTP through `@mastra/server` (under `/api/heartbeats`) and as top-level methods on the `@mastra/client-js` client (`client.createHeartbeat`, `client.getHeartbeat`, `client.listHeartbeats`, etc.). **Lifecycle hooks** React to heartbeat runs via `heartbeat` on the `Mastra` constructor. It's a single hook bundle that runs for every agent's heartbeats; each hook receives the firing `agentId` so you can branch on it. `prepare` resolves fire-time parameters (for example, creating a fresh thread per fire), and `onFinish` / `onError` / `onAbort` mirror `agent.stream`. ```ts new Mastra({ // ... heartbeat: { // Return overrides, `null` to skip this fire, or `undefined` to use defaults. prepare: async ({ agentId, heartbeat }) => { if (agentId === 'chef' && heartbeat.name === 'daily-digest') { return { threadId: await createDailyThread(), resourceId: 'slack:U095PUH0FKL' }; } }, onFinish: ({ agentId, outcome, result, heartbeat }) => { metrics.record({ agentId, heartbeat: heartbeat.name, outcome }); }, onError: ({ agentId, error, phase, heartbeat }) => { alerts.send(`heartbeat ${agentId}/${heartbeat.name} failed in ${phase}: ${error.message}`); }, }, }); ``` **Signal shaping** A heartbeat fire surfaces to the agent as a signal. By default it uses the `notification` type and renders as `…`; override `signalType` and `tagName` to change either. `ifActive` and `ifIdle` mirror the `agent.sendSignal` options shape (`{ behavior, attributes }`, plus `streamOptions` on `ifIdle`) and stay JSON-serializable so they persist with the schedule. `ifIdle.streamOptions` currently accepts `requestContext`, which is rehydrated onto the woken run. Top-level `attributes` are rendered on the signal tag, and top-level `providerOptions` are merged into the signal payload on every fire. ```ts await mastra.heartbeats.create({ agentId: 'chef', threadId, resourceId, cron: '*/5 * * * *', prompt: 'Check in on the user', tagName: 'check-in', // renders as … attributes: { source: 'cron' }, providerOptions: { openai: { store: false } }, ifIdle: { behavior: 'wake', streamOptions: { requestContext: { locale: 'en-US' } }, }, }); ``` - Added storage-backed discovery of suspended agent runs, so human-in-the-loop approval UIs can recover a pending run after a page refresh or server restart. ([#17898](https://github.com/mastra-ai/mastra/pull/17898)) `agent.listSuspendedRuns()` lists runs waiting on a tool-call approval or on a tool that called `suspend()`. Unlike the in-memory `getActiveThreadRunId()`, it reads from storage, so it works after a restart and across multiple server instances: ```ts const { runs, total } = await agent.listSuspendedRuns({ threadId, resourceId }); if (runs[0]) { // runs[0].toolCalls -> [{ toolCallId, toolName, args, requiresApproval }] await agent.approveToolCall({ runId: runs[0].runId, toolCallId: runs[0].toolCalls[0].toolCallId }); } ``` Supports `threadId`/`resourceId`/date filters and pagination, mirroring `listWorkflowRuns()`. The same surface is exposed over HTTP as `GET /agents/:agentId/suspended-runs` and on the client SDK as `agent.listSuspendedRuns()`; server-enforced request-context values take precedence over client query parameters, so clients cannot list runs outside their scope. `sendToolApproval()` now falls back to this storage-backed discovery when no active run is found in memory for the thread, so approvals keep working after a restart. If several suspended runs match, it throws an error asking for a `toolCallId` to disambiguate. **Why:** approval UIs previously had no public way to recover a suspended run after a refresh or restart, forcing apps to parse internal workflow snapshots. ## 1.48.0-alpha.4 ### Patch Changes - Bring `InngestAgent` (Inngest-backed durable agent) to parity with `DurableAgent` for per-call execution options, abort handling, idle-aware resume, and `generate()`. ([#18615](https://github.com/mastra-ai/mastra/pull/18615)) `InngestAgent.stream()` and `resume()` now accept the same execution-option surface as `DurableAgent`, including `stopWhen`, `activeTools`, `structuredOutput`, `versions`, `system`, `disableBackgroundTasks`, `tracingOptions`, `actor`, `transform`, `prepareStep`, `isTaskComplete`, `delegation`, function-form `requireToolApproval`, and the lifecycle callbacks `onAbort` / `onIterationComplete`. Closure-shaped options (`prepareStep`, `transform`, function-form `isTaskComplete` / `requireToolApproval`, `stopWhen` callbacks) continue to work in-process; they degrade after a worker hop the same way they do for in-memory `DurableAgent`. ```ts const result = await inngestAgent.stream(messages, { runId: 'run-1', abortSignal: controller.signal, stopWhen: stepCountIs(5), onIterationComplete: ({ iteration }) => console.log('done', iteration), }); // Cancel a live run from the caller result.abort(); // Resume and drive the run to completion in a single call await inngestAgent.resume({ runId: 'run-1', resumeData, untilIdle: true }); // Durable equivalents of Agent.generate / resumeGenerate const out = await inngestAgent.generate(messages, { runId: 'run-2' }); const resumed = await inngestAgent.resumeGenerate({ runId: 'run-2', resumeData }); ``` `@mastra/core` re-exports `globalRunRegistry` and `runResumeDurableStreamUntilIdle` from `@mastra/core/agent/durable` so durable-agent integrations can share the same registry and idle-wrapper plumbing. - Amazon Bedrock models now appear under their own `amazon-bedrock/` provider in the model picker instead of the `mastracode/amazon-bedrock/` namespace. Bedrock is resolved through a dedicated Amazon Bedrock gateway that authenticates with the AWS credential chain (SigV4) and surfaces models from the public models.dev catalog. Saved model selections using the previous `mastracode/amazon-bedrock/...` IDs are still resolved at runtime, so existing config keeps working. ([#17937](https://github.com/mastra-ai/mastra/pull/17937)) - Fixed custom model gateways being overridden by default gateways. GatewayManager now deduplicates gateways by ID (first-wins) so custom gateways take precedence over defaults. Narrowed the auth-availability check to only swallow expected missing-credential errors instead of all errors, so real gateway failures surface during debugging. ([#18602](https://github.com/mastra-ai/mastra/pull/18602)) ## 1.48.0-alpha.3 ### Patch Changes - Fixed thread metadata being lost when a processor or working memory writes to it during an agent run. The thread is re-saved when the run finishes, and it was using a stale in-memory snapshot that overwrote any metadata written mid-run via updateThread. The agent now re-reads the latest persisted thread before that save, so mid-run metadata is preserved. Affects all storage backends (Postgres, LibSQL, and others). Fixes #16216. ([#18152](https://github.com/mastra-ai/mastra/pull/18152)) - Fix in-memory workflow storage `getWorkflowRunById` returning `null` when `workflowName` is omitted. `workflowName` is optional in the storage contract and the pg/libsql adapters match by `runId` alone when it is not provided, but the in-memory store always compared `workflow_name === workflowName`, which never matched for an undefined name. It now matches by `runId`, only filters by `workflowName` when provided, and returns the most recent run for parity with the persistent adapters. Closes #18585. ([#18586](https://github.com/mastra-ai/mastra/pull/18586)) - Fixed 'Type instantiation is excessively deep' (TS2589) errors that occurred when defining workflows with Zod schemas. Workflow and step type inference is now significantly faster and no longer causes TypeScript to crash or report depth errors. ([#18608](https://github.com/mastra-ai/mastra/pull/18608)) - Updated dependencies [[`9feeaa0`](https://github.com/mastra-ai/mastra/commit/9feeaa0f9a1af07039e5b4f22b932b0cb18617e8), [`213feb8`](https://github.com/mastra-ai/mastra/commit/213feb87bfdd1d8ec00ea660e218f9bcfcb34e7b)]: - @mastra/schema-compat@1.3.2-alpha.0 ## 1.48.0-alpha.2 ### Patch Changes - Reduce repeated schema work during sub-agent tool conversion for more stable memory usage. ([#18566](https://github.com/mastra-ai/mastra/pull/18566)) ## 1.48.0-alpha.1 ### Patch Changes - fix: prevent partial gateway sync from corrupting provider registry ([#18545](https://github.com/mastra-ai/mastra/pull/18545)) - Fixed notification signal delivery to idle threads not including ifIdle with streamOptions. When GitHub notifications or heartbeats wake an idle agent thread, the request context (containing model selection) was missing, causing 'No model selected' errors. Added getNotificationStreamOptions callback to AgentNotificationConfig so the notification dispatcher can resolve stream options for deferred notifications. ([#18549](https://github.com/mastra-ai/mastra/pull/18549)) ## 1.48.0-alpha.0 ### Minor Changes - Added optional `maxSteps` to `ScorerJudgeConfig` and `GoalConfig`, letting callers control the internal judge agent's agentic-loop iteration limit instead of relying on the implicit default of 5. Also fixed judge agent not receiving Mastra registration, which caused the observer's API key resolution to fail silently. ([#18544](https://github.com/mastra-ai/mastra/pull/18544)) - Added optional `maxSteps` to `ScorerJudgeConfig`, letting callers control the internal judge agent's agentic-loop iteration limit instead of relying on the implicit default of 5. ([#18544](https://github.com/mastra-ai/mastra/pull/18544)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b9a2961`](https://github.com/mastra-ai/mastra/commit/b9a2961c1be81e3639c0879e58588c26dd0ae866)) - Fixed find_files and grep tools to always exclude .git directory contents — the .git directory is now skipped at the traversal boundary in both tools since its internals are never useful and waste tokens. Also fixed pipe-separated exclude patterns in find_files (e.g. ".git|node_modules") to work correctly, matching tree's -I flag behavior. ([#18548](https://github.com/mastra-ai/mastra/pull/18548)) ## 1.47.0 ### Minor Changes - Added support for AI SDK v7 models (`LanguageModelV4`). You can now pass any AI SDK v7 provider model directly to an agent, alongside the existing AI SDK v4, v5, and v6 support. ([#18477](https://github.com/mastra-ai/mastra/pull/18477)) ```ts import { Agent } from '@mastra/core/agent'; import { openai } from '@ai-sdk/openai'; // AI SDK v7 const agent = new Agent({ name: 'my-agent', instructions: 'You are a helpful assistant.', model: openai('gpt-5'), }); ``` Mastra detects the model's specification version automatically, so mixing models from different AI SDK versions across your agents continues to work without any extra configuration. - Finished closing the gap between `DurableAgent` and `Agent`. After this change the durable agent surface mirrors the in-process agent's stream, resume, and generate APIs. ([#18508](https://github.com/mastra-ai/mastra/pull/18508)) **What's new** - `abortSignal` on `stream()` and `resume()`, plus `result.abort()` on `stream()`, `resume()`, and `observe()`. `result.abort()` on `streamUntilIdle()` fans out to every inner run. - `untilIdle` on `resume()` (it already existed on `stream()`), using the shared `runWithIdleWrapper` so both paths drive the same idle loop. - `DurableAgent.generate()` and `DurableAgent.resumeGenerate()` wrap `stream()` / `resume()` and resolve a `FullOutput` even when the run suspends mid-flight. - `delegation` callbacks (`onDelegationStart`, `onDelegationComplete`, `messageFilter`) are forwarded to `convertTools` at prepare time and baked into sub-agent tool wrappers. - Per-call `clientTools` and `toolsets` survive in-process resume via the run registry (cross-process resume still falls back to the agent's static tools). - Scorers configured on the wrapped agent or passed per call now actually execute under durable runs and emit `ON_SCORER_RUN` payloads matching the non-durable shape. - `AGENT_RUN` spans now carry `conversationId`, `instructions`, `resolvedVersionId`, `entityVersionId`, and the agent's `tracingPolicy`. Resume spans use `'agent run: (resumed)'` and include `resumedFromSpanId`. **Example** ```ts import { createDurableAgent } from '@mastra/core/agent/durable'; const durable = createDurableAgent({ agent: myAgent }); // 1. generate() — drains a durable run to a single FullOutput const out = await durable.generate('Plan a week in Lisbon', { abortSignal: controller.signal, modelSettings: { temperature: 0.2 }, }); // 2. stream() with result.abort() — cancel mid-run const result = await durable.stream('Long research task'); setTimeout(() => result.abort(), 5_000); for await (const chunk of result.output.fullStream) { process.stdout.write(chunk.payload?.text ?? ''); } // 3. Suspend → resumeGenerate() round-trip (e.g. tool approval) const first = await durable.generate('Run the dangerous tool', { requireToolApproval: true, }); if (first.finishReason === 'suspended') { const final = await durable.resumeGenerate(first.runId!, { approved: true }); console.log(final.text); } // 4. resume({ untilIdle }) — drive a resume through to a quiescent state await durable.resume(runId, resumeData, { untilIdle: true }); ``` - Brought `DurableAgent.stream()` to parity with `Agent.stream()` for the full `AgentExecutionOptions` surface, so the same call works on both. ([#18461](https://github.com/mastra-ai/mastra/pull/18461)) **What's new** `DurableAgent.stream()` now honors these options that were previously dropped or silently coerced: - Full `modelSettings` (was: only `temperature`) — `maxOutputTokens`, `topP`, `topK`, `presencePenalty`, `frequencyPenalty`, `stopSequences`, `seed`, `headers` - `stopWhen`, `prepareStep`, `isTaskComplete`, `transform` - Per-call `instructions` and `system` - `disableBackgroundTasks`, `tracingOptions`, `actor` - Function-form `requireToolApproval(toolName, args, …)` (was: coerced to "approve all" — now evaluated per tool call) - New callbacks: `onAbort` and `onIterationComplete` (joining the existing `onChunk` / `onStepFinish` / `onFinish` / `onError` / `onSuspended` bridge) **Example** The same options that work on `Agent.stream()` now work on `DurableAgent.stream()`: ```ts const durable = createDurableAgent({ agent, pubsub }); await durable.stream('Plan the trip', { modelSettings: { temperature: 0.2, maxOutputTokens: 500, topP: 0.9 }, stopWhen: ({ steps }) => steps.length >= 3, prepareStep: ({ stepNumber }) => ({ activeTools: stepNumber === 0 ? ['search'] : ['book'], }), requireToolApproval: ({ toolName }) => toolName === 'book', onIterationComplete: ({ iteration }) => console.log('iter', iteration), }); ``` - Resolve all Harness models through gateways. The Harness now builds its available-models catalog, model auth status, mode agents, Observational Memory models, and subagents from the gateways you register, instead of the separate `resolveModel`, `customModelCatalogProvider`, and `modelAuthChecker` config hooks. Removed those three options from `HarnessConfig` (and the `ModelAuthChecker` type) — register a gateway via `gateways` instead. ([#18382](https://github.com/mastra-ai/mastra/pull/18382)) `listAvailableModels()` and `getCurrentModelAuthStatus()` are now sourced entirely from gateways: model discovery comes from each gateway's `fetchProviders()` (a network call for gateways like models.dev and Netlify), and auth status is resolved through the same gateway chain the model router uses at run time (`resolveAuth()`, falling back to `getApiKey()`). There is no static provider-registry fallback — everything the model picker shows comes from a gateway. The gateway-chain operations shared by `ModelRouterLanguageModel` and the Harness (gateway merging, model→gateway selection, auth resolution, provider/model listing) are now centralized in a new `GatewayManager` class exported from `@mastra/core`. Both consumers delegate to it, eliminating duplicated logic. `defaultGateways` is still re-exported from the same paths for backward compatibility. - Added `reasoning` option to `modelSettings` for controlling model reasoning effort level. This option accepts standardized levels ('provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh') and is effective with LanguageModelV4 (AI SDK v7) providers that support reasoning. When used with older model providers (V2/V3), the option is a no-op. ([#18500](https://github.com/mastra-ai/mastra/pull/18500)) **Usage:** ```typescript const result = await agent.stream('Solve this problem', { modelSettings: { reasoning: 'high' }, }); ``` Also upgraded the model router to support LanguageModelV4, enabling native V4 model resolution alongside existing V2 and V3 models without regressions. - Added an optional `availableTools` allowlist to `HarnessModeBase` so each mode can declare one unified list of visible tool names. When set, the harness resolves `activeTools` at LLM-call time and hides any tool not in the list — including workspace tools, which are matched by their exposed names. Per-tool and category `deny` permission rules still take precedence over the allowlist. `undefined` means no mode-level restriction (existing behavior). This moves mode-based tool visibility out of workspace construction and into a single, serializable contract. ([#18463](https://github.com/mastra-ai/mastra/pull/18463)) **Example** Declare a mode that only exposes read and plan-writing tools: ```ts import type { HarnessMode } from '@mastra/core/harness'; const planMode: HarnessMode = { id: 'plan', // Only these tools are visible to the model in plan mode. // Workspace tools are matched by their exposed (renamed) names. availableTools: ['view', 'find_files', 'search_content', 'write_file', 'submit_plan'], // ...other mode config }; // Omitting availableTools (or setting undefined) leaves all tools visible. // Set to [] to hide every tool for this mode. ``` - Rename the `Harness` class to `AgentController` and its associated `Harness*` types to `AgentController*` (e.g. `HarnessConfig` → `AgentControllerConfig`, `HarnessMode` → `AgentControllerMode`, `HarnessEvent` → `AgentControllerEvent`). ([#18505](https://github.com/mastra-ai/mastra/pull/18505)) `@mastra/core`: A new canonical subpath `@mastra/core/agent-controller` exports the `AgentController` class plus the `AgentController*` types. The legacy `@mastra/core/harness` subpath remains available and re-exports the deprecated `Harness*` aliases, so existing `Harness` class and type usage continues to work unchanged. New code should import from `@mastra/core/agent-controller`. On `Mastra`, the hosted-controller API is exposed under agent-controller names — `getAgentController`, `getAgentControllerById`, `listAgentControllers`, and the `agentControllers` config key — while `getHarness`/`getHarnessById`/`listHarnesses` and the `harnesses` config key remain as deprecated aliases. `@mastra/server`: The controller session API is now served exclusively under `/agent-controller/...` (with `agent-controller:read` / `agent-controller:execute` permissions). The legacy `/harness/...` routes and `harness:*` permissions have been removed. List responses use the `agentControllers` key, session responses use `controllerId`, and path params use `:controllerId`. `@mastra/client-js`: `AgentController` and `AgentControllerSession` are now the canonical resource classes, with `MastraClient.getAgentController(id)` / `listAgentControllers()` targeting the `/agent-controller` routes and reading the canonical `agentControllers` / `controllerId` response keys. The deprecated `getHarness` / `listHarnesses` methods and `Harness` / `HarnessSession` classes have been removed. This is a breaking change for the recently released client. The `@mastra/core` peer dependency floor is raised to `>=1.47.0-0` so consumers must be on the release that introduces the canonical agent-controller surface. This applies to `@mastra/server`, `@mastra/deployer`, and `mastra` (CLI), and is propagated to the packages that depend on them: the deployers (`@mastra/deployer-cloud`, `@mastra/deployer-cloudflare`, `@mastra/deployer-netlify`, `@mastra/deployer-vercel`), the server adapters (`@mastra/express`, `@mastra/fastify`, `@mastra/hono`, `@mastra/koa`, `@mastra/nestjs`, `@mastra/next`, `@mastra/tanstack-start`), and `@mastra/temporal`. - **Gates and verdict for `runEvals`** ([#18394](https://github.com/mastra-ai/mastra/pull/18394)) New optional `gates` field accepts scorers that must score 1.0 for the run to pass. Scorers can now use a `{ scorer, threshold }` form to set pass/fail thresholds. `threshold` accepts a number (minimum) or `{ min, max }` for range-based checks (e.g. hallucination where high = bad). The result includes `verdict`, `gateResults`, and `thresholdResults`. Fully backward compatible. ```ts import { runEvals } from '@mastra/core/evals'; import { checks } from '@mastra/evals/checks'; const result = await runEvals({ data: [{ input: 'What is the weather?' }], target: weatherAgent, gates: [checks.calledTool('get_weather')], scorers: [ { scorer: faithfulnessScorer, threshold: 0.7 }, { scorer: hallucinationScorer, threshold: { max: 0.3 } }, ], }); result.verdict; // 'passed' | 'scored' | 'failed' ``` - Added `waitUntil` support for channels so background agent runs survive serverless responses. ([#17832](https://github.com/mastra-ai/mastra/pull/17832)) Without `waitUntil`, the runtime freezes the invocation as soon as the webhook response returns, killing the agent run mid-flight and leaving the user with no reply. **Auto-detected (no config needed):** - **Cloudflare Workers** — reads `c.executionCtx.waitUntil` - **Netlify Functions** — reads `c.env.context.waitUntil` **Requires explicit config:** Vercel needs a `waitUntil` function passed directly because it exposes `waitUntil` via AsyncLocalStorage, not the request context. AWS Lambda doesn't need `waitUntil` — it waits for the event loop to drain naturally. ```ts import { waitUntil } from '@vercel/functions'; import { SlackProvider } from '@mastra/slack'; new SlackProvider({ waitUntil }); ``` ```ts new Agent({ channels: { adapters: { slack: createSlackAdapter({ ... }) }, waitUntil, }, }); ``` For runtimes where `waitUntil` lives on the request context but isn't covered by the default, pass `resolveWaitUntil: (c) => fn | undefined` instead. ```ts new Agent({ channels: { adapters: { slack: createSlackAdapter({ ... }) }, resolveWaitUntil: c => c.var.runtime?.waitUntil, }, }); ``` Resolution order: `waitUntil` → `resolveWaitUntil(c)` → core default. - Moved workspace and browser ownership from the Harness to individual sessions. `createSession` now accepts optional `workspace` and `browser` overrides that are passed directly to the `Session` constructor. When no override is provided, the Harness-level config is used as a fallback. ([#18467](https://github.com/mastra-ai/mastra/pull/18467)) **Breaking changes:** - `Session` constructor now requires `workspace: Workspace` and accepts optional `browser?: MastraBrowser` - `HarnessConfig.workspace` no longer accepts a `WorkspaceConfig` object — pass a `Workspace` instance or a dynamic factory function - `createSession` overrides accept static `Workspace` / `MastraBrowser` instances only (not `DynamicArgument`) - Removed `Harness.destroyWorkspace()` — workspace lifecycle is now driven by per-session `workspace_status_changed`, `workspace_ready`, and `workspace_error` events emitted after `workspace.init()` completes - `Harness.getWorkspace()` returns the static workspace instance only (returns `undefined` for dynamic factory configs); use `resolveWorkspace({ session })` to resolve and cache a factory outside the request flow - `Harness.resolveWorkspace()` now requires a `session` parameter to resolve dynamic factories against the session's request context - The `SessionBus` replays the last workspace lifecycle event group to subscribers that attach after session creation, so late listeners always learn the current workspace status **Before:** ```ts const harness = new Harness({ name: 'my-harness', workspace: { name: 'my-workspace' }, // WorkspaceConfig shorthand }); const session = await harness.createSession({ resourceId: 'user-1', ownerId: 'owner-1', id: 'session-1', }); // workspace resolved lazily by the harness const ws = harness.getWorkspace(); ``` **After:** ```ts import { Workspace } from '@mastra/core'; const harness = new Harness({ name: 'my-harness', workspace: new Workspace({ name: 'my-workspace' }), }); // workspace can be overridden per session with a static instance const session = await harness.createSession({ resourceId: 'user-1', ownerId: 'owner-1', id: 'session-1', workspace: new Workspace({ name: 'ws-1' }), }); // for dynamic per-request workspaces, use the harness-level config const harness2 = new Harness({ name: 'my-harness', workspace: ({ requestContext, mastra }) => new Workspace({ name: `ws-${requestContext.session.id}` }), }); ``` - Surface step `providerMetadata` to output-step processors ([#18432](https://github.com/mastra-ai/mastra/pull/18432)) Output-step processors now receive the finishing step's provider-specific metadata through the new optional `providerMetadata` field on `ProcessOutputStepArgs`. This lets observability and guardrail processors attribute a model response to the provider data behind it — most notably an AWS Bedrock guardrail trace on a `content-filter` block, where the completed-steps array is empty and the assessment was previously unreachable. The field is present whenever the underlying model step produced provider metadata, in both streaming and non-streaming generation. Behaviour is unchanged for steps without provider metadata. ```ts class MyProcessor implements Processor { async processOutputStep({ finishReason, providerMetadata }: ProcessOutputStepArgs) { if (finishReason === 'content-filter') { const guardrail = providerMetadata?.bedrock?.trace?.guardrail; // attribute the block to the responsible policy/topic/filter } } } ``` - Scope harness session creation with tags so sessions sharing a resourceId can ([#18446](https://github.com/mastra-ai/mastra/pull/18446)) each resume their own thread. `harness.createSession()` now accepts an optional `tags` record. The tags are (a) seeded into the new session's state, (b) stamped onto every thread the session creates (so thread listings can be filtered back to the session's scope), and (c) used to filter initial thread selection: a thread is a resume candidate only when its metadata matches every provided tag. Previously, initial thread selection only consulted the harness-global `initialState.projectPath`; on a multi-session server (where one Harness serves many scopes) a session could resume the most recently updated thread from a _different_ scope that shared the resourceId. Using a generic tag record (e.g. `{ projectPath }`) keeps room for future scoping dimensions without further API changes. The `@mastra/client-js` `HarnessSession.create()` method accepts `{ tags }`, and the `POST /harness/:id/sessions` route accepts a `tags` body field. ```ts // before: initial thread chosen by resourceId only const session = await harness.createSession({ resourceId, id, ownerId }); // after: initial thread scoped to this worktree via a tag const session = await harness.createSession({ resourceId, id, ownerId, tags: { projectPath: '/repo/worktree-a' }, }); ``` ### Patch Changes - Fixed channel rendering on serverless runtimes (Vercel, AWS Lambda, Cloudflare Workers, Netlify). ([#17832](https://github.com/mastra-ai/mastra/pull/17832)) **No more duplicate replies.** On serverless, the same webhook event can be processed by multiple concurrent invocations. Previously each one rendered the agent reply independently, producing duplicate messages. Now only the invocation that wins the run lease renders — losers resolve cleanly without posting. **Runs no longer get killed mid-flight.** The channel handler now keeps the serverless invocation alive until the agent run finishes, instead of returning immediately and relying on a background subscription that gets frozen. **Multi-step replies fully render.** Agents that make multiple LLM calls per run (e.g. tool use → follow-up) now render every step's text instead of only the first. **Tool approval buttons now appear on all rendering paths.** Previously, tool approval prompts (approve/deny buttons) could fail to render when using output processors. Fixed by routing `tool-call-approval` chunks through the output processor pipeline. **Tool approval and decline use the same rendering path as regular messages.** Approval and decline actions now flow through the per-run output processor instead of a separate subscription-based path, making rendering consistent across all message types. **User output processors now run before channel rendering.** User-configured output processors (e.g. PII redaction, translation) now transform chunks before the channel renders them to the platform, instead of after. **Concurrency strategy switched to `concurrent`.** The chat-sdk queue lock could get stuck in frozen serverless invocations. Ordering is now handled by the signals/lease layer, removing the stale-lock failure mode. - feat(playground): render ask_user tool as interactive question UI in Studio ([#18374](https://github.com/mastra-ai/mastra/pull/18374)) Adds an `AskUserBadge` component that renders suspended `ask_user` tool calls as interactive prompts with clickable option buttons (single/multi-select) or free-text input. The user's answer is sent as `resumeData` through the existing `sendToolApproval` flow to properly resume the tool. Plumbing changes: - `sendToolApprovalBodySchema` now accepts optional `resumeData` - `agent.sendToolApproval()` passes custom `resumeData` when provided - `@mastra/client-js` and `@mastra/react` expose the new parameter - Update provider registry and model documentation with latest models and providers ([`7c9dd77`](https://github.com/mastra-ai/mastra/commit/7c9dd77bd18cb8dc72797e25f1a0fbdc71a11347)) - Forward the parent `abortSignal` to delegated subagents so that aborting a supervisor's `stream()` or `generate()` call cancels in-flight subagents instead of letting them run to completion. ([#17561](https://github.com/mastra-ai/mastra/pull/17561)) Previously, calling `AbortController.abort()` on a supervisor only stopped the supervisor itself: each delegated subagent kept looping and calling its own tools and the LLM for several seconds after the abort, because the delegation tool dropped the parent's `abortSignal`. The signal is now propagated to every delegation path (`stream`, `generate`, `resumeStream`, `resumeGenerate`, and the legacy variants). ```typescript const controller = new AbortController(); const stream = await supervisor.stream('Research AI trends', { abortSignal: controller.signal, }); // Now also cancels any in-flight subagents, not just the supervisor controller.abort(); ``` Fixes #14820. - Added `DatasetItemPayload`, a new exported type describing the user-supplied fields of a dataset item. ([#18489](https://github.com/mastra-ai/mastra/pull/18489)) Dataset item inputs (`AddDatasetItemInput`, `UpdateDatasetItemInput`, and batch insert items) now share this type, so they stay consistent automatically. This is a type-only change with no runtime behavior change. - Added `collectToolMocks` export to `@mastra/core/evals`. The helper walks a `TrajectoryStep[]` and returns `DatasetItemToolMock[]`, collecting top-level `tool_call` and `mcp_tool_call` steps in recorded order (sub-agent delegations are emitted as `matchArgs: 'ignore'`). Consumers can now derive item-level tool mocks from a hydrated trajectory directly from `@mastra/core`. ([#18488](https://github.com/mastra-ai/mastra/pull/18488)) ```ts import { collectToolMocks } from '@mastra/core/evals'; import type { Trajectory } from '@mastra/core/evals'; const mocks = collectToolMocks(trajectory.steps); ``` - Fixed Studio failing to boot in the browser with `TypeError: os.tmpdir is not a function`. The local sandbox resolved its mount marker directory at module-load time via `os.tmpdir()`, which crashed the client bundle where `node:os` has no `tmpdir`. The directory is now resolved lazily, so importing the Agent runtime in the browser no longer throws. ([#18524](https://github.com/mastra-ai/mastra/pull/18524)) - Plan rejection now returns distinct tool results for inline-feedback vs. chat-based revision paths, enabling the TUI to stop the agent and wait for user chat messages. ([#18323](https://github.com/mastra-ai/mastra/pull/18323)) - Added an optional `handlesModel` hook to the model gateway interface so a gateway can claim bare provider model IDs (like `__GATEWAY_ANTHROPIC_MODEL_OPUS__`) it is able to authenticate, before routing falls back to the default models.dev catalog. This lets gateways that hold credentials (for example OAuth-backed logins) be selected for auth resolution and availability checks instead of being bypassed. ([#18503](https://github.com/mastra-ai/mastra/pull/18503)) - Preserved response message boundaries when processor message arrays are reapplied after output step processing. ([#18337](https://github.com/mastra-ai/mastra/pull/18337)) - Reworked plan mode to use named plan files. The agent writes its plan to a markdown file under `.mastracode/plans/` and calls `submit_plan` with the `path` to that file, so multiple plans stay on disk for review. The host validates the submitted path is a `.md` file inside `.mastracode/plans/` before reading it, so the tool can't be pointed at arbitrary files. Plan mode can write any `.md` file inside `.mastracode/plans/` (enforced by a tool guard) but nothing else, submitted plan snapshots are persisted for history replay, revision diffs are computed from a real line-ending-normalized LCS diff and only shown when the change is small relative to the plan, "Request Changes" stops the run immediately with no trailing model output, and diffs only compare revisions of the same plan file. ([#18490](https://github.com/mastra-ai/mastra/pull/18490)) - Fixed `InferToolInput`, `InferToolOutput`, `InferUITool`, and `InferUITools` so tools created with `createTool({ outputSchema: z.object(...) })` now produce a typed `{ input, output }` instead of falling through to `never`. UIMessage parts (`tool-`) and other consumers can now discriminate on the inferred input/output types without manual workaround shims. ([#17039](https://github.com/mastra-ai/mastra/pull/17039)) ```ts const echo = createTool({ id: 'echo', inputSchema: z.object({ x: z.string() }), outputSchema: z.object({ y: z.number() }), execute: async ({ x }) => ({ y: x.length }), }); // before: { input: never; output: never } // after: { input: { x: string }; output: { y: number } } type UI = InferUITool; ``` Closes the gap left by #7184. - Fixed the in-memory workflow store resetting a run's `createdAt` on every re-persist, so it now matches the persistent stores. Re-persisting an existing run preserves the original `createdAt` and only advances `updatedAt`. ([#18004](https://github.com/mastra-ai/mastra/pull/18004)) - Fixed duplicate step ID warnings in Inngest workflows. When a workflow step ran, two persistence calls with the same ID caused AUTOMATIC_PARALLEL_INDEXING warnings in Inngest logs. Steps now use unique IDs for each persistence call, eliminating the warnings. ([#17320](https://github.com/mastra-ai/mastra/pull/17320)) - Fixed thread auto-resume selecting the wrong thread in git worktrees by scoping startup selection to threads tagged with the current project path. When Mastra Code detects a matching project thread on a different resource after resourceId drift, it now prompts before cloning and resuming that thread under the current resource; accepting the prompt leaves the old thread untouched and loads the clone, while declining starts fresh and leaves the old resource untouched. ([#18333](https://github.com/mastra-ai/mastra/pull/18333)) - Fixed duplicate stream events when attaching to an in-progress durable or evented agent run. ([#18191](https://github.com/mastra-ai/mastra/pull/18191)) When you call `agent.observe(runId, { offset })` (or reconnect to a stream with replay) while `agent.stream()` is still running, the buffered portion of `output.textStream` could be delivered twice before settling into normal single delivery. Late observers now receive each text-delta exactly once. **Why:** Two issues in the resumable-stream path could double events. - If you passed a caching pubsub to `new Mastra({ pubsub })` (e.g. `withCaching(...)`), the agent adopted it as its inner transport and then wrapped it again in a second caching layer sharing the same cache. Every event was stored twice, so replay delivered the buffered prefix doubled. The agent now reuses the existing caching pubsub instead of double-wrapping it. ```ts // This setup no longer double-caches: const cache = new InMemoryServerCache(); const mastra = new Mastra({ pubsub: withCaching(new EventEmitterPubSub(), cache), cache }); ``` - Replay/live deduplication keyed on `event.id`, but the underlying pubsub regenerates `id` on publish, so the cached copy and the live copy of the same event carried different ids and dedup never matched. Deduplication now keys on the event's stable sequential index, which is preserved across both paths. - Workspace skills now refresh on each skill-tool invocation, so edits to SKILL.md files (or new skills added to the workspace) are picked up between tool calls without restarting the server. Set `checkSkillFileMtime: true` on the workspace config to also detect content changes to existing SKILL.md files. Fixes [#16640](https://github.com/mastra-ai/mastra/issues/16640). ([#16678](https://github.com/mastra-ai/mastra/pull/16678)) - Fixed createTool type error when passing jsonSchema() or a Schema object from @ai-sdk/provider-utils as inputSchema — no more cast needed ([#17435](https://github.com/mastra-ai/mastra/pull/17435)) - Fixed reasoning text being lost when agent messages are persisted to memory and recalled on the next turn ([#17803](https://github.com/mastra-ai/mastra/pull/17803)) - Preserve original tool-call arguments when a tool result is persisted without its matching tool-call in the same message. Previously the arguments were saved as empty `{}`, which poisoned the model's context and caused it to emit invalid empty-argument tool calls after a few cycles. The adapter now recovers the original arguments from prior persisted messages, covering the server resume path, AG-UI hosts replaying client-tool results, and `@mastra/client-js` streaming recursion. ([#16981](https://github.com/mastra-ai/mastra/pull/16981)) Also execute every streamed client tool call emitted in the same tool-calls step before continuing, instead of only continuing with one tool result. Fixes #16017 and #15576. - Fixed images from tool results being dropped when using AI SDK v6 (`@ai-sdk/*` v3) providers. Tools that return image data via `toModelOutput` (for example screenshots) now reach the model correctly instead of arriving empty. ([#18396](https://github.com/mastra-ai/mastra/pull/18396)) - Address post-merge review follow-ups from the evented-workflow serialization work: ([#18385](https://github.com/mastra-ai/mastra/pull/18385)) - `Mastra` now logs a warning when the TTL sweep evicts a run-scoped workflow that was still registered, surfacing abandoned suspended runs instead of dropping them silently. - The `RegExp` codec's `// lgtm[js/regex-injection]` suppression now spells out all three structural gates (`isEnvelope` length cap + flag whitelist, local re-narrowing in `decodeRegExpEnvelope`, `try/catch` fallback) at the suppression site so future readers don't reintroduce escaping. - The `evented-unix-pubsub` scenario test now allocates its Unix socket under a short `/tmp/aim-*` directory instead of `os.tmpdir()` so it stays under the macOS 104-byte `sun_path` limit. - Added a browser-safe `@mastra/core/utils/collect-tool-mocks` export for `collectToolMocks`. The previous `@mastra/core/evals` barrel pulls in Node-only modules (`node:crypto`), which broke bundling in browser apps such as the Studio playground. Import the helper from the new subpath in browser code: ([#18504](https://github.com/mastra-ai/mastra/pull/18504)) ```ts import { collectToolMocks } from '@mastra/core/utils/collect-tool-mocks'; ``` The `@mastra/core/evals` export still works for Node-side callers. - Fixed dynamic workspace resolution to pass the full request context to workspace factories, allowing them to access session state through getState(), and handled async stream aborts correctly so initial streamed output is preserved. ([#18497](https://github.com/mastra-ai/mastra/pull/18497)) - Added stable session `id` and `ownerId` to Harness sessions. `SessionIdentity` now exposes `getId()` and `getOwnerId()`, and `harness.createSession()` accepts `id` and `ownerId` to set them. These identifiers are stable for the life of the session — they do not change when the resource ID is switched — and are surfaced in the harness request context session snapshot. ([#18372](https://github.com/mastra-ai/mastra/pull/18372)) **Breaking change:** `id` and `ownerId` are now **required** on both `harness.createSession()` and the `Session`/`SessionIdentity` constructors. There are no defaults. ```typescript // Before const session = new Session({ resourceId: 'my-resource' }); const session = await harness.createSession(); // After const session = new Session({ resourceId: 'my-resource', id: 'my-session-id', ownerId: 'my-owner-id', }); const session = await harness.createSession({ id: 'my-session-id', ownerId: 'my-owner-id', }); session.identity.getId(); // 'my-session-id' session.identity.getOwnerId(); // 'my-owner-id' ``` - Fixed a thread becoming permanently unresponsive after a signal woke it without anyone reading the agent's response. ([#18493](https://github.com/mastra-ai/mastra/pull/18493)) When a signal woke a thread in the background and nothing consumed the resulting run, the run never finished and the thread stayed busy forever. Any further signals sent to that thread were then absorbed by the stuck run instead of starting a new one, so the agent stopped responding on that thread. Threads where you read the agent's response normally were never affected. Background-woken threads now finish on their own and keep accepting new signals. Also fixed `consumeStream()` so that `await consumeStream()` always waits for the run to actually finish, even when consumption was already started elsewhere, and so that every caller's `onError` runs if the stream fails. - Fixed tool-level `background` config being silently ignored. The `background` option passed to `createTool` was accepted by the type system but never stored on the tool instance, so tools opted into background execution at the tool level always ran in the foreground. The option is now stored on the tool and dispatched as a background task. ([#18454](https://github.com/mastra-ai/mastra/pull/18454)) ```typescript const researchTool = createTool({ id: 'research', description: 'Run a long research job', inputSchema: z.object({ topic: z.string() }), background: { enabled: true, timeoutMs: 600_000 }, execute: async ({ topic }, context) => { // ... }, }); ``` - Fixed semantic recall not injecting recalled messages into the prompt when chatting through Studio. User messages sent through the agent signal pipeline (used by the Studio playground) were stored as signal rows and skipped when extracting the recall query, so recalled context never reached the model even though retrieval worked and the same request succeeded via the HTTP API. Fixes [#17797](https://github.com/mastra-ai/mastra/issues/17797) ([#17818](https://github.com/mastra-ai/mastra/pull/17818)) - Fixed durable agents (`createDurableAgent`) silently dropping stored working memory from the prompt. Working memory was saved correctly but never injected back, so per-request preferences had no effect on output. Durable preparation now establishes the memory context before resolving input processors so the working-memory injector is included. ([#18411](https://github.com/mastra-ai/mastra/pull/18411)) - Fixed conditional workflows so that re-running or rehydrating a run (time travel) no longer leaves the wrong branch marked as active. When a paused or replayed run lands on a conditional, arms whose condition does not evaluate truthy are now correctly recorded as skipped instead of staying stuck in a running state. ([#18228](https://github.com/mastra-ai/mastra/pull/18228)) The server workflow and schedule run-status response schemas now include the `'skipped'` status so they stay in sync with core's `WorkflowRunStatus`. - Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. ([#17142](https://github.com/mastra-ai/mastra/pull/17142)) - Removed experimental flag from retrieval-mode observational memory. The retrieval API is now stable. ([#18324](https://github.com/mastra-ai/mastra/pull/18324)) - Updated dependencies [[`d5fa3cd`](https://github.com/mastra-ai/mastra/commit/d5fa3cda1788c3cb93a361a3c6ec47de6ba21e98)]: - @mastra/schema-compat@1.3.1 ## 1.47.0-alpha.7 ### Patch Changes - Reworked plan mode to use named plan files. The agent writes its plan to a markdown file under `.mastracode/plans/` and calls `submit_plan` with the `path` to that file, so multiple plans stay on disk for review. The host validates the submitted path is a `.md` file inside `.mastracode/plans/` before reading it, so the tool can't be pointed at arbitrary files. Plan mode can write any `.md` file inside `.mastracode/plans/` (enforced by a tool guard) but nothing else, submitted plan snapshots are persisted for history replay, revision diffs are computed from a real line-ending-normalized LCS diff and only shown when the change is small relative to the plan, "Request Changes" stops the run immediately with no trailing model output, and diffs only compare revisions of the same plan file. ([#18490](https://github.com/mastra-ai/mastra/pull/18490)) ## 1.47.0-alpha.6 ### Minor Changes - Finished closing the gap between `DurableAgent` and `Agent`. After this change the durable agent surface mirrors the in-process agent's stream, resume, and generate APIs. ([#18508](https://github.com/mastra-ai/mastra/pull/18508)) **What's new** - `abortSignal` on `stream()` and `resume()`, plus `result.abort()` on `stream()`, `resume()`, and `observe()`. `result.abort()` on `streamUntilIdle()` fans out to every inner run. - `untilIdle` on `resume()` (it already existed on `stream()`), using the shared `runWithIdleWrapper` so both paths drive the same idle loop. - `DurableAgent.generate()` and `DurableAgent.resumeGenerate()` wrap `stream()` / `resume()` and resolve a `FullOutput` even when the run suspends mid-flight. - `delegation` callbacks (`onDelegationStart`, `onDelegationComplete`, `messageFilter`) are forwarded to `convertTools` at prepare time and baked into sub-agent tool wrappers. - Per-call `clientTools` and `toolsets` survive in-process resume via the run registry (cross-process resume still falls back to the agent's static tools). - Scorers configured on the wrapped agent or passed per call now actually execute under durable runs and emit `ON_SCORER_RUN` payloads matching the non-durable shape. - `AGENT_RUN` spans now carry `conversationId`, `instructions`, `resolvedVersionId`, `entityVersionId`, and the agent's `tracingPolicy`. Resume spans use `'agent run: (resumed)'` and include `resumedFromSpanId`. **Example** ```ts import { createDurableAgent } from '@mastra/core/agent/durable'; const durable = createDurableAgent({ agent: myAgent }); // 1. generate() — drains a durable run to a single FullOutput const out = await durable.generate('Plan a week in Lisbon', { abortSignal: controller.signal, modelSettings: { temperature: 0.2 }, }); // 2. stream() with result.abort() — cancel mid-run const result = await durable.stream('Long research task'); setTimeout(() => result.abort(), 5_000); for await (const chunk of result.output.fullStream) { process.stdout.write(chunk.payload?.text ?? ''); } // 3. Suspend → resumeGenerate() round-trip (e.g. tool approval) const first = await durable.generate('Run the dangerous tool', { requireToolApproval: true, }); if (first.finishReason === 'suspended') { const final = await durable.resumeGenerate(first.runId!, { approved: true }); console.log(final.text); } // 4. resume({ untilIdle }) — drive a resume through to a quiescent state await durable.resume(runId, resumeData, { untilIdle: true }); ``` - Added `reasoning` option to `modelSettings` for controlling model reasoning effort level. This option accepts standardized levels ('provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh') and is effective with LanguageModelV4 (AI SDK v7) providers that support reasoning. When used with older model providers (V2/V3), the option is a no-op. ([#18500](https://github.com/mastra-ai/mastra/pull/18500)) **Usage:** ```typescript const result = await agent.stream('Solve this problem', { modelSettings: { reasoning: 'high' }, }); ``` Also upgraded the model router to support LanguageModelV4, enabling native V4 model resolution alongside existing V2 and V3 models without regressions. - Rename the `Harness` class to `AgentController` and its associated `Harness*` types to `AgentController*` (e.g. `HarnessConfig` → `AgentControllerConfig`, `HarnessMode` → `AgentControllerMode`, `HarnessEvent` → `AgentControllerEvent`). ([#18505](https://github.com/mastra-ai/mastra/pull/18505)) `@mastra/core`: A new canonical subpath `@mastra/core/agent-controller` exports the `AgentController` class plus the `AgentController*` types. The legacy `@mastra/core/harness` subpath remains available and re-exports the deprecated `Harness*` aliases, so existing `Harness` class and type usage continues to work unchanged. New code should import from `@mastra/core/agent-controller`. On `Mastra`, the hosted-controller API is exposed under agent-controller names — `getAgentController`, `getAgentControllerById`, `listAgentControllers`, and the `agentControllers` config key — while `getHarness`/`getHarnessById`/`listHarnesses` and the `harnesses` config key remain as deprecated aliases. `@mastra/server`: The controller session API is now served exclusively under `/agent-controller/...` (with `agent-controller:read` / `agent-controller:execute` permissions). The legacy `/harness/...` routes and `harness:*` permissions have been removed. List responses use the `agentControllers` key, session responses use `controllerId`, and path params use `:controllerId`. `@mastra/client-js`: `AgentController` and `AgentControllerSession` are now the canonical resource classes, with `MastraClient.getAgentController(id)` / `listAgentControllers()` targeting the `/agent-controller` routes and reading the canonical `agentControllers` / `controllerId` response keys. The deprecated `getHarness` / `listHarnesses` methods and `Harness` / `HarnessSession` classes have been removed. This is a breaking change for the recently released client. The `@mastra/core` peer dependency floor is raised to `>=1.47.0-0` so consumers must be on the release that introduces the canonical agent-controller surface. This applies to `@mastra/server`, `@mastra/deployer`, and `mastra` (CLI), and is propagated to the packages that depend on them: the deployers (`@mastra/deployer-cloud`, `@mastra/deployer-cloudflare`, `@mastra/deployer-netlify`, `@mastra/deployer-vercel`), the server adapters (`@mastra/express`, `@mastra/fastify`, `@mastra/hono`, `@mastra/koa`, `@mastra/nestjs`, `@mastra/next`, `@mastra/tanstack-start`), and `@mastra/temporal`. ### Patch Changes - feat(playground): render ask_user tool as interactive question UI in Studio ([#18374](https://github.com/mastra-ai/mastra/pull/18374)) Adds an `AskUserBadge` component that renders suspended `ask_user` tool calls as interactive prompts with clickable option buttons (single/multi-select) or free-text input. The user's answer is sent as `resumeData` through the existing `sendToolApproval` flow to properly resume the tool. Plumbing changes: - `sendToolApprovalBodySchema` now accepts optional `resumeData` - `agent.sendToolApproval()` passes custom `resumeData` when provided - `@mastra/client-js` and `@mastra/react` expose the new parameter - Fixed Studio failing to boot in the browser with `TypeError: os.tmpdir is not a function`. The local sandbox resolved its mount marker directory at module-load time via `os.tmpdir()`, which crashed the client bundle where `node:os` has no `tmpdir`. The directory is now resolved lazily, so importing the Agent runtime in the browser no longer throws. ([#18524](https://github.com/mastra-ai/mastra/pull/18524)) - Added an optional `handlesModel` hook to the model gateway interface so a gateway can claim bare provider model IDs (like `__GATEWAY_ANTHROPIC_MODEL_OPUS__`) it is able to authenticate, before routing falls back to the default models.dev catalog. This lets gateways that hold credentials (for example OAuth-backed logins) be selected for auth resolution and availability checks instead of being bypassed. ([#18503](https://github.com/mastra-ai/mastra/pull/18503)) - Fixed `InferToolInput`, `InferToolOutput`, `InferUITool`, and `InferUITools` so tools created with `createTool({ outputSchema: z.object(...) })` now produce a typed `{ input, output }` instead of falling through to `never`. UIMessage parts (`tool-`) and other consumers can now discriminate on the inferred input/output types without manual workaround shims. ([#17039](https://github.com/mastra-ai/mastra/pull/17039)) ```ts const echo = createTool({ id: 'echo', inputSchema: z.object({ x: z.string() }), outputSchema: z.object({ y: z.number() }), execute: async ({ x }) => ({ y: x.length }), }); // before: { input: never; output: never } // after: { input: { x: string }; output: { y: number } } type UI = InferUITool; ``` Closes the gap left by #7184. - Fixed duplicate step ID warnings in Inngest workflows. When a workflow step ran, two persistence calls with the same ID caused AUTOMATIC_PARALLEL_INDEXING warnings in Inngest logs. Steps now use unique IDs for each persistence call, eliminating the warnings. ([#17320](https://github.com/mastra-ai/mastra/pull/17320)) - Workspace skills now refresh on each skill-tool invocation, so edits to SKILL.md files (or new skills added to the workspace) are picked up between tool calls without restarting the server. Set `checkSkillFileMtime: true` on the workspace config to also detect content changes to existing SKILL.md files. Fixes [#16640](https://github.com/mastra-ai/mastra/issues/16640). ([#16678](https://github.com/mastra-ai/mastra/pull/16678)) - Fixed createTool type error when passing jsonSchema() or a Schema object from @ai-sdk/provider-utils as inputSchema — no more cast needed ([#17435](https://github.com/mastra-ai/mastra/pull/17435)) - Fixed reasoning text being lost when agent messages are persisted to memory and recalled on the next turn ([#17803](https://github.com/mastra-ai/mastra/pull/17803)) - Preserve original tool-call arguments when a tool result is persisted without its matching tool-call in the same message. Previously the arguments were saved as empty `{}`, which poisoned the model's context and caused it to emit invalid empty-argument tool calls after a few cycles. The adapter now recovers the original arguments from prior persisted messages, covering the server resume path, AG-UI hosts replaying client-tool results, and `@mastra/client-js` streaming recursion. ([#16981](https://github.com/mastra-ai/mastra/pull/16981)) Also execute every streamed client tool call emitted in the same tool-calls step before continuing, instead of only continuing with one tool result. Fixes #16017 and #15576. - Added a browser-safe `@mastra/core/utils/collect-tool-mocks` export for `collectToolMocks`. The previous `@mastra/core/evals` barrel pulls in Node-only modules (`node:crypto`), which broke bundling in browser apps such as the Studio playground. Import the helper from the new subpath in browser code: ([#18504](https://github.com/mastra-ai/mastra/pull/18504)) ```ts import { collectToolMocks } from '@mastra/core/utils/collect-tool-mocks'; ``` The `@mastra/core/evals` export still works for Node-side callers. - Updated dependencies [[`d5fa3cd`](https://github.com/mastra-ai/mastra/commit/d5fa3cda1788c3cb93a361a3c6ec47de6ba21e98)]: - @mastra/schema-compat@1.3.1-alpha.0 ## 1.47.0-alpha.5 ### Minor Changes - Added support for AI SDK v7 models (`LanguageModelV4`). You can now pass any AI SDK v7 provider model directly to an agent, alongside the existing AI SDK v4, v5, and v6 support. ([#18477](https://github.com/mastra-ai/mastra/pull/18477)) ```ts import { Agent } from '@mastra/core/agent'; import { openai } from '@ai-sdk/openai'; // AI SDK v7 const agent = new Agent({ name: 'my-agent', instructions: 'You are a helpful assistant.', model: openai('gpt-5'), }); ``` Mastra detects the model's specification version automatically, so mixing models from different AI SDK versions across your agents continues to work without any extra configuration. ### Patch Changes - Added `DatasetItemPayload`, a new exported type describing the user-supplied fields of a dataset item. ([#18489](https://github.com/mastra-ai/mastra/pull/18489)) Dataset item inputs (`AddDatasetItemInput`, `UpdateDatasetItemInput`, and batch insert items) now share this type, so they stay consistent automatically. This is a type-only change with no runtime behavior change. - Added `collectToolMocks` export to `@mastra/core/evals`. The helper walks a `TrajectoryStep[]` and returns `DatasetItemToolMock[]`, collecting top-level `tool_call` and `mcp_tool_call` steps in recorded order (sub-agent delegations are emitted as `matchArgs: 'ignore'`). Consumers can now derive item-level tool mocks from a hydrated trajectory directly from `@mastra/core`. ([#18488](https://github.com/mastra-ai/mastra/pull/18488)) ```ts import { collectToolMocks } from '@mastra/core/evals'; import type { Trajectory } from '@mastra/core/evals'; const mocks = collectToolMocks(trajectory.steps); ``` - Fixed dynamic workspace resolution to pass the full request context to workspace factories, allowing them to access session state through getState(), and handled async stream aborts correctly so initial streamed output is preserved. ([#18497](https://github.com/mastra-ai/mastra/pull/18497)) - Fixed a thread becoming permanently unresponsive after a signal woke it without anyone reading the agent's response. ([#18493](https://github.com/mastra-ai/mastra/pull/18493)) When a signal woke a thread in the background and nothing consumed the resulting run, the run never finished and the thread stayed busy forever. Any further signals sent to that thread were then absorbed by the stuck run instead of starting a new one, so the agent stopped responding on that thread. Threads where you read the agent's response normally were never affected. Background-woken threads now finish on their own and keep accepting new signals. Also fixed `consumeStream()` so that `await consumeStream()` always waits for the run to actually finish, even when consumption was already started elsewhere, and so that every caller's `onError` runs if the stream fails. ## 1.47.0-alpha.4 ### Minor Changes - Moved workspace and browser ownership from the Harness to individual sessions. `createSession` now accepts optional `workspace` and `browser` overrides that are passed directly to the `Session` constructor. When no override is provided, the Harness-level config is used as a fallback. ([#18467](https://github.com/mastra-ai/mastra/pull/18467)) **Breaking changes:** - `Session` constructor now requires `workspace: Workspace` and accepts optional `browser?: MastraBrowser` - `HarnessConfig.workspace` no longer accepts a `WorkspaceConfig` object — pass a `Workspace` instance or a dynamic factory function - `createSession` overrides accept static `Workspace` / `MastraBrowser` instances only (not `DynamicArgument`) - Removed `Harness.destroyWorkspace()` — workspace lifecycle is now driven by per-session `workspace_status_changed`, `workspace_ready`, and `workspace_error` events emitted after `workspace.init()` completes - `Harness.getWorkspace()` returns the static workspace instance only (returns `undefined` for dynamic factory configs); use `resolveWorkspace({ session })` to resolve and cache a factory outside the request flow - `Harness.resolveWorkspace()` now requires a `session` parameter to resolve dynamic factories against the session's request context - The `SessionBus` replays the last workspace lifecycle event group to subscribers that attach after session creation, so late listeners always learn the current workspace status **Before:** ```ts const harness = new Harness({ name: 'my-harness', workspace: { name: 'my-workspace' }, // WorkspaceConfig shorthand }); const session = await harness.createSession({ resourceId: 'user-1', ownerId: 'owner-1', id: 'session-1', }); // workspace resolved lazily by the harness const ws = harness.getWorkspace(); ``` **After:** ```ts import { Workspace } from '@mastra/core'; const harness = new Harness({ name: 'my-harness', workspace: new Workspace({ name: 'my-workspace' }), }); // workspace can be overridden per session with a static instance const session = await harness.createSession({ resourceId: 'user-1', ownerId: 'owner-1', id: 'session-1', workspace: new Workspace({ name: 'ws-1' }), }); // for dynamic per-request workspaces, use the harness-level config const harness2 = new Harness({ name: 'my-harness', workspace: ({ requestContext, mastra }) => new Workspace({ name: `ws-${requestContext.session.id}` }), }); ``` - Surface step `providerMetadata` to output-step processors ([#18432](https://github.com/mastra-ai/mastra/pull/18432)) Output-step processors now receive the finishing step's provider-specific metadata through the new optional `providerMetadata` field on `ProcessOutputStepArgs`. This lets observability and guardrail processors attribute a model response to the provider data behind it — most notably an AWS Bedrock guardrail trace on a `content-filter` block, where the completed-steps array is empty and the assessment was previously unreachable. The field is present whenever the underlying model step produced provider metadata, in both streaming and non-streaming generation. Behaviour is unchanged for steps without provider metadata. ```ts class MyProcessor implements Processor { async processOutputStep({ finishReason, providerMetadata }: ProcessOutputStepArgs) { if (finishReason === 'content-filter') { const guardrail = providerMetadata?.bedrock?.trace?.guardrail; // attribute the block to the responsible policy/topic/filter } } } ``` ### Patch Changes - Fixed duplicate stream events when attaching to an in-progress durable or evented agent run. ([#18191](https://github.com/mastra-ai/mastra/pull/18191)) When you call `agent.observe(runId, { offset })` (or reconnect to a stream with replay) while `agent.stream()` is still running, the buffered portion of `output.textStream` could be delivered twice before settling into normal single delivery. Late observers now receive each text-delta exactly once. **Why:** Two issues in the resumable-stream path could double events. - If you passed a caching pubsub to `new Mastra({ pubsub })` (e.g. `withCaching(...)`), the agent adopted it as its inner transport and then wrapped it again in a second caching layer sharing the same cache. Every event was stored twice, so replay delivered the buffered prefix doubled. The agent now reuses the existing caching pubsub instead of double-wrapping it. ```ts // This setup no longer double-caches: const cache = new InMemoryServerCache(); const mastra = new Mastra({ pubsub: withCaching(new EventEmitterPubSub(), cache), cache }); ``` - Replay/live deduplication keyed on `event.id`, but the underlying pubsub regenerates `id` on publish, so the cached copy and the live copy of the same event carried different ids and dedup never matched. Deduplication now keys on the event's stable sequential index, which is preserved across both paths. ## 1.47.0-alpha.3 ### Minor Changes - Brought `DurableAgent.stream()` to parity with `Agent.stream()` for the full `AgentExecutionOptions` surface, so the same call works on both. ([#18461](https://github.com/mastra-ai/mastra/pull/18461)) **What's new** `DurableAgent.stream()` now honors these options that were previously dropped or silently coerced: - Full `modelSettings` (was: only `temperature`) — `maxOutputTokens`, `topP`, `topK`, `presencePenalty`, `frequencyPenalty`, `stopSequences`, `seed`, `headers` - `stopWhen`, `prepareStep`, `isTaskComplete`, `transform` - Per-call `instructions` and `system` - `disableBackgroundTasks`, `tracingOptions`, `actor` - Function-form `requireToolApproval(toolName, args, …)` (was: coerced to "approve all" — now evaluated per tool call) - New callbacks: `onAbort` and `onIterationComplete` (joining the existing `onChunk` / `onStepFinish` / `onFinish` / `onError` / `onSuspended` bridge) **Example** The same options that work on `Agent.stream()` now work on `DurableAgent.stream()`: ```ts const durable = createDurableAgent({ agent, pubsub }); await durable.stream('Plan the trip', { modelSettings: { temperature: 0.2, maxOutputTokens: 500, topP: 0.9 }, stopWhen: ({ steps }) => steps.length >= 3, prepareStep: ({ stepNumber }) => ({ activeTools: stepNumber === 0 ? ['search'] : ['book'], }), requireToolApproval: ({ toolName }) => toolName === 'book', onIterationComplete: ({ iteration }) => console.log('iter', iteration), }); ``` - Added an optional `availableTools` allowlist to `HarnessModeBase` so each mode can declare one unified list of visible tool names. When set, the harness resolves `activeTools` at LLM-call time and hides any tool not in the list — including workspace tools, which are matched by their exposed names. Per-tool and category `deny` permission rules still take precedence over the allowlist. `undefined` means no mode-level restriction (existing behavior). This moves mode-based tool visibility out of workspace construction and into a single, serializable contract. ([#18463](https://github.com/mastra-ai/mastra/pull/18463)) **Example** Declare a mode that only exposes read and plan-writing tools: ```ts import type { HarnessMode } from '@mastra/core/harness'; const planMode: HarnessMode = { id: 'plan', // Only these tools are visible to the model in plan mode. // Workspace tools are matched by their exposed (renamed) names. availableTools: ['view', 'find_files', 'search_content', 'write_file', 'submit_plan'], // ...other mode config }; // Omitting availableTools (or setting undefined) leaves all tools visible. // Set to [] to hide every tool for this mode. ``` - Scope harness session creation with tags so sessions sharing a resourceId can ([#18446](https://github.com/mastra-ai/mastra/pull/18446)) each resume their own thread. `harness.createSession()` now accepts an optional `tags` record. The tags are (a) seeded into the new session's state, (b) stamped onto every thread the session creates (so thread listings can be filtered back to the session's scope), and (c) used to filter initial thread selection: a thread is a resume candidate only when its metadata matches every provided tag. Previously, initial thread selection only consulted the harness-global `initialState.projectPath`; on a multi-session server (where one Harness serves many scopes) a session could resume the most recently updated thread from a _different_ scope that shared the resourceId. Using a generic tag record (e.g. `{ projectPath }`) keeps room for future scoping dimensions without further API changes. The `@mastra/client-js` `HarnessSession.create()` method accepts `{ tags }`, and the `POST /harness/:id/sessions` route accepts a `tags` body field. ```ts // before: initial thread chosen by resourceId only const session = await harness.createSession({ resourceId, id, ownerId }); // after: initial thread scoped to this worktree via a tag const session = await harness.createSession({ resourceId, id, ownerId, tags: { projectPath: '/repo/worktree-a' }, }); ``` ### Patch Changes - Fixed the in-memory workflow store resetting a run's `createdAt` on every re-persist, so it now matches the persistent stores. Re-persisting an existing run preserves the original `createdAt` and only advances `updatedAt`. ([#18004](https://github.com/mastra-ai/mastra/pull/18004)) - Fixed images from tool results being dropped when using AI SDK v6 (`@ai-sdk/*` v3) providers. Tools that return image data via `toModelOutput` (for example screenshots) now reach the model correctly instead of arriving empty. ([#18396](https://github.com/mastra-ai/mastra/pull/18396)) - Fixed tool-level `background` config being silently ignored. The `background` option passed to `createTool` was accepted by the type system but never stored on the tool instance, so tools opted into background execution at the tool level always ran in the foreground. The option is now stored on the tool and dispatched as a background task. ([#18454](https://github.com/mastra-ai/mastra/pull/18454)) ```typescript const researchTool = createTool({ id: 'research', description: 'Run a long research job', inputSchema: z.object({ topic: z.string() }), background: { enabled: true, timeoutMs: 600_000 }, execute: async ({ topic }, context) => { // ... }, }); ``` - Fixed durable agents (`createDurableAgent`) silently dropping stored working memory from the prompt. Working memory was saved correctly but never injected back, so per-request preferences had no effect on output. Durable preparation now establishes the memory context before resolving input processors so the working-memory injector is included. ([#18411](https://github.com/mastra-ai/mastra/pull/18411)) - Fixed conditional workflows so that re-running or rehydrating a run (time travel) no longer leaves the wrong branch marked as active. When a paused or replayed run lands on a conditional, arms whose condition does not evaluate truthy are now correctly recorded as skipped instead of staying stuck in a running state. ([#18228](https://github.com/mastra-ai/mastra/pull/18228)) The server workflow and schedule run-status response schemas now include the `'skipped'` status so they stay in sync with core's `WorkflowRunStatus`. - Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. ([#17142](https://github.com/mastra-ai/mastra/pull/17142)) ## 1.47.0-alpha.2 ### Minor Changes - Resolve all Harness models through gateways. The Harness now builds its available-models catalog, model auth status, mode agents, Observational Memory models, and subagents from the gateways you register, instead of the separate `resolveModel`, `customModelCatalogProvider`, and `modelAuthChecker` config hooks. Removed those three options from `HarnessConfig` (and the `ModelAuthChecker` type) — register a gateway via `gateways` instead. ([#18382](https://github.com/mastra-ai/mastra/pull/18382)) `listAvailableModels()` and `getCurrentModelAuthStatus()` are now sourced entirely from gateways: model discovery comes from each gateway's `fetchProviders()` (a network call for gateways like models.dev and Netlify), and auth status is resolved through the same gateway chain the model router uses at run time (`resolveAuth()`, falling back to `getApiKey()`). There is no static provider-registry fallback — everything the model picker shows comes from a gateway. The gateway-chain operations shared by `ModelRouterLanguageModel` and the Harness (gateway merging, model→gateway selection, auth resolution, provider/model listing) are now centralized in a new `GatewayManager` class exported from `@mastra/core`. Both consumers delegate to it, eliminating duplicated logic. `defaultGateways` is still re-exported from the same paths for backward compatibility. - **Gates and verdict for `runEvals`** ([#18394](https://github.com/mastra-ai/mastra/pull/18394)) New optional `gates` field accepts scorers that must score 1.0 for the run to pass. Scorers can now use a `{ scorer, threshold }` form to set pass/fail thresholds. `threshold` accepts a number (minimum) or `{ min, max }` for range-based checks (e.g. hallucination where high = bad). The result includes `verdict`, `gateResults`, and `thresholdResults`. Fully backward compatible. ```ts import { runEvals } from '@mastra/core/evals'; import { checks } from '@mastra/evals/checks'; const result = await runEvals({ data: [{ input: 'What is the weather?' }], target: weatherAgent, gates: [checks.calledTool('get_weather')], scorers: [ { scorer: faithfulnessScorer, threshold: 0.7 }, { scorer: hallucinationScorer, threshold: { max: 0.3 } }, ], }); result.verdict; // 'passed' | 'scored' | 'failed' ``` - Added `waitUntil` support for channels so background agent runs survive serverless responses. ([#17832](https://github.com/mastra-ai/mastra/pull/17832)) Without `waitUntil`, the runtime freezes the invocation as soon as the webhook response returns, killing the agent run mid-flight and leaving the user with no reply. **Auto-detected (no config needed):** - **Cloudflare Workers** — reads `c.executionCtx.waitUntil` - **Netlify Functions** — reads `c.env.context.waitUntil` **Requires explicit config:** Vercel needs a `waitUntil` function passed directly because it exposes `waitUntil` via AsyncLocalStorage, not the request context. AWS Lambda doesn't need `waitUntil` — it waits for the event loop to drain naturally. ```ts import { waitUntil } from '@vercel/functions'; import { SlackProvider } from '@mastra/slack'; new SlackProvider({ waitUntil }); ``` ```ts new Agent({ channels: { adapters: { slack: createSlackAdapter({ ... }) }, waitUntil, }, }); ``` For runtimes where `waitUntil` lives on the request context but isn't covered by the default, pass `resolveWaitUntil: (c) => fn | undefined` instead. ```ts new Agent({ channels: { adapters: { slack: createSlackAdapter({ ... }) }, resolveWaitUntil: c => c.var.runtime?.waitUntil, }, }); ``` Resolution order: `waitUntil` → `resolveWaitUntil(c)` → core default. ### Patch Changes - Fixed channel rendering on serverless runtimes (Vercel, AWS Lambda, Cloudflare Workers, Netlify). ([#17832](https://github.com/mastra-ai/mastra/pull/17832)) **No more duplicate replies.** On serverless, the same webhook event can be processed by multiple concurrent invocations. Previously each one rendered the agent reply independently, producing duplicate messages. Now only the invocation that wins the run lease renders — losers resolve cleanly without posting. **Runs no longer get killed mid-flight.** The channel handler now keeps the serverless invocation alive until the agent run finishes, instead of returning immediately and relying on a background subscription that gets frozen. **Multi-step replies fully render.** Agents that make multiple LLM calls per run (e.g. tool use → follow-up) now render every step's text instead of only the first. **Tool approval buttons now appear on all rendering paths.** Previously, tool approval prompts (approve/deny buttons) could fail to render when using output processors. Fixed by routing `tool-call-approval` chunks through the output processor pipeline. **Tool approval and decline use the same rendering path as regular messages.** Approval and decline actions now flow through the per-run output processor instead of a separate subscription-based path, making rendering consistent across all message types. **User output processors now run before channel rendering.** User-configured output processors (e.g. PII redaction, translation) now transform chunks before the channel renders them to the platform, instead of after. **Concurrency strategy switched to `concurrent`.** The chat-sdk queue lock could get stuck in frozen serverless invocations. Ordering is now handled by the signals/lease layer, removing the stale-lock failure mode. - Update provider registry and model documentation with latest models and providers ([`7c9dd77`](https://github.com/mastra-ai/mastra/commit/7c9dd77bd18cb8dc72797e25f1a0fbdc71a11347)) - Plan rejection now returns distinct tool results for inline-feedback vs. chat-based revision paths, enabling the TUI to stop the agent and wait for user chat messages. ([#18323](https://github.com/mastra-ai/mastra/pull/18323)) - Fixed thread auto-resume selecting the wrong thread in git worktrees by scoping startup selection to threads tagged with the current project path. When Mastra Code detects a matching project thread on a different resource after resourceId drift, it now prompts before cloning and resuming that thread under the current resource; accepting the prompt leaves the old thread untouched and loads the clone, while declining starts fresh and leaves the old resource untouched. ([#18333](https://github.com/mastra-ai/mastra/pull/18333)) - Fixed semantic recall not injecting recalled messages into the prompt when chatting through Studio. User messages sent through the agent signal pipeline (used by the Studio playground) were stored as signal rows and skipped when extracting the recall query, so recalled context never reached the model even though retrieval worked and the same request succeeded via the HTTP API. Fixes [#17797](https://github.com/mastra-ai/mastra/issues/17797) ([#17818](https://github.com/mastra-ai/mastra/pull/17818)) ## 1.46.1-alpha.1 ### Patch Changes - Forward the parent `abortSignal` to delegated subagents so that aborting a supervisor's `stream()` or `generate()` call cancels in-flight subagents instead of letting them run to completion. ([#17561](https://github.com/mastra-ai/mastra/pull/17561)) Previously, calling `AbortController.abort()` on a supervisor only stopped the supervisor itself: each delegated subagent kept looping and calling its own tools and the LLM for several seconds after the abort, because the delegation tool dropped the parent's `abortSignal`. The signal is now propagated to every delegation path (`stream`, `generate`, `resumeStream`, `resumeGenerate`, and the legacy variants). ```typescript const controller = new AbortController(); const stream = await supervisor.stream('Research AI trends', { abortSignal: controller.signal, }); // Now also cancels any in-flight subagents, not just the supervisor controller.abort(); ``` Fixes #14820. - Added stable session `id` and `ownerId` to Harness sessions. `SessionIdentity` now exposes `getId()` and `getOwnerId()`, and `harness.createSession()` accepts `id` and `ownerId` to set them. These identifiers are stable for the life of the session — they do not change when the resource ID is switched — and are surfaced in the harness request context session snapshot. ([#18372](https://github.com/mastra-ai/mastra/pull/18372)) **Breaking change:** `id` and `ownerId` are now **required** on both `harness.createSession()` and the `Session`/`SessionIdentity` constructors. There are no defaults. ```typescript // Before const session = new Session({ resourceId: 'my-resource' }); const session = await harness.createSession(); // After const session = new Session({ resourceId: 'my-resource', id: 'my-session-id', ownerId: 'my-owner-id', }); const session = await harness.createSession({ id: 'my-session-id', ownerId: 'my-owner-id', }); session.identity.getId(); // 'my-session-id' session.identity.getOwnerId(); // 'my-owner-id' ``` - Removed experimental flag from retrieval-mode observational memory. The retrieval API is now stable. ([#18324](https://github.com/mastra-ai/mastra/pull/18324)) ## 1.46.1-alpha.0 ### Patch Changes - Preserved response message boundaries when processor message arrays are reapplied after output step processing. ([#18337](https://github.com/mastra-ai/mastra/pull/18337)) - Address post-merge review follow-ups from the evented-workflow serialization work: ([#18385](https://github.com/mastra-ai/mastra/pull/18385)) - `Mastra` now logs a warning when the TTL sweep evicts a run-scoped workflow that was still registered, surfacing abandoned suspended runs instead of dropping them silently. - The `RegExp` codec's `// lgtm[js/regex-injection]` suppression now spells out all three structural gates (`isEnvelope` length cap + flag whitelist, local re-narrowing in `decodeRegExpEnvelope`, `try/catch` fallback) at the suppression site so future readers don't reintroduce escaping. - The `evented-unix-pubsub` scenario test now allocates its Unix socket under a short `/tmp/aim-*` directory instead of `os.tmpdir()` so it stays under the macOS 104-byte `sun_path` limit. ## 1.46.0 ### Minor Changes - Removed the deprecated `Harness.getState()` and `Harness.setState()` compatibility wrappers, along with the unused private `updateState`. Harness state has lived on the session for a while; these were thin proxies marked `@deprecated`. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const state = harness.getState(); await harness.setState({ count: 1 }); ``` **After** ```typescript const state = harness.session.state.get(); await harness.session.state.set({ count: 1 }); ``` This does not affect the tool-facing harness context, which continues to expose `state` / `getState` / `setState` / `updateState` alongside `session.state`. `mastracode` is updated to set browser settings via `session.state.set()`. - Moved the observational-memory model accessors off the Harness onto `session.om`. Reading and switching the observer/reflector models and reading observation/reflection thresholds now live on the session, next to the state they read and write. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const observer = harness.getObserverModelId(); await harness.switchObserverModel({ modelId: 'openai/gpt-4o' }); ``` **After** ```typescript const observer = harness.session.om.observer.modelId(); await harness.session.om.observer.switchModel({ modelId: 'openai/gpt-4o' }); ``` The accessors are grouped by role under `session.om.observer` and `session.om.reflector`, each exposing `modelId()`, `threshold()`, `resolvedModel()`, and `switchModel({ modelId })`. Removed `Harness.getObserverModelId`, `getReflectorModelId`, `getObservationThreshold`, `getReflectionThreshold`, `getResolvedObserverModel`, `getResolvedReflectorModel`, `switchObserverModel`, and `switchReflectorModel`. `mastracode` is updated to consume the new API: the `/om` command and status line now read and switch observer/reflector models via `session.om`. - Moved the run-control surface off the Harness onto the `Session`. Sending messages and signals, steering, following up, aborting, responding to tool suspensions, and saving system reminders now live on the session that owns the run state they drive, instead of being delegated through the Harness. This is the final step of the single-session extraction series and a prerequisite for the upcoming multi-session (`createSession`) work: every per-session operation now lives on `Session`, while the Harness retains only genuinely shared machinery (agent, config builders, storage/lock gateway), which it injects into each session via the `SessionMachinery` provider. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) **Before** ```typescript await harness.sendMessage({ content: 'hello' }); harness.sendSignal({ content: 'steer the run' }); harness.abort(); await harness.respondToToolSuspension({ toolCallId, approved: true }); ``` **After** ```typescript const session = await harness.createSession(); await session.sendMessage({ content: 'hello' }); session.sendSignal({ content: 'steer the run' }); session.abort(); await session.respondToToolSuspension({ toolCallId, approved: true }); ``` Removed `Harness.sendMessage`, `Harness.sendSignal`, `Harness.sendNotificationSignal`, `Harness.steer`, `Harness.followUp`, `Harness.abort`, `Harness.respondToToolSuspension`, `Harness.saveSystemReminderMessage`, and `Harness.waitForCurrentThreadStreamIdle`. The `Session` reaches Harness-owned machinery through the injected `SessionMachinery` provider, so the heavy run loop is still constructed and owned by the Harness while being parameterized by the session it runs on. `mastracode` is updated to consume the new API: the TUI run loop, slash-command dispatch, goal lifecycle, prompt handlers, and headless entry points all drive run-control through the session returned by `harness.createSession()`. - The Harness event bus now lives on the Session. Each `Session` owns its own listeners and emit pipeline (`session.subscribe()` / internal `session.emit()`), so events emitted on one session are delivered only to that session's subscribers — never to another session's. This is the isolation foundation for serving a single Harness to multiple concurrent sessions (e.g. one Harness backing many channel threads). ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) Breaking (Harness is under active development): `Harness.subscribe()` is removed. Subscribe on the session instead: ```diff - harness.subscribe(listener) + harness.session.subscribe(listener) ``` Session subsystems (mode/model/om/permissions/subagents/state) no longer receive an injected `emit` callback — they emit directly to their session's bus. `mastracode` is updated to subscribe via `harness.session.subscribe()`. - Add multi-tenant filtering and candidate identity to the datasets domain. ([#18314](https://github.com/mastra-ai/mastra/pull/18314)) `DatasetRecord`, `DatasetItem`, `DatasetItemRow`, `CreateDatasetInput`, and the `filters` on `ListDatasetsInput` / `ListDatasetItemsInput` now expose optional `organizationId` and `projectId`, matching the per-row tenancy contract already used by the observability domain. Dataset items inherit tenancy from their parent dataset automatically — they cannot be set per-call. `DatasetRecord` and `CreateDatasetInput` also gain two new optional identity fields, `candidateKey` and `candidateId`, for use cases that need a stable per-incident identity at the dataset level (such as auto-materialized candidate datasets). The `DatasetItemSource['type']` union now includes `'candidate-screener'` so externally-materialized items can be distinguished from user-uploaded ones. `DATASETS_SCHEMA` and `DATASET_ITEMS_SCHEMA` gain matching nullable columns, and `DatasetsInMemory` persists and filters on them. `DatasetsManager.create()` accepts the new optional fields, and `DatasetsManager.list()` accepts an optional `filters` arg that forwards to the storage layer. **Before** ```ts const dataset = await storage.createDataset({ name: 'goldens/checkout' }); const items = await storage.listDatasets({ pagination: { page: 0, perPage: 20 } }); ``` **After** ```ts const dataset = await storage.createDataset({ name: 'candidates/missing-tool-call/incident-123', organizationId: 'org_abc', projectId: 'project_xyz', candidateKey: 'missing-tool-call', candidateId: 'incident-123', }); const items = await storage.listDatasets({ pagination: { page: 0, perPage: 20 }, filters: { organizationId: 'org_abc', projectId: 'project_xyz' }, }); ``` - Added `untilIdle` option to `DurableAgent.stream()` — pass `untilIdle: true` or `{ maxIdleMs }` to keep the stream open across background-task continuations. This is the same behavior as the now-deprecated `streamUntilIdle()` method, matching the consolidation done for the non-durable Agent in #17536. ([#18349](https://github.com/mastra-ai/mastra/pull/18349)) ```ts // Before (deprecated) const result = await durableAgent.streamUntilIdle('Research topic', { memory: { thread: 't1', resource: 'u1' }, }); // After const result = await durableAgent.stream('Research topic', { untilIdle: true, memory: { thread: 't1', resource: 'u1' }, }); ``` - Replaced `Harness.switchModel()` with `harness.session.model.switch()`. Model switching now lives on the session, alongside the active mode/model state it already owns. ([#18197](https://github.com/mastra-ai/mastra/pull/18197)) **Before** ```typescript await harness.switchModel({ modelId: 'openai/gpt-5' }); ``` **After** ```typescript await harness.session.model.switch({ modelId: 'openai/gpt-5' }); ``` - Added multi-tenant scoping columns (`organizationId`, `projectId`) to the experiments domain so experiment records and per-item results inherit the tenancy bucket of their parent dataset. ([#18388](https://github.com/mastra-ai/mastra/pull/18388)) `Experiment`, `ExperimentResult`, `CreateExperimentInput`, and `AddExperimentResultInput` now carry optional `organizationId` / `projectId` fields. `ListExperimentsInput` and `ListExperimentResultsInput` gain a `filters: ExperimentTenancyFilters` block (mirrors `DatasetTenancyFilters`) for scoping queries within a `(organizationId, projectId)` bucket. Tenancy is hydrated from the parent dataset on `createExperiment` and denormalized onto each `ExperimentResult` for efficient tenancy-scoped queries. The corresponding columns are also added to the `mastra_experiments` and `mastra_experiment_results` table schemas. Existing rows backfill to `null`, matching the rest of the dataset-tenancy surface. This release also clarifies the `targetType` contract via JSDoc: - `CreateDatasetInput.targetType` remains optional. Datasets without a `TargetType` are **not experiment-eligible** — the experiment runner requires a non-null `CreateExperimentInput.targetType` to resolve an executor. - `Experiment.targetType` / `CreateExperimentInput.targetType` stay required. An experiment by definition replays inputs against a specific target. No behavior change for existing OSS-created experiments; the new fields are additive and optional. Example: ```ts // Create an experiment scoped to a tenancy bucket. When the parent dataset // already carries `organizationId` / `projectId`, `runExperiment` hydrates // these fields automatically from the dataset record. const experiment = await storage.createExperiment({ name: 'qa-regression', datasetId: 'ds_123', datasetVersion: 1, targetType: 'agent', targetId: 'agent_qa', totalItems: 10, organizationId: 'org_123', projectId: 'proj_123', }); // List experiments within a tenancy bucket. const experiments = await storage.listExperiments({ pagination: { page: 0, perPage: 20 }, filters: { organizationId: 'org_123', projectId: 'proj_123' }, }); // List per-item results within the same bucket. const results = await storage.listExperimentResults({ experimentId: experiment.id, pagination: { page: 0, perPage: 50 }, filters: { organizationId: 'org_123', projectId: 'proj_123' }, }); ``` - Replaced `Harness.switchMode()` with `harness.session.mode.switch()`. Switching modes now lives on the session, alongside the active mode/model state it already owns. ([#18197](https://github.com/mastra-ai/mastra/pull/18197)) **Before** ```typescript await harness.switchMode({ modeId: 'build' }); ``` **After** ```typescript await harness.session.mode.switch({ modeId: 'build' }); ``` `mastracode` is updated to consume the new API: the TUI and headless callers now invoke `harness.session.mode.switch()` instead of the removed `harness.switchMode()`. - Made the Harness a pure factory + shared-resource owner and removed its singleton session. The Harness no longer holds a `#session` field or exposes a `harness.session` getter; instead, callers create fully isolated sessions via `harness.createSession()`. Each session owns its own mode, model, state, thread, run-control, event bus, and stream engine, so a single Harness can now serve many concurrent sessions (e.g. one per user/thread in a server or channel adapter) without cross-session state or event leakage. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) `harness.createSession({ resourceId? })` constructs and wires a new `Session`, replays the current workspace status onto it, and selects or creates its thread before returning. Harness methods that previously read the singleton session are now parameterized by an explicit `session` argument (`setResourceId`, `getKnownResourceIds`, `getCurrentModelAuthStatus`, `loadOMProgress`, `getObservationalMemoryRecord`, `destroy`). `harness.init()` is now idempotent, so repeated calls reuse the same initialization instead of rebuilding internal state. **Before** ```typescript const harness = new Harness(config); await harness.init(); const session = harness.session; // singleton await session.sendMessage({ content: 'hello' }); ``` **After** ```typescript const harness = new Harness(config); await harness.init(); const session = await harness.createSession({ resourceId }); await session.sendMessage({ content: 'hello' }); // A second, fully isolated session from the same Harness: const other = await harness.createSession({ resourceId: otherUser }); ``` Removed `harness.session`, `harness.getSession()`, the singleton `#session` field, and the deprecated `harness.subscribe`/`harness.emit`/`harness.memory` delegators. `mastracode` is updated to consume the new API: composition roots call `createSession()` once at startup and store the result on `state.session`, and all per-session operations flow through that session object. - Register `Harness` instances on `Mastra`. ([#18355](https://github.com/mastra-ai/mastra/pull/18355)) Pass harnesses to `new Mastra({ harnesses })` (keyed like agents and workflows) and look them up with `mastra.getHarness(key)`, `mastra.getHarnessById(id)`, or `mastra.listHarnesses()`. A registered Harness shares the parent Mastra — its storage, agents, gateways, and observability — instead of building its own internal one, and is torn down with `mastra.shutdown()`. A standalone Harness is unchanged. This is the foundation for serving Harness sessions over HTTP. ```typescript const code = new Harness({ id: 'code', modes }); const mastra = new Mastra({ harnesses: { code }, storage }); mastra.getHarness('code') === code; // by registration key code.getMastra() === mastra; // shares the parent Mastra and its storage ``` - Move the thread lifecycle onto `session.thread`. Creating, switching, cloning, renaming, and deleting a thread — plus loading a thread's persisted settings and managing the agent subscription — now live on the session's thread domain (`session.thread.create/switch/clone/rename/delete/loadMetadata/ensureSubscription/detachFromCurrent`). The host's storage, thread lock, and clone primitives are injected behind an expanded `ThreadDataStore` gateway, so `SessionThread` owns the full lifecycle while the Harness owns only the DB. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) **Before** ```typescript await harness.createThread(); await harness.switchThread({ threadId }); ``` **After** ```typescript await harness.session.thread.create(); await harness.session.thread.switch({ threadId }); ``` Breaking (Harness is under development): the Harness no longer exposes `createThread`, `switchThread`, `cloneThread`, `renameThread`, `detachFromCurrentThread`, or the `memory` accessor. - Collapsed the result of `agent.sendSignal` / `sendMessage` / `queueMessage` / `sendStateSignal` / `sendNotificationSignal` into a single `accepted` promise that resolves at decision-time to a discriminated union: `{ action: 'wake'; runId; output }` when the signal started a run in this process, `{ action: 'deliver'; runId }` when it was forwarded onto an existing run, or `{ action: 'persist' }` / `{ action: 'discard' }` when nothing ran. `runId` is present only on `wake`/`deliver`; for `persist`/`discard` correlate via `result.signal.id`. `accepted` resolves for routing decisions and rejects only when the signal couldn't be routed at all (e.g. misconfigured agent). This replaces the old `accepted: true` boolean and the best-effort top-level `runId`. `result.persisted` stays top-level. `sendNotificationSignal`'s `accepted` is optional (a notification may be dropped by policy with no signal) — read `result.decision` for the policy verdict. ([#17723](https://github.com/mastra-ai/mastra/pull/17723)) On serverless platforms, when this process/Lambda is the one that woke the run (`action === 'wake'`), it can use the platform's `waitUntil` to keep itself alive until the run it started completes — otherwise the runtime may be frozen or torn down mid-run: ```ts const result = agent.sendSignal(signal, { resourceId, threadId }); ctx.waitUntil( result.accepted.then(async accepted => { if (accepted.action === 'wake') await accepted.output.consumeStream(); }), ); ``` Added a `LeaseProvider` capability (`acquireLease` / `releaseLease` / `renewLease` / `getLeaseOwner` / `transferLease`) — distributed leasing kept separate from event delivery (`PubSub`) — so processes racing to wake the same thread coordinate a single owner. The winner runs the stream; losers forward their signal to it. `EventEmitterPubSub` leases in-memory; `RedisStreamsPubSub` uses `SET NX PX` with owner-verified Lua scripts for release, renew, and transfer; the default `NoopLeaseProvider` always wins, preserving single-process behavior. Fixed a cross-process race where, after a run finished, the thread's lease briefly went free before its queued follow-up work started — letting another process win the lease and start a competing run for the same thread. The owner now keeps the lease until all queued work is drained. The lease TTL and renewal interval are overridable for tests via `MASTRA_AGENT_THREAD_LEASE_TTL_MS` and `MASTRA_AGENT_THREAD_LEASE_RENEW_INTERVAL_MS` (production defaults unchanged). - Made `harness.createSession()` get-or-create by resourceId and added `harness.getSessionByResource()` so notification delivery runs as the session that owns the target thread. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) A resourceId now maps to exactly one durable session per Harness: calling `createSession({ resourceId })` twice returns the same session, so a user/thread always resumes their own session and the in-flight creation is shared by concurrent callers. This is the multi-session behavior a long-running / multiplayer server needs — work can be driven on a thread whether or not a human is currently attached, and it runs with that thread's own model/mode/state instead of an arbitrary session. **Before** ```typescript // createSession was a pure factory: two calls for the same resource produced // two independent sessions, and notification delivery had no way to find // "the session that owns this resource". const a = await harness.createSession({ resourceId: 'user-a' }); const b = await harness.createSession({ resourceId: 'user-a' }); // different object ``` **After** ```typescript const a = await harness.createSession({ resourceId: 'user-a' }); const b = await harness.createSession({ resourceId: 'user-a' }); // same session as a const session = await harness.getSessionByResource('user-a'); // === a ``` - Added agent-level skills: attach skills directly to an Agent without a Workspace via `createSkill()` and the new `skills` config property. ([#18360](https://github.com/mastra-ai/mastra/pull/18360)) **New `skills` property on Agent config** ```typescript import { Agent } from '@mastra/core/agent'; import { createSkill } from '@mastra/core/skills'; const agent = new Agent({ id: 'reviewer', model: openai('gpt-4o'), instructions: 'You are a code review assistant.', skills: [ './skills/review', // filesystem path createSkill({ // inline — no filesystem needed name: 'release-checklist', description: 'Use when preparing a release.', instructions: '## Release Checklist\n1. Run tests...', }), ], }); ``` **Key features:** - `createSkill()` factory for code-defined skills with validation - Filesystem paths and inline skills can be mixed in the same array - Dynamic skill resolution via function: `skills: (ctx) => [...]` - When both `skills` and `workspace.skills` exist, they merge (agent-level wins on conflicts) - `agent.getSkill(name)` and `agent.listSkills()` public API for programmatic access - New `@mastra/core/skills` export path - Moved the tool-permission rule accessors off the Harness onto `session.permissions`. Reading and writing the persisted per-category / per-tool approval policies now lives on the session, next to the state it reads and writes. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const rules = harness.getPermissionRules(); harness.setPermissionForCategory({ category: 'execute', policy: 'ask' }); harness.setPermissionForTool({ toolName: 'dangerous_tool', policy: 'deny' }); ``` **After** ```typescript const rules = harness.session.permissions.getRules(); await harness.session.permissions.setForCategory({ category: 'execute', policy: 'ask' }); await harness.session.permissions.setForTool({ toolName: 'dangerous_tool', policy: 'deny' }); ``` Removed `Harness.getPermissionRules`, `Harness.setPermissionForCategory`, and `Harness.setPermissionForTool`. The setters now return a promise that resolves once the change is persisted to session state, so callers that read the rules back can await the write. Tool-category resolution stays on the harness as `harness.getToolCategory()` since it reads harness config rather than session state. `mastracode` is updated to consume the new API: the `/permissions` command reads and sets policies via `session.permissions`. - Replaced `Harness.getModelName()` and `Harness.getFullModelId()` with session accessors. The full model id is read via the existing `harness.session.model.get()`, and the short display name moves to a new `harness.session.model.displayName()`. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const name = harness.getModelName(); const fullId = harness.getFullModelId(); ``` **After** ```typescript const name = harness.session.model.displayName(); const fullId = harness.session.model.get(); ``` `mastracode` is updated to consume the new API: the TUI status line and message renderer now read the model id via `harness.session.model.get()`. - Added item-level static tool mocks so agent experiments can run deterministically without calling real, side-effecting tools. ([#18036](https://github.com/mastra-ai/mastra/pull/18036)) A dataset item can now declare `toolMocks`. When the agent calls a mocked tool with matching arguments, the experiment serves the recorded `output` instead of executing the tool. Mocks for the same `(toolName, args)` are consumed in order, so repeated calls can return different outputs. If a mocked tool is called with arguments that do not match (or the mocks are exhausted), the item fails immediately and the agent is stopped so it cannot keep calling tools after a failure. Tools without a mock still run live. ```ts await dataset.addItem({ input: { question: 'What is the weather in Seattle?' }, toolMocks: [ { toolName: 'getWeather', args: { city: 'Seattle' }, output: { temperatureF: 52 }, // 'strict' (default) deep-compares args; 'ignore' matches on tool name only, // useful for sub-agent calls where the prompt is LLM-authored. matchArgs: 'strict', }, ], }); ``` Each item result carries a `toolMockReport` describing which mocks were served, which went unconsumed, and which tools ran live, so you can see exactly how a run behaved. Items that declare `toolMocks` run their tools sequentially (`toolCallConcurrency: 1`) within that item run to guarantee ordered consumption. Items without mocks are unaffected. - Moved the subagent model accessors off the Harness onto `session.subagents.model`. Reading and setting the global or per-`agentType` subagent model now lives on the session, next to the state it reads and writes, and is grouped under `session.subagents` to leave room for future subagent settings. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const modelId = harness.getSubagentModelId({ agentType: 'explore' }); await harness.setSubagentModelId({ modelId: 'anthropic/claude-sonnet-4', agentType: 'explore' }); ``` **After** ```typescript const modelId = harness.session.subagents.model.get({ agentType: 'explore' }); await harness.session.subagents.model.set({ modelId: 'anthropic/claude-sonnet-4', agentType: 'explore' }); ``` `get()` prefers the per-`agentType` value, then the global subagent model, returning `null` when neither is set. `set()` persists to thread settings and emits a `subagent_model_changed` event. Removed `Harness.getSubagentModelId` and `Harness.setSubagentModelId`. `mastracode` is updated to consume the new API: the `/subagents` command, model-pack activation, and startup model restoration read and set subagent models via `session.subagents.model`. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`5bd72d2`](https://github.com/mastra-ai/mastra/commit/5bd72d255f45b5ea8ab342643bd463814a980a24)) - Added unit test coverage for `agent/durable/run-registry.ts` RunRegistry and ExtendedRunRegistry classes. No behaviour changes — purely additive test coverage. ([#18265](https://github.com/mastra-ai/mastra/pull/18265)) - Guard `CacheKeyGenerator.fromAIV4Part` against reasoning parts with empty/undefined `details` text. Models that emit an empty reasoning summary (Anthropic Opus 4.7/4.8 with thinking `display: omitted`, OpenAI gpt-5.x via the Responses API with no summary) persist a reasoning part shaped `{ type: 'reasoning', reasoning: '', details: [{ type: 'text' }] }` — the text detail has no `text` field. On the next turn, Observational Memory reloads that message and the cache-key generator crashed with `TypeError: Cannot read properties of undefined (reading 'length')`, killing the whole turn (`PROCESSOR_WORKFLOW_FAILED`). This is the reasoning-branch sibling of the tool-invocation guard (#16756 / #16773). The reduce now tolerates missing `details` and missing detail `text`; no behavior change for well-formed parts. Fixes #18280. ([#18281](https://github.com/mastra-ai/mastra/pull/18281)) - Introduce the `SessionMachinery` injection boundary on the Harness Session. This formalizes the narrow set of Harness-owned capabilities (resolve the current agent, build run/stream options + toolsets + request context, persist token usage, generate ids, open a thread subscription) that a Session leverages to drive an agent run. The Harness injects this machinery into each Session it constructs via `session.setMachinery(...)`. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) This is the dependency-injection foundation for making the run loop, run state, and thread stream session-owned (so one Harness can serve many concurrent sessions). No behavior change in this step — the machinery is wired but not yet consumed. - Fix notification signals not waking idle threads ([#18244](https://github.com/mastra-ai/mastra/pull/18244)) - Agents now receive rich text (markdown) from channel messages instead of stripped plain text. Links, bold, italic, code, blockquotes, and other formatting from Slack (and other platforms) are preserved as standard markdown that LLMs understand natively. Previously, a Slack message like 'Check out ' would arrive as 'Check out Example' — the URL was lost. Now it arrives as 'Check out [Example](https://example.com)'. ([#18109](https://github.com/mastra-ai/mastra/pull/18109)) - Added an optional `delayMs` retry delay to `StreamErrorRetryProcessor`. Consumers can now wait before retrying transient errors, accepting either a fixed number of milliseconds or a function evaluated with the error args. Existing default behavior is unchanged when the option is not supplied. ([#18370](https://github.com/mastra-ai/mastra/pull/18370)) ```ts import { StreamErrorRetryProcessor } from '@mastra/core/processors'; new StreamErrorRetryProcessor({ maxRetries: 2, delayMs: ({ retryCount }) => Math.min(1000 * 2 ** retryCount, 30000), matchers: [error => error?.code === 'ECONNRESET'], }); ``` - Added unit test coverage for `channels/inline-media.ts` and `workspace/tools/output-helpers.ts`. No behaviour changes — purely additive test coverage. ([#18006](https://github.com/mastra-ai/mastra/pull/18006)) - Fixed agent channel initialization errors being silently swallowed. When an agent configured with channels failed to initialize during startup, the error was discarded by an un-awaited promise, leaving the channel dead with nothing logged. Initialization failures are now caught and logged through the Mastra logger so a misconfiguration surfaces clearly. ([#17720](https://github.com/mastra-ai/mastra/pull/17720)) - Fixed an issue where publishing instruction-only or model-only overrides could remove tools from request-scoped `createDurableAgent` agents. ([#18121](https://github.com/mastra-ai/mastra/pull/18121)) Request-scoped agents now stay durable and preserve code-owned tools plus delegated behavior (model and memory). - Fix `DurableAgent.prepare()` ignoring `options.runId`. `prepare()` did not forward `runId` to `prepareForDurableExecution()` (unlike `stream()`), so it always registered a freshly minted run id. This made `prepare()` unusable for rehydrating a persisted, suspended run in a fresh process (e.g. after a server restart or registry eviction): a follow-up `resume(runId)` couldn't find the registry entry `prepare()` had built and threw `No registry entry found for run … Cannot resume.`. `prepare()` now forwards the caller-provided `runId`, so re-registering a known run id and resuming a durable snapshot across a restart works. ([#18113](https://github.com/mastra-ai/mastra/pull/18113)) - Fixed durable agent input/output processor spans orphaning when an `AGENT_RUN` root was present. Following #18083, durable runs opened an `AGENT_RUN` span but `prepareForDurableExecution` and the durable agentic-loop output-processor step still passed `{} as any` as the observability context to `runInputProcessors` / `runOutputProcessors`. Agent-level processors (including the auto-injected `MessageHistory` when memory is configured) emitted `processor_run` spans with no parent — and their inner `MEMORY_OPERATION` children were dropped entirely because the processor bails out when `currentSpan` is undefined. The `AGENT_RUN` span is now opened before input processors run and the durable workflow's output-processor step forwards its step `tracingContext` to the runner, so processor and memory-operation spans nest under `AGENT_RUN` on every durable turn. ([#18344](https://github.com/mastra-ai/mastra/pull/18344)) - Fixed a crash when the goal judge stream outlives the main agent stream. The `emitJudgeActivity` helper now uses `safeEnqueue` (try/catch guard) instead of raw `controller.enqueue()`, preventing `TypeError: Controller is already closed` when the ReadableStream controller closes before the fire-and-forget judge observer finishes draining. ([#18196](https://github.com/mastra-ai/mastra/pull/18196)) - Fixed channel handlers so background tasks finish before responses are posted. ([#16343](https://github.com/mastra-ai/mastra/pull/16343)) The channel handler was calling `agent.stream()`, which closes as soon as the model finishes generating text. Any `agent.backgroundTask()` calls scheduled during the turn were silently abandoned before they could complete. Switch the call site to use `untilIdle: true` so the channel waits for all background tasks to finish before posting the response and releasing the thread. Fixes #16163 - CompositeAuth now supports credentials-based authentication. When a credentials provider is included, `signIn`, `signUp`, and related methods are available on the composite — so Studio shows the sign-in form and the credentials endpoint responds correctly. ([#17708](https://github.com/mastra-ai/mastra/pull/17708)) - Fixed follow-up messages being lost after interrupting a stream. When a user aborted a run (e.g. Ctrl+C) and then immediately sent a new message, the follow-up never received a response. ([#18381](https://github.com/mastra-ai/mastra/pull/18381)) Two issues were addressed in the harness session: - When an aborted run terminated the subscribed-thread consumer loop, the live subscription was left attached but no longer drained. A follow-up signal would start a new run on that subscription, but its chunks were never processed. The run engine now detaches the subscription when the consumer loop breaks on abort, so the next signal re-subscribes and starts a fresh consumer. - `Session.sendSignal` could dispatch a signal onto the dying run because `abort()` clears the `AbortController` immediately while the run id and active-run id linger until `run.reset()` runs after `agent_end`. `sendSignal` now detects the post-abort window (an abort was requested but the run has not reset) and waits for the stream to fully idle before starting a new run. - Fixed tool calls running in parallel even when a tool that requires approval or can suspend was available in a step. This could let tool calls bypass an approval step when the model didn't call the approval tool that turn. ([#18275](https://github.com/mastra-ai/mastra/pull/18275)) Tool calls now run one at a time whenever an approval or suspending tool is available in a step. Parallel tool calls are still allowed when no such tool is available. - Added unit test coverage for `processors/step-schema.ts` Zod validation schemas. No behaviour changes — purely additive test coverage. ([#18264](https://github.com/mastra-ai/mastra/pull/18264)) - Fixed harness follow-up messages sent immediately after an abort so they wait for the aborted stream to finish cleaning up before starting the next run. ([#18390](https://github.com/mastra-ai/mastra/pull/18390)) - Fixed Harness runs so they no longer send a default temperature when the caller did not configure model settings. ([#18147](https://github.com/mastra-ai/mastra/pull/18147)) - Expose Harness sessions over HTTP. ([#18358](https://github.com/mastra-ai/mastra/pull/18358)) Adds a set of `harness`-scoped server routes that let a registered Harness be driven over HTTP: create (get-or-create) a session by `resourceId`, send messages, steer, abort, approve/decline tool calls, respond to tool suspensions, switch mode/model, manage threads, read session state, and subscribe to the session's event stream via SSE. Routes resolve the target Harness through `mastra.getHarness(id)` and operate on the session returned by `harness.createSession(...)`. A new `harness` permission resource is included (`harness:read`, `harness:execute`). The tool-approval route forwards the request's `toolCallId` so a stale or delayed approval can only resolve the gate it targets, and the list-models route no longer returns API key environment variable names. - Enforce resource ownership on Harness session thread operations. `SessionThread.switch`, `delete`, `listMessages`, and `clone` now verify the target thread belongs to the session's `resourceId` before acting, treating threads owned by another resource as not found. This prevents a session (e.g. an authenticated HTTP caller scoped to one `resourceId`) from reading, switching to, renaming, deleting, or cloning a thread owned by a different resource via an arbitrary `threadId`. When a `switch` is rejected for ownership, the session's previous thread lock is restored so it is never left bound but unlocked. ([#18358](https://github.com/mastra-ai/mastra/pull/18358)) - Added BDD-style AIMock scenario tests for the core agentic loop to guard against multi-step composition regressions. Covers tool-result plumbing, cross-turn message ordering, stop conditions, tool approval/resume, structured output, active-tools filtering, output processors, memory/working-memory recall, input/output processors, prepare-step overrides, workspace integration, subagent delegation, dynamic instructions, isTaskComplete gating, text-streaming fidelity, provider errors, guardrail tripwires, background tasks (tool-level, agent-level, streamUntilIdle), goals (satisfied, budget exhausted), tool-level requireApproval, conditional requireToolApproval functions, supervisor delegation hooks (onDelegationStart prompt modification and rejection, messageFilter), onIterationComplete iteration tracking with early stop, multi-tool parallel execution with concurrent tool calls, fullStream chunk ordering (text-start/text-delta/text-end, step/finish lifecycle), abort signal mid-stream halting, runtime context (requestContext) passthrough to tools, per-step input/output processors, provider metadata passthrough, model settings request body override, request-level toolsets merge with agent-level tools, tool lifecycle hooks (onInputAvailable, onOutput), tool streaming with context.writer, observability context in tool execution, structured output schema validation failures with detailed ZodError messages, multiple isTaskComplete scorers with strategy semantics (all/any), processor sequencing and transformation, maxSteps boundary conditions with multiple stopWhen OR logic, error processors with retry count tracking and custom exhaustion logic, lifecycle callbacks (onStepFinish, onFinish), incremental message persistence (savePerStep), actor identity passthrough, workflows-as-tools integration with workflow tool execution and result flow, abort during tool execution with tool-level abort signal detection and early bail, error processor retry exhaustion with retryCount incrementation and state persistence across attempts, structured output validation repair with error chunk emission and partial JSON streaming, concurrent approval requests with independent approve/decline decisions, memory thread switching with conversation isolation across threads, nested agent delegation with multi-level agent-as-tools chains, structured output aggregation from multiple tool results, memory recall windowing with lastMessages configuration, empty/no-tool turn handling, abort signal interaction with structured output streaming, requestContext isolation across multiple tool execution steps, onError callback behavior (API errors vs tool errors), maxSteps/stopWhen interaction in long tool chains, structured output error strategies (strict emits error chunk, fallback returns fallbackValue, warn logs without error chunk), requestContext mutation behavior (tool mutations do NOT persist between executions, each tool sees original values), runtime tool suspension (tools calling suspend() mid-execution with resume via resumeStream()), delegation completion hooks (onDelegationComplete with bail() stopping loop immediately), supervisor context control (includeSubAgentToolResultsInModelContext controlling nested tool result pollution), auto-resume of suspended tools (autoResumeSuspendedTools detecting and resuming on next call), manual resume via resumeStream() with custom resumeData, and non-streaming generate() approval path (approveToolCallGenerate/declineToolCallGenerate). Extended the AIMock harness with createSharedAgent() helper and sharedAgent option to enable scenarios that require shared Mastra storage across multiple agent calls. Also covers thread signals (subscribeToThread/sendMessage/sendStateSignal), signal edge cases (multiple subscribers, unsubscribe cleanup, state-signal cache dedup), and signal delivery to idle, non-subscribed threads (sendMessage still wakes a run; sendStateSignal persists without waking). The test suite now includes 179 scenarios across 83 test files. A test-quality audit additionally hardened several abort and error-processor scenarios that previously could pass regardless of loop behavior: rewrote abort-structured-output and the abort-during-tool-execution bail test to assert deterministic outcomes (finishReason and suppressed post-abort requests) instead of swallowing errors, replaced a tautological `>= 0` delegation assertion in nested-tool-calls with a real two-level delegation check, and pinned the error-processor retry-exhaustion chain to its exact call order. Each was verified by disabling the relevant loop wiring and confirming the scenario now fails. Internal test-only change with no runtime impact. ([#18276](https://github.com/mastra-ai/mastra/pull/18276)) - Exported isBadRequestError matcher for detecting transient HTTP 400 errors that can be retried ([#18384](https://github.com/mastra-ai/mastra/pull/18384)) - Make the evented workflow engine safe for agent streams that carry non-serializable runtime state. ([#17836](https://github.com/mastra-ai/mastra/pull/17836)) - Agent streams no longer drop or flatten `Date`, `Error`, `Map`, `Set`, `RegExp`, `URL`, `BigInt`, `undefined`, or registered class instances (e.g. `GeneratedFile`) when workflow events travel across the cross-process pubsub broker. - New per-run `RunScope` on `Mastra` keeps live runtime handles (message lists, memory, tools, background tasks, transports, …) off the wire entirely. The scope is keyed by `runId`, never persisted, never published, and is released when the run ends. - Migrated the agent's `prepare-stream`, `agentic-execution`, and `agentic-loop` workflow steps onto this scope. The legacy `_internal` field on `streamVNext()` options is still accepted as bootstrap input — it is hydrated into the scope once and marked `@deprecated`; no caller changes are required. Resolves intermittent `getFullOutput is not a function` and `Workflow not found` errors on multi-instance deployments running the evented workflow engine. - Fixed browser state updates to attribute click-driven navigation to the assistant when the browser click result reports the new URL. ([#18239](https://github.com/mastra-ai/mastra/pull/18239)) - Fixed subscribed thread streams so suspended tool resumes, same-run resumed streams, follow-up signals, and post-abort queued context are delivered through the authoritative subscription path without dropping or duplicating output. ([#18183](https://github.com/mastra-ai/mastra/pull/18183)) - Add AIMock scenario tests for dynamic model resolution, client tools, and tool choice ([#18276](https://github.com/mastra-ai/mastra/pull/18276)) Added 3 new BDD-style scenario tests (7 test cases) to the AIMock regression test suite: - `dynamic-model.scenario.test.ts` - Tests that model resolution functions receive `requestContext` and can select different models per-request (e.g., fast vs. smart model based on context flags) - `client-tools.scenario.test.ts` - Tests that client tools passed to `agent.stream()` merge correctly with agent-level tools, both appear in model requests, and execute successfully - `tool-choice.scenario.test.ts` - Tests that `toolChoice` option passes through to the model request with correct values for `'none'`, `'required'`, and specific tool selection (`{ type: 'tool', toolName: 'name' }`) All scenarios run against a real OpenAI provider pointed at an in-test AIMock HTTP server, providing regression coverage for tool resolution and model selection logic in the agentic loop. - Fixed parallel tool calls being serialized by unrelated suspendable tools. ([#18243](https://github.com/mastra-ai/mastra/pull/18243)) - Fixed parallel sub-agent delegations that require approval. When a supervisor agent delegated the same sub-agent twice in a single step (for example, issuing two refunds in parallel), approving them one at a time only ran the first delegation. The second failed to resume with an "AGENT_RESUME_NO_SNAPSHOT_FOUND" error, and on a page refresh the second delegation's approval was lost entirely. ([#18041](https://github.com/mastra-ai/mastra/pull/18041)) Now each delegation tracks its own suspended run, so approving both parallel delegations runs both of them, both during a live session and after reloading. Studio also resolves each delegation's suspend payload by tool call id, so parallel approvals render the correct payload per delegation. **Before** ```ts // Supervisor delegates two refunds to the billing agent in one step await supervisor.stream('Refund order A and order B in parallel.'); // Approving each one by one await supervisor.approveToolCall({ runId, toolCallId: callA }); // runs refund A await supervisor.approveToolCall({ runId, toolCallId: callB }); // error: AGENT_RESUME_NO_SNAPSHOT_FOUND, refund B never runs ``` **After** ```ts await supervisor.approveToolCall({ runId, toolCallId: callA }); // runs refund A await supervisor.approveToolCall({ runId, toolCallId: callB }); // runs refund B ``` - Fixed DurableAgent ignoring the wrapped agent's defaultOptions. When wrapping an agent with createDurableAgent, the agent's configured defaultOptions (maxSteps, providerOptions, modelSettings, etc.) were silently dropped — maxSteps fell back to the durable default of 5 and provider settings like Anthropic thinking config were never sent. DurableAgent now merges the wrapped agent's defaultOptions under each per-request call, matching Agent.stream()/generate(), and delegates getDefaultOptions() to the wrapped agent. ([#17794](https://github.com/mastra-ai/mastra/pull/17794)) Before: const base = new Agent({ model, defaultOptions: { maxSteps: 250 } }); const agent = createDurableAgent({ agent: base }); // runs capped at 5 steps, defaultOptions.providerOptions dropped After: // defaultOptions.maxSteps (250) and providerOptions are honored; per-request options still take precedence - Move the Harness agent run engine onto the Session. The stream loop that consumes an agent's event stream — folding chunks into display messages and token usage, driving tool approval/suspension, and finalizing the run — now lives in a per-session `SessionRunEngine` owned by the Session and driven through the injected `SessionMachinery`. The pure chunk→message content transforms move to a shared `stream-content` module. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) In the multi-user host the run loop, run state, and thread stream are per-session and cannot be shared, so they belong on the Session; how a run is produced (agent + config-backed builders) stays Harness-owned machinery. Behavior is unchanged: `harness.session.processStream(...)` and `session.resolveToolApproval(...)` replace the previously Harness-private equivalents. - Internal: extracted the Harness constructor's session wiring (thread-settings store, mode/model/om/permissions/subagents resolvers, thread data store, and initial mode/model seeding) into a private `#wireSession(session, defaultMode)` helper. No behavior or public API change — this is groundwork for wiring additional sessions to a single Harness. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) ## 1.46.0-alpha.5 ### Patch Changes - Exported isBadRequestError matcher for detecting transient HTTP 400 errors that can be retried ([#18384](https://github.com/mastra-ai/mastra/pull/18384)) ## 1.46.0-alpha.4 ### Minor Changes - Add multi-tenant filtering and candidate identity to the datasets domain. ([#18314](https://github.com/mastra-ai/mastra/pull/18314)) `DatasetRecord`, `DatasetItem`, `DatasetItemRow`, `CreateDatasetInput`, and the `filters` on `ListDatasetsInput` / `ListDatasetItemsInput` now expose optional `organizationId` and `projectId`, matching the per-row tenancy contract already used by the observability domain. Dataset items inherit tenancy from their parent dataset automatically — they cannot be set per-call. `DatasetRecord` and `CreateDatasetInput` also gain two new optional identity fields, `candidateKey` and `candidateId`, for use cases that need a stable per-incident identity at the dataset level (such as auto-materialized candidate datasets). The `DatasetItemSource['type']` union now includes `'candidate-screener'` so externally-materialized items can be distinguished from user-uploaded ones. `DATASETS_SCHEMA` and `DATASET_ITEMS_SCHEMA` gain matching nullable columns, and `DatasetsInMemory` persists and filters on them. `DatasetsManager.create()` accepts the new optional fields, and `DatasetsManager.list()` accepts an optional `filters` arg that forwards to the storage layer. **Before** ```ts const dataset = await storage.createDataset({ name: 'goldens/checkout' }); const items = await storage.listDatasets({ pagination: { page: 0, perPage: 20 } }); ``` **After** ```ts const dataset = await storage.createDataset({ name: 'candidates/missing-tool-call/incident-123', organizationId: 'org_abc', projectId: 'project_xyz', candidateKey: 'missing-tool-call', candidateId: 'incident-123', }); const items = await storage.listDatasets({ pagination: { page: 0, perPage: 20 }, filters: { organizationId: 'org_abc', projectId: 'project_xyz' }, }); ``` - Added multi-tenant scoping columns (`organizationId`, `projectId`) to the experiments domain so experiment records and per-item results inherit the tenancy bucket of their parent dataset. ([#18388](https://github.com/mastra-ai/mastra/pull/18388)) `Experiment`, `ExperimentResult`, `CreateExperimentInput`, and `AddExperimentResultInput` now carry optional `organizationId` / `projectId` fields. `ListExperimentsInput` and `ListExperimentResultsInput` gain a `filters: ExperimentTenancyFilters` block (mirrors `DatasetTenancyFilters`) for scoping queries within a `(organizationId, projectId)` bucket. Tenancy is hydrated from the parent dataset on `createExperiment` and denormalized onto each `ExperimentResult` for efficient tenancy-scoped queries. The corresponding columns are also added to the `mastra_experiments` and `mastra_experiment_results` table schemas. Existing rows backfill to `null`, matching the rest of the dataset-tenancy surface. This release also clarifies the `targetType` contract via JSDoc: - `CreateDatasetInput.targetType` remains optional. Datasets without a `TargetType` are **not experiment-eligible** — the experiment runner requires a non-null `CreateExperimentInput.targetType` to resolve an executor. - `Experiment.targetType` / `CreateExperimentInput.targetType` stay required. An experiment by definition replays inputs against a specific target. No behavior change for existing OSS-created experiments; the new fields are additive and optional. Example: ```ts // Create an experiment scoped to a tenancy bucket. When the parent dataset // already carries `organizationId` / `projectId`, `runExperiment` hydrates // these fields automatically from the dataset record. const experiment = await storage.createExperiment({ name: 'qa-regression', datasetId: 'ds_123', datasetVersion: 1, targetType: 'agent', targetId: 'agent_qa', totalItems: 10, organizationId: 'org_123', projectId: 'proj_123', }); // List experiments within a tenancy bucket. const experiments = await storage.listExperiments({ pagination: { page: 0, perPage: 20 }, filters: { organizationId: 'org_123', projectId: 'proj_123' }, }); // List per-item results within the same bucket. const results = await storage.listExperimentResults({ experimentId: experiment.id, pagination: { page: 0, perPage: 50 }, filters: { organizationId: 'org_123', projectId: 'proj_123' }, }); ``` - Added agent-level skills: attach skills directly to an Agent without a Workspace via `createSkill()` and the new `skills` config property. ([#18360](https://github.com/mastra-ai/mastra/pull/18360)) **New `skills` property on Agent config** ```typescript import { Agent } from '@mastra/core/agent'; import { createSkill } from '@mastra/core/skills'; const agent = new Agent({ id: 'reviewer', model: openai('gpt-4o'), instructions: 'You are a code review assistant.', skills: [ './skills/review', // filesystem path createSkill({ // inline — no filesystem needed name: 'release-checklist', description: 'Use when preparing a release.', instructions: '## Release Checklist\n1. Run tests...', }), ], }); ``` **Key features:** - `createSkill()` factory for code-defined skills with validation - Filesystem paths and inline skills can be mixed in the same array - Dynamic skill resolution via function: `skills: (ctx) => [...]` - When both `skills` and `workspace.skills` exist, they merge (agent-level wins on conflicts) - `agent.getSkill(name)` and `agent.listSkills()` public API for programmatic access - New `@mastra/core/skills` export path ### Patch Changes - Added an optional `delayMs` retry delay to `StreamErrorRetryProcessor`. Consumers can now wait before retrying transient errors, accepting either a fixed number of milliseconds or a function evaluated with the error args. Existing default behavior is unchanged when the option is not supplied. ([#18370](https://github.com/mastra-ai/mastra/pull/18370)) ```ts import { StreamErrorRetryProcessor } from '@mastra/core/processors'; new StreamErrorRetryProcessor({ maxRetries: 2, delayMs: ({ retryCount }) => Math.min(1000 * 2 ** retryCount, 30000), matchers: [error => error?.code === 'ECONNRESET'], }); ``` - Fixed follow-up messages being lost after interrupting a stream. When a user aborted a run (e.g. Ctrl+C) and then immediately sent a new message, the follow-up never received a response. ([#18381](https://github.com/mastra-ai/mastra/pull/18381)) Two issues were addressed in the harness session: - When an aborted run terminated the subscribed-thread consumer loop, the live subscription was left attached but no longer drained. A follow-up signal would start a new run on that subscription, but its chunks were never processed. The run engine now detaches the subscription when the consumer loop breaks on abort, so the next signal re-subscribes and starts a fresh consumer. - `Session.sendSignal` could dispatch a signal onto the dying run because `abort()` clears the `AbortController` immediately while the run id and active-run id linger until `run.reset()` runs after `agent_end`. `sendSignal` now detects the post-abort window (an abort was requested but the run has not reset) and waits for the stream to fully idle before starting a new run. - Fixed harness follow-up messages sent immediately after an abort so they wait for the aborted stream to finish cleaning up before starting the next run. ([#18390](https://github.com/mastra-ai/mastra/pull/18390)) - Make the evented workflow engine safe for agent streams that carry non-serializable runtime state. ([#17836](https://github.com/mastra-ai/mastra/pull/17836)) - Agent streams no longer drop or flatten `Date`, `Error`, `Map`, `Set`, `RegExp`, `URL`, `BigInt`, `undefined`, or registered class instances (e.g. `GeneratedFile`) when workflow events travel across the cross-process pubsub broker. - New per-run `RunScope` on `Mastra` keeps live runtime handles (message lists, memory, tools, background tasks, transports, …) off the wire entirely. The scope is keyed by `runId`, never persisted, never published, and is released when the run ends. - Migrated the agent's `prepare-stream`, `agentic-execution`, and `agentic-loop` workflow steps onto this scope. The legacy `_internal` field on `streamVNext()` options is still accepted as bootstrap input — it is hydrated into the scope once and marked `@deprecated`; no caller changes are required. Resolves intermittent `getFullOutput is not a function` and `Workflow not found` errors on multi-instance deployments running the evented workflow engine. - Fixed parallel sub-agent delegations that require approval. When a supervisor agent delegated the same sub-agent twice in a single step (for example, issuing two refunds in parallel), approving them one at a time only ran the first delegation. The second failed to resume with an "AGENT_RESUME_NO_SNAPSHOT_FOUND" error, and on a page refresh the second delegation's approval was lost entirely. ([#18041](https://github.com/mastra-ai/mastra/pull/18041)) Now each delegation tracks its own suspended run, so approving both parallel delegations runs both of them, both during a live session and after reloading. Studio also resolves each delegation's suspend payload by tool call id, so parallel approvals render the correct payload per delegation. **Before** ```ts // Supervisor delegates two refunds to the billing agent in one step await supervisor.stream('Refund order A and order B in parallel.'); // Approving each one by one await supervisor.approveToolCall({ runId, toolCallId: callA }); // runs refund A await supervisor.approveToolCall({ runId, toolCallId: callB }); // error: AGENT_RESUME_NO_SNAPSHOT_FOUND, refund B never runs ``` **After** ```ts await supervisor.approveToolCall({ runId, toolCallId: callA }); // runs refund A await supervisor.approveToolCall({ runId, toolCallId: callB }); // runs refund B ``` ## 1.46.0-alpha.3 ### Minor Changes - Added `untilIdle` option to `DurableAgent.stream()` — pass `untilIdle: true` or `{ maxIdleMs }` to keep the stream open across background-task continuations. This is the same behavior as the now-deprecated `streamUntilIdle()` method, matching the consolidation done for the non-durable Agent in #17536. ([#18349](https://github.com/mastra-ai/mastra/pull/18349)) ```ts // Before (deprecated) const result = await durableAgent.streamUntilIdle('Research topic', { memory: { thread: 't1', resource: 'u1' }, }); // After const result = await durableAgent.stream('Research topic', { untilIdle: true, memory: { thread: 't1', resource: 'u1' }, }); ``` - Register `Harness` instances on `Mastra`. ([#18355](https://github.com/mastra-ai/mastra/pull/18355)) Pass harnesses to `new Mastra({ harnesses })` (keyed like agents and workflows) and look them up with `mastra.getHarness(key)`, `mastra.getHarnessById(id)`, or `mastra.listHarnesses()`. A registered Harness shares the parent Mastra — its storage, agents, gateways, and observability — instead of building its own internal one, and is torn down with `mastra.shutdown()`. A standalone Harness is unchanged. This is the foundation for serving Harness sessions over HTTP. ```typescript const code = new Harness({ id: 'code', modes }); const mastra = new Mastra({ harnesses: { code }, storage }); mastra.getHarness('code') === code; // by registration key code.getMastra() === mastra; // shares the parent Mastra and its storage ``` - Added item-level static tool mocks so agent experiments can run deterministically without calling real, side-effecting tools. ([#18036](https://github.com/mastra-ai/mastra/pull/18036)) A dataset item can now declare `toolMocks`. When the agent calls a mocked tool with matching arguments, the experiment serves the recorded `output` instead of executing the tool. Mocks for the same `(toolName, args)` are consumed in order, so repeated calls can return different outputs. If a mocked tool is called with arguments that do not match (or the mocks are exhausted), the item fails immediately and the agent is stopped so it cannot keep calling tools after a failure. Tools without a mock still run live. ```ts await dataset.addItem({ input: { question: 'What is the weather in Seattle?' }, toolMocks: [ { toolName: 'getWeather', args: { city: 'Seattle' }, output: { temperatureF: 52 }, // 'strict' (default) deep-compares args; 'ignore' matches on tool name only, // useful for sub-agent calls where the prompt is LLM-authored. matchArgs: 'strict', }, ], }); ``` Each item result carries a `toolMockReport` describing which mocks were served, which went unconsumed, and which tools ran live, so you can see exactly how a run behaved. Items that declare `toolMocks` run their tools sequentially (`toolCallConcurrency: 1`) within that item run to guarantee ordered consumption. Items without mocks are unaffected. ### Patch Changes - Guard `CacheKeyGenerator.fromAIV4Part` against reasoning parts with empty/undefined `details` text. Models that emit an empty reasoning summary (Anthropic Opus 4.7/4.8 with thinking `display: omitted`, OpenAI gpt-5.x via the Responses API with no summary) persist a reasoning part shaped `{ type: 'reasoning', reasoning: '', details: [{ type: 'text' }] }` — the text detail has no `text` field. On the next turn, Observational Memory reloads that message and the cache-key generator crashed with `TypeError: Cannot read properties of undefined (reading 'length')`, killing the whole turn (`PROCESSOR_WORKFLOW_FAILED`). This is the reasoning-branch sibling of the tool-invocation guard (#16756 / #16773). The reduce now tolerates missing `details` and missing detail `text`; no behavior change for well-formed parts. Fixes #18280. ([#18281](https://github.com/mastra-ai/mastra/pull/18281)) - Added unit test coverage for `channels/inline-media.ts` and `workspace/tools/output-helpers.ts`. No behaviour changes — purely additive test coverage. ([#18006](https://github.com/mastra-ai/mastra/pull/18006)) - Fixed an issue where publishing instruction-only or model-only overrides could remove tools from request-scoped `createDurableAgent` agents. ([#18121](https://github.com/mastra-ai/mastra/pull/18121)) Request-scoped agents now stay durable and preserve code-owned tools plus delegated behavior (model and memory). - Fixed durable agent input/output processor spans orphaning when an `AGENT_RUN` root was present. Following #18083, durable runs opened an `AGENT_RUN` span but `prepareForDurableExecution` and the durable agentic-loop output-processor step still passed `{} as any` as the observability context to `runInputProcessors` / `runOutputProcessors`. Agent-level processors (including the auto-injected `MessageHistory` when memory is configured) emitted `processor_run` spans with no parent — and their inner `MEMORY_OPERATION` children were dropped entirely because the processor bails out when `currentSpan` is undefined. The `AGENT_RUN` span is now opened before input processors run and the durable workflow's output-processor step forwards its step `tracingContext` to the runner, so processor and memory-operation spans nest under `AGENT_RUN` on every durable turn. ([#18344](https://github.com/mastra-ai/mastra/pull/18344)) - Fixed channel handlers so background tasks finish before responses are posted. ([#16343](https://github.com/mastra-ai/mastra/pull/16343)) The channel handler was calling `agent.stream()`, which closes as soon as the model finishes generating text. Any `agent.backgroundTask()` calls scheduled during the turn were silently abandoned before they could complete. Switch the call site to use `untilIdle: true` so the channel waits for all background tasks to finish before posting the response and releasing the thread. Fixes #16163 - CompositeAuth now supports credentials-based authentication. When a credentials provider is included, `signIn`, `signUp`, and related methods are available on the composite — so Studio shows the sign-in form and the credentials endpoint responds correctly. ([#17708](https://github.com/mastra-ai/mastra/pull/17708)) - Expose Harness sessions over HTTP. ([#18358](https://github.com/mastra-ai/mastra/pull/18358)) Adds a set of `harness`-scoped server routes that let a registered Harness be driven over HTTP: create (get-or-create) a session by `resourceId`, send messages, steer, abort, approve/decline tool calls, respond to tool suspensions, switch mode/model, manage threads, read session state, and subscribe to the session's event stream via SSE. Routes resolve the target Harness through `mastra.getHarness(id)` and operate on the session returned by `harness.createSession(...)`. A new `harness` permission resource is included (`harness:read`, `harness:execute`). The tool-approval route forwards the request's `toolCallId` so a stale or delayed approval can only resolve the gate it targets, and the list-models route no longer returns API key environment variable names. - Enforce resource ownership on Harness session thread operations. `SessionThread.switch`, `delete`, `listMessages`, and `clone` now verify the target thread belongs to the session's `resourceId` before acting, treating threads owned by another resource as not found. This prevents a session (e.g. an authenticated HTTP caller scoped to one `resourceId`) from reading, switching to, renaming, deleting, or cloning a thread owned by a different resource via an arbitrary `threadId`. When a `switch` is rejected for ownership, the session's previous thread lock is restored so it is never left bound but unlocked. ([#18358](https://github.com/mastra-ai/mastra/pull/18358)) ## 1.46.0-alpha.2 ### Patch Changes - Fixed agent channel initialization errors being silently swallowed. When an agent configured with channels failed to initialize during startup, the error was discarded by an un-awaited promise, leaving the channel dead with nothing logged. Initialization failures are now caught and logged through the Mastra logger so a misconfiguration surfaces clearly. ([#17720](https://github.com/mastra-ai/mastra/pull/17720)) - Fixed subscribed thread streams so suspended tool resumes, same-run resumed streams, follow-up signals, and post-abort queued context are delivered through the authoritative subscription path without dropping or duplicating output. ([#18183](https://github.com/mastra-ai/mastra/pull/18183)) ## 1.46.0-alpha.1 ### Patch Changes - Fix `DurableAgent.prepare()` ignoring `options.runId`. `prepare()` did not forward `runId` to `prepareForDurableExecution()` (unlike `stream()`), so it always registered a freshly minted run id. This made `prepare()` unusable for rehydrating a persisted, suspended run in a fresh process (e.g. after a server restart or registry eviction): a follow-up `resume(runId)` couldn't find the registry entry `prepare()` had built and threw `No registry entry found for run … Cannot resume.`. `prepare()` now forwards the caller-provided `runId`, so re-registering a known run id and resuming a durable snapshot across a restart works. ([#18113](https://github.com/mastra-ai/mastra/pull/18113)) ## 1.46.0-alpha.0 ### Minor Changes - Removed the deprecated `Harness.getState()` and `Harness.setState()` compatibility wrappers, along with the unused private `updateState`. Harness state has lived on the session for a while; these were thin proxies marked `@deprecated`. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const state = harness.getState(); await harness.setState({ count: 1 }); ``` **After** ```typescript const state = harness.session.state.get(); await harness.session.state.set({ count: 1 }); ``` This does not affect the tool-facing harness context, which continues to expose `state` / `getState` / `setState` / `updateState` alongside `session.state`. `mastracode` is updated to set browser settings via `session.state.set()`. - Moved the observational-memory model accessors off the Harness onto `session.om`. Reading and switching the observer/reflector models and reading observation/reflection thresholds now live on the session, next to the state they read and write. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const observer = harness.getObserverModelId(); await harness.switchObserverModel({ modelId: 'openai/gpt-4o' }); ``` **After** ```typescript const observer = harness.session.om.observer.modelId(); await harness.session.om.observer.switchModel({ modelId: 'openai/gpt-4o' }); ``` The accessors are grouped by role under `session.om.observer` and `session.om.reflector`, each exposing `modelId()`, `threshold()`, `resolvedModel()`, and `switchModel({ modelId })`. Removed `Harness.getObserverModelId`, `getReflectorModelId`, `getObservationThreshold`, `getReflectionThreshold`, `getResolvedObserverModel`, `getResolvedReflectorModel`, `switchObserverModel`, and `switchReflectorModel`. `mastracode` is updated to consume the new API: the `/om` command and status line now read and switch observer/reflector models via `session.om`. - Moved the run-control surface off the Harness onto the `Session`. Sending messages and signals, steering, following up, aborting, responding to tool suspensions, and saving system reminders now live on the session that owns the run state they drive, instead of being delegated through the Harness. This is the final step of the single-session extraction series and a prerequisite for the upcoming multi-session (`createSession`) work: every per-session operation now lives on `Session`, while the Harness retains only genuinely shared machinery (agent, config builders, storage/lock gateway), which it injects into each session via the `SessionMachinery` provider. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) **Before** ```typescript await harness.sendMessage({ content: 'hello' }); harness.sendSignal({ content: 'steer the run' }); harness.abort(); await harness.respondToToolSuspension({ toolCallId, approved: true }); ``` **After** ```typescript const session = await harness.createSession(); await session.sendMessage({ content: 'hello' }); session.sendSignal({ content: 'steer the run' }); session.abort(); await session.respondToToolSuspension({ toolCallId, approved: true }); ``` Removed `Harness.sendMessage`, `Harness.sendSignal`, `Harness.sendNotificationSignal`, `Harness.steer`, `Harness.followUp`, `Harness.abort`, `Harness.respondToToolSuspension`, `Harness.saveSystemReminderMessage`, and `Harness.waitForCurrentThreadStreamIdle`. The `Session` reaches Harness-owned machinery through the injected `SessionMachinery` provider, so the heavy run loop is still constructed and owned by the Harness while being parameterized by the session it runs on. `mastracode` is updated to consume the new API: the TUI run loop, slash-command dispatch, goal lifecycle, prompt handlers, and headless entry points all drive run-control through the session returned by `harness.createSession()`. - The Harness event bus now lives on the Session. Each `Session` owns its own listeners and emit pipeline (`session.subscribe()` / internal `session.emit()`), so events emitted on one session are delivered only to that session's subscribers — never to another session's. This is the isolation foundation for serving a single Harness to multiple concurrent sessions (e.g. one Harness backing many channel threads). ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) Breaking (Harness is under active development): `Harness.subscribe()` is removed. Subscribe on the session instead: ```diff - harness.subscribe(listener) + harness.session.subscribe(listener) ``` Session subsystems (mode/model/om/permissions/subagents/state) no longer receive an injected `emit` callback — they emit directly to their session's bus. `mastracode` is updated to subscribe via `harness.session.subscribe()`. - Replaced `Harness.switchModel()` with `harness.session.model.switch()`. Model switching now lives on the session, alongside the active mode/model state it already owns. ([#18197](https://github.com/mastra-ai/mastra/pull/18197)) **Before** ```typescript await harness.switchModel({ modelId: 'openai/gpt-5' }); ``` **After** ```typescript await harness.session.model.switch({ modelId: 'openai/gpt-5' }); ``` - Replaced `Harness.switchMode()` with `harness.session.mode.switch()`. Switching modes now lives on the session, alongside the active mode/model state it already owns. ([#18197](https://github.com/mastra-ai/mastra/pull/18197)) **Before** ```typescript await harness.switchMode({ modeId: 'build' }); ``` **After** ```typescript await harness.session.mode.switch({ modeId: 'build' }); ``` `mastracode` is updated to consume the new API: the TUI and headless callers now invoke `harness.session.mode.switch()` instead of the removed `harness.switchMode()`. - Made the Harness a pure factory + shared-resource owner and removed its singleton session. The Harness no longer holds a `#session` field or exposes a `harness.session` getter; instead, callers create fully isolated sessions via `harness.createSession()`. Each session owns its own mode, model, state, thread, run-control, event bus, and stream engine, so a single Harness can now serve many concurrent sessions (e.g. one per user/thread in a server or channel adapter) without cross-session state or event leakage. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) `harness.createSession({ resourceId? })` constructs and wires a new `Session`, replays the current workspace status onto it, and selects or creates its thread before returning. Harness methods that previously read the singleton session are now parameterized by an explicit `session` argument (`setResourceId`, `getKnownResourceIds`, `getCurrentModelAuthStatus`, `loadOMProgress`, `getObservationalMemoryRecord`, `destroy`). `harness.init()` is now idempotent, so repeated calls reuse the same initialization instead of rebuilding internal state. **Before** ```typescript const harness = new Harness(config); await harness.init(); const session = harness.session; // singleton await session.sendMessage({ content: 'hello' }); ``` **After** ```typescript const harness = new Harness(config); await harness.init(); const session = await harness.createSession({ resourceId }); await session.sendMessage({ content: 'hello' }); // A second, fully isolated session from the same Harness: const other = await harness.createSession({ resourceId: otherUser }); ``` Removed `harness.session`, `harness.getSession()`, the singleton `#session` field, and the deprecated `harness.subscribe`/`harness.emit`/`harness.memory` delegators. `mastracode` is updated to consume the new API: composition roots call `createSession()` once at startup and store the result on `state.session`, and all per-session operations flow through that session object. - Move the thread lifecycle onto `session.thread`. Creating, switching, cloning, renaming, and deleting a thread — plus loading a thread's persisted settings and managing the agent subscription — now live on the session's thread domain (`session.thread.create/switch/clone/rename/delete/loadMetadata/ensureSubscription/detachFromCurrent`). The host's storage, thread lock, and clone primitives are injected behind an expanded `ThreadDataStore` gateway, so `SessionThread` owns the full lifecycle while the Harness owns only the DB. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) **Before** ```typescript await harness.createThread(); await harness.switchThread({ threadId }); ``` **After** ```typescript await harness.session.thread.create(); await harness.session.thread.switch({ threadId }); ``` Breaking (Harness is under development): the Harness no longer exposes `createThread`, `switchThread`, `cloneThread`, `renameThread`, `detachFromCurrentThread`, or the `memory` accessor. - Collapsed the result of `agent.sendSignal` / `sendMessage` / `queueMessage` / `sendStateSignal` / `sendNotificationSignal` into a single `accepted` promise that resolves at decision-time to a discriminated union: `{ action: 'wake'; runId; output }` when the signal started a run in this process, `{ action: 'deliver'; runId }` when it was forwarded onto an existing run, or `{ action: 'persist' }` / `{ action: 'discard' }` when nothing ran. `runId` is present only on `wake`/`deliver`; for `persist`/`discard` correlate via `result.signal.id`. `accepted` resolves for routing decisions and rejects only when the signal couldn't be routed at all (e.g. misconfigured agent). This replaces the old `accepted: true` boolean and the best-effort top-level `runId`. `result.persisted` stays top-level. `sendNotificationSignal`'s `accepted` is optional (a notification may be dropped by policy with no signal) — read `result.decision` for the policy verdict. ([#17723](https://github.com/mastra-ai/mastra/pull/17723)) On serverless platforms, when this process/Lambda is the one that woke the run (`action === 'wake'`), it can use the platform's `waitUntil` to keep itself alive until the run it started completes — otherwise the runtime may be frozen or torn down mid-run: ```ts const result = agent.sendSignal(signal, { resourceId, threadId }); ctx.waitUntil( result.accepted.then(async accepted => { if (accepted.action === 'wake') await accepted.output.consumeStream(); }), ); ``` Added a `LeaseProvider` capability (`acquireLease` / `releaseLease` / `renewLease` / `getLeaseOwner` / `transferLease`) — distributed leasing kept separate from event delivery (`PubSub`) — so processes racing to wake the same thread coordinate a single owner. The winner runs the stream; losers forward their signal to it. `EventEmitterPubSub` leases in-memory; `RedisStreamsPubSub` uses `SET NX PX` with owner-verified Lua scripts for release, renew, and transfer; the default `NoopLeaseProvider` always wins, preserving single-process behavior. Fixed a cross-process race where, after a run finished, the thread's lease briefly went free before its queued follow-up work started — letting another process win the lease and start a competing run for the same thread. The owner now keeps the lease until all queued work is drained. The lease TTL and renewal interval are overridable for tests via `MASTRA_AGENT_THREAD_LEASE_TTL_MS` and `MASTRA_AGENT_THREAD_LEASE_RENEW_INTERVAL_MS` (production defaults unchanged). - Made `harness.createSession()` get-or-create by resourceId and added `harness.getSessionByResource()` so notification delivery runs as the session that owns the target thread. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) A resourceId now maps to exactly one durable session per Harness: calling `createSession({ resourceId })` twice returns the same session, so a user/thread always resumes their own session and the in-flight creation is shared by concurrent callers. This is the multi-session behavior a long-running / multiplayer server needs — work can be driven on a thread whether or not a human is currently attached, and it runs with that thread's own model/mode/state instead of an arbitrary session. **Before** ```typescript // createSession was a pure factory: two calls for the same resource produced // two independent sessions, and notification delivery had no way to find // "the session that owns this resource". const a = await harness.createSession({ resourceId: 'user-a' }); const b = await harness.createSession({ resourceId: 'user-a' }); // different object ``` **After** ```typescript const a = await harness.createSession({ resourceId: 'user-a' }); const b = await harness.createSession({ resourceId: 'user-a' }); // same session as a const session = await harness.getSessionByResource('user-a'); // === a ``` - Moved the tool-permission rule accessors off the Harness onto `session.permissions`. Reading and writing the persisted per-category / per-tool approval policies now lives on the session, next to the state it reads and writes. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const rules = harness.getPermissionRules(); harness.setPermissionForCategory({ category: 'execute', policy: 'ask' }); harness.setPermissionForTool({ toolName: 'dangerous_tool', policy: 'deny' }); ``` **After** ```typescript const rules = harness.session.permissions.getRules(); await harness.session.permissions.setForCategory({ category: 'execute', policy: 'ask' }); await harness.session.permissions.setForTool({ toolName: 'dangerous_tool', policy: 'deny' }); ``` Removed `Harness.getPermissionRules`, `Harness.setPermissionForCategory`, and `Harness.setPermissionForTool`. The setters now return a promise that resolves once the change is persisted to session state, so callers that read the rules back can await the write. Tool-category resolution stays on the harness as `harness.getToolCategory()` since it reads harness config rather than session state. `mastracode` is updated to consume the new API: the `/permissions` command reads and sets policies via `session.permissions`. - Replaced `Harness.getModelName()` and `Harness.getFullModelId()` with session accessors. The full model id is read via the existing `harness.session.model.get()`, and the short display name moves to a new `harness.session.model.displayName()`. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const name = harness.getModelName(); const fullId = harness.getFullModelId(); ``` **After** ```typescript const name = harness.session.model.displayName(); const fullId = harness.session.model.get(); ``` `mastracode` is updated to consume the new API: the TUI status line and message renderer now read the model id via `harness.session.model.get()`. - Moved the subagent model accessors off the Harness onto `session.subagents.model`. Reading and setting the global or per-`agentType` subagent model now lives on the session, next to the state it reads and writes, and is grouped under `session.subagents` to leave room for future subagent settings. ([#18200](https://github.com/mastra-ai/mastra/pull/18200)) **Before** ```typescript const modelId = harness.getSubagentModelId({ agentType: 'explore' }); await harness.setSubagentModelId({ modelId: 'anthropic/claude-sonnet-4', agentType: 'explore' }); ``` **After** ```typescript const modelId = harness.session.subagents.model.get({ agentType: 'explore' }); await harness.session.subagents.model.set({ modelId: 'anthropic/claude-sonnet-4', agentType: 'explore' }); ``` `get()` prefers the per-`agentType` value, then the global subagent model, returning `null` when neither is set. `set()` persists to thread settings and emits a `subagent_model_changed` event. Removed `Harness.getSubagentModelId` and `Harness.setSubagentModelId`. `mastracode` is updated to consume the new API: the `/subagents` command, model-pack activation, and startup model restoration read and set subagent models via `session.subagents.model`. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`5bd72d2`](https://github.com/mastra-ai/mastra/commit/5bd72d255f45b5ea8ab342643bd463814a980a24)) - Added unit test coverage for `agent/durable/run-registry.ts` RunRegistry and ExtendedRunRegistry classes. No behaviour changes — purely additive test coverage. ([#18265](https://github.com/mastra-ai/mastra/pull/18265)) - Introduce the `SessionMachinery` injection boundary on the Harness Session. This formalizes the narrow set of Harness-owned capabilities (resolve the current agent, build run/stream options + toolsets + request context, persist token usage, generate ids, open a thread subscription) that a Session leverages to drive an agent run. The Harness injects this machinery into each Session it constructs via `session.setMachinery(...)`. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) This is the dependency-injection foundation for making the run loop, run state, and thread stream session-owned (so one Harness can serve many concurrent sessions). No behavior change in this step — the machinery is wired but not yet consumed. - Fix notification signals not waking idle threads ([#18244](https://github.com/mastra-ai/mastra/pull/18244)) - Agents now receive rich text (markdown) from channel messages instead of stripped plain text. Links, bold, italic, code, blockquotes, and other formatting from Slack (and other platforms) are preserved as standard markdown that LLMs understand natively. Previously, a Slack message like 'Check out ' would arrive as 'Check out Example' — the URL was lost. Now it arrives as 'Check out [Example](https://example.com)'. ([#18109](https://github.com/mastra-ai/mastra/pull/18109)) - Fixed a crash when the goal judge stream outlives the main agent stream. The `emitJudgeActivity` helper now uses `safeEnqueue` (try/catch guard) instead of raw `controller.enqueue()`, preventing `TypeError: Controller is already closed` when the ReadableStream controller closes before the fire-and-forget judge observer finishes draining. ([#18196](https://github.com/mastra-ai/mastra/pull/18196)) - Fixed tool calls running in parallel even when a tool that requires approval or can suspend was available in a step. This could let tool calls bypass an approval step when the model didn't call the approval tool that turn. ([#18275](https://github.com/mastra-ai/mastra/pull/18275)) Tool calls now run one at a time whenever an approval or suspending tool is available in a step. Parallel tool calls are still allowed when no such tool is available. - Added unit test coverage for `processors/step-schema.ts` Zod validation schemas. No behaviour changes — purely additive test coverage. ([#18264](https://github.com/mastra-ai/mastra/pull/18264)) - Fixed Harness runs so they no longer send a default temperature when the caller did not configure model settings. ([#18147](https://github.com/mastra-ai/mastra/pull/18147)) - Added BDD-style AIMock scenario tests for the core agentic loop to guard against multi-step composition regressions. Covers tool-result plumbing, cross-turn message ordering, stop conditions, tool approval/resume, structured output, active-tools filtering, output processors, memory/working-memory recall, input/output processors, prepare-step overrides, workspace integration, subagent delegation, dynamic instructions, isTaskComplete gating, text-streaming fidelity, provider errors, guardrail tripwires, background tasks (tool-level, agent-level, streamUntilIdle), goals (satisfied, budget exhausted), tool-level requireApproval, conditional requireToolApproval functions, supervisor delegation hooks (onDelegationStart prompt modification and rejection, messageFilter), onIterationComplete iteration tracking with early stop, multi-tool parallel execution with concurrent tool calls, fullStream chunk ordering (text-start/text-delta/text-end, step/finish lifecycle), abort signal mid-stream halting, runtime context (requestContext) passthrough to tools, per-step input/output processors, provider metadata passthrough, model settings request body override, request-level toolsets merge with agent-level tools, tool lifecycle hooks (onInputAvailable, onOutput), tool streaming with context.writer, observability context in tool execution, structured output schema validation failures with detailed ZodError messages, multiple isTaskComplete scorers with strategy semantics (all/any), processor sequencing and transformation, maxSteps boundary conditions with multiple stopWhen OR logic, error processors with retry count tracking and custom exhaustion logic, lifecycle callbacks (onStepFinish, onFinish), incremental message persistence (savePerStep), actor identity passthrough, workflows-as-tools integration with workflow tool execution and result flow, abort during tool execution with tool-level abort signal detection and early bail, error processor retry exhaustion with retryCount incrementation and state persistence across attempts, structured output validation repair with error chunk emission and partial JSON streaming, concurrent approval requests with independent approve/decline decisions, memory thread switching with conversation isolation across threads, nested agent delegation with multi-level agent-as-tools chains, structured output aggregation from multiple tool results, memory recall windowing with lastMessages configuration, empty/no-tool turn handling, abort signal interaction with structured output streaming, requestContext isolation across multiple tool execution steps, onError callback behavior (API errors vs tool errors), maxSteps/stopWhen interaction in long tool chains, structured output error strategies (strict emits error chunk, fallback returns fallbackValue, warn logs without error chunk), requestContext mutation behavior (tool mutations do NOT persist between executions, each tool sees original values), runtime tool suspension (tools calling suspend() mid-execution with resume via resumeStream()), delegation completion hooks (onDelegationComplete with bail() stopping loop immediately), supervisor context control (includeSubAgentToolResultsInModelContext controlling nested tool result pollution), auto-resume of suspended tools (autoResumeSuspendedTools detecting and resuming on next call), manual resume via resumeStream() with custom resumeData, and non-streaming generate() approval path (approveToolCallGenerate/declineToolCallGenerate). Extended the AIMock harness with createSharedAgent() helper and sharedAgent option to enable scenarios that require shared Mastra storage across multiple agent calls. Also covers thread signals (subscribeToThread/sendMessage/sendStateSignal), signal edge cases (multiple subscribers, unsubscribe cleanup, state-signal cache dedup), and signal delivery to idle, non-subscribed threads (sendMessage still wakes a run; sendStateSignal persists without waking). The test suite now includes 179 scenarios across 83 test files. A test-quality audit additionally hardened several abort and error-processor scenarios that previously could pass regardless of loop behavior: rewrote abort-structured-output and the abort-during-tool-execution bail test to assert deterministic outcomes (finishReason and suppressed post-abort requests) instead of swallowing errors, replaced a tautological `>= 0` delegation assertion in nested-tool-calls with a real two-level delegation check, and pinned the error-processor retry-exhaustion chain to its exact call order. Each was verified by disabling the relevant loop wiring and confirming the scenario now fails. Internal test-only change with no runtime impact. ([#18276](https://github.com/mastra-ai/mastra/pull/18276)) - Fixed browser state updates to attribute click-driven navigation to the assistant when the browser click result reports the new URL. ([#18239](https://github.com/mastra-ai/mastra/pull/18239)) - Add AIMock scenario tests for dynamic model resolution, client tools, and tool choice ([#18276](https://github.com/mastra-ai/mastra/pull/18276)) Added 3 new BDD-style scenario tests (7 test cases) to the AIMock regression test suite: - `dynamic-model.scenario.test.ts` - Tests that model resolution functions receive `requestContext` and can select different models per-request (e.g., fast vs. smart model based on context flags) - `client-tools.scenario.test.ts` - Tests that client tools passed to `agent.stream()` merge correctly with agent-level tools, both appear in model requests, and execute successfully - `tool-choice.scenario.test.ts` - Tests that `toolChoice` option passes through to the model request with correct values for `'none'`, `'required'`, and specific tool selection (`{ type: 'tool', toolName: 'name' }`) All scenarios run against a real OpenAI provider pointed at an in-test AIMock HTTP server, providing regression coverage for tool resolution and model selection logic in the agentic loop. - Fixed parallel tool calls being serialized by unrelated suspendable tools. ([#18243](https://github.com/mastra-ai/mastra/pull/18243)) - Fixed DurableAgent ignoring the wrapped agent's defaultOptions. When wrapping an agent with createDurableAgent, the agent's configured defaultOptions (maxSteps, providerOptions, modelSettings, etc.) were silently dropped — maxSteps fell back to the durable default of 5 and provider settings like Anthropic thinking config were never sent. DurableAgent now merges the wrapped agent's defaultOptions under each per-request call, matching Agent.stream()/generate(), and delegates getDefaultOptions() to the wrapped agent. ([#17794](https://github.com/mastra-ai/mastra/pull/17794)) Before: const base = new Agent({ model, defaultOptions: { maxSteps: 250 } }); const agent = createDurableAgent({ agent: base }); // runs capped at 5 steps, defaultOptions.providerOptions dropped After: // defaultOptions.maxSteps (250) and providerOptions are honored; per-request options still take precedence - Move the Harness agent run engine onto the Session. The stream loop that consumes an agent's event stream — folding chunks into display messages and token usage, driving tool approval/suspension, and finalizing the run — now lives in a per-session `SessionRunEngine` owned by the Session and driven through the injected `SessionMachinery`. The pure chunk→message content transforms move to a shared `stream-content` module. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) In the multi-user host the run loop, run state, and thread stream are per-session and cannot be shared, so they belong on the Session; how a run is produced (agent + config-backed builders) stays Harness-owned machinery. Behavior is unchanged: `harness.session.processStream(...)` and `session.resolveToolApproval(...)` replace the previously Harness-private equivalents. - Internal: extracted the Harness constructor's session wiring (thread-settings store, mode/model/om/permissions/subagents resolvers, thread data store, and initial mode/model seeding) into a private `#wireSession(session, defaultMode)` helper. No behavior or public API change — this is groundwork for wiring additional sessions to a single Harness. ([#18213](https://github.com/mastra-ai/mastra/pull/18213)) ## 1.45.0 ### Minor Changes - Random bump ([#18178](https://github.com/mastra-ai/mastra/pull/18178)) ### Patch Changes - Test-quality hardening of the AIMock loop scenarios: audited the suite for tests that pass regardless of loop behavior and rewrote the offenders. abort-structured-output and the abort-during-tool-execution bail test now assert deterministic outcomes (abort `finishReason` and suppressed post-abort model requests) instead of swallowing errors in conditional `catch` blocks; the nested-tool-calls delegation check replaces a tautological `>= 0` assertion with a real two-level parent→planner→researcher delegation verification; the error-processor retry-exhaustion test pins the exact processor call order and retryCount accounting. Each fix was verified by disabling the relevant loop wiring and confirming the scenario fails. Added an "Assertion quality" section to the scenarios README documenting the anti-patterns to avoid. Test-only change with no runtime impact. - Added comprehensive AIMock scenario tests covering error processors, lifecycle callbacks, incremental message persistence, actor identity, completion callbacks, multi-turn memory persistence with message ordering and resource isolation, workflows-as-tools integration, abort during tool execution, error processor retry exhaustion, structured output validation repair, concurrent approval requests with independent approve/decline decisions, memory thread switching with conversation isolation, nested agent delegation chains, structured output aggregation from multiple tool results, memory recall windowing with `lastMessages` configuration, empty/no-tool turn handling, abort signal interaction with structured output streaming, requestContext isolation across multiple tool execution steps, onError callback behavior (API errors vs tool errors), maxSteps/stopWhen interaction in long tool chains, structured output error strategies (strict, warn, fallback), requestContext mutation behavior (documenting that tool mutations do NOT persist between executions), runtime tool suspension (tools calling suspend() mid-execution with resume via resumeStream()), delegation completion hooks (onDelegationComplete with bail() stopping loop immediately), supervisor context control (includeSubAgentToolResultsInModelContext controlling nested tool result pollution), auto-resume of suspended tools (autoResumeSuspendedTools detecting and resuming on next call), manual resume via resumeStream() with custom resumeData, non-streaming generate() approval path (approveToolCallGenerate/declineToolCallGenerate), resume-after-decline flows, multi-call thread persistence, suspended snapshot integrity, skills integration with real SKILL.md files on disk (SkillsProcessor discovery, `` system prompt injection, `skill`/`skill_search`/`skill_read` tool auto-injection, skill activation round-trip, missing skill error handling), same-named skill tie-breaking (local precedence over external, path-based disambiguation bypass), and signal integration (subscribeToThread + sendMessage flow with pubsub, sendStateSignal with persist-only behavior). The test suite now includes 174 scenarios across 81 test files, providing comprehensive coverage of agent loop behaviors including error recovery, step-level observability, execution lifecycle hooks, memory system integrity, workflow tool execution, tool-level abort handling, retry exhaustion semantics, concurrent approval handling, thread isolation, multi-level delegation, memory recall windowing, empty turn handling, abort signal robustness, requestContext consistency, error callback semantics, execution boundary enforcement, structured output error handling, requestContext isolation guarantees, runtime suspension mechanics, delegation hook lifecycle, supervisor context pollution control, shared-storage suspend/resume flows, generate()-path approval gating, decline-and-retry workflows, cross-call persistence, snapshot integrity, workspace skills discovery pipeline, skill name collision resolution, and thread-based signal messaging. Extended the AIMock harness with `createSharedAgent()` helper and `sharedAgent` option to enable scenarios that require shared Mastra storage across multiple agent calls, making previously infeasible features (autoResumeSuspendedTools, resumeStream, generate() approval) fully testable at the AIMock level. **Round 17 comprehensive audit completed:** Final audit of all 18 agent feature docs confirmed every core loop feature has AIMock scenario coverage. **Round 18** extended coverage to the integration layer with a skills integration scenario using real SKILL.md files on disk and the full Workspace + LocalFilesystem + SkillsProcessor pipeline (discovery, system prompt injection, skill tool round-trip, missing skill handling). **Round 19** added signal integration scenarios by extending the harness with `pubsub` support: `subscribeToThread()` + `sendMessage()` drives thread subscriptions with pubsub, and `sendStateSignal()` persists state without waking the agent. **Attempted A2A integration scenario**: A2AAgent requires mocking `global.fetch` for agent card discovery + JSON-RPC message/send, but AIMock intercepts the same `fetch` for LLM provider calls, causing fixture conflicts. The A2A protocol's task/message model and streaming semantics differ fundamentally from OpenAI chat completions. All remaining documented features are external integrations (channels, SDK agents, ACP), infrastructure-heavy (semantic recall, code mode), or already have robust unit tests. The core agentic loop is now battle-tested against multi-step composition regressions with 81 scenario files and 174 passing tests. - Improved state signal restoration so long threads can resume without scanning every message. ([#18182](https://github.com/mastra-ai/mastra/pull/18182)) - Fix agent signal drains so pending signals are recorded through the canonical signal transcript path and consistently rotate the response message id. This prevents follow-up signal turns from being attached to the previous assistant response and helps the agent see the latest completed step before continuing. ([#18105](https://github.com/mastra-ai/mastra/pull/18105)) - Add system guidance explaining that browser and task-list state signals are automatic context updates, not user instructions. ([#18163](https://github.com/mastra-ai/mastra/pull/18163)) - Updated dependencies [[`b04369d`](https://github.com/mastra-ai/mastra/commit/b04369d6b167c698ef103981171a8bf92808e756)]: - @mastra/schema-compat@1.3.0 ## 1.45.0-alpha.0 ### Minor Changes - Random bump ([#18178](https://github.com/mastra-ai/mastra/pull/18178)) ### Patch Changes - Improved state signal restoration so long threads can resume without scanning every message. ([#18182](https://github.com/mastra-ai/mastra/pull/18182)) - Fix agent signal drains so pending signals are recorded through the canonical signal transcript path and consistently rotate the response message id. This prevents follow-up signal turns from being attached to the previous assistant response and helps the agent see the latest completed step before continuing. ([#18105](https://github.com/mastra-ai/mastra/pull/18105)) - Add system guidance explaining that browser and task-list state signals are automatic context updates, not user instructions. ([#18163](https://github.com/mastra-ai/mastra/pull/18163)) - Updated dependencies [[`b04369d`](https://github.com/mastra-ai/mastra/commit/b04369d6b167c698ef103981171a8bf92808e756)]: - @mastra/schema-compat@1.3.0-alpha.0 ## 1.44.0 ### Minor Changes - Removed the legacy harness v1 entrypoint. ([#18131](https://github.com/mastra-ai/mastra/pull/18131)) **Breaking change** Imports from `@mastra/core/harness/v1` no longer work. Use the stable harness entrypoint instead. **Before** ```ts import { Harness } from '@mastra/core/harness/v1'; ``` **After** ```ts import { Harness } from '@mastra/core/harness'; ``` - Add `setStudio()` method to Mastra class for runtime studio configuration. ([#18111](https://github.com/mastra-ai/mastra/pull/18111)) This enables deploy wrappers to configure studio auth/RBAC separately from server config, which is required for dual auth patterns where Studio UI uses platform auth while Server API remains open or uses user-configured auth. ```typescript // Set studio auth separately from server auth mastra.setStudio({ auth: new MastraAuthStudio(), rbac: new MastraRBACStudio({ roleMapping: { admin: ['*'] } }), }); ``` - Made the native Agent goal mechanism robust, restoring behavior that regressed when goal handling moved into core. ([#18016](https://github.com/mastra-ai/mastra/pull/18016)) - **Tool-capable judges.** Scorer judge configs accept optional `tools`, and the default goal scorer can use them to verify the agent's work against reality instead of grading prose alone. MastraCode wires its read-only workspace tools (`view`, `search_content`, `find_files`, `file_stat`, `lsp_inspect`) into `goal.tools`. - **Judge memory restored.** Scorer judge configs accept optional `memory`; goal scoring uses the original MastraCode per-goal judge thread shape and prompt format so repeated evaluations retain prior facts, feedback, and user checkpoints through judge memory. - **Tri-state waiting.** The default goal scorer emits `done`/`continue`/`waiting`; a `waiting` decision (only when the goal text explicitly asks to stop for the user) stops the auto-loop but keeps the objective `active` so the next agent turn is still judged — no `/goal resume` needed. - **Budget-exhaustion pause.** Reaching `maxRuns` without completing now parks the objective as `paused` with a clear reason, resumable by raising `maxRuns` and reactivating, instead of silently leaving it `active`. - **Judge-failure pause (no infinite loop).** Any failure while evaluating the goal — including judge-model/tools resolution, not just the scorer run — pauses the objective and stops the loop, surfacing the cause, rather than re-running the model against a broken judge every turn. - **Structured-output retry.** `tryGenerateWithJsonFallback` now retries with `jsonPromptInjection` when the judge resolves without a parseable object (not only on a thrown error), matching the streaming path. - **Signal-based feedback.** Goal judge feedback is now injected as a `goal-judge` system-reminder signal instead of an assistant-authored "Completion Check Results" transcript message, so reloads and subsequent model context match the original MastraCode goal loop. Continuation, waiting, paused, and done decisions all persist structured evaluation metadata for replay. - **TUI activity/replay fixes.** Goal evaluation chunks close the current assistant message before rendering the judge UI, stream judge activity with useful tool targets, stream partial judge reason text while scoring, replay persisted judge results as judge display components instead of raw Goal reminder text, and correctly persist Esc/Ctrl+C pauses while the judge is running. The goal evaluation chunk now carries `pausedReason`, `judgeFailed`, `waitingForUser`, `pending` (emitted before scoring starts so consumers can show a loading indicator), and judge `activity` entries including streamed `reason` updates. - Added session-owned Harness state. ([#18133](https://github.com/mastra-ai/mastra/pull/18133)) Harness state is now available through `harness.session.state`, so new integrations can read and write state from the same session object that owns mode, model, thread, and run data. **Example** ```ts const state = harness.session.state.get(); await harness.session.state.set({ yolo: true }); ``` `Harness.getState()` and `Harness.setState()` still work and now delegate to `harness.session.state`. - Move the Harness display state onto `session.displayState` ([#18136](https://github.com/mastra-ai/mastra/pull/18136)) The canonical display state a UI renders — `isRunning`, active tools, tool-input buffers, pending approval/suspensions, active subagents, current message, queued follow-ups, modified files, tasks, OM progress, and token usage — now lives on the Session as `session.displayState`, alongside the reducer that keeps it in sync with every Harness event. `SessionDisplayState` owns: - `get()` — a read-only snapshot to render from (replaces `harness.getDisplayState()`) - `apply(event)` — the centralized state machine that folds each event into the snapshot - `resetThread()` — reset thread-scoped fields on thread switch/create - `restoreTasks(tasks)` — restore replayed task history without emitting an event - `clearPendingSuspensions()` / `deletePendingSuspension(toolCallId)` — display mirror upkeep on abort/resume Display state is inherently per-conversation, so in a shared (multi-user) host it can't hang off the Harness — `harness.getDisplayState()` has no single answer when one host serves many sessions. It reads from `harness.session.displayState` instead. The Harness stays the **event-bus owner**: `emit()` folds the event into `session.displayState` and then dispatches to listeners (including the `display_state_changed` fan-out). The reducer needs a few read-only host facts it doesn't own — the live token-usage tally, the subagent display-name lookup (Harness config), and the active thread id — which are injected into the Session at construction. Removed from the Harness public API (read through `harness.session.displayState.*`): - `getDisplayState()` → `session.displayState.get()` - `restoreDisplayTasks(tasks)` → `session.displayState.restoreTasks(tasks)` `restoreTasks` is now a pure session-state mutation (it no longer emits `display_state_changed`); the UI re-renders explicitly after a replay. - Introduce the Harness `Session`: the Harness now exposes `harness.session`, a class that owns the per-conversation runtime state that previously lived flattened on the Harness instance. The Harness remains the shared host (storage, the thread lock, the agent registry, the event bus); the Session owns the state scoped to a single user's conversation. ([#18107](https://github.com/mastra-ai/mastra/pull/18107)) `harness.session` owns: - **`session.identity`** — the session's `resourceId` / `defaultResourceId` (the stable "who"). - **`session.thread`** — the active thread binding (`getId` / `set` / `clear` / `isSet` / `requireId`) plus the thread data domain scoped to the session: `list`, `getById`, `listMessages`, `listActiveMessages`, `firstUserMessage(s)`, and thread settings (`getSetting` / `setSetting` / `deleteSetting`). It reaches shared-host storage through an injected gateway rather than calling back into Harness orchestration. - **`session.mode`** / **`session.model`** — the currently-selected mode and model (source of truth), the mode-switch sequence, and per-mode model memory. The Harness still owns the mode _definitions_ (`config.modes`). - **`session.run`** — transient per-run identity (run id, trace id, operation counter) and abort control (controller, `isRunning`, `requestAbort`, …). Never persisted. - **`session.stream`** — the live agent-thread subscription handle and its dedup key. Adds `session.getCurrentRunId()` and `session.abortRun()`, which compose the subscription. - **`session.suspensions`** — the parked-tool resume map (`toolCallId → { runId, toolName }`) for tools paused via the native suspension primitive (`ask_user` / `request_access` / `submit_plan`). - **`session.followUps`** — the queue of messages to send after the active run finishes. - **`session.approval`** — the interactive tool-approval gate; `session.respondToToolApproval({ decision, requestContext })` applies the user's approve / decline / always-allow choice and releases the run. - session-scoped permission grants and the live token-usage counter (the Harness still persists usage to thread metadata, since usage is thread-scoped). **Removed from the Harness public API** — read these through `harness.session.*` instead: - `getSessionGrants()`, `getTokenUsage()` → `session.getGrants()` / `session.getTokenUsage()` - `getCurrentModelId()`, `hasModelSelected()` → `session.model.get()` / `session.model.hasSelection()` - `getCurrentRunId()`, `getCurrentTraceId()`, `isRunning()` → `session.getCurrentRunId()` / `session.run.getTraceId()` / `session.run.isRunning()` - `getCurrentThreadId()`, `getThreads()`, `listThreads()`, `listMessages()`, `listMessagesForThread()`, `getFirstUserMessage(s)ForThread(s)()`, `getThreadSetting()`, `setThreadSetting()` → `session.thread.*` - `getResourceId()`, `getDefaultResourceId()` → `session.identity.*` - `getFollowUpCount()` → `session.followUps.count()` - `respondToToolApproval()` → `session.respondToToolApproval()` - `getCurrentModeId()` → `session.mode.get()` - `getCurrentMode()` → `session.mode.resolve()` (resolves the selected mode id against the host's `config.modes` catalog, injected into the Session) - `hasPendingSuspensions()` → `session.suspensions.hasPending()` - `isCurrentThreadStreamActive()` → `session.stream.isActive()` `Harness.abort()` and `setResourceId()` remain on the Harness with unchanged behavior — they orchestrate Harness-host state (the display-state mirror, the agent-stream subscription, thread teardown) before delegating the relevant reads/writes to the session. `session.mode` exposes two complementary accessors: `get()` returns the selected mode **id** (a `string`, mirroring `session.model.get()`), while `resolve()` returns the full `HarnessMode` definition by looking the id up in the injected mode catalog. The legacy `HarnessCompat` shim (v1-session/legacy-thread merge) has been removed; its thread-list merge now lives in the Session's thread-data store, so `session.thread.list()` returns the merged result directly. `session.abortRun()` now also releases a parked tool-approval gate: a run awaiting `session.approval.arm()` is not actively streaming, so aborting resolves the gate as a decline (rejecting the gated tool) instead of leaving the await hung. Mirrors how abort already drops parked tool suspensions. ### Patch Changes - Fixed the @mastra/editor build failing on native Windows by using POSIX separators for tsup entry globs. ([#18124](https://github.com/mastra-ai/mastra/pull/18124)) - Fixed harness buildAgentMessageStreamOptions to combine the agent's own dynamic instructions with mode instructions instead of replacing them. Previously, when a shared backing agent had dynamic instructions (e.g. AGENTS.md, project context), passing mode instructions via options.instructions completely overrode them. ([#18127](https://github.com/mastra-ai/mastra/pull/18127)) - Security remediation for the 2026-06-17 "easy-day-js" supply-chain incident. Patch bump to publish clean versions and move the `latest` dist-tag forward, superseding the compromised versions that declared the malicious `easy-day-js` dependency. ([#18056](https://github.com/mastra-ai/mastra/pull/18056)) - Fixed durable agents (`createDurableAgent` / `createEventedAgent`) producing no observability traces. ([#18083](https://github.com/mastra-ai/mastra/pull/18083)) Durable agent runs previously emitted no traces at all because the durable execution path never opened an `AGENT_RUN` root span and the internal workflow spans were marked internal, so the whole trace was dropped on export. Durable runs now produce the same `AGENT_RUN`-rooted trace as non-durable agents — the model call, tool calls, and token usage all appear in the trace UI, nested correctly under the agent run. No API changes are required; existing durable agents start emitting traces automatically once observability is configured. - Fixed stream error display showing [object Object] instead of the actual error message. Errors from subscribed thread streams (e.g. context length exceeded) now properly extract the message from deserialized error objects using getErrorFromUnknown. ([#18087](https://github.com/mastra-ai/mastra/pull/18087)) - Require approval before workspace agents run shell commands on unisolated local sandboxes. ([#18129](https://github.com/mastra-ai/mastra/pull/18129)) - Updated dependencies [[`77a2351`](https://github.com/mastra-ai/mastra/commit/77a2351ee79296e360bce822cb3391f7cfd6489d)]: - @mastra/schema-compat@1.2.14 ## 1.44.0-alpha.2 ### Minor Changes - Removed the legacy harness v1 entrypoint. ([#18131](https://github.com/mastra-ai/mastra/pull/18131)) **Breaking change** Imports from `@mastra/core/harness/v1` no longer work. Use the stable harness entrypoint instead. **Before** ```ts import { Harness } from '@mastra/core/harness/v1'; ``` **After** ```ts import { Harness } from '@mastra/core/harness'; ``` - Added session-owned Harness state. ([#18133](https://github.com/mastra-ai/mastra/pull/18133)) Harness state is now available through `harness.session.state`, so new integrations can read and write state from the same session object that owns mode, model, thread, and run data. **Example** ```ts const state = harness.session.state.get(); await harness.session.state.set({ yolo: true }); ``` `Harness.getState()` and `Harness.setState()` still work and now delegate to `harness.session.state`. - Move the Harness display state onto `session.displayState` ([#18136](https://github.com/mastra-ai/mastra/pull/18136)) The canonical display state a UI renders — `isRunning`, active tools, tool-input buffers, pending approval/suspensions, active subagents, current message, queued follow-ups, modified files, tasks, OM progress, and token usage — now lives on the Session as `session.displayState`, alongside the reducer that keeps it in sync with every Harness event. `SessionDisplayState` owns: - `get()` — a read-only snapshot to render from (replaces `harness.getDisplayState()`) - `apply(event)` — the centralized state machine that folds each event into the snapshot - `resetThread()` — reset thread-scoped fields on thread switch/create - `restoreTasks(tasks)` — restore replayed task history without emitting an event - `clearPendingSuspensions()` / `deletePendingSuspension(toolCallId)` — display mirror upkeep on abort/resume Display state is inherently per-conversation, so in a shared (multi-user) host it can't hang off the Harness — `harness.getDisplayState()` has no single answer when one host serves many sessions. It reads from `harness.session.displayState` instead. The Harness stays the **event-bus owner**: `emit()` folds the event into `session.displayState` and then dispatches to listeners (including the `display_state_changed` fan-out). The reducer needs a few read-only host facts it doesn't own — the live token-usage tally, the subagent display-name lookup (Harness config), and the active thread id — which are injected into the Session at construction. Removed from the Harness public API (read through `harness.session.displayState.*`): - `getDisplayState()` → `session.displayState.get()` - `restoreDisplayTasks(tasks)` → `session.displayState.restoreTasks(tasks)` `restoreTasks` is now a pure session-state mutation (it no longer emits `display_state_changed`); the UI re-renders explicitly after a replay. - Introduce the Harness `Session`: the Harness now exposes `harness.session`, a class that owns the per-conversation runtime state that previously lived flattened on the Harness instance. The Harness remains the shared host (storage, the thread lock, the agent registry, the event bus); the Session owns the state scoped to a single user's conversation. ([#18107](https://github.com/mastra-ai/mastra/pull/18107)) `harness.session` owns: - **`session.identity`** — the session's `resourceId` / `defaultResourceId` (the stable "who"). - **`session.thread`** — the active thread binding (`getId` / `set` / `clear` / `isSet` / `requireId`) plus the thread data domain scoped to the session: `list`, `getById`, `listMessages`, `listActiveMessages`, `firstUserMessage(s)`, and thread settings (`getSetting` / `setSetting` / `deleteSetting`). It reaches shared-host storage through an injected gateway rather than calling back into Harness orchestration. - **`session.mode`** / **`session.model`** — the currently-selected mode and model (source of truth), the mode-switch sequence, and per-mode model memory. The Harness still owns the mode _definitions_ (`config.modes`). - **`session.run`** — transient per-run identity (run id, trace id, operation counter) and abort control (controller, `isRunning`, `requestAbort`, …). Never persisted. - **`session.stream`** — the live agent-thread subscription handle and its dedup key. Adds `session.getCurrentRunId()` and `session.abortRun()`, which compose the subscription. - **`session.suspensions`** — the parked-tool resume map (`toolCallId → { runId, toolName }`) for tools paused via the native suspension primitive (`ask_user` / `request_access` / `submit_plan`). - **`session.followUps`** — the queue of messages to send after the active run finishes. - **`session.approval`** — the interactive tool-approval gate; `session.respondToToolApproval({ decision, requestContext })` applies the user's approve / decline / always-allow choice and releases the run. - session-scoped permission grants and the live token-usage counter (the Harness still persists usage to thread metadata, since usage is thread-scoped). **Removed from the Harness public API** — read these through `harness.session.*` instead: - `getSessionGrants()`, `getTokenUsage()` → `session.getGrants()` / `session.getTokenUsage()` - `getCurrentModelId()`, `hasModelSelected()` → `session.model.get()` / `session.model.hasSelection()` - `getCurrentRunId()`, `getCurrentTraceId()`, `isRunning()` → `session.getCurrentRunId()` / `session.run.getTraceId()` / `session.run.isRunning()` - `getCurrentThreadId()`, `getThreads()`, `listThreads()`, `listMessages()`, `listMessagesForThread()`, `getFirstUserMessage(s)ForThread(s)()`, `getThreadSetting()`, `setThreadSetting()` → `session.thread.*` - `getResourceId()`, `getDefaultResourceId()` → `session.identity.*` - `getFollowUpCount()` → `session.followUps.count()` - `respondToToolApproval()` → `session.respondToToolApproval()` - `getCurrentModeId()` → `session.mode.get()` - `getCurrentMode()` → `session.mode.resolve()` (resolves the selected mode id against the host's `config.modes` catalog, injected into the Session) - `hasPendingSuspensions()` → `session.suspensions.hasPending()` - `isCurrentThreadStreamActive()` → `session.stream.isActive()` `Harness.abort()` and `setResourceId()` remain on the Harness with unchanged behavior — they orchestrate Harness-host state (the display-state mirror, the agent-stream subscription, thread teardown) before delegating the relevant reads/writes to the session. `session.mode` exposes two complementary accessors: `get()` returns the selected mode **id** (a `string`, mirroring `session.model.get()`), while `resolve()` returns the full `HarnessMode` definition by looking the id up in the injected mode catalog. The legacy `HarnessCompat` shim (v1-session/legacy-thread merge) has been removed; its thread-list merge now lives in the Session's thread-data store, so `session.thread.list()` returns the merged result directly. `session.abortRun()` now also releases a parked tool-approval gate: a run awaiting `session.approval.arm()` is not actively streaming, so aborting resolves the gate as a decline (rejecting the gated tool) instead of leaving the await hung. Mirrors how abort already drops parked tool suspensions. ### Patch Changes - Fixed the @mastra/editor build failing on native Windows by using POSIX separators for tsup entry globs. ([#18124](https://github.com/mastra-ai/mastra/pull/18124)) - Fixed harness buildAgentMessageStreamOptions to combine the agent's own dynamic instructions with mode instructions instead of replacing them. Previously, when a shared backing agent had dynamic instructions (e.g. AGENTS.md, project context), passing mode instructions via options.instructions completely overrode them. ([#18127](https://github.com/mastra-ai/mastra/pull/18127)) - Fixed durable agents (`createDurableAgent` / `createEventedAgent`) producing no observability traces. ([#18083](https://github.com/mastra-ai/mastra/pull/18083)) Durable agent runs previously emitted no traces at all because the durable execution path never opened an `AGENT_RUN` root span and the internal workflow spans were marked internal, so the whole trace was dropped on export. Durable runs now produce the same `AGENT_RUN`-rooted trace as non-durable agents — the model call, tool calls, and token usage all appear in the trace UI, nested correctly under the agent run. No API changes are required; existing durable agents start emitting traces automatically once observability is configured. ## 1.44.0-alpha.1 ### Minor Changes - Add `setStudio()` method to Mastra class for runtime studio configuration. ([#18111](https://github.com/mastra-ai/mastra/pull/18111)) This enables deploy wrappers to configure studio auth/RBAC separately from server config, which is required for dual auth patterns where Studio UI uses platform auth while Server API remains open or uses user-configured auth. ```typescript // Set studio auth separately from server auth mastra.setStudio({ auth: new MastraAuthStudio(), rbac: new MastraRBACStudio({ roleMapping: { admin: ['*'] } }), }); ``` - Made the native Agent goal mechanism robust, restoring behavior that regressed when goal handling moved into core. ([#18016](https://github.com/mastra-ai/mastra/pull/18016)) - **Tool-capable judges.** Scorer judge configs accept optional `tools`, and the default goal scorer can use them to verify the agent's work against reality instead of grading prose alone. MastraCode wires its read-only workspace tools (`view`, `search_content`, `find_files`, `file_stat`, `lsp_inspect`) into `goal.tools`. - **Judge memory restored.** Scorer judge configs accept optional `memory`; goal scoring uses the original MastraCode per-goal judge thread shape and prompt format so repeated evaluations retain prior facts, feedback, and user checkpoints through judge memory. - **Tri-state waiting.** The default goal scorer emits `done`/`continue`/`waiting`; a `waiting` decision (only when the goal text explicitly asks to stop for the user) stops the auto-loop but keeps the objective `active` so the next agent turn is still judged — no `/goal resume` needed. - **Budget-exhaustion pause.** Reaching `maxRuns` without completing now parks the objective as `paused` with a clear reason, resumable by raising `maxRuns` and reactivating, instead of silently leaving it `active`. - **Judge-failure pause (no infinite loop).** Any failure while evaluating the goal — including judge-model/tools resolution, not just the scorer run — pauses the objective and stops the loop, surfacing the cause, rather than re-running the model against a broken judge every turn. - **Structured-output retry.** `tryGenerateWithJsonFallback` now retries with `jsonPromptInjection` when the judge resolves without a parseable object (not only on a thrown error), matching the streaming path. - **Signal-based feedback.** Goal judge feedback is now injected as a `goal-judge` system-reminder signal instead of an assistant-authored "Completion Check Results" transcript message, so reloads and subsequent model context match the original MastraCode goal loop. Continuation, waiting, paused, and done decisions all persist structured evaluation metadata for replay. - **TUI activity/replay fixes.** Goal evaluation chunks close the current assistant message before rendering the judge UI, stream judge activity with useful tool targets, stream partial judge reason text while scoring, replay persisted judge results as judge display components instead of raw Goal reminder text, and correctly persist Esc/Ctrl+C pauses while the judge is running. The goal evaluation chunk now carries `pausedReason`, `judgeFailed`, `waitingForUser`, `pending` (emitted before scoring starts so consumers can show a loading indicator), and judge `activity` entries including streamed `reason` updates. ### Patch Changes - Fixed stream error display showing [object Object] instead of the actual error message. Errors from subscribed thread streams (e.g. context length exceeded) now properly extract the message from deserialized error objects using getErrorFromUnknown. ([#18087](https://github.com/mastra-ai/mastra/pull/18087)) ## 1.43.1-alpha.0 ### Patch Changes - Security remediation for the 2026-06-17 "easy-day-js" supply-chain incident. Patch bump to publish clean versions and move the `latest` dist-tag forward, superseding the compromised versions that declared the malicious `easy-day-js` dependency. ([#18056](https://github.com/mastra-ai/mastra/pull/18056)) - Updated dependencies [[`77a2351`](https://github.com/mastra-ai/mastra/commit/77a2351ee79296e360bce822cb3391f7cfd6489d)]: - @mastra/schema-compat@1.2.14-alpha.0 ## 1.43.0 ### Minor Changes - Added harness modes that share a backing agent while applying mode-specific instructions, tools, and approval transitions. ([#17892](https://github.com/mastra-ai/mastra/pull/17892)) - Added source-backed storage primitives for code-mode agent editing. ([#17582](https://github.com/mastra-ai/mastra/pull/17582)) Mastra now exposes a `SourceControlProvider` interface for hosted source-control-backed editor storage, and `MastraEditor` can persist code-mode agent overrides through either local filesystem storage or a source provider. ```ts const editor = new MastraEditor({ source: 'code', sourceControlProvider, }); ``` Code-defined agents still respect their `editor` ownership config, while source-backed storage can read, write, list history, and open change requests through a provider implementation. - Removed the coalesced Harness display state subscription API. ([#17989](https://github.com/mastra-ai/mastra/pull/17989)) **Before** ```ts const unsubscribe = harness.subscribeDisplayState(displayState => { render(displayState); }); ``` **After** ```ts const unsubscribe = harness.subscribe(event => { if (event.type === 'display_state_changed') { render(event.displayState); } }); const currentDisplayState = harness.getDisplayState(); ``` Use `subscribe()` with `display_state_changed` events and `getDisplayState()` for UI rendering. - Added support for Gateway embedding routing with `mastra/provider/model` IDs: ([#17425](https://github.com/mastra-ai/mastra/pull/17425)) ```ts const embedder = new ModelRouterEmbeddingModel('mastra/__GATEWAY_OPENAI_EMBEDDING_MODEL__'); ``` - Add a native **goal** mechanism to the `Agent` — a durable, thread-scoped objective that is judged in-loop and gates the agentic execution loop, mirroring the `isTaskComplete` step. ([#17889](https://github.com/mastra-ai/mastra/pull/17889)) Previously a "goal / Ralph loop" had to be built on top of the agent by re-invoking `agent.stream()` between full turns and running a hand-rolled judge agent. That approach was consumer-specific and could not evaluate an objective mid-run (e.g. when a `sendMessage` signal was delivered into an already-running loop). Goals are now an Agent capability. A new `goal` config is accepted on the `Agent`: ```ts import { Agent } from '@mastra/core/agent'; import { GoalSignalProvider } from '@mastra/core/signals'; const agent = new Agent({ name, instructions, model, memory, goal: { judge: '__GATEWAY_OPENAI_MODEL__', maxRuns: 50 /* prompt? */ }, }); ``` The objective lives in the generic `threadState` storage domain under `type: "goal"` (reusing the domain introduced for task tools), so it persists across process restarts and is serializable mid-run. Programmatic control is via new `Agent` methods: `setObjective(objective, { threadId, resourceId?, judgeModelId?, maxRuns?, prompt? })`, `getObjective({ threadId })`, `clearObjective({ threadId })`, and `updateObjectiveOptions({ threadId, judgeModelId?, maxRuns?, prompt? })`. All no-op when the run is not memory-backed. Behavior: - A new **goal step** runs after `isTaskCompleteStep` in the agentic execution workflow, using the same gating as `isTaskComplete` (skips background-task / mid-tool-loop / working-memory-only iterations). On a candidate answer it scores the objective with an LLM-as-judge (the default `createGoalScorer`, a `createScorer` analyze → score → reason pipeline that returns `1` when the objective is achieved and `0` otherwise; a custom `goal.scorer` can be supplied instead), persists `runsUsed`, and gates the loop: pass → `status: "done"`, `isContinued = false`; not passed within budget → `isContinued = true` (the goal gate wins over `isTaskComplete`); `runsUsed >= maxRuns` → stop and keep `status: "active"`. - **The judge model is required.** Settings resolve per evaluation as ThreadState record value → agent `goal` config → default (`maxRuns` `50`, a default judge prompt). If no judge model resolves from either the record or `goal.judge`, the goal step is a complete no-op: no scoring, no `runsUsed` increment, and no `goal` chunk. - **`goal.judge` accepts a resolver function.** In addition to a model id or model object, `goal.judge` may be a `({ requestContext, mastra }) => model | undefined` function so a consumer can inject provider credentials and read the current judge selection at runtime (returning `undefined` keeps the step a no-op). A bare model id (from `goal.judge` or the per-objective `judgeModelId`) is resolved through the Mastra instance's model router/gateways when the default scorer is built, so provider credentials are applied rather than falling back to the default provider path. - The current objective is projected onto the agent **state-signal** lane (``) by the new `GoalStateProcessor`, so the model sees it in context without invalidating the prompt-cache prefix. - A typed **`goal`** stream chunk is emitted on every evaluation (`GoalEvaluationPayload`: objective, iteration, maxRuns, passed, status, results, reason?, duration, timedOut, maxRunsReached, suppressFeedback) for consumers (TUI / `@mastra/client-js`). For the simplest setup, register `GoalSignalProvider` via the agent's `signals` array — when `goal` config is present the Agent also auto-registers `GoalStateProcessor` if no goal provider is supplied. The goal primitives are co-located under `agent/goal` (objective store, scorer, state processor, signal provider). The public export surface is unchanged: `@mastra/core/tools` re-exports `GoalStateProcessor`, `DEFAULT_GOAL_JUDGE_PROMPT`, `DEFAULT_GOAL_MAX_RUNS`, and the goal helpers/types (`AgentGoalConfigDefaults`, etc.); `@mastra/core/signals` re-exports `GoalSignalProvider`; `@mastra/core/stream` exports `GoalEvaluationPayload`. New type on the thread-state domain: `GoalObjectiveRecord`. The `goal` arm is added to the public `ChunkType` union. No breaking change to `isTaskComplete`. Docs: a new **Goals** page under `docs/agents/goals`, and the `goal` chunk is documented in the streaming `ChunkType` reference. ### Patch Changes - dependencies updates: ([#17838](https://github.com/mastra-ai/mastra/pull/17838)) - Updated dependency [`fastq@^1.20.1` ↗︎](https://www.npmjs.com/package/fastq/v/1.20.1) (from `^1.19.1`, in `dependencies`) - dependencies updates: ([#17846](https://github.com/mastra-ai/mastra/pull/17846)) - Updated dependency [`posthog-node@^5.37.0` ↗︎](https://www.npmjs.com/package/posthog-node/v/5.37.0) (from `^5.30.6`, in `dependencies`) - dependencies updates: ([#17861](https://github.com/mastra-ai/mastra/pull/17861)) - Updated dependency [`fastq@^1.20.1` ↗︎](https://www.npmjs.com/package/fastq/v/1.20.1) (from `^1.19.1`, in `dependencies`) - Added dependency [`jpeg-js@^0.4.4` ↗︎](https://www.npmjs.com/package/jpeg-js/v/0.4.4) (to `dependencies`) - dependencies updates: ([#17961](https://github.com/mastra-ai/mastra/pull/17961)) - Updated dependency [`ajv@^8.20.0` ↗︎](https://www.npmjs.com/package/ajv/v/8.20.0) (from `^8.18.0`, in `dependencies`) - Added full Studio authentication support for Auth0 users. ([#16658](https://github.com/mastra-ai/mastra/pull/16658)) **What's new:** - **Studio SSO login** — your internal team can now sign in to Mastra Studio using their Auth0 accounts via OAuth 2.0/OIDC - **JWT validation** — API requests with Auth0-issued JWTs are automatically validated - **Session persistence** — Studio sessions are maintained with encrypted cookies (no need to log in repeatedly) - **Secure logout** — proper RP-Initiated Logout support via Auth0's `/v2/logout` endpoint **Setup:** 1. Create a Regular Web Application in your Auth0 Dashboard 2. Configure the auth provider with your Auth0 credentials ```typescript import { MastraAuthAuth0 } from '@mastra/auth-auth0'; const auth = new MastraAuthAuth0({ domain: 'your-tenant.auth0.com', audience: 'https://your-api', // For Studio SSO login: clientId: process.env.AUTH0_CLIENT_ID, clientSecret: process.env.AUTH0_CLIENT_SECRET, session: { cookiePassword: process.env.AUTH0_COOKIE_PASSWORD }, }); ``` **Note:** This release includes updates to `@mastra/core` (ISSOProvider interface now supports async getLoginUrl) and `@mastra/server` (handles async login URLs). All three packages should be updated together. - Export the `Middleware` type from `@mastra/core/server` so server middleware can be declared in separate modules with contextual handler types. ([#17882](https://github.com/mastra-ai/mastra/pull/17882)) - Fixed signal providers losing memory access when harness forks agents per mode. Harness now propagates runtime services (memory, storage, etc.) to the base config agent during init so signal providers connected to it can deliver notifications. ([#18008](https://github.com/mastra-ai/mastra/pull/18008)) - Added full Studio authentication support for Clerk users. ([#16659](https://github.com/mastra-ai/mastra/pull/16659)) **What's new:** - **Studio SSO login** — your internal team can now sign in to Mastra Studio using their Clerk accounts via OAuth 2.0/OIDC - **JWT validation** — API requests with Clerk-issued JWTs are automatically validated - **Session persistence** — Studio sessions are maintained with encrypted cookies (no need to log in repeatedly) **Setup:** 1. Create an OAuth Application in your Clerk Dashboard 2. Configure the auth provider with your Clerk credentials ```typescript import { MastraAuthClerk } from '@mastra/auth-clerk'; const auth = new MastraAuthClerk({ jwksUri: process.env.CLERK_JWKS_URI, secretKey: process.env.CLERK_SECRET_KEY, publishableKey: process.env.CLERK_PUBLISHABLE_KEY, // For Studio SSO login: oauthClientId: process.env.CLERK_OAUTH_CLIENT_ID, oauthClientSecret: process.env.CLERK_OAUTH_CLIENT_SECRET, session: { cookiePassword: process.env.CLERK_COOKIE_PASSWORD }, }); ``` **Note:** This release includes updates to `@mastra/core` (ISSOProvider interface now supports async getLoginUrl) and `@mastra/server` (handles async login URLs). All three packages should be updated together. - Multimodal content returned from `toModelOutput` (e.g. images via `type: 'image-url'`) is now correctly preserved as native provider content instead of being stringified into a JSON string. Previously, tools returning `{ type: 'content', value: [...] }` with image parts would cause providers like OpenRouter and Mistral to receive a plain text string instead of multimodal content. ([#17879](https://github.com/mastra-ai/mastra/pull/17879)) - Fixed supervisor sub-agent tool preparation so memory tools are available when agents derive their thread and resource from default options. This prevents misleading "Skipping memory tools (no thread or resource context)" debug logs for correctly configured sub-agents. ([#17175](https://github.com/mastra-ai/mastra/pull/17175)) - Republished clean patch versions after compromised npm releases were published outside of the trusted release workflow. ([#18049](https://github.com/mastra-ai/mastra/pull/18049)) These packages must be released as clean versions higher than the compromised versions currently present on npm so semver ranges resolve to trusted tarballs. - Fix multi-instance evented-mode hangs caused by transport edge cases. ([#17860](https://github.com/mastra-ai/mastra/pull/17860)) Four related fixes that stop `agent.generate()` from hanging silently when multiple processes share a Unix socket pubsub broker: - **Broker election race (`EEXIST`)**: `UnixSocketPubSub#start()` now falls through to client mode when `listen()` throws `EEXIST` on macOS for a path that already exists as a regular file, matching the existing `EADDRINUSE` fallback. - **Local nack redelivery**: `#deliverLocal` now passes real `ack`/`nack` callbacks; nacked events are re-queued via `setTimeout` so consumer-side retries (e.g. SQLITE_BUSY backoff) can drain instead of being silently dropped. - **Broker-death retry (`EPIPE`)**: `#sendToBroker` retries up to 3 times with re-election between attempts so an in-flight publish survives a broker crash and re-election rotation. - **Workflow retry exhaustion surfaces terminally**: `WorkflowEventProcessor.handle` now tracks per-event delivery attempts and, after exhausting transport-level retries, publishes `workflow.fail` instead of returning `{ retry: true }` forever. This unblocks any caller awaiting `workflows-finish` (e.g. `agent.generate()`) so the original error reaches the user rather than hanging. - **Workflow-event push subscription propagates nack**: the `'workflows'` topic callback in `Mastra` now calls `nack()` when the processor returns `{ ok: false, retry: true }`, enabling the transport-level redelivery loop above. - Await `storage.init()` before workers start in `startWorkers()`. ([#17920](https://github.com/mastra-ai/mastra/pull/17920)) The scheduler worker runs an immediate warm-up tick on start, which can dispatch an internal scheduled workflow (the notification dispatcher, enabled by default) and persist a workflow snapshot. Without awaiting `storage.init()` first, that write could race the lazy initialization that creates the `mastra_workflow_snapshot` table, surfacing as repeated `SQLITE_ERROR: no such table: mastra_workflow_snapshot` on SQL stores like libSQL (#17905). `init()` is idempotent and a no-op when storage init is disabled. - Keep the agent loop alive after a tool suspension (e.g. `ask_user`, `request_access`). ([#17990](https://github.com/mastra-ai/mastra/pull/17990)) Two related fixes that stop the agent from appearing to quit mid-task right after a suspended tool resumes: - **Harness resume step budget**: `resumeStream()` was called without a step budget, so a resumed run merged over the agent's small default `maxSteps` and stopped after a single step. The harness now threads a shared set of run options (`maxSteps`, `savePerStep`, `modelSettings`, etc.) into both the initial stream and the resume via one helper, so the two paths can no longer drift. - **LibSQL `busy_timeout` survival**: bump `@libsql/client` to `^0.17.4` and add a configurable connection timeout so `PRAGMA busy_timeout` survives connections created after `transaction()` (libsql-client-ts#288/#345). This reduces `SQLITE_BUSY` retry-exhaustion stalls under the evented engine's concurrent writes. - Moved MastraCode model catalog and gateway-backed model resolution out of the core harness and into a class-backed MastraCode gateway, with model IDs resolving directly to gateway-created provider instances. ([#17913](https://github.com/mastra-ai/mastra/pull/17913)) - Fixed LSP diagnostics always returning empty arrays on Windows when using `lsp: true` in Workspace. ([#17816](https://github.com/mastra-ai/mastra/pull/17816)) Previously, `waitForDiagnostics` returned `[]` after the full timeout on Windows even when the language server published non-empty diagnostics. This affected any LSP server emitting VS Code-style URIs (e.g. lua-language-server). Now diagnostics are correctly returned regardless of how the language server encodes the file URI. Fixes #17813 - Added gateway support to harness v0 model resolution, auth checks, and model discovery. ([#17913](https://github.com/mastra-ai/mastra/pull/17913)) - Fixed workflow snapshot records leaking in storage for every agent run. Completed agent runs left behind stale "pending" or "suspended" records indefinitely. These internal records are now removed when an agent run finishes, fails, or is declined. A "suspended" status in workflow snapshot storage now reliably means the run is actually resumable. ([#17858](https://github.com/mastra-ai/mastra/pull/17858)) - Fixed agent runs hanging or stopping silently when a model refuses a turn. Anthropic models such as claude-fable-5 can block a turn server-side and return a refusal, which the AI SDK surfaces as a `content-filter` finish reason. ([#17893](https://github.com/mastra-ai/mastra/pull/17893)) **Fixed the hang:** the agentic loop treated `content-filter` as "keep going", so it re-sent the same request and re-triggered the refusal on every step — looping until `maxSteps`, or forever when `maxSteps` was unset. `content-filter` is now treated as a terminal finish reason (alongside `stop`, `error`, and `length`), so the run stops instead of hanging. **Surfaced a clear terminal state:** when a run ends on `content-filter`, `error`, or `length`, it now finalizes into an explicit error state, emits an error event with diagnostic context (including the provider's stop details when available), and reports `agent_end` with reason `'error'` — instead of ending on an empty assistant message that looked like a silent stop. **Avoided the refusal in the first place:** when the harness runs `claude-fable-5`, it now automatically enables Anthropic's server-side fallback to `claude-opus-4-8` (`providerOptions.anthropic.fallbacks`). If fable-5's safety classifiers block a turn, Anthropic transparently retries it on the fallback model and returns that answer. If the whole chain still refuses, the run ends on the terminal `content-filter` error state described above. **Made the fallback visible:** when a turn is served by a fallback model (reported via `fallback_message` entries in `providerMetadata.anthropic.iterations`), the harness now emits an `info` event naming the model that actually generated the response, so users know the selected model declined the turn. **Attributed tracing to the serving model:** the `MODEL_GENERATION` span's `responseModel` attribute now reports the fallback model when a server-side fallback served the turn, so tracing exporters (Langfuse, etc.) attribute usage and cost to the model that actually generated the response instead of the requested model. - Fixed harness modes so they can reuse configured agents and resolve their default models before running. ([#17892](https://github.com/mastra-ai/mastra/pull/17892)) - Fixed base64-encoded images failing when sent to Gemini through withMastra. Images now reach the provider in the correct format, matching the behavior of calling generateText without withMastra. ([#17976](https://github.com/mastra-ai/mastra/pull/17976)) - License validation for EE features (RBAC, ACL, FGA, SSO) now checks your license key against the Mastra license server instead of validating it locally. ([#17859](https://github.com/mastra-ai/mastra/pull/17859)) **What changes for you** - Set `MASTRA_LICENSE_KEY` to your license key. `MASTRA_EE_LICENSE` continues to work as a supported legacy alias, so existing deployments are unaffected. - RBAC, ACL, and FGA are now enabled per-feature based on the entitlements your license plan includes, rather than a single blanket license check. - If the license server is briefly unreachable, previously validated licenses keep working (72-hour grace period), and validation never blocks startup — it runs in the background. - Development environments (`NODE_ENV` not set to production) are unaffected and keep full EE access without a license. - Add alpha browser video recording primitives under `@mastra/core/browser`. The new recording helper exposes `browser_record` and `browser_record_caption`, captures browser screencast frames, burns in short captions, and saves Motion-JPEG AVI videos without requiring `ffmpeg`. ([#17028](https://github.com/mastra-ai/mastra/pull/17028)) Example: ```ts import { createBrowserRecordingTools } from '@mastra/core/browser'; const tools = createBrowserRecordingTools(browser, { outputDir: './browser-recordings', }); ``` - Added validation for harness mode configuration so duplicate mode IDs, conflicting tool settings, and invalid transitions are rejected early. ([#17892](https://github.com/mastra-ai/mastra/pull/17892)) - Fixed cross-instance output leaking between mastracode TUI tabs after /new. The /new command now fully unsubscribes from the old thread's PubSub topic instead of only aborting the current run, preventing another mc instance on the same thread from pushing events into the detached TUI. ([#17883](https://github.com/mastra-ai/mastra/pull/17883)) - Fixed skill updates creating duplicate versions when a snapshot had not meaningfully changed. Comparison previously relied on `JSON.stringify`, so reordered object keys (common with PostgreSQL JSONB) or optional fields round-tripping between `undefined` and `null` looked like changes. Skill snapshots are now compared by value, so repeated no-op publish/update cycles no longer increment the version number. ([#16811](https://github.com/mastra-ai/mastra/pull/16811)) - Updated dependencies [[`9b1adf7`](https://github.com/mastra-ai/mastra/commit/9b1adf7f39943c869182106bc4016e793b3304ac)]: - @mastra/schema-compat@1.2.12 ## 1.42.0 ### Minor Changes - You can now execute workflows, tools, and memory thread checks as a trusted actor, such as a background job or scheduled task. Pass an `actor` object to identify the system process making the call while keeping fine-grained authorization checks tenant-scoped. ([#17484](https://github.com/mastra-ai/mastra/pull/17484)) ```ts const actor = { actorKind: 'system', sourceWorkflow: 'nightly-sync' } as const; await workflow.execute({ ...executeOptions, actor }); await tool.execute(input, { ...toolOptions, actor }); await MastraMemory.checkThreadFGA({ ...threadFGAOptions, actor }); ``` - Added an actor signal to core FGA checks for trusted server-side membership bypasses. ([#17483](https://github.com/mastra-ai/mastra/pull/17483)) ```ts const actor = { actorKind: 'system', sourceWorkflow: 'nightly-workflow' } as const; await checkFGA({ ...fgaOptions, requestContext, actor }); await requireFGA({ ...fgaOptions, requestContext, actor }); ``` - Enabled persistent storage for durable harness sessions across storage backends. Harness v1 can now resolve its session store from a configured storage adapter. ([#17712](https://github.com/mastra-ai/mastra/pull/17712)) - Added the `actor` option to agent `generate()` and `stream()` invocations so trusted background work can run without a JWT or human membership. ([#17487](https://github.com/mastra-ai/mastra/pull/17487)) ```ts const requestContext = new RequestContext(); requestContext.set('organizationId', 'org_123'); await agent.generate('Process daily report', { requestContext, actor: { actorKind: 'system', sourceWorkflow: 'daily-report-cron' }, }); ``` Mastra denies trusted actor FGA checks when the request context does not include an `organizationId`. - Added SignalProvider abstraction for building notification signal providers. Enables declarative signal wiring in Agent config with built-in subscription tracking, polling lifecycle, and webhook support. Includes WebhookSignalProvider as a proof-of-concept. ([#17577](https://github.com/mastra-ai/mastra/pull/17577)) **Writing a signal provider:** ```ts import { SignalProvider } from '@mastra/core/signals'; import type { SignalSubscription } from '@mastra/core/signals'; class SlackSignalProvider extends SignalProvider<'slack-signals'> { readonly id = 'slack-signals' as const; readonly pollInterval = 30_000; // poll every 30s async poll(subscriptions: SignalSubscription[]) { for (const sub of subscriptions) { const messages = await fetchNewMessages(sub.externalResourceId); if (messages.length > 0) { await this.notify( { source: 'slack', kind: 'new-message', summary: `${messages.length} new messages` }, { threadId: sub.threadId, resourceId: sub.resourceId }, ); } } } } ``` **Declarative wiring:** ```ts import { Agent } from '@mastra/core/agent'; const agent = new Agent({ signals: [new SlackSignalProvider()], // ... other config }); ``` - **Added** optional `getUsers(userIds)` batch lookup method to `IUserProvider`. Auth providers can implement it to resolve multiple users in a single call; providers that don't implement it continue to work via per-id `getUser` fallback. ([#17205](https://github.com/mastra-ai/mastra/pull/17205)) ```ts // optional batch lookup, returns results positionally aligned to userIds const users = await provider.getUsers?.(['u_1', 'u_2', 'u_3']); ``` - Added MCP server Fine-Grained Authorization mapping overrides for tool authorization. ([#17529](https://github.com/mastra-ai/mastra/pull/17529)) Use the new `fga` option on `MCPServer` to customize the resource and permission mappings used for `tools/list` and `tools/call` checks without changing the Mastra instance-level `tool` mapping used by internal agent and workflow tool execution. ```ts const server = new MCPServer({ name: 'My Server', version: '1.0.0', tools: { getData }, fga: { resourceMapping: { tool: { fgaResourceType: 'user', deriveId: ({ user }) => user.id, }, }, permissionMapping: { 'tools:execute': 'read', }, }, }); ``` - Made the `ask_user` built-in tool agent-agnostic and removed the Harness question channel in favor of native tool suspension. ([#17806](https://github.com/mastra-ai/mastra/pull/17806)) `ask_user` now pauses through the same tool-suspension primitive used by every other interactive tool, so it works on any agent — not just inside a Harness. The Harness no longer has a separate question channel; instead it surfaces these pauses through the generic `tool_suspended` event and resumes them with `respondToToolSuspension`. **Breaking changes** - Removed `harness.respondToQuestion(...)`. Use `harness.respondToToolSuspension(...)` instead. - Removed the `ask_question` event. Listen for `tool_suspended` and read the question from `event.suspendPayload`. - Removed `registerQuestion` from `HarnessRequestContext`, the `HarnessDisplayState.pendingQuestion` field, and the `HarnessQuestionAnswer`, `HarnessQuestionOption`, and `HarnessQuestionSelectionMode` types. - `HarnessDisplayState.pendingSuspension` (a single object or `null`) is now `HarnessDisplayState.pendingSuspensions`, a `Map` keyed by `toolCallId`. This lets the display state hold several parked prompts at once, so resuming one parallel `ask_user` no longer hides the others. Read a specific prompt with `displayState.pendingSuspensions.get(toolCallId)`. `respondToToolSuspension` accepts an optional `toolCallId` so concurrently suspended tools (for example, parallel `ask_user` calls) can each be answered independently. `harness.abort()` now clears any pending tool suspensions, so a run parked in a `suspend()` (e.g. an unanswered `ask_user`) can be aborted instead of staying parked forever. A new `harness.hasPendingSuspensions()` method reports whether the harness is awaiting a resume — useful because a suspended run nulls its internal abort controller, so `isRunning()` returns `false` while the run is still pending. **Before** ```typescript harness.subscribe(event => { if (event.type === 'ask_question') { harness.respondToQuestion({ questionId: event.questionId, answer: 'Yes' }); } }); ``` **After** ```typescript harness.subscribe(event => { if (event.type === 'tool_suspended' && event.toolName === 'ask_user') { const { question } = event.suspendPayload as { question: string }; harness.respondToToolSuspension({ toolCallId: event.toolCallId, resumeData: 'Yes' }); } }); ``` - Made the `submit_plan` built-in tool agent-agnostic and removed the Harness plan-approval channel in favor of native tool suspension. ([#17817](https://github.com/mastra-ai/mastra/pull/17817)) `submit_plan` now pauses through the same tool-suspension primitive used by every other interactive tool, so it works on any agent — not just inside a Harness. A plain Agent (e.g. in Studio or a customer app) can render the plan and resume the tool with `agent.resumeStream({ action, feedback })`. The Harness surfaces the pause through the generic `tool_suspended` event and resumes it with `respondToToolSuspension`; on approval it additionally switches to its default (execution) mode. **Breaking changes** - Removed `harness.respondToPlanApproval(...)`. Resume a submitted plan with `harness.respondToToolSuspension({ toolCallId, resumeData: { action, feedback } })`. - Removed the `plan_approval_required` and `plan_approved` events. Listen for `tool_suspended` with `event.toolName === 'submit_plan'` and read `{ title, plan }` from `event.suspendPayload`. - Removed `registerPlanApproval` from `HarnessRequestContext` and the `HarnessDisplayState.pendingPlanApproval` field. A suspended `submit_plan` now appears as `HarnessDisplayState.pendingSuspension` like any other suspended tool. **Before** ```typescript harness.subscribe(event => { if (event.type === 'plan_approval_required') { harness.respondToPlanApproval({ planId: event.planId, response: { action: 'approved' } }); } }); ``` **After** ```typescript harness.subscribe(event => { if (event.type === 'tool_suspended' && event.toolName === 'submit_plan') { const { title, plan } = event.suspendPayload as { title: string; plan: string }; harness.respondToToolSuspension({ toolCallId: event.toolCallId, resumeData: { action: 'approved' } }); } }); ``` - Add batching primitives to the PubSub abstraction. ([#16482](https://github.com/mastra-ai/mastra/pull/16482)) **New options** - `SubscribeOptions.batch` (`SubscribeBatchOptions`): opt in to coalesced delivery on a per-subscriber basis. Fields: `maxSize`, `maxWaitMs`, `minIntervalMs`, `isImmediate`, `coalesce`, `maxBufferSize`, `overflow`. - `EventEmitterPubSub` constructor accepts optional `EventEmitterPubSubOptions` with a `logger` for batched-delivery error diagnostics. **Example** ```ts import { EventEmitterPubSub } from '@mastra/core/events'; const pubsub = new EventEmitterPubSub(); await pubsub.subscribe( 'agent-events', event => { // delivered in coalesced batches; the cb is still invoked once per event }, { batch: { maxSize: 10, // flush once 10 events have queued maxWaitMs: 500, // ...or after 500ms, whichever comes first }, }, ); ``` **New exports** - `SubscribeBatchOptions` type. - `PubSub.supportsNativeBatching` — advertises whether an adapter honors `options.batch` internally. **Behavior** - `EventEmitterPubSub.supportsNativeBatching === true`. Batched subscribers receive coalesced delivery driven by an in-memory buffer governed by `maxSize` / `maxWaitMs` / `minIntervalMs` / `isImmediate` / `coalesce` / `overflow` / `maxBufferSize`. - `EventEmitterPubSub.flush()` drains every batched subscriber buffer and waits for any pending nack redeliveries before resolving. Non-callback rejections surface through the configured `logger` instead of being swallowed. - `CachingPubSub` is transparent to batching: it forwards `options` (including `batch`) to its inner PubSub and forwards `supportsNativeBatching` from the inner. Whether batching is honored depends entirely on the wrapped transport. **Contract notes** - `SubscribeBatchOptions.coalesce` must return a subset of its input array by reference identity. Returning freshly-constructed `Event` objects (even with matching `id`) is treated as a contract violation: the batching layer can't route `ack`/`nack` to original transport handles for manufactured events, so the entire batch is discarded and every original event is acked as dropped. If you need merged payloads, build them in the subscriber callback after delivery. - `flush()` is best-effort: a successful resolution does not guarantee every subscriber callback succeeded. Per-event errors surface via the configured logger. - Added agent and workspace tool hooks for applications that need to run logic before and after tool calls execute. Mastra Code now uses agent hooks so hook handlers run for built-in workspace tools as well as dynamic tools. ([#17637](https://github.com/mastra-ai/mastra/pull/17637)) **Example** ```ts const agent = new Agent({ name: 'Support Agent', instructions: 'Help users.', model, hooks: { beforeToolCall: ({ toolName, input }) => { console.log(`Running ${toolName}`, input); }, afterToolCall: ({ toolName, output, error }) => { console.log(`Finished ${toolName}`, { output, error }); }, }, }); const workspace = new Workspace({ tools: { hooks: { beforeToolCall: ({ toolName, workspaceToolName, input }) => { console.log(`Running ${toolName} from ${workspaceToolName}`, input); }, }, }, }); ``` - Added interface-first model gateways while keeping the existing `MastraModelGateway` base class backwards compatible. ([#17608](https://github.com/mastra-ai/mastra/pull/17608)) Added `MastraModelGatewayInterface` for plain object/custom gateway implementations and optional gateway `resolveAuth` hooks. Moved MastraCode gateway-routed OAuth model construction into a custom Mastra gateway so `ModelRouterLanguageModel` can route through gateway `resolveAuth` and provider-specific `resolveLanguageModel` behavior. **Usage:** ```typescript import { MastraModelGatewayInterface, ModelRouterLanguageModel } from '@mastra/core/llm'; const myGateway: MastraModelGatewayInterface = { id: 'my-gateway', name: 'My Gateway', async fetchProviders() { return {}; }, buildUrl() { return 'https://api.example.com'; }, async getApiKey() { return process.env.API_KEY ?? ''; }, // Optional: own authentication lookup async resolveAuth(request) { return { apiKey: process.env.API_KEY, source: 'gateway' }; }, async resolveLanguageModel({ modelId, providerId, apiKey }) { // Return an AI SDK language model instance }, }; // Register and route through the gateway const router = new ModelRouterLanguageModel({ modelId: 'my-gateway/provider/model' }, [myGateway]); ``` **Additional changes in this release:** - Inline three-tier auth resolution (explicit → gateway.resolveAuth → legacy getApiKey) into `ModelRouterLanguageModel.resolveAuth` and deprecate the standalone `resolveModelAuth` helper. - Fix `defaultGateways` deduplication in the `Mastra` class to use `getGatewayId(gateway)` instead of registry keys. - Remove no-op `resolveModelId` identity function in mastracode in favor of direct usage. - Fix `defaultNameGenerator` regex in `_llm-recorder` to anchor directory matches to path boundaries (prevents false matches like `-auth` suffixes). - Make the task tools (`task_write`, `task_update`, `task_complete`, `task_check`) agent-agnostic. ([#17820](https://github.com/mastra-ai/mastra/pull/17820)) The task tools no longer depend on the Harness request context. The task list is now held in a generic, thread-scoped **`threadState` storage domain** (`ThreadStateStorage`) — the source of truth — and projected onto the agent **state-signal** lane (`stateId: "tasks"`) by the new `TaskStateProcessor`, so they work on any `Agent` and not only inside the Harness. A new storage domain `threadState` is registered on `MastraStorage` (accessible via `storage.getStore("threadState")`). It is keyed by `(threadId, type)` so it can hold any per-thread state; the task list lives under `type: "task"` and the domain is intentionally generic so other agent-scoped state (e.g. `"goal"`) can reuse it. The composite store always wires an `InMemoryThreadStateStorage` by default, so task tracking works out of the box without configuring a backend, and a durable backend (`@mastra/libsql`'s `ThreadStateLibSQL`) persists the list across process restarts. The tools read/write it within a run via `context.mastra.getStorage().getStore("threadState")`, scoped by the run's `threadId`. The state-signal projection is delta-first (modelled on working memory), cache-aware, and observational-memory-aware: - The first projection of a populated list (and every compaction) is a full `` **snapshot**; each later mutation emits a small `` **delta** carrying only that turn's add/remove/update ops, so a large task list is not re-sent in full on every change. - The task list is carried on the state-signal lane instead of being injected into the cached system prompt, so task updates no longer invalidate the prompt-cache prefix. - The base snapshot and its deltas accumulate in the window; the processor re-snapshots once enough deltas accumulate (compaction) to bound window growth, and whenever observational-memory truncation drops the base snapshot from the window (deltas are meaningless without their base), reading the `threadState` store so the agent never loses track of its tasks. The task tools and the processor require a memory-backed thread. On a run that is not memory backed (no `threadId`/`resourceId`), the task tools no-op and return a result explaining that task tracking requires agent memory. For the simplest setup, register `TaskSignalProvider` via the agent's `signals` array — it bundles the task tools and the `TaskStateProcessor` so they cannot get out of sync: ```ts import { Agent } from '@mastra/core/agent'; import { TaskSignalProvider } from '@mastra/core/signals'; const agent = new Agent({ name, instructions, model, memory, signals: [new TaskSignalProvider()] }); ``` The Agent merges the tools into its toolset and registers the processor automatically. New exports from `@mastra/core/storage`: `ThreadStateStorage`, `InMemoryThreadStateStorage`, and the `TaskRecord` type. New exports from `@mastra/core/tools`: `taskWriteTool`, `taskUpdateTool`, `taskCompleteTool`, `taskCheckTool`, `TaskStateProcessor`, and the task helpers/types (`assignTaskIds`, `summarizeTaskCheck`, `TaskItem`, `TaskItemSnapshot`, `TaskCheckSummary`, `TaskCheckResult`, etc.). New export from `@mastra/core/signals`: `TaskSignalProvider`, alongside the other signal providers. The Harness continues to re-export the task tools, so existing imports and toolset identity are unchanged. Internal behavior change: the Harness no longer stores the task list in session state. Task mutations still emit the `task_updated` display event, so the Harness display snapshot and any pinned task UI are unaffected. To adopt the new behavior on a plain agent (with `Memory` and a Mastra `storage`), register `new TaskSignalProvider()` in `signals` — or, for manual control, add `new TaskStateProcessor()` to `inputProcessors` alongside the task tools. - Add workspace registry cleanup and shutdown destruction. ([#17661](https://github.com/mastra-ai/mastra/pull/17661)) Mastra now exposes `removeWorkspace(id, { destroy })` for removing runtime workspaces from the registry. When `destroy` is true, the workspace is destroyed before removal and remains registered if destruction fails. `mastra.shutdown()` now destroys registered workspaces before closing storage and unregisters only the workspaces that clean up successfully. Workspace tool execution also updates `lastAccessedAt`, so runtime activity is tracked beyond search operations. ```ts await mastra.removeWorkspace('workspace-123', { destroy: true }); // destroys, then unregisters await mastra.removeWorkspace('workspace-456'); // unregisters only; caller owns destroy await mastra.shutdown(); // destroys registered workspaces, then closes storage ``` - Added two options to `ToolSearchProcessor` for cheaper, more cache-friendly tool discovery. ([#17691](https://github.com/mastra-ai/mastra/pull/17691)) **`autoLoad`: one-turn discovery** Set `search.autoLoad` so that `search_tools` activates the matching tools immediately, removing the separate `load_tool` round-trip. The model searches once and can call a discovered tool on its next turn instead of searching, loading, then calling. This cuts one model turn (and a full prompt replay) per discovery. ```typescript import { ToolSearchProcessor } from '@mastra/core/processors'; const toolSearch = new ToolSearchProcessor({ tools: allTools, search: { topK: 3, autoLoad: true }, }); ``` **`storage`: opt-in `'context'` mode** Choose where loaded-tool state lives. The default `'in-memory'` keeps the original behavior. The new opt-in `'context'` mode derives loaded tools from the conversation messages, so it is restart-safe, needs no memory configuration, and de-loads a tool once its discovery result is no longer in the messages. ```typescript const toolSearch = new ToolSearchProcessor({ tools: allTools, storage: 'context', }); ``` Both modes are cache-friendly when loading tools, since loads are append-only and keep the cached prompt prefix stable. The default `'in-memory'` store still shares a single `'default'` entry across anonymous (no thread ID) requests; use `storage: 'context'` to keep anonymous requests isolated and derive loaded tools from the conversation messages. ### Patch Changes - Fixed ConsoleLogger.warn() to correctly call console.warn() instead of console.info(), ensuring warn-level logs are routed to stderr and properly classified by log backends (e.g. Datadog) ([#17623](https://github.com/mastra-ai/mastra/pull/17623)) - Fixed silent error swallowing when agent.stream() fails during idle-start, continuation, or pending-idle signal processing. Errors now propagate to the subscription stream via a new run-failed event, so harness consumers (like MastraCode) surface proper error events instead of silently completing with no response. ([#17727](https://github.com/mastra-ai/mastra/pull/17727)) - **Added** author enrichment to the stored-agents list and get handlers. When an auth provider is configured, each agent record now includes a resolved `author` object alongside the existing `authorId`: ([#17205](https://github.com/mastra-ai/mastra/pull/17205)) ```ts { id: 'agent_…', authorId: 'user_…', id, name?, email?, avatarUrl? } // new, optional // … } ``` Lookups are deduplicated per request and use the provider's `getUsers` batch method when available, falling back to per-id `getUser` calls otherwise. The field is omitted when no auth provider is configured or the ID can't be resolved, so existing clients keep working unchanged. - Fixed ingestion of client-side tool tracing from AI SDK v6 `toolMetadata` when messages return through chatRoute. ([#17202](https://github.com/mastra-ai/mastra/pull/17202)) - Fixed durable and evented agent streams so tool-call steps continue to final text when maxSteps allows it. ([#16679](https://github.com/mastra-ai/mastra/pull/16679)) - Clarified Studio config JSDoc to explain dual auth opt-in behavior. ([#17722](https://github.com/mastra-ai/mastra/pull/17722)) - Fixed a multi-instance hang in evented execution mode. When two Mastra processes shared a UnixSocketPubSub broker, the agent's evented run could fail with `AGENT_GENERATE_MALFORMED_RESULT`, `condition is not a function`, or hang silently with ECANCELED errors during shutdown. ([#17788](https://github.com/mastra-ai/mastra/pull/17788)) **What changed** - Internal workflow events (execution-workflow, agentic-loop, nested children walked via `parentWorkflow`), per-run watch streams (`workflow.events.v2.*`), and scheduler-spawned background runs (`sched_wf_*`) are now delivered only inside the publishing process and no longer cross the unix socket. - Live class instances on event payloads (e.g. the `MastraModelOutput` returned by an agent run) are preserved instead of being stripped by JSON serialization on the broker round-trip. **Why** Follow-up to the broker-side filter shipped in #17727. That filter ran after the publish frame had already been serialized, which stripped functions/streams from payloads and still fanned out cumulative `stepResults` blobs (often 9 MB+) to every connected client. Short-circuiting these publishes in-process removes the serialization round-trip entirely and lets the agent's run result survive intact. - Added anonymous, aggregated model token usage telemetry. When a Mastra server starts and observability metrics are enabled, the input and output token totals per provider and model are sent to Mastra's telemetry. Only aggregate token counts are collected — never prompts, responses, or message content. Opt out by setting MASTRA_TELEMETRY_DISABLED=1. ([#17750](https://github.com/mastra-ai/mastra/pull/17750)) - Plan due notification dispatches per thread from a single due snapshot so high-priority notifications that already emitted summaries are delivered in full before lower-priority notifications or summaries can wake the same thread. `Agent.sendNotificationSignal` also accepts an array of notification inputs so related notifications can be evaluated against the same initial thread state before any delivery wakes the thread. ([#17590](https://github.com/mastra-ai/mastra/pull/17590)) - - Use evented-workflow engine in the internal agentic loop. ([#16796](https://github.com/mastra-ai/mastra/pull/16796)) - Fix nested workflow resume with resumeLabel in evented-workflow engine. - Default to an in-memory store when no `storage` is configured on `Mastra`, and warn that it is not durable and a persistent storage adapter should be used in production. - Prevented user-created filesystem stored agents from being treated as code agents when they are not declared in the code Mastra instance. ([#17622](https://github.com/mastra-ai/mastra/pull/17622)) - Fixed per-item request context being dropped for inline experiment data. When running an experiment with inline `data`, each item's `requestContext` is now passed to the agent or workflow and merged over the global request context (per-item values win on key collisions), matching the behavior of storage-backed datasets. ([#17597](https://github.com/mastra-ai/mastra/pull/17597)) ```ts await dataset.startExperiment({ data: [{ input: { prompt: 'Hello' }, requestContext: { clinicId: 'clinic-1' } }], targetType: 'agent', targetId: 'support-agent', }); // Tools can now read clinicId from requestContext during inline experiments ``` - Fixed thread subscribers so they can join an already-running remote stream and receive new output without waiting for the next run. ([#17635](https://github.com/mastra-ai/mastra/pull/17635)) - Improved BM25 tokenization to support CJK (Japanese, Chinese, Korean) and other non-Latin languages. Added `TokenizeOptions.tokenizer` for plugging in custom tokenizers (e.g. n-gram, kuromoji). Workspace `bm25` config now accepts `tokenize` options for full control over how text is split into search tokens. ([#17640](https://github.com/mastra-ai/mastra/pull/17640)) **Before:** BM25 search returned no results for non-English content — CJK characters were silently stripped during tokenization. **After:** Non-Latin characters are preserved by default. Users can also plug in a custom tokenizer: ```ts new Workspace({ bm25: { tokenize: { tokenizer: myCustomCjkTokenizer, }, }, }); ``` Fixes #17636 - Added OTel span instrumentation for the hardcoded 10-second rate-limit backpressure sleep, making it visible in traces as a 'rate-limit-sleep' span with remainingTokens and delayMs metadata ([#17623](https://github.com/mastra-ai/mastra/pull/17623)) - Fixed medium-priority notification summaries so they wake idle agent threads when they are ready to deliver. ([#17590](https://github.com/mastra-ai/mastra/pull/17590)) - Fixed message part timestamps affecting transcript ordering by ensuring only message-level timestamps advance the ordering watermark. ([#17598](https://github.com/mastra-ai/mastra/pull/17598)) - Added a local development warning when Enterprise features are used without a valid license. ([#17694](https://github.com/mastra-ai/mastra/pull/17694)) - Fix `response.modelId` being overwritten with `undefined` in `step-finish` chunks when the upstream model stream omits `modelId` from `response-metadata`. The explicit empty-string fallback was placed before a `...otherMetadata` spread that could overwrite it with `undefined`. Moved the `modelId` assignment after the spread so the fallback always wins. ([#17795](https://github.com/mastra-ai/mastra/pull/17795)) This restores consistent `response.modelId === ''` behavior across both the direct and evented agentic-loop workflow paths. - Fix `Cannot get workflow run. Mastra storage is not initialized` debug log spam on agents that use memory or any input/output processors. ([#17571](https://github.com/mastra-ai/mastra/pull/17571)) #17344 fixed this for the internal `execution-workflow`, but agents with memory/processors also build an internal _processor_ workflow (`Agent.combineProcessorsIntoWorkflow`, run by `ProcessorRunner.executeWorkflowAsProcessor`) that never received the parent `Mastra` instance — so its `createRun()` → `getWorkflowRunById()` saw no storage and logged the noise on every run. The processor workflow now receives the parent `Mastra` instance and opts out of snapshot persistence (`shouldPersistSnapshot: () => false`), mirroring the execution-workflow fix. Follow-up to #17137 / #17344. - Fix `request_access` granting a path but the file staying unreadable with `EACCES`. After approving access, the very next tool (e.g. `view`) could still be rejected for the path that was just granted. The grant now reliably applies to the filesystem the agent's tools actually read from, and persists before the run resumes, so reads of an approved path succeed. ([#17806](https://github.com/mastra-ai/mastra/pull/17806)) - Update ai-sdk deps ([#17144](https://github.com/mastra-ai/mastra/pull/17144)) - Fixed browser state signals so they no longer show 'Browser is closed' before the browser is used. Added attribution for browser closes and active URL changes when the agent or user caused them. ([#17631](https://github.com/mastra-ai/mastra/pull/17631)) - **Added** optional `author` field on `StoredAgentResponse` so consumers can render the resolved author (name, email, avatar) returned by stored-agent list and detail endpoints. ([#17205](https://github.com/mastra-ai/mastra/pull/17205)) - Fixed Windows absolute paths breaking workspace skill discovery. `WorkspaceSkills` split paths on `/` only, so a skill referenced by an absolute Windows path loaded with the wrong name (the full path string or `unknown`) or failed to load, and skills under `node_modules` were misclassified as local instead of external. ([#17671](https://github.com/mastra-ai/mastra/pull/17671)) Skills passed by absolute path now resolve the same way on Windows and POSIX: ```ts new Workspace({ skills: ['C:\\Users\\me\\skills\\my-skill'], }); ``` ## 1.42.0-alpha.4 ### Minor Changes - You can now execute workflows, tools, and memory thread checks as a trusted actor, such as a background job or scheduled task. Pass an `actor` object to identify the system process making the call while keeping fine-grained authorization checks tenant-scoped. ([#17484](https://github.com/mastra-ai/mastra/pull/17484)) ```ts const actor = { actorKind: 'system', sourceWorkflow: 'nightly-sync' } as const; await workflow.execute({ ...executeOptions, actor }); await tool.execute(input, { ...toolOptions, actor }); await MastraMemory.checkThreadFGA({ ...threadFGAOptions, actor }); ``` - Added an actor signal to core FGA checks for trusted server-side membership bypasses. ([#17483](https://github.com/mastra-ai/mastra/pull/17483)) ```ts const actor = { actorKind: 'system', sourceWorkflow: 'nightly-workflow' } as const; await checkFGA({ ...fgaOptions, requestContext, actor }); await requireFGA({ ...fgaOptions, requestContext, actor }); ``` - Enabled persistent storage for durable harness sessions across storage backends. Harness v1 can now resolve its session store from a configured storage adapter. ([#17712](https://github.com/mastra-ai/mastra/pull/17712)) - Added the `actor` option to agent `generate()` and `stream()` invocations so trusted background work can run without a JWT or human membership. ([#17487](https://github.com/mastra-ai/mastra/pull/17487)) ```ts const requestContext = new RequestContext(); requestContext.set('organizationId', 'org_123'); await agent.generate('Process daily report', { requestContext, actor: { actorKind: 'system', sourceWorkflow: 'daily-report-cron' }, }); ``` Mastra denies trusted actor FGA checks when the request context does not include an `organizationId`. - Added MCP server Fine-Grained Authorization mapping overrides for tool authorization. ([#17529](https://github.com/mastra-ai/mastra/pull/17529)) Use the new `fga` option on `MCPServer` to customize the resource and permission mappings used for `tools/list` and `tools/call` checks without changing the Mastra instance-level `tool` mapping used by internal agent and workflow tool execution. ```ts const server = new MCPServer({ name: 'My Server', version: '1.0.0', tools: { getData }, fga: { resourceMapping: { tool: { fgaResourceType: 'user', deriveId: ({ user }) => user.id, }, }, permissionMapping: { 'tools:execute': 'read', }, }, }); ``` - Made the `ask_user` built-in tool agent-agnostic and removed the Harness question channel in favor of native tool suspension. ([#17806](https://github.com/mastra-ai/mastra/pull/17806)) `ask_user` now pauses through the same tool-suspension primitive used by every other interactive tool, so it works on any agent — not just inside a Harness. The Harness no longer has a separate question channel; instead it surfaces these pauses through the generic `tool_suspended` event and resumes them with `respondToToolSuspension`. **Breaking changes** - Removed `harness.respondToQuestion(...)`. Use `harness.respondToToolSuspension(...)` instead. - Removed the `ask_question` event. Listen for `tool_suspended` and read the question from `event.suspendPayload`. - Removed `registerQuestion` from `HarnessRequestContext`, the `HarnessDisplayState.pendingQuestion` field, and the `HarnessQuestionAnswer`, `HarnessQuestionOption`, and `HarnessQuestionSelectionMode` types. - `HarnessDisplayState.pendingSuspension` (a single object or `null`) is now `HarnessDisplayState.pendingSuspensions`, a `Map` keyed by `toolCallId`. This lets the display state hold several parked prompts at once, so resuming one parallel `ask_user` no longer hides the others. Read a specific prompt with `displayState.pendingSuspensions.get(toolCallId)`. `respondToToolSuspension` accepts an optional `toolCallId` so concurrently suspended tools (for example, parallel `ask_user` calls) can each be answered independently. `harness.abort()` now clears any pending tool suspensions, so a run parked in a `suspend()` (e.g. an unanswered `ask_user`) can be aborted instead of staying parked forever. A new `harness.hasPendingSuspensions()` method reports whether the harness is awaiting a resume — useful because a suspended run nulls its internal abort controller, so `isRunning()` returns `false` while the run is still pending. **Before** ```typescript harness.subscribe(event => { if (event.type === 'ask_question') { harness.respondToQuestion({ questionId: event.questionId, answer: 'Yes' }); } }); ``` **After** ```typescript harness.subscribe(event => { if (event.type === 'tool_suspended' && event.toolName === 'ask_user') { const { question } = event.suspendPayload as { question: string }; harness.respondToToolSuspension({ toolCallId: event.toolCallId, resumeData: 'Yes' }); } }); ``` - Made the `submit_plan` built-in tool agent-agnostic and removed the Harness plan-approval channel in favor of native tool suspension. ([#17817](https://github.com/mastra-ai/mastra/pull/17817)) `submit_plan` now pauses through the same tool-suspension primitive used by every other interactive tool, so it works on any agent — not just inside a Harness. A plain Agent (e.g. in Studio or a customer app) can render the plan and resume the tool with `agent.resumeStream({ action, feedback })`. The Harness surfaces the pause through the generic `tool_suspended` event and resumes it with `respondToToolSuspension`; on approval it additionally switches to its default (execution) mode. **Breaking changes** - Removed `harness.respondToPlanApproval(...)`. Resume a submitted plan with `harness.respondToToolSuspension({ toolCallId, resumeData: { action, feedback } })`. - Removed the `plan_approval_required` and `plan_approved` events. Listen for `tool_suspended` with `event.toolName === 'submit_plan'` and read `{ title, plan }` from `event.suspendPayload`. - Removed `registerPlanApproval` from `HarnessRequestContext` and the `HarnessDisplayState.pendingPlanApproval` field. A suspended `submit_plan` now appears as `HarnessDisplayState.pendingSuspension` like any other suspended tool. **Before** ```typescript harness.subscribe(event => { if (event.type === 'plan_approval_required') { harness.respondToPlanApproval({ planId: event.planId, response: { action: 'approved' } }); } }); ``` **After** ```typescript harness.subscribe(event => { if (event.type === 'tool_suspended' && event.toolName === 'submit_plan') { const { title, plan } = event.suspendPayload as { title: string; plan: string }; harness.respondToToolSuspension({ toolCallId: event.toolCallId, resumeData: { action: 'approved' } }); } }); ``` - Make the task tools (`task_write`, `task_update`, `task_complete`, `task_check`) agent-agnostic. ([#17820](https://github.com/mastra-ai/mastra/pull/17820)) The task tools no longer depend on the Harness request context. The task list is now held in a generic, thread-scoped **`threadState` storage domain** (`ThreadStateStorage`) — the source of truth — and projected onto the agent **state-signal** lane (`stateId: "tasks"`) by the new `TaskStateProcessor`, so they work on any `Agent` and not only inside the Harness. A new storage domain `threadState` is registered on `MastraStorage` (accessible via `storage.getStore("threadState")`). It is keyed by `(threadId, type)` so it can hold any per-thread state; the task list lives under `type: "task"` and the domain is intentionally generic so other agent-scoped state (e.g. `"goal"`) can reuse it. The composite store always wires an `InMemoryThreadStateStorage` by default, so task tracking works out of the box without configuring a backend, and a durable backend (`@mastra/libsql`'s `ThreadStateLibSQL`) persists the list across process restarts. The tools read/write it within a run via `context.mastra.getStorage().getStore("threadState")`, scoped by the run's `threadId`. The state-signal projection is delta-first (modelled on working memory), cache-aware, and observational-memory-aware: - The first projection of a populated list (and every compaction) is a full `` **snapshot**; each later mutation emits a small `` **delta** carrying only that turn's add/remove/update ops, so a large task list is not re-sent in full on every change. - The task list is carried on the state-signal lane instead of being injected into the cached system prompt, so task updates no longer invalidate the prompt-cache prefix. - The base snapshot and its deltas accumulate in the window; the processor re-snapshots once enough deltas accumulate (compaction) to bound window growth, and whenever observational-memory truncation drops the base snapshot from the window (deltas are meaningless without their base), reading the `threadState` store so the agent never loses track of its tasks. The task tools and the processor require a memory-backed thread. On a run that is not memory backed (no `threadId`/`resourceId`), the task tools no-op and return a result explaining that task tracking requires agent memory. For the simplest setup, register `TaskSignalProvider` via the agent's `signals` array — it bundles the task tools and the `TaskStateProcessor` so they cannot get out of sync: ```ts import { Agent } from '@mastra/core/agent'; import { TaskSignalProvider } from '@mastra/core/signals'; const agent = new Agent({ name, instructions, model, memory, signals: [new TaskSignalProvider()] }); ``` The Agent merges the tools into its toolset and registers the processor automatically. New exports from `@mastra/core/storage`: `ThreadStateStorage`, `InMemoryThreadStateStorage`, and the `TaskRecord` type. New exports from `@mastra/core/tools`: `taskWriteTool`, `taskUpdateTool`, `taskCompleteTool`, `taskCheckTool`, `TaskStateProcessor`, and the task helpers/types (`assignTaskIds`, `summarizeTaskCheck`, `TaskItem`, `TaskItemSnapshot`, `TaskCheckSummary`, `TaskCheckResult`, etc.). New export from `@mastra/core/signals`: `TaskSignalProvider`, alongside the other signal providers. The Harness continues to re-export the task tools, so existing imports and toolset identity are unchanged. Internal behavior change: the Harness no longer stores the task list in session state. Task mutations still emit the `task_updated` display event, so the Harness display snapshot and any pinned task UI are unaffected. To adopt the new behavior on a plain agent (with `Memory` and a Mastra `storage`), register `new TaskSignalProvider()` in `signals` — or, for manual control, add `new TaskStateProcessor()` to `inputProcessors` alongside the task tools. - Add workspace registry cleanup and shutdown destruction. ([#17661](https://github.com/mastra-ai/mastra/pull/17661)) Mastra now exposes `removeWorkspace(id, { destroy })` for removing runtime workspaces from the registry. When `destroy` is true, the workspace is destroyed before removal and remains registered if destruction fails. `mastra.shutdown()` now destroys registered workspaces before closing storage and unregisters only the workspaces that clean up successfully. Workspace tool execution also updates `lastAccessedAt`, so runtime activity is tracked beyond search operations. ```ts await mastra.removeWorkspace('workspace-123', { destroy: true }); // destroys, then unregisters await mastra.removeWorkspace('workspace-456'); // unregisters only; caller owns destroy await mastra.shutdown(); // destroys registered workspaces, then closes storage ``` - Added two options to `ToolSearchProcessor` for cheaper, more cache-friendly tool discovery. ([#17691](https://github.com/mastra-ai/mastra/pull/17691)) **`autoLoad`: one-turn discovery** Set `search.autoLoad` so that `search_tools` activates the matching tools immediately, removing the separate `load_tool` round-trip. The model searches once and can call a discovered tool on its next turn instead of searching, loading, then calling. This cuts one model turn (and a full prompt replay) per discovery. ```typescript import { ToolSearchProcessor } from '@mastra/core/processors'; const toolSearch = new ToolSearchProcessor({ tools: allTools, search: { topK: 3, autoLoad: true }, }); ``` **`storage`: opt-in `'context'` mode** Choose where loaded-tool state lives. The default `'in-memory'` keeps the original behavior. The new opt-in `'context'` mode derives loaded tools from the conversation messages, so it is restart-safe, needs no memory configuration, and de-loads a tool once its discovery result is no longer in the messages. ```typescript const toolSearch = new ToolSearchProcessor({ tools: allTools, storage: 'context', }); ``` Both modes are cache-friendly when loading tools, since loads are append-only and keep the cached prompt prefix stable. The default `'in-memory'` store still shares a single `'default'` entry across anonymous (no thread ID) requests; use `storage: 'context'` to keep anonymous requests isolated and derive loaded tools from the conversation messages. ### Patch Changes - Fixed silent error swallowing when agent.stream() fails during idle-start, continuation, or pending-idle signal processing. Errors now propagate to the subscription stream via a new run-failed event, so harness consumers (like MastraCode) surface proper error events instead of silently completing with no response. ([#17727](https://github.com/mastra-ai/mastra/pull/17727)) - Fixed durable and evented agent streams so tool-call steps continue to final text when maxSteps allows it. ([#16679](https://github.com/mastra-ai/mastra/pull/16679)) - Clarified Studio config JSDoc to explain dual auth opt-in behavior. ([#17722](https://github.com/mastra-ai/mastra/pull/17722)) - Fixed a multi-instance hang in evented execution mode. When two Mastra processes shared a UnixSocketPubSub broker, the agent's evented run could fail with `AGENT_GENERATE_MALFORMED_RESULT`, `condition is not a function`, or hang silently with ECANCELED errors during shutdown. ([#17788](https://github.com/mastra-ai/mastra/pull/17788)) **What changed** - Internal workflow events (execution-workflow, agentic-loop, nested children walked via `parentWorkflow`), per-run watch streams (`workflow.events.v2.*`), and scheduler-spawned background runs (`sched_wf_*`) are now delivered only inside the publishing process and no longer cross the unix socket. - Live class instances on event payloads (e.g. the `MastraModelOutput` returned by an agent run) are preserved instead of being stripped by JSON serialization on the broker round-trip. **Why** Follow-up to the broker-side filter shipped in #17727. That filter ran after the publish frame had already been serialized, which stripped functions/streams from payloads and still fanned out cumulative `stepResults` blobs (often 9 MB+) to every connected client. Short-circuiting these publishes in-process removes the serialization round-trip entirely and lets the agent's run result survive intact. - Added anonymous, aggregated model token usage telemetry. When a Mastra server starts and observability metrics are enabled, the input and output token totals per provider and model are sent to Mastra's telemetry. Only aggregate token counts are collected — never prompts, responses, or message content. Opt out by setting MASTRA_TELEMETRY_DISABLED=1. ([#17750](https://github.com/mastra-ai/mastra/pull/17750)) - - Use evented-workflow engine in the internal agentic loop. ([#16796](https://github.com/mastra-ai/mastra/pull/16796)) - Fix nested workflow resume with resumeLabel in evented-workflow engine. - Default to an in-memory store when no `storage` is configured on `Mastra`, and warn that it is not durable and a persistent storage adapter should be used in production. - Added a local development warning when Enterprise features are used without a valid license. ([#17694](https://github.com/mastra-ai/mastra/pull/17694)) - Fix `response.modelId` being overwritten with `undefined` in `step-finish` chunks when the upstream model stream omits `modelId` from `response-metadata`. The explicit empty-string fallback was placed before a `...otherMetadata` spread that could overwrite it with `undefined`. Moved the `modelId` assignment after the spread so the fallback always wins. ([#17795](https://github.com/mastra-ai/mastra/pull/17795)) This restores consistent `response.modelId === ''` behavior across both the direct and evented agentic-loop workflow paths. - Fix `request_access` granting a path but the file staying unreadable with `EACCES`. After approving access, the very next tool (e.g. `view`) could still be rejected for the path that was just granted. The grant now reliably applies to the filesystem the agent's tools actually read from, and persists before the run resumes, so reads of an approved path succeed. ([#17806](https://github.com/mastra-ai/mastra/pull/17806)) - Update ai-sdk deps ([#17144](https://github.com/mastra-ai/mastra/pull/17144)) - Fixed Windows absolute paths breaking workspace skill discovery. `WorkspaceSkills` split paths on `/` only, so a skill referenced by an absolute Windows path loaded with the wrong name (the full path string or `unknown`) or failed to load, and skills under `node_modules` were misclassified as local instead of external. ([#17671](https://github.com/mastra-ai/mastra/pull/17671)) Skills passed by absolute path now resolve the same way on Windows and POSIX: ```ts new Workspace({ skills: ['C:\\Users\\me\\skills\\my-skill'], }); ``` ## 1.42.0-alpha.3 ### Minor Changes - **Added** optional `getUsers(userIds)` batch lookup method to `IUserProvider`. Auth providers can implement it to resolve multiple users in a single call; providers that don't implement it continue to work via per-id `getUser` fallback. ([#17205](https://github.com/mastra-ai/mastra/pull/17205)) ```ts // optional batch lookup, returns results positionally aligned to userIds const users = await provider.getUsers?.(['u_1', 'u_2', 'u_3']); ``` - Added interface-first model gateways while keeping the existing `MastraModelGateway` base class backwards compatible. ([#17608](https://github.com/mastra-ai/mastra/pull/17608)) Added `MastraModelGatewayInterface` for plain object/custom gateway implementations and optional gateway `resolveAuth` hooks. Moved MastraCode gateway-routed OAuth model construction into a custom Mastra gateway so `ModelRouterLanguageModel` can route through gateway `resolveAuth` and provider-specific `resolveLanguageModel` behavior. **Usage:** ```typescript import { MastraModelGatewayInterface, ModelRouterLanguageModel } from '@mastra/core/llm'; const myGateway: MastraModelGatewayInterface = { id: 'my-gateway', name: 'My Gateway', async fetchProviders() { return {}; }, buildUrl() { return 'https://api.example.com'; }, async getApiKey() { return process.env.API_KEY ?? ''; }, // Optional: own authentication lookup async resolveAuth(request) { return { apiKey: process.env.API_KEY, source: 'gateway' }; }, async resolveLanguageModel({ modelId, providerId, apiKey }) { // Return an AI SDK language model instance }, }; // Register and route through the gateway const router = new ModelRouterLanguageModel({ modelId: 'my-gateway/provider/model' }, [myGateway]); ``` **Additional changes in this release:** - Inline three-tier auth resolution (explicit → gateway.resolveAuth → legacy getApiKey) into `ModelRouterLanguageModel.resolveAuth` and deprecate the standalone `resolveModelAuth` helper. - Fix `defaultGateways` deduplication in the `Mastra` class to use `getGatewayId(gateway)` instead of registry keys. - Remove no-op `resolveModelId` identity function in mastracode in favor of direct usage. - Fix `defaultNameGenerator` regex in `_llm-recorder` to anchor directory matches to path boundaries (prevents false matches like `-auth` suffixes). ### Patch Changes - **Added** author enrichment to the stored-agents list and get handlers. When an auth provider is configured, each agent record now includes a resolved `author` object alongside the existing `authorId`: ([#17205](https://github.com/mastra-ai/mastra/pull/17205)) ```ts { id: 'agent_…', authorId: 'user_…', id, name?, email?, avatarUrl? } // new, optional // … } ``` Lookups are deduplicated per request and use the provider's `getUsers` batch method when available, falling back to per-id `getUser` calls otherwise. The field is omitted when no auth provider is configured or the ID can't be resolved, so existing clients keep working unchanged. - Fixed ingestion of client-side tool tracing from AI SDK v6 `toolMetadata` when messages return through chatRoute. ([#17202](https://github.com/mastra-ai/mastra/pull/17202)) - **Added** optional `author` field on `StoredAgentResponse` so consumers can render the resolved author (name, email, avatar) returned by stored-agent list and detail endpoints. ([#17205](https://github.com/mastra-ai/mastra/pull/17205)) ## 1.42.0-alpha.2 ### Patch Changes - Improved BM25 tokenization to support CJK (Japanese, Chinese, Korean) and other non-Latin languages. Added `TokenizeOptions.tokenizer` for plugging in custom tokenizers (e.g. n-gram, kuromoji). Workspace `bm25` config now accepts `tokenize` options for full control over how text is split into search tokens. ([#17640](https://github.com/mastra-ai/mastra/pull/17640)) **Before:** BM25 search returned no results for non-English content — CJK characters were silently stripped during tokenization. **After:** Non-Latin characters are preserved by default. Users can also plug in a custom tokenizer: ```ts new Workspace({ bm25: { tokenize: { tokenizer: myCustomCjkTokenizer, }, }, }); ``` Fixes #17636 ## 1.42.0-alpha.1 ### Minor Changes - Added agent and workspace tool hooks for applications that need to run logic before and after tool calls execute. Mastra Code now uses agent hooks so hook handlers run for built-in workspace tools as well as dynamic tools. ([#17637](https://github.com/mastra-ai/mastra/pull/17637)) **Example** ```ts const agent = new Agent({ name: 'Support Agent', instructions: 'Help users.', model, hooks: { beforeToolCall: ({ toolName, input }) => { console.log(`Running ${toolName}`, input); }, afterToolCall: ({ toolName, output, error }) => { console.log(`Finished ${toolName}`, { output, error }); }, }, }); const workspace = new Workspace({ tools: { hooks: { beforeToolCall: ({ toolName, workspaceToolName, input }) => { console.log(`Running ${toolName} from ${workspaceToolName}`, input); }, }, }, }); ``` ### Patch Changes - Plan due notification dispatches per thread from a single due snapshot so high-priority notifications that already emitted summaries are delivered in full before lower-priority notifications or summaries can wake the same thread. `Agent.sendNotificationSignal` also accepts an array of notification inputs so related notifications can be evaluated against the same initial thread state before any delivery wakes the thread. ([#17590](https://github.com/mastra-ai/mastra/pull/17590)) - Fixed medium-priority notification summaries so they wake idle agent threads when they are ready to deliver. ([#17590](https://github.com/mastra-ai/mastra/pull/17590)) - Fixed browser state signals so they no longer show 'Browser is closed' before the browser is used. Added attribution for browser closes and active URL changes when the agent or user caused them. ([#17631](https://github.com/mastra-ai/mastra/pull/17631)) ## 1.42.0-alpha.0 ### Minor Changes - Added SignalProvider abstraction for building notification signal providers. Enables declarative signal wiring in Agent config with built-in subscription tracking, polling lifecycle, and webhook support. Includes WebhookSignalProvider as a proof-of-concept. ([#17577](https://github.com/mastra-ai/mastra/pull/17577)) **Writing a signal provider:** ```ts import { SignalProvider } from '@mastra/core/signals'; import type { SignalSubscription } from '@mastra/core/signals'; class SlackSignalProvider extends SignalProvider<'slack-signals'> { readonly id = 'slack-signals' as const; readonly pollInterval = 30_000; // poll every 30s async poll(subscriptions: SignalSubscription[]) { for (const sub of subscriptions) { const messages = await fetchNewMessages(sub.externalResourceId); if (messages.length > 0) { await this.notify( { source: 'slack', kind: 'new-message', summary: `${messages.length} new messages` }, { threadId: sub.threadId, resourceId: sub.resourceId }, ); } } } } ``` **Declarative wiring:** ```ts import { Agent } from '@mastra/core/agent'; const agent = new Agent({ signals: [new SlackSignalProvider()], // ... other config }); ``` - Add batching primitives to the PubSub abstraction. ([#16482](https://github.com/mastra-ai/mastra/pull/16482)) **New options** - `SubscribeOptions.batch` (`SubscribeBatchOptions`): opt in to coalesced delivery on a per-subscriber basis. Fields: `maxSize`, `maxWaitMs`, `minIntervalMs`, `isImmediate`, `coalesce`, `maxBufferSize`, `overflow`. - `EventEmitterPubSub` constructor accepts optional `EventEmitterPubSubOptions` with a `logger` for batched-delivery error diagnostics. **Example** ```ts import { EventEmitterPubSub } from '@mastra/core/events'; const pubsub = new EventEmitterPubSub(); await pubsub.subscribe( 'agent-events', event => { // delivered in coalesced batches; the cb is still invoked once per event }, { batch: { maxSize: 10, // flush once 10 events have queued maxWaitMs: 500, // ...or after 500ms, whichever comes first }, }, ); ``` **New exports** - `SubscribeBatchOptions` type. - `PubSub.supportsNativeBatching` — advertises whether an adapter honors `options.batch` internally. **Behavior** - `EventEmitterPubSub.supportsNativeBatching === true`. Batched subscribers receive coalesced delivery driven by an in-memory buffer governed by `maxSize` / `maxWaitMs` / `minIntervalMs` / `isImmediate` / `coalesce` / `overflow` / `maxBufferSize`. - `EventEmitterPubSub.flush()` drains every batched subscriber buffer and waits for any pending nack redeliveries before resolving. Non-callback rejections surface through the configured `logger` instead of being swallowed. - `CachingPubSub` is transparent to batching: it forwards `options` (including `batch`) to its inner PubSub and forwards `supportsNativeBatching` from the inner. Whether batching is honored depends entirely on the wrapped transport. **Contract notes** - `SubscribeBatchOptions.coalesce` must return a subset of its input array by reference identity. Returning freshly-constructed `Event` objects (even with matching `id`) is treated as a contract violation: the batching layer can't route `ack`/`nack` to original transport handles for manufactured events, so the entire batch is discarded and every original event is acked as dropped. If you need merged payloads, build them in the subscriber callback after delivery. - `flush()` is best-effort: a successful resolution does not guarantee every subscriber callback succeeded. Per-event errors surface via the configured logger. ### Patch Changes - Fixed ConsoleLogger.warn() to correctly call console.warn() instead of console.info(), ensuring warn-level logs are routed to stderr and properly classified by log backends (e.g. Datadog) ([#17623](https://github.com/mastra-ai/mastra/pull/17623)) - Prevented user-created filesystem stored agents from being treated as code agents when they are not declared in the code Mastra instance. ([#17622](https://github.com/mastra-ai/mastra/pull/17622)) - Fixed per-item request context being dropped for inline experiment data. When running an experiment with inline `data`, each item's `requestContext` is now passed to the agent or workflow and merged over the global request context (per-item values win on key collisions), matching the behavior of storage-backed datasets. ([#17597](https://github.com/mastra-ai/mastra/pull/17597)) ```ts await dataset.startExperiment({ data: [{ input: { prompt: 'Hello' }, requestContext: { clinicId: 'clinic-1' } }], targetType: 'agent', targetId: 'support-agent', }); // Tools can now read clinicId from requestContext during inline experiments ``` - Fixed thread subscribers so they can join an already-running remote stream and receive new output without waiting for the next run. ([#17635](https://github.com/mastra-ai/mastra/pull/17635)) - Added OTel span instrumentation for the hardcoded 10-second rate-limit backpressure sleep, making it visible in traces as a 'rate-limit-sleep' span with remainingTokens and delayMs metadata ([#17623](https://github.com/mastra-ai/mastra/pull/17623)) - Fixed message part timestamps affecting transcript ordering by ensuring only message-level timestamps advance the ordering watermark. ([#17598](https://github.com/mastra-ai/mastra/pull/17598)) - Fix `Cannot get workflow run. Mastra storage is not initialized` debug log spam on agents that use memory or any input/output processors. ([#17571](https://github.com/mastra-ai/mastra/pull/17571)) #17344 fixed this for the internal `execution-workflow`, but agents with memory/processors also build an internal _processor_ workflow (`Agent.combineProcessorsIntoWorkflow`, run by `ProcessorRunner.executeWorkflowAsProcessor`) that never received the parent `Mastra` instance — so its `createRun()` → `getWorkflowRunById()` saw no storage and logged the noise on every run. The processor workflow now receives the parent `Mastra` instance and opts out of snapshot persistence (`shouldPersistSnapshot: () => false`), mirroring the execution-workflow fix. Follow-up to #17137 / #17344. ## 1.41.0 ### Minor Changes - Workspace `sandbox` now accepts a resolver function for per-request sandboxes. ([#16048](https://github.com/mastra-ai/mastra/pull/16048)) **Before:** `sandbox: WorkspaceSandbox` (static, same sandbox for every request) **After:** `sandbox: WorkspaceSandbox | (({ requestContext }) => WorkspaceSandbox)` (static or per-request) This enables per-request sandbox routing from a single Workspace — useful for multi-tenant deployments where each user/role needs an isolated working directory or different execution permissions. ```ts const workspace = new Workspace({ sandbox: ({ requestContext }) => { const userId = requestContext.get('user-id') as string; return new LocalSandbox({ workingDirectory: `/workspaces/${userId}` }); }, }); ``` When using a resolver, the caller owns the returned sandbox's lifecycle — the Workspace will not call `start()` or `destroy()` on it. `mounts` throws an `INVALID_CONFIG` error with a resolver, and `lsp: true` is disabled with a warning because both require a concrete sandbox instance up front. **Stable prompts by default** Building workspace instructions no longer calls a sandbox resolver. Resolver-backed sandboxes contribute stable placeholder text to the agent's system message, so constructing the prompt never provisions a caller-owned sandbox and the prompt stays cache-friendly. Opt into concrete per-request instructions with `instructions.dynamicSandbox`: ```ts const workspace = new Workspace({ sandbox: ({ requestContext }) => resolveSandbox(requestContext), instructions: { dynamicSandbox: 'resolve' }, // or a ({ requestContext }) => string function }); ``` **Background process continuity** Set `sandboxCacheKey` to keep `execute_command({ background: true })`, `get_process_output`, and `kill_process` on the same sandbox across follow-up requests — continuity is keyed by a stable id rather than the `RequestContext` instance: ```ts const workspace = new Workspace({ sandbox: ({ requestContext }) => resolveSandbox(requestContext), sandboxCacheKey: ({ requestContext }) => requestContext.get('thread-id') as string, }); ``` Failed sandbox resolver calls are removed from the cache so later calls can retry. Use `workspace.clearSandboxCache(cacheKey)` to drop a keyed sandbox reference when your own lifecycle code has destroyed or replaced that sandbox. When background process tools cannot find a PID on a dynamic sandbox without `sandboxCacheKey`, the tool output now points to `sandboxCacheKey` so callers can fix continuity across follow-up requests. ### Patch Changes - Added `untilIdle` option to `stream()` and `resumeStream()` methods. Pass `untilIdle: true` (or `untilIdle: { maxIdleMs: 60_000 }`) to keep the stream open across background-task continuations — same behavior as the now-deprecated `streamUntilIdle()` method. ([#17536](https://github.com/mastra-ai/mastra/pull/17536)) **Example:** ```ts const result = await agent.stream('Research solana for me', { untilIdle: true, memory: { thread: 't1', resource: 'u1' }, }); ``` Deprecated `streamUntilIdle()` and `resumeStreamUntilIdle()` — they still work but now delegate internally to `stream({ untilIdle: true })`. ## 1.41.0-alpha.0 ### Minor Changes - Workspace `sandbox` now accepts a resolver function for per-request sandboxes. ([#16048](https://github.com/mastra-ai/mastra/pull/16048)) **Before:** `sandbox: WorkspaceSandbox` (static, same sandbox for every request) **After:** `sandbox: WorkspaceSandbox | (({ requestContext }) => WorkspaceSandbox)` (static or per-request) This enables per-request sandbox routing from a single Workspace — useful for multi-tenant deployments where each user/role needs an isolated working directory or different execution permissions. ```ts const workspace = new Workspace({ sandbox: ({ requestContext }) => { const userId = requestContext.get('user-id') as string; return new LocalSandbox({ workingDirectory: `/workspaces/${userId}` }); }, }); ``` When using a resolver, the caller owns the returned sandbox's lifecycle — the Workspace will not call `start()` or `destroy()` on it. `mounts` throws an `INVALID_CONFIG` error with a resolver, and `lsp: true` is disabled with a warning because both require a concrete sandbox instance up front. **Stable prompts by default** Building workspace instructions no longer calls a sandbox resolver. Resolver-backed sandboxes contribute stable placeholder text to the agent's system message, so constructing the prompt never provisions a caller-owned sandbox and the prompt stays cache-friendly. Opt into concrete per-request instructions with `instructions.dynamicSandbox`: ```ts const workspace = new Workspace({ sandbox: ({ requestContext }) => resolveSandbox(requestContext), instructions: { dynamicSandbox: 'resolve' }, // or a ({ requestContext }) => string function }); ``` **Background process continuity** Set `sandboxCacheKey` to keep `execute_command({ background: true })`, `get_process_output`, and `kill_process` on the same sandbox across follow-up requests — continuity is keyed by a stable id rather than the `RequestContext` instance: ```ts const workspace = new Workspace({ sandbox: ({ requestContext }) => resolveSandbox(requestContext), sandboxCacheKey: ({ requestContext }) => requestContext.get('thread-id') as string, }); ``` Failed sandbox resolver calls are removed from the cache so later calls can retry. Use `workspace.clearSandboxCache(cacheKey)` to drop a keyed sandbox reference when your own lifecycle code has destroyed or replaced that sandbox. When background process tools cannot find a PID on a dynamic sandbox without `sandboxCacheKey`, the tool output now points to `sandboxCacheKey` so callers can fix continuity across follow-up requests. ### Patch Changes - Added `untilIdle` option to `stream()` and `resumeStream()` methods. Pass `untilIdle: true` (or `untilIdle: { maxIdleMs: 60_000 }`) to keep the stream open across background-task continuations — same behavior as the now-deprecated `streamUntilIdle()` method. ([#17536](https://github.com/mastra-ai/mastra/pull/17536)) **Example:** ```ts const result = await agent.stream('Research solana for me', { untilIdle: true, memory: { thread: 't1', resource: 'u1' }, }); ``` Deprecated `streamUntilIdle()` and `resumeStreamUntilIdle()` — they still work but now delegate internally to `stream({ untilIdle: true })`. ## 1.40.0 ### Minor Changes - Added support for Gateway embedding routing with `mastra/provider/model` IDs: ([#17469](https://github.com/mastra-ai/mastra/pull/17469)) ```ts const embedder = new ModelRouterEmbeddingModel('mastra/__GATEWAY_OPENAI_EMBEDDING_MODEL__'); ``` ## 1.40.0-alpha.0 ### Minor Changes - Added support for Gateway embedding routing with `mastra/provider/model` IDs: ([#17469](https://github.com/mastra-ai/mastra/pull/17469)) ```ts const embedder = new ModelRouterEmbeddingModel('mastra/__GATEWAY_OPENAI_EMBEDDING_MODEL__'); ``` ## 1.39.0 ### Minor Changes - Add a type-safe `MessageFactory` component to `@mastra/react` for rendering a `MastraDBMessage` with your own per-part components, and align its tripwire/task-verdict types with `@mastra/core`. ([#17514](https://github.com/mastra-ai/mastra/pull/17514)) **@mastra/react** `MessageFactory` provides optional, fully type-safe render functions for each kind of message part. Only the renderer matching a part's type runs, and each receives correctly narrowed props; missing renderers fall back gracefully. Runtime-only `dynamic-tool` and AI SDK v5 `tool-${string}` parts are covered by a dedicated `DynamicTool` renderer, and optional role wrappers let you frame parts per message role. ```tsx import { MessageFactory } from '@mastra/react';

{part.text}

} ToolInvocation={part => } DynamicTool={part => } Data={part => } roles={{ Signal: ({ children }) => {children} }} />; ``` It also accepts an optional `status` prop with four strongly-typed slots that render from a message's metadata while keeping part renderers pure. `Tripwire`, `Warning`, and `Error` are _replacement_ slots (rendered instead of the parts when `metadata.status` matches); `Task` is an _adjacent_ slot (rendered alongside the parts when a task-completion verdict exists). The factory only surfaces metadata to the slots and never filters it (for example, it still invokes `Task` when `suppressFeedback` is true) — the consumer decides what to render or skip. Existing behavior is unchanged when `status` is omitted. ```tsx {text}, Task: ({ passed, suppressFeedback }) => (suppressFeedback ? null : ), }} {...renderers} /> ``` The narrowed part types used by the renderers are exported so consumers can type their own components: `TextPart`, `ReasoningPart`, `FilePart`, `StepStartPart`, `ToolInvocationPart`, `SourceDocumentPart`, and `SourceUrlPart`, plus `MessageFactoryPart` (the exact union of part shapes `MessageFactory` can dispatch — the typed accumulator parts plus the runtime-only `dynamic-tool` / `tool-${string}` parts) for typing part arrays precisely instead of `unknown[]`. `MastraDBMessageMetadata.isTaskCompleteResult` is now typed as the `{ passed?, suppressFeedback? }` completion-verdict shape (matching `completionResult`) instead of `boolean`, so the `Task` slot resolves verdicts from either field without a cast. `TripwireMetadata` is now an alias of core's `TripwirePayload`, and the message accumulator persists the canonical shape. Two behavioral changes to persisted `metadata.tripwire`: - The tripwire `reason` is now persisted as `tripwire.reason` (previously it was only stored in the message text part). - The processor metadata field was renamed from `tripwire.tripwirePayload` to `tripwire.metadata` to match the canonical type. The `MessageFactory` `Tripwire` slot receives `reason` through `props.tripwire`. **@mastra/core** Exported the canonical `IsTaskCompletePayload` and `TripwirePayload` types from `@mastra/core/stream` so consumers can type their own task/completion and tripwire UI against them instead of redeclaring the shapes. ```ts import type { IsTaskCompletePayload, TripwirePayload } from '@mastra/core/stream'; ``` - Add experimental record-first notification signals with thread-scoped inbox storage, `agent.sendNotificationSignal()`, priority-aware notification delivery policies, due-notification dispatch, summary rollups for low-priority notifications, a structured `metadata.notification` signal contract, and a flexible notification inbox tool. ([#17241](https://github.com/mastra-ai/mastra/pull/17241)) ```ts await agent.sendNotificationSignal( { source: 'github', kind: 'ci-status', priority: 'high', summary: 'CI failed on main', }, { resourceId: 'user-1', threadId: 'thread-1' }, ); ``` Agents can then use the `notification_inbox` tool to list, read, dismiss, or archive persisted inbox records. - Add processor state signals for memory-backed thread context. ([#17240](https://github.com/mastra-ai/mastra/pull/17240)) Processors can now publish named state lanes with `computeStateSignal()`, and external producers can update the same lanes with `agent.sendStateSignal()`. The runtime tracks each lane by `id`, `cacheKey`, and `mode`, so unchanged state is deduped and snapshot/delta history can be replayed efficiently. Browser context now uses this state-signal path, so browser state is represented as thread state instead of being injected as ad hoc context. ```ts // External producer: push browser state outside a model run. await agent.sendStateSignal( { id: 'browser', cacheKey: 'tab-42:https://app.example.com/dashboard', mode: 'snapshot', contents: 'Browser is open on https://app.example.com/dashboard', value: { url: 'https://app.example.com/dashboard', title: 'Dashboard' }, }, { resourceId, threadId, ifIdle: { behavior: 'persist' } }, ); // Processor-owned state: compute state before the model request. const browserProcessor = { id: 'browser-state', stateId: 'browser', async computeStateSignal({ lastSnapshot }) { const nextBrowserState = await getBrowserState(); return { cacheKey: nextBrowserState.url, mode: 'snapshot', contents: `Browser is open on ${nextBrowserState.url}`, value: nextBrowserState, }; }, }; ``` ### Patch Changes - Fixed CostGuardProcessor thread and resource scope resolution when running without auth middleware (e.g. Studio dev mode). The processor now falls back to the MastraMemory context on RequestContext to resolve threadId and resourceId, matching the pattern used by other processor helpers. ([#17522](https://github.com/mastra-ai/mastra/pull/17522)) - Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. ([#17410](https://github.com/mastra-ai/mastra/pull/17410)) - Improved notification delivery targeting and inbox search so notification records can be found and delivered to the intended thread. ([#17447](https://github.com/mastra-ai/mastra/pull/17447)) - Fixed subscribed client tools so browser-executed tool results continue through the existing thread subscription instead of opening and canceling a second stream. This prevents closed-stream errors in apps like Agent Builder when multiple client tools run during one response. ([#17532](https://github.com/mastra-ai/mastra/pull/17532)) - Added experimental `workingMemory.useStateSignals` opt-in. When set to `true`, working memory is delivered to the model as a `state` signal (via the new state-signals API) instead of being folded into the system message. `Memory` auto-attaches a `WorkingMemoryStateProcessor` that emits a signal with `stateId: 'working-memory'` and dedups via `cacheKey`. Subsequent turns emit unified-diff deltas against the prior snapshot when the diff is smaller than the snapshot (markdown mode only); schema mode and the fallback path always emit a full snapshot. The working-memory tool is registered as `setWorkingMemory` instead of `updateWorkingMemory` under this opt-in so legacy persistence/prompt strip filters naturally bypass it. The default (`false`) preserves the existing system-message behavior. `useStateSignals` is not supported with template working memory `version: 'vnext'`. ([#17497](https://github.com/mastra-ai/mastra/pull/17497)) ```ts import { Memory } from '@mastra/memory'; const memory = new Memory({ options: { workingMemory: { enabled: true, useStateSignals: true, }, }, }); ``` ## 1.39.0-alpha.0 ### Minor Changes - Add a type-safe `MessageFactory` component to `@mastra/react` for rendering a `MastraDBMessage` with your own per-part components, and align its tripwire/task-verdict types with `@mastra/core`. ([#17514](https://github.com/mastra-ai/mastra/pull/17514)) **@mastra/react** `MessageFactory` provides optional, fully type-safe render functions for each kind of message part. Only the renderer matching a part's type runs, and each receives correctly narrowed props; missing renderers fall back gracefully. Runtime-only `dynamic-tool` and AI SDK v5 `tool-${string}` parts are covered by a dedicated `DynamicTool` renderer, and optional role wrappers let you frame parts per message role. ```tsx import { MessageFactory } from '@mastra/react';

{part.text}

} ToolInvocation={part => } DynamicTool={part => } Data={part => } roles={{ Signal: ({ children }) => {children} }} />; ``` It also accepts an optional `status` prop with four strongly-typed slots that render from a message's metadata while keeping part renderers pure. `Tripwire`, `Warning`, and `Error` are _replacement_ slots (rendered instead of the parts when `metadata.status` matches); `Task` is an _adjacent_ slot (rendered alongside the parts when a task-completion verdict exists). The factory only surfaces metadata to the slots and never filters it (for example, it still invokes `Task` when `suppressFeedback` is true) — the consumer decides what to render or skip. Existing behavior is unchanged when `status` is omitted. ```tsx {text}, Task: ({ passed, suppressFeedback }) => (suppressFeedback ? null : ), }} {...renderers} /> ``` The narrowed part types used by the renderers are exported so consumers can type their own components: `TextPart`, `ReasoningPart`, `FilePart`, `StepStartPart`, `ToolInvocationPart`, `SourceDocumentPart`, and `SourceUrlPart`, plus `MessageFactoryPart` (the exact union of part shapes `MessageFactory` can dispatch — the typed accumulator parts plus the runtime-only `dynamic-tool` / `tool-${string}` parts) for typing part arrays precisely instead of `unknown[]`. `MastraDBMessageMetadata.isTaskCompleteResult` is now typed as the `{ passed?, suppressFeedback? }` completion-verdict shape (matching `completionResult`) instead of `boolean`, so the `Task` slot resolves verdicts from either field without a cast. `TripwireMetadata` is now an alias of core's `TripwirePayload`, and the message accumulator persists the canonical shape. Two behavioral changes to persisted `metadata.tripwire`: - The tripwire `reason` is now persisted as `tripwire.reason` (previously it was only stored in the message text part). - The processor metadata field was renamed from `tripwire.tripwirePayload` to `tripwire.metadata` to match the canonical type. The `MessageFactory` `Tripwire` slot receives `reason` through `props.tripwire`. **@mastra/core** Exported the canonical `IsTaskCompletePayload` and `TripwirePayload` types from `@mastra/core/stream` so consumers can type their own task/completion and tripwire UI against them instead of redeclaring the shapes. ```ts import type { IsTaskCompletePayload, TripwirePayload } from '@mastra/core/stream'; ``` - Add experimental record-first notification signals with thread-scoped inbox storage, `agent.sendNotificationSignal()`, priority-aware notification delivery policies, due-notification dispatch, summary rollups for low-priority notifications, a structured `metadata.notification` signal contract, and a flexible notification inbox tool. ([#17241](https://github.com/mastra-ai/mastra/pull/17241)) ```ts await agent.sendNotificationSignal( { source: 'github', kind: 'ci-status', priority: 'high', summary: 'CI failed on main', }, { resourceId: 'user-1', threadId: 'thread-1' }, ); ``` Agents can then use the `notification_inbox` tool to list, read, dismiss, or archive persisted inbox records. - Add processor state signals for memory-backed thread context. ([#17240](https://github.com/mastra-ai/mastra/pull/17240)) Processors can now publish named state lanes with `computeStateSignal()`, and external producers can update the same lanes with `agent.sendStateSignal()`. The runtime tracks each lane by `id`, `cacheKey`, and `mode`, so unchanged state is deduped and snapshot/delta history can be replayed efficiently. Browser context now uses this state-signal path, so browser state is represented as thread state instead of being injected as ad hoc context. ```ts // External producer: push browser state outside a model run. await agent.sendStateSignal( { id: 'browser', cacheKey: 'tab-42:https://app.example.com/dashboard', mode: 'snapshot', contents: 'Browser is open on https://app.example.com/dashboard', value: { url: 'https://app.example.com/dashboard', title: 'Dashboard' }, }, { resourceId, threadId, ifIdle: { behavior: 'persist' } }, ); // Processor-owned state: compute state before the model request. const browserProcessor = { id: 'browser-state', stateId: 'browser', async computeStateSignal({ lastSnapshot }) { const nextBrowserState = await getBrowserState(); return { cacheKey: nextBrowserState.url, mode: 'snapshot', contents: `Browser is open on ${nextBrowserState.url}`, value: nextBrowserState, }; }, }; ``` ### Patch Changes - Fixed CostGuardProcessor thread and resource scope resolution when running without auth middleware (e.g. Studio dev mode). The processor now falls back to the MastraMemory context on RequestContext to resolve threadId and resourceId, matching the pattern used by other processor helpers. ([#17522](https://github.com/mastra-ai/mastra/pull/17522)) - Removed Hono from @mastra/core and auth package runtime dependencies. Auth providers now receive framework-agnostic request types that support standard Request objects and Hono-compatible request shapes. MCP and deployer avoid relying on core-bundled Hono context types at package boundaries. ([#17410](https://github.com/mastra-ai/mastra/pull/17410)) - Improved notification delivery targeting and inbox search so notification records can be found and delivered to the intended thread. ([#17447](https://github.com/mastra-ai/mastra/pull/17447)) - Fixed subscribed client tools so browser-executed tool results continue through the existing thread subscription instead of opening and canceling a second stream. This prevents closed-stream errors in apps like Agent Builder when multiple client tools run during one response. ([#17532](https://github.com/mastra-ai/mastra/pull/17532)) - Added experimental `workingMemory.useStateSignals` opt-in. When set to `true`, working memory is delivered to the model as a `state` signal (via the new state-signals API) instead of being folded into the system message. `Memory` auto-attaches a `WorkingMemoryStateProcessor` that emits a signal with `stateId: 'working-memory'` and dedups via `cacheKey`. Subsequent turns emit unified-diff deltas against the prior snapshot when the diff is smaller than the snapshot (markdown mode only); schema mode and the fallback path always emit a full snapshot. The working-memory tool is registered as `setWorkingMemory` instead of `updateWorkingMemory` under this opt-in so legacy persistence/prompt strip filters naturally bypass it. The default (`false`) preserves the existing system-message behavior. `useStateSignals` is not supported with template working memory `version: 'vnext'`. ([#17497](https://github.com/mastra-ai/mastra/pull/17497)) ```ts import { Memory } from '@mastra/memory'; const memory = new Memory({ options: { workingMemory: { enabled: true, useStateSignals: true, }, }, }); ``` ## 1.38.0 ### Minor Changes - Added `channels.threadContext.addSystemMessage` to opt out of the built-in channel system message. By default, `AgentChannels` injects a short system message telling the agent which channel/platform a request came from (DM vs public, bot identity, etc.). Set `addSystemMessage: false` to skip it: ([#17171](https://github.com/mastra-ai/mastra/pull/17171)) ```ts new Agent({ channels: { adapters: { slack: createSlackAdapter() }, threadContext: { addSystemMessage: false, }, }, }); ``` - Added `agent.sendMessage()` and `agent.queueMessage()` APIs for sending user-authored input into agent threads. These are intended to be used with `agent.subscribeToThread()` and replace lower-level `agent.sendSignal()` calls for regular user messages. ([#17191](https://github.com/mastra-ai/mastra/pull/17191)) ```ts await agent.sendMessage('Continue with the latest user input', { resourceId, threadId }); await agent.queueMessage('Follow up after the active turn finishes', { resourceId, threadId }); await agent.sendMessage( { contents: [ { type: 'text', text: 'What is in this image?' }, { type: 'file', data: imageBase64, mediaType: 'image/png', filename: 'screenshot.png' }, ], }, { resourceId, threadId }, ); ``` - Support conditional, function-based tool approvals. ([#17337](https://github.com/mastra-ai/mastra/pull/17337)) - MCP tools that wrap a server-level `requireToolApproval` function are now honored end-to-end. The per-tool approval function was previously dropped when the agent converted MCP tools (it kept only the boolean flag), so conditional approval silently fell back to always-on. `CoreToolBuilder` now preserves a `needsApprovalFn` attached directly to a tool instance. - The global `requireToolApproval` option on `agent.stream`/`agent.generate` now accepts a function in addition to a boolean. It is evaluated per tool call with the tool name, arguments, and request context, enabling policies such as regex allowlists on tool names. Returning `true` requires approval for that call; `false` allows it. On error the call defaults to requiring approval. When a function policy is set, tool calls run sequentially so approval suspensions don't race. Durable agents and stored agents continue to accept only a boolean (a function degrades to requiring approval for every call, since their options must be serializable). ```ts // Approve only tool calls whose name is not on an allowlist. const allowlist = /^(get|list|search)_/; await agent.generate('...', { requireToolApproval: ({ toolName }) => !allowlist.test(toolName), }); ``` - Precedence is unchanged from before: a per-tool approval function (`createTool({ requireApproval: fn })` or an MCP-derived `needsApprovalFn`) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returning `false` even when the global option is on. The only new behavior is that the global option may now be a function in addition to a boolean. - The previously implicit, runtime-attached per-tool approval predicate is now a typed contract: `@mastra/core` exports `NeedsApprovalFn` and declares the optional `needsApprovalFn` property on the `Tool` class. The MCP client and the agent runtime now share this typed contract instead of reaching through `any`. This is additive — no public API changes. - Added harness events for session lifecycle updates, mode changes, model changes, and cloned threads. ([#17290](https://github.com/mastra-ai/mastra/pull/17290)) Users can now subscribe to harness events to observe harness activity. **Example** ```ts const unsubscribe = harness.subscribe(event => { console.log(event.id, event.type); }); ``` - Added agent override support to the agent and editor APIs. ([#17227](https://github.com/mastra-ai/mastra/pull/17227)) Code-defined agents can now declare which fields Studio may edit with the `editor` option: ```ts new Agent({ name: 'Weather Agent', model, editor: { instructions: true, tools: { description: true }, }, }); ``` The editor applies stored overrides only for fields the `editor` config owns, so locked fields keep their code-defined values. Per-agent `editor: false` locks an agent entirely. `MastraEditor` accepts a `source` setting that picks the editing experience: ```ts new MastraEditor({ source: 'code' }); ``` - `source: 'code'` — the editor auto-wires a `FilesystemStore` (defaulting to `./mastra/editor/`, overridable with `codePath`) when no editor storage is supplied, and persists overrides as deterministic per-agent JSON files. - `source: 'db'` (default) — keeps the existing storage-backed flow against whatever storage the project has configured. - Added the `tool_provider_connections` storage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. ([#17247](https://github.com/mastra-ai/mastra/pull/17247)) **What you can do** - Pin a connection on a stored agent's config and have it round-trip on read/write/create. - Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time. **Example** ```ts import { LibSQLStore } from '@mastra/libsql'; const storage = new LibSQLStore({ url: process.env.DATABASE_URL }); // Persist an OAuth connection that an agent can pin later await storage.toolProviders.upsertConnection({ authorId: 'user-123', providerId: 'composio', connectionId: 'auth_abc', toolkit: 'gmail', label: 'Work inbox', scope: 'per-author', }); // List a user's own connections (admin can omit authorId to list across users) const { items } = await storage.toolProviders.listConnectionsByAuthor({ authorId: 'user-123', providerId: 'composio', }); ``` Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR. PR 1 of 3 split from #17224. - Added opt-in MCP server instructions forwarding into agent system prompts. ([#17155](https://github.com/mastra-ai/mastra/pull/17155)) When an MCP server advertises instructions during initialization, you can now forward that guidance into the system prompt of agents that use the server's tools. This is **opt-in** — set `forwardInstructions: true` per server to enable it. Forwarded instructions are injected into the agent's system prompt, so only enable this for servers you trust. ```ts const mcp = new MCPClient({ servers: { db: { url: new URL('http://localhost:4111/mcp'), forwardInstructions: true, // opt in; defaults to false instructionsMaxLength: 512, // max chars forwarded per server }, }, }); const agent = new Agent({ id: 'db-agent', name: 'DB Agent', instructions: 'Help with database changes.', model, tools: await mcp.listTools(), }); ``` You can always inspect cached instructions without forwarding them: ```ts const instructions = mcp.getServerInstructions(); // => { db: 'Always validate before migrating.', other: undefined } ``` - Add fire-and-forget workflow resume that returns immediately with `{ runId }` without awaiting the run output. ([#17230](https://github.com/mastra-ai/mastra/pull/17230)) For `@mastra/inngest`, this skips the `getRunOutput()` polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine. - `Run.resumeAsync()` added to core: dispatches the resume in the background and returns `{ runId }` immediately. Engines that poll for results (Inngest) override it to skip polling entirely. - `InngestRun.resumeAsync()` sends the resume event and returns `{ runId }`, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot. - New `POST /workflows/:workflowId/resume-no-wait` and `POST /agent-builder/:actionId/resume-no-wait` routes return `{ runId }` immediately. - New client SDK `run.resumeNoWait()` resolves with `{ runId }`. The existing `resumeAsync()` client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change. `resumeNoWait` is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of `resumeAsync()` (mirroring `start`/`resume` semantics), at which point `resumeNoWait` and the `resume-no-wait` routes will be removed. The code paths carry `TODO(v2)` comments documenting this consolidation. - Added experimental Code Mode for agents. `createCodeMode` returns an `execute_typescript` tool plus generated instructions that let an agent write one TypeScript program to orchestrate your tools (batch with `Promise.all`, aggregate, and do math in a real runtime) instead of calling tools one at a time. Tools still run on the host with full validation and tracing; only the orchestration code runs in a workspace sandbox. ([#17324](https://github.com/mastra-ai/mastra/pull/17324)) ```typescript import { createCodeMode, createTool } from '@mastra/core/tools'; const { tool, instructions } = createCodeMode({ tools: { getTopProducts, getProductRatings }, }); const agent = new Agent({ instructions: ['You are a helpful assistant.', instructions], tools: { execute_typescript: tool }, }); ``` - Add per-entity file persistence and per-entity git history to `FilesystemVersionedHelpers`. ([#17225](https://github.com/mastra-ai/mastra/pull/17225)) `FilesystemVersionedHelpers` now accepts three optional hooks that let a storage domain split a published entity across many per-entity JSON files (e.g. `agents/.json`) instead of one shared map file: - `perEntityFilesDir` — directory (under the FilesystemDB root) for per-entity files. - `shouldPersistToPerEntityFile(entity)` — decide per published entity whether to write its snapshot to a per-entity file. - `perEntitySnapshotFilter(snapshot, entity)` — filter the snapshot before writing it to the per-entity file (e.g. drop fields the user does not own). When configured, the helper: - Reads per-entity files on hydrate (alongside the shared map file). - Writes published snapshots to per-entity files with stable alphabetical key ordering for friendly diffs. - Walks per-entity file git history and surfaces each commit as a read-only version in `listVersions` (in addition to the existing shared-file git history). - Skips writing an empty shared map file when every published entity is persisted to per-entity files, so a code-only project does not end up with an empty stub committed to git. Also adds `FilesystemDB.listDomainFiles`, `domainFileExists`, and `removeDomainFile` helpers, and broadens `GitHistory.getFileAtCommit` to be generic so callers can request a per-entity snapshot type rather than the shared-map shape. Example — configure a domain to persist each entity to its own file: ```typescript import { FilesystemDB, FilesystemVersionedHelpers } from '@mastra/core/storage'; const db = new FilesystemDB('./mastra/editor'); // Entities that should be persisted as their own files. const codeModeEntityIds = new Set(['support-bot']); const agents = new FilesystemVersionedHelpers({ db, entitiesFile: 'agents.json', parentIdField: 'agentId', name: 'agents', versionMetadataFields: ['id', 'agentId', 'versionNumber', 'createdAt'], // New per-entity hooks: perEntityFilesDir: 'agents', // Decide per entity whether it gets its own file (vs. the shared agents.json). shouldPersistToPerEntityFile: entity => codeModeEntityIds.has(entity.id), // Drop fields that should not live in the per-entity file. perEntitySnapshotFilter: snapshot => { const { model, ...userOwned } = snapshot; return userOwned; }, }); // Published snapshots are now written to ./mastra/editor/agents/.json, // and each git commit to those files shows up as a read-only version. const versions = await agents.listVersions({ agentId: 'support-bot' }, 'agentId'); ``` - Added support for resolving an agent's `voice` per request. ([#17345](https://github.com/mastra-ai/mastra/pull/17345)) You can now pass `voice` as a resolver, just like `instructions`, `tools`, and `model`. Mastra runs the resolver on each `getVoice()` call and returns a fresh, session-owned voice instance. This fixes concurrent realtime and speech-to-speech sessions on a single deployed agent, where a shared voice instance previously let one session overwrite another session's WebSocket, tools, and instructions. A static `voice` keeps its existing shared behavior, so this change is backward compatible. **Before** ```typescript const agent = new Agent({ name: 'support-line', voice: new GeminiLiveVoice({ apiKey: KEY }), // shared across every session }); ``` **After** ```typescript const agent = new Agent({ name: 'support-line', voice: ({ requestContext }) => new GeminiLiveVoice({ apiKey: requestContext.get('apiKey') }), }); const voice = await agent.getVoice({ requestContext }); // owns its own ws/tools/instructions await voice.connect(); ``` The caller owns the lifecycle of a resolver instance and should call `disconnect()` when the session ends. The `agent.voice` getter throws when `voice` is a resolver because it has no request context; use `agent.getVoice({ requestContext })` instead. - Workflows that suspend during dataset experiments now resume automatically. Provide resume data in your dataset items and the workflow will continue execution through multiple suspend/resume cycles. ([#17378](https://github.com/mastra-ai/mastra/pull/17378)) For multi-step workflows, use `resumeSteps` keyed by step ID: ```typescript const item = { input: { prompt: 'Draft a blog post' }, resumeSteps: { 'approval-step': { approved: true } }, }; ``` For single-step workflows, use flat `resumeData`: ```typescript const item = { input: { prompt: 'Draft a blog post' }, resumeData: { approved: true }, }; ``` Storage-backed items can use `metadata.resumeSteps` or `metadata.resumeData` as fallback. When no resume data is provided, the suspend payload is returned as output with guidance on how to add it. ([#15382](https://github.com/mastra-ai/mastra/issues/15382)) - Added request-aware filtering for ToolSearchProcessor search, load, and active tools. The filter hook receives the resolved tool ID as `toolName`. ([#16088](https://github.com/mastra-ai/mastra/pull/16088)) ```ts new ToolSearchProcessor({ tools, filter: ({ toolName, requestContext }) => { const plan = requestContext?.get('plan'); return plan === 'pro' || !toolName.startsWith('premium_'); }, }); ``` - Added `channels.resolveResourceId` to control which `resourceId` owns a channel thread's memory, separately from who sent the message. Useful for SSO apps that want a user's memory shared across web and a Feishu/Lark DM, or group chats scoped to the conversation instead of the sender. Only affects newly-created threads; return the provided default to keep current behavior. ([#17471](https://github.com/mastra-ai/mastra/pull/17471)) ```ts new Agent({ // ... channels: { adapters: { slack: createSlackAdapter() }, resolveResourceId: async ({ thread, message }) => { if (thread.isDM) return resolveSsoUserId(message); // shared with web return thread.channelId; // group owns the memory }, }, }); ``` - Added the `disableInit` option to the `MastraVector` base class. When set to `true`, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existing `disableInit` behavior on storage adapters and is useful for deployments where schemas and indexes are created ahead of time by a privileged database role, while the application runs with a least-privilege role. ([#17272](https://github.com/mastra-ai/mastra/pull/17272)) **Usage** ```typescript const vector = new PgVector({ id: 'vectors', connectionString: process.env.DATABASE_URL, disableInit: true, }); ``` The `MASTRA_DISABLE_STORAGE_INIT` environment variable also disables vector init, so a single flag prevents both storage and vector stores from creating schemas, tables, or indexes at startup. - Storage adapters can now receive a narrow back-pointer to the Mastra instance ([#17226](https://github.com/mastra-ai/mastra/pull/17226)) via `MastraCompositeStore.__registerMastra`. This mirrors the existing registration pattern on agents, workflows, memory, scorers and processors, and lets a storage domain look up agents and editor config without pulling the full Mastra type into the storage layer (which would create a circular import). The reference is cascaded automatically to any parent composites and owned domain stores, and is wired both during Mastra construction and via `setStorage`. The editor is registered after storage so editor-driven storage overlays observe the assigned storage. A new `StorageMastraRef` interface exposes only the methods storage needs today (`getAgentById`, `getEditor`). ```ts // Inside a domain store, read the registered reference after Mastra wires it up: class MyAgentsStore extends AgentsStorage { protected getEditorConfig(agentId: string) { // `this.mastra` is populated by MastraCompositeStore.__registerMastra, // which runs during Mastra construction and on setStorage(). const agent = this.mastra?.getAgentById?.(agentId); if (agent?.source !== 'code') return undefined; return agent.__getEditorConfig?.(); } } ``` - Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. ([#17248](https://github.com/mastra-ai/mastra/pull/17248)) **Stored agents can now pin OAuth connections per toolkit** A stored agent's config accepts a new `toolProviders` shape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller. ```ts { toolProviders: { composio: { connections: { gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }], }, tools: { GMAIL_FETCH_EMAILS: { toolkit: 'gmail' }, }, }, }, } ``` **New client SDK surface for managing connections** ```ts import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); const composio = client.toolProvider('composio'); const { items } = await composio.listConnections({ toolkit: 'gmail' }); await composio.disconnectConnection('auth_abc'); ``` **New `ToolProvider` interface for custom providers** Providers implement a VNext surface (`listToolkitsVNext`, `listToolsVNext`, `resolveToolsVNext`) plus the auth round-trip (`authorize`, `getAuthStatus`, `listConnections`, `disconnectConnection`, `listConnectionFields`, `health`). The Composio provider has been rewritten on this surface; the older catalog methods remain as `@deprecated` shims for back-compat. Connections list responses use `page`/`perPage` pagination, matching the rest of the server surface. Both stored agents (`editor.agent.getById(...)`) and code-defined agents with stored overrides (`editor.agent.applyStoredOverrides(...)`) resolve `toolProviders` at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools. Stored agents that don't set `toolProviders` continue to work unchanged. The Studio/Builder UI ships separately. ### Patch Changes - Improved per-message latency in channels by removing two awaited storage round-trips from the chat message dispatch path. ([#17185](https://github.com/mastra-ai/mastra/pull/17185)) - Fixed Observational Memory and other tagged system context being lost when an agent uses Channels. Channel-specific context now adds itself alongside other processors' system messages instead of replacing them. ([#17168](https://github.com/mastra-ai/mastra/pull/17168)) - Fixed output processors so they receive agent step lifecycle chunks during streaming. ([#16687](https://github.com/mastra-ai/mastra/pull/16687)) - Fixed UnixSocketPubSub streaming so a slow or stuck subscriber no longer blocks active local streams or other subscribers. ([#17302](https://github.com/mastra-ai/mastra/pull/17302)) - Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. ([#17311](https://github.com/mastra-ai/mastra/pull/17311)) ```ts await agent.sendToolApproval({ resourceId: 'user-123', threadId: 'thread-123', toolCallId: 'tool-call-123', approved: true, }); ``` - Fixed `filterMessagesForPersistence` unconditionally trimming whitespace from text parts, which caused spaces between words to be lost when text parts were split by token boundaries in the streaming span-based persistence. The trim now only applies when working memory tags are actually stripped. ([#17404](https://github.com/mastra-ai/mastra/pull/17404)) - Fixed resumed agent observability spans so `agent.resumeStream()` and `agent.resumeGenerate()` use the resume payload as the `AGENT_RUN` span input instead of an empty array. ([#17134](https://github.com/mastra-ai/mastra/pull/17134)) Resumed spans now also link back to the suspended trace when persisted tracing context is available, so human-in-the-loop approval flows show the decision payload and remain connected in tracing backends. Fixes #17075. - Fixed BatchPartsProcessor dropping the final stream part when a stopWhen condition stops the agent on a non-text part (such as a tool result). The processor batches text deltas and previously deferred the next non-text part to the following stream iteration; if the loop stopped on that part, it was lost. BatchPartsProcessor now returns the flushed batch and hands the non-text part back to the output processor runner, which re-drives it through the full output processor chain. As a result the final tool result always reaches the stream, and the flushed batch still passes through any downstream output processors (e.g. a moderation or PII processor configured after BatchPartsProcessor) instead of bypassing them. Fixes #17094. ([#17342](https://github.com/mastra-ai/mastra/pull/17342)) - Fixed Convex workflow storage to save concurrent workflow updates atomically. ([#16641](https://github.com/mastra-ai/mastra/pull/16641)) - Fixed processor-returned `systemMessages` wiping tagged system messages owned by other processors (e.g. observational memory). Processor `args.systemMessages` now exposes only the untagged system message bucket, so tagged messages owned by other processors are no longer round-tripped through the replacement API. `MessageList.replaceAllSystemMessages()` replaces only the untagged bucket and leaves tagged buckets intact. Final model input still receives both via `messageList.getAllSystemMessages()`. ([#16950](https://github.com/mastra-ai/mastra/pull/16950)) - Moved shared voice primitives and route metadata into the new `@internal/voice` package so voice providers no longer depend on `@mastra/core` and server voice routes share the same route definitions. ([#16725](https://github.com/mastra-ai/mastra/pull/16725)) `@mastra/core/voice` continues to re-export the voice APIs for backwards compatibility. - Fixed `AgentExecutionOptions` (and its public/inner variants) incorrectly requiring a `structuredOutput` property. When the output type is `undefined` or `null` — including a fully-nullish union such as `undefined | null` — `structuredOutput` is now correctly optional, regardless of the `strictNullChecks` setting. This is the shape produced by `AgentConfig.defaultOptions`, so `defaultOptions: { maxSteps: 50 }` now type-checks without a spurious `structuredOutput` requirement. Object output types still require `structuredOutput` as before. ([#17131](https://github.com/mastra-ai/mastra/pull/17131)) - Fixes a crash where OpenAI rejects resumed tool-approval requests with `AI_APICallError: Duplicate item found with id rs_*`. ([#17439](https://github.com/mastra-ai/mastra/pull/17439)) The same message stored in PostgreSQL's `jsonb` column (workflow snapshot) and `text` column (messages) could have different JSON key orders, causing the deduplication check to treat them as different messages and send the same reasoning item twice. Both representations now compare equal regardless of key order. - Fixed a render crash when loading stored threads containing signal messages (such as system reminders). Non-user signal data parts are now merged onto the neighboring assistant message instead of becoming standalone system messages that break assistant-ui. ([#17429](https://github.com/mastra-ai/mastra/pull/17429)) - Added Alibaba provider support for Qwen models. You can now use Qwen, DashScope, and other Alibaba models with automatic provider detection. ([#17433](https://github.com/mastra-ai/mastra/pull/17433)) **Example usage:** ```typescript import { Mastra } from '@mastra/core'; const mastra = new Mastra(); // Use any Alibaba variant - automatically detected const agent = mastra.getAgent('myAgent'); const result = await agent.generate({ model: '__GATEWAY_ALIBABA_MODEL__', messages: [{ role: 'user', content: 'Hello' }], }); ``` Works with all Alibaba variants (alibaba, alibaba-cn, alibaba-coding-plan, etc.) and future variants like alibaba-coding-plan-cn-v2. - Fixed observational memory replaying previously observed assistant responses during reprocessing, so past assistant messages no longer reappear in later turns. ([#17338](https://github.com/mastra-ai/mastra/pull/17338)) - Use `queueMessage()` for Harness follow-up scheduling while preserving queued follow-up display state. ([#17191](https://github.com/mastra-ai/mastra/pull/17191)) - Asset download errors now include the failing URL so callers can identify which media link broke and recover from it (e.g. drop the dead part on retry). The URL appears redacted (query string and fragment stripped) in the error message and in full on `error.details.url`. ([#17069](https://github.com/mastra-ai/mastra/pull/17069)) - Fixed output processor state continuity for step lifecycle chunks during streaming. Lifecycle chunks (e.g. `step-finish`) routed through the stream `outputWriter` now share the same per-processor state map as the main model-output path, so state set in `processOutputStream` while handling content chunks is visible when the lifecycle chunk is processed (and in `processOutputResult`). Previously these chunks were handled with an isolated, empty state. ([#17370](https://github.com/mastra-ai/mastra/pull/17370)) Note: as part of routing lifecycle chunks through output processors, an aborted stream now surfaces content the model produced before the abort (e.g. a `text-start` chunk and partial `text`) instead of dropping it. Consumers that assumed an aborted result always had empty text may now observe partial output. - Removed a stale AI SDK UI utils dependency from @mastra/core so projects using Zod 4 do not get Zod 3 peer dependency warnings from core. ([#16994](https://github.com/mastra-ai/mastra/pull/16994)) - Fixed AGENT_RUN spans not closing when an agent stream is aborted mid-flight (e.g. browser disconnect or `AbortController.abort()`). Aborted runs now end with `{ status: 'aborted', reason: 'abort' }` so traces are exported to observability backends. ([#17203](https://github.com/mastra-ai/mastra/pull/17203)) - Improved active workflow run listing latency. ([#17374](https://github.com/mastra-ai/mastra/pull/17374)) - `Mastra.shutdown()` now releases storage resources automatically. Stores that expose a `close()` lifecycle hook (such as `LibSQLStore`) are closed during shutdown, so file handles are freed and the storage directory can be removed cleanly afterward, including on Windows. ([#17306](https://github.com/mastra-ai/mastra/pull/17306)) ```typescript const mastra = new Mastra({ storage }); // Storage is closed for you — no manual cleanup needed await mastra.shutdown(); ``` - Add `updateThread` as an abstract method on the `MastraMemory` base class and implement it in `MockMemory`. Previously the method existed only on the concrete `Memory` subclass, so calling `updateThread` on a variable typed as `MastraMemory` (or any other `MastraMemory` subclass) produced a TypeScript error. Callers can now rename or re-title threads through the base class API without casting. ([#17130](https://github.com/mastra-ai/mastra/pull/17130)) - Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. ([#17165](https://github.com/mastra-ai/mastra/pull/17165)) - Fixed read_file line ranges when offsets are past the end of a file ([#17275](https://github.com/mastra-ai/mastra/pull/17275)) - Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. ([#17475](https://github.com/mastra-ai/mastra/pull/17475)) - Fixed forEach workflow steps losing completed outputs and status during resume. ([#17294](https://github.com/mastra-ai/mastra/pull/17294)) - Reduced overhead when streaming tool calls in agentic workflows ([#17354](https://github.com/mastra-ai/mastra/pull/17354)) - Fixed noisy "Cannot get workflow run. Mastra storage is not initialized" debug logs that appeared on every `agent.generate()` and `agent.stream()` call when the agent's Mastra instance had storage configured. ([#17344](https://github.com/mastra-ai/mastra/pull/17344)) The internal workflow that runs each agent call never received the parent Mastra instance, so it could not see configured storage and logged the warning before falling back to in-memory state. It now receives the Mastra instance. It still does not write any of its own snapshots to your storage, so no extra rows are created. - Fixed Google model routing to accept GOOGLE_GENERATIVE_AI_API_KEY when GOOGLE_API_KEY is not set. ([#17343](https://github.com/mastra-ai/mastra/pull/17343)) - Fixed grep context output so overlapping matches are shown once. ([#17274](https://github.com/mastra-ai/mastra/pull/17274)) - Added `jsonPromptInjection` to the scorer `judge` config so users can opt out of native `response_format` for models that don't support it (e.g. some Groq Llama models). Previously, every scorer invocation made a wasted 400 API call before falling back to prompt injection. ([#17046](https://github.com/mastra-ai/mastra/pull/17046)) ```typescript import { createScorer } from '@mastra/core/evals'; const scorer = createScorer({ id: 'translation-quality', description: 'Evaluates translation quality', judge: { model: 'groq/llama-3.3-70b-versatile', instructions: 'You are an expert evaluator…', jsonPromptInjection: true, // skip the unsupported `response_format` attempt }, }); ``` Fixes #17040. - Added native multimodal tool-result support. Core now converts MCP-style tool results with image and audio `content` parts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads in `providerMetadata.mastra.modelOutput`. ([#16866](https://github.com/mastra-ai/mastra/pull/16866)) ```ts return { content: [ { type: 'text', text: 'Screenshot captured' }, { type: 'image', data: base64Png, mimeType: 'image/png' }, ], }; ``` - Fixed `MASTRA_TELEMETRY_DISABLED` opt-out detection. The values `1`, `true`, and `yes` (case-insensitive, trimmed) now reliably disable telemetry in both `@mastra/core` enterprise events and the `mastra` CLI's PostHog analytics. ([#16990](https://github.com/mastra-ai/mastra/pull/16990)) Previously, `@mastra/core` only treated the literal string `'1'` as disabled, so common opt-out values like `MASTRA_TELEMETRY_DISABLED=true` silently kept telemetry on. The `mastra` CLI's `PosthogAnalytics` constructor now also short-circuits when telemetry is disabled — no disk I/O, no tracking ID generation, no PostHog client. Previously the config file (`mastra-cli.json`) was written even when telemetry was disabled. **Example:** ```bash # .env — any of these now reliably disable telemetry MASTRA_TELEMETRY_DISABLED=true MASTRA_TELEMETRY_DISABLED=1 MASTRA_TELEMETRY_DISABLED=yes ``` - Improved PIIDetector streaming performance. ([#17377](https://github.com/mastra-ai/mastra/pull/17377)) - Removed per-chunk LLM calls during streaming PII checks. - Added local regex detection for common PII types (email, phone, SSN, credit card, IP address, API keys, URLs, UUIDs, crypto wallets, and IBAN). - Added regex carryover buffer across chunk boundaries to catch split PII patterns. - Buffered context-dependent PII types (names, addresses, dates of birth) with periodic LLM calls at configurable thresholds. - Added `bufferSize` option (default: 200) to control LLM buffer flush threshold. - Reduced streaming API cost, latency, and rate-limit pressure. Closes #16466. - Fixed a TypeScript TS2589 "type instantiation is excessively deep" error when using Mastra alongside deeply-generic libraries such as @hono/zod-openapi. ([#17339](https://github.com/mastra-ai/mastra/pull/17339)) - Improved observability and error isolation in the v1 ToolProvider runtime. ([#17248](https://github.com/mastra-ai/mastra/pull/17248)) **Better visibility into connection-scope misconfiguration** When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime. **One bad toolkit no longer disables sibling providers** If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally. - Workflows now support an optional `metadata` field for attaching custom key-value data such as `displayName`, `author`, or `category`. Metadata is preserved through serialization and returned in workflow info API responses. ([#17355](https://github.com/mastra-ai/mastra/pull/17355)) ```typescript // Define a workflow with metadata const myWorkflow = createWorkflow({ id: 'data-processing', metadata: { displayName: 'Data Processing Pipeline', category: 'ETL', }, inputSchema: z.object({ ... }), outputSchema: z.object({ ... }), }); // Retrieve workflow info with metadata via the Mastra Server API const workflowInfo = await mastraClient.getWorkflow('data-processing'); console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline" ``` - Updated dependencies [[`00eca42`](https://github.com/mastra-ai/mastra/commit/00eca4252393aa114dc8c9a5e1da68df91fa06cf), [`ff9d743`](https://github.com/mastra-ai/mastra/commit/ff9d743f71d7e072927725c0d700632aca0c1fee)]: - @mastra/schema-compat@1.2.11 ## 1.38.0-alpha.9 ### Patch Changes - Fixed `filterMessagesForPersistence` unconditionally trimming whitespace from text parts, which caused spaces between words to be lost when text parts were split by token boundaries in the streaming span-based persistence. The trim now only applies when working memory tags are actually stripped. ([#17404](https://github.com/mastra-ai/mastra/pull/17404)) - Fixes a crash where OpenAI rejects resumed tool-approval requests with `AI_APICallError: Duplicate item found with id rs_*`. ([#17439](https://github.com/mastra-ai/mastra/pull/17439)) The same message stored in PostgreSQL's `jsonb` column (workflow snapshot) and `text` column (messages) could have different JSON key orders, causing the deduplication check to treat them as different messages and send the same reasoning item twice. Both representations now compare equal regardless of key order. ## 1.38.0-alpha.8 ### Minor Changes - Added `channels.resolveResourceId` to control which `resourceId` owns a channel thread's memory, separately from who sent the message. Useful for SSO apps that want a user's memory shared across web and a Feishu/Lark DM, or group chats scoped to the conversation instead of the sender. Only affects newly-created threads; return the provided default to keep current behavior. ([#17471](https://github.com/mastra-ai/mastra/pull/17471)) ```ts new Agent({ // ... channels: { adapters: { slack: createSlackAdapter() }, resolveResourceId: async ({ thread, message }) => { if (thread.isDM) return resolveSsoUserId(message); // shared with web return thread.channelId; // group owns the memory }, }, }); ``` ### Patch Changes - Fixed FGA-enabled MCP servers so OAuth authInfo can be mapped to a Mastra user before tools/list and tools/call authorization. ([#17475](https://github.com/mastra-ai/mastra/pull/17475)) ## 1.38.0-alpha.7 ## 1.38.0-alpha.6 ### Patch Changes - Added subscription-native tool approval APIs so approving or declining a tool call resumes through the active thread subscription instead of requiring a separate continuation stream. New messages are queued while a tool approval is waiting, preventing overlapping runs from duplicating approval requests. ([#17311](https://github.com/mastra-ai/mastra/pull/17311)) ```ts await agent.sendToolApproval({ resourceId: 'user-123', threadId: 'thread-123', toolCallId: 'tool-call-123', approved: true, }); ``` - Fixed a render crash when loading stored threads containing signal messages (such as system reminders). Non-user signal data parts are now merged onto the neighboring assistant message instead of becoming standalone system messages that break assistant-ui. ([#17429](https://github.com/mastra-ai/mastra/pull/17429)) - Added Alibaba provider support for Qwen models. You can now use Qwen, DashScope, and other Alibaba models with automatic provider detection. ([#17433](https://github.com/mastra-ai/mastra/pull/17433)) **Example usage:** ```typescript import { Mastra } from '@mastra/core'; const mastra = new Mastra(); // Use any Alibaba variant - automatically detected const agent = mastra.getAgent('myAgent'); const result = await agent.generate({ model: '__GATEWAY_ALIBABA_MODEL__', messages: [{ role: 'user', content: 'Hello' }], }); ``` Works with all Alibaba variants (alibaba, alibaba-cn, alibaba-coding-plan, etc.) and future variants like alibaba-coding-plan-cn-v2. ## 1.38.0-alpha.5 ### Minor Changes - Added harness events for session lifecycle updates, mode changes, model changes, and cloned threads. ([#17290](https://github.com/mastra-ai/mastra/pull/17290)) Users can now subscribe to harness events to observe harness activity. **Example** ```ts const unsubscribe = harness.subscribe(event => { console.log(event.id, event.type); }); ``` - Added agent override support to the agent and editor APIs. ([#17227](https://github.com/mastra-ai/mastra/pull/17227)) Code-defined agents can now declare which fields Studio may edit with the `editor` option: ```ts new Agent({ name: 'Weather Agent', model, editor: { instructions: true, tools: { description: true }, }, }); ``` The editor applies stored overrides only for fields the `editor` config owns, so locked fields keep their code-defined values. Per-agent `editor: false` locks an agent entirely. `MastraEditor` accepts a `source` setting that picks the editing experience: ```ts new MastraEditor({ source: 'code' }); ``` - `source: 'code'` — the editor auto-wires a `FilesystemStore` (defaulting to `./mastra/editor/`, overridable with `codePath`) when no editor storage is supplied, and persists overrides as deterministic per-agent JSON files. - `source: 'db'` (default) — keeps the existing storage-backed flow against whatever storage the project has configured. ## 1.38.0-alpha.4 ### Minor Changes - Workflows that suspend during dataset experiments now resume automatically. Provide resume data in your dataset items and the workflow will continue execution through multiple suspend/resume cycles. ([#17378](https://github.com/mastra-ai/mastra/pull/17378)) For multi-step workflows, use `resumeSteps` keyed by step ID: ```typescript const item = { input: { prompt: 'Draft a blog post' }, resumeSteps: { 'approval-step': { approved: true } }, }; ``` For single-step workflows, use flat `resumeData`: ```typescript const item = { input: { prompt: 'Draft a blog post' }, resumeData: { approved: true }, }; ``` Storage-backed items can use `metadata.resumeSteps` or `metadata.resumeData` as fallback. When no resume data is provided, the suspend payload is returned as output with guidance on how to add it. ([#15382](https://github.com/mastra-ai/mastra/issues/15382)) - Added the v1 ToolProvider runtime, server routes, client SDK methods, and editor wiring that power OAuth-backed integrations on stored agents. ([#17248](https://github.com/mastra-ai/mastra/pull/17248)) **Stored agents can now pin OAuth connections per toolkit** A stored agent's config accepts a new `toolProviders` shape that tells the runtime which connection to bind for each toolkit at execution time. Connections can be scoped per-author, shared across an org, or supplied by the caller. ```ts { toolProviders: { composio: { connections: { gmail: [{ kind: 'author', toolkit: 'gmail', connectionId: 'auth_abc', scope: 'per-author' }], }, tools: { GMAIL_FETCH_EMAILS: { toolkit: 'gmail' }, }, }, }, } ``` **New client SDK surface for managing connections** ```ts import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: '…' }); const composio = client.toolProvider('composio'); const { items } = await composio.listConnections({ toolkit: 'gmail' }); await composio.disconnectConnection('auth_abc'); ``` **New `ToolProvider` interface for custom providers** Providers implement a VNext surface (`listToolkitsVNext`, `listToolsVNext`, `resolveToolsVNext`) plus the auth round-trip (`authorize`, `getAuthStatus`, `listConnections`, `disconnectConnection`, `listConnectionFields`, `health`). The Composio provider has been rewritten on this surface; the older catalog methods remain as `@deprecated` shims for back-compat. Connections list responses use `page`/`perPage` pagination, matching the rest of the server surface. Both stored agents (`editor.agent.getById(...)`) and code-defined agents with stored overrides (`editor.agent.applyStoredOverrides(...)`) resolve `toolProviders` at request time, merging provider-resolved tools alongside code/registry/MCP/integration tools. Stored agents that don't set `toolProviders` continue to work unchanged. The Studio/Builder UI ships separately. ### Patch Changes - Improved agent performance in long-running loops with many iterations. ([#17368](https://github.com/mastra-ai/mastra/pull/17368)) - Fixed output processors so they receive agent step lifecycle chunks during streaming. ([#16687](https://github.com/mastra-ai/mastra/pull/16687)) - Fixed `AgentExecutionOptions` (and its public/inner variants) incorrectly requiring a `structuredOutput` property. When the output type is `undefined` or `null` — including a fully-nullish union such as `undefined | null` — `structuredOutput` is now correctly optional, regardless of the `strictNullChecks` setting. This is the shape produced by `AgentConfig.defaultOptions`, so `defaultOptions: { maxSteps: 50 }` now type-checks without a spurious `structuredOutput` requirement. Object output types still require `structuredOutput` as before. ([#17131](https://github.com/mastra-ai/mastra/pull/17131)) - Fixed output processor state continuity for step lifecycle chunks during streaming. Lifecycle chunks (e.g. `step-finish`) routed through the stream `outputWriter` now share the same per-processor state map as the main model-output path, so state set in `processOutputStream` while handling content chunks is visible when the lifecycle chunk is processed (and in `processOutputResult`). Previously these chunks were handled with an isolated, empty state. ([#17370](https://github.com/mastra-ai/mastra/pull/17370)) Note: as part of routing lifecycle chunks through output processors, an aborted stream now surfaces content the model produced before the abort (e.g. a `text-start` chunk and partial `text`) instead of dropping it. Consumers that assumed an aborted result always had empty text may now observe partial output. - Improved active workflow run listing latency. ([#17374](https://github.com/mastra-ai/mastra/pull/17374)) - Improved PIIDetector streaming performance. ([#17377](https://github.com/mastra-ai/mastra/pull/17377)) - Removed per-chunk LLM calls during streaming PII checks. - Added local regex detection for common PII types (email, phone, SSN, credit card, IP address, API keys, URLs, UUIDs, crypto wallets, and IBAN). - Added regex carryover buffer across chunk boundaries to catch split PII patterns. - Buffered context-dependent PII types (names, addresses, dates of birth) with periodic LLM calls at configurable thresholds. - Added `bufferSize` option (default: 200) to control LLM buffer flush threshold. - Reduced streaming API cost, latency, and rate-limit pressure. Closes #16466. - Improved observability and error isolation in the v1 ToolProvider runtime. ([#17248](https://github.com/mastra-ai/mastra/pull/17248)) **Better visibility into connection-scope misconfiguration** When an agent runs with a stored ToolProvider connection whose scope cannot be resolved from the request context, the runtime now logs a one-shot warning and falls back to a shared bucket instead of silently routing every caller to the same OAuth account. Multi-tenant deployments get a clear signal when their identity wiring isn't reaching the runtime. **One bad toolkit no longer disables sibling providers** If a provider returns more connections for a toolkit than its declared capabilities allow, the runtime now logs and skips that toolkit instead of throwing. Other providers and other toolkits on the same agent continue to resolve normally. ## 1.38.0-alpha.3 ### Minor Changes - Added `agent.sendMessage()` and `agent.queueMessage()` APIs for sending user-authored input into agent threads. These are intended to be used with `agent.subscribeToThread()` and replace lower-level `agent.sendSignal()` calls for regular user messages. ([#17191](https://github.com/mastra-ai/mastra/pull/17191)) ```ts await agent.sendMessage('Continue with the latest user input', { resourceId, threadId }); await agent.queueMessage('Follow up after the active turn finishes', { resourceId, threadId }); await agent.sendMessage( { contents: [ { type: 'text', text: 'What is in this image?' }, { type: 'file', data: imageBase64, mediaType: 'image/png', filename: 'screenshot.png' }, ], }, { resourceId, threadId }, ); ``` - Support conditional, function-based tool approvals. ([#17337](https://github.com/mastra-ai/mastra/pull/17337)) - MCP tools that wrap a server-level `requireToolApproval` function are now honored end-to-end. The per-tool approval function was previously dropped when the agent converted MCP tools (it kept only the boolean flag), so conditional approval silently fell back to always-on. `CoreToolBuilder` now preserves a `needsApprovalFn` attached directly to a tool instance. - The global `requireToolApproval` option on `agent.stream`/`agent.generate` now accepts a function in addition to a boolean. It is evaluated per tool call with the tool name, arguments, and request context, enabling policies such as regex allowlists on tool names. Returning `true` requires approval for that call; `false` allows it. On error the call defaults to requiring approval. When a function policy is set, tool calls run sequentially so approval suspensions don't race. Durable agents and stored agents continue to accept only a boolean (a function degrades to requiring approval for every call, since their options must be serializable). ```ts // Approve only tool calls whose name is not on an allowlist. const allowlist = /^(get|list|search)_/; await agent.generate('...', { requireToolApproval: ({ toolName }) => !allowlist.test(toolName), }); ``` - Precedence is unchanged from before: a per-tool approval function (`createTool({ requireApproval: fn })` or an MCP-derived `needsApprovalFn`) is authoritative for that tool and overrides the global setting, so a tool can still opt out of approval by returning `false` even when the global option is on. The only new behavior is that the global option may now be a function in addition to a boolean. - The previously implicit, runtime-attached per-tool approval predicate is now a typed contract: `@mastra/core` exports `NeedsApprovalFn` and declares the optional `needsApprovalFn` property on the `Tool` class. The MCP client and the agent runtime now share this typed contract instead of reaching through `any`. This is additive — no public API changes. - Added the `tool_provider_connections` storage domain. Stored agents can now persist per-agent ToolProvider config that round-trips on read/write/create. Runtime connection resolution (per-author, shared, caller-supplied) ships in a follow-up PR. ([#17247](https://github.com/mastra-ai/mastra/pull/17247)) **What you can do** - Pin a connection on a stored agent's config and have it round-trip on read/write/create. - Persist multiple connections per toolkit so a follow-up runtime PR can fan-out to the right one at execution time. **Example** ```ts import { LibSQLStore } from '@mastra/libsql'; const storage = new LibSQLStore({ url: process.env.DATABASE_URL }); // Persist an OAuth connection that an agent can pin later await storage.toolProviders.upsertConnection({ authorId: 'user-123', providerId: 'composio', connectionId: 'auth_abc', toolkit: 'gmail', label: 'Work inbox', scope: 'per-author', }); // List a user's own connections (admin can omit authorId to list across users) const { items } = await storage.toolProviders.listConnectionsByAuthor({ authorId: 'user-123', providerId: 'composio', }); ``` Additive — existing stored agents continue to work unchanged. The runtime that consumes this domain ships in a follow-up PR. PR 1 of 3 split from #17224. - Added opt-in MCP server instructions forwarding into agent system prompts. ([#17155](https://github.com/mastra-ai/mastra/pull/17155)) When an MCP server advertises instructions during initialization, you can now forward that guidance into the system prompt of agents that use the server's tools. This is **opt-in** — set `forwardInstructions: true` per server to enable it. Forwarded instructions are injected into the agent's system prompt, so only enable this for servers you trust. ```ts const mcp = new MCPClient({ servers: { db: { url: new URL('http://localhost:4111/mcp'), forwardInstructions: true, // opt in; defaults to false instructionsMaxLength: 512, // max chars forwarded per server }, }, }); const agent = new Agent({ id: 'db-agent', name: 'DB Agent', instructions: 'Help with database changes.', model, tools: await mcp.listTools(), }); ``` You can always inspect cached instructions without forwarding them: ```ts const instructions = mcp.getServerInstructions(); // => { db: 'Always validate before migrating.', other: undefined } ``` - Add fire-and-forget workflow resume that returns immediately with `{ runId }` without awaiting the run output. ([#17230](https://github.com/mastra-ai/mastra/pull/17230)) For `@mastra/inngest`, this skips the `getRunOutput()` polling that previously raced a realtime subscription against the Inngest runs API and could surface spurious 404s even though the durable workflow was running fine. - `Run.resumeAsync()` added to core: dispatches the resume in the background and returns `{ runId }` immediately. Engines that poll for results (Inngest) override it to skip polling entirely. - `InngestRun.resumeAsync()` sends the resume event and returns `{ runId }`, skipping polling. Send-time failures (bad payload, event send failure) still reject synchronously and roll back the snapshot. - New `POST /workflows/:workflowId/resume-no-wait` and `POST /agent-builder/:actionId/resume-no-wait` routes return `{ runId }` immediately. - New client SDK `run.resumeNoWait()` resolves with `{ runId }`. The existing `resumeAsync()` client/server surface is unchanged and still resolves with the full workflow result, so there is no breaking change. `resumeNoWait` is intentionally additive in v1. In Mastra v2 the fire-and-forget behavior is planned to become the default behavior of `resumeAsync()` (mirroring `start`/`resume` semantics), at which point `resumeNoWait` and the `resume-no-wait` routes will be removed. The code paths carry `TODO(v2)` comments documenting this consolidation. - Added experimental Code Mode for agents. `createCodeMode` returns an `execute_typescript` tool plus generated instructions that let an agent write one TypeScript program to orchestrate your tools (batch with `Promise.all`, aggregate, and do math in a real runtime) instead of calling tools one at a time. Tools still run on the host with full validation and tracing; only the orchestration code runs in a workspace sandbox. ([#17324](https://github.com/mastra-ai/mastra/pull/17324)) ```typescript import { createCodeMode, createTool } from '@mastra/core/tools'; const { tool, instructions } = createCodeMode({ tools: { getTopProducts, getProductRatings }, }); const agent = new Agent({ instructions: ['You are a helpful assistant.', instructions], tools: { execute_typescript: tool }, }); ``` - Add per-entity file persistence and per-entity git history to `FilesystemVersionedHelpers`. ([#17225](https://github.com/mastra-ai/mastra/pull/17225)) `FilesystemVersionedHelpers` now accepts three optional hooks that let a storage domain split a published entity across many per-entity JSON files (e.g. `agents/.json`) instead of one shared map file: - `perEntityFilesDir` — directory (under the FilesystemDB root) for per-entity files. - `shouldPersistToPerEntityFile(entity)` — decide per published entity whether to write its snapshot to a per-entity file. - `perEntitySnapshotFilter(snapshot, entity)` — filter the snapshot before writing it to the per-entity file (e.g. drop fields the user does not own). When configured, the helper: - Reads per-entity files on hydrate (alongside the shared map file). - Writes published snapshots to per-entity files with stable alphabetical key ordering for friendly diffs. - Walks per-entity file git history and surfaces each commit as a read-only version in `listVersions` (in addition to the existing shared-file git history). - Skips writing an empty shared map file when every published entity is persisted to per-entity files, so a code-only project does not end up with an empty stub committed to git. Also adds `FilesystemDB.listDomainFiles`, `domainFileExists`, and `removeDomainFile` helpers, and broadens `GitHistory.getFileAtCommit` to be generic so callers can request a per-entity snapshot type rather than the shared-map shape. Example — configure a domain to persist each entity to its own file: ```typescript import { FilesystemDB, FilesystemVersionedHelpers } from '@mastra/core/storage'; const db = new FilesystemDB('./mastra/editor'); // Entities that should be persisted as their own files. const codeModeEntityIds = new Set(['support-bot']); const agents = new FilesystemVersionedHelpers({ db, entitiesFile: 'agents.json', parentIdField: 'agentId', name: 'agents', versionMetadataFields: ['id', 'agentId', 'versionNumber', 'createdAt'], // New per-entity hooks: perEntityFilesDir: 'agents', // Decide per entity whether it gets its own file (vs. the shared agents.json). shouldPersistToPerEntityFile: entity => codeModeEntityIds.has(entity.id), // Drop fields that should not live in the per-entity file. perEntitySnapshotFilter: snapshot => { const { model, ...userOwned } = snapshot; return userOwned; }, }); // Published snapshots are now written to ./mastra/editor/agents/.json, // and each git commit to those files shows up as a read-only version. const versions = await agents.listVersions({ agentId: 'support-bot' }, 'agentId'); ``` - Added support for resolving an agent's `voice` per request. ([#17345](https://github.com/mastra-ai/mastra/pull/17345)) You can now pass `voice` as a resolver, just like `instructions`, `tools`, and `model`. Mastra runs the resolver on each `getVoice()` call and returns a fresh, session-owned voice instance. This fixes concurrent realtime and speech-to-speech sessions on a single deployed agent, where a shared voice instance previously let one session overwrite another session's WebSocket, tools, and instructions. A static `voice` keeps its existing shared behavior, so this change is backward compatible. **Before** ```typescript const agent = new Agent({ name: 'support-line', voice: new GeminiLiveVoice({ apiKey: KEY }), // shared across every session }); ``` **After** ```typescript const agent = new Agent({ name: 'support-line', voice: ({ requestContext }) => new GeminiLiveVoice({ apiKey: requestContext.get('apiKey') }), }); const voice = await agent.getVoice({ requestContext }); // owns its own ws/tools/instructions await voice.connect(); ``` The caller owns the lifecycle of a resolver instance and should call `disconnect()` when the session ends. The `agent.voice` getter throws when `voice` is a resolver because it has no request context; use `agent.getVoice({ requestContext })` instead. - Added request-aware filtering for ToolSearchProcessor search, load, and active tools. The filter hook receives the resolved tool ID as `toolName`. ([#16088](https://github.com/mastra-ai/mastra/pull/16088)) ```ts new ToolSearchProcessor({ tools, filter: ({ toolName, requestContext }) => { const plan = requestContext?.get('plan'); return plan === 'pro' || !toolName.startsWith('premium_'); }, }); ``` - Added the `disableInit` option to the `MastraVector` base class. When set to `true`, vector stores skip creating schemas, extensions, tables, and indexes at application startup. This matches the existing `disableInit` behavior on storage adapters and is useful for deployments where schemas and indexes are created ahead of time by a privileged database role, while the application runs with a least-privilege role. ([#17272](https://github.com/mastra-ai/mastra/pull/17272)) **Usage** ```typescript const vector = new PgVector({ id: 'vectors', connectionString: process.env.DATABASE_URL, disableInit: true, }); ``` The `MASTRA_DISABLE_STORAGE_INIT` environment variable also disables vector init, so a single flag prevents both storage and vector stores from creating schemas, tables, or indexes at startup. - Storage adapters can now receive a narrow back-pointer to the Mastra instance ([#17226](https://github.com/mastra-ai/mastra/pull/17226)) via `MastraCompositeStore.__registerMastra`. This mirrors the existing registration pattern on agents, workflows, memory, scorers and processors, and lets a storage domain look up agents and editor config without pulling the full Mastra type into the storage layer (which would create a circular import). The reference is cascaded automatically to any parent composites and owned domain stores, and is wired both during Mastra construction and via `setStorage`. The editor is registered after storage so editor-driven storage overlays observe the assigned storage. A new `StorageMastraRef` interface exposes only the methods storage needs today (`getAgentById`, `getEditor`). ```ts // Inside a domain store, read the registered reference after Mastra wires it up: class MyAgentsStore extends AgentsStorage { protected getEditorConfig(agentId: string) { // `this.mastra` is populated by MastraCompositeStore.__registerMastra, // which runs during Mastra construction and on setStorage(). const agent = this.mastra?.getAgentById?.(agentId); if (agent?.source !== 'code') return undefined; return agent.__getEditorConfig?.(); } } ``` ### Patch Changes - Fixed failed tool calls showing up as successful when a conversation is reloaded from history. Previously a tool that threw an error displayed correctly while the agent was running, but reappeared as a successful result once the conversation was loaded from memory. The error is now preserved and displayed on reload, matching what you see during the live run. Fixes #15569. ([#16976](https://github.com/mastra-ai/mastra/pull/16976)) - Improved per-message latency in channels by removing two awaited storage round-trips from the chat message dispatch path. ([#17185](https://github.com/mastra-ai/mastra/pull/17185)) - Fixed UnixSocketPubSub streaming so a slow or stuck subscriber no longer blocks active local streams or other subscribers. ([#17302](https://github.com/mastra-ai/mastra/pull/17302)) - Fixed resumed agent observability spans so `agent.resumeStream()` and `agent.resumeGenerate()` use the resume payload as the `AGENT_RUN` span input instead of an empty array. ([#17134](https://github.com/mastra-ai/mastra/pull/17134)) Resumed spans now also link back to the suspended trace when persisted tracing context is available, so human-in-the-loop approval flows show the decision payload and remain connected in tracing backends. Fixes #17075. - Fixed BatchPartsProcessor dropping the final stream part when a stopWhen condition stops the agent on a non-text part (such as a tool result). The processor batches text deltas and previously deferred the next non-text part to the following stream iteration; if the loop stopped on that part, it was lost. BatchPartsProcessor now returns the flushed batch and hands the non-text part back to the output processor runner, which re-drives it through the full output processor chain. As a result the final tool result always reaches the stream, and the flushed batch still passes through any downstream output processors (e.g. a moderation or PII processor configured after BatchPartsProcessor) instead of bypassing them. Fixes #17094. ([#17342](https://github.com/mastra-ai/mastra/pull/17342)) - Fixed processor-returned `systemMessages` wiping tagged system messages owned by other processors (e.g. observational memory). Processor `args.systemMessages` now exposes only the untagged system message bucket, so tagged messages owned by other processors are no longer round-tripped through the replacement API. `MessageList.replaceAllSystemMessages()` replaces only the untagged bucket and leaves tagged buckets intact. Final model input still receives both via `messageList.getAllSystemMessages()`. ([#16950](https://github.com/mastra-ai/mastra/pull/16950)) - Fixed observational memory replaying previously observed assistant responses during reprocessing, so past assistant messages no longer reappear in later turns. ([#17338](https://github.com/mastra-ai/mastra/pull/17338)) - Use `queueMessage()` for Harness follow-up scheduling while preserving queued follow-up display state. ([#17191](https://github.com/mastra-ai/mastra/pull/17191)) - Asset download errors now include the failing URL so callers can identify which media link broke and recover from it (e.g. drop the dead part on retry). The URL appears redacted (query string and fragment stripped) in the error message and in full on `error.details.url`. ([#17069](https://github.com/mastra-ai/mastra/pull/17069)) - Removed a stale AI SDK UI utils dependency from @mastra/core so projects using Zod 4 do not get Zod 3 peer dependency warnings from core. ([#16994](https://github.com/mastra-ai/mastra/pull/16994)) - Fixed AGENT_RUN spans not closing when an agent stream is aborted mid-flight (e.g. browser disconnect or `AbortController.abort()`). Aborted runs now end with `{ status: 'aborted', reason: 'abort' }` so traces are exported to observability backends. ([#17203](https://github.com/mastra-ai/mastra/pull/17203)) - `Mastra.shutdown()` now releases storage resources automatically. Stores that expose a `close()` lifecycle hook (such as `LibSQLStore`) are closed during shutdown, so file handles are freed and the storage directory can be removed cleanly afterward, including on Windows. ([#17306](https://github.com/mastra-ai/mastra/pull/17306)) ```typescript const mastra = new Mastra({ storage }); // Storage is closed for you — no manual cleanup needed await mastra.shutdown(); ``` - Fixed read_file line ranges when offsets are past the end of a file ([#17275](https://github.com/mastra-ai/mastra/pull/17275)) - Fixed forEach workflow steps losing completed outputs and status during resume. ([#17294](https://github.com/mastra-ai/mastra/pull/17294)) - Reduced overhead when streaming tool calls in agentic workflows ([#17354](https://github.com/mastra-ai/mastra/pull/17354)) - Fixed noisy "Cannot get workflow run. Mastra storage is not initialized" debug logs that appeared on every `agent.generate()` and `agent.stream()` call when the agent's Mastra instance had storage configured. ([#17344](https://github.com/mastra-ai/mastra/pull/17344)) The internal workflow that runs each agent call never received the parent Mastra instance, so it could not see configured storage and logged the warning before falling back to in-memory state. It now receives the Mastra instance. It still does not write any of its own snapshots to your storage, so no extra rows are created. - Fixed Google model routing to accept GOOGLE_GENERATIVE_AI_API_KEY when GOOGLE_API_KEY is not set. ([#17343](https://github.com/mastra-ai/mastra/pull/17343)) - Fixed grep context output so overlapping matches are shown once. ([#17274](https://github.com/mastra-ai/mastra/pull/17274)) - Added native multimodal tool-result support. Core now converts MCP-style tool results with image and audio `content` parts into model-native media output when building model prompts, without requiring MCP tools to persist duplicate media payloads in `providerMetadata.mastra.modelOutput`. ([#16866](https://github.com/mastra-ai/mastra/pull/16866)) ```ts return { content: [ { type: 'text', text: 'Screenshot captured' }, { type: 'image', data: base64Png, mimeType: 'image/png' }, ], }; ``` - Fixed a TypeScript TS2589 "type instantiation is excessively deep" error when using Mastra alongside deeply-generic libraries such as @hono/zod-openapi. ([#17339](https://github.com/mastra-ai/mastra/pull/17339)) - Workflows now support an optional `metadata` field for attaching custom key-value data such as `displayName`, `author`, or `category`. Metadata is preserved through serialization and returned in workflow info API responses. ([#17355](https://github.com/mastra-ai/mastra/pull/17355)) ```typescript // Define a workflow with metadata const myWorkflow = createWorkflow({ id: 'data-processing', metadata: { displayName: 'Data Processing Pipeline', category: 'ETL', }, inputSchema: z.object({ ... }), outputSchema: z.object({ ... }), }); // Retrieve workflow info with metadata via the Mastra Server API const workflowInfo = await mastraClient.getWorkflow('data-processing'); console.log(workflowInfo.metadata?.displayName); // "Data Processing Pipeline" ``` - Updated dependencies [[`00eca42`](https://github.com/mastra-ai/mastra/commit/00eca4252393aa114dc8c9a5e1da68df91fa06cf), [`ff9d743`](https://github.com/mastra-ai/mastra/commit/ff9d743f71d7e072927725c0d700632aca0c1fee)]: - @mastra/schema-compat@1.2.11-alpha.0 ## 1.38.0-alpha.2 ### Minor Changes - Added `channels.threadContext.addSystemMessage` to opt out of the built-in channel system message. By default, `AgentChannels` injects a short system message telling the agent which channel/platform a request came from (DM vs public, bot identity, etc.). Set `addSystemMessage: false` to skip it: ([#17171](https://github.com/mastra-ai/mastra/pull/17171)) ```ts new Agent({ channels: { adapters: { slack: createSlackAdapter() }, threadContext: { addSystemMessage: false, }, }, }); ``` ### Patch Changes - Fixed Observational Memory and other tagged system context being lost when an agent uses Channels. Channel-specific context now adds itself alongside other processors' system messages instead of replacing them. ([#17168](https://github.com/mastra-ai/mastra/pull/17168)) - Fixed Convex workflow storage to save concurrent workflow updates atomically. ([#16641](https://github.com/mastra-ai/mastra/pull/16641)) - Add `updateThread` as an abstract method on the `MastraMemory` base class and implement it in `MockMemory`. Previously the method existed only on the concrete `Memory` subclass, so calling `updateThread` on a variable typed as `MastraMemory` (or any other `MastraMemory` subclass) produced a TypeScript error. Callers can now rename or re-title threads through the base class API without casting. ([#17130](https://github.com/mastra-ai/mastra/pull/17130)) - Added `jsonPromptInjection` to the scorer `judge` config so users can opt out of native `response_format` for models that don't support it (e.g. some Groq Llama models). Previously, every scorer invocation made a wasted 400 API call before falling back to prompt injection. ([#17046](https://github.com/mastra-ai/mastra/pull/17046)) ```typescript import { createScorer } from '@mastra/core/evals'; const scorer = createScorer({ id: 'translation-quality', description: 'Evaluates translation quality', judge: { model: 'groq/llama-3.3-70b-versatile', instructions: 'You are an expert evaluator…', jsonPromptInjection: true, // skip the unsupported `response_format` attempt }, }); ``` Fixes #17040. - Fixed `MASTRA_TELEMETRY_DISABLED` opt-out detection. The values `1`, `true`, and `yes` (case-insensitive, trimmed) now reliably disable telemetry in both `@mastra/core` enterprise events and the `mastra` CLI's PostHog analytics. ([#16990](https://github.com/mastra-ai/mastra/pull/16990)) Previously, `@mastra/core` only treated the literal string `'1'` as disabled, so common opt-out values like `MASTRA_TELEMETRY_DISABLED=true` silently kept telemetry on. The `mastra` CLI's `PosthogAnalytics` constructor now also short-circuits when telemetry is disabled — no disk I/O, no tracking ID generation, no PostHog client. Previously the config file (`mastra-cli.json`) was written even when telemetry was disabled. **Example:** ```bash # .env — any of these now reliably disable telemetry MASTRA_TELEMETRY_DISABLED=true MASTRA_TELEMETRY_DISABLED=1 MASTRA_TELEMETRY_DISABLED=yes ``` ## 1.37.2-alpha.1 ### Patch Changes - Fixed sub-agent version resolution in supervisor mode. Sub-agents now inherit the parent's draft/published version semantics — when chatting in the editor (draft mode), sub-agents resolve to their latest draft version; in the main agent chat (published mode), sub-agents resolve to their published version. Previously, sub-agents without explicit per-agent version overrides always fell back to the code-defined default, ignoring the parent's version context. ([#17165](https://github.com/mastra-ai/mastra/pull/17165)) ## 1.37.2-alpha.0 ### Patch Changes - Moved shared voice primitives and route metadata into the new `@internal/voice` package so voice providers no longer depend on `@mastra/core` and server voice routes share the same route definitions. ([#16725](https://github.com/mastra-ai/mastra/pull/16725)) `@mastra/core/voice` continues to re-export the voice APIs for backwards compatibility. ## 1.37.1 ### Patch Changes - End the AGENT_RUN observability span when an agent stream suspends for human-in-the-loop tool approval (`tool-call-approval`) or tool `suspend()` (`tool-call-suspended`). Previously these terminations left the span open, so traces never reached observability backends like Langfuse, Braintrust, or Datadog. The span output now includes the suspend reason, tool name, and tool call ID. ([#17097](https://github.com/mastra-ai/mastra/pull/17097)) ## 1.37.0 ### Minor Changes - Improved agent channels UX: ([#16937](https://github.com/mastra-ai/mastra/pull/16937)) - **Streaming text** — opt-in per-adapter `streaming` flag (`boolean | { updateIntervalMs?: number }`) that pushes the agent's text deltas into the platform message progressively via the Chat SDK. - **Adaptive typing indicator** — the platform's typing status now reflects what the agent is doing (`is working…` at run start, `is thinking…` during reasoning, `is typing…` while generating text, `is calling {toolName}…` while a tool runs, `is saving to memory…`/`is recalling memory…` during memory work, `is requesting approval for {toolName}…` while a tool is suspended), coalesced so the platform API isn't called on every delta. Skipped while a streaming session is open since the widget itself conveys progress. - **`toolDisplay` modes** — new `ChannelAdapterConfig.toolDisplay` controls how tool calls render: - `'cards'` (default) — per-tool running/result cards in rich Block Kit form. - `'text'` — per-tool running/result messages as plain text (replaces the old `cards: false` flag). - `'timeline'` — every tool gets its own task row in a streaming widget with status icons and args. - `'grouped'` — all tools in the run collapse into a single streaming widget; args fold inline into the title and successful results are suppressed for an at-a-glance summary (errors keep their full text). - `'hidden'` — tools run silently; only the typing indicator shows work. - **Function form (`ToolDisplayFn`)** — pass a function `(event, ctx) => { kind: 'post', message } | { kind: 'stream', chunk } | undefined` to fully control how every tool event renders. `'post'` results post a discrete message (closing/reopening the streaming session when needed); `'stream'` results push a `task_update`/`plan_update` into the active streaming widget; `undefined` skips the event. Every `ToolDisplayEvent` variant (`running`/`result`/`error`/`approval`) carries a stable `toolCallId` so user code can correlate events for the same tool invocation (e.g. as the `id` on a streamed `task_update` so the SDK updates a row in place rather than appending a new one). `'timeline'` and `'grouped'` require `streaming: true` and fall back to `'cards'` with a one-time warn if not enabled. `'cards'`/`'text'` work under both streaming modes — with `streaming: true`, the driver closes the streaming session around each card, posts it, and reopens on the next chunk. Approve/deny prompts always render as a separate Block Kit card regardless of mode, since inline task entries and plain text can't carry interactive buttons. **Deprecation (no breaking changes)**: `ChannelAdapterConfig.cards: boolean` and `ChannelAdapterConfig.formatToolCall` are now `@deprecated` and surface in IDEs with a strikethrough. Both still work at runtime — they're mutually exclusive with `toolDisplay` at the type level. When `toolDisplay` is not set: `cards: true` resolves to `toolDisplay: 'cards'`, `cards: false` resolves to `toolDisplay: 'text'`, and `formatToolCall` is shimmed into an equivalent `ToolDisplayFn` that only fires on `result`/`error` events. Migrate at your leisure: `cards: false` → `toolDisplay: 'text'`, and `formatToolCall: ({ toolName, result }) => msg` → `toolDisplay: event => event.kind === 'result' ? { kind: 'post', message: msg } : undefined`. - **`typingStatus` customization** — new `ChannelAdapterConfig.typingStatus` (`boolean | (chunk, ctx) => string | false`, default `true`). Set to `false` to suppress all typing indicators (useful when a live streaming widget already conveys progress), or pass a function to set custom copy per chunk. Compose with the exported `defaultTypingStatus` helper to fall back to built-in defaults for chunks you don't handle. - **Signal-aware message boundaries** — when a `data-user-message` signal echoes into the stream mid-reply, any in-flight text is flushed first so the agent's response renders as a new message after the user's signal instead of streaming into the prior reply. - **Stronger stay-silent prompt** — the channel input processor's non-DM system message now explicitly calls out anti-patterns (bracketed status notes, "Got it"/"Noted" acknowledgments, apologizing for silence) and points the model at `add_reaction` for silent acknowledgments. Empty responses are framed as a first-class action rather than a fallback. - **Slack DM thread routing** — each Slack thread (including top-level DMs) now maps to its own Mastra thread. Previously, replies and tool-approval clicks in a top-level DM could be routed into a sub-thread keyed by the bot's last message, causing follow-ups to thread under that message and tool approvals to fail to find the pending approval. - **Parallel same-tool approval** — fixed a bug where two parallel calls to the same tool with `requireApproval: true` clobbered each other's pending entry, so only the most recent could be approved. - **Tool error rendering** — failing tools now emit a closing task update in `'timeline'`/`'grouped'` modes (previously the row stayed `in_progress` and rendered as ⚠ at session close) and edit their card in `'cards'`/`'hidden'` modes. The error text is inlined into the task `details` (with a ⚠ glyph) while the task itself stays `status: 'complete'` so a single tool failure doesn't flip the overall plan header to an error state. - **Observational-memory lifecycle in streaming widgets** — `data-om-buffering-*` and `data-om-activation` chunks are routed into the active streaming session in `'timeline'`/`'grouped'` modes as their own task rows (e.g. `Saved to memory (10x)` with `12.4k → 1.2k tokens`), so memory work is visible alongside tool calls. Consecutive observation activations within a session coalesce into a single `Recalled memory (Nx)` row with running totals instead of stacking — reflection runs often fire several activations back-to-back. The plan title is set to `Updating memory` on the first OM event so memory-only runs don't show the chat-SDK default of `Thinking completed`. OM buffering runs async in the background, so any still-`in_progress` OM task is optimistically marked `complete` when the streaming session closes — without this, the chat-SDK plan widget would flip the "Saving to memory…" row to an error icon when the stream ends before the buffer flush resolves. In non-plan modes (`'cards'`/`'text'`/`'hidden'`/`ToolDisplayFn`) OM events are skipped entirely — a phantom Plan widget showing only memory rows would be inconsistent with the mode contract. Approval `plan_update`/`task_update` rows are also gated by `'timeline'`/`'grouped'` for the same reason; non-plan modes now post the approval card directly without flashing a single-row Plan widget that closes immediately after. - **Logger propagation** — the Mastra logger is now propagated into `AgentChannels` on register so channel-level logs flow through the configured logger. - **Internal refactor (no public API change)** — `consumeAgentStream` now dispatches to one of two focused drivers (streaming vs static) instead of switching on `toolDisplay` inside a single 700-line loop. Tool-call correlation moved into a `ToolTracker` helper and observational-memory rendering into a dedicated `renderOmTaskUpdate` helper, both shared between drivers. Invalid combinations now warn and downgrade: `streaming: false` + `'timeline'`/`'grouped'` falls back to `'cards'`. `streaming: true` + `'cards'`/`'text'` is now valid and uses the streaming driver's close/post/reopen lifecycle. ```ts import { Agent } from '@mastra/core/agent'; import { defaultTypingStatus } from '@mastra/core/channels'; import { createDiscordAdapter } from '@mastra/discord'; const agent = new Agent({ name: 'support-bot', channels: { adapters: { discord: { adapter: createDiscordAdapter(), streaming: true, toolDisplay: 'grouped', // 'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn typingStatus: false, // suppress typing indicator when the widget already shows progress // Custom typing status per chunk; fall back to defaults for everything else. // typingStatus: (chunk, ctx) => { // if (chunk.type === 'tool-call' && chunk.payload.toolName === 'searchDocs') { // return 'is searching docs…'; // } // return defaultTypingStatus(chunk, ctx); // }, // Custom tool rendering via the function form: skip the running message, // post a single line on result. `undefined` skips rendering that event. // toolDisplay: event => { // if (event.kind !== 'result') return undefined; // return { kind: 'post', message: `🛠 ${event.toolName} → ${event.resultText}` }; // }, }, }, }, }); ``` - Client-side tools now appear in your traces when observability is configured. When an agent calls a tool that executes in the browser via `@mastra/client-js`, a `CLIENT_TOOL_CALL` span is recorded on the server trace so you can see which client tools were invoked, what arguments they received, and how they relate to the rest of the agent run. ([#16425](https://github.com/mastra-ai/mastra/pull/16425)) Tools also gain an `observe` helper on their execution context for recording child spans and logs from inside `execute`: ```ts execute: async ({ userId }, { observe }) => { observe.log('info', 'fetching user', { userId }); return observe.span('fetch user', () => fetch(`/api/users/${userId}`)); }; ``` - Added signal delivery option attributes API that conditionally merges branch `attributes` based on whether a signal is delivered to an active agent run (`ifActive.attributes`) or an idle run (`ifIdle.attributes`). This enables contextual signal delivery — for example, tagging user messages as `while-active` when the agent is actively working. ([#16923](https://github.com/mastra-ai/mastra/pull/16923)) - Add per-provider capability files and `auto` mode for `observeAttachments` ([#16922](https://github.com/mastra-ai/mastra/pull/16922)) - Generate per-provider capability files (e.g. `capabilities/openai.json`) alongside the model router registry, sourced from models.dev API - Export `modelSupportsAttachments(modelRouterId)` from `@mastra/core/llm` to check whether a model supports image/file attachments - Extend `observeAttachments` config to accept `'auto'` in addition to `boolean | string[]` - When set to `'auto'`, the observer resolves the model (including function-based models) and checks the capability registry before deciding to forward or drop attachment parts ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`cfa2e3a`](https://github.com/mastra-ai/mastra/commit/cfa2e3a5292322f48bb28b4d257d631da7f9d3cc)) - Added sub-agent token usage to `onDelegationComplete` results, so apps can track per-sub-agent token costs ([#15825](https://github.com/mastra-ai/mastra/pull/15825)) - Fixed thread subscription streams stalling or deadlocking when multiple consumers observe the same active run. Thread streams are now multicast to subscribers so each subscriber receives the run without competing for the underlying stream, and follow-up messages can continue while a subscription is active. ([#16946](https://github.com/mastra-ai/mastra/pull/16946)) - Connecting or disconnecting an agent through a channel (e.g. Slack) now requires the same write permission as editing the underlying stored agent. The check runs on `POST /channels/:platform/connect` and `POST /channels/:platform/:agentId/disconnect` whenever the target agent has a record in the stored-agents store. Callers without write access receive a `404 Not found`, matching the behavior of the stored-agent edit routes. Agents defined in code (no stored-agents record) are unaffected and continue to honor only the route's existing auth requirement. ([#16949](https://github.com/mastra-ai/mastra/pull/16949)) The caller must either own the stored agent, have admin bypass, or hold `agents:edit` (or a scoped `agents:edit:`). ```http POST /channels/slack/connect Authorization: Bearer Content-Type: application/json { "agentId": "support-bot" } ``` ```http POST /channels/slack/support-bot/disconnect Authorization: Bearer ``` `@mastra/core` is bumped as a patch to ship the regenerated permission definitions that back this check. - Made provider capabilities loading lazy instead of bulk-syncing on every registry access. Removed subscription setup from harness switchMode() — subscriptions are now lazily ensured at send time. Added 10s TTL cache and invalidation method for listAvailableModels(). ([#17008](https://github.com/mastra-ai/mastra/pull/17008)) - Preserve `this` when resolving permissions for the "View as role" picker in `buildCapabilities`. Class-based RBAC providers (e.g. `@mastra/auth-workos`) read state from `this` inside `getPermissionsForRole`, but the method was being detached to a bare variable before invocation. This caused a `TypeError: Cannot read properties of undefined (reading 'options')` to be logged on every authenticated request and silently emptied the available roles list. The error was swallowed by a surrounding try/catch so admins saw an empty picker instead of a crash. ([#17112](https://github.com/mastra-ai/mastra/pull/17112)) - Fixed `workflow.parallel()` type-checking for steps that declare `requestContextSchema`. ([#16989](https://github.com/mastra-ai/mastra/pull/16989)) Steps with matching request context now type-check correctly. Steps with mismatched request context still fail with a type error. Fixes #16975. - Fixed split-brain broker election race in UnixSocketPubSub. When a broker process dies and multiple clients recover concurrently, an exclusive lock file now serializes the election so exactly one process becomes the new broker. ([#16955](https://github.com/mastra-ai/mastra/pull/16955)) - Fixed a crash that could occur when background execution is enabled for tools with Zod v3 input schemas. ([#16915](https://github.com/mastra-ai/mastra/pull/16915)) Tools with Zod v3, Zod v4, and JSON Schema input definitions now work consistently with background execution. - Fixed processor workflow steps so `sendSignal` is available when processors inject Agent signals, and updated Observational Memory temporal gap markers to use Agent signals. ([#16927](https://github.com/mastra-ai/mastra/pull/16927)) - Fixed an issue where thread subscriptions could appear idle after a run finished. Subsequent runs now stream promptly, and post-finish signals correctly start from an idle state. ([#16928](https://github.com/mastra-ai/mastra/pull/16928)) - Fixed agent responses being ordered before the user message that triggered them in long conversations. This prevents duplicate tool calls in the next step. This regression started in 1.35.0. Fixes https://github.com/mastra-ai/mastra/issues/16893. ([#16913](https://github.com/mastra-ai/mastra/pull/16913)) - Fixed agent crash when `backgroundTasks.enabled: true` is combined with a tool whose `inputSchema` uses Zod's `.refine()` or `.superRefine()`. On Zod v4, such agents threw `Cannot overwrite keys on object schemas containing refinements` on repeated invocations and became unusable. Tools with refined input schemas now work with background tasks. ([#16966](https://github.com/mastra-ai/mastra/pull/16966)) - UnixSocketPubSub: skip serialization when broker has 0 remote clients, lazily build ServerFrame only when a subscribed client exists, and automatically elect a new broker with resubscription when the active broker disconnects. ([#16939](https://github.com/mastra-ai/mastra/pull/16939)) - Fixed v1 agent tools to preserve legacy tool result output while still allowing usage data in delegation hooks. ([#17070](https://github.com/mastra-ai/mastra/pull/17070)) - Fixed thread stream subscriptions so streamed agent responses are saved to memory while still supporting multiple subscribers. ([#16982](https://github.com/mastra-ai/mastra/pull/16982)) - Gate stored-workspace handlers by author. Previously any authenticated caller within a tenant could list, read, update, or delete another user's workspace. ([#16974](https://github.com/mastra-ai/mastra/pull/16974)) **Behavior changes** - `POST /stored/workspaces` — server stamps `authorId` from the authenticated caller; any body-provided `authorId` is ignored. - `GET /stored/workspaces/:id`, `PATCH /stored/workspaces/:id`, `DELETE /stored/workspaces/:id` — return `404 Not found` unless the caller is the owner, an admin (`*`), or holds `stored-workspaces:[:]`. - `GET /stored/workspaces` — filters to the caller's own rows plus legacy unowned records; admins still see every row. - Legacy workspaces created before this change (no `authorId`) remain accessible to any authenticated caller for backwards compatibility. **Example** ```ts // Client POST body — authorId is ignored if sent await fetch('/stored/workspaces', { method: 'POST', body: JSON.stringify({ name: 'My workspace', authorId: 'someone-else' }), }); // Stored row — authorId is stamped from the authenticated caller // { // id: 'my-workspace', // name: 'My workspace', // authorId: 'user_abc123', // from requestContext, NOT from body // ... // } ``` **Migration** - Existing rows with `authorId === null/undefined` remain readable/writable by any authenticated caller — no action required for backwards compatibility. - To lock down legacy rows, backfill `authorId` directly in the `workspaces` table with the original creator's id. - For service accounts or tooling that need cross-user access, grant `stored-workspaces:*` (or per-id `stored-workspaces::`) instead of relying on the legacy unowned bypass. - Admins (callers with `*`) continue to see and mutate every row regardless of `authorId`. The `@mastra/core` patch regenerates `permissions.generated.ts` to include the `auth` and `infrastructure` resources that already had routes on `main`. - Agent Builder action routes (`/agent-builder/*`) are now registered automatically through the standard server route pipeline. Any adapter built on `@mastra/server` (Hono, Express, Fastify, Koa, etc.) serves the 15 `/agent-builder/*` endpoints without consumers wiring them manually. ([#17085](https://github.com/mastra-ai/mastra/pull/17085)) **Example** ```ts import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); // `/agent-builder/*` routes are now reachable out-of-the-box const actions = await client.getAgentBuilderActions(); const action = client.getAgentBuilderAction('generate-agent'); const { runId } = await action.createRun(); const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId); ``` **Why** Previously, `AGENT_BUILDER_ROUTES` was a type-only entry in the route registry to keep `@mastra/agent-builder` out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of `@mastra/agent-builder` is preserved — handlers still resolve the workflow module on first request via dynamic `import()`, so Cloudflare bundles are unaffected. **New EE permissions** The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes: - `agent-builder:read` - `agent-builder:write` - `agent-builder:execute` Two legacy stream routes (`STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`, `OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`) are now registered through the standard pipeline as well. - Fixed a race that could cause an immediate auto-resume of a suspended tool call to fail on some storage backends. Resume now succeeds reliably whether the underlying storage is fast or slow. ([#17015](https://github.com/mastra-ai/mastra/pull/17015)) Fixes [#16158](https://github.com/mastra-ai/mastra/issues/16158). - Fixed a race in Harness `sendMessage` where a phantom `agent_end: 'complete'` event could fire before any chunks arrived. Subscribers, such as apps running on Cloudflare Workers or Durable Objects, will no longer miss text deltas, messages, or tool events when an agent run completes. ([#17024](https://github.com/mastra-ai/mastra/pull/17024)) The cause was `AgentThreadStreamRuntime.subscribeToThread`'s `activeRunId()` returning `null` during the gap between `sendSignal` reserving a runId and `registerRun` populating the stream record, which made `waitForCurrentThreadStreamIdle()` exit immediately and `sendMessage` emit a synthetic `agent_end`. The subscriber now treats both a reserved-but-not-yet-registered local run and an active remote run as live, matching `sendSignal`'s own behavior. - Suppressed noisy gateway fetch errors when models.dev is unreachable. The registry no longer retries or logs errors on network failure since all model data is already bundled at publish time. ([#16984](https://github.com/mastra-ai/mastra/pull/16984)) ## 1.37.0-alpha.9 ### Patch Changes - Preserve `this` when resolving permissions for the "View as role" picker in `buildCapabilities`. Class-based RBAC providers (e.g. `@mastra/auth-workos`) read state from `this` inside `getPermissionsForRole`, but the method was being detached to a bare variable before invocation. This caused a `TypeError: Cannot read properties of undefined (reading 'options')` to be logged on every authenticated request and silently emptied the available roles list. The error was swallowed by a surrounding try/catch so admins saw an empty picker instead of a crash. ([#17112](https://github.com/mastra-ai/mastra/pull/17112)) ## 1.37.0-alpha.8 ### Patch Changes - Fixed v1 agent tools to preserve legacy tool result output while still allowing usage data in delegation hooks. ([#17070](https://github.com/mastra-ai/mastra/pull/17070)) - Agent Builder action routes (`/agent-builder/*`) are now registered automatically through the standard server route pipeline. Any adapter built on `@mastra/server` (Hono, Express, Fastify, Koa, etc.) serves the 15 `/agent-builder/*` endpoints without consumers wiring them manually. ([#17085](https://github.com/mastra-ai/mastra/pull/17085)) **Example** ```ts import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); // `/agent-builder/*` routes are now reachable out-of-the-box const actions = await client.getAgentBuilderActions(); const action = client.getAgentBuilderAction('generate-agent'); const { runId } = await action.createRun(); const result = await action.startAsync({ inputData: { prompt: 'Build me an agent' } }, runId); ``` **Why** Previously, `AGENT_BUILDER_ROUTES` was a type-only entry in the route registry to keep `@mastra/agent-builder` out of Cloudflare worker bundles. Consumers had to register the routes themselves to expose Agent Builder functionality. Lazy-loading of `@mastra/agent-builder` is preserved — handlers still resolve the workflow module on first request via dynamic `import()`, so Cloudflare bundles are unaffected. **New EE permissions** The following permissions are added to the EE registry. RBAC consumers with strict allowlists must grant these to retain access to builder action routes: - `agent-builder:read` - `agent-builder:write` - `agent-builder:execute` Two legacy stream routes (`STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`, `OBSERVE_STREAM_LEGACY_AGENT_BUILDER_ACTION_ROUTE`) are now registered through the standard pipeline as well. ## 1.37.0-alpha.7 ### Patch Changes - Fixed a crash that could occur when background execution is enabled for tools with Zod v3 input schemas. ([#16915](https://github.com/mastra-ai/mastra/pull/16915)) Tools with Zod v3, Zod v4, and JSON Schema input definitions now work consistently with background execution. ## 1.37.0-alpha.6 ### Minor Changes - Improved agent channels UX: ([#16937](https://github.com/mastra-ai/mastra/pull/16937)) - **Streaming text** — opt-in per-adapter `streaming` flag (`boolean | { updateIntervalMs?: number }`) that pushes the agent's text deltas into the platform message progressively via the Chat SDK. - **Adaptive typing indicator** — the platform's typing status now reflects what the agent is doing (`is working…` at run start, `is thinking…` during reasoning, `is typing…` while generating text, `is calling {toolName}…` while a tool runs, `is saving to memory…`/`is recalling memory…` during memory work, `is requesting approval for {toolName}…` while a tool is suspended), coalesced so the platform API isn't called on every delta. Skipped while a streaming session is open since the widget itself conveys progress. - **`toolDisplay` modes** — new `ChannelAdapterConfig.toolDisplay` controls how tool calls render: - `'cards'` (default) — per-tool running/result cards in rich Block Kit form. - `'text'` — per-tool running/result messages as plain text (replaces the old `cards: false` flag). - `'timeline'` — every tool gets its own task row in a streaming widget with status icons and args. - `'grouped'` — all tools in the run collapse into a single streaming widget; args fold inline into the title and successful results are suppressed for an at-a-glance summary (errors keep their full text). - `'hidden'` — tools run silently; only the typing indicator shows work. - **Function form (`ToolDisplayFn`)** — pass a function `(event, ctx) => { kind: 'post', message } | { kind: 'stream', chunk } | undefined` to fully control how every tool event renders. `'post'` results post a discrete message (closing/reopening the streaming session when needed); `'stream'` results push a `task_update`/`plan_update` into the active streaming widget; `undefined` skips the event. Every `ToolDisplayEvent` variant (`running`/`result`/`error`/`approval`) carries a stable `toolCallId` so user code can correlate events for the same tool invocation (e.g. as the `id` on a streamed `task_update` so the SDK updates a row in place rather than appending a new one). `'timeline'` and `'grouped'` require `streaming: true` and fall back to `'cards'` with a one-time warn if not enabled. `'cards'`/`'text'` work under both streaming modes — with `streaming: true`, the driver closes the streaming session around each card, posts it, and reopens on the next chunk. Approve/deny prompts always render as a separate Block Kit card regardless of mode, since inline task entries and plain text can't carry interactive buttons. **Deprecation (no breaking changes)**: `ChannelAdapterConfig.cards: boolean` and `ChannelAdapterConfig.formatToolCall` are now `@deprecated` and surface in IDEs with a strikethrough. Both still work at runtime — they're mutually exclusive with `toolDisplay` at the type level. When `toolDisplay` is not set: `cards: true` resolves to `toolDisplay: 'cards'`, `cards: false` resolves to `toolDisplay: 'text'`, and `formatToolCall` is shimmed into an equivalent `ToolDisplayFn` that only fires on `result`/`error` events. Migrate at your leisure: `cards: false` → `toolDisplay: 'text'`, and `formatToolCall: ({ toolName, result }) => msg` → `toolDisplay: event => event.kind === 'result' ? { kind: 'post', message: msg } : undefined`. - **`typingStatus` customization** — new `ChannelAdapterConfig.typingStatus` (`boolean | (chunk, ctx) => string | false`, default `true`). Set to `false` to suppress all typing indicators (useful when a live streaming widget already conveys progress), or pass a function to set custom copy per chunk. Compose with the exported `defaultTypingStatus` helper to fall back to built-in defaults for chunks you don't handle. - **Signal-aware message boundaries** — when a `data-user-message` signal echoes into the stream mid-reply, any in-flight text is flushed first so the agent's response renders as a new message after the user's signal instead of streaming into the prior reply. - **Stronger stay-silent prompt** — the channel input processor's non-DM system message now explicitly calls out anti-patterns (bracketed status notes, "Got it"/"Noted" acknowledgments, apologizing for silence) and points the model at `add_reaction` for silent acknowledgments. Empty responses are framed as a first-class action rather than a fallback. - **Slack DM thread routing** — each Slack thread (including top-level DMs) now maps to its own Mastra thread. Previously, replies and tool-approval clicks in a top-level DM could be routed into a sub-thread keyed by the bot's last message, causing follow-ups to thread under that message and tool approvals to fail to find the pending approval. - **Parallel same-tool approval** — fixed a bug where two parallel calls to the same tool with `requireApproval: true` clobbered each other's pending entry, so only the most recent could be approved. - **Tool error rendering** — failing tools now emit a closing task update in `'timeline'`/`'grouped'` modes (previously the row stayed `in_progress` and rendered as ⚠ at session close) and edit their card in `'cards'`/`'hidden'` modes. The error text is inlined into the task `details` (with a ⚠ glyph) while the task itself stays `status: 'complete'` so a single tool failure doesn't flip the overall plan header to an error state. - **Observational-memory lifecycle in streaming widgets** — `data-om-buffering-*` and `data-om-activation` chunks are routed into the active streaming session in `'timeline'`/`'grouped'` modes as their own task rows (e.g. `Saved to memory (10x)` with `12.4k → 1.2k tokens`), so memory work is visible alongside tool calls. Consecutive observation activations within a session coalesce into a single `Recalled memory (Nx)` row with running totals instead of stacking — reflection runs often fire several activations back-to-back. The plan title is set to `Updating memory` on the first OM event so memory-only runs don't show the chat-SDK default of `Thinking completed`. OM buffering runs async in the background, so any still-`in_progress` OM task is optimistically marked `complete` when the streaming session closes — without this, the chat-SDK plan widget would flip the "Saving to memory…" row to an error icon when the stream ends before the buffer flush resolves. In non-plan modes (`'cards'`/`'text'`/`'hidden'`/`ToolDisplayFn`) OM events are skipped entirely — a phantom Plan widget showing only memory rows would be inconsistent with the mode contract. Approval `plan_update`/`task_update` rows are also gated by `'timeline'`/`'grouped'` for the same reason; non-plan modes now post the approval card directly without flashing a single-row Plan widget that closes immediately after. - **Logger propagation** — the Mastra logger is now propagated into `AgentChannels` on register so channel-level logs flow through the configured logger. - **Internal refactor (no public API change)** — `consumeAgentStream` now dispatches to one of two focused drivers (streaming vs static) instead of switching on `toolDisplay` inside a single 700-line loop. Tool-call correlation moved into a `ToolTracker` helper and observational-memory rendering into a dedicated `renderOmTaskUpdate` helper, both shared between drivers. Invalid combinations now warn and downgrade: `streaming: false` + `'timeline'`/`'grouped'` falls back to `'cards'`. `streaming: true` + `'cards'`/`'text'` is now valid and uses the streaming driver's close/post/reopen lifecycle. ```ts import { Agent } from '@mastra/core/agent'; import { defaultTypingStatus } from '@mastra/core/channels'; import { createDiscordAdapter } from '@mastra/discord'; const agent = new Agent({ name: 'support-bot', channels: { adapters: { discord: { adapter: createDiscordAdapter(), streaming: true, toolDisplay: 'grouped', // 'cards' | 'text' | 'timeline' | 'grouped' | 'hidden' | ToolDisplayFn typingStatus: false, // suppress typing indicator when the widget already shows progress // Custom typing status per chunk; fall back to defaults for everything else. // typingStatus: (chunk, ctx) => { // if (chunk.type === 'tool-call' && chunk.payload.toolName === 'searchDocs') { // return 'is searching docs…'; // } // return defaultTypingStatus(chunk, ctx); // }, // Custom tool rendering via the function form: skip the running message, // post a single line on result. `undefined` skips rendering that event. // toolDisplay: event => { // if (event.kind !== 'result') return undefined; // return { kind: 'post', message: `🛠 ${event.toolName} → ${event.resultText}` }; // }, }, }, }, }); ``` ### Patch Changes - Added sub-agent token usage to `onDelegationComplete` results, so apps can track per-sub-agent token costs ([#15825](https://github.com/mastra-ai/mastra/pull/15825)) - Fixed a race that could cause an immediate auto-resume of a suspended tool call to fail on some storage backends. Resume now succeeds reliably whether the underlying storage is fast or slow. ([#17015](https://github.com/mastra-ai/mastra/pull/17015)) Fixes [#16158](https://github.com/mastra-ai/mastra/issues/16158). - Suppressed noisy gateway fetch errors when models.dev is unreachable. The registry no longer retries or logs errors on network failure since all model data is already bundled at publish time. ([#16984](https://github.com/mastra-ai/mastra/pull/16984)) ## 1.37.0-alpha.5 ### Patch Changes - Made provider capabilities loading lazy instead of bulk-syncing on every registry access. Removed subscription setup from harness switchMode() — subscriptions are now lazily ensured at send time. Added 10s TTL cache and invalidation method for listAvailableModels(). ([#17008](https://github.com/mastra-ai/mastra/pull/17008)) - Fixed a race in Harness `sendMessage` where a phantom `agent_end: 'complete'` event could fire before any chunks arrived. Subscribers, such as apps running on Cloudflare Workers or Durable Objects, will no longer miss text deltas, messages, or tool events when an agent run completes. ([#17024](https://github.com/mastra-ai/mastra/pull/17024)) The cause was `AgentThreadStreamRuntime.subscribeToThread`'s `activeRunId()` returning `null` during the gap between `sendSignal` reserving a runId and `registerRun` populating the stream record, which made `waitForCurrentThreadStreamIdle()` exit immediately and `sendMessage` emit a synthetic `agent_end`. The subscriber now treats both a reserved-but-not-yet-registered local run and an active remote run as live, matching `sendSignal`'s own behavior. ## 1.37.0-alpha.4 ### Patch Changes - Connecting or disconnecting an agent through a channel (e.g. Slack) now requires the same write permission as editing the underlying stored agent. The check runs on `POST /channels/:platform/connect` and `POST /channels/:platform/:agentId/disconnect` whenever the target agent has a record in the stored-agents store. Callers without write access receive a `404 Not found`, matching the behavior of the stored-agent edit routes. Agents defined in code (no stored-agents record) are unaffected and continue to honor only the route's existing auth requirement. ([#16949](https://github.com/mastra-ai/mastra/pull/16949)) The caller must either own the stored agent, have admin bypass, or hold `agents:edit` (or a scoped `agents:edit:`). ```http POST /channels/slack/connect Authorization: Bearer Content-Type: application/json { "agentId": "support-bot" } ``` ```http POST /channels/slack/support-bot/disconnect Authorization: Bearer ``` `@mastra/core` is bumped as a patch to ship the regenerated permission definitions that back this check. - Fixed `workflow.parallel()` type-checking for steps that declare `requestContextSchema`. ([#16989](https://github.com/mastra-ai/mastra/pull/16989)) Steps with matching request context now type-check correctly. Steps with mismatched request context still fail with a type error. Fixes #16975. - Fixed agent crash when `backgroundTasks.enabled: true` is combined with a tool whose `inputSchema` uses Zod's `.refine()` or `.superRefine()`. On Zod v4, such agents threw `Cannot overwrite keys on object schemas containing refinements` on repeated invocations and became unusable. Tools with refined input schemas now work with background tasks. ([#16966](https://github.com/mastra-ai/mastra/pull/16966)) - Gate stored-workspace handlers by author. Previously any authenticated caller within a tenant could list, read, update, or delete another user's workspace. ([#16974](https://github.com/mastra-ai/mastra/pull/16974)) **Behavior changes** - `POST /stored/workspaces` — server stamps `authorId` from the authenticated caller; any body-provided `authorId` is ignored. - `GET /stored/workspaces/:id`, `PATCH /stored/workspaces/:id`, `DELETE /stored/workspaces/:id` — return `404 Not found` unless the caller is the owner, an admin (`*`), or holds `stored-workspaces:[:]`. - `GET /stored/workspaces` — filters to the caller's own rows plus legacy unowned records; admins still see every row. - Legacy workspaces created before this change (no `authorId`) remain accessible to any authenticated caller for backwards compatibility. **Example** ```ts // Client POST body — authorId is ignored if sent await fetch('/stored/workspaces', { method: 'POST', body: JSON.stringify({ name: 'My workspace', authorId: 'someone-else' }), }); // Stored row — authorId is stamped from the authenticated caller // { // id: 'my-workspace', // name: 'My workspace', // authorId: 'user_abc123', // from requestContext, NOT from body // ... // } ``` **Migration** - Existing rows with `authorId === null/undefined` remain readable/writable by any authenticated caller — no action required for backwards compatibility. - To lock down legacy rows, backfill `authorId` directly in the `workspaces` table with the original creator's id. - For service accounts or tooling that need cross-user access, grant `stored-workspaces:*` (or per-id `stored-workspaces::`) instead of relying on the legacy unowned bypass. - Admins (callers with `*`) continue to see and mutate every row regardless of `authorId`. The `@mastra/core` patch regenerates `permissions.generated.ts` to include the `auth` and `infrastructure` resources that already had routes on `main`. ## 1.37.0-alpha.3 ### Minor Changes - Add per-provider capability files and `auto` mode for `observeAttachments` ([#16922](https://github.com/mastra-ai/mastra/pull/16922)) - Generate per-provider capability files (e.g. `capabilities/openai.json`) alongside the model router registry, sourced from models.dev API - Export `modelSupportsAttachments(modelRouterId)` from `@mastra/core/llm` to check whether a model supports image/file attachments - Extend `observeAttachments` config to accept `'auto'` in addition to `boolean | string[]` - When set to `'auto'`, the observer resolves the model (including function-based models) and checks the capability registry before deciding to forward or drop attachment parts ### Patch Changes - Fixed thread subscription streams stalling or deadlocking when multiple consumers observe the same active run. Thread streams are now multicast to subscribers so each subscriber receives the run without competing for the underlying stream, and follow-up messages can continue while a subscription is active. ([#16946](https://github.com/mastra-ai/mastra/pull/16946)) - Fixed split-brain broker election race in UnixSocketPubSub. When a broker process dies and multiple clients recover concurrently, an exclusive lock file now serializes the election so exactly one process becomes the new broker. ([#16955](https://github.com/mastra-ai/mastra/pull/16955)) - Fixed agent responses being ordered before the user message that triggered them in long conversations. This prevents duplicate tool calls in the next step. This regression started in 1.35.0. Fixes https://github.com/mastra-ai/mastra/issues/16893. ([#16913](https://github.com/mastra-ai/mastra/pull/16913)) - UnixSocketPubSub: skip serialization when broker has 0 remote clients, lazily build ServerFrame only when a subscribed client exists, and automatically elect a new broker with resubscription when the active broker disconnects. ([#16939](https://github.com/mastra-ai/mastra/pull/16939)) - Fixed thread stream subscriptions so streamed agent responses are saved to memory while still supporting multiple subscribers. ([#16982](https://github.com/mastra-ai/mastra/pull/16982)) ## 1.37.0-alpha.2 ### Minor Changes - Added signal delivery option attributes API that conditionally merges branch `attributes` based on whether a signal is delivered to an active agent run (`ifActive.attributes`) or an idle run (`ifIdle.attributes`). This enables contextual signal delivery — for example, tagging user messages as `while-active` when the agent is actively working. ([#16923](https://github.com/mastra-ai/mastra/pull/16923)) ### Patch Changes - Fixed processor workflow steps so `sendSignal` is available when processors inject Agent signals, and updated Observational Memory temporal gap markers to use Agent signals. ([#16927](https://github.com/mastra-ai/mastra/pull/16927)) - Fixed an issue where thread subscriptions could appear idle after a run finished. Subsequent runs now stream promptly, and post-finish signals correctly start from an idle state. ([#16928](https://github.com/mastra-ai/mastra/pull/16928)) ## 1.37.0-alpha.1 ### Minor Changes - Client-side tools now appear in your traces when observability is configured. When an agent calls a tool that executes in the browser via `@mastra/client-js`, a `CLIENT_TOOL_CALL` span is recorded on the server trace so you can see which client tools were invoked, what arguments they received, and how they relate to the rest of the agent run. ([#16425](https://github.com/mastra-ai/mastra/pull/16425)) Tools also gain an `observe` helper on their execution context for recording child spans and logs from inside `execute`: ```ts execute: async ({ userId }, { observe }) => { observe.log('info', 'fetching user', { userId }); return observe.span('fetch user', () => fetch(`/api/users/${userId}`)); }; ``` ## 1.36.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`cfa2e3a`](https://github.com/mastra-ai/mastra/commit/cfa2e3a5292322f48bb28b4d257d631da7f9d3cc)) ## 1.36.0 ### Minor Changes - Added `activateAfterIdle: "auto"` for Observational Memory early activation. ([#16663](https://github.com/mastra-ai/mastra/pull/16663)) Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects `providerOptions.openai.promptCacheRetention` when available. ```ts const memory = new Memory({ options: { observationalMemory: { model: 'google/gemini-2.5-flash', activateAfterIdle: 'auto', activateOnProviderChange: true, }, }, }); ``` - Added support for permission arrays in FGA checks and route configuration. When an array is provided, the user needs **any one** of the listed permissions (logical OR). ([#16605](https://github.com/mastra-ai/mastra/pull/16605)) **Affected types** - `FGACheckParams.permission` - `FGARouteConfig.permission` - `FGARouteInfo.requiresPermission` - `FGADeniedError.permission` - `CheckFGAOptions.permission` Single-permission usage continues to work unchanged. ```ts // Before — single permission only await fga.check({ resource: { type: 'agent', id: 'abc' }, permission: 'agents:read', }); // After — single permission or array (ANY-of) await fga.check({ resource: { type: 'agent', id: 'abc' }, permission: ['agents:read', 'agents:execute'], }); ``` - Added consistent FGA execution checks across agents, tools, memory, and workflows to prevent unauthenticated executions when FGA is configured. Pass an authenticated user through `requestContext` when invoking protected APIs directly: ([#16651](https://github.com/mastra-ai/mastra/pull/16651)) ```ts const requestContext = new RequestContext(); requestContext.set('user', user); await agent.generate('Summarize this thread', { requestContext, }); ``` - Added the `EditorFavorite*` types and an optional `favorites` namespace on `IMastraEditor` so editor implementations can expose favoriting of stored agents and skills. ([#16749](https://github.com/mastra-ai/mastra/pull/16749)) ```ts import type { IMastraEditor, IEditorFavoritesNamespace, EditorFavoriteTargetInput, EditorFavoriteToggleResult, } from '@mastra/core/editor'; interface IMastraEditor { // ...existing members... favorites?: IEditorFavoritesNamespace; } ``` The `favorites` field is optional — existing implementations of `IMastraEditor` continue to work unchanged. `@mastra/editor` ships a default `EditorFavoritesNamespace` that wires this up against the storage `favorites` domain. Also renamed `AgentFeatures.stars` to `AgentFeatures.favorites` in `@mastra/core/agent-builder/ee` so the feature flag aligns with the storage column (`favoriteCount`), HTTP routes (`/favorite`), and the editor `favorites` namespace. The field had no functional consumers, so this is a name-only change. - Enterprise edition now automatically captures PostHog telemetry for EE license checks and feature usage, including license validation status, RBAC access resolution, FGA authorization calls, and EE feature invocation metadata. Telemetry is enabled by default for EE customers and can be disabled with `MASTRA_TELEMETRY_DISABLED=1`; community users are unaffected. ([#16660](https://github.com/mastra-ai/mastra/pull/16660)) - Added new editor configuration primitives for browser providers, agent builder integration, and stored-agent visibility. ([#16778](https://github.com/mastra-ai/mastra/pull/16778)) **New: `BrowserProvider` interface** Implement a browser provider to expose browser automation tools to agents via the editor. Each provider declares an id, name, and config schema, then returns a `MastraBrowser` instance from `createBrowser`. ```ts import type { BrowserProvider } from '@mastra/core/editor'; const myProvider: BrowserProvider = { id: 'my-browser', name: 'My Browser', description: 'Custom browser automation', configSchema: z.object({ apiKey: z.string() }), createBrowser: async config => { return createMyBrowser(config.apiKey); }, }; ``` **New: `MastraEditorConfig.browsers` and `.builder`** Wire browser providers and agent-builder options into the editor: ```ts new MastraEditor({ browsers: { 'my-browser': myProvider }, builder: { features: { agent: { favorites: true } } }, }); ``` **New: `visibility` on `updateAgentMeta`** Set an agent's visibility (private or public) through the editor namespace: ```ts await editor.agent.updateAgentMeta('agent-id', { visibility: 'public' }); ``` - `publishSkill` now returns the full skill file tree so consumers can persist the UI-facing tree alongside storage blobs without re-walking the source. ([#16666](https://github.com/mastra-ai/mastra/pull/16666)) ```ts import { publishSkill } from '@mastra/core/workspace'; const result = await publishSkill({ workspace, skillId, source }); // New: nested tree of folders + files; binary content base64-encoded. for (const node of result.files) { console.log(node.type, node.path); } ``` Also added two optional capability methods to `IMastraEditor` for server-side gating of builder-aware behavior: ```ts interface IMastraEditor { // ...existing members... hasEnabledBuilderConfig?(): boolean; resolveBuilder?(): Promise; } ``` Both methods are optional — existing implementations of `IMastraEditor` continue to work unchanged. Servers that consume them treat `undefined` / missing implementation as "no builder configured." - Added route-specific CORS configuration so credentialed cross-origin access can be limited to selected custom routes and channel webhooks. ([#16689](https://github.com/mastra-ai/mastra/pull/16689)) ```ts registerApiRoute('/customer-webhook', { method: 'POST', cors: { origin: ['https://customer-saas.example'], credentials: true, }, handler: async c => c.json({ ok: true }), }); ``` ```ts new Agent({ id: 'support-agent', name: 'Support Agent', instructions: '...', model, channels: { adapters: { web: { adapter: createWebAdapter(), cors: { origin: ['https://customer-saas.example'], credentials: true, }, }, }, }, }); ``` Use `server.cors` for one global CORS policy across the server: ```ts new Mastra({ server: { cors: { origin: '*', }, }, }); ``` - Narrowed `AgentSignalContents` from `BaseMessageListInput` to `string | (TextPart | FilePart)[]`. ([#16622](https://github.com/mastra-ai/mastra/pull/16622)) Fixed two signal-content bugs: - `user-message` signal attributes now reach the LLM - multimodal non-`user-message` signals no longer lose file parts Callers that previously passed wrapped message shapes to `agent.sendSignal` should now pass a bare string or a bare parts array. Before: `{ type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }` After: `{ type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }` Added an optional `providerOptions` field to `agent.sendSignal` that flows through to the resulting prompt turn (as `providerOptions` on the LLM message) and is persisted on the stored signal message (as `content.providerMetadata`). - `publishSkillFromSource()` (and `collectSkillForPublish()`) now return a `files` field containing the full skill source as a tree of `StorageSkillFileNode` entries with base64-encoded blob content — handy for storing a UI-facing copy of a skill alongside its content-addressable tree: ([#16673](https://github.com/mastra-ai/mastra/pull/16673)) ```ts const { snapshot, tree, files } = await publishSkillFromSource({ source }); // files: StorageSkillFileNode[] — name, mimeType, base64 content per node ``` Existing callers that only destructure `{ snapshot, tree }` are unaffected; the field is additive. Also adds `parseSkillSnapshotFromFiles()` for parsing skill snapshot frontmatter from a flat file list (used by the registry install flow): ```ts import { parseSkillSnapshotFromFiles, type SkillSnapshotFile } from '@mastra/core/workspace'; const files: SkillSnapshotFile[] = [{ path: 'SKILL.md', content: '...' }, ...]; const snapshot = parseSkillSnapshotFromFiles(files); ``` - Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. ([#16632](https://github.com/mastra-ai/mastra/pull/16632)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`452036a`](https://github.com/mastra-ai/mastra/commit/452036a0d965b4f4c1efd93606e4f03b50b807a5)) - Fixed task_update to auto-demote previously in_progress tasks instead of returning an error when moving another task to in_progress. ([#16843](https://github.com/mastra-ai/mastra/pull/16843)) - Fixed durable agents to honor activeTools when streaming. ([#16646](https://github.com/mastra-ai/mastra/pull/16646)) - Fixed type error when a tool calls `suspend(...)` inside `execute` while also declaring an `outputSchema`. The `execute` return type now allows `void` in addition to the declared output shape, so the idiomatic `return await suspend(...)` pattern type-checks correctly. ([#16799](https://github.com/mastra-ai/mastra/pull/16799)) - Fixed a startup bug in `MastraCompositeStore.init()` when using `default` or `editor`. ([#16786](https://github.com/mastra-ai/mastra/pull/16786)) Before this fix, the composite initialized inner domains directly and could skip parent store initialization. That could skip adapter setup steps and cause missing-table errors during startup (most visibly with `LibSQLStore` on a local file). Now, `MastraCompositeStore.init()` runs parent `default` and `editor` initialization first, then initializes only domains not already covered by those parents. This preserves adapter-specific initialization behavior and prevents startup races. Fixes #16782. - Fixed CompositeAuth incorrectly advertising SSO, session, and user provider capabilities when no inner provider supports them. Studio would show an SSO login button even when no provider had SSO configured, leading to 401 errors on login attempts. The duck-typing check now verifies that interface methods are actual functions rather than just present on the prototype chain. ([#16664](https://github.com/mastra-ai/mastra/pull/16664)) - Hide internal workflow spans from Mastra-owned plumbing in exported traces. ([#16631](https://github.com/mastra-ai/mastra/pull/16631)) - Channels now serialize messages per thread to keep conversations in order, and the tool approval flow is fixed end-to-end: ([#16517](https://github.com/mastra-ai/mastra/pull/16517)) - Messages arriving while the agent is busy are delivered into the running agent loop instead of starting a new, conflicting stream on the same thread. Each Mastra thread shares one subscription, and channel/author facts (platform, message id, author name) are surfaced on the stored message under `providerMetadata.mastra.channels.`. - Tool approval flow: approving now drains the resumed run so the card is updated with the tool result and any follow-up assistant text is posted. Denying now resumes the run via `declineToolCall` instead of leaving it suspended. `agent.subscribeToThread()` consumers also receive chunks from resumed runs (the subscription used to drop the second registration for a resumed run that kept its original `runId`). - The original `ChannelConfig` is now exposed via the new `AgentChannels.channelConfig` field so channel providers can merge with existing adapters instead of replacing them. - Bumped `chat` to `^4.29.0`. - Added the `internalUsage?: UsageStats` field to `AIBaseAttributes`, so any span type can carry token usage rolled up from internal descendant spans. Populated automatically by `@mastra/observability` when an internal `MODEL_GENERATION` ends inside a non-internal ancestor. ([#16434](https://github.com/mastra-ai/mastra/pull/16434)) - Fixed trajectory scorers so tool calls stored only in V2 content.parts are included in extracted eval steps. ([#15439](https://github.com/mastra-ai/mastra/pull/15439)) - Fixed thread metadata updates to merge with existing fields instead of replacing them. Previously, updating a thread's metadata would silently drop any fields not included in the update. Now existing metadata fields are preserved when updating. ([#16846](https://github.com/mastra-ai/mastra/pull/16846)) Fixed MockMemory working memory tool to support partial JSON updates when using schema-based working memory. Previously, sending a partial update would overwrite all existing data. Now for schema-based configs, unchanged fields are preserved automatically (matching @mastra/memory behavior). Fixed MockMemory constructor to preserve workingMemory config options (like schema) when enableWorkingMemory is true. - Improved MastraCode quiet mode so terminal sessions are easier to scan. ([#16771](https://github.com/mastra-ai/mastra/pull/16771)) - Quiet mode is now the default for new installs, and existing classic users get a one-time prompt to choose whether to enable it. - Added compact tool previews with a configurable preview-line limit, including an option to hide previews. - Improved repeated tool-call rendering, path continuation handling, task wrapping, shell/error previews, and spacing between tools, messages, plans, and completed subagents. - Added edited line ranges to workspace edit results so tool UIs can show where replacements happened. - Remove hardcoded `/api/` prefix check from `registerApiRoute()`. The check incorrectly rejected custom routes starting with `/api/` even when users configured a different `apiPrefix`. Reserved-path validation is already handled at the server adapter level using the actual configured prefix. ([#16859](https://github.com/mastra-ai/mastra/pull/16859)) - Fixed infinite recursion in `RequestContext.toJSON()` when multiple ([#16686](https://github.com/mastra-ai/mastra/pull/16686)) `RequestContext` instances reference each other through stored values. Previously, serializing such cross-context cycles would cause a CPU hang. Cyclic references are now detected and omitted from the serialized output, consistent with how circular references within a single context are handled. - Fixed crash in CacheKeyGenerator.fromAIV4Part when a tool-invocation part has undefined toolInvocation. This can happen when observational memory seals a partially-streamed assistant message. Also guarded MessageMerger against the same condition. ([#16773](https://github.com/mastra-ai/mastra/pull/16773)) - Agent signals can now coordinate active thread runs across agents that share a PubSub instance, so thread subscribers and signal senders can observe the same run instead of being limited to one runtime instance. ([#16665](https://github.com/mastra-ai/mastra/pull/16665)) ```ts import { Agent } from '@mastra/core/agent'; import { EventEmitterPubSub } from '@mastra/core/events'; const pubsub = new EventEmitterPubSub(); const agent = new Agent({ id: 'agent', name: 'Agent', instructions: 'Help the user', model, pubsub, }); ``` - Exposed `formatSkillActivation(skill)` from `@mastra/core/workspace`. It returns the activation payload — instructions plus references, scripts, and assets listings — that the built-in `skill` tool uses, so callers (e.g. an explicit `/skill/` slash command) can produce the same output without duplicating the formatting logic. ([#16618](https://github.com/mastra-ai/mastra/pull/16618)) Also preserves the `user-invocable` skill frontmatter field in workspace skill metadata. ```ts import { formatSkillActivation } from '@mastra/core/workspace'; const content = formatSkillActivation(skill); ``` - Fixed sub-agent streams so nested tool input progress is emitted while tool arguments are still being generated. This lets UIs show delegated agents preparing tool calls before the final tool input is available. Fixes #16422. ([#16553](https://github.com/mastra-ai/mastra/pull/16553)) - Fixed `backgroundTasks: { enabled: true }` silently dispatching foreground-only tools to the background. ([#16792](https://github.com/mastra-ai/mastra/pull/16792)) Previously, enabling `backgroundTasks` on the `Mastra` instance injected a system prompt into every agent that taught the LLM to flip any tool to background by passing `_background: { enabled: true }` in its arguments, and the resolver honored that override unconditionally. Models would readily do this for short, deterministic tools — a plain calculator could return `"Background task started…"` instead of `{ result: 42 }`, breaking `agent.generate()` / `agent.stream()` for tool-using flows. The LLM `_background` override is now treated as a _modifier_ on tools the developer has opted in at the tool or agent layer, not a standalone opt-in. If a tool hasn't been opted in via tool-level `background: { enabled: true }` or agent-level `backgroundTasks: { tools: { … } }` (or `tools: 'all'`), `_background.enabled: true` from the model is ignored and the tool runs in the foreground. Opted-in tools continue to honor LLM overrides for `enabled`, `timeoutMs`, and `maxRetries` as documented. Fixes https://github.com/mastra-ai/mastra/issues/16783 - Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. ([#16805](https://github.com/mastra-ai/mastra/pull/16805)) - Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: `(status, next_fire_at)` on `mastra_schedules` and `(schedule_id, actual_fire_at)` on `mastra_schedule_triggers`. Without these the scheduler performed a full sequential scan on every tick. - The scheduler tick loop is now only started when at least one workflow declares a schedule (or `scheduler.enabled` is set explicitly), so deployments without scheduled workflows no longer poll the database at all. - The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever. - The scheduler is now lazily initialized inside `startWorkers()`. Accessing `mastra.scheduler` before `startWorkers()` runs throws a descriptive error instead of returning a half-initialized instance. - Fixed Mastra getting stuck after a storage startup failure. Previously, if storage couldn't start up (for example, because the database was briefly unreachable), Mastra would keep returning the same error for every operation until the process was restarted. Now the next storage operation tries to start storage again, so brief outages recover on their own. Storage startup failures are also logged, so the problem is visible even when a later retry succeeds. ([#16427](https://github.com/mastra-ai/mastra/pull/16427)) - Restore MastraCode local command execution to inherit parent environment variables while redacting env-shaped and secret-looking workspace trace data. ([#16691](https://github.com/mastra-ai/mastra/pull/16691)) - Added a Unix socket PubSub transport and wired the Mastra Code TUI through a per-resource socket so local sessions can coordinate thread streams across processes. Programmatic `createMastraCode` usage remains opt-in: ([#16669](https://github.com/mastra-ai/mastra/pull/16669)) ```ts await createMastraCode({ unixSocketPubSub: true }); ``` - Fix in-memory observability storage to match the contract validated against DuckDB/ClickHouse vNext adapters. ([#16808](https://github.com/mastra-ai/mastra/pull/16808)) Previously, when running Mastra with the default in-memory storage, several observability operations behaved differently than they would against a production database: - **`getSpans`** threw 'This storage provider does not support batch-fetching spans'. It now batch-fetches spans by id within a trace, enabling the optimized `getBranch` path on in-memory storage. - **`batchCreateLogs`, `batchCreateMetrics`, `createScore`/`batchCreateScores`, `createFeedback`/`batchCreateFeedback`** appended duplicate records on retry. They now upsert by id, preserving the cursor id so delta polling does not re-emit the record. This makes client retries safe. - **Discovery operations** (`getEntityTypes`, `getEntityNames`, `getServiceNames`, `getEnvironments`, `getTags`) only inspected spans. They now also scan logs and metrics, so dimensions emitted on those surfaces are surfaced in discovery results. - **`getMetricTimeSeries`** merged grouped series whose label values contained the `|` character (e.g. `{segmentA: 'a', segmentB: 'b|c'}` collided with `{segmentA: 'a|b', segmentB: 'c'}`). Series are now keyed on the original label tuple, so colliding display names remain distinct series. ## 1.36.0-alpha.10 ### Minor Changes - Enterprise edition now automatically captures PostHog telemetry for EE license checks and feature usage, including license validation status, RBAC access resolution, FGA authorization calls, and EE feature invocation metadata. Telemetry is enabled by default for EE customers and can be disabled with `MASTRA_TELEMETRY_DISABLED=1`; community users are unaffected. ([#16660](https://github.com/mastra-ai/mastra/pull/16660)) ### Patch Changes - Fixed task_update to auto-demote previously in_progress tasks instead of returning an error when moving another task to in_progress. ([#16843](https://github.com/mastra-ai/mastra/pull/16843)) - Channels now serialize messages per thread to keep conversations in order, and the tool approval flow is fixed end-to-end: ([#16517](https://github.com/mastra-ai/mastra/pull/16517)) - Messages arriving while the agent is busy are delivered into the running agent loop instead of starting a new, conflicting stream on the same thread. Each Mastra thread shares one subscription, and channel/author facts (platform, message id, author name) are surfaced on the stored message under `providerMetadata.mastra.channels.`. - Tool approval flow: approving now drains the resumed run so the card is updated with the tool result and any follow-up assistant text is posted. Denying now resumes the run via `declineToolCall` instead of leaving it suspended. `agent.subscribeToThread()` consumers also receive chunks from resumed runs (the subscription used to drop the second registration for a resumed run that kept its original `runId`). - The original `ChannelConfig` is now exposed via the new `AgentChannels.channelConfig` field so channel providers can merge with existing adapters instead of replacing them. - Bumped `chat` to `^4.29.0`. - Fixed trajectory scorers so tool calls stored only in V2 content.parts are included in extracted eval steps. ([#15439](https://github.com/mastra-ai/mastra/pull/15439)) - Fixed thread metadata updates to merge with existing fields instead of replacing them. Previously, updating a thread's metadata would silently drop any fields not included in the update. Now existing metadata fields are preserved when updating. ([#16846](https://github.com/mastra-ai/mastra/pull/16846)) Fixed MockMemory working memory tool to support partial JSON updates when using schema-based working memory. Previously, sending a partial update would overwrite all existing data. Now for schema-based configs, unchanged fields are preserved automatically (matching @mastra/memory behavior). Fixed MockMemory constructor to preserve workingMemory config options (like schema) when enableWorkingMemory is true. - Remove hardcoded `/api/` prefix check from `registerApiRoute()`. The check incorrectly rejected custom routes starting with `/api/` even when users configured a different `apiPrefix`. Reserved-path validation is already handled at the server adapter level using the actual configured prefix. ([#16859](https://github.com/mastra-ai/mastra/pull/16859)) - Fixed scheduler performance and correctness issues that could cause excessive Postgres CPU and noisy failed runs. ([#16805](https://github.com/mastra-ai/mastra/pull/16805)) - Added missing indexes on the schedules tables that the tick loop polls every 10 seconds: `(status, next_fire_at)` on `mastra_schedules` and `(schedule_id, actual_fire_at)` on `mastra_schedule_triggers`. Without these the scheduler performed a full sequential scan on every tick. - The scheduler tick loop is now only started when at least one workflow declares a schedule (or `scheduler.enabled` is set explicitly), so deployments without scheduled workflows no longer poll the database at all. - The scheduler now validates that a schedule's target workflow is still registered before firing it. Schedules whose target workflow has been removed from the Mastra config are skipped for a short grace window and then deleted, so stale rows stop producing failed workflow runs forever. - The scheduler is now lazily initialized inside `startWorkers()`. Accessing `mastra.scheduler` before `startWorkers()` runs throws a descriptive error instead of returning a half-initialized instance. ## 1.36.0-alpha.9 ### Patch Changes - Fix in-memory observability storage to match the contract validated against DuckDB/ClickHouse vNext adapters. ([#16808](https://github.com/mastra-ai/mastra/pull/16808)) Previously, when running Mastra with the default in-memory storage, several observability operations behaved differently than they would against a production database: - **`getSpans`** threw 'This storage provider does not support batch-fetching spans'. It now batch-fetches spans by id within a trace, enabling the optimized `getBranch` path on in-memory storage. - **`batchCreateLogs`, `batchCreateMetrics`, `createScore`/`batchCreateScores`, `createFeedback`/`batchCreateFeedback`** appended duplicate records on retry. They now upsert by id, preserving the cursor id so delta polling does not re-emit the record. This makes client retries safe. - **Discovery operations** (`getEntityTypes`, `getEntityNames`, `getServiceNames`, `getEnvironments`, `getTags`) only inspected spans. They now also scan logs and metrics, so dimensions emitted on those surfaces are surfaced in discovery results. - **`getMetricTimeSeries`** merged grouped series whose label values contained the `|` character (e.g. `{segmentA: 'a', segmentB: 'b|c'}` collided with `{segmentA: 'a|b', segmentB: 'c'}`). Series are now keyed on the original label tuple, so colliding display names remain distinct series. ## 1.36.0-alpha.8 ### Patch Changes - Fixed type error when a tool calls `suspend(...)` inside `execute` while also declaring an `outputSchema`. The `execute` return type now allows `void` in addition to the declared output shape, so the idiomatic `return await suspend(...)` pattern type-checks correctly. ([#16799](https://github.com/mastra-ai/mastra/pull/16799)) ## 1.36.0-alpha.7 ### Minor Changes - Added new editor configuration primitives for browser providers, agent builder integration, and stored-agent visibility. ([#16778](https://github.com/mastra-ai/mastra/pull/16778)) **New: `BrowserProvider` interface** Implement a browser provider to expose browser automation tools to agents via the editor. Each provider declares an id, name, and config schema, then returns a `MastraBrowser` instance from `createBrowser`. ```ts import type { BrowserProvider } from '@mastra/core/editor'; const myProvider: BrowserProvider = { id: 'my-browser', name: 'My Browser', description: 'Custom browser automation', configSchema: z.object({ apiKey: z.string() }), createBrowser: async config => { return createMyBrowser(config.apiKey); }, }; ``` **New: `MastraEditorConfig.browsers` and `.builder`** Wire browser providers and agent-builder options into the editor: ```ts new MastraEditor({ browsers: { 'my-browser': myProvider }, builder: { features: { agent: { favorites: true } } }, }); ``` **New: `visibility` on `updateAgentMeta`** Set an agent's visibility (private or public) through the editor namespace: ```ts await editor.agent.updateAgentMeta('agent-id', { visibility: 'public' }); ``` ## 1.36.0-alpha.6 ### Patch Changes - Added a Unix socket PubSub transport and wired the Mastra Code TUI through a per-resource socket so local sessions can coordinate thread streams across processes. Programmatic `createMastraCode` usage remains opt-in: ([#16669](https://github.com/mastra-ai/mastra/pull/16669)) ```ts await createMastraCode({ unixSocketPubSub: true }); ``` ## 1.36.0-alpha.5 ### Patch Changes - Improved MastraCode quiet mode so terminal sessions are easier to scan. ([#16771](https://github.com/mastra-ai/mastra/pull/16771)) - Quiet mode is now the default for new installs, and existing classic users get a one-time prompt to choose whether to enable it. - Added compact tool previews with a configurable preview-line limit, including an option to hide previews. - Improved repeated tool-call rendering, path continuation handling, task wrapping, shell/error previews, and spacing between tools, messages, plans, and completed subagents. - Added edited line ranges to workspace edit results so tool UIs can show where replacements happened. - Fixed `backgroundTasks: { enabled: true }` silently dispatching foreground-only tools to the background. ([#16792](https://github.com/mastra-ai/mastra/pull/16792)) Previously, enabling `backgroundTasks` on the `Mastra` instance injected a system prompt into every agent that taught the LLM to flip any tool to background by passing `_background: { enabled: true }` in its arguments, and the resolver honored that override unconditionally. Models would readily do this for short, deterministic tools — a plain calculator could return `"Background task started…"` instead of `{ result: 42 }`, breaking `agent.generate()` / `agent.stream()` for tool-using flows. The LLM `_background` override is now treated as a _modifier_ on tools the developer has opted in at the tool or agent layer, not a standalone opt-in. If a tool hasn't been opted in via tool-level `background: { enabled: true }` or agent-level `backgroundTasks: { tools: { … } }` (or `tools: 'all'`), `_background.enabled: true` from the model is ignored and the tool runs in the foreground. Opted-in tools continue to honor LLM overrides for `enabled`, `timeoutMs`, and `maxRetries` as documented. Fixes https://github.com/mastra-ai/mastra/issues/16783 ## 1.36.0-alpha.4 ### Minor Changes - Added `activateAfterIdle: "auto"` for Observational Memory early activation. ([#16663](https://github.com/mastra-ai/mastra/pull/16663)) Mastra can now choose an idle activation timeout from the active model provider's prompt cache behavior. OpenAI also respects `providerOptions.openai.promptCacheRetention` when available. ```ts const memory = new Memory({ options: { observationalMemory: { model: 'google/gemini-2.5-flash', activateAfterIdle: 'auto', activateOnProviderChange: true, }, }, }); ``` ### Patch Changes - Fixed a startup bug in `MastraCompositeStore.init()` when using `default` or `editor`. ([#16786](https://github.com/mastra-ai/mastra/pull/16786)) Before this fix, the composite initialized inner domains directly and could skip parent store initialization. That could skip adapter setup steps and cause missing-table errors during startup (most visibly with `LibSQLStore` on a local file). Now, `MastraCompositeStore.init()` runs parent `default` and `editor` initialization first, then initializes only domains not already covered by those parents. This preserves adapter-specific initialization behavior and prevents startup races. Fixes #16782. - Agent signals can now coordinate active thread runs across agents that share a PubSub instance, so thread subscribers and signal senders can observe the same run instead of being limited to one runtime instance. ([#16665](https://github.com/mastra-ai/mastra/pull/16665)) ```ts import { Agent } from '@mastra/core/agent'; import { EventEmitterPubSub } from '@mastra/core/events'; const pubsub = new EventEmitterPubSub(); const agent = new Agent({ id: 'agent', name: 'Agent', instructions: 'Help the user', model, pubsub, }); ``` ## 1.36.0-alpha.3 ### Minor Changes - Added the `EditorFavorite*` types and an optional `favorites` namespace on `IMastraEditor` so editor implementations can expose favoriting of stored agents and skills. ([#16749](https://github.com/mastra-ai/mastra/pull/16749)) ```ts import type { IMastraEditor, IEditorFavoritesNamespace, EditorFavoriteTargetInput, EditorFavoriteToggleResult, } from '@mastra/core/editor'; interface IMastraEditor { // ...existing members... favorites?: IEditorFavoritesNamespace; } ``` The `favorites` field is optional — existing implementations of `IMastraEditor` continue to work unchanged. `@mastra/editor` ships a default `EditorFavoritesNamespace` that wires this up against the storage `favorites` domain. Also renamed `AgentFeatures.stars` to `AgentFeatures.favorites` in `@mastra/core/agent-builder/ee` so the feature flag aligns with the storage column (`favoriteCount`), HTTP routes (`/favorite`), and the editor `favorites` namespace. The field had no functional consumers, so this is a name-only change. - Added delta polling support for observability list APIs in core, DuckDB, and ClickHouse. ([#16632](https://github.com/mastra-ai/mastra/pull/16632)) ### Patch Changes - Fixed durable agents to honor activeTools when streaming. ([#16646](https://github.com/mastra-ai/mastra/pull/16646)) - Fixed infinite recursion in `RequestContext.toJSON()` when multiple ([#16686](https://github.com/mastra-ai/mastra/pull/16686)) `RequestContext` instances reference each other through stored values. Previously, serializing such cross-context cycles would cause a CPU hang. Cyclic references are now detected and omitted from the serialized output, consistent with how circular references within a single context are handled. - Fixed crash in CacheKeyGenerator.fromAIV4Part when a tool-invocation part has undefined toolInvocation. This can happen when observational memory seals a partially-streamed assistant message. Also guarded MessageMerger against the same condition. ([#16773](https://github.com/mastra-ai/mastra/pull/16773)) - Restore MastraCode local command execution to inherit parent environment variables while redacting env-shaped and secret-looking workspace trace data. ([#16691](https://github.com/mastra-ai/mastra/pull/16691)) ## 1.36.0-alpha.2 ### Minor Changes - Added support for permission arrays in FGA checks and route configuration. When an array is provided, the user needs **any one** of the listed permissions (logical OR). ([#16605](https://github.com/mastra-ai/mastra/pull/16605)) **Affected types** - `FGACheckParams.permission` - `FGARouteConfig.permission` - `FGARouteInfo.requiresPermission` - `FGADeniedError.permission` - `CheckFGAOptions.permission` Single-permission usage continues to work unchanged. ```ts // Before — single permission only await fga.check({ resource: { type: 'agent', id: 'abc' }, permission: 'agents:read', }); // After — single permission or array (ANY-of) await fga.check({ resource: { type: 'agent', id: 'abc' }, permission: ['agents:read', 'agents:execute'], }); ``` - Added consistent FGA execution checks across agents, tools, memory, and workflows to prevent unauthenticated executions when FGA is configured. Pass an authenticated user through `requestContext` when invoking protected APIs directly: ([#16651](https://github.com/mastra-ai/mastra/pull/16651)) ```ts const requestContext = new RequestContext(); requestContext.set('user', user); await agent.generate('Summarize this thread', { requestContext, }); ``` - `publishSkill` now returns the full skill file tree so consumers can persist the UI-facing tree alongside storage blobs without re-walking the source. ([#16666](https://github.com/mastra-ai/mastra/pull/16666)) ```ts import { publishSkill } from '@mastra/core/workspace'; const result = await publishSkill({ workspace, skillId, source }); // New: nested tree of folders + files; binary content base64-encoded. for (const node of result.files) { console.log(node.type, node.path); } ``` Also added two optional capability methods to `IMastraEditor` for server-side gating of builder-aware behavior: ```ts interface IMastraEditor { // ...existing members... hasEnabledBuilderConfig?(): boolean; resolveBuilder?(): Promise; } ``` Both methods are optional — existing implementations of `IMastraEditor` continue to work unchanged. Servers that consume them treat `undefined` / missing implementation as "no builder configured." - `publishSkillFromSource()` (and `collectSkillForPublish()`) now return a `files` field containing the full skill source as a tree of `StorageSkillFileNode` entries with base64-encoded blob content — handy for storing a UI-facing copy of a skill alongside its content-addressable tree: ([#16673](https://github.com/mastra-ai/mastra/pull/16673)) ```ts const { snapshot, tree, files } = await publishSkillFromSource({ source }); // files: StorageSkillFileNode[] — name, mimeType, base64 content per node ``` Existing callers that only destructure `{ snapshot, tree }` are unaffected; the field is additive. Also adds `parseSkillSnapshotFromFiles()` for parsing skill snapshot frontmatter from a flat file list (used by the registry install flow): ```ts import { parseSkillSnapshotFromFiles, type SkillSnapshotFile } from '@mastra/core/workspace'; const files: SkillSnapshotFile[] = [{ path: 'SKILL.md', content: '...' }, ...]; const snapshot = parseSkillSnapshotFromFiles(files); ``` ### Patch Changes - Fixed sub-agent streams so nested tool input progress is emitted while tool arguments are still being generated. This lets UIs show delegated agents preparing tool calls before the final tool input is available. Fixes #16422. ([#16553](https://github.com/mastra-ai/mastra/pull/16553)) ## 1.36.0-alpha.1 ### Minor Changes - Added route-specific CORS configuration so credentialed cross-origin access can be limited to selected custom routes and channel webhooks. ([#16689](https://github.com/mastra-ai/mastra/pull/16689)) ```ts registerApiRoute('/customer-webhook', { method: 'POST', cors: { origin: ['https://customer-saas.example'], credentials: true, }, handler: async c => c.json({ ok: true }), }); ``` ```ts new Agent({ id: 'support-agent', name: 'Support Agent', instructions: '...', model, channels: { adapters: { web: { adapter: createWebAdapter(), cors: { origin: ['https://customer-saas.example'], credentials: true, }, }, }, }, }); ``` Use `server.cors` for one global CORS policy across the server: ```ts new Mastra({ server: { cors: { origin: '*', }, }, }); ``` ### Patch Changes - Hide internal workflow spans from Mastra-owned plumbing in exported traces. ([#16631](https://github.com/mastra-ai/mastra/pull/16631)) - Added the `internalUsage?: UsageStats` field to `AIBaseAttributes`, so any span type can carry token usage rolled up from internal descendant spans. Populated automatically by `@mastra/observability` when an internal `MODEL_GENERATION` ends inside a non-internal ancestor. ([#16434](https://github.com/mastra-ai/mastra/pull/16434)) - Fixed Mastra getting stuck after a storage startup failure. Previously, if storage couldn't start up (for example, because the database was briefly unreachable), Mastra would keep returning the same error for every operation until the process was restarted. Now the next storage operation tries to start storage again, so brief outages recover on their own. Storage startup failures are also logged, so the problem is visible even when a later retry succeeds. ([#16427](https://github.com/mastra-ai/mastra/pull/16427)) ## 1.36.0-alpha.0 ### Minor Changes - Narrowed `AgentSignalContents` from `BaseMessageListInput` to `string | (TextPart | FilePart)[]`. ([#16622](https://github.com/mastra-ai/mastra/pull/16622)) Fixed two signal-content bugs: - `user-message` signal attributes now reach the LLM - multimodal non-`user-message` signals no longer lose file parts Callers that previously passed wrapped message shapes to `agent.sendSignal` should now pass a bare string or a bare parts array. Before: `{ type: 'user-message', contents: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }] }` After: `{ type: 'user-message', contents: [{ type: 'text', text: 'hi' }] }` Added an optional `providerOptions` field to `agent.sendSignal` that flows through to the resulting prompt turn (as `providerOptions` on the LLM message) and is persisted on the stored signal message (as `content.providerMetadata`). ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`452036a`](https://github.com/mastra-ai/mastra/commit/452036a0d965b4f4c1efd93606e4f03b50b807a5)) - Fixed CompositeAuth incorrectly advertising SSO, session, and user provider capabilities when no inner provider supports them. Studio would show an SSO login button even when no provider had SSO configured, leading to 401 errors on login attempts. The duck-typing check now verifies that interface methods are actual functions rather than just present on the prototype chain. ([#16664](https://github.com/mastra-ai/mastra/pull/16664)) - Exposed `formatSkillActivation(skill)` from `@mastra/core/workspace`. It returns the activation payload — instructions plus references, scripts, and assets listings — that the built-in `skill` tool uses, so callers (e.g. an explicit `/skill/` slash command) can produce the same output without duplicating the formatting logic. ([#16618](https://github.com/mastra-ai/mastra/pull/16618)) Also preserves the `user-invocable` skill frontmatter field in workspace skill metadata. ```ts import { formatSkillActivation } from '@mastra/core/workspace'; const content = formatSkillActivation(skill); ``` ## 1.35.0 ### Minor Changes - Added FGA route policy coverage controls, built-in resource route metadata resolution, and resolver hooks. ([#16485](https://github.com/mastra-ai/mastra/pull/16485)) For example: ```ts import { MastraFGAWorkos } from '@mastra/auth-workos'; import type { FGARouteConfig, FGARouteResolver, IFGAProvider } from '@mastra/core/auth/ee'; import { createRoute } from '@mastra/server/server-adapter'; const routeFGA = { 'GET /billing/:accountId': { resourceType: 'account', resourceIdParam: 'accountId', permission: 'billing:read', }, } satisfies Record; const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`]; const fga: IFGAProvider = new MastraFGAWorkos({ apiKey: process.env.WORKOS_API_KEY!, clientId: process.env.WORKOS_CLIENT_ID!, requireForProtectedRoutes: true, auditProtectedRoutes: 'warn', resolveRouteFGA, validatePermissions: async permissions => { /* validate mappings */ }, }); export const getProjectRoute = createRoute({ method: 'GET', path: '/projects/:projectId', responseType: 'json', requiresAuth: true, fga: { resourceType: 'project', resourceIdParam: 'projectId', permission: 'projects:read', }, handler: async () => { return { project: null }; }, }); ``` - Added a favorites storage domain that lets users mark stored agents and skills as favorites, plus `visibility` (`'private' | 'public'`) and `favoriteCount` fields on stored agents and skills so callers can list, filter, and order by favorite state. ([#16580](https://github.com/mastra-ai/mastra/pull/16580)) Existing rows without `visibility` or `favoriteCount` continue to work; the new fields and APIs are opt-in. **Example** ```ts const favorites = await storage.getStore('favorites'); await favorites?.favorite({ userId: 'u1', entityType: 'agent', entityId: 'agent-123' }); const favoritedIds = await favorites?.listFavoritedIds({ userId: 'u1', entityType: 'agent' }); // List agents the user has favorited, surfaced first const { agents } = await storage.getStore('agents').list({ pinFavoritedFor: 'u1', favoritedOnly: true, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b661349`](https://github.com/mastra-ai/mastra/commit/b661349281514691db78941a9044e6e4f1cde7a7)) - Fixed a workspace PATCH bug in the inmemory workspace adapter: omitted config fields in a PATCH no longer overwrite previously-persisted values with `undefined`. ([#16580](https://github.com/mastra-ai/mastra/pull/16580)) - Fixed active signals so they stay in the correct order when conversations are reloaded from memory. ([#16623](https://github.com/mastra-ai/mastra/pull/16623)) - Fixed scheduled workflows created from the public @mastra/core/workflows entry point so declared schedules are applied correctly. ([#16637](https://github.com/mastra-ai/mastra/pull/16637)) - Fixed a crash when importing `@mastra/core/workflows/workflow` from tests or apps, which previously failed with `TypeError: Class extends value undefined is not a constructor or null` (caused by a circular ESM import through the `workflows` barrel). ([#16661](https://github.com/mastra-ai/mastra/pull/16661)) ## 1.35.0-alpha.3 ### Patch Changes - Fixed active signals so they stay in the correct order when conversations are reloaded from memory. ([#16623](https://github.com/mastra-ai/mastra/pull/16623)) - Fixed a crash when importing `@mastra/core/workflows/workflow` from tests or apps, which previously failed with `TypeError: Class extends value undefined is not a constructor or null` (caused by a circular ESM import through the `workflows` barrel). ([#16661](https://github.com/mastra-ai/mastra/pull/16661)) ## 1.35.0-alpha.2 ### Minor Changes - Added a favorites storage domain that lets users mark stored agents and skills as favorites, plus `visibility` (`'private' | 'public'`) and `favoriteCount` fields on stored agents and skills so callers can list, filter, and order by favorite state. ([#16580](https://github.com/mastra-ai/mastra/pull/16580)) Existing rows without `visibility` or `favoriteCount` continue to work; the new fields and APIs are opt-in. **Example** ```ts const favorites = await storage.getStore('favorites'); await favorites?.favorite({ userId: 'u1', entityType: 'agent', entityId: 'agent-123' }); const favoritedIds = await favorites?.listFavoritedIds({ userId: 'u1', entityType: 'agent' }); // List agents the user has favorited, surfaced first const { agents } = await storage.getStore('agents').list({ pinFavoritedFor: 'u1', favoritedOnly: true, }); ``` ### Patch Changes - Fixed a workspace PATCH bug in the inmemory workspace adapter: omitted config fields in a PATCH no longer overwrite previously-persisted values with `undefined`. ([#16580](https://github.com/mastra-ai/mastra/pull/16580)) - Fixed scheduled workflows created from the public @mastra/core/workflows entry point so declared schedules are applied correctly. ([#16637](https://github.com/mastra-ai/mastra/pull/16637)) ## 1.35.0-alpha.1 ### Minor Changes - Added FGA route policy coverage controls, built-in resource route metadata resolution, and resolver hooks. ([#16485](https://github.com/mastra-ai/mastra/pull/16485)) For example: ```ts import { MastraFGAWorkos } from '@mastra/auth-workos'; import type { FGARouteConfig, FGARouteResolver, IFGAProvider } from '@mastra/core/auth/ee'; import { createRoute } from '@mastra/server/server-adapter'; const routeFGA = { 'GET /billing/:accountId': { resourceType: 'account', resourceIdParam: 'accountId', permission: 'billing:read', }, } satisfies Record; const resolveRouteFGA: FGARouteResolver = ({ route }) => routeFGA[`${route.method} ${route.path}`]; const fga: IFGAProvider = new MastraFGAWorkos({ apiKey: process.env.WORKOS_API_KEY!, clientId: process.env.WORKOS_CLIENT_ID!, requireForProtectedRoutes: true, auditProtectedRoutes: 'warn', resolveRouteFGA, validatePermissions: async permissions => { /* validate mappings */ }, }); export const getProjectRoute = createRoute({ method: 'GET', path: '/projects/:projectId', responseType: 'json', requiresAuth: true, fga: { resourceType: 'project', resourceIdParam: 'projectId', permission: 'projects:read', }, handler: async () => { return { project: null }; }, }); ``` ## 1.34.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b661349`](https://github.com/mastra-ai/mastra/commit/b661349281514691db78941a9044e6e4f1cde7a7)) ## 1.34.0 ### Minor Changes - You can now run ACP-compatible coding agents as Mastra tools or lightweight subagents. ACP agents support incremental response streaming and can be used anywhere Mastra accepts a `SubAgent`, including supervisor delegation and workflow steps. ([#16423](https://github.com/mastra-ai/mastra/pull/16423)) ```ts import { createACPTool, AcpAgent } from '@mastra/acp'; export const codingTool = createACPTool({ id: 'coding-agent', command: 'my-acp-agent', }); export const codingAgent = new AcpAgent({ id: 'coding-agent', command: 'my-acp-agent', }); ``` You can also wire an `AcpAgent` into a supervisor or workflow as a `SubAgent`-compatible implementation: ```ts import { Agent } from '@mastra/core/agent'; export const supervisor = new Agent({ name: 'supervisor', instructions: 'Delegate coding tasks to the ACP agent.', model, agents: { codingAgent, }, }); ``` Workflows and the Inngest workflow adapter now recognize `SubAgent`-compatible implementations when creating agent-backed workflow steps. - Added optional `metadata` to code-defined agents. Pass a `metadata` record to `new Agent({...})`, read it back with `agent.getMetadata()`, and clients can filter on it from the existing `/agents` and `/agents/:agentId` responses without encoding the data into IDs or names. ([#16603](https://github.com/mastra-ai/mastra/pull/16603)) Metadata supports the same `DynamicArgument` form as other agent config fields, so it can also be resolved per request from the request context. Stored agents loaded via the editor also expose their metadata through `agent.getMetadata()`, so clients can filter these agents as well. Cloning a runtime agent via `editor.agent.clone()` now carries the source agent's metadata over to the stored clone when the caller does not provide one explicitly. ```ts // Static const supportAgent = new Agent({ id: 'support-agent', name: 'Support Agent', instructions: 'You help customers with support requests.', model: 'openai/gpt-5', metadata: { type: 'support' }, }); supportAgent.getMetadata(); // { type: 'support' } // Dynamic const tenantAgent = new Agent({ id: 'tenant-agent', name: 'Tenant Agent', instructions: 'You help customers with tenant-specific tasks.', model: 'openai/gpt-5', metadata: ({ requestContext }) => ({ type: 'support', tenant: requestContext.get('tenant'), }), }); await tenantAgent.getMetadata({ requestContext }); // { type: 'support', tenant: 'acme' } ``` - Added an opt-in foundation for building agent-builder admin policies and role-aware capabilities, available under two new entry points. ([#16578](https://github.com/mastra-ai/mastra/pull/16578)) **`@mastra/core/agent-builder/ee`** Exposes types, validators, and picker utilities for working with model allowlists and admin model policies on stored agents — for example normalizing model candidates, choosing a default from a configured allowlist, and producing typed errors when a request violates policy. **`@mastra/core/auth/ee`** Adds optional methods on `IRBACProvider` for listing available roles and resolving the permissions for a given role: ```ts interface IRBACProvider { // existing methods... getAvailableRoles?(): Promise; getPermissionsForRole?(role: string): Promise; } ``` Static defaults, an expanded permissions catalog, and a capabilities helper that surfaces `availableRoles` to clients when the provider supports it are also included. Providers that do not implement the new methods continue to work unchanged. Also adds a `StorageBrowserRef` shape to `@mastra/core/storage` for referencing a configured headless browser on stored agents. - Improved how the workspace `read_file` tool returns files to the model. Reads now branch on file type: ([#16570](https://github.com/mastra-ai/mastra/pull/16570)) 1. **Media files** (default: `image/png`, `image/jpeg`, `image/webp`, `application/pdf`) are surfaced as native file/image parts the model can directly view, instead of being dumped as base64 text. Capped at 10 MiB by default so large media don't get base64-encoded into context and persisted in storage — configurable via `maxMediaBytes`. 2. **Text-readable files** (anything `text/*`, common code/config mime types, or unknown extensions) are returned as text content as before. 3. **Unsupported binaries** (e.g. `image/png` when `mediaTypes` is disabled, `application/zip`, oversized media, etc.) now return a short metadata description (`path`, size, mime type) instead of dumping useless base64 into the conversation. Pass an explicit `encoding` to opt back into the raw base64/hex dump. The set of mime types treated as native media parts and the inline size cap are configurable per workspace: ```ts import { Workspace, WORKSPACE_TOOLS } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem, tools: { [WORKSPACE_TOOLS.FILESYSTEM.READ_FILE]: { // Broaden to any image (e.g. SVG, BMP, HEIC) — may fail on some providers mediaTypes: ['image/*', 'application/pdf'], // Raise the inline-media cap to 25 MiB maxMediaBytes: 25 * 1024 * 1024, // Or a custom predicate // mediaTypes: (mime) => mime.startsWith('image/'), // Or disable media parts entirely // mediaTypes: false, }, }, }); ``` The default `mediaTypes` is intentionally the cross-provider-safe intersection — formats universally supported across Anthropic, OpenAI, and Gemini. - Changed background process output retention. ([#16574](https://github.com/mastra-ai/mastra/pull/16574)) **Before:** Spawned process handles retained all stdout and stderr, which could grow without bound for long-running background processes. **After:** Spawned process handles now retain the latest 1 MiB of stdout and stderr per stream by default. Pass `maxRetainedBytes` to `processes.spawn()` to customize the limit, use `0` to disable retained polling output, or use `Infinity` to keep the previous retain-all behavior. ```ts const handle = await sandbox.processes.spawn('npm run dev', { maxRetainedBytes: 512 * 1024, }); ``` Streaming callbacks and reader streams still receive every chunk in full. Handles also expose truncation and dropped-byte counters so callers can detect when `stdout`, `stderr`, or `wait()` results only include retained output. The built-in `executeCommand()` implementation still retains full output by default; pass `maxRetainedBytes` there only when you want bounded command results. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`784ad98`](https://github.com/mastra-ai/mastra/commit/784ad989549de91dc5d33ab8ef36caa6f7dcd34e)) - Improved background-task observability. ([#16590](https://github.com/mastra-ai/mastra/pull/16590)) Retry attempts now appear as separate workflow steps in run history and event traces, making retry progression easier to debug. No public API changes. - Fixed approval resume for tools loaded by processor workflows. ([#16365](https://github.com/mastra-ai/mastra/pull/16365)) - Fixed durable agents that could drop object-form system instructions when provider options like `cacheControl` were used. These instructions are now preserved so provider-specific options are respected. ([#16599](https://github.com/mastra-ai/mastra/pull/16599)) - Fixed approval resume for tools loaded with ToolSearchProcessor. ([#16365](https://github.com/mastra-ai/mastra/pull/16365)) - Fixed listMessages perPage=0 behavior in the in-memory store to match other adapters. ([#16602](https://github.com/mastra-ai/mastra/pull/16602)) - **Fixed** non-deterministic ordering of cross-thread semantic recall messages. ([#16600](https://github.com/mastra-ai/mastra/pull/16600)) When messages recalled from other threads shared the same timestamp, they were rendered into the system prompt in whatever order the vector query returned them — driven by similarity scores that can vary between equivalent runs. This made any test or evaluation that snapshots prompt output (or hashes the outbound LLM request) flaky. Recalled cross-thread messages are now sorted by createdAt, then threadId, then role (user → assistant → tool → system), then id before formatting, so the same set of recalled messages always produces the same prompt. - Expose `GET /observability/traces/light` and storage support for fetching paginated trace-list rows without span payload data. ([#16608](https://github.com/mastra-ai/mastra/pull/16608)) - `GET /api/observability/discovery/metric-names` and `GET /api/observability/discovery/metric-label-values` now accept `limit` as a URL query parameter without pre-parsing. Previously, passing `?limit=10` was rejected as a validation error; callers can now use these endpoints directly from HTTP clients, consistent with other query endpoints (e.g. pagination). ([#16489](https://github.com/mastra-ai/mastra/pull/16489)) - Fixed agent signals so standalone agents coordinate thread streams through a shared runtime. ([#16581](https://github.com/mastra-ai/mastra/pull/16581)) ## 1.34.0-alpha.3 ### Minor Changes - Added optional `metadata` to code-defined agents. Pass a `metadata` record to `new Agent({...})`, read it back with `agent.getMetadata()`, and clients can filter on it from the existing `/agents` and `/agents/:agentId` responses without encoding the data into IDs or names. ([#16603](https://github.com/mastra-ai/mastra/pull/16603)) Metadata supports the same `DynamicArgument` form as other agent config fields, so it can also be resolved per request from the request context. Stored agents loaded via the editor also expose their metadata through `agent.getMetadata()`, so clients can filter these agents as well. Cloning a runtime agent via `editor.agent.clone()` now carries the source agent's metadata over to the stored clone when the caller does not provide one explicitly. ```ts // Static const supportAgent = new Agent({ id: 'support-agent', name: 'Support Agent', instructions: 'You help customers with support requests.', model: 'openai/gpt-5', metadata: { type: 'support' }, }); supportAgent.getMetadata(); // { type: 'support' } // Dynamic const tenantAgent = new Agent({ id: 'tenant-agent', name: 'Tenant Agent', instructions: 'You help customers with tenant-specific tasks.', model: 'openai/gpt-5', metadata: ({ requestContext }) => ({ type: 'support', tenant: requestContext.get('tenant'), }), }); await tenantAgent.getMetadata({ requestContext }); // { type: 'support', tenant: 'acme' } ``` - Added an opt-in foundation for building agent-builder admin policies and role-aware capabilities, available under two new entry points. ([#16578](https://github.com/mastra-ai/mastra/pull/16578)) **`@mastra/core/agent-builder/ee`** Exposes types, validators, and picker utilities for working with model allowlists and admin model policies on stored agents — for example normalizing model candidates, choosing a default from a configured allowlist, and producing typed errors when a request violates policy. **`@mastra/core/auth/ee`** Adds optional methods on `IRBACProvider` for listing available roles and resolving the permissions for a given role: ```ts interface IRBACProvider { // existing methods... getAvailableRoles?(): Promise; getPermissionsForRole?(role: string): Promise; } ``` Static defaults, an expanded permissions catalog, and a capabilities helper that surfaces `availableRoles` to clients when the provider supports it are also included. Providers that do not implement the new methods continue to work unchanged. Also adds a `StorageBrowserRef` shape to `@mastra/core/storage` for referencing a configured headless browser on stored agents. ### Patch Changes - Fixed approval resume for tools loaded by processor workflows. ([#16365](https://github.com/mastra-ai/mastra/pull/16365)) - Fixed approval resume for tools loaded with ToolSearchProcessor. ([#16365](https://github.com/mastra-ai/mastra/pull/16365)) - Expose `GET /observability/traces/light` and storage support for fetching paginated trace-list rows without span payload data. ([#16608](https://github.com/mastra-ai/mastra/pull/16608)) - `GET /api/observability/discovery/metric-names` and `GET /api/observability/discovery/metric-label-values` now accept `limit` as a URL query parameter without pre-parsing. Previously, passing `?limit=10` was rejected as a validation error; callers can now use these endpoints directly from HTTP clients, consistent with other query endpoints (e.g. pagination). ([#16489](https://github.com/mastra-ai/mastra/pull/16489)) ## 1.34.0-alpha.2 ### Patch Changes - Fixed listMessages perPage=0 behavior in the in-memory store to match other adapters. ([#16602](https://github.com/mastra-ai/mastra/pull/16602)) ## 1.34.0-alpha.1 ### Minor Changes - Changed background process output retention. ([#16574](https://github.com/mastra-ai/mastra/pull/16574)) **Before:** Spawned process handles retained all stdout and stderr, which could grow without bound for long-running background processes. **After:** Spawned process handles now retain the latest 1 MiB of stdout and stderr per stream by default. Pass `maxRetainedBytes` to `processes.spawn()` to customize the limit, use `0` to disable retained polling output, or use `Infinity` to keep the previous retain-all behavior. ```ts const handle = await sandbox.processes.spawn('npm run dev', { maxRetainedBytes: 512 * 1024, }); ``` Streaming callbacks and reader streams still receive every chunk in full. Handles also expose truncation and dropped-byte counters so callers can detect when `stdout`, `stderr`, or `wait()` results only include retained output. The built-in `executeCommand()` implementation still retains full output by default; pass `maxRetainedBytes` there only when you want bounded command results. ### Patch Changes - Improved background-task observability. ([#16590](https://github.com/mastra-ai/mastra/pull/16590)) Retry attempts now appear as separate workflow steps in run history and event traces, making retry progression easier to debug. No public API changes. - Fixed durable agents that could drop object-form system instructions when provider options like `cacheControl` were used. These instructions are now preserved so provider-specific options are respected. ([#16599](https://github.com/mastra-ai/mastra/pull/16599)) - **Fixed** non-deterministic ordering of cross-thread semantic recall messages. ([#16600](https://github.com/mastra-ai/mastra/pull/16600)) When messages recalled from other threads shared the same timestamp, they were rendered into the system prompt in whatever order the vector query returned them — driven by similarity scores that can vary between equivalent runs. This made any test or evaluation that snapshots prompt output (or hashes the outbound LLM request) flaky. Recalled cross-thread messages are now sorted by createdAt, then threadId, then role (user → assistant → tool → system), then id before formatting, so the same set of recalled messages always produces the same prompt. - Fixed agent signals so standalone agents coordinate thread streams through a shared runtime. ([#16581](https://github.com/mastra-ai/mastra/pull/16581)) ## 1.34.0-alpha.0 ### Minor Changes - You can now run ACP-compatible coding agents as Mastra tools or lightweight subagents. ACP agents support incremental response streaming and can be used anywhere Mastra accepts a `SubAgent`, including supervisor delegation and workflow steps. ([#16423](https://github.com/mastra-ai/mastra/pull/16423)) ```ts import { createACPTool, AcpAgent } from '@mastra/acp'; export const codingTool = createACPTool({ id: 'coding-agent', command: 'my-acp-agent', }); export const codingAgent = new AcpAgent({ id: 'coding-agent', command: 'my-acp-agent', }); ``` You can also wire an `AcpAgent` into a supervisor or workflow as a `SubAgent`-compatible implementation: ```ts import { Agent } from '@mastra/core/agent'; export const supervisor = new Agent({ name: 'supervisor', instructions: 'Delegate coding tasks to the ACP agent.', model, agents: { codingAgent, }, }); ``` Workflows and the Inngest workflow adapter now recognize `SubAgent`-compatible implementations when creating agent-backed workflow steps. - Improved how the workspace `read_file` tool returns files to the model. Reads now branch on file type: ([#16570](https://github.com/mastra-ai/mastra/pull/16570)) 1. **Media files** (default: `image/png`, `image/jpeg`, `image/webp`, `application/pdf`) are surfaced as native file/image parts the model can directly view, instead of being dumped as base64 text. Capped at 10 MiB by default so large media don't get base64-encoded into context and persisted in storage — configurable via `maxMediaBytes`. 2. **Text-readable files** (anything `text/*`, common code/config mime types, or unknown extensions) are returned as text content as before. 3. **Unsupported binaries** (e.g. `image/png` when `mediaTypes` is disabled, `application/zip`, oversized media, etc.) now return a short metadata description (`path`, size, mime type) instead of dumping useless base64 into the conversation. Pass an explicit `encoding` to opt back into the raw base64/hex dump. The set of mime types treated as native media parts and the inline size cap are configurable per workspace: ```ts import { Workspace, WORKSPACE_TOOLS } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem, tools: { [WORKSPACE_TOOLS.FILESYSTEM.READ_FILE]: { // Broaden to any image (e.g. SVG, BMP, HEIC) — may fail on some providers mediaTypes: ['image/*', 'application/pdf'], // Raise the inline-media cap to 25 MiB maxMediaBytes: 25 * 1024 * 1024, // Or a custom predicate // mediaTypes: (mime) => mime.startsWith('image/'), // Or disable media parts entirely // mediaTypes: false, }, }, }); ``` The default `mediaTypes` is intentionally the cross-provider-safe intersection — formats universally supported across Anthropic, OpenAI, and Gemini. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`784ad98`](https://github.com/mastra-ai/mastra/commit/784ad989549de91dc5d33ab8ef36caa6f7dcd34e)) ## 1.33.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`6ba46dc`](https://github.com/mastra-ai/mastra/commit/6ba46dc1ac04af635d0f59377d7384ca6af44cd1)) - Fixed a bug where clicking Approve on a plan from `/plan` mode would show the system reminder twice and sometimes hang instead of starting build execution. Approving now reliably triggers the build agent with a single reminder. ([#16521](https://github.com/mastra-ai/mastra/pull/16521)) - **Added** ([#16520](https://github.com/mastra-ai/mastra/pull/16520)) You can now call `run.restart()` for evented workflows to continue execution from the latest persisted run state. ```ts const run = workflow.createRun(); await run.start({ inputData: { jobId: 'job-123' } }); // Later, restart the same run from the last active step await run.restart(); ``` ## 1.33.1-alpha.1 ### Patch Changes - Fixed a bug where clicking Approve on a plan from `/plan` mode would show the system reminder twice and sometimes hang instead of starting build execution. Approving now reliably triggers the build agent with a single reminder. ([#16521](https://github.com/mastra-ai/mastra/pull/16521)) - **Added** ([#16520](https://github.com/mastra-ai/mastra/pull/16520)) You can now call `run.restart()` for evented workflows to continue execution from the latest persisted run state. ```ts const run = workflow.createRun(); await run.start({ inputData: { jobId: 'job-123' } }); // Later, restart the same run from the last active step await run.restart(); ``` ## 1.33.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`6ba46dc`](https://github.com/mastra-ai/mastra/commit/6ba46dc1ac04af635d0f59377d7384ca6af44cd1)) ## 1.33.0 ### Minor Changes - Added `processLLMRequest`, a processor hook that runs after messages are converted to the provider-facing prompt and before the model request is sent. The hook lets processors make temporary, model-aware prompt changes without mutating stored message history, memory, UI history, or later provider calls. ([#16176](https://github.com/mastra-ai/mastra/pull/16176)) `ProviderHistoryCompat` now uses this hook to prevent reasoning-history incompatibilities when switching providers. It strips reasoning parts from Cerebras-bound prompts that would otherwise be sent as rejected `reasoning_content`, and strips non-Anthropic reasoning from Anthropic-bound prompts while preserving Anthropic-native thinking blocks. - Improved Harness support for Agent thread signals. ([#16231](https://github.com/mastra-ai/mastra/pull/16231)) Harness thread subscriptions now own stream processing for followed runs, echo user-message signal data with stable IDs, and support idle signal starts without delaying optimistic rendering. ```ts const { id, accepted } = harness.sendSignal({ type: 'user-message', contents: 'Follow up while the agent is still streaming', }); await accepted; ``` - Added target-aware tool payload transforms for display streams and transcript messages. Tool authors can transform tool input, output, errors, approval payloads, and suspension payloads without changing raw runtime behavior or toModelOutput. See https://github.com/mastra-ai/mastra/issues/16054. ([#16103](https://github.com/mastra-ai/mastra/pull/16103)) Use `transform` on tools, agents, Mastra, or individual generation calls to configure these payload transforms. Runtime callers using the previous `toolPayloadProjection` shape continue to be normalized for compatibility. ```ts const lookupCustomer = createTool({ execute: async ({ customerId, internalPath }) => lookupCustomerRecord(customerId, internalPath), transform: { display: { input: ({ input }) => ({ customerId: input?.customerId }), output: ({ output }) => ({ displayName: output?.displayName }), }, transcript: { input: ({ input }) => ({ customerId: input?.customerId }), output: ({ output }) => ({ displayName: output?.displayName }), }, }, }); ``` - Added Azure OpenAI Responses API and v1 routing controls. ([#16246](https://github.com/mastra-ai/mastra/pull/16246)) Use `useResponsesAPI: true` to resolve Azure deployments through the Responses API with the Azure v1 route by default: ```ts new AzureOpenAIGateway({ resourceName: 'my-openai-resource', apiKey: process.env.AZURE_API_KEY!, useResponsesAPI: true, deployments: ['my-gpt-5-4-deployment'], }); ``` When `useDeploymentBasedUrls: false` is used directly, the gateway now defaults `apiVersion` to `"v1"` to match the AI SDK Azure provider's v1 URL route. Passing `apiVersion: "v1"` by itself keeps the existing deployment-based URL default for compatibility. - Mastra Eval results are now emitted once through the unified observability score pipeline. ([#16185](https://github.com/mastra-ai/mastra/pull/16185)) - Added Azure OpenAI Responses WebSocket transport support for streaming agent and tool loops. ([#16246](https://github.com/mastra-ai/mastra/pull/16246)) Configure the Azure gateway with `useResponsesAPI: true`, then opt into WebSocket streaming per request: ```ts const stream = await agent.stream('Review this task', { providerOptions: { azure: { transport: 'websocket', websocket: { closeOnFinish: false }, }, }, }); ``` Responses WebSocket streams now preserve transport handles through agent loops, reuse explicit API-key router connections safely, clean up cancelled streams, and reject overlapping `previous_response_id` continuations on the same connection. - Added `preserveModelOutput` to `ToolCallFilter` so filtered tool history can keep compact model-facing output without raw tool args or results. ([#16060](https://github.com/mastra-ai/mastra/pull/16060)) ```ts import { ToolCallFilter } from '@mastra/core/processors'; const filter = new ToolCallFilter({ preserveModelOutput: true, }); ``` - Added a SubAgent interface for custom supervisor subagents. ([#16359](https://github.com/mastra-ai/mastra/pull/16359)) - **Added `ResponseCache` input processor** ([#16283](https://github.com/mastra-ai/mastra/pull/16283)) Cache identical LLM steps to skip the model call and replay a previously cached response. Useful for prompt templates, suggested-prompt buttons, agentic search re-asks, or guardrail LLMs that classify the same input over and over. Caching is opt-in: register `ResponseCache` explicitly on `inputProcessors`. There is no agent-level option — this keeps the surface small while we collect feedback on the processor API. Per-call overrides flow through `RequestContext`. ```ts import { Agent } from '@mastra/core/agent'; import { InMemoryServerCache } from '@mastra/core/cache'; import { ResponseCache } from '@mastra/core/processors'; const cache = new InMemoryServerCache(); const agent = new Agent({ name: 'Search Agent', instructions: 'You answer questions concisely.', model: 'openai/gpt-5', inputProcessors: [new ResponseCache({ cache, ttl: 600 })], }); // First call: cache miss → LLM call await agent.generate('What is the capital of France?'); // Second identical call: cache hit → no LLM call await agent.generate('What is the capital of France?'); ``` Per-call overrides via `RequestContext`: ```ts import { ResponseCache } from '@mastra/core/processors'; import { RequestContext } from '@mastra/core/request-context'; // Force a fresh call but still update the cache. await agent.stream(prompt, { requestContext: ResponseCache.context({ bust: true }), }); // Or merge into an existing context. const ctx = new RequestContext(); ResponseCache.applyContext(ctx, { key: 'custom-key' }); await agent.stream(prompt, { requestContext: ctx }); ``` Three fields are overridable per call: `key`, `scope`, `bust`. `cache`, `ttl`, and `agentId` stay on the constructor. A `key` function receives `{ agentId, scope, model, prompt, stepNumber }` and returns a string (or `Promise`): ```ts await agent.stream(prompt, { requestContext: ResponseCache.context({ key: ({ model, prompt }) => `qa:${model.modelId}:${JSON.stringify(prompt).slice(-200)}`, }), }); ``` The cache key is derived from the resolved prompt Mastra is about to send to the model — i.e. _after_ memory loading and earlier input processors have run — so cached entries are tenant-isolated and don't leak context across users with shared prompts but different memory state. Each step in an agentic tool loop is independently cached. By default, the cache scope falls back to `MASTRA_RESOURCE_ID_KEY` from the request context for automatic per-user isolation. Failed runs (errors, tripwire activations) are not cached. See [Response caching](https://mastra.ai/en/docs/agents/response-caching) for details. Also adds: - `InMemoryServerCache` (in `@mastra/core/cache`) for local development. `ResponseCache` accepts any `MastraServerCache` directly — use `RedisCache` from `@mastra/redis` for production. - `MastraServerCache.set()` now accepts an optional `ttlMs` argument so implementations can override the configured default TTL on a per-entry basis. `InMemoryServerCache` and `RedisCache` (in `@mastra/redis`) both honor this. - New paired processor hooks `processLLMRequest` and `processLLMResponse`. `ProcessLLMRequestResult` may return `{ response }` to short-circuit the LLM call with a cached payload. - Added workflow state reader helpers to inspect persisted workflow runs and recover suspended or long-running workflows. ([#16091](https://github.com/mastra-ai/mastra/pull/16091)) The reader exposes suspended steps, resume labels, step payloads, and step outputs from the public WorkflowState returned by workflow.getWorkflowRunById(), and WorkflowState step results now reflect foreach array entries. - Added Agent signals for sending contextual messages into agent thread loops and subscribing to thread activity. ([#16229](https://github.com/mastra-ai/mastra/pull/16229)) Call `agent.sendSignal()` to inject context into a running agent loop. When the thread is idle, that same signal becomes the prompt that starts the next loop by default. Use `ifActive.behavior` and `ifIdle.behavior` to deliver, persist, discard, or wake from a signal. Use `agent.subscribeToThread()` to follow the raw stream chunks for a memory thread, observe signal echoes with stable IDs, and abort the active stream for that thread. ```ts const subscription = await agent.subscribeToThread({ resourceId, threadId }); void (async () => { for await (const part of subscription.stream) { if (part.type === 'finish') { subscription.unsubscribe(); } } })(); agent.sendSignal({ type: 'user-message', contents: 'Use the latest answer' }, { resourceId, threadId }); ``` - Add metadata filtering support to semantic recall. ([#9256](https://github.com/mastra-ai/mastra/pull/9256)) - Fixed Azure and OpenAI Responses item handling so multi-step reasoning and tool-call histories round-trip correctly without item ID collisions. ([#16246](https://github.com/mastra-ai/mastra/pull/16246)) Added provider-neutral response item helpers to `@mastra/core/agent/message-list`. Existing in-memory message cache entries are regenerated after upgrade. - Improved foreach workflow execution to keep concurrency slots filled as iterations finish. ([#12860](https://github.com/mastra-ai/mastra/pull/12860)) - Added processor `sendSignal` support and routed built-in system reminders through signal messages. ([#16438](https://github.com/mastra-ai/mastra/pull/16438)) - Added structured drop event types and an `onDroppedEvent` hook so exporters and bridge integrations can observe events dropped by the observability pipeline. ([#16111](https://github.com/mastra-ai/mastra/pull/16111)) - Added stable IDs to Harness task items plus `task_update` and `task_complete` for updating or completing one tracked task by ID. Task tools now return structured task snapshots, and `task_check` returns `summary` and `incompleteTasks` fields so agents and UIs can restore and verify task state without parsing text. ([#16254](https://github.com/mastra-ai/mastra/pull/16254)) Harness also exports `TaskItemSnapshot`, `assignTaskIds`, and `harness.restoreDisplayTasks()` for UI history replay, serializes task reads and mutations against the latest task state snapshot, and returns task-tool errors inside forked subagents so sidecar work cannot mutate parent task state. - Added new `MODEL_INFERENCE` span type under `MODEL_STEP`, covering only the model provider call. Use it to measure model latency separately from input/output processors and tool executions. ([#16267](https://github.com/mastra-ai/mastra/pull/16267)) - Added experimental support for using remote A2A agents as Mastra subagents. ([#16348](https://github.com/mastra-ai/mastra/pull/16348)) **What changed** - Mastra agents can register remote A2A endpoints through `A2AAgent` and delegate to them like other subagents. - Remote A2A subagents support `generate`, `resumeGenerate`, `stream`, and `resumeStream` so parent agents can use them in normal subagent flows. - Agent Cards can be cached and verified with pluggable verification hooks before remote execution begins. - Browser environments can import shared A2A types and errors from `@mastra/core/a2a/client`. **Example** ```ts import { Agent } from '@mastra/core/agent'; import { A2AAgent } from '@mastra/core/a2a'; const agent = new Agent({ name: 'Support Agent', instructions: 'Use the remote billing specialist for billing questions.', model: 'openai/gpt-4o-mini', agents: { billingSpecialist: new A2AAgent({ url: 'https://billing.example.com/.well-known/agent-card.json', }), }, }); const result = await agent.generate('Can you check the latest invoice status?'); ``` **Why** This lets Mastra agents compose with remote A2A agents without exposing those integrations as plain tools or depending directly on the client SDK. - Worker review fixes: ([#16309](https://github.com/mastra-ai/mastra/pull/16309)) - Step-execution endpoint (`POST /workflows/:id/runs/:runId/steps/execute`) is now gated by Mastra's standard `requiresAuth: true` + `authenticateToken` pipeline rather than a parallel "worker secret" body field. The previously introduced `workerSecret` config knob and `MASTRA_WORKER_SECRET` env var have been removed (they were never released). To gate the endpoint on a standalone-worker deployment, configure an auth provider on the server's `Mastra` instance — without one the framework currently treats `requiresAuth: true` as a no-op for this route. - `HttpRemoteStrategy` now sends credentials as a normal `Authorization: Bearer ` header. The token comes from the new `MASTRA_WORKER_AUTH_TOKEN` env var or an explicit `auth` constructor option. - Honor the caller's `abortSignal` in `HttpRemoteStrategy` by combining it with the per-request timeout via `AbortSignal.any` (with a manual fallback for runtimes that don't expose it). - Implement comma-separated name filtering for the `MASTRA_WORKERS` env var. `MASTRA_WORKERS=scheduler,backgroundTasks` now boots only those named workers; `MASTRA_WORKERS=false` still disables all workers. - Restore `Mastra.startEventEngine` / `stopEventEngine` as `@deprecated` aliases for the renamed `startWorkers` / `stopWorkers`. - `BackgroundTaskWorker` now subscribes to PubSub in `start()` instead of `init()`, matching the lifecycle of the other workers and making `isRunning` accurately reflect subscription state. - `RedisStreamsPubSub` adds a `maxDeliveryAttempts` option (default 5) that drops events after the configured number of failed deliveries instead of redelivering forever, and replaces empty `catch {}` blocks with `logger.warn`/`logger.debug` calls. - `RedisStreamsPubSub.unsubscribe(topic, cb)` now honors the topic argument so the same callback can be subscribed to multiple topics independently. - `PullTransport` guards the async router callback against unhandled promise rejections by attaching a `.catch` that nacks the message. - Drop the dead `MASTRA_WORKER_NAME` env var injection in the CLI worker spawn (the bundle entrypoint already passes the worker name directly). - Add a real cross-process e2e auth suite (`pubsub/redis-streams/src/auth-e2e.test.ts`) covering happy path, wrong token, missing token, anonymous direct hits, and the no-auth-provider pin-down behavior. - Step-execution route now has a response schema, satisfying `schema-consistency.test.ts`. - Internal type cleanups (drop several `as any` casts in worker strategies and `BackgroundTaskWorker`). - `RedisStreamsPubSub.maxDeliveryAttempts` now rejects negative / NaN values at construction. `0` still means "no cap" for back-compat but emits a one-time warning; pass `Infinity` to disable the cap explicitly. - `PullTransport` accepts a logger and uses it for unhandled router-callback rejections instead of `console.error`. - `BackgroundTaskWorker.start()` now throws if `init()` was not called, matching the contract of the other workers. - Cross-process integration tests now spawn a single user-owned project (`test-fixtures/cli-project/src/mastra/index.ts`) through two generic entries that mirror what `BuildBundler` and `WorkerBundler` emit. The previous one-off `server.entry.ts` / `worker.entry.ts` / `scheduler.entry.ts` / `background.entry.ts` files have been deleted — they implied users hand-roll entry files, which they don't. Worker role is selected via `MASTRA_WORKERS` exactly as in production. Push-capable PubSub: - The `PubSub` abstract class now declares a `supportedModes` getter (defaulting to `['pull']` for backward compatibility) so consumers can tell whether a broker delivers events through a pull loop, an in-process push, or an out-of-process HTTP push. `EventEmitterPubSub` reports `['pull', 'push']` (EventEmitter dispatches synchronously and works for either path), `@mastra/redis-streams` reports `['pull']`. - `Mastra` now exposes a public `handleWorkflowEvent(event)` method backed by a shared `WorkflowEventProcessor`. It is the single entry point used by the existing pull-mode `OrchestrationWorker`, by in-process push pubsubs (auto-wired during `startWorkers()`), and by the new `POST /api/workflows/events` route which lets push-mode brokers (GCP Pub/Sub push, SNS, EventBridge) deliver events over HTTP. - When the configured pubsub does not support `'pull'`, Mastra automatically skips creating an `OrchestrationWorker` and `OrchestrationWorker.init()` throws a clear error if it is constructed against a push-only pubsub. - `WorkflowEventProcessor` gains a `handle(event)` method that returns a structured `{ ok, retry }` result. The original `process(event, ack?)` method is preserved as a thin wrapper for back-compat. Public-API example for a push-capable PubSub: ```ts import { Mastra } from '@mastra/core/mastra'; import { EventEmitterPubSub } from '@mastra/core/pubsub'; const mastra = new Mastra({ // A push-capable broker (GCP Pub/Sub push, SNS, EventEmitter, …). // EventEmitterPubSub reports supportedModes = ['pull', 'push']. pubsub: new EventEmitterPubSub(), workflows: { myWorkflow }, }); // In-process push pubsubs are auto-wired here. For out-of-process // push (e.g. HTTP webhook from a cloud broker), POST the event to // /api/workflows/events on your Mastra server instead. await mastra.startWorkers(); // Direct invocation (e.g. inside an HTTP handler that bridges from a // cloud broker's push delivery): await mastra.handleWorkflowEvent({ id: 'evt-1', type: 'workflow.start', runId: 'run-1', createdAt: new Date(), data: { workflowId: 'myWorkflow', inputData: { name: 'world' } }, }); ``` CI follow-ups: - `Mastra` only auto-registers `SchedulerWorker` when storage is configured. Without storage the worker would crash on startup (`deps.storage.getStore` on undefined); the scheduler now silently no-ops in that case, matching the pre-worker scheduler behavior. - `SchedulerWorker.init` defensively logs and returns when called without storage instead of throwing a TypeError. - `RECEIVE_WORKFLOW_EVENT_ROUTE` (`POST /workflows/events`) `createdAt` is now a plain `z.string()` on the wire and the handler converts it to a `Date` (validating "Invalid Date" -> 400). The previous `union(...).transform().refine()` schema couldn't be exercised by the shared adapter test suite because the generator didn't unwrap Zod 4's `ZodPipe`. - `_test-utils/route-test-utils` recognizes Zod 4's `number_format` check (used for `int()` / `safeint()`), and `generateContextualValue` now produces a valid ISO timestamp for `createdAt` / `updatedAt` fields. - Configured workflow step scorers now automatically emit scores through the observability score pipeline. ([#16371](https://github.com/mastra-ai/mastra/pull/16371)) ### Patch Changes - dependencies updates: ([#16126](https://github.com/mastra-ai/mastra/pull/16126)) - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.25` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.25) (from `npm:@ai-sdk/provider-utils@3.0.23`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.26` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.26) (from `npm:@ai-sdk/provider-utils@4.0.23`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v5@npm:@ai-sdk/provider@2.0.3` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v5/v/2.0.3) (from `npm:@ai-sdk/provider@2.0.1`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.10` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.10) (from `npm:@ai-sdk/provider@3.0.8`, in `dependencies`) - dependencies updates: ([#16398](https://github.com/mastra-ai/mastra/pull/16398)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.27` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.27) (from `npm:@ai-sdk/provider-utils@4.0.26`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`ac47842`](https://github.com/mastra-ai/mastra/commit/ac478427aa7a5f5fdaed633a911218689b438c60)) - Fixed two bugs that affected scheduled workflows. ([#16510](https://github.com/mastra-ai/mastra/pull/16510)) **Scheduled workflow with mismatched `id` could not be dispatched ([#16471](https://github.com/mastra-ai/mastra/issues/16471))** When a workflow's `id` differed from the key it was registered under, the scheduler published events the event processor could not resolve, causing the run to fail with "Workflow not found." The dispatcher now looks up workflows by `.id` first (falling back to the registration key), so the following now works as expected: ```ts const workflow = createWorkflow({ id: 'daily-report', schedule: { cron: '0 9 * * *' } }); new Mastra({ workflows: { dailyReport: workflow } }); ``` **Deleted scheduled workflows caused infinite event redelivery** Removing a scheduled workflow from code used to leave its schedule row in storage. The scheduler kept firing for the missing workflow and the event processor kept telling the transport to redeliver the event forever. On boot, Mastra now cleans up declarative schedule rows (those it wrote itself, prefixed with `wf_`) for workflows that are no longer registered. User-created schedules made via the schedules API are left untouched. The event processor also handles in-flight events for missing workflows by emitting a single terminal `workflow.fail` instead of looping. - Fixed processor-combined workflows to use the agent logger so processor step failures are logged through the configured logger instead of console output. ([#16369](https://github.com/mastra-ai/mastra/pull/16369)) - Fixed plan approval so accepting a plan can switch modes after the waiting plan tool resolves, clears stale abort state before starting the approved goal, and injects the goal trigger directly instead of queueing a follow-up. ([#16340](https://github.com/mastra-ai/mastra/pull/16340)) - Fixed guardrail processor schemas so structured output works with Anthropic models. ([#15739](https://github.com/mastra-ai/mastra/pull/15739)) - Propagate cache metrics (`cachedInputTokens`, `cacheCreationInputTokens`) through harness token usage. The step-finish handler now extracts `cachedInputTokens` from AI SDK usage and propagates it through `usage_update` events, `getTokenUsage()`, display state, and thread metadata persistence. ([#14746](https://github.com/mastra-ai/mastra/pull/14746)) - Fixed workflow resume to reuse suspended step input payloads when previous step output is stale. Fixes https://github.com/mastra-ai/mastra/issues/16051. ([#16066](https://github.com/mastra-ai/mastra/pull/16066)) - A tool's `toModelOutput` result is now computed before the `tool-result` chunk is emitted, so it travels with the chunk on `providerMetadata.mastra.modelOutput`. Previously `toModelOutput` only ran later when the message list was updated, meaning live stream consumers couldn't see it without re-running the tool. ([#16457](https://github.com/mastra-ai/mastra/pull/16457)) The harness now forwards that `providerMetadata` on `tool_result` content (both streaming and replayed history) and on `tool_end` events, so UIs can render rich tool output (e.g. screenshot images) inline. ```ts harness.on('tool_end', event => { const modelOutput = event.providerMetadata?.mastra?.modelOutput; // e.g. { type: 'content', value: [{ type: 'image-data', mediaType: 'image/png', data: '...' }] } }); ``` - Fixed ToolCallFilter so assistant messages with top-level text are preserved when tool parts are removed. ([#16077](https://github.com/mastra-ai/mastra/pull/16077)) - Fixed AI SDK v6 dynamic tool UI messages so MessageList preserves tool state during history normalization. Fixes #16046. ([#16062](https://github.com/mastra-ai/mastra/pull/16062)) - Fixed tool result media content not reaching the model. Tools using `toModelOutput` to return images or files (e.g. screenshot tools) now work correctly with all AI SDK providers (Anthropic, OpenAI, Google). ([#16449](https://github.com/mastra-ai/mastra/pull/16449)) - Added A2A Agent Card signing config types for server configuration. ([#16207](https://github.com/mastra-ai/mastra/pull/16207)) **Example** ```ts const mastra = new Mastra({ server: { a2a: { agentCardSigning: { privateKey: process.env.A2A_AGENT_CARD_PRIVATE_KEY!, protectedHeader: { alg: 'ES256', kid: 'agent-card-key', }, }, }, }, }); ``` - Fixed a Harness issue where reopening a thread could apply the wrong model for ([#16278](https://github.com/mastra-ai/mastra/pull/16278)) the saved mode. Threads now reopen with the correct model for that mode, including when no explicit per-mode model was selected. - **Fixed** Workflow runs no longer fail to persist when request context contains non-serializable values (for example functions, circular references, or platform proxy objects). This prevents errors when saving workflow snapshots and scorer results. See #12301. ([#12573](https://github.com/mastra-ai/mastra/pull/12573)) - Added `/goal` to Mastra Code, a persistent autonomous task loop similar to the goal modes in Codex and Hermes Agent. ([#16065](https://github.com/mastra-ai/mastra/pull/16065)) A user can start a goal with `/goal `. Mastra Code saves that objective to the current thread, runs the normal assistant turn, then asks a separate judge model whether the goal is `done`, should `continue`, or is `waiting` on an explicit user checkpoint. When the judge says to continue, Mastra Code feeds the judge feedback back into the conversation and keeps working until the goal is complete, paused, cleared, or reaches the configured attempt limit. Use `/judge` to configure the default judge model and max attempts used by future goals. Approved plans can be selected as a goal from the inline plan approval UI, slash commands can opt into `/goal/` with top-level `goal: true`, and skills can opt into goal commands with `metadata.goal: true`. `/goal` objectives can also span multiple lines. - Fixed nested workflow runs retaining abort listeners after completion. ([#16212](https://github.com/mastra-ai/mastra/pull/16212)) - - Fixed `foreach` state update and `foreach` bail in evented workflows ([#16436](https://github.com/mastra-ai/mastra/pull/16436)) - Fixed suspend-resume in evented-workflows legacy stream. - Hide internal spans from Mastra-owned processors in exported traces. The `PROCESSOR_RUN` span still appears, but the agent, model, and tool spans that processors create under the hood are now marked internal and filtered out by default. ([#16424](https://github.com/mastra-ai/mastra/pull/16424)) Affects the moderation, PII detector, language detector, prompt-injection detector, system-prompt scrubber, and structured-output processors. To inspect the internals (e.g. for debugging a Mastra-owned processor's behavior), set `includeInternalSpans: true` on your Observability config and the full subtree will be exported. - Fixed goal reminders in MastraCode to continue through signals without duplicating prompts. Updated core signal stream completion handling so idle-started reminder runs emit the expected lifecycle events. ([#16231](https://github.com/mastra-ai/mastra/pull/16231)) - Fixed requestContext serialization in workflow suspend/resume. The snapshot persistence used instanceof check which fails across module boundaries, causing context values to be lost on resume. ([#16082](https://github.com/mastra-ai/mastra/pull/16082)) - Fixed `timeTravel()` on evented workflows so jumping to a `.branch()` step no longer returns empty results for branches that did not run. Branch results now include only the branch that ran, matching the default workflow engine. ([#16428](https://github.com/mastra-ai/mastra/pull/16428)) - Fixed an issue where trajectory and step scorer results from `runEvals` were not saved to storage. These scores now persist correctly and appear alongside agent and workflow scores in Studio's observability section. ([#16249](https://github.com/mastra-ai/mastra/pull/16249)) - Tool `toModelOutput` invocations now emit a `MAPPING` tracing span, showing the transformed output the model receives. ([#16347](https://github.com/mastra-ai/mastra/pull/16347)) - Default top-level observational memory early activation settings to observations only, while allowing per-phase overrides under `observation` and `reflection`. ([#16367](https://github.com/mastra-ai/mastra/pull/16367)) - Respect optional `resourceId` in `getThreadById` so scoped thread lookups return `null` when the thread belongs to a different resource. ([#14237](https://github.com/mastra-ai/mastra/pull/14237)) Example: ```typescript const thread = await memory.getThreadById({ threadId: 'my-thread-id', resourceId: 'my-user-id', }); // Returns null if the thread does not belong to 'my-user-id'. ``` - Fixed assistant message tracking when ObservationalMemory clears step-1 output to memory and step-2 text merges into the same assistant message, so merged text is not lost on the next response clear. ([#15277](https://github.com/mastra-ai/mastra/pull/15277)) - Added subagent display names to Harness display state so UIs can render configured subagent names without duplicating agent type lookup. ([#16237](https://github.com/mastra-ai/mastra/pull/16237)) - Fixed title generation to send plain text instead of JSON-serialized part objects to the title model. Previously, internal metadata like providerOptions and framework details leaked into the title prompt. Now formats messages using role-prefixed plain text (similar to observational memory formatting), supporting both single-message and multi-turn conversations. ([#15798](https://github.com/mastra-ai/mastra/pull/15798)) - Fixed a bug where orphaned provider-executed tool calls (e.g. Anthropic `web_search`, Gemini `code_execution`) could brick a thread. When a provider dropped the tool-result chunk ([#15668](https://github.com/mastra-ai/mastra/issues/15668)) or a run aborted mid-stream ([#14148](https://github.com/mastra-ai/mastra/issues/14148)), the unresolved call was replayed on every subsequent request — causing Gemini to return empty text and Anthropic to reject the tool-call/tool-result invariant. ([#15682](https://github.com/mastra-ai/mastra/pull/15682)) `sanitizeV5UIMessages` now only keeps an `input-available` provider-executed tool part on the most recent assistant message, and only when no user turn has followed it. Orphans on earlier assistant turns are dropped so the outgoing history always satisfies the tool-call/tool-result pairing required by provider APIs. - Fixed TokenLimiterProcessor crashing when counting text that contains special token strings. ([#16302](https://github.com/mastra-ai/mastra/pull/16302)) - Fix nested loops in evented workflow ([#16312](https://github.com/mastra-ai/mastra/pull/16312)) - Fixed agent requests to Anthropic failing with `400 unexpected tool_use_id` when recalled history contained an incomplete `tool_use` / `tool_result` pair. This most commonly hit agents using parallel tool calls when an earlier run was interrupted before every tool finished — pulling that broken exchange into a new prompt no longer crashes the next request. ([#16201](https://github.com/mastra-ai/mastra/pull/16201)) Closes #16193 - Stop logging auto-recoverable provider cache corruption warnings when `~/.cache/mastra/` contains stale content from another Mastra version. Corrupted cache files are still deleted on read so they cannot propagate into a project's `dist/`, and the next gateway sync rewrites valid files. ([#16332](https://github.com/mastra-ai/mastra/pull/16332)) - Fix evented workflow foreach timing out when payload is an empty array ([#16358](https://github.com/mastra-ai/mastra/pull/16358)) - Fixed FGA EE license gating so production servers require an Enterprise license when FGA is configured. ([#16362](https://github.com/mastra-ai/mastra/pull/16362)) - Fixed a bug where message-level `providerOptions` could be lost or applied to the wrong turn after tool calls. Anthropic `cacheControl` markers now stay attached to the intended message in tool-using conversations. ([#16133](https://github.com/mastra-ai/mastra/pull/16133)) - Fixed `MODEL_INFERENCE` span timing so it measures pure model latency. ([#16357](https://github.com/mastra-ai/mastra/pull/16357)) - Added extra defensive checks to prevent edge cases where system messages may have already been stored in message history. ([#15787](https://github.com/mastra-ai/mastra/pull/15787)) - Fixes tool call args being lost when split across messages in client tools. When a tool invocation spans multiple messages (call with args, result with empty args), the `findToolCallArgs` function now continues searching for non-empty args instead of returning the first match. ([#12454](https://github.com/mastra-ai/mastra/pull/12454)) - Replace `js-tiktoken` with `tokenx` in `@mastra/core` to reduce bundle size by removing the bundled BPE rank tables. Token limiting and truncation now use heuristic token estimates, which is appropriate for output limiting and truncation. ([#16326](https://github.com/mastra-ai/mastra/pull/16326)) - Fix `abort()` not cancelling evented workflows ([#16416](https://github.com/mastra-ai/mastra/pull/16416)) - Fixed suspend and resume for evented workflows that use parallel steps, `.branch()`, `dountil`/`dowhile` loops, and nested workflows — previously these only worked reliably for simple linear flows. ([#16476](https://github.com/mastra-ai/mastra/pull/16476)) **Parallel & `.branch()` steps** — when more than one branch suspends at the same time (e.g. each branch waits on its own approval), every suspended branch can now be resumed, the workflow stays suspended until all of them have been resumed, and the branch outputs are merged correctly. Before, only the last branch to suspend was resumable, and resuming one branch could prematurely complete the run. **`dountil` / `dowhile` loops** — a loop body that calls `suspend()` now suspends the workflow instead of crashing the run. And after a resume, subsequent loop iterations run fresh instead of re-receiving the resume data — which previously made loops either run forever or skip their own suspend logic. **Nested workflows** — resuming a suspended step inside a nested workflow now gives it the correct input (the output of the step right before it, not the nested workflow's own input), so it produces correct results, even when workflows are nested several levels deep. The suspended-step path returned in a workflow result is also correct now, so you can pass it straight back into `resume({ step })`. - Added support for attaching a browser instance to the harness after initialization so consumers can defer browser creation until it is needed: ([#16513](https://github.com/mastra-ai/mastra/pull/16513)) ```ts const harness = new Harness({ agent, mastra }); await harness.init(); harness.setBrowser(browser); ``` - Added validation to evented workflows to ensure execution prerequisites are met. `EventedWorkflow.createRun()` now throw clear error messages when the workflow execution flow is empty (missing `.then()`, `.branch()`, etc. calls) or when the step graph has uncommitted changes (missing `.commit()` call). This catches configuration errors early rather than failing during execution. ([#16361](https://github.com/mastra-ai/mastra/pull/16361)) - Background tasks now run as evented workflows. Each task is dispatched as a workflow run that owns executor invocation, retries, and suspend/resume; pubsub topics, lifecycle event shapes, concurrency gating, and the `BackgroundTaskManager.stream()` contract are unchanged. ([#16260](https://github.com/mastra-ai/mastra/pull/16260)) Add suspend/resume to background tasks. Tools can call `ctx.agent.suspend(data)` from `execute` to pause a task and release the concurrency slot; resume with `mastra.backgroundTaskManager.resume(taskId, resumeData)` or `agent.resumeStreamUntilIdle(resumeData, { runId, toolCallId })`. Surfaces `background-task-suspended` / `background-task-resumed` chunks on `backgroundTaskManager.stream()` and `agent.streamUntilIdle().fullStream`. - Fixed message part ordering in agent streaming responses. Message parts (text, reasoning, tool calls) now appear in the correct order they arrived in the stream, preventing incorrect step sequencing and agent loop behavior issues. ([#16073](https://github.com/mastra-ai/mastra/pull/16073)) - Updated dependencies [[`c50ebc3`](https://github.com/mastra-ai/mastra/commit/c50ebc34da71044558315735e69bfb94fcfb74bf)]: - @mastra/schema-compat@1.2.10 ## 1.33.0-alpha.17 ### Patch Changes - Added support for attaching a browser instance to the harness after initialization so consumers can defer browser creation until it is needed: ([#16513](https://github.com/mastra-ai/mastra/pull/16513)) ```ts const harness = new Harness({ agent, mastra }); await harness.init(); harness.setBrowser(browser); ``` ## 1.33.0-alpha.16 ### Patch Changes - Fixed two bugs that affected scheduled workflows. ([#16510](https://github.com/mastra-ai/mastra/pull/16510)) **Scheduled workflow with mismatched `id` could not be dispatched ([#16471](https://github.com/mastra-ai/mastra/issues/16471))** When a workflow's `id` differed from the key it was registered under, the scheduler published events the event processor could not resolve, causing the run to fail with "Workflow not found." The dispatcher now looks up workflows by `.id` first (falling back to the registration key), so the following now works as expected: ```ts const workflow = createWorkflow({ id: 'daily-report', schedule: { cron: '0 9 * * *' } }); new Mastra({ workflows: { dailyReport: workflow } }); ``` **Deleted scheduled workflows caused infinite event redelivery** Removing a scheduled workflow from code used to leave its schedule row in storage. The scheduler kept firing for the missing workflow and the event processor kept telling the transport to redeliver the event forever. On boot, Mastra now cleans up declarative schedule rows (those it wrote itself, prefixed with `wf_`) for workflows that are no longer registered. User-created schedules made via the schedules API are left untouched. The event processor also handles in-flight events for missing workflows by emitting a single terminal `workflow.fail` instead of looping. ## 1.33.0-alpha.15 ### Minor Changes - Added experimental support for using remote A2A agents as Mastra subagents. ([#16348](https://github.com/mastra-ai/mastra/pull/16348)) **What changed** - Mastra agents can register remote A2A endpoints through `A2AAgent` and delegate to them like other subagents. - Remote A2A subagents support `generate`, `resumeGenerate`, `stream`, and `resumeStream` so parent agents can use them in normal subagent flows. - Agent Cards can be cached and verified with pluggable verification hooks before remote execution begins. - Browser environments can import shared A2A types and errors from `@mastra/core/a2a/client`. **Example** ```ts import { Agent } from '@mastra/core/agent'; import { A2AAgent } from '@mastra/core/a2a'; const agent = new Agent({ name: 'Support Agent', instructions: 'Use the remote billing specialist for billing questions.', model: 'openai/gpt-4o-mini', agents: { billingSpecialist: new A2AAgent({ url: 'https://billing.example.com/.well-known/agent-card.json', }), }, }); const result = await agent.generate('Can you check the latest invoice status?'); ``` **Why** This lets Mastra agents compose with remote A2A agents without exposing those integrations as plain tools or depending directly on the client SDK. ## 1.33.0-alpha.14 ## 1.33.0-alpha.13 ### Minor Changes - Improved Harness support for Agent thread signals. ([#16231](https://github.com/mastra-ai/mastra/pull/16231)) Harness thread subscriptions now own stream processing for followed runs, echo user-message signal data with stable IDs, and support idle signal starts without delaying optimistic rendering. - Improved foreach workflow execution to keep concurrency slots filled as iterations finish. ([#12860](https://github.com/mastra-ai/mastra/pull/12860)) ### Patch Changes - - Fixed `foreach` state update and `foreach` bail in evented workflows ([#16436](https://github.com/mastra-ai/mastra/pull/16436)) - Fixed suspend-resume in evented-workflows legacy stream. - Fixed goal reminders in MastraCode to continue through signals without duplicating prompts. ([#16231](https://github.com/mastra-ai/mastra/pull/16231)) - Fixed suspend and resume for evented workflows that use parallel steps, `.branch()`, `dountil`/`dowhile` loops, and nested workflows — previously these only worked reliably for simple linear flows. ([#16476](https://github.com/mastra-ai/mastra/pull/16476)) **Parallel & `.branch()` steps** — when more than one branch suspends at the same time (e.g. each branch waits on its own approval), every suspended branch can now be resumed, the workflow stays suspended until all of them have been resumed, and the branch outputs are merged correctly. Before, only the last branch to suspend was resumable, and resuming one branch could prematurely complete the run. **`dountil` / `dowhile` loops** — a loop body that calls `suspend()` now suspends the workflow instead of crashing the run. And after a resume, subsequent loop iterations run fresh instead of re-receiving the resume data — which previously made loops either run forever or skip their own suspend logic. **Nested workflows** — resuming a suspended step inside a nested workflow now gives it the correct input (the output of the step right before it, not the nested workflow's own input), so it produces correct results, even when workflows are nested several levels deep. The suspended-step path returned in a workflow result is also correct now, so you can pass it straight back into `resume({ step })`. ## 1.33.0-alpha.12 ### Minor Changes - Added Agent signals for sending contextual messages into agent thread loops and subscribing to thread activity. ([#16229](https://github.com/mastra-ai/mastra/pull/16229)) Call `agent.sendSignal()` to inject context into a running agent loop. When the thread is idle, that same signal becomes the prompt that starts the next loop by default. Use `ifActive.behavior` and `ifIdle.behavior` to deliver, persist, discard, or wake from a signal. Use `agent.subscribeToThread()` to follow the raw stream chunks for a memory thread, observe signal echoes with stable IDs, and abort the active stream for that thread. ```ts const subscription = await agent.subscribeToThread({ resourceId, threadId }); void (async () => { for await (const part of subscription.stream) { if (part.type === 'finish') { subscription.unsubscribe(); } } })(); agent.sendSignal({ type: 'user-message', contents: 'Use the latest answer' }, { resourceId, threadId }); ``` - Added processor `sendSignal` support and routed built-in system reminders through signal messages. ([#16438](https://github.com/mastra-ai/mastra/pull/16438)) ### Patch Changes - A tool's `toModelOutput` result is now computed before the `tool-result` chunk is emitted, so it travels with the chunk on `providerMetadata.mastra.modelOutput`. Previously `toModelOutput` only ran later when the message list was updated, meaning live stream consumers couldn't see it without re-running the tool. ([#16457](https://github.com/mastra-ai/mastra/pull/16457)) The harness now forwards that `providerMetadata` on `tool_result` content (both streaming and replayed history) and on `tool_end` events, so UIs can render rich tool output (e.g. screenshot images) inline. ```ts harness.on('tool_end', event => { const modelOutput = event.providerMetadata?.mastra?.modelOutput; // e.g. { type: 'content', value: [{ type: 'image-data', mediaType: 'image/png', data: '...' }] } }); ``` ## 1.33.0-alpha.11 ### Patch Changes - Fixed ToolCallFilter so assistant messages with top-level text are preserved when tool parts are removed. ([#16077](https://github.com/mastra-ai/mastra/pull/16077)) - Fixed tool result media content not reaching the model. Tools using `toModelOutput` to return images or files (e.g. screenshot tools) now work correctly with all AI SDK providers (Anthropic, OpenAI, Google). ([#16449](https://github.com/mastra-ai/mastra/pull/16449)) - Added subagent display names to Harness display state so UIs can render configured subagent names without duplicating agent type lookup. ([#16237](https://github.com/mastra-ai/mastra/pull/16237)) - Fixed agent requests to Anthropic failing with `400 unexpected tool_use_id` when recalled history contained an incomplete `tool_use` / `tool_result` pair. This most commonly hit agents using parallel tool calls when an earlier run was interrupted before every tool finished — pulling that broken exchange into a new prompt no longer crashes the next request. ([#16201](https://github.com/mastra-ai/mastra/pull/16201)) Closes #16193 - Fixed FGA EE license gating so production servers require an Enterprise license when FGA is configured. ([#16362](https://github.com/mastra-ai/mastra/pull/16362)) - Fixed a bug where message-level `providerOptions` could be lost or applied to the wrong turn after tool calls. Anthropic `cacheControl` markers now stay attached to the intended message in tool-using conversations. ([#16133](https://github.com/mastra-ai/mastra/pull/16133)) ## 1.33.0-alpha.10 ### Minor Changes - Added `preserveModelOutput` to `ToolCallFilter` so filtered tool history can keep compact model-facing output without raw tool args or results. ([#16060](https://github.com/mastra-ai/mastra/pull/16060)) ```ts import { ToolCallFilter } from '@mastra/core/processors'; const filter = new ToolCallFilter({ preserveModelOutput: true, }); ``` - Added a SubAgent interface for custom supervisor subagents. ([#16359](https://github.com/mastra-ai/mastra/pull/16359)) ### Patch Changes - dependencies updates: ([#16398](https://github.com/mastra-ai/mastra/pull/16398)) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.27` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.27) (from `npm:@ai-sdk/provider-utils@4.0.26`, in `dependencies`) - Fixed processor-combined workflows to use the agent logger so processor step failures are logged through the configured logger instead of console output. ([#16369](https://github.com/mastra-ai/mastra/pull/16369)) - Fixed AI SDK v6 dynamic tool UI messages so MessageList preserves tool state during history normalization. Fixes #16046. ([#16062](https://github.com/mastra-ai/mastra/pull/16062)) - Hide internal spans from Mastra-owned processors in exported traces. The `PROCESSOR_RUN` span still appears, but the agent, model, and tool spans that processors create under the hood are now marked internal and filtered out by default. ([#16424](https://github.com/mastra-ai/mastra/pull/16424)) Affects the moderation, PII detector, language detector, prompt-injection detector, system-prompt scrubber, and structured-output processors. To inspect the internals (e.g. for debugging a Mastra-owned processor's behavior), set `includeInternalSpans: true` on your Observability config and the full subtree will be exported. - Fixed `timeTravel()` on evented workflows so jumping to a `.branch()` step no longer returns empty results for branches that did not run. Branch results now include only the branch that ran, matching the default workflow engine. ([#16428](https://github.com/mastra-ai/mastra/pull/16428)) - Fix `abort()` not cancelling evented workflows ([#16416](https://github.com/mastra-ai/mastra/pull/16416)) - Added validation to evented workflows to ensure execution prerequisites are met. `EventedWorkflow.createRun()` now throw clear error messages when the workflow execution flow is empty (missing `.then()`, `.branch()`, etc. calls) or when the step graph has uncommitted changes (missing `.commit()` call). This catches configuration errors early rather than failing during execution. ([#16361](https://github.com/mastra-ai/mastra/pull/16361)) ## 1.33.0-alpha.9 ### Minor Changes - Configured workflow step scorers now automatically emit scores through the observability score pipeline. ([#16371](https://github.com/mastra-ai/mastra/pull/16371)) ## 1.33.0-alpha.8 ### Minor Changes - Added target-aware tool payload transforms for display streams and transcript messages. Tool authors can transform tool input, output, errors, approval payloads, and suspension payloads without changing raw runtime behavior or toModelOutput. See https://github.com/mastra-ai/mastra/issues/16054. ([#16103](https://github.com/mastra-ai/mastra/pull/16103)) Use `transform` on tools, agents, Mastra, or individual generation calls to configure these payload transforms. Runtime callers using the previous `toolPayloadProjection` shape continue to be normalized for compatibility. ```ts const lookupCustomer = createTool({ execute: async ({ customerId, internalPath }) => lookupCustomerRecord(customerId, internalPath), transform: { display: { input: ({ input }) => ({ customerId: input?.customerId }), output: ({ output }) => ({ displayName: output?.displayName }), }, transcript: { input: ({ input }) => ({ customerId: input?.customerId }), output: ({ output }) => ({ displayName: output?.displayName }), }, }, }); ``` - **Added `ResponseCache` input processor** ([#16283](https://github.com/mastra-ai/mastra/pull/16283)) Cache identical LLM steps to skip the model call and replay a previously cached response. Useful for prompt templates, suggested-prompt buttons, agentic search re-asks, or guardrail LLMs that classify the same input over and over. Caching is opt-in: register `ResponseCache` explicitly on `inputProcessors`. There is no agent-level option — this keeps the surface small while we collect feedback on the processor API. Per-call overrides flow through `RequestContext`. ```ts import { Agent } from '@mastra/core/agent'; import { InMemoryServerCache } from '@mastra/core/cache'; import { ResponseCache } from '@mastra/core/processors'; const cache = new InMemoryServerCache(); const agent = new Agent({ name: 'Search Agent', instructions: 'You answer questions concisely.', model: 'openai/gpt-5', inputProcessors: [new ResponseCache({ cache, ttl: 600 })], }); // First call: cache miss → LLM call await agent.generate('What is the capital of France?'); // Second identical call: cache hit → no LLM call await agent.generate('What is the capital of France?'); ``` Per-call overrides via `RequestContext`: ```ts import { ResponseCache } from '@mastra/core/processors'; import { RequestContext } from '@mastra/core/request-context'; // Force a fresh call but still update the cache. await agent.stream(prompt, { requestContext: ResponseCache.context({ bust: true }), }); // Or merge into an existing context. const ctx = new RequestContext(); ResponseCache.applyContext(ctx, { key: 'custom-key' }); await agent.stream(prompt, { requestContext: ctx }); ``` Three fields are overridable per call: `key`, `scope`, `bust`. `cache`, `ttl`, and `agentId` stay on the constructor. A `key` function receives `{ agentId, scope, model, prompt, stepNumber }` and returns a string (or `Promise`): ```ts await agent.stream(prompt, { requestContext: ResponseCache.context({ key: ({ model, prompt }) => `qa:${model.modelId}:${JSON.stringify(prompt).slice(-200)}`, }), }); ``` The cache key is derived from the resolved prompt Mastra is about to send to the model — i.e. _after_ memory loading and earlier input processors have run — so cached entries are tenant-isolated and don't leak context across users with shared prompts but different memory state. Each step in an agentic tool loop is independently cached. By default, the cache scope falls back to `MASTRA_RESOURCE_ID_KEY` from the request context for automatic per-user isolation. Failed runs (errors, tripwire activations) are not cached. See [Response caching](https://mastra.ai/en/docs/agents/response-caching) for details. Also adds: - `InMemoryServerCache` (in `@mastra/core/cache`) for local development. `ResponseCache` accepts any `MastraServerCache` directly — use `RedisCache` from `@mastra/redis` for production. - `MastraServerCache.set()` now accepts an optional `ttlMs` argument so implementations can override the configured default TTL on a per-entry basis. `InMemoryServerCache` and `RedisCache` (in `@mastra/redis`) both honor this. - New paired processor hooks `processLLMRequest` and `processLLMResponse`. `ProcessLLMRequestResult` may return `{ response }` to short-circuit the LLM call with a cached payload. ### Patch Changes - Fixed plan approval so accepting a plan can switch modes after the waiting plan tool resolves, clears stale abort state before starting the approved goal, and injects the goal trigger directly instead of queueing a follow-up. ([#16340](https://github.com/mastra-ai/mastra/pull/16340)) - Propagate cache metrics (`cachedInputTokens`, `cacheCreationInputTokens`) through harness token usage. The step-finish handler now extracts `cachedInputTokens` from AI SDK usage and propagates it through `usage_update` events, `getTokenUsage()`, display state, and thread metadata persistence. ([#14746](https://github.com/mastra-ai/mastra/pull/14746)) - Default top-level observational memory early activation settings to observations only, while allowing per-phase overrides under `observation` and `reflection`. ([#16367](https://github.com/mastra-ai/mastra/pull/16367)) - Respect optional `resourceId` in `getThreadById` so scoped thread lookups return `null` when the thread belongs to a different resource. ([#14237](https://github.com/mastra-ai/mastra/pull/14237)) Example: ```typescript const thread = await memory.getThreadById({ threadId: 'my-thread-id', resourceId: 'my-user-id', }); // Returns null if the thread does not belong to 'my-user-id'. ``` - Fixed assistant message tracking when ObservationalMemory clears step-1 output to memory and step-2 text merges into the same assistant message, so merged text is not lost on the next response clear. ([#15277](https://github.com/mastra-ai/mastra/pull/15277)) - Stop logging auto-recoverable provider cache corruption warnings when `~/.cache/mastra/` contains stale content from another Mastra version. Corrupted cache files are still deleted on read so they cannot propagate into a project's `dist/`, and the next gateway sync rewrites valid files. ([#16332](https://github.com/mastra-ai/mastra/pull/16332)) - Updated dependencies [[`c50ebc3`](https://github.com/mastra-ai/mastra/commit/c50ebc34da71044558315735e69bfb94fcfb74bf)]: - @mastra/schema-compat@1.2.10-alpha.0 ## 1.33.0-alpha.7 ### Minor Changes - Mastra Eval results are now emitted once through the unified observability score pipeline. ([#16185](https://github.com/mastra-ai/mastra/pull/16185)) - Added workflow state reader helpers to inspect persisted workflow runs and recover suspended or long-running workflows. ([#16091](https://github.com/mastra-ai/mastra/pull/16091)) The reader exposes suspended steps, resume labels, step payloads, and step outputs from the public WorkflowState returned by workflow.getWorkflowRunById(), and WorkflowState step results now reflect foreach array entries. - Add metadata filtering support to semantic recall. ([#9256](https://github.com/mastra-ai/mastra/pull/9256)) - Added structured drop event types and an `onDroppedEvent` hook so exporters and bridge integrations can observe events dropped by the observability pipeline. ([#16111](https://github.com/mastra-ai/mastra/pull/16111)) - Added stable IDs to Harness task items plus `task_update` and `task_complete` for updating or completing one tracked task by ID. Task tools now return structured task snapshots, and `task_check` returns `summary` and `incompleteTasks` fields so agents and UIs can restore and verify task state without parsing text. ([#16254](https://github.com/mastra-ai/mastra/pull/16254)) Harness also exports `TaskItemSnapshot`, `assignTaskIds`, and `harness.restoreDisplayTasks()` for UI history replay, serializes task reads and mutations against the latest task state snapshot, and returns task-tool errors inside forked subagents so sidecar work cannot mutate parent task state. - Added new `MODEL_INFERENCE` span type under `MODEL_STEP`, covering only the model provider call. Use it to measure model latency separately from input/output processors and tool executions. ([#16267](https://github.com/mastra-ai/mastra/pull/16267)) - Worker review fixes: ([#16309](https://github.com/mastra-ai/mastra/pull/16309)) - Step-execution endpoint (`POST /workflows/:id/runs/:runId/steps/execute`) is now gated by Mastra's standard `requiresAuth: true` + `authenticateToken` pipeline rather than a parallel "worker secret" body field. The previously introduced `workerSecret` config knob and `MASTRA_WORKER_SECRET` env var have been removed (they were never released). To gate the endpoint on a standalone-worker deployment, configure an auth provider on the server's `Mastra` instance — without one the framework currently treats `requiresAuth: true` as a no-op for this route. - `HttpRemoteStrategy` now sends credentials as a normal `Authorization: Bearer ` header. The token comes from the new `MASTRA_WORKER_AUTH_TOKEN` env var or an explicit `auth` constructor option. - Honor the caller's `abortSignal` in `HttpRemoteStrategy` by combining it with the per-request timeout via `AbortSignal.any` (with a manual fallback for runtimes that don't expose it). - Implement comma-separated name filtering for the `MASTRA_WORKERS` env var. `MASTRA_WORKERS=scheduler,backgroundTasks` now boots only those named workers; `MASTRA_WORKERS=false` still disables all workers. - Restore `Mastra.startEventEngine` / `stopEventEngine` as `@deprecated` aliases for the renamed `startWorkers` / `stopWorkers`. - `BackgroundTaskWorker` now subscribes to PubSub in `start()` instead of `init()`, matching the lifecycle of the other workers and making `isRunning` accurately reflect subscription state. - `RedisStreamsPubSub` adds a `maxDeliveryAttempts` option (default 5) that drops events after the configured number of failed deliveries instead of redelivering forever, and replaces empty `catch {}` blocks with `logger.warn`/`logger.debug` calls. - `RedisStreamsPubSub.unsubscribe(topic, cb)` now honors the topic argument so the same callback can be subscribed to multiple topics independently. - `PullTransport` guards the async router callback against unhandled promise rejections by attaching a `.catch` that nacks the message. - Drop the dead `MASTRA_WORKER_NAME` env var injection in the CLI worker spawn (the bundle entrypoint already passes the worker name directly). - Add a real cross-process e2e auth suite (`pubsub/redis-streams/src/auth-e2e.test.ts`) covering happy path, wrong token, missing token, anonymous direct hits, and the no-auth-provider pin-down behavior. - Step-execution route now has a response schema, satisfying `schema-consistency.test.ts`. - Internal type cleanups (drop several `as any` casts in worker strategies and `BackgroundTaskWorker`). - `RedisStreamsPubSub.maxDeliveryAttempts` now rejects negative / NaN values at construction. `0` still means "no cap" for back-compat but emits a one-time warning; pass `Infinity` to disable the cap explicitly. - `PullTransport` accepts a logger and uses it for unhandled router-callback rejections instead of `console.error`. - `BackgroundTaskWorker.start()` now throws if `init()` was not called, matching the contract of the other workers. - Cross-process integration tests now spawn a single user-owned project (`test-fixtures/cli-project/src/mastra/index.ts`) through two generic entries that mirror what `BuildBundler` and `WorkerBundler` emit. The previous one-off `server.entry.ts` / `worker.entry.ts` / `scheduler.entry.ts` / `background.entry.ts` files have been deleted — they implied users hand-roll entry files, which they don't. Worker role is selected via `MASTRA_WORKERS` exactly as in production. Push-capable PubSub: - The `PubSub` abstract class now declares a `supportedModes` getter (defaulting to `['pull']` for backward compatibility) so consumers can tell whether a broker delivers events through a pull loop, an in-process push, or an out-of-process HTTP push. `EventEmitterPubSub` reports `['pull', 'push']` (EventEmitter dispatches synchronously and works for either path), `@mastra/redis-streams` reports `['pull']`. - `Mastra` now exposes a public `handleWorkflowEvent(event)` method backed by a shared `WorkflowEventProcessor`. It is the single entry point used by the existing pull-mode `OrchestrationWorker`, by in-process push pubsubs (auto-wired during `startWorkers()`), and by the new `POST /api/workflows/events` route which lets push-mode brokers (GCP Pub/Sub push, SNS, EventBridge) deliver events over HTTP. - When the configured pubsub does not support `'pull'`, Mastra automatically skips creating an `OrchestrationWorker` and `OrchestrationWorker.init()` throws a clear error if it is constructed against a push-only pubsub. - `WorkflowEventProcessor` gains a `handle(event)` method that returns a structured `{ ok, retry }` result. The original `process(event, ack?)` method is preserved as a thin wrapper for back-compat. Public-API example for a push-capable PubSub: ```ts import { Mastra } from '@mastra/core/mastra'; import { EventEmitterPubSub } from '@mastra/core/pubsub'; const mastra = new Mastra({ // A push-capable broker (GCP Pub/Sub push, SNS, EventEmitter, …). // EventEmitterPubSub reports supportedModes = ['pull', 'push']. pubsub: new EventEmitterPubSub(), workflows: { myWorkflow }, }); // In-process push pubsubs are auto-wired here. For out-of-process // push (e.g. HTTP webhook from a cloud broker), POST the event to // /api/workflows/events on your Mastra server instead. await mastra.startWorkers(); // Direct invocation (e.g. inside an HTTP handler that bridges from a // cloud broker's push delivery): await mastra.handleWorkflowEvent({ id: 'evt-1', type: 'workflow.start', runId: 'run-1', createdAt: new Date(), data: { workflowId: 'myWorkflow', inputData: { name: 'world' } }, }); ``` CI follow-ups: - `Mastra` only auto-registers `SchedulerWorker` when storage is configured. Without storage the worker would crash on startup (`deps.storage.getStore` on undefined); the scheduler now silently no-ops in that case, matching the pre-worker scheduler behavior. - `SchedulerWorker.init` defensively logs and returns when called without storage instead of throwing a TypeError. - `RECEIVE_WORKFLOW_EVENT_ROUTE` (`POST /workflows/events`) `createdAt` is now a plain `z.string()` on the wire and the handler converts it to a `Date` (validating "Invalid Date" -> 400). The previous `union(...).transform().refine()` schema couldn't be exercised by the shared adapter test suite because the generator didn't unwrap Zod 4's `ZodPipe`. - `_test-utils/route-test-utils` recognizes Zod 4's `number_format` check (used for `int()` / `safeint()`), and `generateContextualValue` now produces a valid ISO timestamp for `createdAt` / `updatedAt` fields. ### Patch Changes - Fixed workflow resume to reuse suspended step input payloads when previous step output is stale. Fixes https://github.com/mastra-ai/mastra/issues/16051. ([#16066](https://github.com/mastra-ai/mastra/pull/16066)) - **Fixed** Workflow runs no longer fail to persist when request context contains non-serializable values (for example functions, circular references, or platform proxy objects). This prevents errors when saving workflow snapshots and scorer results. See #12301. ([#12573](https://github.com/mastra-ai/mastra/pull/12573)) - Fixed nested workflow runs retaining abort listeners after completion. ([#16212](https://github.com/mastra-ai/mastra/pull/16212)) - Fixed requestContext serialization in workflow suspend/resume. The snapshot persistence used instanceof check which fails across module boundaries, causing context values to be lost on resume. ([#16082](https://github.com/mastra-ai/mastra/pull/16082)) - Tool `toModelOutput` invocations now emit a `MAPPING` tracing span, showing the transformed output the model receives. ([#16347](https://github.com/mastra-ai/mastra/pull/16347)) - Fix evented workflow foreach timing out when payload is an empty array ([#16358](https://github.com/mastra-ai/mastra/pull/16358)) - Fixed `MODEL_INFERENCE` span timing so it measures pure model latency. ([#16357](https://github.com/mastra-ai/mastra/pull/16357)) ## 1.33.0-alpha.6 ### Patch Changes - Fixed title generation to send plain text instead of JSON-serialized part objects to the title model. Previously, internal metadata like providerOptions and framework details leaked into the title prompt. Now formats messages using role-prefixed plain text (similar to observational memory formatting), supporting both single-message and multi-turn conversations. ([#15798](https://github.com/mastra-ai/mastra/pull/15798)) - Added extra defensive checks to prevent edge cases where system messages may have already been stored in message history. ([#15787](https://github.com/mastra-ai/mastra/pull/15787)) - Replace `js-tiktoken` with `tokenx` in `@mastra/core` to reduce bundle size by removing the bundled BPE rank tables. Token limiting and truncation now use heuristic token estimates, which is appropriate for output limiting and truncation. ([#16326](https://github.com/mastra-ai/mastra/pull/16326)) ## 1.33.0-alpha.5 ### Patch Changes - Fixed guardrail processor schemas so structured output works with Anthropic models. ([#15739](https://github.com/mastra-ai/mastra/pull/15739)) - Added `/goal` to Mastra Code, a persistent autonomous task loop similar to the goal modes in Codex and Hermes Agent. ([#16065](https://github.com/mastra-ai/mastra/pull/16065)) A user can start a goal with `/goal `. Mastra Code saves that objective to the current thread, runs the normal assistant turn, then asks a separate judge model whether the goal is `done`, should `continue`, or is `waiting` on an explicit user checkpoint. When the judge says to continue, Mastra Code feeds the judge feedback back into the conversation and keeps working until the goal is complete, paused, cleared, or reaches the configured attempt limit. Use `/judge` to configure the default judge model and max attempts used by future goals. Approved plans can be selected as a goal from the inline plan approval UI, slash commands can opt into `/goal/` with top-level `goal: true`, and skills can opt into goal commands with `metadata.goal: true`. `/goal` objectives can also span multiple lines. - Fixed a bug where orphaned provider-executed tool calls (e.g. Anthropic `web_search`, Gemini `code_execution`) could brick a thread. When a provider dropped the tool-result chunk ([#15668](https://github.com/mastra-ai/mastra/issues/15668)) or a run aborted mid-stream ([#14148](https://github.com/mastra-ai/mastra/issues/14148)), the unresolved call was replayed on every subsequent request — causing Gemini to return empty text and Anthropic to reject the tool-call/tool-result invariant. ([#15682](https://github.com/mastra-ai/mastra/pull/15682)) `sanitizeV5UIMessages` now only keeps an `input-available` provider-executed tool part on the most recent assistant message, and only when no user turn has followed it. Orphans on earlier assistant turns are dropped so the outgoing history always satisfies the tool-call/tool-result pairing required by provider APIs. - Fixes tool call args being lost when split across messages in client tools. When a tool invocation spans multiple messages (call with args, result with empty args), the `findToolCallArgs` function now continues searching for non-empty args instead of returning the first match. ([#12454](https://github.com/mastra-ai/mastra/pull/12454)) ## 1.33.0-alpha.4 ### Patch Changes - dependencies updates: ([#16126](https://github.com/mastra-ai/mastra/pull/16126)) - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.25` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.25) (from `npm:@ai-sdk/provider-utils@3.0.23`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-utils-v6@npm:@ai-sdk/provider-utils@4.0.26` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v6/v/4.0.26) (from `npm:@ai-sdk/provider-utils@4.0.23`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v5@npm:@ai-sdk/provider@2.0.3` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v5/v/2.0.3) (from `npm:@ai-sdk/provider@2.0.1`, in `dependencies`) - Updated dependency [`@ai-sdk/provider-v6@npm:@ai-sdk/provider@3.0.10` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-v6/v/3.0.10) (from `npm:@ai-sdk/provider@3.0.8`, in `dependencies`) - Added A2A Agent Card signing config types for server configuration. ([#16207](https://github.com/mastra-ai/mastra/pull/16207)) **Example** ```ts const mastra = new Mastra({ server: { a2a: { agentCardSigning: { privateKey: process.env.A2A_AGENT_CARD_PRIVATE_KEY!, protectedHeader: { alg: 'ES256', kid: 'agent-card-key', }, }, }, }, }); ``` - Fixed TokenLimiterProcessor crashing when counting text that contains special token strings. ([#16302](https://github.com/mastra-ai/mastra/pull/16302)) - Fix nested loops in evented workflow ([#16312](https://github.com/mastra-ai/mastra/pull/16312)) - Background tasks now run as evented workflows. Each task is dispatched as a workflow run that owns executor invocation, retries, and suspend/resume; pubsub topics, lifecycle event shapes, concurrency gating, and the `BackgroundTaskManager.stream()` contract are unchanged. ([#16260](https://github.com/mastra-ai/mastra/pull/16260)) Add suspend/resume to background tasks. Tools can call `ctx.agent.suspend(data)` from `execute` to pause a task and release the concurrency slot; resume with `mastra.backgroundTaskManager.resume(taskId, resumeData)` or `agent.resumeStreamUntilIdle(resumeData, { runId, toolCallId })`. Surfaces `background-task-suspended` / `background-task-resumed` chunks on `backgroundTaskManager.stream()` and `agent.streamUntilIdle().fullStream`. - Fixed message part ordering in agent streaming responses. Message parts (text, reasoning, tool calls) now appear in the correct order they arrived in the stream, preventing incorrect step sequencing and agent loop behavior issues. ([#16073](https://github.com/mastra-ai/mastra/pull/16073)) ## 1.33.0-alpha.3 ### Minor Changes - Added Azure OpenAI Responses API and v1 routing controls. ([#16246](https://github.com/mastra-ai/mastra/pull/16246)) Use `useResponsesAPI: true` to resolve Azure deployments through the Responses API with the Azure v1 route by default: ```ts new AzureOpenAIGateway({ resourceName: 'my-openai-resource', apiKey: process.env.AZURE_API_KEY!, useResponsesAPI: true, deployments: ['my-gpt-5-4-deployment'], }); ``` When `useDeploymentBasedUrls: false` is used directly, the gateway now defaults `apiVersion` to `"v1"` to match the AI SDK Azure provider's v1 URL route. Passing `apiVersion: "v1"` by itself keeps the existing deployment-based URL default for compatibility. - Added Azure OpenAI Responses WebSocket transport support for streaming agent and tool loops. ([#16246](https://github.com/mastra-ai/mastra/pull/16246)) Configure the Azure gateway with `useResponsesAPI: true`, then opt into WebSocket streaming per request: ```ts const stream = await agent.stream('Review this task', { providerOptions: { azure: { transport: 'websocket', websocket: { closeOnFinish: false }, }, }, }); ``` Responses WebSocket streams now preserve transport handles through agent loops, reuse explicit API-key router connections safely, clean up cancelled streams, and reject overlapping `previous_response_id` continuations on the same connection. - Fixed Azure and OpenAI Responses item handling so multi-step reasoning and tool-call histories round-trip correctly without item ID collisions. ([#16246](https://github.com/mastra-ai/mastra/pull/16246)) Added provider-neutral response item helpers to `@mastra/core/agent/message-list`. Existing in-memory message cache entries are regenerated after upgrade. ## 1.33.0-alpha.2 ### Minor Changes - Added `processLLMRequest`, a processor hook that runs after messages are converted to the provider-facing prompt and before the model request is sent. The hook lets processors make temporary, model-aware prompt changes without mutating stored message history, memory, UI history, or later provider calls. ([#16176](https://github.com/mastra-ai/mastra/pull/16176)) `ProviderHistoryCompat` now uses this hook to prevent reasoning-history incompatibilities when switching providers. It strips reasoning parts from Cerebras-bound prompts that would otherwise be sent as rejected `reasoning_content`, and strips non-Anthropic reasoning from Anthropic-bound prompts while preserving Anthropic-native thinking blocks. ### Patch Changes - Fixed a Harness issue where reopening a thread could apply the wrong model for ([#16278](https://github.com/mastra-ai/mastra/pull/16278)) the saved mode. Threads now reopen with the correct model for that mode, including when no explicit per-mode model was selected. ## 1.33.0-alpha.1 ### Patch Changes - Fixed an issue where trajectory and step scorer results from `runEvals` were not saved to storage. These scores now persist correctly and appear alongside agent and workflow scores in Studio's observability section. ([#16249](https://github.com/mastra-ai/mastra/pull/16249)) ## 1.33.0-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ac47842`](https://github.com/mastra-ai/mastra/commit/ac478427aa7a5f5fdaed633a911218689b438c60)) ## 1.32.1 ### Patch Changes - Added configurable response-based retry handling to `fetchWithRetry`. ([#16155](https://github.com/mastra-ai/mastra/pull/16155)) Developers can now pass `shouldRetryResponse` to control which non-OK HTTP responses should be retried while network failures continue to retry automatically. ```ts await fetchWithRetry(url, requestOptions, 3, { shouldRetryResponse: response => response.status === 429 || response.status >= 500, }); ``` ## 1.32.1-alpha.0 ### Patch Changes - Added configurable response-based retry handling to `fetchWithRetry`. ([#16155](https://github.com/mastra-ai/mastra/pull/16155)) Developers can now pass `shouldRetryResponse` to control which non-OK HTTP responses should be retried while network failures continue to retry automatically. ```ts await fetchWithRetry(url, requestOptions, 3, { shouldRetryResponse: response => response.status === 429 || response.status >= 500, }); ``` ## 1.32.0 ### Minor Changes - Added Fine-Grained Authorization (FGA) support for relationship-based, resource-level access control. FGA answers "can this user perform this action on this specific resource?" — enabling multi-tenant isolation and per-resource permissions. ([#15410](https://github.com/mastra-ai/mastra/pull/15410)) **New interfaces:** `IFGAProvider` (read-only checks) and `IFGAManager` (read + write operations) with types for access checks, resources, and role assignments. **Enforcement at all execution points:** FGA checks are automatically enforced before agent execution (`generate()`, `stream()`), tool execution, workflow execution, and memory thread access. When no FGA provider is configured, all checks are skipped (backward compatible). **New utility:** `checkFGA()` provides centralized FGA enforcement with `FGADeniedError` for denied checks. `MastraMemory.checkThreadFGA()` adds thread-level access control. **Request-aware authorization:** Resource ID resolvers receive request context so route-level FGA checks can derive tenant- or request-scoped resource IDs. **Typed permission constants:** Strongly-typed permission identifiers (e.g. `'agents:execute'`, `'workflows:execute'`, `'memory:threads:read'`) for use in authorization config and `permissionMapping`. ```typescript const mastra = new Mastra({ server: { fga: new MastraFGAWorkos({ apiKey, clientId }), }, }); ``` - Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to `mastra_schedules` and `mastra_schedule_triggers`; scheduled workflows are still in alpha so no compat shim is provided. ([#16166](https://github.com/mastra-ai/mastra/pull/16166)) - `Schedule` gains optional `ownerType` / `ownerId` so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset. - `ScheduleTrigger.status` is renamed to `outcome` and the type is widened to `ScheduleTriggerOutcome` so future outcome values can be added without another rename. - `ScheduleTrigger` gains a stable `id` primary key and new `triggerKind`, `parentTriggerId`, and `metadata` fields. `triggerKind` distinguishes `schedule-fire` rows from later `queue-drain` rows (used by upcoming heartbeat work); `parentTriggerId` links related rows; `metadata` carries outcome-specific context. - The libsql, pg, and mongodb adapters all add the new columns/indexes. Their `@mastra/core` peer dependency is tightened to `>=1.32.0-0 <2.0.0-0` so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field. - Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The `triggers` response on `GET /api/schedules/:id/triggers` now returns `outcome` instead of `status`. - The bundled Studio (Mastra CLI) is updated to read `outcome` so the schedule detail page keeps polling and rendering publish-failure rows correctly. - Added `listResources()` and `readResource()` abstract methods to `MCPServerBase`, enabling MCP servers to expose app resources. These resources power interactive UI rendering (MCP Apps) in Studio and other consumers. ([#16004](https://github.com/mastra-ai/mastra/pull/16004)) - Added `count_distinct` aggregation and server-side TopK to the metrics storage API so dashboards built on high-cardinality fields (like `threadId` or `resourceId`) stay fast and bounded. ([#16137](https://github.com/mastra-ai/mastra/pull/16137)) **New aggregation** `getMetricAggregate`, `getMetricBreakdown`, and `getMetricTimeSeries` accept `aggregation: 'count_distinct'` with a `distinctColumn`. Backends pick the most efficient native implementation — `uniq` on ClickHouse, `approx_count_distinct` on DuckDB. `distinctColumn` is restricted to a low/medium-cardinality categorical allowlist (`entityType`, `entityName`, `parentEntityType`, `parentEntityName`, `rootEntityType`, `rootEntityName`, `name`, `provider`, `model`, `environment`, `executionSource`, `serviceName`). ID columns are not allowed — distinct counts over near-unique values converge to the row count and are rarely useful. ```ts await store.getMetricAggregate({ name: ['mastra_llm_tokens_total'], aggregation: 'count_distinct', distinctColumn: 'model', filters: { timestamp: { start, end } }, }); ``` **Server-side TopK** `getMetricBreakdown` accepts `limit` and `orderDirection`, so breakdowns never return the full cardinality of a column from the database. Ordering is always by the aggregated `value`; `orderDirection` flips between top-N (`DESC`, default) and bottom-N (`ASC`). ```ts await store.getMetricBreakdown({ name: ['mastra_agent_duration_ms'], aggregation: 'sum', groupBy: ['threadId'], limit: 20, orderDirection: 'DESC', }); ``` - Added RegexFilterProcessor — a zero-cost regex-based content filter for blocking, redacting, or warning on pattern matches in agent messages. Includes built-in presets for PII, secrets, URLs, and prompt injection patterns. Supports input, output, and streaming phases. ([#16058](https://github.com/mastra-ai/mastra/pull/16058)) - Added scheduled workflows. Declare a `schedule` on `createWorkflow` and Mastra fires the workflow on cron with no extra wiring. ([#15830](https://github.com/mastra-ai/mastra/pull/15830)) ```typescript import { createWorkflow } from '@mastra/core/workflows'; const dailyReport = createWorkflow({ id: 'daily-report', inputSchema: z.object({ date: z.string() }), outputSchema: z.object({ summary: z.string() }), schedule: { cron: '0 9 * * *', timezone: 'America/Los_Angeles', inputData: { date: 'today' }, }, }) .then(/* steps */) .commit(); ``` A workflow with a `schedule` is auto-promoted to the evented engine, so scheduled fires share the same execution path as manual `start()` calls. `inputData`, `initialState`, and `requestContext` on the schedule are type-checked against the workflow's schemas at definition time. Pass an array of schedules with stable `id`s to fire one workflow on multiple crons. Mastra auto-instantiates a `WorkflowScheduler` when any registered workflow declares a `schedule`. The scheduler claims due schedules via compare-and-swap, so multiple instances polling the same storage cannot double-fire. Projects with no scheduled workflows pay zero cost. Configure with `new Mastra({ scheduler: { tickIntervalMs, batchSize, enabled, onError } })`. Requires a storage adapter that implements the new `schedules` domain (`@mastra/libsql` and `@mastra/pg` ship adapters; `InMemorySchedulesStorage` is included for tests). Adds a `croner` dependency. - Added MCP Apps extension support (SEP-1865). MCPServer now accepts an `appResources` config to register interactive `ui://` HTML resources. MCPClient preserves full tool `_meta` (including `ui.resourceUri`) when converting MCP tools to Mastra tools. Both advertise the `io.modelcontextprotocol/ui` extension capability. ([#16004](https://github.com/mastra-ai/mastra/pull/16004)) **Example — MCPServer with app resources:** ```typescript const server = new MCPServer({ name: 'my-server', tools: { myTool }, appResources: { dashboard: { name: 'Dashboard', description: 'Interactive dashboard UI', html: '...', }, }, }); ``` - Added `CostGuardProcessor`, a built-in processor for enforcing monetary cost limits across agent runs. Supports run, resource, and thread scopes with configurable time windows (default 7 days), blocking or warning when limits are reached. Also added `onViolation` callback to the base `Processor` interface for generalized violation handling across all processors. ([#16057](https://github.com/mastra-ai/mastra/pull/16057)) ```typescript import { Agent } from '@mastra/core/agent'; import { CostGuardProcessor } from '@mastra/core/processors'; const costGuard = new CostGuardProcessor({ maxCost: 5.0, scope: 'resource', window: '24h', strategy: 'block', }); costGuard.onViolation = ({ processorId, message, detail }) => { console.log(`[${processorId}] ${message}`, detail); }; const agent = new Agent({ name: 'my-agent', model: 'openai/gpt-5-nano', inputProcessors: [costGuard], }); ``` - **Added** `listBranches` and `getBranch` for querying named-entity invocations across traces, including nested ones. `listTraces` only returns root-rooted traces, so an entity that always runs as a child (e.g., an `Observer` agent inside a workflow) wasn't queryable before. ([#16154](https://github.com/mastra-ai/mastra/pull/16154)) ```ts // Before: nested-only entities returned nothing await store.listTraces({ filters: { entityName: 'Observer' } }); // [] // After: one row per AGENT_RUN, WORKFLOW_RUN, PROCESSOR_RUN, SCORER_RUN, // RAG_INGESTION, TOOL_CALL, or MCP_TOOL_CALL span await store.listBranches({ filters: { entityName: 'Observer' } }); // Plus: fetch the subtree at any span, with optional depth const branch = await store.getBranch({ traceId, spanId, depth: 1 }); ``` **Added** `getStructure({ traceId })` (canonical name for the lightweight trace skeleton; `getTraceLight` retained as a deprecated alias) and `getSpans({ traceId, spanIds })` (batch-fetch spans by id, used internally by `getBranch` to avoid pulling whole traces). ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`6dcd65f`](https://github.com/mastra-ai/mastra/commit/6dcd65f2a34069e6dc43ba35f1d11119b9b40bef)) - Fixed Harness token usage so provider-reported totals, reasoning tokens, and cache token fields are preserved. Fixes https://github.com/mastra-ai/mastra/issues/16055 ([#16072](https://github.com/mastra-ai/mastra/pull/16072)) - Fixed supervisor output processors so they can filter streamed chunks from delegated sub-agents. ([#16071](https://github.com/mastra-ai/mastra/pull/16071)) - Fixed Observational Memory model resolution for user-defined gateways. Models such as `cloudflare/google/gemini-2.5-flash-lite` now resolve through registered gateways instead of failing with provider-config errors. Closes #13841. ([#16083](https://github.com/mastra-ai/mastra/pull/16083)) - Fixed model step traces to show the final prompt sent to the model, including memory-injected system messages. ([#16029](https://github.com/mastra-ai/mastra/pull/16029)) - Fixed tool results dropping provider metadata from the original tool call. ([#16078](https://github.com/mastra-ai/mastra/pull/16078)) - Fixed workflow request context serialization to skip values that cannot be safely stored as JSON. Fixes #16043. ([#16061](https://github.com/mastra-ai/mastra/pull/16061)) - Fix buildResumedBlockResult returning suspended instead of failed when a step throws after resume in a parallel or conditional block ([#14410](https://github.com/mastra-ai/mastra/pull/14410)) - Fixed serializeRequestContext to handle plain Map instances passed as requestContext, restoring backward compatibility broken in #16061 ([#16081](https://github.com/mastra-ai/mastra/pull/16081)) - Added direct score lookup support to observability storage so score records can be fetched by `scoreId` without scanning paginated score lists, including DuckDB and ClickHouse vNext observability stores. ([#16162](https://github.com/mastra-ai/mastra/pull/16162)) - fix(harness): use type 'image' and mimeType for image parts in convertToHarnessMessage to fix Gemini image recognition ([#13917](https://github.com/mastra-ai/mastra/pull/13917)) - Fixed TokenLimiterProcessor failing silently when no input messages fit the token budget. ([#16063](https://github.com/mastra-ai/mastra/pull/16063)) ## 1.32.0-alpha.4 ### Minor Changes - Added `listResources()` and `readResource()` abstract methods to `MCPServerBase`, enabling MCP servers to expose app resources. These resources power interactive UI rendering (MCP Apps) in Studio and other consumers. ([#16004](https://github.com/mastra-ai/mastra/pull/16004)) - Added MCP Apps extension support (SEP-1865). MCPServer now accepts an `appResources` config to register interactive `ui://` HTML resources. MCPClient preserves full tool `_meta` (including `ui.resourceUri`) when converting MCP tools to Mastra tools. Both advertise the `io.modelcontextprotocol/ui` extension capability. ([#16004](https://github.com/mastra-ai/mastra/pull/16004)) **Example — MCPServer with app resources:** ```typescript const server = new MCPServer({ name: 'my-server', tools: { myTool }, appResources: { dashboard: { name: 'Dashboard', description: 'Interactive dashboard UI', html: '...', }, }, }); ``` ### Patch Changes - Fixed Observational Memory model resolution for user-defined gateways. Models such as `cloudflare/google/gemini-2.5-flash-lite` now resolve through registered gateways instead of failing with provider-config errors. Closes #13841. ([#16083](https://github.com/mastra-ai/mastra/pull/16083)) - Fix buildResumedBlockResult returning suspended instead of failed when a step throws after resume in a parallel or conditional block ([#14410](https://github.com/mastra-ai/mastra/pull/14410)) ## 1.32.0-alpha.3 ### Minor Changes - Extend the schedules storage schema to support owned schedules and richer trigger audit. This is a breaking schema change to `mastra_schedules` and `mastra_schedule_triggers`; scheduled workflows are still in alpha so no compat shim is provided. ([#16166](https://github.com/mastra-ai/mastra/pull/16166)) - `Schedule` gains optional `ownerType` / `ownerId` so a schedule row can be attributed to an owning subsystem (e.g. an agent that owns a heartbeat schedule). Workflow schedules leave both fields unset. - `ScheduleTrigger.status` is renamed to `outcome` and the type is widened to `ScheduleTriggerOutcome` so future outcome values can be added without another rename. - `ScheduleTrigger` gains a stable `id` primary key and new `triggerKind`, `parentTriggerId`, and `metadata` fields. `triggerKind` distinguishes `schedule-fire` rows from later `queue-drain` rows (used by upcoming heartbeat work); `parentTriggerId` links related rows; `metadata` carries outcome-specific context. - The libsql, pg, and mongodb adapters all add the new columns/indexes. Their `@mastra/core` peer dependency is tightened to `>=1.32.0-0 <2.0.0-0` so installing a new storage adapter against an older core (or vice-versa) surfaces a peer-dependency warning at install time instead of silently writing/reading the wrong field. - Scheduler producer, server schemas/handler, and client SDK types are updated to use the new fields. The `triggers` response on `GET /api/schedules/:id/triggers` now returns `outcome` instead of `status`. - The bundled Studio (Mastra CLI) is updated to read `outcome` so the schedule detail page keeps polling and rendering publish-failure rows correctly. ## 1.32.0-alpha.2 ### Minor Changes - Added Fine-Grained Authorization (FGA) support for relationship-based, resource-level access control. FGA answers "can this user perform this action on this specific resource?" — enabling multi-tenant isolation and per-resource permissions. ([#15410](https://github.com/mastra-ai/mastra/pull/15410)) **New interfaces:** `IFGAProvider` (read-only checks) and `IFGAManager` (read + write operations) with types for access checks, resources, and role assignments. **Enforcement at all execution points:** FGA checks are automatically enforced before agent execution (`generate()`, `stream()`), tool execution, workflow execution, and memory thread access. When no FGA provider is configured, all checks are skipped (backward compatible). **New utility:** `checkFGA()` provides centralized FGA enforcement with `FGADeniedError` for denied checks. `MastraMemory.checkThreadFGA()` adds thread-level access control. **Request-aware authorization:** Resource ID resolvers receive request context so route-level FGA checks can derive tenant- or request-scoped resource IDs. **Typed permission constants:** Strongly-typed permission identifiers (e.g. `'agents:execute'`, `'workflows:execute'`, `'memory:threads:read'`) for use in authorization config and `permissionMapping`. ```typescript const mastra = new Mastra({ server: { fga: new MastraFGAWorkos({ apiKey, clientId }), }, }); ``` - Added `count_distinct` aggregation and server-side TopK to the metrics storage API so dashboards built on high-cardinality fields (like `threadId` or `resourceId`) stay fast and bounded. ([#16137](https://github.com/mastra-ai/mastra/pull/16137)) **New aggregation** `getMetricAggregate`, `getMetricBreakdown`, and `getMetricTimeSeries` accept `aggregation: 'count_distinct'` with a `distinctColumn`. Backends pick the most efficient native implementation — `uniq` on ClickHouse, `approx_count_distinct` on DuckDB. `distinctColumn` is restricted to a low/medium-cardinality categorical allowlist (`entityType`, `entityName`, `parentEntityType`, `parentEntityName`, `rootEntityType`, `rootEntityName`, `name`, `provider`, `model`, `environment`, `executionSource`, `serviceName`). ID columns are not allowed — distinct counts over near-unique values converge to the row count and are rarely useful. ```ts await store.getMetricAggregate({ name: ['mastra_llm_tokens_total'], aggregation: 'count_distinct', distinctColumn: 'model', filters: { timestamp: { start, end } }, }); ``` **Server-side TopK** `getMetricBreakdown` accepts `limit` and `orderDirection`, so breakdowns never return the full cardinality of a column from the database. Ordering is always by the aggregated `value`; `orderDirection` flips between top-N (`DESC`, default) and bottom-N (`ASC`). ```ts await store.getMetricBreakdown({ name: ['mastra_agent_duration_ms'], aggregation: 'sum', groupBy: ['threadId'], limit: 20, orderDirection: 'DESC', }); ``` - Added RegexFilterProcessor — a zero-cost regex-based content filter for blocking, redacting, or warning on pattern matches in agent messages. Includes built-in presets for PII, secrets, URLs, and prompt injection patterns. Supports input, output, and streaming phases. ([#16058](https://github.com/mastra-ai/mastra/pull/16058)) - Added `CostGuardProcessor`, a built-in processor for enforcing monetary cost limits across agent runs. Supports run, resource, and thread scopes with configurable time windows (default 7 days), blocking or warning when limits are reached. Also added `onViolation` callback to the base `Processor` interface for generalized violation handling across all processors. ([#16057](https://github.com/mastra-ai/mastra/pull/16057)) ```typescript import { Agent } from '@mastra/core/agent'; import { CostGuardProcessor } from '@mastra/core/processors'; const costGuard = new CostGuardProcessor({ maxCost: 5.0, scope: 'resource', window: '24h', strategy: 'block', }); costGuard.onViolation = ({ processorId, message, detail }) => { console.log(`[${processorId}] ${message}`, detail); }; const agent = new Agent({ name: 'my-agent', model: 'openai/gpt-5-nano', inputProcessors: [costGuard], }); ``` - **Added** `listBranches` and `getBranch` for querying named-entity invocations across traces, including nested ones. `listTraces` only returns root-rooted traces, so an entity that always runs as a child (e.g., an `Observer` agent inside a workflow) wasn't queryable before. ([#16154](https://github.com/mastra-ai/mastra/pull/16154)) ```ts // Before: nested-only entities returned nothing await store.listTraces({ filters: { entityName: 'Observer' } }); // [] // After: one row per AGENT_RUN, WORKFLOW_RUN, PROCESSOR_RUN, SCORER_RUN, // RAG_INGESTION, TOOL_CALL, or MCP_TOOL_CALL span await store.listBranches({ filters: { entityName: 'Observer' } }); // Plus: fetch the subtree at any span, with optional depth const branch = await store.getBranch({ traceId, spanId, depth: 1 }); ``` **Added** `getStructure({ traceId })` (canonical name for the lightweight trace skeleton; `getTraceLight` retained as a deprecated alias) and `getSpans({ traceId, spanIds })` (batch-fetch spans by id, used internally by `getBranch` to avoid pulling whole traces). ### Patch Changes - Added direct score lookup support to observability storage so score records can be fetched by `scoreId` without scanning paginated score lists, including DuckDB and ClickHouse vNext observability stores. ([#16162](https://github.com/mastra-ai/mastra/pull/16162)) ## 1.32.0-alpha.1 ### Minor Changes - Added scheduled workflows. Declare a `schedule` on `createWorkflow` and Mastra fires the workflow on cron with no extra wiring. ([#15830](https://github.com/mastra-ai/mastra/pull/15830)) ```typescript import { createWorkflow } from '@mastra/core/workflows'; const dailyReport = createWorkflow({ id: 'daily-report', inputSchema: z.object({ date: z.string() }), outputSchema: z.object({ summary: z.string() }), schedule: { cron: '0 9 * * *', timezone: 'America/Los_Angeles', inputData: { date: 'today' }, }, }) .then(/* steps */) .commit(); ``` A workflow with a `schedule` is auto-promoted to the evented engine, so scheduled fires share the same execution path as manual `start()` calls. `inputData`, `initialState`, and `requestContext` on the schedule are type-checked against the workflow's schemas at definition time. Pass an array of schedules with stable `id`s to fire one workflow on multiple crons. Mastra auto-instantiates a `WorkflowScheduler` when any registered workflow declares a `schedule`. The scheduler claims due schedules via compare-and-swap, so multiple instances polling the same storage cannot double-fire. Projects with no scheduled workflows pay zero cost. Configure with `new Mastra({ scheduler: { tickIntervalMs, batchSize, enabled, onError } })`. Requires a storage adapter that implements the new `schedules` domain (`@mastra/libsql` and `@mastra/pg` ship adapters; `InMemorySchedulesStorage` is included for tests). Adds a `croner` dependency. ### Patch Changes - Fixed Harness token usage so provider-reported totals, reasoning tokens, and cache token fields are preserved. Fixes https://github.com/mastra-ai/mastra/issues/16055 ([#16072](https://github.com/mastra-ai/mastra/pull/16072)) - Fixed supervisor output processors so they can filter streamed chunks from delegated sub-agents. ([#16071](https://github.com/mastra-ai/mastra/pull/16071)) - Fixed tool results dropping provider metadata from the original tool call. ([#16078](https://github.com/mastra-ai/mastra/pull/16078)) - Fixed workflow request context serialization to skip values that cannot be safely stored as JSON. Fixes #16043. ([#16061](https://github.com/mastra-ai/mastra/pull/16061)) - Fixed serializeRequestContext to handle plain Map instances passed as requestContext, restoring backward compatibility broken in #16061 ([#16081](https://github.com/mastra-ai/mastra/pull/16081)) - Fixed TokenLimiterProcessor failing silently when no input messages fit the token budget. ([#16063](https://github.com/mastra-ai/mastra/pull/16063)) ## 1.31.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`6dcd65f`](https://github.com/mastra-ai/mastra/commit/6dcd65f2a34069e6dc43ba35f1d11119b9b40bef)) - Fixed model step traces to show the final prompt sent to the model, including memory-injected system messages. ([#16029](https://github.com/mastra-ai/mastra/pull/16029)) ## 1.31.0 ### Minor Changes - Enhanced load_tool to accept an array of tool names, enabling bulk tool loading in a single call. Returns 'loaded', 'notFound', and 'alreadyLoaded' arrays for clearer response shape. ([#15472](https://github.com/mastra-ai/mastra/pull/15472)) - Added Microsoft Entra ID authentication support for Azure OpenAI gateways, so Azure deployments can call models without API keys when using Azure SDK credentials. ([#15983](https://github.com/mastra-ai/mastra/pull/15983)) - Added platform channels framework with ChannelProvider interface, ChannelsStorage domain, and ChannelConnectResult discriminated union supporting OAuth, deep link, and immediate connection flows. Channels can be registered on the Mastra instance and expose connect/disconnect/list APIs for platform integrations. ([#15876](https://github.com/mastra-ai/mastra/pull/15876)) - Added top-level `environment` config on `Mastra` to tag observability signals with the deployment environment. ([#15956](https://github.com/mastra-ai/mastra/pull/15956)) Set it once on the `Mastra` instance and it will be attached to all observability signals automatically. Falls back to `process.env.NODE_ENV` when unset; per-call `tracingOptions.metadata.environment` still takes precedence. **Before** ```ts await agent.generate('hello', { tracingOptions: { metadata: { environment: process.env.NODE_ENV } }, }); ``` **After** ```ts new Mastra({ environment: 'production', observability: new Observability({ ... }), }) ``` `mastra.getEnvironment()` returns the resolved value. - Fixed trajectory scorers in dataset.startExperiment receiving raw agent messages instead of a Trajectory object, which caused a crash when accessing run.output.steps. Trajectory scorers now receive the same pre-extracted Trajectory that runEvals provides. ([#15693](https://github.com/mastra-ai/mastra/pull/15693)) The scorers option now also accepts the same categorised shape as runEvals (AgentScorerConfig / WorkflowScorerConfig), so you no longer need to rewrite your scorer config when moving from runEvals to dataset.startExperiment. **Before (trajectory scorer crashed at runtime):** await dataset.startExperiment({ scorers: [orderScorer] }) // run.output.steps was undefined **After (works correctly, both flat and categorised forms accepted):** await dataset.startExperiment({ scorers: [orderScorer] }) await dataset.startExperiment({ scorers: { agent: [accuracyScorer], trajectory: [orderScorer] } }) Per-step scorers are now also supported for workflow targets, matching `runEvals`. Pass `scorers: { workflow: [...], steps: { stepId: [...] }, trajectory: [...] }` to score individual workflow steps with their own scorers; results carry the originating `stepId` and keep `targetScope: 'span'` (with `targetEntityType: WORKFLOW_STEP` on the underlying scorer run), matching how `runEvals` encodes step identity. - Workspace search now supports batch-capable embedders. Pass an embedder branded with `batch: true` (and an optional `maxBatchSize`) to embed all pending chunks for a flush in a single provider call instead of one call per chunk. This dramatically reduces index-rebuild time on large workspaces when using providers that support batch embedding (e.g. OpenAI's `embedMany`). Existing single-text embedders continue to work unchanged. ([#14735](https://github.com/mastra-ai/mastra/pull/14735)) ```ts import { embedMany } from 'ai'; import { openai } from '@ai-sdk/openai'; const model = openai.embedding('text-embedding-3-small'); const workspace = new Workspace({ // ... embedder: Object.assign( async (texts: string[]) => { const { embeddings } = await embedMany({ model, values: texts }); return embeddings; }, { batch: true as const, maxBatchSize: 2048 }, ), }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`1723e09`](https://github.com/mastra-ai/mastra/commit/1723e099829892419ddbfe49287acfeac2522724)) - Fixed workflow runs not being cancellable when steps or conditions ignored the abort signal. Cancelling a run now correctly stops `dountil`, `dowhile`, and `foreach` loops at every cancellation boundary — between iterations, after a step returns, after the loop condition is evaluated, and (for `foreach`) between concurrency chunks and after the final chunk. Previously, long-running loops (e.g. a `dountil` with a `setTimeout` inside the step) would keep running and eventually emit `success` even after the run was cancelled. Closes #15990. ([#15994](https://github.com/mastra-ai/mastra/pull/15994)) - Fixed type inference on workflow loop helpers (`foreach`, `dowhile`, `dountil`) so a step's `requestContextSchema` correctly aligns with the workflow's `requestContextSchema`. Previously these methods dropped the workflow's `TRequestContext` from the step parameter, causing TypeScript to reject typed-context steps even when the workflow declared a matching schema. Steps without a `requestContextSchema` are still accepted; steps whose schema does not match the workflow's now produce a type error. Fixes [#15989](https://github.com/mastra-ai/mastra/issues/15989). ([#15995](https://github.com/mastra-ai/mastra/pull/15995)) - Fixed sub-agent delegation so nested tool results stay out of the parent model context by default while remaining available to application code. Set `delegation.includeSubAgentToolResultsInModelContext` to include the full subagent result in the parent model context. ([#15832](https://github.com/mastra-ai/mastra/pull/15832)) - Added a coalesced display state subscription API for Harness. ([#15974](https://github.com/mastra-ai/mastra/pull/15974)) This helps UI clients render fewer updates while still receiving the latest state. The example below renders the initial state, then subscribes to coalesced updates with the default `windowMs` and `maxWaitMs` timing options. ```ts render(harness.getDisplayState()); const unsubscribe = harness.subscribeDisplayState(render, { windowMs: 250, maxWaitMs: 500, }); ``` - Fixed BatchPartsProcessor using a hardcoded id in batched text-delta chunks. The real message id and runId are now preserved from the original chunks, preventing AI SDK UIMessage stream from dropping batched deltas. ([#14974](https://github.com/mastra-ai/mastra/pull/14974)) - Add `filterAfterToolSteps` to `ToolCallFilter` so tool calls can be filtered during agentic loops after they are no longer recent. By default, `ToolCallFilter` keeps its previous behavior and only filters the initial input. ([#15795](https://github.com/mastra-ai/mastra/pull/15795)) - Workspace search no longer throws when requesting hybrid or vector mode if the configuration does not support it. The search tool now gracefully falls back to the best available mode instead of throwing an error. ([#14533](https://github.com/mastra-ai/mastra/pull/14533)) - Workspace file tools no longer use misleading absolute-path examples (e.g. `/data/output.txt`) that caused weaker LLMs to attempt writes at the actual filesystem root. The example paths in `read_file` and `write_file` are now relative. ([#14544](https://github.com/mastra-ai/mastra/pull/14544)) Additionally, when a contained workspace rejects an absolute path that escapes its boundary, the resulting `PermissionError` now guides the agent toward a relative path so it can self-correct on the next turn. When the path's first segment names a real directory in the workspace (e.g. `/src/app.ts` with an existing `src/`), the error suggests the exact relative form. Otherwise it falls back to a generic hint instead of inventing a misleading suggestion for genuinely out-of-workspace paths like `/etc/passwd`. Fixes #14542 - Fixed `SkillSearchProcessor` so agents use it as the on-demand skill discovery path without also adding eager skill context. ([#15916](https://github.com/mastra-ai/mastra/pull/15916)) When `SkillSearchProcessor` is configured, agents no longer auto-add the eager `SkillsProcessor`, and they hide the overlapping `skill` and `skill_search` tools while keeping `skill_read` available for supporting skill files. Workspace file tools can still read `SKILL.md` files during explicit file inspection or editing workflows. - Fixed tool calls to run in parallel when active tools exclude approval or suspending tools. ([#15978](https://github.com/mastra-ai/mastra/pull/15978)) - - **SearchEngine**: `indexMany` uses `p-map` with a default concurrency of 8 when vector embedding runs, with optional `concurrency` and `stopOnError` (same semantics as `p-map`). Lazy vector indexing flushes pending documents at the same concurrency, drains the queue before awaiting so concurrent `index` calls are not dropped, loops until the queue is empty before search, dedupes by document id (last wins), and re-queues the batch if a flush throws. ([#14735](https://github.com/mastra-ai/mastra/pull/14735)) - **Workspace**: Search auto-indexing reads files in parallel with a bounded concurrency, skips unreadable paths, awaits batch indexing, and falls back to per-file indexing when the batch path throws. Successful single-file indexing returns the path so callers can track what was indexed. - Fix semantic recall indexing to honor read-only memory mode. ([#15949](https://github.com/mastra-ai/mastra/pull/15949)) - Fixed Linux bubblewrap failing when Workspace mounts use symlinks under LocalSandbox by resolving mount paths to real directories for isolation allowlists. ([#15498](https://github.com/mastra-ai/mastra/pull/15498)) ## 1.31.0-alpha.5 ## 1.31.0-alpha.4 ### Minor Changes - Fixed trajectory scorers in dataset.startExperiment receiving raw agent messages instead of a Trajectory object, which caused a crash when accessing run.output.steps. Trajectory scorers now receive the same pre-extracted Trajectory that runEvals provides. ([#15693](https://github.com/mastra-ai/mastra/pull/15693)) The scorers option now also accepts the same categorised shape as runEvals (AgentScorerConfig / WorkflowScorerConfig), so you no longer need to rewrite your scorer config when moving from runEvals to dataset.startExperiment. **Before (trajectory scorer crashed at runtime):** await dataset.startExperiment({ scorers: [orderScorer] }) // run.output.steps was undefined **After (works correctly, both flat and categorised forms accepted):** await dataset.startExperiment({ scorers: [orderScorer] }) await dataset.startExperiment({ scorers: { agent: [accuracyScorer], trajectory: [orderScorer] } }) Per-step scorers are now also supported for workflow targets, matching `runEvals`. Pass `scorers: { workflow: [...], steps: { stepId: [...] }, trajectory: [...] }` to score individual workflow steps with their own scorers; results carry the originating `stepId` and keep `targetScope: 'span'` (with `targetEntityType: WORKFLOW_STEP` on the underlying scorer run), matching how `runEvals` encodes step identity. - Workspace search now supports batch-capable embedders. Pass an embedder branded with `batch: true` (and an optional `maxBatchSize`) to embed all pending chunks for a flush in a single provider call instead of one call per chunk. This dramatically reduces index-rebuild time on large workspaces when using providers that support batch embedding (e.g. OpenAI's `embedMany`). Existing single-text embedders continue to work unchanged. ([#14735](https://github.com/mastra-ai/mastra/pull/14735)) ```ts import { embedMany } from 'ai'; import { openai } from '@ai-sdk/openai'; const model = openai.embedding('text-embedding-3-small'); const workspace = new Workspace({ // ... embedder: Object.assign( async (texts: string[]) => { const { embeddings } = await embedMany({ model, values: texts }); return embeddings; }, { batch: true as const, maxBatchSize: 2048 }, ), }); ``` ### Patch Changes - - **SearchEngine**: `indexMany` uses `p-map` with a default concurrency of 8 when vector embedding runs, with optional `concurrency` and `stopOnError` (same semantics as `p-map`). Lazy vector indexing flushes pending documents at the same concurrency, drains the queue before awaiting so concurrent `index` calls are not dropped, loops until the queue is empty before search, dedupes by document id (last wins), and re-queues the batch if a flush throws. ([#14735](https://github.com/mastra-ai/mastra/pull/14735)) - **Workspace**: Search auto-indexing reads files in parallel with a bounded concurrency, skips unreadable paths, awaits batch indexing, and falls back to per-file indexing when the batch path throws. Successful single-file indexing returns the path so callers can track what was indexed. ## 1.31.0-alpha.3 ### Minor Changes - Enhanced load_tool to accept an array of tool names, enabling bulk tool loading in a single call. Returns 'loaded', 'notFound', and 'alreadyLoaded' arrays for clearer response shape. ([#15472](https://github.com/mastra-ai/mastra/pull/15472)) - Added platform channels framework with ChannelProvider interface, ChannelsStorage domain, and ChannelConnectResult discriminated union supporting OAuth, deep link, and immediate connection flows. Channels can be registered on the Mastra instance and expose connect/disconnect/list APIs for platform integrations. ([#15876](https://github.com/mastra-ai/mastra/pull/15876)) ### Patch Changes - Workspace search no longer throws when requesting hybrid or vector mode if the configuration does not support it. The search tool now gracefully falls back to the best available mode instead of throwing an error. ([#14533](https://github.com/mastra-ai/mastra/pull/14533)) - Workspace file tools no longer use misleading absolute-path examples (e.g. `/data/output.txt`) that caused weaker LLMs to attempt writes at the actual filesystem root. The example paths in `read_file` and `write_file` are now relative. ([#14544](https://github.com/mastra-ai/mastra/pull/14544)) Additionally, when a contained workspace rejects an absolute path that escapes its boundary, the resulting `PermissionError` now guides the agent toward a relative path so it can self-correct on the next turn. When the path's first segment names a real directory in the workspace (e.g. `/src/app.ts` with an existing `src/`), the error suggests the exact relative form. Otherwise it falls back to a generic hint instead of inventing a misleading suggestion for genuinely out-of-workspace paths like `/etc/passwd`. Fixes #14542 - Fixed Linux bubblewrap failing when Workspace mounts use symlinks under LocalSandbox by resolving mount paths to real directories for isolation allowlists. ([#15498](https://github.com/mastra-ai/mastra/pull/15498)) ## 1.31.0-alpha.2 ### Patch Changes - Fixed BatchPartsProcessor using a hardcoded id in batched text-delta chunks. The real message id and runId are now preserved from the original chunks, preventing AI SDK UIMessage stream from dropping batched deltas. ([#14974](https://github.com/mastra-ai/mastra/pull/14974)) ## 1.31.0-alpha.1 ### Patch Changes - Fixed `SkillSearchProcessor` so agents use it as the on-demand skill discovery path without also adding eager skill context. ([#15916](https://github.com/mastra-ai/mastra/pull/15916)) When `SkillSearchProcessor` is configured, agents no longer auto-add the eager `SkillsProcessor`, and they hide the overlapping `skill` and `skill_search` tools while keeping `skill_read` available for supporting skill files. Workspace file tools can still read `SKILL.md` files during explicit file inspection or editing workflows. ## 1.31.0-alpha.0 ### Minor Changes - Added Microsoft Entra ID authentication support for Azure OpenAI gateways, so Azure deployments can call models without API keys when using Azure SDK credentials. ([#15983](https://github.com/mastra-ai/mastra/pull/15983)) - Added top-level `environment` config on `Mastra` to tag observability signals with the deployment environment. ([#15956](https://github.com/mastra-ai/mastra/pull/15956)) Set it once on the `Mastra` instance and it will be attached to all observability signals automatically. Falls back to `process.env.NODE_ENV` when unset; per-call `tracingOptions.metadata.environment` still takes precedence. **Before** ```ts await agent.generate('hello', { tracingOptions: { metadata: { environment: process.env.NODE_ENV } }, }); ``` **After** ```ts new Mastra({ environment: 'production', observability: new Observability({ ... }), }) ``` `mastra.getEnvironment()` returns the resolved value. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`1723e09`](https://github.com/mastra-ai/mastra/commit/1723e099829892419ddbfe49287acfeac2522724)) - Fixed workflow runs not being cancellable when steps or conditions ignored the abort signal. Cancelling a run now correctly stops `dountil`, `dowhile`, and `foreach` loops at every cancellation boundary — between iterations, after a step returns, after the loop condition is evaluated, and (for `foreach`) between concurrency chunks and after the final chunk. Previously, long-running loops (e.g. a `dountil` with a `setTimeout` inside the step) would keep running and eventually emit `success` even after the run was cancelled. Closes #15990. ([#15994](https://github.com/mastra-ai/mastra/pull/15994)) - Fixed type inference on workflow loop helpers (`foreach`, `dowhile`, `dountil`) so a step's `requestContextSchema` correctly aligns with the workflow's `requestContextSchema`. Previously these methods dropped the workflow's `TRequestContext` from the step parameter, causing TypeScript to reject typed-context steps even when the workflow declared a matching schema. Steps without a `requestContextSchema` are still accepted; steps whose schema does not match the workflow's now produce a type error. Fixes [#15989](https://github.com/mastra-ai/mastra/issues/15989). ([#15995](https://github.com/mastra-ai/mastra/pull/15995)) - Fixed sub-agent delegation so nested tool results stay out of the parent model context by default while remaining available to application code. Set `delegation.includeSubAgentToolResultsInModelContext` to include the full subagent result in the parent model context. ([#15832](https://github.com/mastra-ai/mastra/pull/15832)) - Added a coalesced display state subscription API for Harness. ([#15974](https://github.com/mastra-ai/mastra/pull/15974)) This helps UI clients render fewer updates while still receiving the latest state. The example below renders the initial state, then subscribes to coalesced updates with the default `windowMs` and `maxWaitMs` timing options. ```ts render(harness.getDisplayState()); const unsubscribe = harness.subscribeDisplayState(render, { windowMs: 250, maxWaitMs: 500, }); ``` - Add `filterAfterToolSteps` to `ToolCallFilter` so tool calls can be filtered during agentic loops after they are no longer recent. By default, `ToolCallFilter` keeps its previous behavior and only filters the initial input. ([#15795](https://github.com/mastra-ai/mastra/pull/15795)) - Fixed tool calls to run in parallel when active tools exclude approval or suspending tools. ([#15978](https://github.com/mastra-ai/mastra/pull/15978)) - Fix semantic recall indexing to honor read-only memory mode. ([#15949](https://github.com/mastra-ai/mastra/pull/15949)) ## 1.30.0 ### Minor Changes - Add durable agents with resumable streams ([#12557](https://github.com/mastra-ai/mastra/pull/12557)) Durable agents make agent execution resilient to disconnections, crashes, and long-running operations. ### The Problem Standard agent streaming has two fragility points: 1. **Connection drops** - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed. 2. **Long-running operations** - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost. ### The Solution **Resumable streams** solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive. **Durable execution** solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress. ### Usage Wrap any existing `Agent` with durability using factory functions: ```typescript import { Agent } from '@mastra/core/agent'; import { createDurableAgent } from '@mastra/core/agent/durable'; const agent = new Agent({ id: 'my-agent', model: openai('gpt-4'), instructions: 'You are helpful', }); const durableAgent = createDurableAgent({ agent }); ``` **Factory functions for different execution strategies:** | Factory | Execution | Use Case | | ---------------------------------------- | ----------------------------------- | ------------------------------- | | `createDurableAgent({ agent })` | Local, synchronous | Development, simple deployments | | `createEventedAgent({ agent })` | Fire-and-forget via workflow engine | Long-running operations | | `createInngestAgent({ agent, inngest })` | Inngest-powered | Production, distributed systems | ### Resumable Streams ```typescript // Start streaming const { runId, output } = await durableAgent.stream('Analyze this data...'); // Client disconnects at event 5... // Reconnect and resume from where we left off const { output: resumed } = await durableAgent.observe(runId, { offset: 6 }); // Receives events 6, 7, 8... from cache, then continues with live events ``` ### PubSub and Cache Durable agents use two infrastructure components: | Component | Purpose | Default | | ---------- | ----------------------------------------- | --------------------- | | **PubSub** | Real-time event delivery during streaming | `EventEmitterPubSub` | | **Cache** | Stores events for replay on reconnection | `InMemoryServerCache` | When `stream()` is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When `observe()` is called, missed events replay from cache before continuing with live events. **Configure via Mastra instance (recommended):** ```typescript const mastra = new Mastra({ cache: new RedisServerCache({ url: 'redis://...' }), pubsub: new RedisPubSub({ url: 'redis://...' }), agents: { // Inherits cache and pubsub from Mastra myAgent: createDurableAgent({ agent }), }, }); ``` **Configure per-agent (overrides Mastra):** ```typescript const durableAgent = createDurableAgent({ agent, cache: new RedisServerCache({ url: 'redis://...' }), pubsub: new RedisPubSub({ url: 'redis://...' }), }); ``` **Disable caching (streams won't be resumable):** ```typescript const durableAgent = createDurableAgent({ agent, cache: false }); ``` For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests. ### Class Hierarchy - `DurableAgent` extends `Agent` - base class with resumable streams - `EventedAgent` extends `DurableAgent` - fire-and-forget execution - `InngestAgent` extends `DurableAgent` - Inngest-powered execution ### Patch Changes - Fixed a regression in 1.29.0 where configuring an agent with channel adapters (e.g. `channels.adapters.slack`) caused server startup to crash with a "Custom API route ... must not start with /api" error. The custom-route prefix validation now skips framework-generated webhook routes. ([#15952](https://github.com/mastra-ai/mastra/pull/15952)) - Update provider registry and model documentation with latest models and providers ([`d587199`](https://github.com/mastra-ai/mastra/commit/d5871993c0371bde2b0717d6b47194755baa1443)) - Fix MCP client support in the agent editor: ([#15945](https://github.com/mastra-ai/mastra/pull/15945)) - MCP client form dirty state: Save button now enables after adding/removing MCP clients - MCP tool name matching: Both bare and namespaced tool names are matched correctly - Auth token forwarding: Token from cookie or header is forwarded to auth-protected MCP servers - String interpolation: Request context variables in system prompts now resolve correctly ## 1.30.0-alpha.1 ### Minor Changes - Add durable agents with resumable streams ([#12557](https://github.com/mastra-ai/mastra/pull/12557)) Durable agents make agent execution resilient to disconnections, crashes, and long-running operations. ### The Problem Standard agent streaming has two fragility points: 1. **Connection drops** - If a client disconnects mid-stream (network blip, browser refresh, mobile app backgrounded), all subsequent events are lost. The client has no way to "catch up" on what they missed. 2. **Long-running operations** - Agent loops with tool calls can take minutes. Holding an HTTP connection open that long is unreliable. If the server restarts or the connection times out, the work is lost. ### The Solution **Resumable streams** solve connection drops. Every event is cached with a sequential index. If a client disconnects at event 5, they can reconnect and request events starting from index 6. They receive cached events immediately, then continue with live events as they arrive. **Durable execution** solves long-running operations. Instead of executing the agent loop directly in the HTTP request, execution happens in a workflow engine (built-in evented engine or Inngest). The HTTP request just subscribes to events. If the connection drops, execution continues. The client can reconnect anytime to observe progress. ### Usage Wrap any existing `Agent` with durability using factory functions: ```typescript import { Agent } from '@mastra/core/agent'; import { createDurableAgent } from '@mastra/core/agent/durable'; const agent = new Agent({ id: 'my-agent', model: openai('gpt-4'), instructions: 'You are helpful', }); const durableAgent = createDurableAgent({ agent }); ``` **Factory functions for different execution strategies:** | Factory | Execution | Use Case | | ---------------------------------------- | ----------------------------------- | ------------------------------- | | `createDurableAgent({ agent })` | Local, synchronous | Development, simple deployments | | `createEventedAgent({ agent })` | Fire-and-forget via workflow engine | Long-running operations | | `createInngestAgent({ agent, inngest })` | Inngest-powered | Production, distributed systems | ### Resumable Streams ```typescript // Start streaming const { runId, output } = await durableAgent.stream('Analyze this data...'); // Client disconnects at event 5... // Reconnect and resume from where we left off const { output: resumed } = await durableAgent.observe(runId, { offset: 6 }); // Receives events 6, 7, 8... from cache, then continues with live events ``` ### PubSub and Cache Durable agents use two infrastructure components: | Component | Purpose | Default | | ---------- | ----------------------------------------- | --------------------- | | **PubSub** | Real-time event delivery during streaming | `EventEmitterPubSub` | | **Cache** | Stores events for replay on reconnection | `InMemoryServerCache` | When `stream()` is called, events flow through pubsub in real-time. The cache stores each event with a sequential index. When `observe()` is called, missed events replay from cache before continuing with live events. **Configure via Mastra instance (recommended):** ```typescript const mastra = new Mastra({ cache: new RedisServerCache({ url: 'redis://...' }), pubsub: new RedisPubSub({ url: 'redis://...' }), agents: { // Inherits cache and pubsub from Mastra myAgent: createDurableAgent({ agent }), }, }); ``` **Configure per-agent (overrides Mastra):** ```typescript const durableAgent = createDurableAgent({ agent, cache: new RedisServerCache({ url: 'redis://...' }), pubsub: new RedisPubSub({ url: 'redis://...' }), }); ``` **Disable caching (streams won't be resumable):** ```typescript const durableAgent = createDurableAgent({ agent, cache: false }); ``` For single-instance deployments, the defaults work fine. For multi-instance deployments (load balancer, horizontal scaling), use Redis-backed implementations so any instance can serve reconnection requests. ### Class Hierarchy - `DurableAgent` extends `Agent` - base class with resumable streams - `EventedAgent` extends `DurableAgent` - fire-and-forget execution - `InngestAgent` extends `DurableAgent` - Inngest-powered execution ### Patch Changes - Fixed a regression in 1.29.0 where configuring an agent with channel adapters (e.g. `channels.adapters.slack`) caused server startup to crash with a "Custom API route ... must not start with /api" error. The custom-route prefix validation now skips framework-generated webhook routes. ([#15952](https://github.com/mastra-ai/mastra/pull/15952)) - Fix MCP client support in the agent editor: ([#15945](https://github.com/mastra-ai/mastra/pull/15945)) - MCP client form dirty state: Save button now enables after adding/removing MCP clients - MCP tool name matching: Both bare and namespaced tool names are matched correctly - Auth token forwarding: Token from cookie or header is forwarded to auth-protected MCP servers - String interpolation: Request context variables in system prompts now resolve correctly ## 1.29.2-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`d587199`](https://github.com/mastra-ai/mastra/commit/d5871993c0371bde2b0717d6b47194755baa1443)) ## 1.29.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`6db978c`](https://github.com/mastra-ai/mastra/commit/6db978c42e94e75540a504f7230086f0b5cd35f9)) - Fixed several processor bugs: BatchPartsProcessor no longer drops non-text parts (tool calls, step events) when they trigger a text batch flush. PIIDetector now correctly applies the confidence threshold to individual detections instead of flagging any detection regardless of confidence. Added missing break/return statements after abort() calls in PII detector and language detector switch statements. Unified error handling across all LLM-based processors to consistently use abort() for proper tripwire tracking in observability spans. ([#15881](https://github.com/mastra-ai/mastra/pull/15881)) - Fixed duplicate provider response items from being re-added during agent loops. ([#15897](https://github.com/mastra-ai/mastra/pull/15897)) - Fixed `getOrCreateSpan` to restore previous span-parenting behavior. ([#15907](https://github.com/mastra-ai/mastra/pull/15907)) Calls without `tracingContext.currentSpan` now start a new root span instead of attaching to ambient context, preventing unintended nesting. - Fixed Anthropic prompt-caching cache-write tokens being overwritten with the latest step's value instead of summed across multi-step runs. ([#15828](https://github.com/mastra-ai/mastra/pull/15828)) In multi-step agent runs (e.g. a subagent calling a model 3 times), per-step `usage.inputTokens.cacheWrite` was dropped during V3-to-V2 usage normalization and only survived in `raw`, which RunOutput intentionally keeps as the latest step. The aggregated total therefore reflected only the last step's cache-write tokens. Added a new top-level `cacheCreationInputTokens` field on `LanguageModelUsage`. The V3 usage normalizer now extracts it from `inputTokens.cacheWrite`, and `RunOutput.updateUsageCount`/`populateUsageCount` sum it across steps, mirroring how `cachedInputTokens` already aggregates cache-read tokens. `MastraAgentNetworkStream` was updated symmetrically. ```ts // totalUsage on a 3-step Anthropic run with prompt caching: // Before: { cacheCreationInputTokens: 4005 } // only the last step // After: { cacheCreationInputTokens: 5268 } // 967 + 296 + 4005 ``` ## 1.29.1-alpha.2 ### Patch Changes - Fixed several processor bugs: BatchPartsProcessor no longer drops non-text parts (tool calls, step events) when they trigger a text batch flush. PIIDetector now correctly applies the confidence threshold to individual detections instead of flagging any detection regardless of confidence. Added missing break/return statements after abort() calls in PII detector and language detector switch statements. Unified error handling across all LLM-based processors to consistently use abort() for proper tripwire tracking in observability spans. ([#15881](https://github.com/mastra-ai/mastra/pull/15881)) - Fixed duplicate provider response items from being re-added during agent loops. ([#15897](https://github.com/mastra-ai/mastra/pull/15897)) ## 1.29.1-alpha.1 ### Patch Changes - Fixed `getOrCreateSpan` to restore previous span-parenting behavior. ([#15907](https://github.com/mastra-ai/mastra/pull/15907)) Calls without `tracingContext.currentSpan` now start a new root span instead of attaching to ambient context, preventing unintended nesting. - Fixed Anthropic prompt-caching cache-write tokens being overwritten with the latest step's value instead of summed across multi-step runs. ([#15828](https://github.com/mastra-ai/mastra/pull/15828)) In multi-step agent runs (e.g. a subagent calling a model 3 times), per-step `usage.inputTokens.cacheWrite` was dropped during V3-to-V2 usage normalization and only survived in `raw`, which RunOutput intentionally keeps as the latest step. The aggregated total therefore reflected only the last step's cache-write tokens. Added a new top-level `cacheCreationInputTokens` field on `LanguageModelUsage`. The V3 usage normalizer now extracts it from `inputTokens.cacheWrite`, and `RunOutput.updateUsageCount`/`populateUsageCount` sum it across steps, mirroring how `cachedInputTokens` already aggregates cache-read tokens. `MastraAgentNetworkStream` was updated symmetrically. ```ts // totalUsage on a 3-step Anthropic run with prompt caching: // Before: { cacheCreationInputTokens: 4005 } // only the last step // After: { cacheCreationInputTokens: 5268 } // 967 + 296 + 4005 ``` ## 1.29.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`6db978c`](https://github.com/mastra-ai/mastra/commit/6db978c42e94e75540a504f7230086f0b5cd35f9)) ## 1.29.0 ### Minor Changes - Added `prepareRun` and `filterRun` utilities for scorer input preparation, allowing scorers to filter and transform agent messages before scoring. Scorers can now declare a `prepareRun` hook or use the `filterRun` builder to select specific message part types and tool names before scoring runs. ([#15642](https://github.com/mastra-ai/mastra/pull/15642)) Added `getCurrentTraceId()` to Harness, which captures the observability trace ID from agent stream responses. This allows callers to correlate feedback and other annotations to the correct trace. - Added `progressThrottleMs` to background task configuration so high-frequency progress output can be coalesced before it reaches pubsub and stream consumers. ([#15829](https://github.com/mastra-ai/mastra/pull/15829)) ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, progressThrottleMs: 500, }, }); ``` - Added forked subagents: a new `forked` flag on `HarnessSubagent` definitions and the built-in `subagent` tool input. When set, the subagent runs on a clone of the parent thread using the parent agent's instructions and tools, preserving prompt-cache prefix while isolating writes from the main conversation. ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) ### Patch Changes - Stop logging client-disconnect aborts as `Error in LLM execution` at error level. The catch block in `agentic-execution/llm-execution-step.ts` now checks for `isAbortError(error)` first and exits via a `debug`-level log + the existing `onAbort` flow before the upstream-error / generic-error branches run. Closes #15844. ([#15847](https://github.com/mastra-ai/mastra/pull/15847)) - Fixed Studio observability tabs so runtime-injected observability unlocks them. ([#15821](https://github.com/mastra-ai/mastra/pull/15821)) - Update provider registry and model documentation with latest models and providers ([`b510d36`](https://github.com/mastra-ai/mastra/commit/b510d368f73dab6be2e2c2bc99035aaef1fb7d7a)) - Added a stream error retry processor with OpenAI Responses stream error matching. ([#15760](https://github.com/mastra-ai/mastra/pull/15760)) - Enable ProviderHistoryCompat error processor by default in mastracode ([#15730](https://github.com/mastra-ai/mastra/pull/15730)) - Fixed `AgentChannels.consumeAgentStream` silently dropping `tripwire` chunks, which left channel users (Slack, Discord) with no response when a `strategy: "block"` processor fired. The chunk is now handled: when `retry` is `false`/unset the block reason is posted to the channel (prefixed with the `processorId` when present); when `retry` is `true` the chunk is skipped so the agent's retried output can flow through normally. ([#15692](https://github.com/mastra-ai/mastra/pull/15692)) - Fixed toModelOutput lookup for dynamically loaded tools via ToolSearchProcessor ([#15452](https://github.com/mastra-ai/mastra/pull/15452)) - Replace wildcard `./*` export with an explicit allowlist of 48 valid subpath exports. The wildcard combined with tsc emitting individual `.d.ts` files created phantom subpaths (e.g. `@mastra/core/auth/ee/defaults`) that compiled in TypeScript but crashed at runtime with `MODULE_NOT_FOUND`. The allowlist approach only exposes subpaths that have actual runtime JS, preventing phantom imports entirely. ([#15794](https://github.com/mastra-ai/mastra/pull/15794)) - fix(tools): preserve args for programmatic tool calls when merging synthetic tool-call ([#15227](https://github.com/mastra-ai/mastra/pull/15227)) Fixes an issue where programmatic tool calls (PTC) received empty `{}` arguments during streaming. When a synthetic tool-call was created with empty args, the real tool-call event (containing actual args) was ignored. This change ensures that args from the real tool-call are merged into the synthetic one when missing. - Fixed agents forcing `temperature: 0` when the user did not explicitly set it. Previously, every `agent.stream()` / `agent.generate()` call silently injected `temperature: 0` into model settings, which broke models that restrict acceptable temperature values (for example Moonshot Kimi K2.5, which only accepts `temperature=1` and rejects any other value with `400 Bad Request`). The model provider's own default is now used when the user does not configure a temperature. Users who explicitly set `temperature` (including `temperature: 0` for deterministic output) are unaffected. Fixes [#15240](https://github.com/mastra-ai/mastra/issues/15240). ([#15611](https://github.com/mastra-ai/mastra/pull/15611)) - Fix forked subagent fork threads starting with empty history. The parent stream's message saves are debounced through `SaveQueueManager`, so a forked subagent that calls `memory.cloneThread` mid-stream used to clone from an empty store and lose the parent's user + assistant turn. The tool now drains the parent save queue via a new `flushMessages` callback on `AgentToolExecutionContext` before cloning, so forks actually carry the prior conversation. ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) - The internal `` tag is no longer appended to subagent tool result content. The tag was previously visible to the parent model in the tool result, which could cause it to be echoed back as literal markup in the parent's assistant text on subsequent turns. Live UIs continue to receive model / duration / tool-call information via the structured `subagent_*` events; history UIs read the persisted `tool_call.args.modelId`. `parseSubagentMeta` is retained so already-persisted threads carrying the legacy tag continue to render cleanly (and the tag is stripped before display in all cases). ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) - Forked subagents now inherit the parent agent's toolsets (so harness-injected tools like `ask_user`, `submit_plan`, and user-configured harness tools remain available inside a fork). The `subagent` tool entry is kept in the inherited toolset with its id, description, and schemas unchanged so the LLM request prefix stays byte-identical to the parent's and the prompt cache continues to hit; recursive forking is blocked at the runtime layer by replacing only the tool's `execute` with a stub that returns a "tool unavailable inside a forked subagent" message. Forked runs allow follow-up steps so the model can recover and answer directly if it accidentally calls that stub. Fork threads are tagged with `metadata.forkedSubagent: true` and `metadata.parentThreadId`, and `Harness.listThreads()` hides them by default so they don't surface in user-facing thread pickers; pass `includeForkedSubagents: true` to opt back in for admin/debug tooling. ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) Mastra Code now renders forked subagent footers as `subagent fork `, including persisted history reloaded after the live event metadata is gone. - Added missing A2A vNext error variants for protocol 0.3 handling. ([#15720](https://github.com/mastra-ai/mastra/pull/15720)) - Users now get a clear error when using Observational Memory with agent network. ([#15808](https://github.com/mastra-ai/mastra/pull/15808)) - Improved skills discovery performance by parallelizing filesystem I/O operations. Discovery of multiple skills, subdirectory scanning, reference file reads, and staleness checks now run concurrently instead of sequentially. Also fixed a bug in CompositeVersionedSkillSource where the root directory stat always returned the current time, causing unnecessary re-discovery on every refresh cycle. ([#14360](https://github.com/mastra-ai/mastra/pull/14360)) - Fixed tool `strict: true` being silently dropped when routing through V2 (AI SDK v5) OpenAI providers. V2 providers use a global `strictJsonSchema` provider option instead of per-tool `strict`, so Mastra now propagates the intent automatically — when any tool on a call has `strict: true`, `providerOptions.openai.strictJsonSchema` is set to `true` before the request is sent. Explicit user-supplied `strictJsonSchema` values are respected and never overridden. ([#15450](https://github.com/mastra-ai/mastra/pull/15450)) - Fixed execute_command timeout handling for models that send numeric values as strings. ([#15765](https://github.com/mastra-ai/mastra/pull/15765)) - Fixed resourceId not being forwarded to createRun() in the agentic loop, which caused persistWorkflowSnapshot to receive resourceId: undefined. ([#15742](https://github.com/mastra-ai/mastra/pull/15742)) - Fixed agent loops so truncated model responses stop instead of retrying pending tool calls until max steps. ([#15788](https://github.com/mastra-ai/mastra/pull/15788)) - Fixed `dataset.startExperiment` for workflow targets to match `runEvals`. Previously, scorers running inside a persisted experiment could not access per-step input or output, `requestContext` configured on the experiment was not forwarded into the workflow run, and direct agent calls inside workflow steps could start detached traces instead of nesting under the workflow step span. Step-level data is now exposed to scorers via `run.targetMetadata.stepResults` and `run.targetMetadata.stepExecutionPath`, the workflow's root span ID is available as `run.targetSpanId`, `requestContext` propagates into every step, and ambient workflow step tracing is used when creating nested spans. Fixes [#15613](https://github.com/mastra-ai/mastra/issues/15613). ([#15792](https://github.com/mastra-ai/mastra/pull/15792)) - Fixed `requireApproval` being silently ignored for tools loaded dynamically via `ToolSearchProcessor`. The approval gate now fires a `tool-call-approval` event and pauses execution before running, matching the behaviour of tools registered directly on the agent. ([#15782](https://github.com/mastra-ai/mastra/pull/15782)) - Fixed AI SDK v5 message rehydration so suspended and approval tool state data parts are restored from persisted message metadata after reload. ([#14246](https://github.com/mastra-ai/mastra/pull/14246)) - Fixed the TypeScript type for `requireApproval` on tools so it accepts a function in addition to a boolean. The runtime already supported per-call approval functions (added in #15346), but the type still required `boolean`, forcing an `as any` cast. You can now pass a sync or async predicate without a cast — the predicate receives the validated tool input and an optional `{ requestContext, workspace }` second argument. Fixes #15647. ([#15783](https://github.com/mastra-ai/mastra/pull/15783)) - Added ProviderHistoryCompat error processor that automatically sanitizes tool-call IDs when switching between LLM providers. When a provider rejects tool IDs from another provider's history (e.g. Anthropic enforces `^[a-zA-Z0-9_-]+$`), the processor rewrites invalid characters and retries the request. ([#15730](https://github.com/mastra-ai/mastra/pull/15730)) - Add `agent.streamUntilIdle()` and default sub-agents to run as background tasks. ([#15686](https://github.com/mastra-ai/mastra/pull/15686)) **`streamUntilIdle`** A new agent streaming method that keeps the stream open until all background tasks dispatched during the turn complete. When a task finishes, the agent is re-invoked automatically so the result is processed in the same call — no second user turn required. ```ts // Before — stream closes once the LLM returns. Background task // results are only processed on the next user message. const result = await agent.stream('Research quantum computing', { memory }); for await (const chunk of result.fullStream) { /* ... */ } // After — stream stays open through the background task completion // and the follow-up agent turn; the final answer arrives in the same call. const result = await agent.streamUntilIdle('Research quantum computing', { memory }); for await (const chunk of result.fullStream) { /* ... */ } ``` ## 1.29.0-alpha.6 ### Minor Changes - Added forked subagents: a new `forked` flag on `HarnessSubagent` definitions and the built-in `subagent` tool input. When set, the subagent runs on a clone of the parent thread using the parent agent's instructions and tools, preserving prompt-cache prefix while isolating writes from the main conversation. ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) ### Patch Changes - Fixed Studio observability tabs so runtime-injected observability unlocks them. ([#15821](https://github.com/mastra-ai/mastra/pull/15821)) - Added a stream error retry processor with OpenAI Responses stream error matching. ([#15760](https://github.com/mastra-ai/mastra/pull/15760)) - Fix forked subagent fork threads starting with empty history. The parent stream's message saves are debounced through `SaveQueueManager`, so a forked subagent that calls `memory.cloneThread` mid-stream used to clone from an empty store and lose the parent's user + assistant turn. The tool now drains the parent save queue via a new `flushMessages` callback on `AgentToolExecutionContext` before cloning, so forks actually carry the prior conversation. ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) - The internal `` tag is no longer appended to subagent tool result content. The tag was previously visible to the parent model in the tool result, which could cause it to be echoed back as literal markup in the parent's assistant text on subsequent turns. Live UIs continue to receive model / duration / tool-call information via the structured `subagent_*` events; history UIs read the persisted `tool_call.args.modelId`. `parseSubagentMeta` is retained so already-persisted threads carrying the legacy tag continue to render cleanly (and the tag is stripped before display in all cases). ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) - Forked subagents now inherit the parent agent's toolsets (so harness-injected tools like `ask_user`, `submit_plan`, and user-configured harness tools remain available inside a fork). The `subagent` tool entry is kept in the inherited toolset with its id, description, and schemas unchanged so the LLM request prefix stays byte-identical to the parent's and the prompt cache continues to hit; recursive forking is blocked at the runtime layer by replacing only the tool's `execute` with a stub that returns a "tool unavailable inside a forked subagent" message. Forked runs allow follow-up steps so the model can recover and answer directly if it accidentally calls that stub. Fork threads are tagged with `metadata.forkedSubagent: true` and `metadata.parentThreadId`, and `Harness.listThreads()` hides them by default so they don't surface in user-facing thread pickers; pass `includeForkedSubagents: true` to opt back in for admin/debug tooling. ([#15695](https://github.com/mastra-ai/mastra/pull/15695)) Mastra Code now renders forked subagent footers as `subagent fork `, including persisted history reloaded after the live event metadata is gone. - Added missing A2A vNext error variants for protocol 0.3 handling. ([#15720](https://github.com/mastra-ai/mastra/pull/15720)) - Fixed Observational Memory duplicate assistant messages when a thread is rerun or an agent continues a long-running loop. ([#15774](https://github.com/mastra-ai/mastra/pull/15774)) ## 1.29.0-alpha.5 ### Patch Changes - Stop logging client-disconnect aborts as `Error in LLM execution` at error level. The catch block in `agentic-execution/llm-execution-step.ts` now checks for `isAbortError(error)` first and exits via a `debug`-level log + the existing `onAbort` flow before the upstream-error / generic-error branches run. Closes #15844. ([#15847](https://github.com/mastra-ai/mastra/pull/15847)) - Users now get a clear error when using Observational Memory with agent network. ([#15808](https://github.com/mastra-ai/mastra/pull/15808)) - Fixed `dataset.startExperiment` for workflow targets to match `runEvals`. Previously, scorers running inside a persisted experiment could not access per-step input or output, `requestContext` configured on the experiment was not forwarded into the workflow run, and direct agent calls inside workflow steps could start detached traces instead of nesting under the workflow step span. Step-level data is now exposed to scorers via `run.targetMetadata.stepResults` and `run.targetMetadata.stepExecutionPath`, the workflow's root span ID is available as `run.targetSpanId`, `requestContext` propagates into every step, and ambient workflow step tracing is used when creating nested spans. Fixes [#15613](https://github.com/mastra-ai/mastra/issues/15613). ([#15792](https://github.com/mastra-ai/mastra/pull/15792)) ## 1.29.0-alpha.4 ### Minor Changes - Added `progressThrottleMs` to background task configuration so high-frequency progress output can be coalesced before it reaches pubsub and stream consumers. ([#15829](https://github.com/mastra-ai/mastra/pull/15829)) ```ts const mastra = new Mastra({ backgroundTasks: { enabled: true, progressThrottleMs: 500, }, }); ``` ### Patch Changes - Fixed execute_command timeout handling for models that send numeric values as strings. ([#15765](https://github.com/mastra-ai/mastra/pull/15765)) ## 1.29.0-alpha.3 ### Minor Changes - Added `prepareRun` and `filterRun` utilities for scorer input preparation, allowing scorers to filter and transform agent messages before scoring. Scorers can now declare a `prepareRun` hook or use the `filterRun` builder to select specific message part types and tool names before scoring runs. ([#15642](https://github.com/mastra-ai/mastra/pull/15642)) Added `getCurrentTraceId()` to Harness, which captures the observability trace ID from agent stream responses. This allows callers to correlate feedback and other annotations to the correct trace. ### Patch Changes - Fixed `AgentChannels.consumeAgentStream` silently dropping `tripwire` chunks, which left channel users (Slack, Discord) with no response when a `strategy: "block"` processor fired. The chunk is now handled: when `retry` is `false`/unset the block reason is posted to the channel (prefixed with the `processorId` when present); when `retry` is `true` the chunk is skipped so the agent's retried output can flow through normally. ([#15692](https://github.com/mastra-ai/mastra/pull/15692)) - fix(tools): preserve args for programmatic tool calls when merging synthetic tool-call ([#15227](https://github.com/mastra-ai/mastra/pull/15227)) Fixes an issue where programmatic tool calls (PTC) received empty `{}` arguments during streaming. When a synthetic tool-call was created with empty args, the real tool-call event (containing actual args) was ignored. This change ensures that args from the real tool-call are merged into the synthetic one when missing. ## 1.29.0-alpha.2 ### Patch Changes - Fixed toModelOutput lookup for dynamically loaded tools via ToolSearchProcessor ([#15452](https://github.com/mastra-ai/mastra/pull/15452)) - Replace wildcard `./*` export with an explicit allowlist of 48 valid subpath exports. The wildcard combined with tsc emitting individual `.d.ts` files created phantom subpaths (e.g. `@mastra/core/auth/ee/defaults`) that compiled in TypeScript but crashed at runtime with `MODULE_NOT_FOUND`. The allowlist approach only exposes subpaths that have actual runtime JS, preventing phantom imports entirely. ([#15794](https://github.com/mastra-ai/mastra/pull/15794)) - Fixed resourceId not being forwarded to createRun() in the agentic loop, which caused persistWorkflowSnapshot to receive resourceId: undefined. ([#15742](https://github.com/mastra-ai/mastra/pull/15742)) - Fixed agent loops so truncated model responses stop instead of retrying pending tool calls until max steps. ([#15788](https://github.com/mastra-ai/mastra/pull/15788)) - Fixed `requireApproval` being silently ignored for tools loaded dynamically via `ToolSearchProcessor`. The approval gate now fires a `tool-call-approval` event and pauses execution before running, matching the behaviour of tools registered directly on the agent. ([#15782](https://github.com/mastra-ai/mastra/pull/15782)) - Fixed the TypeScript type for `requireApproval` on tools so it accepts a function in addition to a boolean. The runtime already supported per-call approval functions (added in #15346), but the type still required `boolean`, forcing an `as any` cast. You can now pass a sync or async predicate without a cast — the predicate receives the validated tool input and an optional `{ requestContext, workspace }` second argument. Fixes #15647. ([#15783](https://github.com/mastra-ai/mastra/pull/15783)) - Add `agent.streamUntilIdle()` and default sub-agents to run as background tasks. ([#15686](https://github.com/mastra-ai/mastra/pull/15686)) **`streamUntilIdle`** A new agent streaming method that keeps the stream open until all background tasks dispatched during the turn complete. When a task finishes, the agent is re-invoked automatically so the result is processed in the same call — no second user turn required. ```ts // Before — stream closes once the LLM returns. Background task // results are only processed on the next user message. const result = await agent.stream('Research quantum computing', { memory }); for await (const chunk of result.fullStream) { /* ... */ } // After — stream stays open through the background task completion // and the follow-up agent turn; the final answer arrives in the same call. const result = await agent.streamUntilIdle('Research quantum computing', { memory }); for await (const chunk of result.fullStream) { /* ... */ } ``` ## 1.29.0-alpha.1 ### Patch Changes - Enable ProviderHistoryCompat error processor by default in mastracode ([#15730](https://github.com/mastra-ai/mastra/pull/15730)) - Fixed agents forcing `temperature: 0` when the user did not explicitly set it. Previously, every `agent.stream()` / `agent.generate()` call silently injected `temperature: 0` into model settings, which broke models that restrict acceptable temperature values (for example Moonshot Kimi K2.5, which only accepts `temperature=1` and rejects any other value with `400 Bad Request`). The model provider's own default is now used when the user does not configure a temperature. Users who explicitly set `temperature` (including `temperature: 0` for deterministic output) are unaffected. Fixes [#15240](https://github.com/mastra-ai/mastra/issues/15240). ([#15611](https://github.com/mastra-ai/mastra/pull/15611)) - Improved skills discovery performance by parallelizing filesystem I/O operations. Discovery of multiple skills, subdirectory scanning, reference file reads, and staleness checks now run concurrently instead of sequentially. Also fixed a bug in CompositeVersionedSkillSource where the root directory stat always returned the current time, causing unnecessary re-discovery on every refresh cycle. ([#14360](https://github.com/mastra-ai/mastra/pull/14360)) - Fixed tool `strict: true` being silently dropped when routing through V2 (AI SDK v5) OpenAI providers. V2 providers use a global `strictJsonSchema` provider option instead of per-tool `strict`, so Mastra now propagates the intent automatically — when any tool on a call has `strict: true`, `providerOptions.openai.strictJsonSchema` is set to `true` before the request is sent. Explicit user-supplied `strictJsonSchema` values are respected and never overridden. ([#15450](https://github.com/mastra-ai/mastra/pull/15450)) - Fixed AI SDK v5 message rehydration so suspended and approval tool state data parts are restored from persisted message metadata after reload. ([#14246](https://github.com/mastra-ai/mastra/pull/14246)) - Added ProviderHistoryCompat error processor that automatically sanitizes tool-call IDs when switching between LLM providers. When a provider rejects tool IDs from another provider's history (e.g. Anthropic enforces `^[a-zA-Z0-9_-]+$`), the processor rewrites invalid characters and retries the request. ([#15730](https://github.com/mastra-ai/mastra/pull/15730)) ## 1.29.0-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`b510d36`](https://github.com/mastra-ai/mastra/commit/b510d368f73dab6be2e2c2bc99035aaef1fb7d7a)) ## 1.28.0 ### Minor Changes - The Workspace `filesystem` option now accepts a resolver function in addition to a static instance. ([#13150](https://github.com/mastra-ai/mastra/pull/13150)) **Before:** `filesystem: WorkspaceFilesystem` (static, same filesystem for every request) **After:** `filesystem: WorkspaceFilesystem | (({ requestContext }) => WorkspaceFilesystem)` (static or per-request) This enables per-request filesystem routing from a single Workspace — useful for multi-tenant setups, role-based access (e.g. admin vs user directories), and scoped filesystem permissions without creating separate Workspace instances. - Added support for custom language server registration with the `servers` field in `LSPConfig`. Previously, LSP inspection only worked with built-in server definitions for TypeScript, JavaScript, Python, Go, and Rust. You can now register additional language servers, such as PHP, Ruby, Java, Kotlin, Swift, or Elixir, by providing a `CustomLSPServer` definition. ([#14969](https://github.com/mastra-ai/mastra/pull/14969)) **Example:** ```typescript const workspace = new Workspace({ lsp: { servers: { phpactor: { id: 'phpactor', name: 'Phpactor Language Server', languageIds: ['php'], extensions: ['.php'], markers: ['composer.json'], command: 'phpactor language-server', }, }, }, }); ``` Custom servers are merged with built-in servers and can also override them by using the same ID. Closes #14828. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`733bf53`](https://github.com/mastra-ai/mastra/commit/733bf53d9352aedd3ef38c3d501edb275b65b43c)) - Fixed output processors returning `undefined` from `processOutputStream` causing an `undefined` chunk to be enqueued into the consumer stream. A processor that forgets to `return part` (or explicitly returns `undefined`) now drops that chunk, matching existing `null` behavior, instead of emitting a bogus value to downstream readers. ([#15674](https://github.com/mastra-ai/mastra/pull/15674)) ```ts // Before: returning undefined emitted { value: undefined, done: false } to consumers // After: returning undefined drops the chunk, same as returning null const processor = { id: 'my-processor', processOutputStream: async ({ part }) => { if (shouldDrop(part)) return; // implicit undefined — now safely dropped return part; }, }; ``` - Fixed streamed tool results being replayed when observational memory runs mid-stream. ([#15701](https://github.com/mastra-ai/mastra/pull/15701)) Fixed observational memory markers being saved as separate empty assistant messages. - Fixed false positive provider change detection in observational memory. Message metadata now uses the configured model ID instead of the API response model ID, ensuring consistency with step-start parts and preventing incorrect 'Model changed' activations when the provider returns versioned model names (e.g., gpt-5.4-2026-03-05 vs gpt-5.4). ([#15681](https://github.com/mastra-ai/mastra/pull/15681)) - Fixed interaction between savePerStep and observational memory that caused message duplication. The saveStepMessages method redundantly re-added response messages to the message list on every step, duplicating them. Additionally, savePerStep is now force-disabled when observational memory is enabled, since OM handles its own per-step persistence and the two features conflict. ([#15684](https://github.com/mastra-ai/mastra/pull/15684)) - Fixed rotated response message ids not propagating to the active output stream after error processor retries, which could split a single response across two ids on the API-error retry path. ([#15702](https://github.com/mastra-ai/mastra/pull/15702)) Fixed processor-supplied options to `writer.custom` being dropped in the agentic execution step, so future options like `transient` now reach the underlying output writer. - Fixed `agent.stream()` callbacks so that `onStepFinish` and `onFinish` now preserve the provider-level `usage.raw` object on `LanguageModelUsage`. This lets consumers inspect provider-specific cache metrics (e.g., Anthropic and Bedrock prompt caching) directly from the callback payload without having to wrap the stream. ([#15546](https://github.com/mastra-ai/mastra/pull/15546)) Closes #15510. - Add opt-in `checkSkillFileMtime` option to detect in-place SKILL.md edits during hot reload. ([#15676](https://github.com/mastra-ai/mastra/pull/15676)) Previously, only directory mtime was checked for skill staleness, so editing a skill's name (to fix a validation error) or updating its description wouldn't trigger re-discovery until server restart. The option is off by default since it doubles `stat()` calls per skill during staleness checks. Recommended for local development only, not for cloud storage backends where `stat()` has higher latency. ```ts const myAgent = new Agent({ workspace: { filesystem: new LocalFilesystem({ basePath: process.cwd() }), skills: ['./**/skills'], checkSkillFileMtime: true, // Enable for local dev }, }); ``` - Added `filterIncompleteToolCalls` option to memory config. When set to `false`, suspended tool calls remain visible in the agent's prompt context, allowing the agent to see its own pending interactions in thread history. Defaults to `true` (current behavior). Useful for suspend/resume patterns with providers that support incomplete tool calls (e.g. Anthropic). ([#14721](https://github.com/mastra-ai/mastra/pull/14721)) - Fixed workspace file indexing so vector search works out of the box. ([#15011](https://github.com/mastra-ai/mastra/pull/15011)) - Large files that exceeded the embedding model token limit were previously silently skipped, leaving the vector store empty and causing search failures. Large files are now split into overlapping line-based chunks, each indexed separately with correct line-range tracking back to the original file. - The vector index is now created automatically before the first upsert. Previously, backends that require an explicit `createIndex` call (e.g. LibSQL) would leave the table uncreated, causing `no such table` errors on search. Workspaces with `vectorStore` + `embedder` + `autoIndexPaths` configured now work without any manual setup. - Disable `savePerStep` in Harness to prevent duplicate messages when observational memory is enabled ([#15684](https://github.com/mastra-ai/mastra/pull/15684)) The `savePerStep` option in Harness caused message duplication when used alongside observational memory. This change temporarily disables `savePerStep` in the Harness runtime while we work on a permanent fix. ## 1.28.0-alpha.2 ### Minor Changes - The Workspace `filesystem` option now accepts a resolver function in addition to a static instance. ([#13150](https://github.com/mastra-ai/mastra/pull/13150)) **Before:** `filesystem: WorkspaceFilesystem` (static, same filesystem for every request) **After:** `filesystem: WorkspaceFilesystem | (({ requestContext }) => WorkspaceFilesystem)` (static or per-request) This enables per-request filesystem routing from a single Workspace — useful for multi-tenant setups, role-based access (e.g. admin vs user directories), and scoped filesystem permissions without creating separate Workspace instances. ### Patch Changes - Fixed rotated response message ids not propagating to the active output stream after error processor retries, which could split a single response across two ids on the API-error retry path. ([#15702](https://github.com/mastra-ai/mastra/pull/15702)) Fixed processor-supplied options to `writer.custom` being dropped in the agentic execution step, so future options like `transient` now reach the underlying output writer. ## 1.28.0-alpha.1 ### Patch Changes - Fixed observational memory message boundaries so streamed tool results continue in a fresh assistant message instead of being filtered out mid-run, and OM lifecycle markers persist through memory without creating data-only database messages. ([#15701](https://github.com/mastra-ai/mastra/pull/15701)) ## 1.28.0-alpha.0 ### Minor Changes - Added support for custom language server registration with the `servers` field in `LSPConfig`. Previously, LSP inspection only worked with built-in server definitions for TypeScript, JavaScript, Python, Go, and Rust. You can now register additional language servers, such as PHP, Ruby, Java, Kotlin, Swift, or Elixir, by providing a `CustomLSPServer` definition. ([#14969](https://github.com/mastra-ai/mastra/pull/14969)) **Example:** ```typescript const workspace = new Workspace({ lsp: { servers: { phpactor: { id: 'phpactor', name: 'Phpactor Language Server', languageIds: ['php'], extensions: ['.php'], markers: ['composer.json'], command: 'phpactor language-server', }, }, }, }); ``` Custom servers are merged with built-in servers and can also override them by using the same ID. Closes #14828. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`733bf53`](https://github.com/mastra-ai/mastra/commit/733bf53d9352aedd3ef38c3d501edb275b65b43c)) - Fixed output processors returning `undefined` from `processOutputStream` causing an `undefined` chunk to be enqueued into the consumer stream. A processor that forgets to `return part` (or explicitly returns `undefined`) now drops that chunk, matching existing `null` behavior, instead of emitting a bogus value to downstream readers. ([#15674](https://github.com/mastra-ai/mastra/pull/15674)) ```ts // Before: returning undefined emitted { value: undefined, done: false } to consumers // After: returning undefined drops the chunk, same as returning null const processor = { id: 'my-processor', processOutputStream: async ({ part }) => { if (shouldDrop(part)) return; // implicit undefined — now safely dropped return part; }, }; ``` - Fixed false positive provider change detection in observational memory. Message metadata now uses the configured model ID instead of the API response model ID, ensuring consistency with step-start parts and preventing incorrect 'Model changed' activations when the provider returns versioned model names (e.g., gpt-5.4-2026-03-05 vs gpt-5.4). ([#15681](https://github.com/mastra-ai/mastra/pull/15681)) - Fixed interaction between savePerStep and observational memory that caused message duplication. The saveStepMessages method redundantly re-added response messages to the message list on every step, duplicating them. Additionally, savePerStep is now force-disabled when observational memory is enabled, since OM handles its own per-step persistence and the two features conflict. ([#15684](https://github.com/mastra-ai/mastra/pull/15684)) - Fixed `agent.stream()` callbacks so that `onStepFinish` and `onFinish` now preserve the provider-level `usage.raw` object on `LanguageModelUsage`. This lets consumers inspect provider-specific cache metrics (e.g., Anthropic and Bedrock prompt caching) directly from the callback payload without having to wrap the stream. ([#15546](https://github.com/mastra-ai/mastra/pull/15546)) Closes #15510. - Add opt-in `checkSkillFileMtime` option to detect in-place SKILL.md edits during hot reload. ([#15676](https://github.com/mastra-ai/mastra/pull/15676)) Previously, only directory mtime was checked for skill staleness, so editing a skill's name (to fix a validation error) or updating its description wouldn't trigger re-discovery until server restart. The option is off by default since it doubles `stat()` calls per skill during staleness checks. Recommended for local development only, not for cloud storage backends where `stat()` has higher latency. ```ts const myAgent = new Agent({ workspace: { filesystem: new LocalFilesystem({ basePath: process.cwd() }), skills: ['./**/skills'], checkSkillFileMtime: true, // Enable for local dev }, }); ``` - Added `filterIncompleteToolCalls` option to memory config. When set to `false`, suspended tool calls remain visible in the agent's prompt context, allowing the agent to see its own pending interactions in thread history. Defaults to `true` (current behavior). Useful for suspend/resume patterns with providers that support incomplete tool calls (e.g. Anthropic). ([#14721](https://github.com/mastra-ai/mastra/pull/14721)) - Fixed workspace file indexing so vector search works out of the box. ([#15011](https://github.com/mastra-ai/mastra/pull/15011)) - Large files that exceeded the embedding model token limit were previously silently skipped, leaving the vector store empty and causing search failures. Large files are now split into overlapping line-based chunks, each indexed separately with correct line-range tracking back to the original file. - The vector index is now created automatically before the first upsert. Previously, backends that require an explicit `createIndex` call (e.g. LibSQL) would leave the table uncreated, causing `no such table` errors on search. Workspaces with `vectorStore` + `embedder` + `autoIndexPaths` configured now work without any manual setup. - Disable `savePerStep` in Harness to prevent duplicate messages when observational memory is enabled ([#15684](https://github.com/mastra-ai/mastra/pull/15684)) The `savePerStep` option in Harness caused message duplication when used alongside observational memory. This change temporarily disables `savePerStep` in the Harness runtime while we work on a permanent fix. ## 1.27.0 ### Minor Changes - Added support for CLI-driven browser automation with screencast support in `@mastra/core`, including automatic CDP injection for browser CLIs. ([#15415](https://github.com/mastra-ai/mastra/pull/15415)) Fixed local process spawning so workspace-relative `cwd` values no longer get duplicated. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`f112db1`](https://github.com/mastra-ai/mastra/commit/f112db179557ae9b5a0f1d25dc47f928d7d61cd9)) - Fixed `foreach` parallel iterations losing their `suspendPayload` when a sibling iteration was resumed. Previously, every result entry written back to the workflow snapshot had its `suspendPayload` cleared, so iterations that were still suspended (e.g. parallel tool-call approvals each carrying an agent's `__streamState`) lost the context they needed to resume correctly. Suspended iterations now retain their `suspendPayload` across resume cycles; completed iterations still have it cleared to keep snapshots small. ([#15551](https://github.com/mastra-ai/mastra/pull/15551)) ```ts const approvalWorkflow = createWorkflow({ id: 'approve' }).foreach(approveToolStep, { concurrency: 5 }).commit(); // Before: resuming the first approval wiped streamState on the others, // so subsequent resumes lost conversation context. // After: each suspended iteration keeps its suspendPayload (including // streamState) until it is individually resumed. ``` - Fixed interaction between savePerStep and observational memory that caused message duplication. The saveStepMessages method redundantly re-added response messages to the message list on every step, duplicating them. Additionally, savePerStep is now force-disabled when observational memory is enabled, since OM handles its own per-step persistence and the two features conflict. ([#15652](https://github.com/mastra-ai/mastra/pull/15652)) - Added opt-in temporal-gap markers for observational memory. When enabled via `observationalMemory.temporalMarkers: true`, the agent receives a `` before any user message that arrives more than 10 minutes after the previous one, so it can anchor responses in real elapsed time. Markers are persisted, surfaced to the observer, and rendered by the MastraCode TUI on reload. ([#15605](https://github.com/mastra-ai/mastra/pull/15605)) ## 1.27.0-alpha.2 ### Patch Changes - Fixed interaction between savePerStep and observational memory that caused message duplication. The saveStepMessages method redundantly re-added response messages to the message list on every step, duplicating them. Additionally, savePerStep is now force-disabled when observational memory is enabled, since OM handles its own per-step persistence and the two features conflict. ([#15652](https://github.com/mastra-ai/mastra/pull/15652)) ## 1.27.0-alpha.1 ### Minor Changes - Added support for CLI-driven browser automation with screencast support in `@mastra/core`, including automatic CDP injection for browser CLIs. ([#15415](https://github.com/mastra-ai/mastra/pull/15415)) Fixed local process spawning so workspace-relative `cwd` values no longer get duplicated. ### Patch Changes - Added opt-in temporal-gap markers for observational memory. When enabled via `observationalMemory.temporalMarkers: true`, the agent receives a `` before any user message that arrives more than 10 minutes after the previous one, so it can anchor responses in real elapsed time. Markers are persisted, surfaced to the observer, and rendered by the MastraCode TUI on reload. ([#15605](https://github.com/mastra-ai/mastra/pull/15605)) ## 1.26.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`f112db1`](https://github.com/mastra-ai/mastra/commit/f112db179557ae9b5a0f1d25dc47f928d7d61cd9)) - Fixed `foreach` parallel iterations losing their `suspendPayload` when a sibling iteration was resumed. Previously, every result entry written back to the workflow snapshot had its `suspendPayload` cleared, so iterations that were still suspended (e.g. parallel tool-call approvals each carrying an agent's `__streamState`) lost the context they needed to resume correctly. Suspended iterations now retain their `suspendPayload` across resume cycles; completed iterations still have it cleared to keep snapshots small. ([#15551](https://github.com/mastra-ai/mastra/pull/15551)) ```ts const approvalWorkflow = createWorkflow({ id: 'approve' }).foreach(approveToolStep, { concurrency: 5 }).commit(); // Before: resuming the first approval wiped streamState on the others, // so subsequent resumes lost conversation context. // After: each suspended iteration keeps its suspendPayload (including // streamState) until it is individually resumed. ``` ## 1.26.0 ### Minor Changes - RAG ingestion runs now appear in observability traces, next to your agents, workflows, and scorers. ([#15512](https://github.com/mastra-ai/mastra/pull/15512)) You can now filter traces by `traceId` when listing them. Added lightweight span and trace schemas (`LightSpanRecord`, `GetTraceLightResponse`) that exclude heavy fields like `input`, `output`, `attributes`, and `metadata` — reducing per-span payload by ~97% for timeline rendering. - Fixed potential credential leakage in observability spans. LLM API keys, authentication headers, and gateway tokens could previously appear in span input or output data sent to telemetry backends. ([#15489](https://github.com/mastra-ai/mastra/pull/15489)) **What's fixed** The model router, AI SDK model wrappers (v4 legacy, v5, v6), built-in gateways (Mastra, Netlify, Models.dev, Azure OpenAI), and the voice provider base class now restrict what they expose to spans. Only public identity fields — model ID, provider, gateway ID, voice name — are included. Private configuration such as API keys, `Authorization` headers, OAuth tokens, and proxy credentials is no longer serialized into spans. Legacy AI SDK v4 models passed to `resolveModelConfig` were previously returned unwrapped. They are now wrapped in `AISDKV4LegacyLanguageModel`, which applies the same `serializeForSpan()` safety as the v5/v6 wrappers while preserving the `LanguageModelV1` interface so existing consumers continue to work. The `SensitiveDataFilter` span output processor already redacted values under common field names (`apiKey`, `token`, `authorization`, etc.) when enabled. This fix closes the gap for users who did not have it configured, and for cases where credentials were nested under custom field names that the filter's exact-match list did not cover. **Recommended action** - Review existing telemetry data for leaked credentials and rotate any keys that may have been captured. - Custom gateways extending `MastraModelGateway` and custom voice providers extending `MastraVoice` are automatically covered — they inherit the new safe default. Override `serializeForSpan()` only if you want to expose additional non-sensitive fields. - For any other class you pass into a span (e.g. as `input`, `output`, `attributes`, or `metadata`) that holds enumerable fields with credentials or other sensitive state, add a `serializeForSpan()` method. TypeScript-`private` properties are still walked by span serialization because `private` is compile-time only. ```ts class MyServiceClient { constructor(private config: { apiKey: string; endpoint: string }) {} // Without this, spans carrying a MyServiceClient instance would // serialize `config.apiKey` through every enumerable property. serializeForSpan() { return { endpoint: this.config.endpoint }; } } ``` - Added support for sub-agent version overrides in core execution. Global defaults can be set on the Mastra instance and overridden per `generate()`/`stream()` call, with cascading propagation via requestContext. ([#15373](https://github.com/mastra-ai/mastra/pull/15373)) - Added per-entry `modelSettings`, `providerOptions`, and `headers` to agent model fallback arrays. Each entry can now specify its own temperature, topP, provider-specific options, and HTTP headers — either statically or as a function of `requestContext`. Closes #15421. ([#15429](https://github.com/mastra-ai/mastra/pull/15429)) **Example** ```ts const agent = new Agent({ model: [ { model: 'google/gemini-2.5-flash', maxRetries: 2, modelSettings: { temperature: 0.3 }, providerOptions: { google: { thinkingConfig: { thinkingBudget: 0 } } }, }, { model: 'openai/gpt-5-mini', maxRetries: 2, modelSettings: { temperature: 0.7 }, providerOptions: { openai: { reasoningEffort: 'low' } }, }, ], }); ``` **Precedence**: - `modelSettings` and `providerOptions`: per-fallback entry > call-time `stream()` / `generate()` options > agent `defaultOptions`. `modelSettings` shallow-merges by key; `providerOptions` deep-merges recursively, preserving sibling and nested keys. - `headers`: call-time `modelSettings.headers` > per-fallback `headers` > model-router-extracted headers. This preserves the existing Mastra contract from #11275, where runtime headers (typically tracing, auth, tenancy) intentionally override model-level headers. - Added activateAfterIdle setting for observational memory so buffered observations can activate after idle time before the next prompt. ([#15365](https://github.com/mastra-ai/mastra/pull/15365)) **Example** Set `activateAfterIdle: 300_000` (or `"5m"`) on the `observationalMemory` config to activate buffered context after 5 minutes of inactivity. This helps long-running threads reuse compressed context after prompt cache TTLs expire instead of sending a larger raw message window on the next request. - You can now opt into parent-agent reuse for the separate structured-output pass with `structuredOutput: { schema, model, useAgent: true }`, which lets the structuring request reuse the parent agent config, including memory. ([#15318](https://github.com/mastra-ai/mastra/pull/15318)) - Added unique IDs (`logId`, `metricId`, `scoreId`, `feedbackId`) to all observability signals, generated automatically at emission time for de-duplication across the framework pipeline and cross-system correlation. User-facing APIs (`logger.info()`, `metrics.emit()`, `addScore()`, `addFeedback()`) are unchanged. ([#15242](https://github.com/mastra-ai/mastra/pull/15242)) For existing ClickHouse and DuckDB observability signal tables, run `npx mastra migrate` before initializing the store so the new signal-ID schema is applied. - Processor traces now store hook-specific inputs and only include changed outputs, reducing payload size while keeping traces more replayable. If you consume `PROCESSOR_RUN` payloads directly, update any dashboards or parsers that depend on the previous shape. ([#15493](https://github.com/mastra-ai/mastra/pull/15493)) ### Patch Changes - dependencies updates: ([#15538](https://github.com/mastra-ai/mastra/pull/15538)) - Updated dependency [`ws@^8.20.0` ↗︎](https://www.npmjs.com/package/ws/v/8.20.0) (from `^8.19.0`, in `dependencies`) - Fixed `CompositeAuth` types so typed auth providers, such as `SimpleAuth` or `MastraAuthClerk`, can be combined without casts. ([#15556](https://github.com/mastra-ai/mastra/pull/15556)) - Update provider registry and model documentation with latest models and providers ([`3d83d06`](https://github.com/mastra-ai/mastra/commit/3d83d06f776f00fb5f4163dddd32a030c5c20844)) - Fixed browser context reminders breaking prompt cache. Browser reminders are now added as new user messages instead of modifying existing message history. ([#15417](https://github.com/mastra-ai/mastra/pull/15417)) - Fixed Harness subagent tracing so delegated runs keep the parent tracing context and show up in the same trace in observability exporters. Fixes #15461. ([#15473](https://github.com/mastra-ai/mastra/pull/15473)) - Refactored how assistant messages are constructed during streaming. Messages are now built from the complete chunk sequence after each step instead of being assembled mid-stream. This fixes duplicate OpenAI item IDs (`rs_*`, `msg_*`), eliminates empty text parts from streaming artifacts, and ensures provider metadata is correctly attributed. ([#15454](https://github.com/mastra-ai/mastra/pull/15454)) - Fixed nested workflows dropping `resourceId` when executed as a step of a parent workflow. Child workflow snapshots now preserve the parent run's resource association, so tenant-scoped persistence works end-to-end. Closes [#15246](https://github.com/mastra-ai/mastra/issues/15246). ([#15447](https://github.com/mastra-ai/mastra/pull/15447)) ```ts const run = await parent.createRun({ runId: 'run-1', resourceId: 'workspace-1', }); await run.start({ inputData: { ok: true } }); // Before: child snapshots persisted with resourceId: undefined // After: child snapshots persisted with resourceId: 'workspace-1' ``` - Fixed a security issue where several parsing and tracing paths could slow down on malformed or attacker-crafted input. Normal behavior is unchanged, and these packages now handle pathological input in linear time. ([#15566](https://github.com/mastra-ai/mastra/pull/15566)) - Fixed messages not being persisted when multiple memory processors are used together. Processor state is now correctly passed between chained workflow steps, ensuring all messages are saved. ([#14884](https://github.com/mastra-ai/mastra/pull/14884)) - Fix prototype pollution in `setNestedValue` (`@mastra/core/utils`) and `generateOpenAPIDocument` (`@mastra/server`). ([#15565](https://github.com/mastra-ai/mastra/pull/15565)) `setNestedValue` now rejects dot-path segments named `__proto__`, `constructor`, or `prototype`, preventing attacker-controlled field paths passed to `selectFields` from polluting `Object.prototype`. `generateOpenAPIDocument` builds its `paths` map with `Object.create(null)` so a route path of `__proto__` cannot poison the prototype chain. - Fixed assistant model attribution so provider and model information is preserved more reliably in stored assistant messages. ([#15462](https://github.com/mastra-ai/mastra/pull/15462)) Loop runs now keep the resolved model on the first `step-start`, already-attributed `step-start` parts are left alone, and post-tool assistant continuations preserve their incoming metadata when they merge into an existing assistant message. This keeps downstream features working with the correct model identity instead of falling back to incomplete metadata or losing it during merge. - Fixed channel webhook handling in Node.js when no execution context is available. ([#15441](https://github.com/mastra-ai/mastra/pull/15441)) - Recalled V4 messages now preserve `data-*` message parts (e.g. `data-tool-call-suspended`) after a page refresh, so suspended HITL workflows can resume correctly. ([#14211](https://github.com/mastra-ai/mastra/pull/14211)) - Fixed structured output to keep persisted assistant text behavior aligned with existing memory recall paths. ([#15318](https://github.com/mastra-ai/mastra/pull/15318)) - Fixed processOutputStep not receiving token usage data. Output processors now receive usage (inputTokens, outputTokens, totalTokens) for the current LLM step, enabling per-step cost tracking and token budget enforcement. ([#15068](https://github.com/mastra-ai/mastra/pull/15068)) - Fixed `requireApproval` on tools to accept a function in addition to a boolean. Previously, passing a function for `requireApproval` on a tool created with `createTool` was silently ignored and approval was never required. ([#15346](https://github.com/mastra-ai/mastra/pull/15346)) ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; createTool({ id: 'delete-file', description: 'Delete a file', inputSchema: z.object({ path: z.string() }), // Now works: only require approval for paths outside /tmp requireApproval: input => !input.path.startsWith('/tmp/'), execute: async ({ context }) => { // ... }, }); ``` - Fixed resume errors for suspended agent runs: `resumeStream()` and `resumeGenerate()` now return a clear message when storage is missing or the `runId` is invalid. ([#15514](https://github.com/mastra-ai/mastra/pull/15514)) - Fixed OpenAI tool strict mode when requests pass through the model router. `strict: true` on function tools now survives compatibility prep, so OpenAI Responses models receive strict tool definitions instead of silently downgrading them to non-strict. ([#15397](https://github.com/mastra-ai/mastra/pull/15397)) - Added multi-select choices to the Harness ask_user tool. ([#15485](https://github.com/mastra-ai/mastra/pull/15485)) - Fixed noisy browser reminders being added to non-browser turns. Browser reminders are now added only when browser context exists (for example, current page URL or title). ([#15416](https://github.com/mastra-ai/mastra/pull/15416)) - Fixed `dataset.startExperiment` hanging forever when `targetType` is `'workflow'`. Workflow experiments now complete normally, honour `itemTimeout`, and surface failures. Fixes #15453. ([#15570](https://github.com/mastra-ai/mastra/pull/15570)) - Fixed PrefillErrorHandler to recover from Qwen/llama.cpp prefill rejections with enable_thinking, so agents retry with a continue reminder instead of failing after skill/tool turns. ([#15518](https://github.com/mastra-ai/mastra/pull/15518)) - Add background task execution for agents. Agents can dispatch slow tool calls to run asynchronously while the conversation keeps streaming, and results are injected back into the loop when they complete. ([#15307](https://github.com/mastra-ai/mastra/pull/15307)) - Fixed fallback model attribution in agent traces. When an agent fell back after the primary model failed, token usage and cost were reported against the primary model instead of the fallback that actually served the response (e.g. in Langfuse). Fixes #13547. ([#15503](https://github.com/mastra-ai/mastra/pull/15503)) - Fixed agent stream errors when providers end a stream without an error payload. ([#15435](https://github.com/mastra-ai/mastra/pull/15435)) - Fixed provider-defined tools with custom execute callbacks (e.g. openai.tools.applyPatch) being incorrectly skipped during execution. Previously, all provider-defined tools were assumed to be provider-executed, which meant user-supplied execute functions were never called. Now, provider tools with a custom execute are correctly identified as client-executed. ([#14819](https://github.com/mastra-ai/mastra/pull/14819)) - Added model metadata to step-start parts so model changes can be detected across steps, including within a single assistant message. ([#15420](https://github.com/mastra-ai/mastra/pull/15420)) - Fixed message serialization to preserve millisecond precision in createdAt timestamps. ([#15500](https://github.com/mastra-ai/mastra/pull/15500)) - Updated dependencies [[`7db42a9`](https://github.com/mastra-ai/mastra/commit/7db42a9cccd3b29c44fb0731f792c51575e8421c), [`c65aec3`](https://github.com/mastra-ai/mastra/commit/c65aec356cc037ee7c4b30ccea946807d4c4f443)]: - @mastra/schema-compat@1.2.9 ## 1.26.0-alpha.13 ## 1.26.0-alpha.12 ### Minor Changes - You can now opt into parent-agent reuse for the separate structured-output pass with `structuredOutput: { schema, model, useAgent: true }`, which lets the structuring request reuse the parent agent config, including memory. ([#15318](https://github.com/mastra-ai/mastra/pull/15318)) ### Patch Changes - Refactored how assistant messages are constructed during streaming. Messages are now built from the complete chunk sequence after each step instead of being assembled mid-stream. This fixes duplicate OpenAI item IDs (`rs_*`, `msg_*`), eliminates empty text parts from streaming artifacts, and ensures provider metadata is correctly attributed. ([#15454](https://github.com/mastra-ai/mastra/pull/15454)) - Fixed structured output to keep persisted assistant text behavior aligned with existing memory recall paths. ([#15318](https://github.com/mastra-ai/mastra/pull/15318)) - Fixed `dataset.startExperiment` hanging forever when `targetType` is `'workflow'`. Workflow experiments now complete normally, honour `itemTimeout`, and surface failures. Fixes #15453. ([#15570](https://github.com/mastra-ai/mastra/pull/15570)) ## 1.26.0-alpha.11 ### Minor Changes - RAG ingestion runs now appear in observability traces, next to your agents, workflows, and scorers. ([#15512](https://github.com/mastra-ai/mastra/pull/15512)) You can now filter traces by `traceId` when listing them. Added lightweight span and trace schemas (`LightSpanRecord`, `GetTraceLightResponse`) that exclude heavy fields like `input`, `output`, `attributes`, and `metadata` — reducing per-span payload by ~97% for timeline rendering. - Added unique IDs (`logId`, `metricId`, `scoreId`, `feedbackId`) to all observability signals, generated automatically at emission time for de-duplication across the framework pipeline and cross-system correlation. User-facing APIs (`logger.info()`, `metrics.emit()`, `addScore()`, `addFeedback()`) are unchanged. ([#15242](https://github.com/mastra-ai/mastra/pull/15242)) For existing ClickHouse and DuckDB observability signal tables, run `npx mastra migrate` before initializing the store so the new signal-ID schema is applied. ### Patch Changes - dependencies updates: ([#15538](https://github.com/mastra-ai/mastra/pull/15538)) - Updated dependency [`ws@^8.20.0` ↗︎](https://www.npmjs.com/package/ws/v/8.20.0) (from `^8.19.0`, in `dependencies`) - Fixed message serialization to preserve millisecond precision in createdAt timestamps. ([#15500](https://github.com/mastra-ai/mastra/pull/15500)) ## 1.26.0-alpha.10 ### Minor Changes - Added support for sub-agent version overrides in core execution. Global defaults can be set on the Mastra instance and overridden per `generate()`/`stream()` call, with cascading propagation via requestContext. ([#15373](https://github.com/mastra-ai/mastra/pull/15373)) ### Patch Changes - Fixed `CompositeAuth` types so typed auth providers, such as `SimpleAuth` or `MastraAuthClerk`, can be combined without casts. ([#15556](https://github.com/mastra-ai/mastra/pull/15556)) - Fixed a security issue where several parsing and tracing paths could slow down on malformed or attacker-crafted input. Normal behavior is unchanged, and these packages now handle pathological input in linear time. ([#15566](https://github.com/mastra-ai/mastra/pull/15566)) - Fix prototype pollution in `setNestedValue` (`@mastra/core/utils`) and `generateOpenAPIDocument` (`@mastra/server`). ([#15565](https://github.com/mastra-ai/mastra/pull/15565)) `setNestedValue` now rejects dot-path segments named `__proto__`, `constructor`, or `prototype`, preventing attacker-controlled field paths passed to `selectFields` from polluting `Object.prototype`. `generateOpenAPIDocument` builds its `paths` map with `Object.create(null)` so a route path of `__proto__` cannot poison the prototype chain. - Fixed PrefillErrorHandler to recover from Qwen/llama.cpp prefill rejections with enable_thinking, so agents retry with a continue reminder instead of failing after skill/tool turns. ([#15518](https://github.com/mastra-ai/mastra/pull/15518)) ## 1.26.0-alpha.9 ### Patch Changes - Fixed resume errors for suspended agent runs: `resumeStream()` and `resumeGenerate()` now return a clear message when storage is missing or the `runId` is invalid. ([#15514](https://github.com/mastra-ai/mastra/pull/15514)) ## 1.26.0-alpha.8 ### Patch Changes - Fixed fallback model attribution in agent traces. When an agent fell back after the primary model failed, token usage and cost were reported against the primary model instead of the fallback that actually served the response (e.g. in Langfuse). Fixes #13547. ([#15503](https://github.com/mastra-ai/mastra/pull/15503)) ## 1.26.0-alpha.7 ### Minor Changes - Processor traces now store hook-specific inputs and only include changed outputs, reducing payload size while keeping traces more replayable. If you consume `PROCESSOR_RUN` payloads directly, update any dashboards or parsers that depend on the previous shape. ([#15493](https://github.com/mastra-ai/mastra/pull/15493)) ### Patch Changes - Added multi-select choices to the Harness ask_user tool. ([#15485](https://github.com/mastra-ai/mastra/pull/15485)) ## 1.26.0-alpha.6 ### Minor Changes - Fixed potential credential leakage in observability spans. LLM API keys, authentication headers, and gateway tokens could previously appear in span input or output data sent to telemetry backends. ([#15489](https://github.com/mastra-ai/mastra/pull/15489)) **What's fixed** The model router, AI SDK model wrappers (v4 legacy, v5, v6), built-in gateways (Mastra, Netlify, Models.dev, Azure OpenAI), and the voice provider base class now restrict what they expose to spans. Only public identity fields — model ID, provider, gateway ID, voice name — are included. Private configuration such as API keys, `Authorization` headers, OAuth tokens, and proxy credentials is no longer serialized into spans. Legacy AI SDK v4 models passed to `resolveModelConfig` were previously returned unwrapped. They are now wrapped in `AISDKV4LegacyLanguageModel`, which applies the same `serializeForSpan()` safety as the v5/v6 wrappers while preserving the `LanguageModelV1` interface so existing consumers continue to work. The `SensitiveDataFilter` span output processor already redacted values under common field names (`apiKey`, `token`, `authorization`, etc.) when enabled. This fix closes the gap for users who did not have it configured, and for cases where credentials were nested under custom field names that the filter's exact-match list did not cover. **Recommended action** - Review existing telemetry data for leaked credentials and rotate any keys that may have been captured. - Custom gateways extending `MastraModelGateway` and custom voice providers extending `MastraVoice` are automatically covered — they inherit the new safe default. Override `serializeForSpan()` only if you want to expose additional non-sensitive fields. - For any other class you pass into a span (e.g. as `input`, `output`, `attributes`, or `metadata`) that holds enumerable fields with credentials or other sensitive state, add a `serializeForSpan()` method. TypeScript-`private` properties are still walked by span serialization because `private` is compile-time only. ```ts class MyServiceClient { constructor(private config: { apiKey: string; endpoint: string }) {} // Without this, spans carrying a MyServiceClient instance would // serialize `config.apiKey` through every enumerable property. serializeForSpan() { return { endpoint: this.config.endpoint }; } } ``` ### Patch Changes - Fixed Harness subagent tracing so delegated runs keep the parent tracing context and show up in the same trace in observability exporters. Fixes #15461. ([#15473](https://github.com/mastra-ai/mastra/pull/15473)) ## 1.26.0-alpha.5 ### Patch Changes - Fixed provider-defined tools with custom execute callbacks (e.g. openai.tools.applyPatch) being incorrectly skipped during execution. Previously, all provider-defined tools were assumed to be provider-executed, which meant user-supplied execute functions were never called. Now, provider tools with a custom execute are correctly identified as client-executed. ([#14819](https://github.com/mastra-ai/mastra/pull/14819)) ## 1.26.0-alpha.4 ### Minor Changes - Added per-entry `modelSettings`, `providerOptions`, and `headers` to agent model fallback arrays. Each entry can now specify its own temperature, topP, provider-specific options, and HTTP headers — either statically or as a function of `requestContext`. Closes #15421. ([#15429](https://github.com/mastra-ai/mastra/pull/15429)) **Example** ```ts const agent = new Agent({ model: [ { model: 'google/gemini-2.5-flash', maxRetries: 2, modelSettings: { temperature: 0.3 }, providerOptions: { google: { thinkingConfig: { thinkingBudget: 0 } } }, }, { model: 'openai/gpt-5-mini', maxRetries: 2, modelSettings: { temperature: 0.7 }, providerOptions: { openai: { reasoningEffort: 'low' } }, }, ], }); ``` **Precedence**: - `modelSettings` and `providerOptions`: per-fallback entry > call-time `stream()` / `generate()` options > agent `defaultOptions`. `modelSettings` shallow-merges by key; `providerOptions` deep-merges recursively, preserving sibling and nested keys. - `headers`: call-time `modelSettings.headers` > per-fallback `headers` > model-router-extracted headers. This preserves the existing Mastra contract from #11275, where runtime headers (typically tracing, auth, tenancy) intentionally override model-level headers. ### Patch Changes - Fixed nested workflows dropping `resourceId` when executed as a step of a parent workflow. Child workflow snapshots now preserve the parent run's resource association, so tenant-scoped persistence works end-to-end. Closes [#15246](https://github.com/mastra-ai/mastra/issues/15246). ([#15447](https://github.com/mastra-ai/mastra/pull/15447)) ```ts const run = await parent.createRun({ runId: 'run-1', resourceId: 'workspace-1', }); await run.start({ inputData: { ok: true } }); // Before: child snapshots persisted with resourceId: undefined // After: child snapshots persisted with resourceId: 'workspace-1' ``` - Fixed assistant model attribution so provider and model information is preserved more reliably in stored assistant messages. ([#15462](https://github.com/mastra-ai/mastra/pull/15462)) Loop runs now keep the resolved model on the first `step-start`, already-attributed `step-start` parts are left alone, and post-tool assistant continuations preserve their incoming metadata when they merge into an existing assistant message. This keeps downstream features working with the correct model identity instead of falling back to incomplete metadata or losing it during merge. - Added model metadata to step-start parts so model changes can be detected across steps, including within a single assistant message. ([#15420](https://github.com/mastra-ai/mastra/pull/15420)) ## 1.26.0-alpha.3 ### Patch Changes - Fixed browser context reminders breaking prompt cache. Browser reminders are now added as new user messages instead of modifying existing message history. ([#15417](https://github.com/mastra-ai/mastra/pull/15417)) - Fixed channel webhook handling in Node.js when no execution context is available. ([#15441](https://github.com/mastra-ai/mastra/pull/15441)) - Recalled V4 messages now preserve `data-*` message parts (e.g. `data-tool-call-suspended`) after a page refresh, so suspended HITL workflows can resume correctly. ([#14211](https://github.com/mastra-ai/mastra/pull/14211)) - Fixed processOutputStep not receiving token usage data. Output processors now receive usage (inputTokens, outputTokens, totalTokens) for the current LLM step, enabling per-step cost tracking and token budget enforcement. ([#15068](https://github.com/mastra-ai/mastra/pull/15068)) - Fixed `requireApproval` on tools to accept a function in addition to a boolean. Previously, passing a function for `requireApproval` on a tool created with `createTool` was silently ignored and approval was never required. ([#15346](https://github.com/mastra-ai/mastra/pull/15346)) ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; createTool({ id: 'delete-file', description: 'Delete a file', inputSchema: z.object({ path: z.string() }), // Now works: only require approval for paths outside /tmp requireApproval: input => !input.path.startsWith('/tmp/'), execute: async ({ context }) => { // ... }, }); ``` - Fixed agent stream errors when providers end a stream without an error payload. ([#15435](https://github.com/mastra-ai/mastra/pull/15435)) - Updated dependencies [[`7db42a9`](https://github.com/mastra-ai/mastra/commit/7db42a9cccd3b29c44fb0731f792c51575e8421c)]: - @mastra/schema-compat@1.2.9-alpha.1 ## 1.26.0-alpha.2 ### Minor Changes - Added activateAfterIdle setting for observational memory so buffered observations can activate after idle time before the next prompt. ([#15365](https://github.com/mastra-ai/mastra/pull/15365)) **Example** Set `activateAfterIdle: 300_000` (or `"5m"`) on the `observationalMemory` config to activate buffered context after 5 minutes of inactivity. This helps long-running threads reuse compressed context after prompt cache TTLs expire instead of sending a larger raw message window on the next request. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`3d83d06`](https://github.com/mastra-ai/mastra/commit/3d83d06f776f00fb5f4163dddd32a030c5c20844)) - Fixed messages not being persisted when multiple memory processors are used together. Processor state is now correctly passed between chained workflow steps, ensuring all messages are saved. ([#14884](https://github.com/mastra-ai/mastra/pull/14884)) - Fixed noisy browser reminders being added to non-browser turns. Browser reminders are now added only when browser context exists (for example, current page URL or title). ([#15416](https://github.com/mastra-ai/mastra/pull/15416)) - Updated dependencies [[`c65aec3`](https://github.com/mastra-ai/mastra/commit/c65aec356cc037ee7c4b30ccea946807d4c4f443)]: - @mastra/schema-compat@1.2.9-alpha.0 ## 1.25.1-alpha.1 ### Patch Changes - Fixed OpenAI tool strict mode when requests pass through the model router. `strict: true` on function tools now survives compatibility prep, so OpenAI Responses models receive strict tool definitions instead of silently downgrading them to non-strict. ([#15397](https://github.com/mastra-ai/mastra/pull/15397)) ## 1.25.1-alpha.0 ### Patch Changes - Add background task execution for agents. Agents can dispatch slow tool calls to run asynchronously while the conversation keeps streaming, and results are injected back into the loop when they complete. ([#15307](https://github.com/mastra-ai/mastra/pull/15307)) ## 1.25.0 ### Minor Changes - feat(server): Add `mapUserToResourceId` callback to auth config for automatic resource ID scoping ([#13954](https://github.com/mastra-ai/mastra/pull/13954)) Auth configs now accept a `mapUserToResourceId` callback that maps the authenticated user to a resource ID after successful authentication. This enables per-user memory and thread isolation without requiring custom middleware or adapter subclassing. ```typescript const mastra = new Mastra({ server: { auth: { authenticateToken: async token => verifyToken(token), mapUserToResourceId: user => user.id, }, }, }); ``` The callback is called in `coreAuthMiddleware` after the user is authenticated and set on the request context. The returned value is set as `MASTRA_RESOURCE_ID_KEY`, which takes precedence over client-provided values for security. Works across all server adapters (Hono, Express, Next.js, etc.). - Added `processAPIError` hook to the Processor interface for intercepting LLM API call failures before they surface as errors. New built-in `PrefillErrorHandler` automatically recovers from Anthropic "assistant message prefill" errors by appending a `continue` user message and retrying once. ([#14435](https://github.com/mastra-ai/mastra/pull/14435)) - **Experiments now run the correct agent version** ([#15317](https://github.com/mastra-ai/mastra/pull/15317)) When an experiment specifies `agentVersion`, the experiment pipeline now resolves and executes against that specific version instead of ignoring it. Previously, the version was stored as metadata but the agent always ran with its current default configuration. **`entityVersionId` is now a first-class observability dimension** New `entityVersionId`, `parentEntityVersionId`, and `rootEntityVersionId` fields are available on all observability records (spans, metrics, scores, feedback, logs). This enables filtering and grouping OLAP queries by version at any level of the span tree. `rootEntityVersionId` is particularly useful for aggregating all signals within a versioned agent's trace. This replaces the previous `resolvedVersionId` attribute which was buried in span attributes and unfilterable. **`experimentId` propagated to agent spans** Agent spans created during experiment execution now carry the `experimentId`, enabling trace-to-experiment cross-referencing. **Scorer correlation context** Scorers running in the experiment pipeline now receive full `targetCorrelationContext` (including `experimentId`), so scores emitted via observability carry experiment context. **New experiment query filters** `listExperiments` now supports filtering by `targetType`, `targetId`, `agentVersion`, and `status`. `listExperimentResults` now supports filtering by `traceId` and `status`. - Added `profile` and `executablePath` options to browser config for persistent sessions and custom browser support. Automatically cleans up stale Chrome lock files on browser close. ([#15194](https://github.com/mastra-ai/mastra/pull/15194)) - **Added** ([#15313](https://github.com/mastra-ai/mastra/pull/15313)) Added per-tool strict mode for providers that support strict tool calling. You can now set `strict: true` on `createTool()` and Mastra will forward it when preparing tool definitions. ```ts const weatherTool = createTool({ id: 'weather', description: 'Get weather for a city', strict: true, inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ city }), }); ``` ### Patch Changes - dependencies updates: ([#15214](https://github.com/mastra-ai/mastra/pull/15214)) - Updated dependency [`chat@^4.24.0` ↗︎](https://www.npmjs.com/package/chat/v/4.24.0) (from `^4.23.0`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`582644c`](https://github.com/mastra-ai/mastra/commit/582644c4a87f83b4f245a84d72b9e8590585012e)) - Fixed `mastra_workspace_list_files` silently returning no files when agents passed an empty `pattern` (e.g. `pattern: []` or `pattern: ''`). Empty and whitespace-only patterns are now treated as "no filter" and return the full listing instead of a dirs-only view or a picomatch error. ([#15360](https://github.com/mastra-ai/mastra/pull/15360)) Fixed harness tool approval, decline, and resume handlers hardcoding `requireToolApproval: true`. They now follow the harness `yolo` state like `sendMessage` already does, so resumed tool calls in yolo mode no longer get unexpectedly re-gated on approval. - Update references to "Mastra Cloud" to "Mastra platform" ([#15297](https://github.com/mastra-ai/mastra/pull/15297)) - Fixed symlinked skill paths so workspace skills resolve consistently and allowed path checks work through both symlink and real paths. ([#15228](https://github.com/mastra-ai/mastra/pull/15228)) - AgentBrowser with default thread scope now initializes correctly. Previously, calling launch() followed by getPage() would throw "Browser not launched" when no explicit thread ID was provided. ([#15285](https://github.com/mastra-ai/mastra/pull/15285)) - fix: ensure listVectorStores always returns a string id ([#15239](https://github.com/mastra-ai/mastra/pull/15239)) - Improved `structuredOutput.model` error messages to surface upstream structuring failures, including plain-object errors, instead of a generic internal agent error. ([#15226](https://github.com/mastra-ai/mastra/pull/15226)) - Agent instances can now create lightweight clones that preserve all configuration, so version overrides and tools are isolated without mutating the shared runtime agent. ([#15314](https://github.com/mastra-ai/mastra/pull/15314)) - Fixed `structuredOutput.model` custom gateway resolution by registering the internal structuring agent with the parent Mastra instance. ([#15230](https://github.com/mastra-ai/mastra/pull/15230)) - Fixed OpenAI reasoning summary streaming so reasoning summary text is preserved when multiple summaries overlap or finish out of order. ([#15225](https://github.com/mastra-ai/mastra/pull/15225)) - Upgraded model router providers to AI SDK v3 spec: OpenAI, Anthropic, Google, xAI, Groq, and Mistral now use the latest v6 SDK packages. Providers built on `openai-compatible` (Cerebras, DeepInfra, DeepSeek, Perplexity, TogetherAI) remain on v2 spec until their base package is updated. All provider packages (both v5 and v6) bumped to their latest stable patch versions. ([#15358](https://github.com/mastra-ai/mastra/pull/15358)) Fixed 'item missing its reasoning part' error for OpenAI reasoning models (gpt-5-mini, gpt-5.2). The v5 SDK couldn't serialize reasoning items for OpenAI's Responses API, so Mastra stripped them from prompts — but this caused errors in multi-turn conversations with memory enabled. With v3 providers, reasoning items are serialized natively and the stripping workaround has been removed. - Fixed gateway model detection to use duck typing instead of instanceof check, preventing potential failures from cross-package module resolution issues. Propagates `gatewayId` through the AISDKV5LanguageModel wrapper so duck-type detection works even when models are re-wrapped. ([#15168](https://github.com/mastra-ai/mastra/pull/15168)) - Fixed Channels not working on Vercel serverless (and other serverless platforms). Webhook handlers now await initialization on cold starts instead of immediately returning 503, and pass the platform's `waitUntil` to the Chat SDK so agent processing survives after the HTTP response is sent. See #15300. ([#15335](https://github.com/mastra-ai/mastra/pull/15335)) - fix(core): Restore AI SDK v6 provider option typings for vector embeddings ([#15306](https://github.com/mastra-ai/mastra/pull/15306)) The vendored AI SDK v6 declaration build now re-exports `ProviderOptions` after type bundling renames it to `ProviderOptions_2`. This fixes `TS2724` errors in `@mastra/core` when vector embeddings import AI SDK v6 provider option types. - Updated dependencies [[`2a69802`](https://github.com/mastra-ai/mastra/commit/2a69802a0fc6d8a25a77fa6a42276e9d59a83914)]: - @mastra/schema-compat@1.2.8 ## 1.25.0-alpha.3 ### Minor Changes - Added `processAPIError` hook to the Processor interface for intercepting LLM API call failures before they surface as errors. New built-in `PrefillErrorHandler` automatically recovers from Anthropic "assistant message prefill" errors by appending a `continue` user message and retrying once. ([#14435](https://github.com/mastra-ai/mastra/pull/14435)) - **Experiments now run the correct agent version** ([#15317](https://github.com/mastra-ai/mastra/pull/15317)) When an experiment specifies `agentVersion`, the experiment pipeline now resolves and executes against that specific version instead of ignoring it. Previously, the version was stored as metadata but the agent always ran with its current default configuration. **`entityVersionId` is now a first-class observability dimension** New `entityVersionId`, `parentEntityVersionId`, and `rootEntityVersionId` fields are available on all observability records (spans, metrics, scores, feedback, logs). This enables filtering and grouping OLAP queries by version at any level of the span tree. `rootEntityVersionId` is particularly useful for aggregating all signals within a versioned agent's trace. This replaces the previous `resolvedVersionId` attribute which was buried in span attributes and unfilterable. **`experimentId` propagated to agent spans** Agent spans created during experiment execution now carry the `experimentId`, enabling trace-to-experiment cross-referencing. **Scorer correlation context** Scorers running in the experiment pipeline now receive full `targetCorrelationContext` (including `experimentId`), so scores emitted via observability carry experiment context. **New experiment query filters** `listExperiments` now supports filtering by `targetType`, `targetId`, `agentVersion`, and `status`. `listExperimentResults` now supports filtering by `traceId` and `status`. - Added `profile` and `executablePath` options to browser config for persistent sessions and custom browser support. Automatically cleans up stale Chrome lock files on browser close. ([#15194](https://github.com/mastra-ai/mastra/pull/15194)) - **Added** ([#15313](https://github.com/mastra-ai/mastra/pull/15313)) Added per-tool strict mode for providers that support strict tool calling. You can now set `strict: true` on `createTool()` and Mastra will forward it when preparing tool definitions. ```ts const weatherTool = createTool({ id: 'weather', description: 'Get weather for a city', strict: true, inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ city }), }); ``` ### Patch Changes - Fixed `mastra_workspace_list_files` silently returning no files when agents passed an empty `pattern` (e.g. `pattern: []` or `pattern: ''`). Empty and whitespace-only patterns are now treated as "no filter" and return the full listing instead of a dirs-only view or a picomatch error. ([#15360](https://github.com/mastra-ai/mastra/pull/15360)) Fixed harness tool approval, decline, and resume handlers hardcoding `requireToolApproval: true`. They now follow the harness `yolo` state like `sendMessage` already does, so resumed tool calls in yolo mode no longer get unexpectedly re-gated on approval. - AgentBrowser with default thread scope now initializes correctly. Previously, calling launch() followed by getPage() would throw "Browser not launched" when no explicit thread ID was provided. ([#15285](https://github.com/mastra-ai/mastra/pull/15285)) - fix: ensure listVectorStores always returns a string id ([#15239](https://github.com/mastra-ai/mastra/pull/15239)) - Agent instances can now create lightweight clones that preserve all configuration, so version overrides and tools are isolated without mutating the shared runtime agent. ([#15314](https://github.com/mastra-ai/mastra/pull/15314)) - Upgraded model router providers to AI SDK v3 spec: OpenAI, Anthropic, Google, xAI, Groq, and Mistral now use the latest v6 SDK packages. Providers built on `openai-compatible` (Cerebras, DeepInfra, DeepSeek, Perplexity, TogetherAI) remain on v2 spec until their base package is updated. All provider packages (both v5 and v6) bumped to their latest stable patch versions. ([#15358](https://github.com/mastra-ai/mastra/pull/15358)) Fixed 'item missing its reasoning part' error for OpenAI reasoning models (gpt-5-mini, gpt-5.2). The v5 SDK couldn't serialize reasoning items for OpenAI's Responses API, so Mastra stripped them from prompts — but this caused errors in multi-turn conversations with memory enabled. With v3 providers, reasoning items are serialized natively and the stripping workaround has been removed. - Fixed Channels not working on Vercel serverless (and other serverless platforms). Webhook handlers now await initialization on cold starts instead of immediately returning 503, and pass the platform's `waitUntil` to the Chat SDK so agent processing survives after the HTTP response is sent. See #15300. ([#15335](https://github.com/mastra-ai/mastra/pull/15335)) - fix(core): Restore AI SDK v6 provider option typings for vector embeddings ([#15306](https://github.com/mastra-ai/mastra/pull/15306)) The vendored AI SDK v6 declaration build now re-exports `ProviderOptions` after type bundling renames it to `ProviderOptions_2`. This fixes `TS2724` errors in `@mastra/core` when vector embeddings import AI SDK v6 provider option types. ## 1.25.0-alpha.2 ### Patch Changes - Update references to "Mastra Cloud" to "Mastra platform" ([#15297](https://github.com/mastra-ai/mastra/pull/15297)) - Updated dependencies [[`2a69802`](https://github.com/mastra-ai/mastra/commit/2a69802a0fc6d8a25a77fa6a42276e9d59a83914)]: - @mastra/schema-compat@1.2.8-alpha.0 ## 1.25.0-alpha.1 ### Minor Changes - feat(server): Add `mapUserToResourceId` callback to auth config for automatic resource ID scoping ([#13954](https://github.com/mastra-ai/mastra/pull/13954)) Auth configs now accept a `mapUserToResourceId` callback that maps the authenticated user to a resource ID after successful authentication. This enables per-user memory and thread isolation without requiring custom middleware or adapter subclassing. ```typescript const mastra = new Mastra({ server: { auth: { authenticateToken: async token => verifyToken(token), mapUserToResourceId: user => user.id, }, }, }); ``` The callback is called in `coreAuthMiddleware` after the user is authenticated and set on the request context. The returned value is set as `MASTRA_RESOURCE_ID_KEY`, which takes precedence over client-provided values for security. Works across all server adapters (Hono, Express, Next.js, etc.). ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`582644c`](https://github.com/mastra-ai/mastra/commit/582644c4a87f83b4f245a84d72b9e8590585012e)) - Fixed symlinked skill paths so workspace skills resolve consistently and allowed path checks work through both symlink and real paths. ([#15228](https://github.com/mastra-ai/mastra/pull/15228)) - Improved `structuredOutput.model` error messages to surface upstream structuring failures, including plain-object errors, instead of a generic internal agent error. ([#15226](https://github.com/mastra-ai/mastra/pull/15226)) - Fixed `structuredOutput.model` custom gateway resolution by registering the internal structuring agent with the parent Mastra instance. ([#15230](https://github.com/mastra-ai/mastra/pull/15230)) - Fixed OpenAI reasoning summary streaming so reasoning summary text is preserved when multiple summaries overlap or finish out of order. ([#15225](https://github.com/mastra-ai/mastra/pull/15225)) ## 1.24.2-alpha.0 ### Patch Changes - dependencies updates: ([#15214](https://github.com/mastra-ai/mastra/pull/15214)) - Updated dependency [`chat@^4.24.0` ↗︎](https://www.npmjs.com/package/chat/v/4.24.0) (from `^4.23.0`, in `dependencies`) - Fixed gateway model detection to use duck typing instead of instanceof check, preventing potential failures from cross-package module resolution issues. Propagates `gatewayId` through the AISDKV5LanguageModel wrapper so duck-type detection works even when models are re-wrapped. ([#15168](https://github.com/mastra-ai/mastra/pull/15168)) ## 1.24.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ef94400`](https://github.com/mastra-ai/mastra/commit/ef9440049402596b31f2ab976c5e4508f6cb6c91)) - Fixed subagent writing observations to the parent agent's memory thread. When a parent agent spawns a subagent via `createSubagentTool`, the subagent now receives its own isolated request context with `threadId` and `resourceId` cleared, preventing it from corrupting the parent's observation history. ([#15103](https://github.com/mastra-ai/mastra/pull/15103)) ## 1.24.1-alpha.1 ### Patch Changes - Fixed subagent writing observations to the parent agent's memory thread. When a parent agent spawns a subagent via `createSubagentTool`, the subagent now receives its own isolated request context with `threadId` and `resourceId` cleared, preventing it from corrupting the parent's observation history. ([#15103](https://github.com/mastra-ai/mastra/pull/15103)) ## 1.24.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ef94400`](https://github.com/mastra-ai/mastra/commit/ef9440049402596b31f2ab976c5e4508f6cb6c91)) ## 1.24.0 ### Minor Changes - Added `excludeSpanTypes` and `spanFilter` options to `ObservabilityInstanceConfig` for selectively filtering spans before export. Use `excludeSpanTypes` to drop entire categories of spans by type (e.g., `MODEL_CHUNK`, `MODEL_STEP`) or `spanFilter` for fine-grained predicate-based filtering by attributes, metadata, entity, or any combination. Both options help reduce noise and costs in observability platforms that charge per-span. ([#15131](https://github.com/mastra-ai/mastra/pull/15131)) **`excludeSpanTypes` example:** ```ts excludeSpanTypes: [SpanType.MODEL_CHUNK, SpanType.MODEL_STEP, SpanType.WORKFLOW_SLEEP]; ``` **`spanFilter` example:** ```ts spanFilter: span => { if (span.type === SpanType.MODEL_CHUNK) return false; if (span.type === SpanType.TOOL_CALL && span.attributes?.success) return false; return true; }; ``` Resolves https://github.com/mastra-ai/mastra/issues/12710 - Add RAG observability (#10898) ([#15137](https://github.com/mastra-ai/mastra/pull/15137)) Surfaces RAG ingestion and query operations in Mastra's AI tracing. New span types in `@mastra/core/observability`: - `RAG_INGESTION` (root) — wraps an ingestion pipeline run - `RAG_EMBEDDING` — embedding call (used by ingestion and query) - `RAG_VECTOR_OPERATION` — vector store I/O (`query`/`upsert`/`delete`/`fetch`) - `RAG_ACTION` — `chunk` / `extract_metadata` / `rerank` - `GRAPH_ACTION` — non-RAG graph `build` / `traverse` / `update` / `prune` New helpers exported from `@mastra/core/observability`: - `startRagIngestion(opts)` — manual: returns `{ span, observabilityContext }` - `withRagIngestion(opts, fn)` — scoped: runs `fn(observabilityContext)`, attaches the return value as the span's output, routes thrown errors to `span.error(...)` Wired in `@mastra/rag`: - `vectorQuerySearch` emits `RAG_EMBEDDING` (mode: `query`) and `RAG_VECTOR_OPERATION` (operation: `query`) - `rerank` / `rerankWithScorer` emit `RAG_ACTION` (action: `rerank`) - `MDocument.chunk` emits `RAG_ACTION` (action: `chunk`) and `RAG_ACTION` (action: `extract_metadata`) - `createGraphRAGTool` emits `GRAPH_ACTION` (action: `build` / `traverse`) - `createVectorQueryTool` and `createGraphRAGTool` thread `observabilityContext` from the agent's `TOOL_CALL` span automatically All new instrumentation is opt-in: functions accept an optional `observabilityContext` and no-op when absent, so existing callers are unaffected. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`8db7663`](https://github.com/mastra-ai/mastra/commit/8db7663c9a9c735828094c359d2e327fd4f8fba3)) - Added AI SDK v6 UI message support to MessageList in @mastra/core. ([#14592](https://github.com/mastra-ai/mastra/pull/14592)) MessageList can now accept AI SDK v6 UI and model messages in add(...), and project stored messages with messageList.get.all.aiV6.ui(). This adds first-class handling for v6 approval request and response message flows. - Fix observability log correlation: logs emitted from inside an agent run were being persisted with `entityId`, `runId`, `traceId`, and the other correlation fields set to `null`, breaking trace ↔ log linking in Mastra Studio and downstream observability tools. Logs now correctly carry the active span's correlation context end to end. ([#15148](https://github.com/mastra-ai/mastra/pull/15148)) - Added `createdAt` timestamps to message parts in message history. ([#15121](https://github.com/mastra-ai/mastra/pull/15121)) Message parts now keep their own creation timestamps so downstream code can preserve part-level timing instead of relying only on the parent message timestamp. After: ```ts { type: 'text', text: 'hello', createdAt: 1712534400000 } ``` ## 1.24.0-alpha.1 ### Minor Changes - Added `excludeSpanTypes` and `spanFilter` options to `ObservabilityInstanceConfig` for selectively filtering spans before export. Use `excludeSpanTypes` to drop entire categories of spans by type (e.g., `MODEL_CHUNK`, `MODEL_STEP`) or `spanFilter` for fine-grained predicate-based filtering by attributes, metadata, entity, or any combination. Both options help reduce noise and costs in observability platforms that charge per-span. ([#15131](https://github.com/mastra-ai/mastra/pull/15131)) **`excludeSpanTypes` example:** ```ts excludeSpanTypes: [SpanType.MODEL_CHUNK, SpanType.MODEL_STEP, SpanType.WORKFLOW_SLEEP]; ``` **`spanFilter` example:** ```ts spanFilter: span => { if (span.type === SpanType.MODEL_CHUNK) return false; if (span.type === SpanType.TOOL_CALL && span.attributes?.success) return false; return true; }; ``` Resolves https://github.com/mastra-ai/mastra/issues/12710 - Add RAG observability (#10898) ([#15137](https://github.com/mastra-ai/mastra/pull/15137)) Surfaces RAG ingestion and query operations in Mastra's AI tracing. New span types in `@mastra/core/observability`: - `RAG_INGESTION` (root) — wraps an ingestion pipeline run - `RAG_EMBEDDING` — embedding call (used by ingestion and query) - `RAG_VECTOR_OPERATION` — vector store I/O (`query`/`upsert`/`delete`/`fetch`) - `RAG_ACTION` — `chunk` / `extract_metadata` / `rerank` - `GRAPH_ACTION` — non-RAG graph `build` / `traverse` / `update` / `prune` New helpers exported from `@mastra/core/observability`: - `startRagIngestion(opts)` — manual: returns `{ span, observabilityContext }` - `withRagIngestion(opts, fn)` — scoped: runs `fn(observabilityContext)`, attaches the return value as the span's output, routes thrown errors to `span.error(...)` Wired in `@mastra/rag`: - `vectorQuerySearch` emits `RAG_EMBEDDING` (mode: `query`) and `RAG_VECTOR_OPERATION` (operation: `query`) - `rerank` / `rerankWithScorer` emit `RAG_ACTION` (action: `rerank`) - `MDocument.chunk` emits `RAG_ACTION` (action: `chunk`) and `RAG_ACTION` (action: `extract_metadata`) - `createGraphRAGTool` emits `GRAPH_ACTION` (action: `build` / `traverse`) - `createVectorQueryTool` and `createGraphRAGTool` thread `observabilityContext` from the agent's `TOOL_CALL` span automatically All new instrumentation is opt-in: functions accept an optional `observabilityContext` and no-op when absent, so existing callers are unaffected. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`8db7663`](https://github.com/mastra-ai/mastra/commit/8db7663c9a9c735828094c359d2e327fd4f8fba3)) - Fix observability log correlation: logs emitted from inside an agent run were being persisted with `entityId`, `runId`, `traceId`, and the other correlation fields set to `null`, breaking trace ↔ log linking in Mastra Studio and downstream observability tools. Logs now correctly carry the active span's correlation context end to end. ([#15148](https://github.com/mastra-ai/mastra/pull/15148)) - Added `createdAt` timestamps to message parts in message history. ([#15121](https://github.com/mastra-ai/mastra/pull/15121)) Message parts now keep their own creation timestamps so downstream code can preserve part-level timing instead of relying only on the parent message timestamp. After: ```ts { type: 'text', text: 'hello', createdAt: 1712534400000 } ``` ## 1.23.1-alpha.0 ### Patch Changes - Added AI SDK v6 UI message support to MessageList in @mastra/core. ([#14592](https://github.com/mastra-ai/mastra/pull/14592)) MessageList can now accept AI SDK v6 UI and model messages in add(...), and project stored messages with messageList.get.all.aiV6.ui(). This adds first-class handling for v6 approval request and response message flows. ## 1.23.0 ### Minor Changes - Added `UpdateObservationalMemoryConfigInput` type, `updateObservationalMemoryConfig()` method stub, and `deepMergeConfig()` utility to the `MemoryStorage` base class. These additions support per-record observational memory config overrides introduced in `@mastra/memory`. ([#15115](https://github.com/mastra-ai/mastra/pull/15115)) - Memory operations now produce observability spans and accept an optional `observabilityContext` parameter. A new `MEMORY_OPERATION` span type, `MEMORY` entity type, and `MemoryOperationAttributes` interface let you identify memory spans in your traces. Agent and network code automatically threads observability context into memory calls. ([#14305](https://github.com/mastra-ai/mastra/pull/14305)) **New observability identifiers:** - `SpanType.MEMORY_OPERATION` — span type for all memory operations - `EntityType.MEMORY` — entity type for memory spans - `MemoryOperationAttributes` — typed attributes (operationType, messageCount, embeddingTokens, etc.) **Updated abstract methods** on `MastraMemory` now accept optional `observabilityContext`: ```typescript import type { ObservabilityContext } from '@mastra/core/observability'; // All four methods accept an optional observabilityContext await memory.recall({ threadId: 'thread-1', observabilityContext: { tracingContext: { currentSpan: parentSpan } }, }); await memory.saveMessages({ messages, observabilityContext: { tracingContext: { currentSpan: parentSpan } }, }); await memory.deleteMessages(['msg-1'], { tracingContext: { currentSpan: parentSpan } }); await memory.updateWorkingMemory({ threadId: 'thread-1', workingMemory: 'content', observabilityContext: { tracingContext: { currentSpan: parentSpan } }, }); ``` - Added dynamic function support for workspace tool config. The `enabled`, `requireApproval`, and `requireReadBeforeWrite` options now accept async functions in addition to static booleans, enabling context-aware tool behavior like disabling tools based on user tier or requiring approval only for certain file paths. ([#14528](https://github.com/mastra-ai/mastra/pull/14528)) **Example** ```typescript tools: { [WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: { requireApproval: async ({ args }) => { return (args.path as string).startsWith('/protected') }, }, [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: { enabled: async ({ requestContext }) => { return requestContext['allowExecution'] === 'true' }, }, } ``` - Added MASTRA_OFFLINE environment variable to disable network fetches for provider data in offline/air-gapped environments. When set to 'true' or '1', all provider sync and auto-refresh operations are skipped while the static provider registry remains fully functional. ([#15101](https://github.com/mastra-ai/mastra/pull/15101)) - Added `WORKSPACE_ACTION` span type for workspace tracing. All workspace tools (filesystem, sandbox, search, skill, LSP) now create child spans with metadata including category, operation, file paths, commands, exit codes, bytes transferred, and duration. Added `startWorkspaceSpan()` utility for span creation with graceful no-op when tracing is inactive. ([#14554](https://github.com/mastra-ai/mastra/pull/14554)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`f32b9e1`](https://github.com/mastra-ai/mastra/commit/f32b9e115a3c754d1c8cfa3f4256fba87b09cfb7)) - Added `setBrowser` method to Agent class for runtime browser configuration ([#15036](https://github.com/mastra-ai/mastra/pull/15036)) - Fixed resourceId being overwritten to NULL during workflow execution of loop, foreach, parallel, and conditional steps. The resourceId set via `createRun()` is now correctly preserved throughout the entire workflow lifecycle. ([#14958](https://github.com/mastra-ai/mastra/pull/14958)) - Fix onScorerRun hook payloads missing threadId and resourceId so hooks now receive correct identifiers ([#13835](https://github.com/mastra-ai/mastra/pull/13835)) - Fixed AGENTS.md reminder persistence and deduplication across turns by storing reminder metadata on injected user messages. ([#15100](https://github.com/mastra-ai/mastra/pull/15100)) - Fixed sub-agent memory isolation so sub-agents no longer inherit parent requestContext keys and write to their own threads when `mastra__threadId` or `mastra__resourceId` are set. ([#15022](https://github.com/mastra-ai/mastra/pull/15022)) - Fully isolate `async_hooks` from shared chunks so importing `@mastra/core` in browser bundles never pulls in the Node-only dependency ([#15074](https://github.com/mastra-ai/mastra/pull/15074)) - Fixed `__setLogger` passing a raw string to `logger.child()`, which caused PinoLogger to serialize each character as a separate log field (e.g. `0: "B", 1: "U", 2: "N"...`). Now passes `{ component }` object instead. ([#15079](https://github.com/mastra-ai/mastra/pull/15079)) - Fixed observational memory buffering so sealed assistant chunks stay split instead of being merged back into one persisted message during long tool runs. ([#14995](https://github.com/mastra-ai/mastra/pull/14995)) - Fixed image attachments from @ai-sdk/react and @ai-sdk/vue clients throwing errors. File parts using the AI SDK v5 UIMessage format ({ type: 'file', url: '...', mediaType: '...' }) are now handled correctly when routed through the ModelMessage code path. ([#14734](https://github.com/mastra-ai/mastra/pull/14734)) - Fixed a browser bundling issue where importing `@mastra/core` could pull in a Node-only dependency. ([#15072](https://github.com/mastra-ai/mastra/pull/15072)) ## 1.23.0-alpha.9 ### Minor Changes - Added `UpdateObservationalMemoryConfigInput` type, `updateObservationalMemoryConfig()` method stub, and `deepMergeConfig()` utility to the `MemoryStorage` base class. These additions support per-record observational memory config overrides introduced in `@mastra/memory`. ([#15115](https://github.com/mastra-ai/mastra/pull/15115)) ## 1.23.0-alpha.8 ### Minor Changes - Memory operations now produce observability spans and accept an optional `observabilityContext` parameter. A new `MEMORY_OPERATION` span type, `MEMORY` entity type, and `MemoryOperationAttributes` interface let you identify memory spans in your traces. Agent and network code automatically threads observability context into memory calls. ([#14305](https://github.com/mastra-ai/mastra/pull/14305)) **New observability identifiers:** - `SpanType.MEMORY_OPERATION` — span type for all memory operations - `EntityType.MEMORY` — entity type for memory spans - `MemoryOperationAttributes` — typed attributes (operationType, messageCount, embeddingTokens, etc.) **Updated abstract methods** on `MastraMemory` now accept optional `observabilityContext`: ```typescript import type { ObservabilityContext } from '@mastra/core/observability'; // All four methods accept an optional observabilityContext await memory.recall({ threadId: 'thread-1', observabilityContext: { tracingContext: { currentSpan: parentSpan } }, }); await memory.saveMessages({ messages, observabilityContext: { tracingContext: { currentSpan: parentSpan } }, }); await memory.deleteMessages(['msg-1'], { tracingContext: { currentSpan: parentSpan } }); await memory.updateWorkingMemory({ threadId: 'thread-1', workingMemory: 'content', observabilityContext: { tracingContext: { currentSpan: parentSpan } }, }); ``` - Added MASTRA_OFFLINE environment variable to disable network fetches for provider data in offline/air-gapped environments. When set to 'true' or '1', all provider sync and auto-refresh operations are skipped while the static provider registry remains fully functional. ([#15101](https://github.com/mastra-ai/mastra/pull/15101)) - Added `WORKSPACE_ACTION` span type for workspace tracing. All workspace tools (filesystem, sandbox, search, skill, LSP) now create child spans with metadata including category, operation, file paths, commands, exit codes, bytes transferred, and duration. Added `startWorkspaceSpan()` utility for span creation with graceful no-op when tracing is inactive. ([#14554](https://github.com/mastra-ai/mastra/pull/14554)) ### Patch Changes - Fixed AGENTS.md reminder persistence and deduplication across turns by storing reminder metadata on injected user messages. ([#15100](https://github.com/mastra-ai/mastra/pull/15100)) ## 1.23.0-alpha.7 ### Patch Changes - Fixed resourceId being overwritten to NULL during workflow execution of loop, foreach, parallel, and conditional steps. The resourceId set via `createRun()` is now correctly preserved throughout the entire workflow lifecycle. ([#14958](https://github.com/mastra-ai/mastra/pull/14958)) - Fix onScorerRun hook payloads missing threadId and resourceId so hooks now receive correct identifiers ([#13835](https://github.com/mastra-ai/mastra/pull/13835)) ## 1.23.0-alpha.6 ### Patch Changes - Added `setBrowser` method to Agent class for runtime browser configuration ([#15036](https://github.com/mastra-ai/mastra/pull/15036)) ## 1.23.0-alpha.5 ### Patch Changes - Fully isolate `async_hooks` from shared chunks so importing `@mastra/core` in browser bundles never pulls in the Node-only dependency ([#15074](https://github.com/mastra-ai/mastra/pull/15074)) - Fixed `__setLogger` passing a raw string to `logger.child()`, which caused PinoLogger to serialize each character as a separate log field (e.g. `0: "B", 1: "U", 2: "N"...`). Now passes `{ component }` object instead. ([#15079](https://github.com/mastra-ai/mastra/pull/15079)) ## 1.23.0-alpha.4 ### Patch Changes - Fixed a browser bundling issue where importing `@mastra/core` could pull in a Node-only dependency. ([#15072](https://github.com/mastra-ai/mastra/pull/15072)) ## 1.23.0-alpha.3 ### Patch Changes - Fixed image attachments from @ai-sdk/react and @ai-sdk/vue clients throwing errors. File parts using the AI SDK v5 UIMessage format ({ type: 'file', url: '...', mediaType: '...' }) are now handled correctly when routed through the ModelMessage code path. ([#14734](https://github.com/mastra-ai/mastra/pull/14734)) ## 1.23.0-alpha.2 ## 1.23.0-alpha.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`f32b9e1`](https://github.com/mastra-ai/mastra/commit/f32b9e115a3c754d1c8cfa3f4256fba87b09cfb7)) ## 1.23.0-alpha.0 ### Minor Changes - Added dynamic function support for workspace tool config. The `enabled`, `requireApproval`, and `requireReadBeforeWrite` options now accept async functions in addition to static booleans, enabling context-aware tool behavior like disabling tools based on user tier or requiring approval only for certain file paths. ([#14528](https://github.com/mastra-ai/mastra/pull/14528)) **Example** ```typescript tools: { [WORKSPACE_TOOLS.FILESYSTEM.WRITE_FILE]: { requireApproval: async ({ args }) => { return (args.path as string).startsWith('/protected') }, }, [WORKSPACE_TOOLS.SANDBOX.EXECUTE_COMMAND]: { enabled: async ({ requestContext }) => { return requestContext['allowExecution'] === 'true' }, }, } ``` ### Patch Changes - Fixed sub-agent memory isolation so sub-agents no longer inherit parent requestContext keys and write to their own threads when `mastra__threadId` or `mastra__resourceId` are set. ([#15022](https://github.com/mastra-ai/mastra/pull/15022)) - Fixed observational memory buffering so sealed assistant chunks stay split instead of being merged back into one persisted message during long tool runs. ([#14995](https://github.com/mastra-ai/mastra/pull/14995)) ## 1.22.0 ### Minor Changes - Add browser integration support for agents ([#14938](https://github.com/mastra-ai/mastra/pull/14938)) - New `browser` property on agents for browser automation toolsets - `MastraBrowser` base class with screencast streaming, input injection, and state management - `ThreadManager` for browser session isolation per thread - Browser tools are automatically available when a browser is configured on an agent - New `@mastra/core/browser` export with browser types and utilities - Added agent-level chat channels via Vercel Chat SDK adapters. ([#14642](https://github.com/mastra-ai/mastra/pull/14642)) Agents can now communicate over messaging platforms like Slack, Discord, and Telegram using the `channels` configuration option. Each agent manages its own adapters and automatically handles event routing, thread mapping, tool generation, and streaming responses. **Key features:** - Configure channels directly on agents with `channels: { adapters: { slack: createSlackAdapter(), discord: createDiscordAdapter() } }` - Automatic webhook route generation at `/api/agents/{agentId}/channels/{platform}/webhook` - Tool approval buttons with `requireApproval: true` tools rendered as interactive cards - Multi-user thread awareness with author prefixes for group conversations - Thread subscriptions persisted via Mastra storage (survives restarts) **New exports from `@mastra/core/channels`:** - `AgentChannels` — internal class managing Chat SDK instance and event handlers - `ChatChannelProcessor` — input processor injecting channel context into prompts - `MastraStateAdapter` — StateAdapter backed by Mastra storage - Added expectedTrajectory support to dataset items across all storage backends and API layer. Dataset items can now store trajectory expectations that define expected agent execution steps, ordering, and constraints for trajectory-based evaluation scoring. ([#14902](https://github.com/mastra-ai/mastra/pull/14902)) - Added Mastra Gateway as a model router provider. ([#14952](https://github.com/mastra-ai/mastra/pull/14952)) The Mastra Gateway enables access to multiple LLM providers through a unified endpoint at server.mastra.ai, supporting both API key and OAuth authentication flows. **New exports:** - `MastraGateway` — gateway provider class for routing models through the Mastra Gateway service - `MastraGatewayConfig` — configuration type with `apiKey`, `baseUrl`, and `customFetch` options - `GATEWAY_AUTH_HEADER` — constant for the custom gateway authentication header (`X-Memory-Gateway-Authorization`) - `GatewayRegistry` — manages gateway-based provider discovery with atomic file caching - `parseModelString` — utility to parse provider/model ID strings ```ts import { MastraGateway } from '@mastra/core/llm'; const gateway = new MastraGateway({ apiKey: process.env.MASTRA_GATEWAY_API_KEY, }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`81e4259`](https://github.com/mastra-ai/mastra/commit/81e425939b4ceeb4f586e9b6d89c3b1c1f2d2fe7)) - **Fixed streamed finish metadata being dropped from final model results** ([#13914](https://github.com/mastra-ai/mastra/pull/13914)) Provider-specific metadata from streamed `finish` events is now preserved consistently across final output results, buffered steps, and `onFinish` callbacks. This improves compatibility with providers like Anthropic and Google/Gemini when they attach cache, reasoning, or other finish-time metadata during streaming. - Fixed providerMetadata (e.g. Gemini thoughtSignature) being lost on assistant file parts during multi-turn conversations. This resolves 'Image part is missing a thought_signature' errors when round-tripping model-generated images with Gemini 3.x models. ([#14972](https://github.com/mastra-ai/mastra/pull/14972)) - Fixed `LocalSandbox` `execute_command` with a relative `cwd` (e.g. `"."` or `"./subdir"`) resolving against the server's working directory instead of the sandbox's configured `workingDirectory`. ([#14964](https://github.com/mastra-ai/mastra/pull/14964)) - Fixed tool result persistence so savePerStep keeps raw tool output while prompt messages still use stored model output. ([#14966](https://github.com/mastra-ai/mastra/pull/14966)) - Fixed skill path resolution when disambiguating same-named skills. The `skill` tool now correctly resolves skills by path when the location includes the `/SKILL.md` suffix. Fixes #14918. ([#14951](https://github.com/mastra-ai/mastra/pull/14951)) - Fixed thread titles not persisting when generated during async buffered observation. Titles now update immediately when the observer produces them, rather than being lost until activation. ([#14992](https://github.com/mastra-ai/mastra/pull/14992)) ## 1.22.0-alpha.3 ## 1.22.0-alpha.2 ### Minor Changes - Add browser integration support for agents ([#14938](https://github.com/mastra-ai/mastra/pull/14938)) - New `browser` property on agents for browser automation toolsets - `MastraBrowser` base class with screencast streaming, input injection, and state management - `ThreadManager` for browser session isolation per thread - Browser tools are automatically available when a browser is configured on an agent - New `@mastra/core/browser` export with browser types and utilities - Added agent-level chat channels via Vercel Chat SDK adapters. ([#14642](https://github.com/mastra-ai/mastra/pull/14642)) Agents can now communicate over messaging platforms like Slack, Discord, and Telegram using the `channels` configuration option. Each agent manages its own adapters and automatically handles event routing, thread mapping, tool generation, and streaming responses. **Key features:** - Configure channels directly on agents with `channels: { adapters: { slack: createSlackAdapter(), discord: createDiscordAdapter() } }` - Automatic webhook route generation at `/api/agents/{agentId}/channels/{platform}/webhook` - Tool approval buttons with `requireApproval: true` tools rendered as interactive cards - Multi-user thread awareness with author prefixes for group conversations - Thread subscriptions persisted via Mastra storage (survives restarts) **New exports from `@mastra/core/channels`:** - `AgentChannels` — internal class managing Chat SDK instance and event handlers - `ChatChannelProcessor` — input processor injecting channel context into prompts - `MastraStateAdapter` — StateAdapter backed by Mastra storage - Added expectedTrajectory support to dataset items across all storage backends and API layer. Dataset items can now store trajectory expectations that define expected agent execution steps, ordering, and constraints for trajectory-based evaluation scoring. ([#14902](https://github.com/mastra-ai/mastra/pull/14902)) ### Patch Changes - Fixed providerMetadata (e.g. Gemini thoughtSignature) being lost on assistant file parts during multi-turn conversations. This resolves 'Image part is missing a thought_signature' errors when round-tripping model-generated images with Gemini 3.x models. ([#14972](https://github.com/mastra-ai/mastra/pull/14972)) - Fixed skill path resolution when disambiguating same-named skills. The `skill` tool now correctly resolves skills by path when the location includes the `/SKILL.md` suffix. Fixes #14918. ([#14951](https://github.com/mastra-ai/mastra/pull/14951)) - Fixed thread titles not persisting when generated during async buffered observation. Titles now update immediately when the observer produces them, rather than being lost until activation. ([#14992](https://github.com/mastra-ai/mastra/pull/14992)) ## 1.22.0-alpha.1 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`81e4259`](https://github.com/mastra-ai/mastra/commit/81e425939b4ceeb4f586e9b6d89c3b1c1f2d2fe7)) - **Fixed streamed finish metadata being dropped from final model results** ([#13914](https://github.com/mastra-ai/mastra/pull/13914)) Provider-specific metadata from streamed `finish` events is now preserved consistently across final output results, buffered steps, and `onFinish` callbacks. This improves compatibility with providers like Anthropic and Google/Gemini when they attach cache, reasoning, or other finish-time metadata during streaming. ## 1.22.0-alpha.0 ### Minor Changes - Added Mastra Gateway as a model router provider. ([#14952](https://github.com/mastra-ai/mastra/pull/14952)) The Mastra Gateway enables access to multiple LLM providers through a unified endpoint at server.mastra.ai, supporting both API key and OAuth authentication flows. **New exports:** - `MastraGateway` — gateway provider class for routing models through the Mastra Gateway service - `MastraGatewayConfig` — configuration type with `apiKey`, `baseUrl`, and `customFetch` options - `GATEWAY_AUTH_HEADER` — constant for the custom gateway authentication header (`X-Memory-Gateway-Authorization`) - `GatewayRegistry` — manages gateway-based provider discovery with atomic file caching - `parseModelString` — utility to parse provider/model ID strings ```ts import { MastraGateway } from '@mastra/core/llm'; const gateway = new MastraGateway({ apiKey: process.env.MASTRA_GATEWAY_API_KEY, }); ``` ### Patch Changes - Fixed `LocalSandbox` `execute_command` with a relative `cwd` (e.g. `"."` or `"./subdir"`) resolving against the server's working directory instead of the sandbox's configured `workingDirectory`. ([#14964](https://github.com/mastra-ai/mastra/pull/14964)) - Fixed tool result persistence so savePerStep keeps raw tool output while prompt messages still use stored model output. ([#14966](https://github.com/mastra-ai/mastra/pull/14966)) ## 1.21.0 ### Minor Changes - Adds a new `trimMode` option with a `contiguous` strategy that preserves a continuous suffix of messages by stopping at the first message that exceeds the token budget. Default behavior remains unchanged. ([#14801](https://github.com/mastra-ai/mastra/pull/14801)) - Added scorer tracing and exported scores through the observability bus. ([#14920](https://github.com/mastra-ai/mastra/pull/14920)) **What changed** - Added `SCORER_RUN` and `SCORER_STEP` spans for scorer execution. - Exported scorer results through `mastra.observability.addScore()` when a target trace is available. - Added score metadata for scorer name, target entity type, target scope, and scorer trace links. - Deprecated the legacy scores-store helper while keeping the legacy write path during the transition. **Why** This makes scorer execution easier to debug and starts moving scorer results onto the new observability-based score pipeline. **Example** ```ts await scorer.run({ input, output, scoreSource: 'experiment', targetScope: 'span', targetTraceId: traceId, targetSpanId: spanId, }); ``` - Added component-scoped logging with custom filtering to ConsoleLogger ([#14947](https://github.com/mastra-ai/mastra/pull/14947)) ```typescript new ConsoleLogger({ level: 'debug', filter: ({ component }) => component === 'AGENT', }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`9a43b47`](https://github.com/mastra-ai/mastra/commit/9a43b476465e86c9aca381c2831066b5c33c999a)) - Fixed score and feedback emission to support live correlation context and unanchored annotations. ([#14942](https://github.com/mastra-ai/mastra/pull/14942)) - Fixed a crash when using provider-defined tools (like `openai.tools.webSearch()`) with `autoResumeSuspendedTools` enabled. ([#14940](https://github.com/mastra-ai/mastra/pull/14940)) - Fixed an AsyncLocalStorage runtime error when importing `@mastra/core/observability` in browser environments. ([#14948](https://github.com/mastra-ai/mastra/pull/14948)) - Fixed assistant message prefill error crashing sessions. When a model does not support assistant message prefill, the harness now automatically retries with a user message instead of failing. ([#14953](https://github.com/mastra-ai/mastra/pull/14953)) - Added error name and stack trace to SpanErrorInfo, allowing exporters to access the original error class name and stack trace for richer error reporting. ([#14944](https://github.com/mastra-ai/mastra/pull/14944)) - Fixed workflow spans missing entityName, which caused the metrics dashboard to show 'unknown' for workflow trace volume ([#14949](https://github.com/mastra-ai/mastra/pull/14949)) ## 1.21.0-alpha.2 ### Minor Changes - Adds a new `trimMode` option with a `contiguous` strategy that preserves a continuous suffix of messages by stopping at the first message that exceeds the token budget. Default behavior remains unchanged. ([#14801](https://github.com/mastra-ai/mastra/pull/14801)) - Added component-scoped logging with custom filtering to ConsoleLogger ([#14947](https://github.com/mastra-ai/mastra/pull/14947)) ```typescript new ConsoleLogger({ level: 'debug', filter: ({ component }) => component === 'AGENT', }); ``` ### Patch Changes - Fixed score and feedback emission to support live correlation context and unanchored annotations. ([#14942](https://github.com/mastra-ai/mastra/pull/14942)) - Fixed a crash when using provider-defined tools (like `openai.tools.webSearch()`) with `autoResumeSuspendedTools` enabled. ([#14940](https://github.com/mastra-ai/mastra/pull/14940)) - Fixed an AsyncLocalStorage runtime error when importing `@mastra/core/observability` in browser environments. ([#14948](https://github.com/mastra-ai/mastra/pull/14948)) - Fixed assistant message prefill error crashing sessions. When a model does not support assistant message prefill, the harness now automatically retries with a user message instead of failing. ([#14953](https://github.com/mastra-ai/mastra/pull/14953)) - Added error name and stack trace to SpanErrorInfo, allowing exporters to access the original error class name and stack trace for richer error reporting. ([#14944](https://github.com/mastra-ai/mastra/pull/14944)) - Fixed workflow spans missing entityName, which caused the metrics dashboard to show 'unknown' for workflow trace volume ([#14949](https://github.com/mastra-ai/mastra/pull/14949)) ## 1.21.0-alpha.1 ### Minor Changes - Added scorer tracing and exported scores through the observability bus. ([#14920](https://github.com/mastra-ai/mastra/pull/14920)) **What changed** - Added `SCORER_RUN` and `SCORER_STEP` spans for scorer execution. - Exported scorer results through `mastra.observability.addScore()` when a target trace is available. - Added score metadata for scorer name, target entity type, target scope, and scorer trace links. - Deprecated the legacy scores-store helper while keeping the legacy write path during the transition. **Why** This makes scorer execution easier to debug and starts moving scorer results onto the new observability-based score pipeline. **Example** ```ts await scorer.run({ input, output, scoreSource: 'experiment', targetScope: 'span', targetTraceId: traceId, targetSpanId: spanId, }); ``` ## 1.21.0-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`9a43b47`](https://github.com/mastra-ai/mastra/commit/9a43b476465e86c9aca381c2831066b5c33c999a)) ## 1.20.0 ### Minor Changes - Added DualLogger that transparently forwards all infrastructure logger calls (debug, info, warn, error, trackException) to the observability system (loggerVNext). This means all internal Mastra logs now automatically appear in your observability storage (e.g. DuckDB) without any code changes. ([#14899](https://github.com/mastra-ai/mastra/pull/14899)) **trackException** now extracts structured error data (errorId, domain, category, details, cause) and forwards it as an error-level log to observability storage, so exceptions are queryable alongside regular logs. Added `logging` config option to ObservabilityInstance for controlling which logs reach observability storage: ```ts new Observability({ instance: new MastraObservability({ logging: { enabled: true, // set to false to disable log forwarding level: 'info', // minimum level: 'debug' | 'info' | 'warn' | 'error' | 'fatal' }, }), }); ``` - Add `registerExporter` method to the observability stack and Mastra class for runtime exporter registration ([#14730](https://github.com/mastra-ai/mastra/pull/14730)) ### Patch Changes - Fixed Anthropic API rejection of empty user text content blocks. ([#14906](https://github.com/mastra-ai/mastra/pull/14906)) User messages containing only empty text parts (e.g., `{ type: 'text', text: '' }`) are now filtered out before being sent to the LLM. This prevents the "text content blocks must be non-empty" error that could occur when corrupted messages existed in the database. Note: The root cause of how these empty user messages get persisted is still under investigation. - Improved the `pattern` field description in the `list_files` workspace tool to prevent AI models from passing `"*"` when they intend to match all files. The description now clarifies that omitting `pattern` lists all files, that `*` only matches within a single directory level (standard glob), and that glob patterns only filter files while directories are always shown. ([#14897](https://github.com/mastra-ai/mastra/pull/14897)) - Added a `lastMessageOnly` option to the LLM-backed moderation, language detection, prompt injection, PII, and system prompt scrubber processors so they can inspect only the newest message instead of re-checking the full conversation on every run. ([#14903](https://github.com/mastra-ai/mastra/pull/14903)) - Fixed providerMetadata (e.g. Gemini's thoughtSignature) being stripped from tool-call events when using the non-streaming (generate) code path ([#14900](https://github.com/mastra-ai/mastra/pull/14900)) - Standardized all logger calls across the codebase to use static string messages with structured data objects. Dynamic values are now passed as key-value pairs in the second argument instead of being interpolated into template literal strings. This improves log filterability and searchability in observability storage. ([#14899](https://github.com/mastra-ai/mastra/pull/14899)) Removed ~150 redundant or noisy log calls including duplicate error logging after trackException and verbose in-memory storage CRUD traces. - Fixed duplicate OpenAI item ID errors when using web search. When OpenAI streams responses with web search citations, it interleaves source chunks with text, causing multiple message parts to share the same item ID. This resulted in 'Duplicate item found' errors on subsequent requests. The fix prevents text flushing on source chunks and merges any existing duplicate parts. ([#14908](https://github.com/mastra-ai/mastra/pull/14908)) ## 1.20.0-alpha.0 ### Minor Changes - Added DualLogger that transparently forwards all infrastructure logger calls (debug, info, warn, error, trackException) to the observability system (loggerVNext). This means all internal Mastra logs now automatically appear in your observability storage (e.g. DuckDB) without any code changes. ([#14899](https://github.com/mastra-ai/mastra/pull/14899)) **trackException** now extracts structured error data (errorId, domain, category, details, cause) and forwards it as an error-level log to observability storage, so exceptions are queryable alongside regular logs. Added `logging` config option to ObservabilityInstance for controlling which logs reach observability storage: ```ts new Observability({ instance: new MastraObservability({ logging: { enabled: true, // set to false to disable log forwarding level: 'info', // minimum level: 'debug' | 'info' | 'warn' | 'error' | 'fatal' }, }), }); ``` - Add `registerExporter` method to the observability stack and Mastra class for runtime exporter registration ([#14730](https://github.com/mastra-ai/mastra/pull/14730)) ### Patch Changes - Fixed Anthropic API rejection of empty user text content blocks. ([#14906](https://github.com/mastra-ai/mastra/pull/14906)) User messages containing only empty text parts (e.g., `{ type: 'text', text: '' }`) are now filtered out before being sent to the LLM. This prevents the "text content blocks must be non-empty" error that could occur when corrupted messages existed in the database. Note: The root cause of how these empty user messages get persisted is still under investigation. - Improved the `pattern` field description in the `list_files` workspace tool to prevent AI models from passing `"*"` when they intend to match all files. The description now clarifies that omitting `pattern` lists all files, that `*` only matches within a single directory level (standard glob), and that glob patterns only filter files while directories are always shown. ([#14897](https://github.com/mastra-ai/mastra/pull/14897)) - Added a `lastMessageOnly` option to the LLM-backed moderation, language detection, prompt injection, PII, and system prompt scrubber processors so they can inspect only the newest message instead of re-checking the full conversation on every run. ([#14903](https://github.com/mastra-ai/mastra/pull/14903)) - Fixed providerMetadata (e.g. Gemini's thoughtSignature) being stripped from tool-call events when using the non-streaming (generate) code path ([#14900](https://github.com/mastra-ai/mastra/pull/14900)) - Standardized all logger calls across the codebase to use static string messages with structured data objects. Dynamic values are now passed as key-value pairs in the second argument instead of being interpolated into template literal strings. This improves log filterability and searchability in observability storage. ([#14899](https://github.com/mastra-ai/mastra/pull/14899)) Removed ~150 redundant or noisy log calls including duplicate error logging after trackException and verbose in-memory storage CRUD traces. - Fixed duplicate OpenAI item ID errors when using web search. When OpenAI streams responses with web search citations, it interleaves source chunks with text, causing multiple message parts to share the same item ID. This resulted in 'Duplicate item found' errors on subsequent requests. The fix prevents text flushing on source chunks and merges any existing duplicate parts. ([#14908](https://github.com/mastra-ai/mastra/pull/14908)) ## 1.19.0 ### Minor Changes - feat(memory): add minMessages option to generateTitle config ([#14778](https://github.com/mastra-ai/mastra/pull/14778)) Delay automatic title generation until a minimum number of messages is reached, improving title quality and reducing unnecessary LLM calls. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`180aaaf`](https://github.com/mastra-ai/mastra/commit/180aaaf4d0903d33a49bc72de2d40ca69a5bc599)) - Streaming traces now end correctly when a model call fails or a request is aborted, so they no longer remain stuck "in progress" in observability tools. ([#14661](https://github.com/mastra-ai/mastra/pull/14661)) - Fix getWorkflowRunById with withNestedWorkflows not returning nested steps for branch sub-workflows ([#14713](https://github.com/mastra-ai/mastra/pull/14713)) - Tools that return objects with circular references no longer crash the agent with "Converting circular structure to JSON". Circular parts are replaced with `"[Circular]"` and the conversation continues normally. ([#14535](https://github.com/mastra-ai/mastra/pull/14535)) - Fixed crashes when using `ModelRouterLanguageModel` with AI SDK v6's `generateObject()` or `generateText()`. The model router now correctly preserves usage and metadata from underlying models. ([#14283](https://github.com/mastra-ai/mastra/pull/14283)) - Agents using structured output no longer fail when workflow tools are present. Setting toolChoice to 'none' now correctly prevents tools from being sent to the provider, fixing errors from providers like Gemini that reject structured output requests when tools are included. ([#14466](https://github.com/mastra-ai/mastra/pull/14466)) - Sub-agent tool calls no longer fail when LLMs use `query`, `message`, or `input` instead of `prompt` during repeated sub-agent calls via custom gateways. These common aliases are now automatically recognized and mapped to `prompt` when the schema expects it. ([#14219](https://github.com/mastra-ai/mastra/pull/14219)) - Fixed an issue where supervisor agent messages were being saved to the sub-agent thread, causing duplicate tool call badges to appear in the chat history when sub-agents are invoked multiple times. ([#13881](https://github.com/mastra-ai/mastra/pull/13881)) - Fixed workspace vector indexing silently swallowing embedder and search engine errors during auto-indexing. File-read errors (binary files, invalid UTF-8) are still skipped, but indexing failures are now logged as warnings instead of being silently ignored. ([#14786](https://github.com/mastra-ai/mastra/pull/14786)) - Fixed incorrect type cast for sub-agent context messages. The context option for new API methods (generate, stream, resumeGenerate, resumeStream) now correctly casts to ModelMessage[] instead of CoreMessage[]. ([#14895](https://github.com/mastra-ai/mastra/pull/14895)) ## 1.19.0-alpha.2 ### Minor Changes - feat(memory): add minMessages option to generateTitle config ([#14778](https://github.com/mastra-ai/mastra/pull/14778)) Delay automatic title generation until a minimum number of messages is reached, improving title quality and reducing unnecessary LLM calls. ### Patch Changes - Sub-agent tool calls no longer fail when LLMs use `query`, `message`, or `input` instead of `prompt` during repeated sub-agent calls via custom gateways. These common aliases are now automatically recognized and mapped to `prompt` when the schema expects it. ([#14219](https://github.com/mastra-ai/mastra/pull/14219)) ## 1.18.1-alpha.1 ### Patch Changes - Streaming traces now end correctly when a model call fails or a request is aborted, so they no longer remain stuck "in progress" in observability tools. ([#14661](https://github.com/mastra-ai/mastra/pull/14661)) - Fix getWorkflowRunById with withNestedWorkflows not returning nested steps for branch sub-workflows ([#14713](https://github.com/mastra-ai/mastra/pull/14713)) - Tools that return objects with circular references no longer crash the agent with "Converting circular structure to JSON". Circular parts are replaced with `"[Circular]"` and the conversation continues normally. ([#14535](https://github.com/mastra-ai/mastra/pull/14535)) - Fixed crashes when using `ModelRouterLanguageModel` with AI SDK v6's `generateObject()` or `generateText()`. The model router now correctly preserves usage and metadata from underlying models. ([#14283](https://github.com/mastra-ai/mastra/pull/14283)) - Agents using structured output no longer fail when workflow tools are present. Setting toolChoice to 'none' now correctly prevents tools from being sent to the provider, fixing errors from providers like Gemini that reject structured output requests when tools are included. ([#14466](https://github.com/mastra-ai/mastra/pull/14466)) - Fixed an issue where supervisor agent messages were being saved to the sub-agent thread, causing duplicate tool call badges to appear in the chat history when sub-agents are invoked multiple times. ([#13881](https://github.com/mastra-ai/mastra/pull/13881)) - Fixed workspace vector indexing silently swallowing embedder and search engine errors during auto-indexing. File-read errors (binary files, invalid UTF-8) are still skipped, but indexing failures are now logged as warnings instead of being silently ignored. ([#14786](https://github.com/mastra-ai/mastra/pull/14786)) - Fixed incorrect type cast for sub-agent context messages. The context option for new API methods (generate, stream, resumeGenerate, resumeStream) now correctly casts to ModelMessage[] instead of CoreMessage[]. ([#14895](https://github.com/mastra-ai/mastra/pull/14895)) ## 1.18.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`180aaaf`](https://github.com/mastra-ai/mastra/commit/180aaaf4d0903d33a49bc72de2d40ca69a5bc599)) ## 1.18.0 ### Minor Changes - Add version-aware code-agent lookup and override version lifecycle support. ([#14776](https://github.com/mastra-ai/mastra/pull/14776)) `Mastra.getAgent(name, version)` and `Mastra.getAgentById(id, version)` can now resolve draft or specific stored override versions when the editor package is configured, and throw a clear error when versioned lookup is requested without the editor. `client.getAgent(id, version)` now carries version selection through agent detail and voice metadata requests, and the `Agent` resource now supports override version management methods including `listVersions`, `createVersion`, `getVersion`, `activateVersion`, `restoreVersion`, `deleteVersion`, and `compareVersions`. `Agent.createVersion(...)` is intentionally limited to code-agent overrideable fields plus version metadata, rather than the full stored-agent configuration surface. - **Trajectory evaluation**: Added trajectory types and trace-based extraction for evaluating agent and workflow execution paths. ([#14697](https://github.com/mastra-ai/mastra/pull/14697)) `TrajectoryStep` models each step in an execution as a typed object — tool calls, model generations, agent runs, workflow steps, and control flow nodes each have their own variant with relevant properties (e.g., `toolArgs`/`toolResult` for tool calls, `modelId`/`promptTokens` for model generations). Steps can be nested via `children` to represent hierarchical execution. `TrajectoryExpectation` lets you define what a good trajectory looks like — expected steps, ordering, step/token/duration budgets, blacklisted tools, and retry thresholds. `ExpectedStep` provides a simple way to define expected steps by name and optional stepType, with support for nested expectations via `children` to set different evaluation rules at each level of the hierarchy. **Trace-based extraction:** `extractTrajectoryFromTrace()` builds hierarchical trajectories from observability trace spans. The `runEvals` pipeline automatically uses this when storage is configured, capturing the full execution tree including nested agent runs and tool calls. Falls back to `extractTrajectory` (agents) or `extractWorkflowTrajectory` (workflows) when storage is unavailable. **Pipeline:** `expectedTrajectory` flows from dataset items through `runEvals` to trajectory scorers. Added `trajectory` key to both `AgentScorerConfig` and `WorkflowScorerConfig`. - Added support for attaching scorers to datasets. Scorers attached to a dataset automatically run when an experiment is triggered, alongside any scorers specified at trigger time. New `scorerIds` field on `DatasetRecord`, `CreateDatasetInput`, and `UpdateDatasetInput` types. ([#14783](https://github.com/mastra-ai/mastra/pull/14783)) - Add `lsp_inspect` tool for LSP-based code inspection with hover, definition, and implementation queries ([#14565](https://github.com/mastra-ai/mastra/pull/14565)) - Added `disableBuiltinTools` to `HarnessConfig` so you can disable specific built-in harness tools. ([#14227](https://github.com/mastra-ai/mastra/pull/14227)) Example: ```ts new Harness({ disableBuiltinTools: ['submit_plan', 'subagent'] }); ``` - Added SkillSearchProcessor for on-demand skill discovery. Instead of injecting all skill metadata upfront, agents get `search_skills` and `load_skill` meta-tools to find and load skills on demand with thread-scoped state and TTL cleanup. ([#14596](https://github.com/mastra-ai/mastra/pull/14596)) **Example** ```typescript import { SkillSearchProcessor } from '@mastra/core/processors'; const skillSearch = new SkillSearchProcessor({ workspace, search: { topK: 5 }, }); const agent = new Agent({ workspace, inputProcessors: [skillSearch], }); ``` - Added public score and feedback analytics APIs to observability storage: ([#14861](https://github.com/mastra-ai/mastra/pull/14861)) `getScoreAggregate` / `getFeedbackAggregate` for counts, sums, averages, minimums, maximums, or latest values; `getScoreBreakdown` / `getFeedbackBreakdown` for grouped results by dimension; `getScoreTimeSeries` / `getFeedbackTimeSeries` for time-bucketed trends; and `getScorePercentiles` / `getFeedbackPercentiles` for percentile series such as p50 and p95. ```ts await observability.getScoreTimeSeries({ scorerId: 'relevance', interval: '1h', aggregation: 'avg', }); // returns time-bucketed average scores ``` - Added new observability entrypoint APIs for persisted traces. You can now call `mastra.observability.getRecordedTrace({ traceId })` to load a recorded trace, and use optional top-level `mastra.observability.addScore()/addFeedback()` helpers to annotate a persisted trace by ID. ([#14842](https://github.com/mastra-ai/mastra/pull/14842)) - Align observability signal contracts around first-class trace and span fields. ([#14838](https://github.com/mastra-ai/mastra/pull/14838)) **Improved observability signal consistency** Logs, metrics, scores, and feedback now carry `traceId` and `spanId` directly on each signal. Shared correlation metadata stays in `correlationContext`. **Added clearer provenance fields** Score and feedback payloads now support `scoreSource`, `feedbackSource`, and `executionSource` for clearer source tracking. **Migration note** Deprecated fields (like `source` and feedback `userId`) are still accepted for compatibility. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`dc514a8`](https://github.com/mastra-ai/mastra/commit/dc514a83dba5f719172dddfd2c7b858e4943d067)) - Persist observational memory threshold settings across restarts and restore per-thread overrides. ([#14788](https://github.com/mastra-ai/mastra/pull/14788)) - Fixed title generation blocking stream completion. The `generateTitle` LLM call now runs in the background instead of blocking the stream from closing, removing the 2-3 second post-response delay in the UI when memory is enabled. ([#14757](https://github.com/mastra-ai/mastra/pull/14757)) - feat(memory): add recall-tool history retrieval for agents using observational memory ([#14567](https://github.com/mastra-ai/mastra/pull/14567)) Agents that use observational memory can now use the `recall` tool to retrieve history from past conversations, including raw messages, thread listings, and indexed observation-group memories. Enable observational-memory retrieval when listing tools: ```ts const tools = await memory.listTools({ threadId: 'thread_123', resourceId: 'resource_abc', observationalMemory: { retrieval: { vector: true, scope: 'resource' }, }, }); ``` With retrieval enabled, `recall` can browse the current thread, list threads for the current resource, and search indexed observation groups with source ranges. - Added resolvedVersionId to agent run trace span attributes for tracking which agent version was used during execution. ([#14847](https://github.com/mastra-ai/mastra/pull/14847)) - Limit dynamically injected AGENTS.md reminders to 1000 estimated tokens by default and tell mastracode observational memory to ignore those ephemeral reminder messages. ([#14790](https://github.com/mastra-ai/mastra/pull/14790)) - Fixed missing `TRequestContext` type parameter on `DynamicArgument` fields in `AgentConfig`. Previously, only `instructions` and `tools` correctly propagated the `requestContextSchema` type to their dynamic function callbacks. Now all dynamic fields — `model`, `workflows`, `workspace`, `agents`, `memory`, `scorers`, `defaultGenerateOptionsLegacy`, `defaultStreamOptionsLegacy`, `defaultOptions`, `defaultNetworkOptions`, `inputProcessors`, and `outputProcessors` — properly type `requestContext` based on the agent's `requestContextSchema`. ([#14582](https://github.com/mastra-ai/mastra/pull/14582)) **Before:** ```typescript const agent = new Agent({ requestContextSchema: z.object({ userId: z.string() }), workspace: ({ requestContext }) => { requestContext.get('userId'); // typed as `unknown` }, }); ``` **After:** ```typescript const agent = new Agent({ requestContextSchema: z.object({ userId: z.string() }), workspace: ({ requestContext }) => { requestContext.get('userId'); // typed as `string` }, }); ``` - Fixed resuming suspended tool calls with `resumeStream` or `approveToolCall` failing with a TripWire when input processors (e.g. TokenLimiterProcessor) are enabled on the agent. ([#14561](https://github.com/mastra-ai/mastra/pull/14561)) - Fixed `Harness.listThreads()` so callers can request threads across all resources. ([#14690](https://github.com/mastra-ai/mastra/pull/14690)) - Fixed agent run traces not appearing in Datadog and other observability backends when LLM calls fail. Previously, an API error during streaming would leave the root AGENT_RUN span open indefinitely, causing the entire trace tree to be silently dropped by exporters that wait for the root span to close. Failed agent runs now correctly end the span with error information, making failures visible in your observability dashboard. ([#14850](https://github.com/mastra-ai/mastra/pull/14850)) - Fixed streaming delegation to propagate output processor modifications to the supervisor. Previously, when a sub-agent had an output processor that modified text via `processOutputResult`, the supervisor received the raw LLM output instead of the processed text. The processed text was only saved to the sub-agent's memory. Now the supervisor correctly receives the output-processor-modified text from delegated sub-agents in the streaming path. ([#14731](https://github.com/mastra-ai/mastra/pull/14731)) - Fixed Harness `stateSchema` typing to accept Zod schemas with `.default()`, `.optional()`, and `.transform()` modifiers. Previously, these modifiers caused TypeScript errors because the type system forced schema Input and Output types to be identical. Now `stateSchema` correctly accepts any schema regardless of input type divergence. ([#14606](https://github.com/mastra-ai/mastra/pull/14606)) - Add `getReviewSummary()` to experiments storage for aggregating review status counts ([#14649](https://github.com/mastra-ai/mastra/pull/14649)) Query experiment results grouped by experiment ID, returning counts of `needs-review`, `reviewed`, and `complete` items in a single query instead of fetching all results client-side. ```ts const summary = await storage.experiments.getReviewSummary(); // [{ experimentId: 'exp-1', needsReview: 3, reviewed: 5, complete: 2, total: 10 }, ...] ``` - Fixed `mcpOptions` (including `serverless: true`) being silently ignored when using the Mastra deployer. The deployer now forwards `mcpOptions` from your server config to the underlying `MastraServer`, so MCP stateless mode works correctly in serverless environments like Cloudflare Workers, Vercel Edge, and AWS Lambda. ([#14810](https://github.com/mastra-ai/mastra/issues/14810)) ([#14812](https://github.com/mastra-ai/mastra/pull/14812)) **What changed:** - Added `mcpOptions` to the `ServerConfig` type so it can be set in `new Mastra({ server: { ... } })` - The deployer now passes `server.mcpOptions` through to `MastraServer` **Example:** ```typescript const mastra = new Mastra({ server: { mcpOptions: { serverless: true, }, }, }); ``` - Added `isValidationError` type guard for the `ValidationError` interface ([#14853](https://github.com/mastra-ai/mastra/pull/14853)) - Fixed models.dev provider URLs to interpolate environment variable placeholders like `${ACCOUNT_ID}` before creating the underlying provider client. ([#14722](https://github.com/mastra-ai/mastra/pull/14722)) - Fixed tool input validation failures not producing observability spans. When input schema validation failed, no TOOL_CALL span was created because span creation happened inside the execution function that ran after validation. Moved span creation before input validation so validation errors are now captured in spans and visible in observability backends like Datadog. ([#14677](https://github.com/mastra-ai/mastra/pull/14677)) - Fixed MODEL_GENERATION and AGENT_RUN spans not reflecting model, provider, parameters, and availableTools overrides from input processors. Traces in Langfuse and other exporters now show the correct model info when a processor dynamically switches models. ([#14705](https://github.com/mastra-ai/mastra/pull/14705)) - Fixed MODEL_GENERATION observability span to include all system messages (tagged and untagged). Previously, working memory and semantic recall instructions were missing from trace inputs because only untagged system messages were captured. ([#14800](https://github.com/mastra-ai/mastra/pull/14800)) - Fixed models.dev auth env selection to prefer auth credentials over URL path identifiers, so Cloudflare Workers AI no longer uses the account ID for authentication. ([#14687](https://github.com/mastra-ai/mastra/pull/14687)) - Fixed processInputStep always receiving an empty steps array. Processors can now inspect previous step results (tool calls, LLM responses) when running inside the agentic loop. ([#14821](https://github.com/mastra-ai/mastra/pull/14821)) - **Configurable weights**: Add `weights` option to `createTrajectoryScorerCode` for controlling how dimension scores are combined. Defaults to `{ accuracy: 0.4, efficiency: 0.3, toolFailures: 0.2, blacklist: 0.1 }`. ([#14740](https://github.com/mastra-ai/mastra/pull/14740)) ```ts const scorer = createTrajectoryScorerCode({ defaults: { steps: [{ name: 'search' }], maxSteps: 5 }, weights: { accuracy: 0.6, efficiency: 0.2, toolFailures: 0.1, blacklist: 0.1 }, }); ``` **ExpectedStep redesign**: `ExpectedStep` is now a discriminated union mirroring `TrajectoryStep`. When you specify a `stepType`, you get autocomplete for that variant's fields (e.g., `toolArgs` for `tool_call`, `modelId` for `model_generation`). The old `data: Record` field is replaced by direct variant fields. ```ts // Before: { name: 'search', stepType: 'tool_call', data: { input: { query: 'weather' } } } // After: { name: 'search', stepType: 'tool_call', toolArgs: { query: 'weather' } } ``` **Remove `compareStepData`**: The `compareStepData` option is removed from `compareTrajectories`, `TrajectoryExpectation`, and all scorers. Data fields are now auto-compared when present on expected steps — if you specify `toolArgs` on an `ExpectedStep`, it will be compared against the actual step. If you omit it, only name and stepType are matched. Also fixes documentation inaccuracies in `trajectory-accuracy.mdx` and `scorer-utils.mdx`. ## 1.18.0-alpha.5 ### Minor Changes - Added public score and feedback analytics APIs to observability storage: ([#14861](https://github.com/mastra-ai/mastra/pull/14861)) `getScoreAggregate` / `getFeedbackAggregate` for counts, sums, averages, minimums, maximums, or latest values; `getScoreBreakdown` / `getFeedbackBreakdown` for grouped results by dimension; `getScoreTimeSeries` / `getFeedbackTimeSeries` for time-bucketed trends; and `getScorePercentiles` / `getFeedbackPercentiles` for percentile series such as p50 and p95. ```ts await observability.getScoreTimeSeries({ scorerId: 'relevance', interval: '1h', aggregation: 'avg', }); // returns time-bucketed average scores ``` ### Patch Changes - Added resolvedVersionId to agent run trace span attributes for tracking which agent version was used during execution. ([#14847](https://github.com/mastra-ai/mastra/pull/14847)) ## 1.18.0-alpha.4 ### Minor Changes - Added support for attaching scorers to datasets. Scorers attached to a dataset automatically run when an experiment is triggered, alongside any scorers specified at trigger time. New `scorerIds` field on `DatasetRecord`, `CreateDatasetInput`, and `UpdateDatasetInput` types. ([#14783](https://github.com/mastra-ai/mastra/pull/14783)) - Added new observability entrypoint APIs for persisted traces. You can now call `mastra.observability.getRecordedTrace({ traceId })` to load a recorded trace, and use optional top-level `mastra.observability.addScore()/addFeedback()` helpers to annotate a persisted trace by ID. ([#14842](https://github.com/mastra-ai/mastra/pull/14842)) - Align observability signal contracts around first-class trace and span fields. ([#14838](https://github.com/mastra-ai/mastra/pull/14838)) **Improved observability signal consistency** Logs, metrics, scores, and feedback now carry `traceId` and `spanId` directly on each signal. Shared correlation metadata stays in `correlationContext`. **Added clearer provenance fields** Score and feedback payloads now support `scoreSource`, `feedbackSource`, and `executionSource` for clearer source tracking. **Migration note** Deprecated fields (like `source` and feedback `userId`) are still accepted for compatibility. ### Patch Changes - Fixed agent run traces not appearing in Datadog and other observability backends when LLM calls fail. Previously, an API error during streaming would leave the root AGENT_RUN span open indefinitely, causing the entire trace tree to be silently dropped by exporters that wait for the root span to close. Failed agent runs now correctly end the span with error information, making failures visible in your observability dashboard. ([#14850](https://github.com/mastra-ai/mastra/pull/14850)) - Fixed `mcpOptions` (including `serverless: true`) being silently ignored when using the Mastra deployer. The deployer now forwards `mcpOptions` from your server config to the underlying `MastraServer`, so MCP stateless mode works correctly in serverless environments like Cloudflare Workers, Vercel Edge, and AWS Lambda. ([#14810](https://github.com/mastra-ai/mastra/issues/14810)) ([#14812](https://github.com/mastra-ai/mastra/pull/14812)) **What changed:** - Added `mcpOptions` to the `ServerConfig` type so it can be set in `new Mastra({ server: { ... } })` - The deployer now passes `server.mcpOptions` through to `MastraServer` **Example:** ```typescript const mastra = new Mastra({ server: { mcpOptions: { serverless: true, }, }, }); ``` - Added `isValidationError` type guard for the `ValidationError` interface ([#14853](https://github.com/mastra-ai/mastra/pull/14853)) - Fixed models.dev provider URLs to interpolate environment variable placeholders like `${ACCOUNT_ID}` before creating the underlying provider client. ([#14722](https://github.com/mastra-ai/mastra/pull/14722)) - Fixed MODEL_GENERATION observability span to include all system messages (tagged and untagged). Previously, working memory and semantic recall instructions were missing from trace inputs because only untagged system messages were captured. ([#14800](https://github.com/mastra-ai/mastra/pull/14800)) - Fixed models.dev auth env selection to prefer auth credentials over URL path identifiers, so Cloudflare Workers AI no longer uses the account ID for authentication. ([#14687](https://github.com/mastra-ai/mastra/pull/14687)) - Fixed processInputStep always receiving an empty steps array. Processors can now inspect previous step results (tool calls, LLM responses) when running inside the agentic loop. ([#14821](https://github.com/mastra-ai/mastra/pull/14821)) ## 1.18.0-alpha.3 ### Minor Changes - Add version-aware code-agent lookup and override version lifecycle support. ([#14776](https://github.com/mastra-ai/mastra/pull/14776)) `Mastra.getAgent(name, version)` and `Mastra.getAgentById(id, version)` can now resolve draft or specific stored override versions when the editor package is configured, and throw a clear error when versioned lookup is requested without the editor. `client.getAgent(id, version)` now carries version selection through agent detail and voice metadata requests, and the `Agent` resource now supports override version management methods including `listVersions`, `createVersion`, `getVersion`, `activateVersion`, `restoreVersion`, `deleteVersion`, and `compareVersions`. `Agent.createVersion(...)` is intentionally limited to code-agent overrideable fields plus version metadata, rather than the full stored-agent configuration surface. ### Patch Changes - Persist observational memory threshold settings across restarts and restore per-thread overrides. ([#14788](https://github.com/mastra-ai/mastra/pull/14788)) - feat(memory): add recall-tool history retrieval for agents using observational memory ([#14567](https://github.com/mastra-ai/mastra/pull/14567)) Agents that use observational memory can now use the `recall` tool to retrieve history from past conversations, including raw messages, thread listings, and indexed observation-group memories. Enable observational-memory retrieval when listing tools: ```ts const tools = await memory.listTools({ threadId: 'thread_123', resourceId: 'resource_abc', observationalMemory: { retrieval: { vector: true, scope: 'resource' }, }, }); ``` With retrieval enabled, `recall` can browse the current thread, list threads for the current resource, and search indexed observation groups with source ranges. - Limit dynamically injected AGENTS.md reminders to 1000 estimated tokens by default and tell mastracode observational memory to ignore those ephemeral reminder messages. ([#14790](https://github.com/mastra-ai/mastra/pull/14790)) - Fixed missing `TRequestContext` type parameter on `DynamicArgument` fields in `AgentConfig`. Previously, only `instructions` and `tools` correctly propagated the `requestContextSchema` type to their dynamic function callbacks. Now all dynamic fields — `model`, `workflows`, `workspace`, `agents`, `memory`, `scorers`, `defaultGenerateOptionsLegacy`, `defaultStreamOptionsLegacy`, `defaultOptions`, `defaultNetworkOptions`, `inputProcessors`, and `outputProcessors` — properly type `requestContext` based on the agent's `requestContextSchema`. ([#14582](https://github.com/mastra-ai/mastra/pull/14582)) **Before:** ```typescript const agent = new Agent({ requestContextSchema: z.object({ userId: z.string() }), workspace: ({ requestContext }) => { requestContext.get('userId'); // typed as `unknown` }, }); ``` **After:** ```typescript const agent = new Agent({ requestContextSchema: z.object({ userId: z.string() }), workspace: ({ requestContext }) => { requestContext.get('userId'); // typed as `string` }, }); ``` - Fixed resuming suspended tool calls with `resumeStream` or `approveToolCall` failing with a TripWire when input processors (e.g. TokenLimiterProcessor) are enabled on the agent. ([#14561](https://github.com/mastra-ai/mastra/pull/14561)) - Fixed streaming delegation to propagate output processor modifications to the supervisor. Previously, when a sub-agent had an output processor that modified text via `processOutputResult`, the supervisor received the raw LLM output instead of the processed text. The processed text was only saved to the sub-agent's memory. Now the supervisor correctly receives the output-processor-modified text from delegated sub-agents in the streaming path. ([#14731](https://github.com/mastra-ai/mastra/pull/14731)) - Fixed MODEL_GENERATION and AGENT_RUN spans not reflecting model, provider, parameters, and availableTools overrides from input processors. Traces in Langfuse and other exporters now show the correct model info when a processor dynamically switches models. ([#14705](https://github.com/mastra-ai/mastra/pull/14705)) - **Configurable weights**: Add `weights` option to `createTrajectoryScorerCode` for controlling how dimension scores are combined. Defaults to `{ accuracy: 0.4, efficiency: 0.3, toolFailures: 0.2, blacklist: 0.1 }`. ([#14740](https://github.com/mastra-ai/mastra/pull/14740)) ```ts const scorer = createTrajectoryScorerCode({ defaults: { steps: [{ name: 'search' }], maxSteps: 5 }, weights: { accuracy: 0.6, efficiency: 0.2, toolFailures: 0.1, blacklist: 0.1 }, }); ``` **ExpectedStep redesign**: `ExpectedStep` is now a discriminated union mirroring `TrajectoryStep`. When you specify a `stepType`, you get autocomplete for that variant's fields (e.g., `toolArgs` for `tool_call`, `modelId` for `model_generation`). The old `data: Record` field is replaced by direct variant fields. ```ts // Before: { name: 'search', stepType: 'tool_call', data: { input: { query: 'weather' } } } // After: { name: 'search', stepType: 'tool_call', toolArgs: { query: 'weather' } } ``` **Remove `compareStepData`**: The `compareStepData` option is removed from `compareTrajectories`, `TrajectoryExpectation`, and all scorers. Data fields are now auto-compared when present on expected steps — if you specify `toolArgs` on an `ExpectedStep`, it will be compared against the actual step. If you omit it, only name and stepType are matched. Also fixes documentation inaccuracies in `trajectory-accuracy.mdx` and `scorer-utils.mdx`. ## 1.18.0-alpha.2 ### Patch Changes - Fixed title generation blocking stream completion. The `generateTitle` LLM call now runs in the background instead of blocking the stream from closing, removing the 2-3 second post-response delay in the UI when memory is enabled. ([#14757](https://github.com/mastra-ai/mastra/pull/14757)) ## 1.18.0-alpha.1 ### Minor Changes - **Trajectory evaluation**: Added trajectory types and trace-based extraction for evaluating agent and workflow execution paths. ([#14697](https://github.com/mastra-ai/mastra/pull/14697)) `TrajectoryStep` models each step in an execution as a typed object — tool calls, model generations, agent runs, workflow steps, and control flow nodes each have their own variant with relevant properties (e.g., `toolArgs`/`toolResult` for tool calls, `modelId`/`promptTokens` for model generations). Steps can be nested via `children` to represent hierarchical execution. `TrajectoryExpectation` lets you define what a good trajectory looks like — expected steps, ordering, step/token/duration budgets, blacklisted tools, and retry thresholds. `ExpectedStep` provides a simple way to define expected steps by name and optional stepType, with support for nested expectations via `children` to set different evaluation rules at each level of the hierarchy. **Trace-based extraction:** `extractTrajectoryFromTrace()` builds hierarchical trajectories from observability trace spans. The `runEvals` pipeline automatically uses this when storage is configured, capturing the full execution tree including nested agent runs and tool calls. Falls back to `extractTrajectory` (agents) or `extractWorkflowTrajectory` (workflows) when storage is unavailable. **Pipeline:** `expectedTrajectory` flows from dataset items through `runEvals` to trajectory scorers. Added `trajectory` key to both `AgentScorerConfig` and `WorkflowScorerConfig`. ### Patch Changes - Add `getReviewSummary()` to experiments storage for aggregating review status counts ([#14649](https://github.com/mastra-ai/mastra/pull/14649)) Query experiment results grouped by experiment ID, returning counts of `needs-review`, `reviewed`, and `complete` items in a single query instead of fetching all results client-side. ```ts const summary = await storage.experiments.getReviewSummary(); // [{ experimentId: 'exp-1', needsReview: 3, reviewed: 5, complete: 2, total: 10 }, ...] ``` ## 1.18.0-alpha.0 ### Minor Changes - Add `lsp_inspect` tool for LSP-based code inspection with hover, definition, and implementation queries ([#14565](https://github.com/mastra-ai/mastra/pull/14565)) - Added `disableBuiltinTools` to `HarnessConfig` so you can disable specific built-in harness tools. ([#14227](https://github.com/mastra-ai/mastra/pull/14227)) Example: ```ts new Harness({ disableBuiltinTools: ['submit_plan', 'subagent'] }); ``` - Added SkillSearchProcessor for on-demand skill discovery. Instead of injecting all skill metadata upfront, agents get `search_skills` and `load_skill` meta-tools to find and load skills on demand with thread-scoped state and TTL cleanup. ([#14596](https://github.com/mastra-ai/mastra/pull/14596)) **Example** ```typescript import { SkillSearchProcessor } from '@mastra/core/processors'; const skillSearch = new SkillSearchProcessor({ workspace, search: { topK: 5 }, }); const agent = new Agent({ workspace, inputProcessors: [skillSearch], }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`dc514a8`](https://github.com/mastra-ai/mastra/commit/dc514a83dba5f719172dddfd2c7b858e4943d067)) - Fixed `Harness.listThreads()` so callers can request threads across all resources. ([#14690](https://github.com/mastra-ai/mastra/pull/14690)) - Fixed Harness `stateSchema` typing to accept Zod schemas with `.default()`, `.optional()`, and `.transform()` modifiers. Previously, these modifiers caused TypeScript errors because the type system forced schema Input and Output types to be identical. Now `stateSchema` correctly accepts any schema regardless of input type divergence. ([#14606](https://github.com/mastra-ai/mastra/pull/14606)) - Fixed tool input validation failures not producing observability spans. When input schema validation failed, no TOOL_CALL span was created because span creation happened inside the execution function that ran after validation. Moved span creation before input validation so validation errors are now captured in spans and visible in observability backends like Datadog. ([#14677](https://github.com/mastra-ai/mastra/pull/14677)) ## 1.17.0 ### Minor Changes - Add `lsp_inspect` tool for LSP-based code inspection with hover, definition, and implementation queries ([#14565](https://github.com/mastra-ai/mastra/pull/14565)) - Added `disableBuiltinTools` to `HarnessConfig` so you can disable specific built-in harness tools. ([#14227](https://github.com/mastra-ai/mastra/pull/14227)) Example: ```ts new Harness({ disableBuiltinTools: ['submit_plan', 'subagent'] }); ``` - Added SkillSearchProcessor for on-demand skill discovery. Instead of injecting all skill metadata upfront, agents get `search_skills` and `load_skill` meta-tools to find and load skills on demand with thread-scoped state and TTL cleanup. ([#14596](https://github.com/mastra-ai/mastra/pull/14596)) **Example** ```typescript import { SkillSearchProcessor } from '@mastra/core/processors'; const skillSearch = new SkillSearchProcessor({ workspace, search: { topK: 5 }, }); const agent = new Agent({ workspace, inputProcessors: [skillSearch], }); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`dc514a8`](https://github.com/mastra-ai/mastra/commit/dc514a83dba5f719172dddfd2c7b858e4943d067)) - Fixed `Harness.listThreads()` so callers can request threads across all resources. ([#14690](https://github.com/mastra-ai/mastra/pull/14690)) - The internal architecture of observational memory has been refactored. The public API and behavior remain unchanged. ([#14453](https://github.com/mastra-ai/mastra/pull/14453)) - Fixed Harness `stateSchema` typing to accept Zod schemas with `.default()`, `.optional()`, and `.transform()` modifiers. Previously, these modifiers caused TypeScript errors because the type system forced schema Input and Output types to be identical. Now `stateSchema` correctly accepts any schema regardless of input type divergence. ([#14606](https://github.com/mastra-ai/mastra/pull/14606)) - Improved the Loaded AGENTS.md reminder in the TUI so it uses the new bordered notice style and collapses long reminder content by default. ([#14637](https://github.com/mastra-ai/mastra/pull/14637)) - Fixed tool input validation failures not producing observability spans. When input schema validation failed, no TOOL_CALL span was created because span creation happened inside the execution function that ran after validation. Moved span creation before input validation so validation errors are now captured in spans and visible in observability backends like Datadog. ([#14677](https://github.com/mastra-ai/mastra/pull/14677)) ## 1.17.0-alpha.2 ### Minor Changes - Add lsp_inspect tool for LSP-based code inspection with hover, definition, and implementation queries ([#14565](https://github.com/mastra-ai/mastra/pull/14565)) - Added `disableBuiltinTools` to `HarnessConfig` so you can disable specific built-in harness tools. ([#14227](https://github.com/mastra-ai/mastra/pull/14227)) Example: `new Harness({ disableBuiltinTools: ['submit_plan', 'subagent'] })` - Added SkillSearchProcessor for on-demand skill discovery. Instead of injecting all skill metadata upfront, agents get `search_skills` and `load_skill` meta-tools to find and load skills on demand with thread-scoped state and TTL cleanup. ([#14596](https://github.com/mastra-ai/mastra/pull/14596)) **Example** ```typescript import { SkillSearchProcessor } from '@mastra/core/processors'; const skillSearch = new SkillSearchProcessor({ workspace, search: { topK: 5 }, }); const agent = new Agent({ workspace, inputProcessors: [skillSearch], }); ``` ### Patch Changes - Fixed `Harness.listThreads()` so callers can request threads across all resources. ([#14690](https://github.com/mastra-ai/mastra/pull/14690)) - Fixed Harness `stateSchema` typing to accept Zod schemas with `.default()`, `.optional()`, and `.transform()` modifiers. Previously, these modifiers caused TypeScript errors because the type system forced schema Input and Output types to be identical. Now `stateSchema` correctly accepts any schema regardless of input type divergence. ([#14606](https://github.com/mastra-ai/mastra/pull/14606)) - Improved the Loaded AGENTS.md reminder in the TUI so it uses the new bordered notice style and collapses long reminder content by default. ([#14637](https://github.com/mastra-ai/mastra/pull/14637)) - Fixed tool input validation failures not producing observability spans. When input schema validation failed, no TOOL_CALL span was created because span creation happened inside the execution function that ran after validation. Moved span creation before input validation so validation errors are now captured in spans and visible in observability backends like Datadog. ([#14677](https://github.com/mastra-ai/mastra/pull/14677)) ## 1.16.1-alpha.1 ### Patch Changes - **Refactored Observational Memory into modular architecture** ([#14453](https://github.com/mastra-ai/mastra/pull/14453)) Restructured the Observational Memory (OM) engine from a single ~3,800-line monolithic class into a modular, strategy-based architecture. The public API and behavior are unchanged — this is a purely internal refactor that improves maintainability, testability, and separation of concerns. **Why** — The original `ObservationalMemory` class handled everything: orchestration, LLM calling, observation logic for three different scopes, reflection, buffering coordination, turn lifecycle, and message processing. This made it difficult to reason about individual behaviors, test them in isolation, or extend the system. The refactor separates these responsibilities into focused modules. **Observation strategies** — Extracted three duplicated observation code paths (~650 lines of conditionals) into pluggable strategy classes sharing a common `prepare → process → persist` lifecycle via an abstract base class. The correct strategy is selected automatically based on scope and buffering configuration. ``` observation-strategies/ base.ts — abstract ObservationStrategy + StrategyDeps interface sync.ts — SyncObservationStrategy (thread-scoped synchronous) async-buffer.ts — AsyncBufferObservationStrategy (background buffered) resource-scoped.ts — ResourceScopedObservationStrategy (multi-thread) index.ts — static factory: ObservationStrategy.create(om, opts) ``` ```ts // Internal usage — strategies are selected and run automatically: const strategy = ObservationStrategy.create(om, { record, threadId, resourceId, messages, cycleId, startedAt, }); const result = await strategy.run(); ``` **Turn/Step abstraction** — Introduced `ObservationTurn` and `StepContext` to model the lifecycle of a single agent interaction. A Turn manages message loading, system message injection, record caching, and cleanup. A Step handles per-generation observation, activation, and reflection decisions. This replaced ~580 lines of inline orchestration in the processor with ~170 lines of structured calls. ```ts // Internal lifecycle managed by the processor: const turn = new ObservationTurn(om, memory, { threadId, resourceId }); await turn.start(messageList, writer); // loads history, injects OM system message const step = turn.step(0); await step.prepare(messageList, writer); // activate buffered, maybe reflect // ... agent generates response ... await step.complete(messageList, writer); // observe new messages, buffer if needed await turn.end(messageList, writer); // persist, cleanup ``` **Dedicated runners** — Moved observer and reflector LLM-calling logic into `ObserverRunner` (194 lines) and `ReflectorRunner` (710 lines), separating prompt construction, degenerate output detection, retry logic, and compression level escalation from orchestration. `BufferingCoordinator` (175 lines) extracts the static buffering state machine and async operation tracking. **Processor** — Added `ObservationalMemoryProcessor` implementing the `Processor` interface, bridging the OM engine with the AI SDK message pipeline. It owns the decision of _when_ to buffer, activate, observe, and reflect — while the OM engine owns _how_ to do each operation. ```ts // The processor is created automatically by Memory when OM is enabled. // It plugs into the AI SDK message pipeline: const memory = new Memory({ storage: new InMemoryStore(), options: { observationalMemory: { enabled: true, observation: { model, messageTokens: 500 }, reflection: { model, observationTokens: 10_000 }, }, }, }); // For direct access to the OM engine (e.g. for manual observe/buffer/activate): const om = await memory.omEngine; ``` **Unified OM engine instantiation** — Replaced the duplicated `getOMEngine()` singleton and per-call `createOMProcessor()` engine creation with a single lazy `omEngine` property on the `Memory` class. This eliminates config drift between the legacy `getContext()` API and the processor pipeline — both now share the same `ObservationalMemory` instance with the full configuration. ```ts // Before (casting required, config could drift): const om = (await (memory as any).getOMEngine()) as ObservationalMemory; // After (typed, single shared engine): const om = await memory.omEngine; ``` **Improved observation activation atomicity** — Added conditional WHERE clauses to `activateBufferedObservations` in all storage adapters (pg, libsql, mongodb) to prevent duplicate chunk swaps when concurrent processes attempt activation simultaneously. If chunks have already been cleared by another process, the operation returns early with zero counts instead of corrupting state. **Compression start level from model context** — Integrated model-aware compression start levels into the `ReflectorRunner`. Models like `gemini-2.5-flash` that struggle with light compression now start at compression level 2 instead of 1, reducing wasted reflection retries. **Pure function extraction** — Moved reusable helpers into `message-utils.ts`: `filterObservedMessages`, `getBufferedChunks`, `sortThreadsByOldestMessage`, `stripThreadTags`. Eliminated dead code including `isObserving` DB flag, `countMessageTokens`, `acquireObservingLock`/`releaseObservingLock`, and ~10 cascading dead private methods. **Cleanup** — Dropped `threadIdCache` (pointless memoization), removed `as any` casts for private method access (made methods properly public with `@internal` tsdoc), replaced sealed-ID-based tracking with message-level `metadata.mastra.sealed` flag checks. ## 1.16.1-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`dc514a8`](https://github.com/mastra-ai/mastra/commit/dc514a83dba5f719172dddfd2c7b858e4943d067)) ## 1.16.0 ### Minor Changes - Added dataset-agent association and experiment status tracking for the Evaluate workflow. ([#14470](https://github.com/mastra-ai/mastra/pull/14470)) - **Dataset targeting**: Added `targetType` and `targetIds` fields to datasets, enabling association with agents, scorers, or workflows. Datasets can now be linked to multiple entities. - **Experiment status**: Added `status` field to experiment results (`'needs-review'`, `'reviewed'`, `'complete'`) for review queue workflow. - **Dataset experiment routes**: Added API endpoints for triggering experiments from a dataset with configurable target type and target ID. - **LLM data generation**: Added endpoint for generating dataset items using an LLM with configurable count and prompt. - **Failure analysis**: Added endpoint for clustering experiment failures and proposing tags using LLM analysis. - Added agent version support for experiments. When triggering an experiment, you can now pass an `agentVersion` parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. ([#14562](https://github.com/mastra-ai/mastra/pull/14562)) ```ts const client = new MastraClient(); await client.triggerDatasetExperiment({ datasetId: 'my-dataset', targetType: 'agent', targetId: 'my-agent', version: 3, // pin to dataset version 3 agentVersion: 'ver_abc123', // pin to a specific agent version }); ``` - Added tool suspension handling to the Harness. ([#14611](https://github.com/mastra-ai/mastra/pull/14611)) When a tool calls `suspend()` during execution, the harness now emits a `tool_suspended` event, reports `agent_end` with reason `'suspended'`, and exposes `respondToToolSuspension()` to resume execution with user-provided data. ```ts harness.subscribe(event => { if (event.type === 'tool_suspended') { // event.toolName, event.suspendPayload, event.resumeSchema } }); // Resume after collecting user input await harness.respondToToolSuspension({ resumeData: { confirmed: true } }); ``` - Added `agentId` to the agent tool execution context. Tools executed by an agent can now access `context.agent.agentId` to identify which agent is calling them. This enables tools to look up agent metadata, share workspace configuration with sub-agents, or customize behavior per agent. ([#14502](https://github.com/mastra-ai/mastra/pull/14502)) - Improved observability metrics and logs storage support. ([#14607](https://github.com/mastra-ai/mastra/pull/14607)) - Added typed observability storage fields for shared correlation context and cost data. - Added storage-layer metric listing and richer metric aggregations that can return estimated cost alongside values. - Improved observability filter parity across log and metric storage APIs. - Add optional `?path=` query param to workspace skill routes for disambiguating same-named skills. ([#14430](https://github.com/mastra-ai/mastra/pull/14430)) Skill routes continue to use `:skillName` in the URL path (no breaking change). When two skills share the same name (e.g. from different directories), pass the optional `?path=` query parameter to select the exact skill: ``` GET /workspaces/:workspaceId/skills/:skillName?path=skills/brand-guidelines ``` `SkillMetadata` now includes a `path` field, and the `list()` method returns all same-named skills for disambiguation. The client SDK's `getSkill()` accepts an optional `skillPath` parameter for disambiguation. - Added `ModelByInputTokens` in `@mastra/memory` for token-threshold-based model selection in Observational Memory. ([#14614](https://github.com/mastra-ai/mastra/pull/14614)) When configured, OM automatically selects different observer or reflector models based on the actual input token count at the time the OM call runs. Example usage: ```ts import { Memory, ModelByInputTokens } from '@mastra/memory'; const memory = new Memory({ options: { observationalMemory: { model: new ModelByInputTokens({ upTo: { 10_000: 'google/gemini-2.5-flash', 40_000: 'openai/gpt-4o', 1_000_000: 'openai/gpt-4.5', }, }), }, }, }); ``` The `upTo` keys are inclusive upper bounds. OM resolves the matching tier directly at the observer or reflector call site. If the input exceeds the largest configured threshold, OM throws an error. Improved Observational Memory tracing so traces show the observer and reflector spans and make it easier to see which resolved model was used at runtime. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`68ed4e9`](https://github.com/mastra-ai/mastra/commit/68ed4e9f118e8646b60a6112dabe854d0ef53902)) - Fixed `Harness.destroy()` to properly clean up heartbeats and workspace on teardown. ([#14568](https://github.com/mastra-ai/mastra/pull/14568)) - Fixed null detection in tool input validation to check actual values at failing paths instead of relying on error message string matching. This ensures null values from LLMs are correctly handled even when validators produce error messages that don't contain the word "null" (e.g., "must be string"). Fixes #14476. ([#14496](https://github.com/mastra-ai/mastra/pull/14496)) - Fixed missing tool lists in agent traces for streaming runs. Exporters like Datadog LLM Observability now receive the tools available to the agent. ([#14550](https://github.com/mastra-ai/mastra/pull/14550)) - Fix consecutive tool-only loop iterations being merged into a single assistant message block. When the agentic loop runs multiple iterations that each produce only tool calls, the LLM would misinterpret them as parallel calls from a single turn. A `step-start` boundary is now inserted between iterations to ensure they are treated as sequential steps. ([#14652](https://github.com/mastra-ai/mastra/pull/14652)) - Improved custom OpenAI-compatible model configuration guidance in the models docs. ([#14594](https://github.com/mastra-ai/mastra/pull/14594)) - Added client/server body schemas for feedback and scores that omit the timestamp field, allowing it to be set server-side ([#14470](https://github.com/mastra-ai/mastra/pull/14470)) - **Workspace skills now surface all same-named skills for disambiguation.** ([#14430](https://github.com/mastra-ai/mastra/pull/14430)) When multiple skills share the same name (e.g., a local `brand-guidelines` skill and one from `node_modules`), `list()` now returns all of them instead of only the tie-break winner. This lets agents and UIs see every available skill, along with its path and source type. **Tie-breaking behavior:** - `get(name)` still returns a single skill using source-type priority: local > managed > external - If two skills share the same name _and_ source type, `get(name)` throws an error — rename one or move it to a different source type - `get(path)` bypasses tie-breaking entirely and returns the exact skill Agents and UIs now receive all same-named skills with their paths, which improves disambiguation in prompts and tool calls. ```ts const skills = await workspace.skills.list(); // Returns both local and external "brand-guidelines" skills const exact = await workspace.skills.get('node_modules/@myorg/skills/brand-guidelines'); // Fetches the external copy directly by path ``` - Fixed Anthropic 'tool_use ids were found without tool_result blocks immediately after' error. When client tools (e.g. execute_command) and provider tools (e.g. web_search) are called in parallel, the tool ordering in message history could cause Anthropic to reject subsequent requests, making the thread unrecoverable. Tool blocks are now correctly split to satisfy Anthropic's ordering requirements. ([#14648](https://github.com/mastra-ai/mastra/pull/14648)) - Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. ([#14464](https://github.com/mastra-ai/mastra/pull/14464)) Mastra agent and client APIs accept schemas from either `zod/v3` or `zod/v4`, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions. - Updated dependencies [[`47358d9`](https://github.com/mastra-ai/mastra/commit/47358d960bb2b931321de7e798f341ab0df81f44), [`d3930ea`](https://github.com/mastra-ai/mastra/commit/d3930eac51c30b0ecf7eaa54bb9430758b399777)]: - @mastra/schema-compat@1.2.7 ## 1.16.0-alpha.5 ### Patch Changes - Fix consecutive tool-only loop iterations being merged into a single assistant message block. When the agentic loop runs multiple iterations that each produce only tool calls, the LLM would misinterpret them as parallel calls from a single turn. A `step-start` boundary is now inserted between iterations to ensure they are treated as sequential steps. ([#14652](https://github.com/mastra-ai/mastra/pull/14652)) ## 1.16.0-alpha.4 ### Minor Changes - Added `ModelByInputTokens` in `@mastra/memory` for token-threshold-based model selection in Observational Memory. ([#14614](https://github.com/mastra-ai/mastra/pull/14614)) When configured, OM automatically selects different observer or reflector models based on the actual input token count at the time the OM call runs. Example usage: ```ts import { Memory, ModelByInputTokens } from '@mastra/memory'; const memory = new Memory({ options: { observationalMemory: { model: new ModelByInputTokens({ upTo: { 10_000: 'google/gemini-2.5-flash', 40_000: 'openai/gpt-4o', 1_000_000: 'openai/gpt-4.5', }, }), }, }, }); ``` The `upTo` keys are inclusive upper bounds. OM resolves the matching tier directly at the observer or reflector call site. If the input exceeds the largest configured threshold, OM throws an error. Improved Observational Memory tracing so traces show the observer and reflector spans and make it easier to see which resolved model was used at runtime. ### Patch Changes - Fixed null detection in tool input validation to check actual values at failing paths instead of relying on error message string matching. This ensures null values from LLMs are correctly handled even when validators produce error messages that don't contain the word "null" (e.g., "must be string"). Fixes #14476. ([#14496](https://github.com/mastra-ai/mastra/pull/14496)) - Fixed Anthropic 'tool_use ids were found without tool_result blocks immediately after' error. When client tools (e.g. execute_command) and provider tools (e.g. web_search) are called in parallel, the tool ordering in message history could cause Anthropic to reject subsequent requests, making the thread unrecoverable. Tool blocks are now correctly split to satisfy Anthropic's ordering requirements. ([#14648](https://github.com/mastra-ai/mastra/pull/14648)) ## 1.16.0-alpha.3 ### Minor Changes - Added `agentId` to the agent tool execution context. Tools executed by an agent can now access `context.agent.agentId` to identify which agent is calling them. This enables tools to look up agent metadata, share workspace configuration with sub-agents, or customize behavior per agent. ([#14502](https://github.com/mastra-ai/mastra/pull/14502)) - Add optional `?path=` query param to workspace skill routes for disambiguating same-named skills. ([#14430](https://github.com/mastra-ai/mastra/pull/14430)) Skill routes continue to use `:skillName` in the URL path (no breaking change). When two skills share the same name (e.g. from different directories), pass the optional `?path=` query parameter to select the exact skill: ``` GET /workspaces/:workspaceId/skills/:skillName?path=skills/brand-guidelines ``` `SkillMetadata` now includes a `path` field, and the `list()` method returns all same-named skills for disambiguation. The client SDK's `getSkill()` accepts an optional `skillPath` parameter for disambiguation. ### Patch Changes - **Workspace skills now surface all same-named skills for disambiguation.** ([#14430](https://github.com/mastra-ai/mastra/pull/14430)) When multiple skills share the same name (e.g., a local `brand-guidelines` skill and one from `node_modules`), `list()` now returns all of them instead of only the tie-break winner. This lets agents and UIs see every available skill, along with its path and source type. **Tie-breaking behavior:** - `get(name)` still returns a single skill using source-type priority: local > managed > external - If two skills share the same name _and_ source type, `get(name)` throws an error — rename one or move it to a different source type - `get(path)` bypasses tie-breaking entirely and returns the exact skill Agents and UIs now receive all same-named skills with their paths, which improves disambiguation in prompts and tool calls. ```ts const skills = await workspace.skills.list(); // Returns both local and external "brand-guidelines" skills const exact = await workspace.skills.get('node_modules/@myorg/skills/brand-guidelines'); // Fetches the external copy directly by path ``` - Updated dependencies [[`47358d9`](https://github.com/mastra-ai/mastra/commit/47358d960bb2b931321de7e798f341ab0df81f44)]: - @mastra/schema-compat@1.2.7-alpha.1 ## 1.16.0-alpha.2 ### Minor Changes - Added tool suspension handling to the Harness. ([#14611](https://github.com/mastra-ai/mastra/pull/14611)) When a tool calls `suspend()` during execution, the harness now emits a `tool_suspended` event, reports `agent_end` with reason `'suspended'`, and exposes `respondToToolSuspension()` to resume execution with user-provided data. ```ts harness.subscribe(event => { if (event.type === 'tool_suspended') { // event.toolName, event.suspendPayload, event.resumeSchema } }); // Resume after collecting user input await harness.respondToToolSuspension({ resumeData: { confirmed: true } }); ``` - Improved observability metrics and logs storage support. ([#14607](https://github.com/mastra-ai/mastra/pull/14607)) - Added typed observability storage fields for shared correlation context and cost data. - Added storage-layer metric listing and richer metric aggregations that can return estimated cost alongside values. - Improved observability filter parity across log and metric storage APIs. ### Patch Changes - Fixed `Harness.destroy()` to properly clean up heartbeats and workspace on teardown. ([#14568](https://github.com/mastra-ai/mastra/pull/14568)) - Fix Zod v3 and Zod v4 compatibility across public structured-output APIs. ([#14464](https://github.com/mastra-ai/mastra/pull/14464)) Mastra agent and client APIs accept schemas from either `zod/v3` or `zod/v4`, matching the documented peer dependency range and preserving TypeScript compatibility for both Zod versions. - Updated dependencies [[`d3930ea`](https://github.com/mastra-ai/mastra/commit/d3930eac51c30b0ecf7eaa54bb9430758b399777)]: - @mastra/schema-compat@1.2.7-alpha.0 ## 1.16.0-alpha.1 ### Minor Changes - Added agent version support for experiments. When triggering an experiment, you can now pass an `agentVersion` parameter to pin which agent version to use. The agent version is stored with the experiment and returned in experiment responses. ([#14562](https://github.com/mastra-ai/mastra/pull/14562)) ```ts const client = new MastraClient(); await client.triggerDatasetExperiment({ datasetId: 'my-dataset', targetType: 'agent', targetId: 'my-agent', version: 3, // pin to dataset version 3 agentVersion: 'ver_abc123', // pin to a specific agent version }); ``` ### Patch Changes - Improved custom OpenAI-compatible model configuration guidance in the models docs. ([#14594](https://github.com/mastra-ai/mastra/pull/14594)) ## 1.16.0-alpha.0 ### Minor Changes - Added dataset-agent association and experiment status tracking for the Evaluate workflow. ([#14470](https://github.com/mastra-ai/mastra/pull/14470)) - **Dataset targeting**: Added `targetType` and `targetIds` fields to datasets, enabling association with agents, scorers, or workflows. Datasets can now be linked to multiple entities. - **Experiment status**: Added `status` field to experiment results (`'needs-review'`, `'reviewed'`, `'complete'`) for review queue workflow. - **Dataset experiment routes**: Added API endpoints for triggering experiments from a dataset with configurable target type and target ID. - **LLM data generation**: Added endpoint for generating dataset items using an LLM with configurable count and prompt. - **Failure analysis**: Added endpoint for clustering experiment failures and proposing tags using LLM analysis. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`68ed4e9`](https://github.com/mastra-ai/mastra/commit/68ed4e9f118e8646b60a6112dabe854d0ef53902)) - Fixed missing tool lists in agent traces for streaming runs. Exporters like Datadog LLM Observability now receive the tools available to the agent. ([#14550](https://github.com/mastra-ai/mastra/pull/14550)) - Added client/server body schemas for feedback and scores that omit the timestamp field, allowing it to be set server-side ([#14470](https://github.com/mastra-ai/mastra/pull/14470)) ## 1.15.0 ### Minor Changes - Add `server.studioHost`, `server.studioProtocol`, and `server.studioPort` options for Studio in cloud deployments ([#12899](https://github.com/mastra-ai/mastra/pull/12899)) When deploying to cloud environments (e.g., Google Cloud Run), `server.host` must be `0.0.0.0` for the container to accept traffic, and the internal port often differs from the external one (e.g., 8080 internally vs 443 externally). Studio needs the actual public domain, protocol, and port to make API calls from the browser. These new options decouple the server bind configuration from the Studio API URL. ```typescript export const mastra = new Mastra({ server: { host: '0.0.0.0', port: 8080, studioHost: 'my-app.run.app', studioProtocol: 'https', studioPort: 443, }, }); ``` All three options are optional and fall back to existing behavior when not set. - Added filesystem-level optimistic concurrency for file writes. When `expectedMtime` is provided in `WriteOptions`, the write will be rejected with a `StaleFileError` if the file was modified externally since it was last read. This provides defense-in-depth against external modifications (e.g., LSP-based editors) that occur between the tool-level mtime check and the actual write. ([#14354](https://github.com/mastra-ai/mastra/pull/14354)) **New `expectedMtime` option on `WriteOptions`** Any caller of `filesystem.writeFile()` can now opt into optimistic concurrency: ```ts // Read a file and capture its mtime const stat = await filesystem.stat('config.json'); const content = await filesystem.readFile('config.json'); // Later, write with mtime guard — fails if file changed externally await filesystem.writeFile('config.json', newContent, { overwrite: true, expectedMtime: stat.modifiedAt, }); ``` If the file was modified between the read and write, a `StaleFileError` is thrown instead of silently overwriting. **Automatic mtime pass-through for workspace tools** When `requireReadBeforeWrite` is enabled, the `edit_file`, `write_file`, and `ast_edit` tools now automatically pass the recorded mtime through to the filesystem layer, providing a second line of defense beyond the existing tool-level read tracker check. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`cb611a1`](https://github.com/mastra-ai/mastra/commit/cb611a1e89a4f4cf74c97b57e0c27bb56f2eceb5)) - Added experimental retrieval-mode recall tooling for observational memory. ([#14437](https://github.com/mastra-ai/mastra/pull/14437)) When `observationalMemory.retrieval` is enabled with `scope: 'thread'`, observation groups store colon-delimited message ranges (`startId:endId`) pointing back to the raw messages they were derived from. A `recall` tool is registered that lets agents retrieve those source messages via cursor-based pagination. The recall tool supports: - **Detail levels**: `detail: 'low'` (default) returns truncated text with part indices; `detail: 'high'` returns full content clamped to one part per call with continuation hints - **Part-level fetch**: `partIndex` targets a single message part at full detail - **Pagination flags**: `hasNextPage` and `hasPrevPage` in results - **Token limiting**: results are capped at a token budget with `truncated` and `tokenOffset` reporting - **Smart range detection**: passing a range as a cursor returns a helpful hint explaining how to extract individual IDs - Fixed agent.stream() so the returned spanId matches the top-level agent run span instead of the nested model span. ([#14447](https://github.com/mastra-ai/mastra/pull/14447)) - Fixed deserializeRequestContext to return an actual RequestContext instance instead of a Map, preventing crashes during Inngest durable execution replay with observability enabled. ([#14442](https://github.com/mastra-ai/mastra/pull/14442)) - Fixed trace context propagation in evented workflow steps and processors. Operations started inside those steps now appear under the correct parent in distributed traces. ([#14455](https://github.com/mastra-ai/mastra/pull/14455)) - Fix OTEL context propagation in workflow step execution. Wrapping `step.execute()` in `executeWithContext` ensures auto-instrumented code inside a step (e.g. AI SDK spans) is correctly nested under the workflow step span rather than appearing as siblings. ([#13755](https://github.com/mastra-ai/mastra/pull/13755)) - Added opt-in Observational Memory thread titles. ([#14436](https://github.com/mastra-ai/mastra/pull/14436)) When enabled, the Observer suggests a short thread title and updates it as the conversation topic changes. Harness consumers can detect these updates via the new `om_thread_title_updated` event. **Example** ```ts const memory = new Memory({ options: { observationalMemory: { observation: { threadTitle: true, }, }, }, }); ``` - Added version query parameters to GET /api/agents/:agentId endpoint. Code-defined agents can now be resolved with specific stored config versions using ?status=draft (latest, default), ?status=published (active version), or ?versionId= (specific version). ([#14156](https://github.com/mastra-ai/mastra/pull/14156)) - Fixed generation span output to include tool call data, enabling PostHog's LLM Analytics Tools tab to extract and display tool usage ([#14383](https://github.com/mastra-ai/mastra/pull/14383)) - Fixed a bug where workflow-based processor execution would pass null stream parts to subsequent processors. When a processor's processOutputStream returns null (e.g., a guardrail filtering a part), the next processor in the chain would receive null as the part, causing potential errors. The null guard now matches the inline processor path behavior, skipping processors when the part is null. ([#14514](https://github.com/mastra-ai/mastra/pull/14514)) - Updated dependencies [[`b71bce1`](https://github.com/mastra-ai/mastra/commit/b71bce144912ed33f76c52a94e594988a649c3e1), [`cd7b568`](https://github.com/mastra-ai/mastra/commit/cd7b568fe427b1b4838abe744fa5367a47539db3)]: - @mastra/schema-compat@1.2.6 ## 1.15.0-alpha.4 ### Patch Changes - Added experimental retrieval-mode recall tooling for observational memory. ([#14437](https://github.com/mastra-ai/mastra/pull/14437)) When `observationalMemory.retrieval` is enabled with `scope: 'thread'`, observation groups store colon-delimited message ranges (`startId:endId`) pointing back to the raw messages they were derived from. A `recall` tool is registered that lets agents retrieve those source messages via cursor-based pagination. The recall tool supports: - **Detail levels**: `detail: 'low'` (default) returns truncated text with part indices; `detail: 'high'` returns full content clamped to one part per call with continuation hints - **Part-level fetch**: `partIndex` targets a single message part at full detail - **Pagination flags**: `hasNextPage` and `hasPrevPage` in results - **Token limiting**: results are capped at a token budget with `truncated` and `tokenOffset` reporting - **Smart range detection**: passing a range as a cursor returns a helpful hint explaining how to extract individual IDs - Fixed a bug where workflow-based processor execution would pass null stream parts to subsequent processors. When a processor's processOutputStream returns null (e.g., a guardrail filtering a part), the next processor in the chain would receive null as the part, causing potential errors. The null guard now matches the inline processor path behavior, skipping processors when the part is null. ([#14514](https://github.com/mastra-ai/mastra/pull/14514)) ## 1.15.0-alpha.3 ### Minor Changes - Added filesystem-level optimistic concurrency for file writes. When `expectedMtime` is provided in `WriteOptions`, the write will be rejected with a `StaleFileError` if the file was modified externally since it was last read. This provides defense-in-depth against external modifications (e.g., LSP-based editors) that occur between the tool-level mtime check and the actual write. ([#14354](https://github.com/mastra-ai/mastra/pull/14354)) **New `expectedMtime` option on `WriteOptions`** Any caller of `filesystem.writeFile()` can now opt into optimistic concurrency: ```ts // Read a file and capture its mtime const stat = await filesystem.stat('config.json'); const content = await filesystem.readFile('config.json'); // Later, write with mtime guard — fails if file changed externally await filesystem.writeFile('config.json', newContent, { overwrite: true, expectedMtime: stat.modifiedAt, }); ``` If the file was modified between the read and write, a `StaleFileError` is thrown instead of silently overwriting. **Automatic mtime pass-through for workspace tools** When `requireReadBeforeWrite` is enabled, the `edit_file`, `write_file`, and `ast_edit` tools now automatically pass the recorded mtime through to the filesystem layer, providing a second line of defense beyond the existing tool-level read tracker check. ## 1.15.0-alpha.2 ### Patch Changes - Fixed deserializeRequestContext to return an actual RequestContext instance instead of a Map, preventing crashes during Inngest durable execution replay with observability enabled. ([#14442](https://github.com/mastra-ai/mastra/pull/14442)) - Fixed generation span output to include tool call data, enabling PostHog's LLM Analytics Tools tab to extract and display tool usage ([#14383](https://github.com/mastra-ai/mastra/pull/14383)) ## 1.15.0-alpha.1 ### Patch Changes - Added opt-in Observational Memory thread titles. ([#14436](https://github.com/mastra-ai/mastra/pull/14436)) When enabled, the Observer suggests a short thread title and updates it as the conversation topic changes. Harness consumers can detect these updates via the new `om_thread_title_updated` event. **Example** ```ts const memory = new Memory({ options: { observationalMemory: { observation: { threadTitle: true, }, }, }, }); ``` - Updated dependencies [[`cd7b568`](https://github.com/mastra-ai/mastra/commit/cd7b568fe427b1b4838abe744fa5367a47539db3)]: - @mastra/schema-compat@1.2.6-alpha.1 ## 1.15.0-alpha.0 ### Minor Changes - Add `server.studioHost`, `server.studioProtocol`, and `server.studioPort` options for Studio in cloud deployments ([#12899](https://github.com/mastra-ai/mastra/pull/12899)) When deploying to cloud environments (e.g., Google Cloud Run), `server.host` must be `0.0.0.0` for the container to accept traffic, and the internal port often differs from the external one (e.g., 8080 internally vs 443 externally). Studio needs the actual public domain, protocol, and port to make API calls from the browser. These new options decouple the server bind configuration from the Studio API URL. ```typescript export const mastra = new Mastra({ server: { host: '0.0.0.0', port: 8080, studioHost: 'my-app.run.app', studioProtocol: 'https', studioPort: 443, }, }); ``` All three options are optional and fall back to existing behavior when not set. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`cb611a1`](https://github.com/mastra-ai/mastra/commit/cb611a1e89a4f4cf74c97b57e0c27bb56f2eceb5)) - Fixed agent.stream() so the returned spanId matches the top-level agent run span instead of the nested model span. ([#14447](https://github.com/mastra-ai/mastra/pull/14447)) - Fixed trace context propagation in evented workflow steps and processors. Operations started inside those steps now appear under the correct parent in distributed traces. ([#14455](https://github.com/mastra-ai/mastra/pull/14455)) - Fix OTEL context propagation in workflow step execution. Wrapping `step.execute()` in `executeWithContext` ensures auto-instrumented code inside a step (e.g. AI SDK spans) is correctly nested under the workflow step span rather than appearing as siblings. ([#13755](https://github.com/mastra-ai/mastra/pull/13755)) - Added version query parameters to GET /api/agents/:agentId endpoint. Code-defined agents can now be resolved with specific stored config versions using ?status=draft (latest, default), ?status=published (active version), or ?versionId= (specific version). ([#14156](https://github.com/mastra-ai/mastra/pull/14156)) - Updated dependencies [[`b71bce1`](https://github.com/mastra-ai/mastra/commit/b71bce144912ed33f76c52a94e594988a649c3e1)]: - @mastra/schema-compat@1.2.6-alpha.0 ## 1.14.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`51970b3`](https://github.com/mastra-ai/mastra/commit/51970b3828494d59a8dd4df143b194d37d31e3f5)) - Added dated message boundary delimiters when activating buffered observations for improved cache stability. ([#14367](https://github.com/mastra-ai/mastra/pull/14367)) - Fixed provider-executed tool calls being saved out of order or without results in memory replay. (Fixes #13762) ([#13860](https://github.com/mastra-ai/mastra/pull/13860)) - Fix `generateEmptyFromSchema` to accept both string and pre-parsed object JSON schema inputs, recursively initialize nested object properties, and respect default values. Updated `WorkingMemoryTemplate` type to a discriminated union supporting `Record` content for JSON format templates. Removed duplicate private schema generator in the working-memory processor in favor of the shared utility. ([#14310](https://github.com/mastra-ai/mastra/pull/14310)) - Fixed provider-executed tool calls (e.g. Anthropic `web_search`) being dropped or incorrectly persisted when deferred by the provider. Tool call parts are now persisted in stream order, and deferred tool results are correctly merged back into the originating message. ([#14282](https://github.com/mastra-ai/mastra/pull/14282)) - Fixed `replaceString` utility to properly escape `$` characters in replacement strings. Previously, patterns like `$&` in the replacement text would be interpreted as regex backreferences instead of literal text. ([#14434](https://github.com/mastra-ai/mastra/pull/14434)) - Fixed tool invocation updates to preserve `providerExecuted` and `providerMetadata` from the original tool call when updating to result state. ([#14431](https://github.com/mastra-ai/mastra/pull/14431)) - `@mastra/core`: patch ([#14327](https://github.com/mastra-ai/mastra/pull/14327)) Added `spanId` alongside `traceId` across user-facing execution results that return tracing identifiers (including agent stream/generate and workflow run results) so integrations can query observability vendors by run root span ID - Add AI Gateway tool support in the agentic loop. ([#14016](https://github.com/mastra-ai/mastra/pull/14016)) Gateway tools (e.g., `gateway.tools.perplexitySearch()`) are provider-executed but, unlike native provider tools (e.g., `openai.tools.webSearch()`), the LLM provider does not store their results server-side. The agentic loop now correctly infers `providerExecuted` for these tools, merges streamed provider results with their corresponding tool calls, and skips local execution when a provider result is already present. Fixes #13190 - Fixed schema-based working memory typing so `workingMemory.schema` accepts supported schemas such as Zod and JSON Schema. ([#14363](https://github.com/mastra-ai/mastra/pull/14363)) - Fixed workspace search being wiped when skills refresh. Previously, calling `skills.refresh()` or triggering a skills re-discovery via `maybeRefresh()` would clear the entire BM25 search index, including auto-indexed workspace content. Now only skill entries are removed from the index during refresh, preserving workspace search results. ([#14287](https://github.com/mastra-ai/mastra/pull/14287)) - Added client/server body schemas for feedback and scores that omit the timestamp field, allowing it to be set server-side ([#14270](https://github.com/mastra-ai/mastra/pull/14270)) - Fixed processor state not persisting between processOutputStream and processOutputResult when processors are wrapped in workflows. State set during stream processing is now correctly accessible in processOutputResult. ([#14279](https://github.com/mastra-ai/mastra/pull/14279)) - Fixed type inference for requestContext schemas when using Zod v3 and v4. Agent and tool configurations now correctly infer RequestContext types from Zod schemas and other StandardSchema-compatible schemas. ([#14363](https://github.com/mastra-ai/mastra/pull/14363)) - Updated dependencies [[`4a7ce05`](https://github.com/mastra-ai/mastra/commit/4a7ce05125b8d3d260f68f1fc4a6c6866d22ba24)]: - @mastra/schema-compat@1.2.5 ## 1.14.0-alpha.3 ### Patch Changes - Fixed `replaceString` utility to properly escape `$` characters in replacement strings. Previously, patterns like `$&` in the replacement text would be interpreted as regex backreferences instead of literal text. ([#14434](https://github.com/mastra-ai/mastra/pull/14434)) - Fixed tool invocation updates to preserve `providerExecuted` and `providerMetadata` from the original tool call when updating to result state. ([#14431](https://github.com/mastra-ai/mastra/pull/14431)) - Fixed schema-based working memory typing so `workingMemory.schema` accepts supported schemas such as Zod and JSON Schema. ([#14363](https://github.com/mastra-ai/mastra/pull/14363)) - Fixed type inference for requestContext schemas when using Zod v3 and v4. Agent and tool configurations now correctly infer RequestContext types from Zod schemas and other StandardSchema-compatible schemas. ([#14363](https://github.com/mastra-ai/mastra/pull/14363)) ## 1.14.0-alpha.2 ### Patch Changes - Added dated message boundary delimiters when activating buffered observations for improved cache stability. ([#14367](https://github.com/mastra-ai/mastra/pull/14367)) - Fixed provider-executed tool calls (e.g. Anthropic `web_search`) being dropped or incorrectly persisted when deferred by the provider. Tool call parts are now persisted in stream order, and deferred tool results are correctly merged back into the originating message. ([#14282](https://github.com/mastra-ai/mastra/pull/14282)) - Added client/server body schemas for feedback and scores that omit the timestamp field, allowing it to be set server-side ([#14270](https://github.com/mastra-ai/mastra/pull/14270)) ## 1.13.3-alpha.1 ### Patch Changes - Fix `generateEmptyFromSchema` to accept both string and pre-parsed object JSON schema inputs, recursively initialize nested object properties, and respect default values. Updated `WorkingMemoryTemplate` type to a discriminated union supporting `Record` content for JSON format templates. Removed duplicate private schema generator in the working-memory processor in favor of the shared utility. ([#14310](https://github.com/mastra-ai/mastra/pull/14310)) - `@mastra/core`: patch ([#14327](https://github.com/mastra-ai/mastra/pull/14327)) Added `spanId` alongside `traceId` across user-facing execution results that return tracing identifiers (including agent stream/generate and workflow run results) so integrations can query observability vendors by run root span ID - Fixed workspace search being wiped when skills refresh. Previously, calling `skills.refresh()` or triggering a skills re-discovery via `maybeRefresh()` would clear the entire BM25 search index, including auto-indexed workspace content. Now only skill entries are removed from the index during refresh, preserving workspace search results. ([#14287](https://github.com/mastra-ai/mastra/pull/14287)) ## 1.13.3-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`51970b3`](https://github.com/mastra-ai/mastra/commit/51970b3828494d59a8dd4df143b194d37d31e3f5)) - Fixed provider-executed tool calls being saved out of order or without results in memory replay. (Fixes #13762) ([#13860](https://github.com/mastra-ai/mastra/pull/13860)) - Add AI Gateway tool support in the agentic loop. ([#14016](https://github.com/mastra-ai/mastra/pull/14016)) Gateway tools (e.g., `gateway.tools.perplexitySearch()`) are provider-executed but, unlike native provider tools (e.g., `openai.tools.webSearch()`), the LLM provider does not store their results server-side. The agentic loop now correctly infers `providerExecuted` for these tools, merges streamed provider results with their corresponding tool calls, and skips local execution when a provider result is already present. Fixes #13190 - Fixed processor state not persisting between processOutputStream and processOutputResult when processors are wrapped in workflows. State set during stream processing is now correctly accessible in processOutputResult. ([#14279](https://github.com/mastra-ai/mastra/pull/14279)) - Updated dependencies [[`4a7ce05`](https://github.com/mastra-ai/mastra/commit/4a7ce05125b8d3d260f68f1fc4a6c6866d22ba24)]: - @mastra/schema-compat@1.2.5-alpha.0 ## 1.13.2 ### Patch Changes - Fix WorkingMemory type to accept Zod schemas in workingMemory.schema configuration ([#14261](https://github.com/mastra-ai/mastra/pull/14261)) - Updated dependencies [[`1978bc4`](https://github.com/mastra-ai/mastra/commit/1978bc424dbb04f5f7c5d8522f07f1166006fa3f)]: - @mastra/schema-compat@1.2.4 ## 1.13.2-alpha.0 ### Patch Changes - Fix WorkingMemory type to accept Zod schemas in workingMemory.schema configuration ([#14261](https://github.com/mastra-ai/mastra/pull/14261)) - Updated dependencies [[`1978bc4`](https://github.com/mastra-ai/mastra/commit/1978bc424dbb04f5f7c5d8522f07f1166006fa3f)]: - @mastra/schema-compat@1.2.4-alpha.0 ## 1.13.1 ### Patch Changes - Fixed tryRepairJson to handle unquoted date values (e.g. 2026-04-15) in tool call arguments. LLMs like Claude Sonnet sometimes produce bare date values without quotes, causing JSON.parse to fail and tool calls to silently lose all arguments. The repair function now detects and quotes date-like values matching YYYY-MM-DD and YYYY-MM-DDTHH:MM:SS patterns. See https://github.com/mastra-ai/mastra/issues/14230 ([#14233](https://github.com/mastra-ai/mastra/pull/14233)) - Updated dependencies [[`c4e600e`](https://github.com/mastra-ai/mastra/commit/c4e600e39a04309c3a7ff182bd806ab2b3c788ea)]: - @mastra/schema-compat@1.2.3 ## 1.13.0 ### Minor Changes - **Added observability storage domain schemas and implementations** ([#14214](https://github.com/mastra-ai/mastra/pull/14214)) Introduced comprehensive storage schemas and in-memory implementations for all observability signals (scores, logs, feedback, metrics, discovery). All schemas are zod-based with full type inference. The `ObservabilityStorage` base class includes default implementations for all new methods. **Breaking changes:** - `MetricType` (`counter`/`gauge`/`histogram`) is deprecated — metrics are now raw events with aggregation at query time - Score schemas use `scorerId` instead of `scorerName` for scorer identification ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ea86967`](https://github.com/mastra-ai/mastra/commit/ea86967449426e0a3673253bd1c2c052a99d970d)) - Fixed provider tools (e.g. `openai.tools.webSearch()`) being silently dropped when using a custom gateway that returns AI SDK v6 (V3) models. The router now remaps tool types from `provider-defined` to `provider` when delegating to V3 models, so provider tools work correctly through gateways. Fixes #13667. ([#13895](https://github.com/mastra-ai/mastra/pull/13895)) - Fixed TypeScript type errors in `onStepFinish` and `onFinish` callbacks, and resolved compatibility issues with `createOpenRouter()` across different AI SDK versions. ([#14229](https://github.com/mastra-ai/mastra/pull/14229)) - Fixed a bug where thread metadata (e.g. title, custom properties) passed via `options.memory.thread` was discarded when `MASTRA_THREAD_ID_KEY` was set in the request context. The thread ID from context still takes precedence, but all other user-provided thread properties are now preserved. ([#13146](https://github.com/mastra-ai/mastra/pull/13146)) - Fixed workspace tools such as `mastra_workspace_list_files` and `mastra_workspace_read_file` failing with `WorkspaceNotAvailableError` in some execution paths. ([#14228](https://github.com/mastra-ai/mastra/pull/14228)) Workspace tools now work consistently across execution paths. - Added observer context optimization for Observational Memory. The `observation.previousObserverTokens` field reduces Observer input token costs for long-running conversations: ([#13568](https://github.com/mastra-ai/mastra/pull/13568)) - **previousObserverTokens** (default: `2000`): Truncates the 'Previous Observations' section to a token budget, keeping the most recent observations and automatically replacing already-reflected lines with the buffered reflection summary. Set to `0` to omit previous observations entirely, or `false` to disable truncation and keep the full observation history. ```typescript const memory = new Memory({ options: { observationalMemory: { model: 'google/gemini-2.5-flash', observation: { previousObserverTokens: 10_000, }, }, }, }); ``` - Updated dependencies [[`a1d6b9c`](https://github.com/mastra-ai/mastra/commit/a1d6b9c907c909f259632a7ea26e9e3c221fb691), [`c562ec2`](https://github.com/mastra-ai/mastra/commit/c562ec228f1af63693e2984ffa9712aa6db8fea8)]: - @mastra/schema-compat@1.2.2 ## 1.13.0-alpha.0 ### Minor Changes - **Added observability storage domain schemas and implementations** ([#14214](https://github.com/mastra-ai/mastra/pull/14214)) Introduced comprehensive storage schemas and in-memory implementations for all observability signals (scores, logs, feedback, metrics, discovery). All schemas are zod-based with full type inference. The `ObservabilityStorage` base class includes default implementations for all new methods. **Breaking changes:** - `MetricType` (`counter`/`gauge`/`histogram`) is deprecated — metrics are now raw events with aggregation at query time - Score schemas use `scorerId` instead of `scorerName` for scorer identification ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`ea86967`](https://github.com/mastra-ai/mastra/commit/ea86967449426e0a3673253bd1c2c052a99d970d)) - Fixed provider tools (e.g. `openai.tools.webSearch()`) being silently dropped when using a custom gateway that returns AI SDK v6 (V3) models. The router now remaps tool types from `provider-defined` to `provider` when delegating to V3 models, so provider tools work correctly through gateways. Fixes #13667. ([#13895](https://github.com/mastra-ai/mastra/pull/13895)) - Fixed TypeScript type errors in `onStepFinish` and `onFinish` callbacks, and resolved compatibility issues with `createOpenRouter()` across different AI SDK versions. ([#14229](https://github.com/mastra-ai/mastra/pull/14229)) - Fixed a bug where thread metadata (e.g. title, custom properties) passed via `options.memory.thread` was discarded when `MASTRA_THREAD_ID_KEY` was set in the request context. The thread ID from context still takes precedence, but all other user-provided thread properties are now preserved. ([#13146](https://github.com/mastra-ai/mastra/pull/13146)) - Fixed workspace tools such as `mastra_workspace_list_files` and `mastra_workspace_read_file` failing with `WorkspaceNotAvailableError` in some execution paths. ([#14228](https://github.com/mastra-ai/mastra/pull/14228)) Workspace tools now work consistently across execution paths. - Added observer context optimization for Observational Memory. The `observation.previousObserverTokens` field reduces Observer input token costs for long-running conversations: ([#13568](https://github.com/mastra-ai/mastra/pull/13568)) - **previousObserverTokens** (default: `2000`): Truncates the 'Previous Observations' section to a token budget, keeping the most recent observations and automatically replacing already-reflected lines with the buffered reflection summary. Set to `0` to omit previous observations entirely, or `false` to disable truncation and keep the full observation history. ```typescript const memory = new Memory({ options: { observationalMemory: { model: 'google/gemini-2.5-flash', observation: { previousObserverTokens: 10_000, }, }, }, }); ``` - Updated dependencies [[`a1d6b9c`](https://github.com/mastra-ai/mastra/commit/a1d6b9c907c909f259632a7ea26e9e3c221fb691), [`c562ec2`](https://github.com/mastra-ai/mastra/commit/c562ec228f1af63693e2984ffa9712aa6db8fea8)]: - @mastra/schema-compat@1.2.2-alpha.0 ## 1.12.0 ### Minor Changes - MCP tool calls now use `MCP_TOOL_CALL` span type instead of `TOOL_CALL` in traces. `CoreToolBuilder` detects `mcpMetadata` on tools and creates spans with MCP server name, version, and tool description attributes. ([#13274](https://github.com/mastra-ai/mastra/pull/13274)) - **Absolute paths now resolve to real filesystem locations instead of being treated as workspace-relative.** ([#13804](https://github.com/mastra-ai/mastra/pull/13804)) Previously, `LocalFilesystem` in contained mode treated absolute paths like `/file.txt` as shorthand for `basePath/file.txt` (a "virtual-root" convention). This could silently resolve paths to unexpected locations — for example, `/home/user/.config/file.txt` would resolve to `basePath/home/user/.config/file.txt` instead of the real path. Now: - **Absolute paths** (starting with `/`) are real filesystem paths, subject to containment checks - **Relative paths** (e.g., `file.txt`, `src/index.ts`) resolve against `basePath` - **Tilde paths** (e.g., `~/Documents`) expand to the home directory ### Migration If your code passes paths like `/file.txt` to workspace filesystem methods expecting them to resolve relative to `basePath`, change them to relative paths: ```ts // Before await filesystem.readFile('/src/index.ts'); // After await filesystem.readFile('src/index.ts'); ``` Also fixed: - `allowedPaths` resolving against the working directory instead of `basePath`, causing unexpected permission errors when `basePath` differed from `cwd` - Permission errors when accessing paths under `allowedPaths` directories that don't exist yet (e.g., during skills discovery) - Changed `ProcessHandle.pid` type from `number` to `string` to support sandbox providers that use non-numeric process identifiers (e.g., session IDs). ([#13591](https://github.com/mastra-ai/mastra/pull/13591)) **Before:** ```typescript const handle = await sandbox.processes.spawn('node server.js'); handle.pid; // number await sandbox.processes.get(42); ``` **After:** ```typescript const handle = await sandbox.processes.spawn('node server.js'); handle.pid; // string (e.g., '1234' for local, 'session-abc' for Daytona) await sandbox.processes.get('1234'); ``` ### Patch Changes - Added a `mastra/` User-Agent header to all provider API requests (OpenAI, Anthropic, Google, Mistral, Groq, xAI, DeepSeek, and others) across models.dev, Netlify, and Azure gateways for better traffic attribution. ([#13087](https://github.com/mastra-ai/mastra/pull/13087)) - Update provider registry and model documentation with latest models and providers ([`9cede11`](https://github.com/mastra-ai/mastra/commit/9cede110abac9d93072e0521bb3c8bcafb9fdadf)) - Fixed processor-triggered aborts not appearing in traces. Processor spans now include abort details (reason, retry flag, metadata) and agent-level spans capture the same information when an abort short-circuits the agent run. This makes guardrail and processor aborts fully visible in tracing dashboards. ([#14038](https://github.com/mastra-ai/mastra/pull/14038)) - Fix agent loop not continuing when `onIterationComplete` returns `continue: true` ([#14170](https://github.com/mastra-ai/mastra/pull/14170)) - Fixed exponential token growth during multi-step agent workflows by implementing `processInputStep` on `TokenLimiterProcessor` and removing the redundant `processInput` method. Token-based message pruning now runs at every step of the agentic loop (including tool call continuations), keeping the in-memory message list within budget before each LLM call. Also refactored Tiktoken encoder to use the shared global singleton from `getTiktoken()` instead of creating a new instance per processor. ([#13929](https://github.com/mastra-ai/mastra/pull/13929)) - Sub-agents with `defaultOptions.memory` configurations were having their memory settings overridden when called as tools from a parent agent. The parent unconditionally passed its own `memory` option (with newly generated thread/resource IDs), which replaced the sub-agent's intended memory configuration due to shallow object merging. ([#11561](https://github.com/mastra-ai/mastra/pull/11561)) This fix checks if the sub-agent has its own `defaultOptions.memory` before applying parent-derived memory settings. Sub-agents without their own memory config continue to receive parent-derived IDs as a fallback. No code changes required for consumers - sub-agents with explicit `defaultOptions.memory` will now work correctly when used via the `agents: {}` option. - Fixed listConfiguredInputProcessors() and listConfiguredOutputProcessors() returning a combined workflow instead of individual processors. Previously, these methods wrapped all configured processors into a single committed workflow, making it impossible to inspect or look up processors by ID. Now they return the raw flat array of configured processors as intended. ([#14158](https://github.com/mastra-ai/mastra/pull/14158)) - `fetchWithRetry` now backs off in sequence 2s → 4s → 8s and then caps at 10s. ([#14159](https://github.com/mastra-ai/mastra/pull/14159)) - Preserve trace continuity across workflow suspend/resume for workflows run by the default engine, so resumed workflows appear as children of the original span in tracing tools. ([#12276](https://github.com/mastra-ai/mastra/pull/12276)) - Updated dependencies [[`709362d`](https://github.com/mastra-ai/mastra/commit/709362d67b80d8832729bbf9e449cad27640a5d2), [`787f3ac`](https://github.com/mastra-ai/mastra/commit/787f3ac08b3bb77413645a7ab5c447fa851708fd)]: - @mastra/schema-compat@1.2.1 ## 1.12.0-alpha.1 ### Minor Changes - **Absolute paths now resolve to real filesystem locations instead of being treated as workspace-relative.** ([#13804](https://github.com/mastra-ai/mastra/pull/13804)) Previously, `LocalFilesystem` in contained mode treated absolute paths like `/file.txt` as shorthand for `basePath/file.txt` (a "virtual-root" convention). This could silently resolve paths to unexpected locations — for example, `/home/user/.config/file.txt` would resolve to `basePath/home/user/.config/file.txt` instead of the real path. Now: - **Absolute paths** (starting with `/`) are real filesystem paths, subject to containment checks - **Relative paths** (e.g., `file.txt`, `src/index.ts`) resolve against `basePath` - **Tilde paths** (e.g., `~/Documents`) expand to the home directory ### Migration If your code passes paths like `/file.txt` to workspace filesystem methods expecting them to resolve relative to `basePath`, change them to relative paths: ```ts // Before await filesystem.readFile('/src/index.ts'); // After await filesystem.readFile('src/index.ts'); ``` Also fixed: - `allowedPaths` resolving against the working directory instead of `basePath`, causing unexpected permission errors when `basePath` differed from `cwd` - Permission errors when accessing paths under `allowedPaths` directories that don't exist yet (e.g., during skills discovery) - Changed `ProcessHandle.pid` type from `number` to `string` to support sandbox providers that use non-numeric process identifiers (e.g., session IDs). ([#13591](https://github.com/mastra-ai/mastra/pull/13591)) **Before:** ```typescript const handle = await sandbox.processes.spawn('node server.js'); handle.pid; // number await sandbox.processes.get(42); ``` **After:** ```typescript const handle = await sandbox.processes.spawn('node server.js'); handle.pid; // string (e.g., '1234' for local, 'session-abc' for Daytona) await sandbox.processes.get('1234'); ``` ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`9cede11`](https://github.com/mastra-ai/mastra/commit/9cede110abac9d93072e0521bb3c8bcafb9fdadf)) - Fixed processor-triggered aborts not appearing in traces. Processor spans now include abort details (reason, retry flag, metadata) and agent-level spans capture the same information when an abort short-circuits the agent run. This makes guardrail and processor aborts fully visible in tracing dashboards. ([#14038](https://github.com/mastra-ai/mastra/pull/14038)) - Fixed exponential token growth during multi-step agent workflows by implementing `processInputStep` on `TokenLimiterProcessor` and removing the redundant `processInput` method. Token-based message pruning now runs at every step of the agentic loop (including tool call continuations), keeping the in-memory message list within budget before each LLM call. Also refactored Tiktoken encoder to use the shared global singleton from `getTiktoken()` instead of creating a new instance per processor. ([#13929](https://github.com/mastra-ai/mastra/pull/13929)) - Fixed listConfiguredInputProcessors() and listConfiguredOutputProcessors() returning a combined workflow instead of individual processors. Previously, these methods wrapped all configured processors into a single committed workflow, making it impossible to inspect or look up processors by ID. Now they return the raw flat array of configured processors as intended. ([#14158](https://github.com/mastra-ai/mastra/pull/14158)) - Updated dependencies [[`709362d`](https://github.com/mastra-ai/mastra/commit/709362d67b80d8832729bbf9e449cad27640a5d2)]: - @mastra/schema-compat@1.2.1-alpha.1 ## 1.12.0-alpha.0 ### Minor Changes - MCP tool calls now use `MCP_TOOL_CALL` span type instead of `TOOL_CALL` in traces. `CoreToolBuilder` detects `mcpMetadata` on tools and creates spans with MCP server name, version, and tool description attributes. ([#13274](https://github.com/mastra-ai/mastra/pull/13274)) ### Patch Changes - Added a `mastra/` User-Agent header to all provider API requests (OpenAI, Anthropic, Google, Mistral, Groq, xAI, DeepSeek, and others) across models.dev, Netlify, and Azure gateways for better traffic attribution. ([#13087](https://github.com/mastra-ai/mastra/pull/13087)) - Fix agent loop not continuing when `onIterationComplete` returns `continue: true` ([#14155](https://github.com/mastra-ai/mastra/pull/14155)) - Sub-agents with `defaultOptions.memory` configurations were having their memory settings overridden when called as tools from a parent agent. The parent unconditionally passed its own `memory` option (with newly generated thread/resource IDs), which replaced the sub-agent's intended memory configuration due to shallow object merging. ([#11561](https://github.com/mastra-ai/mastra/pull/11561)) This fix checks if the sub-agent has its own `defaultOptions.memory` before applying parent-derived memory settings. Sub-agents without their own memory config continue to receive parent-derived IDs as a fallback. No code changes required for consumers - sub-agents with explicit `defaultOptions.memory` will now work correctly when used via the `agents: {}` option. - `fetchWithRetry` now backs off in sequence 2s → 4s → 8s and then caps at 10s. ([#14159](https://github.com/mastra-ai/mastra/pull/14159)) - Preserve trace continuity across workflow suspend/resume for workflows run by the default engine, so resumed workflows appear as children of the original span in tracing tools. ([#12276](https://github.com/mastra-ai/mastra/pull/12276)) - Updated dependencies [[`787f3ac`](https://github.com/mastra-ai/mastra/commit/787f3ac08b3bb77413645a7ab5c447fa851708fd)]: - @mastra/schema-compat@1.2.1-alpha.0 ## 1.11.0 ### Minor Changes - feat: support dynamic functions returning model fallback arrays ([#11975](https://github.com/mastra-ai/mastra/pull/11975)) Agents can now use dynamic functions that return entire fallback arrays based on runtime context. This enables: - Dynamic selection of complete fallback configurations - Context-based model selection with automatic fallback - Flexible model routing based on user tier, region, or other factors - Nested dynamic functions within returned arrays (each model in array can also be dynamic) ## Examples ### Basic dynamic fallback array ```typescript const agent = new Agent({ model: ({ requestContext }) => { const tier = requestContext.get('tier'); if (tier === 'premium') { return [ { model: 'openai/gpt-4', maxRetries: 2 }, { model: 'anthropic/claude-3-opus', maxRetries: 1 }, ]; } return [{ model: 'openai/gpt-3.5-turbo', maxRetries: 1 }]; }, }); ``` ### Region-based routing with nested dynamics ```typescript const agent = new Agent({ model: ({ requestContext }) => { const region = requestContext.get('region'); return [ { model: ({ requestContext }) => { // Select model variant based on region return region === 'eu' ? 'openai/gpt-4-eu' : 'openai/gpt-4'; }, maxRetries: 2, }, { model: 'anthropic/claude-3-opus', maxRetries: 1 }, ]; }, maxRetries: 1, // Agent-level default for models without explicit maxRetries }); ``` ### Async dynamic selection ```typescript const agent = new Agent({ model: async ({ requestContext }) => { // Fetch user's tier from database const userId = requestContext.get('userId'); const user = await db.users.findById(userId); if (user.tier === 'enterprise') { return [ { model: 'openai/gpt-4', maxRetries: 3 }, { model: 'anthropic/claude-3-opus', maxRetries: 2 }, ]; } return [{ model: 'openai/gpt-3.5-turbo', maxRetries: 1 }]; }, }); ``` ## Technical Details - Functions can return `MastraModelConfig` (single model) or `ModelWithRetries[]` (array) - Models without explicit `maxRetries` inherit agent-level `maxRetries` default - Each model in returned array can also be a dynamic function for nested selection - Empty arrays are validated and throw errors early - Arrays are normalized to `ModelFallbacks` with all required fields filled in - Performance optimization: Already-normalized arrays skip re-normalization ## Fixes and Improvements - Dynamic model fallbacks now properly inherit agent-level `maxRetries` when not explicitly specified - `getModelList()` now correctly handles dynamic functions that return arrays - Added validation for empty arrays returned from dynamic functions - Added type guard optimization to prevent double normalization of static arrays - Preserved backward-compatible `getLLM()` and `getModel()` return behavior while adding dynamic fallback array support - Comprehensive test coverage for edge cases (async functions, nested dynamics, error handling) ## Documentation - Added dynamic fallback array example in Models docs under **Model fallbacks** ## Migration Guide No breaking changes. All existing model configurations continue to work: - Static single models: `model: 'openai/gpt-4'` - Static arrays: `model: [{ model: 'openai/gpt-4', maxRetries: 2 }]` - Dynamic single: `model: ({ requestContext }) => 'openai/gpt-4'` - Dynamic arrays (NEW): `model: ({ requestContext }) => [{ model: 'openai/gpt-4', maxRetries: 2 }]` Closes #11951 - Added `onValidationError` hook to `ServerConfig` and `createRoute()`. When a request fails Zod schema validation (query parameters, request body, or path parameters), this hook lets you customize the error response — including the HTTP status code and response body — instead of the default 400 response. Set it on the server config to apply globally, or on individual routes to override per-route. All server adapters (Hono, Express, Fastify, Koa) support this hook. ([#13477](https://github.com/mastra-ai/mastra/pull/13477)) ```ts const mastra = new Mastra({ server: { onValidationError: (error, context) => ({ status: 422, body: { ok: false, errors: error.issues.map(i => ({ path: i.path.join('.'), message: i.message, })), source: context, }, }), }, }); ``` - Added `requestContext` field to tracing spans. Each span now automatically captures a snapshot of the active `RequestContext`, making request-scoped values like user IDs, tenant IDs, and feature flags available when viewing traces. ([#14020](https://github.com/mastra-ai/mastra/pull/14020)) - Added `allowedWorkspaceTools` to `HarnessSubagent`. Subagents now automatically inherit the parent agent's workspace. Use `allowedWorkspaceTools` to restrict which workspace tools a subagent can see: ([#13940](https://github.com/mastra-ai/mastra/pull/13940)) ```ts const subagent: HarnessSubagent = { id: 'explore', name: 'Explore', allowedWorkspaceTools: ['view', 'search_content', 'find_files'], }; ``` - Enabled tracing for tool executions through mcp server ([#12804](https://github.com/mastra-ai/mastra/pull/12804)) Traces now appear in the Observability UI for MCP server tool calls - Added `result` to `processOutputResult` args, providing resolved generation data (usage, text, steps, finishReason) directly. This replaces raw stream chunks with an easy-to-use `OutputResult` object containing the same data available in the `onFinish` callback. ([#13810](https://github.com/mastra-ai/mastra/pull/13810)) ```typescript const usageProcessor: Processor = { id: 'usage-processor', processOutputResult({ result, messages }) { console.log(`Text: ${result.text}`); console.log(`Tokens: ${result.usage.inputTokens} in, ${result.usage.outputTokens} out`); console.log(`Finish reason: ${result.finishReason}`); console.log(`Steps: ${result.steps.length}`); return messages; }, }; ``` - Added `requestContext` support for dataset items and experiments. ([#13938](https://github.com/mastra-ai/mastra/pull/13938)) **Dataset items** now accept an optional `requestContext` field when adding or updating items. This lets you store per-item request context alongside inputs and ground truths. **Datasets** now support a `requestContextSchema` field to describe the expected shape of request context on items. **Experiments** now accept a `requestContext` option that gets passed through to `agent.generate()` during execution. Per-item request context merges with (and takes precedence over) the experiment-level context. ```ts // Add item with request context await dataset.addItem({ input: messages, groundTruth: expectedOutput, requestContext: { userId: '123', locale: 'en' }, }); // Run experiment with global request context await runExperiment(mastra, { datasetId: 'my-dataset', targetType: 'agent', targetId: 'my-agent', requestContext: { environment: 'staging' }, }); ``` - Add Zod v4 and Standard Schema support ([#12238](https://github.com/mastra-ai/mastra/pull/12238)) ## Zod v4 Breaking Changes - Fix all `z.record()` calls to use 2-argument form (key + value schema) as required by Zod v4 - Update `ZodError.errors` to `ZodError.issues` (Zod v4 API change) - Update `@ai-sdk/provider` versions for Zod v4 compatibility ## Standard Schema Integration - Add `packages/core/src/schema/` module that re-exports from `@mastra/schema-compat` - Migrate codebase to use `PublicSchema` type for schema parameters - Use `toStandardSchema()` for normalizing schemas across Zod v3, Zod v4, AI SDK Schema, and JSON Schema - Use `standardSchemaToJSONSchema()` for JSON Schema conversion ## Schema Compatibility (@mastra/schema-compat) - Add new adapter exports: `@mastra/schema-compat/adapters/ai-sdk`, `@mastra/schema-compat/adapters/zod-v3`, `@mastra/schema-compat/adapters/json-schema` - Enhance test coverage with separate v3 and v4 test suites - Improve zod-to-json conversion with `unrepresentable: 'any'` support ## TypeScript Fixes - Resolve deep instantiation errors in client-js and model.ts - Add proper type assertions where Zod v4 inference differs **BREAKING CHANGE**: Minimum Zod version is now `^3.25.0` for v3 compatibility or `^4.0.0` for v4 ### Patch Changes - dependencies updates: ([#14085](https://github.com/mastra-ai/mastra/pull/14085)) - Updated dependency [`ajv@^8.18.0` ↗︎](https://www.npmjs.com/package/ajv/v/8.18.0) (from `^8.17.1`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`332c014`](https://github.com/mastra-ai/mastra/commit/332c014e076b81edf7fe45b58205882726415e90)) - fix(workflows): add generic bail signature with overloads. The bail() function now uses method overloads - bail(result: TStepOutput) for backward compatibility and bail(result: ...) for workflow type inference. This allows flexible early exits while maintaining type safety for workflow chaining. Runtime validation will be added in a follow-up. ([#12211](https://github.com/mastra-ai/mastra/pull/12211)) - Fixed structured output parsing when JSON string fields include fenced JSON examples. ([#13948](https://github.com/mastra-ai/mastra/pull/13948)) - Fixed `writer` being undefined in `processOutputStream` for all output processors. The root cause was that `processPart` in `ProcessorRunner` did not pass the `writer` to `executeWorkflowAsProcessor` in the outputStream phase. Since all user processors are wrapped into workflows via `combineProcessorsIntoWorkflow`, this meant no processor ever received a `writer`. Custom output processors (like guardrail processors) can now reliably use `writer.custom()` to emit stream events. ([#14111](https://github.com/mastra-ai/mastra/pull/14111)) - Added JSON repair for malformed tool call arguments from LLM providers. When an LLM (e.g., Kimi/K2) generates broken JSON for tool call arguments, Mastra now attempts to fix common errors (missing quotes on property names, single quotes, trailing commas) before falling back to undefined. This reduces silent tool execution failures caused by minor JSON formatting issues. See https://github.com/mastra-ai/mastra/issues/11078 ([#14033](https://github.com/mastra-ai/mastra/pull/14033)) - Fixed Windows shell command execution to avoid visible cmd.exe popups and broken output piping. ([#13886](https://github.com/mastra-ai/mastra/pull/13886)) - Fixed OpenAI reasoning models (e.g. gpt-5-mini) failing with "function*call was provided without its required reasoning item" when the agent loops back after a tool call. The issue was that `callProviderMetadata.openai` carrying `fc*\*`item IDs was not being stripped alongside reasoning parts, causing the AI SDK to send`item_reference`instead of inline`function_call` content. ([#14144](https://github.com/mastra-ai/mastra/pull/14144)) - Output processors can now inspect, modify, or block custom `data-*` chunks emitted by tools via `writer.custom()` during streaming. Processors must opt in by setting `processDataParts = true` to receive these chunks in `processOutputStream`. ([#13823](https://github.com/mastra-ai/mastra/pull/13823)) ```ts class MyDataProcessor extends Processor { processDataParts = true; processOutputStream(part, { abort }) { if (part.type === 'data-sensitive') { abort('Blocked sensitive data'); } return part; } } ``` - Fixed agent tool calls not being surfaced in evented workflow streams. Added StreamChunkWriter abstraction and stream format configuration ('legacy' | 'vnext') to forward agent stream chunks through the workflow output stream. ([#12692](https://github.com/mastra-ai/mastra/pull/12692)) - Fixed OpenAI strict mode schema rejection when using agent networks with structured output. The compat layer was skipped when modelId was undefined, causing optional fields to be missing from the required array. (Fixes #12284) ([#13695](https://github.com/mastra-ai/mastra/pull/13695)) - Fixed `activeTools` to also enforce at execution time, not just at the model prompt. Tool calls to tools not in the active set are now rejected with a `ToolNotFoundError`. ([#13949](https://github.com/mastra-ai/mastra/pull/13949)) - Fix build failures on Windows when running `build:patch-commonjs` during `pnpm run setup` ([#14029](https://github.com/mastra-ai/mastra/pull/14029)) - Experiments now fail immediately with a clear error when triggered on a dataset with zero items, instead of getting stuck in "pending" status forever. The experiment trigger API returns HTTP 400 for empty datasets. Unexpected errors during async experiment setup are now logged and mark the experiment as failed. ([#14031](https://github.com/mastra-ai/mastra/pull/14031)) - fix: respect `lastMessages: false` in `recall()` to disable conversation history ([#12951](https://github.com/mastra-ai/mastra/pull/12951)) Setting `lastMessages: false` in Memory options now correctly prevents `recall()` from returning previous messages. Previously, the agent would retain the full conversation history despite this setting being disabled. Callers can still pass `perPage: false` explicitly to `recall()` to retrieve all messages (e.g., for displaying thread history in a UI). - `@mastra/core`: patch ([#14103](https://github.com/mastra-ai/mastra/pull/14103)) Fixed reasoning content being lost in multi-turn conversations with thinking models (kimi-k2.5, DeepSeek-R1) when using OpenAI-compatible providers (e.g., OpenRouter). Previously, reasoning content could be discarded during streaming, causing 400 errors when the model tried to continue the conversation. Multi-turn conversations now preserve reasoning content correctly across all turns. - fix(workflows): propagate logger to executionEngine ([#12517](https://github.com/mastra-ai/mastra/pull/12517)) When a custom logger is set on a Workflow via `__registerPrimitives` or `__setLogger`, it is now correctly propagated to the internal `executionEngine`. This ensures workflow step execution errors are logged through the custom logger instead of the default ConsoleLogger, enabling proper observability integration. - Added permission denied handling for dataset pages. Datasets now show a "Permission Denied" screen when the user lacks access, matching the behavior of agents, workflows, and other resources. ([#13876](https://github.com/mastra-ai/mastra/pull/13876)) - Fixed stream freezing when using Anthropic's Programmatic Tool Calling (PTC). Streams that contain only tool-input streaming chunks (without explicit tool-call chunks) now correctly synthesize tool-call events and complete without hanging. See [#12390](https://github.com/mastra-ai/mastra/issues/12390). ([#12400](https://github.com/mastra-ai/mastra/pull/12400)) - Fixed subagents receiving parent's tool call/result parts in their context messages. On subsequent queries in a conversation, these references to tools the subagent doesn't have caused models (especially via custom gateways) to return blank or incorrect results. Parent delegation tool artifacts are now stripped from context before forwarding to subagents. ([#13927](https://github.com/mastra-ai/mastra/pull/13927)) - Memory now automatically creates btree indexes on `thread_id` and `resource_id` metadata fields when using PgVector. This prevents sequential scans on the `memory_messages` vector table, resolving performance issues under high load. ([#14034](https://github.com/mastra-ai/mastra/pull/14034)) Fixes #12109 - Clarified the `idGenerator` documentation to reflect the current context-aware function signature and documented the available `IdGeneratorContext` fields used for type-specific ID generation. ([#14081](https://github.com/mastra-ai/mastra/pull/14081)) - Reasoning content from OpenAI models is now stripped from conversation history before replaying it to the LLM, preventing API errors on follow-up messages while preserving reasoning data in the database. Fixes #12980. ([#13418](https://github.com/mastra-ai/mastra/pull/13418)) - Added `transient` option for data chunks to skip database persistence. Chunks marked as transient are streamed to the client for live display but not saved to storage, reducing bloat from large streaming outputs. ([#13869](https://github.com/mastra-ai/mastra/pull/13869)) ```ts await context.writer?.custom({ type: 'data-my-stream', data: { output: line }, transient: true, }); ``` Workspace tools now use this to mark stdout/stderr streaming chunks as transient. - Fixed message ID mismatch between generate/stream response and memory-stored messages. When an agent used memory, the message IDs returned in the response (e.g. `response.uiMessages[].id`) could differ from the IDs persisted to the database. This was caused by a format conversion that stripped message IDs during internal re-processing. Messages now retain their original IDs throughout the entire save pipeline. ([#13796](https://github.com/mastra-ai/mastra/pull/13796)) - Fixed assistant messages to persist `content.metadata.modelId` during streaming. ([#12969](https://github.com/mastra-ai/mastra/pull/12969)) This ensures stored and processed assistant messages keep the model identifier. Developers can now reliably read `content.metadata.modelId` from downstream storage adapters and processors. - Fixed `savePerStep: true` not actually persisting messages to storage during step execution. Previously, `onStepFinish` only accumulated messages in the in-memory MessageList but never flushed them to the storage backend. The only persistence path was `executeOnFinish`, which is skipped when the stream is aborted. Now messages are flushed to storage after each completed step, so they survive page refreshes and stream aborts. Fixes https://github.com/mastra-ai/mastra/issues/13984 ([#14030](https://github.com/mastra-ai/mastra/pull/14030)) - Fixed agentic loop continuing indefinitely when model hits max output tokens (finishReason: 'length'). Previously, only 'stop' and 'error' were treated as termination conditions, causing runaway token generation up to maxSteps when using structuredOutput with generate(). The loop now correctly stops on 'length' finish reason. Fixes #13012. ([#13861](https://github.com/mastra-ai/mastra/pull/13861)) - **Fixed tool-call arguments being silently lost when LLMs append internal tokens to JSON** ([#13400](https://github.com/mastra-ai/mastra/pull/13400)) LLMs (particularly via OpenRouter and OpenAI) sometimes append internal tokens like `<|call|>`, `<|endoftext|>`, or `<|end|>` to otherwise valid JSON in streamed tool-call arguments. Previously, these inputs would fail `JSON.parse` and the tool call would silently lose its arguments (set to `undefined`). Now, `sanitizeToolCallInput` strips these token patterns before parsing, recovering valid data that was previously discarded. Valid JSON containing `<|...|>` inside string values is left untouched. Truly malformed JSON still gracefully returns `undefined`. Fixes https://github.com/mastra-ai/mastra/issues/13185 and https://github.com/mastra-ai/mastra/issues/13261. - Fixed agent loop stopping prematurely when LLM returns tool calls with finishReason 'stop'. Some models (e.g., OpenAI gpt-5.3-codex) return 'stop' even when tool calls are present, causing the agent to halt instead of processing tool results and continuing. The agent now correctly continues the loop whenever tool calls exist, regardless of finishReason. ([#14043](https://github.com/mastra-ai/mastra/pull/14043)) - **Fixed** ([#14133](https://github.com/mastra-ai/mastra/pull/14133)) - Prevented provider-executed tools from triggering extra loop iterations and duplicate requests. - Preserved tool-call metadata during streaming so multi-turn conversations continue to work correctly with provider-executed tools. - Fixed observational memory activation using outdated buffered observations in some long-running threads. Activation now uses the latest thread state so the correct observations are promoted. ([#13955](https://github.com/mastra-ai/mastra/pull/13955)) - Fixed model fallback retry behavior. Non-retryable errors (401, 403) are no longer retried on the same model before falling back. Retryable errors (429, 500) are now only retried by a single layer (p-retry) instead of being duplicated across two layers, preventing (maxRetries + 1)² total calls. The per-model maxRetries setting now correctly controls how many times p-retry retries on that specific model before the fallback loop moves to the next model. ([#14039](https://github.com/mastra-ai/mastra/pull/14039)) - Added processor-driven response message ID rotation so streamed assistant IDs use the rotated ID. ([#13887](https://github.com/mastra-ai/mastra/pull/13887)) Processors that run outside the agent loop no longer need synthetic response message IDs. - Fixed a regression where dynamic `model` functions returning a single v1 model were treated as model arrays. ([#14018](https://github.com/mastra-ai/mastra/pull/14018)) - Fixed `requestContext` not being forwarded to tools dynamically added by input processors. ([#13827](https://github.com/mastra-ai/mastra/pull/13827)) - Added 'sandbox_access_request' to the HarnessEvent union type, enabling type-safe handling of sandbox access request events without requiring type casts. ([#13648](https://github.com/mastra-ai/mastra/pull/13648)) - Fix wrong threadId and resourceId being sent to subagent ([#13868](https://github.com/mastra-ai/mastra/pull/13868)) - Updated dependencies [[`fb58ce1`](https://github.com/mastra-ai/mastra/commit/fb58ce1de85d57f142005c4b3b7559f909167a3f), [`aae2295`](https://github.com/mastra-ai/mastra/commit/aae2295838a2d329ad6640829e87934790ffe5b8), [`17c4145`](https://github.com/mastra-ai/mastra/commit/17c4145166099354545582335b5252bdfdfd908b)]: - @mastra/schema-compat@1.2.0 ## 1.11.0-alpha.2 ### Patch Changes - Fixed OpenAI reasoning models (e.g. gpt-5-mini) failing with "function*call was provided without its required reasoning item" when the agent loops back after a tool call. The issue was that `callProviderMetadata.openai` carrying `fc*\*`item IDs was not being stripped alongside reasoning parts, causing the AI SDK to send`item_reference`instead of inline`function_call` content. ([#14144](https://github.com/mastra-ai/mastra/pull/14144)) ## 1.11.0-alpha.1 ### Minor Changes - Enabled tracing for tool executions through mcp server ([#12804](https://github.com/mastra-ai/mastra/pull/12804)) Traces now appear in the Observability UI for MCP server tool calls ### Patch Changes - Fixed `writer` being undefined in `processOutputStream` for all output processors. The root cause was that `processPart` in `ProcessorRunner` did not pass the `writer` to `executeWorkflowAsProcessor` in the outputStream phase. Since all user processors are wrapped into workflows via `combineProcessorsIntoWorkflow`, this meant no processor ever received a `writer`. Custom output processors (like guardrail processors) can now reliably use `writer.custom()` to emit stream events. ([#14111](https://github.com/mastra-ai/mastra/pull/14111)) - Fixed agent tool calls not being surfaced in evented workflow streams. Added StreamChunkWriter abstraction and stream format configuration ('legacy' | 'vnext') to forward agent stream chunks through the workflow output stream. ([#12692](https://github.com/mastra-ai/mastra/pull/12692)) - Experiments now fail immediately with a clear error when triggered on a dataset with zero items, instead of getting stuck in "pending" status forever. The experiment trigger API returns HTTP 400 for empty datasets. Unexpected errors during async experiment setup are now logged and mark the experiment as failed. ([#14031](https://github.com/mastra-ai/mastra/pull/14031)) - `@mastra/core`: patch ([#14103](https://github.com/mastra-ai/mastra/pull/14103)) Fixed reasoning content being lost in multi-turn conversations with thinking models (kimi-k2.5, DeepSeek-R1) when using OpenAI-compatible providers (e.g., OpenRouter). Previously, reasoning content could be discarded during streaming, causing 400 errors when the model tried to continue the conversation. Multi-turn conversations now preserve reasoning content correctly across all turns. - fix(workflows): propagate logger to executionEngine ([#12517](https://github.com/mastra-ai/mastra/pull/12517)) When a custom logger is set on a Workflow via `__registerPrimitives` or `__setLogger`, it is now correctly propagated to the internal `executionEngine`. This ensures workflow step execution errors are logged through the custom logger instead of the default ConsoleLogger, enabling proper observability integration. - Added permission denied handling for dataset pages. Datasets now show a "Permission Denied" screen when the user lacks access, matching the behavior of agents, workflows, and other resources. ([#13876](https://github.com/mastra-ai/mastra/pull/13876)) - Reasoning content from OpenAI models is now stripped from conversation history before replaying it to the LLM, preventing API errors on follow-up messages while preserving reasoning data in the database. Fixes #12980. ([#13418](https://github.com/mastra-ai/mastra/pull/13418)) - **Fixed** ([#14133](https://github.com/mastra-ai/mastra/pull/14133)) - Prevented provider-executed tools from triggering extra loop iterations and duplicate requests. - Preserved tool-call metadata during streaming so multi-turn conversations continue to work correctly with provider-executed tools. ## 1.11.0-alpha.0 ### Minor Changes - feat: support dynamic functions returning model fallback arrays ([#11975](https://github.com/mastra-ai/mastra/pull/11975)) Agents can now use dynamic functions that return entire fallback arrays based on runtime context. This enables: - Dynamic selection of complete fallback configurations - Context-based model selection with automatic fallback - Flexible model routing based on user tier, region, or other factors - Nested dynamic functions within returned arrays (each model in array can also be dynamic) ## Examples ### Basic dynamic fallback array ```typescript const agent = new Agent({ model: ({ requestContext }) => { const tier = requestContext.get('tier'); if (tier === 'premium') { return [ { model: 'openai/gpt-4', maxRetries: 2 }, { model: 'anthropic/claude-3-opus', maxRetries: 1 }, ]; } return [{ model: 'openai/gpt-3.5-turbo', maxRetries: 1 }]; }, }); ``` ### Region-based routing with nested dynamics ```typescript const agent = new Agent({ model: ({ requestContext }) => { const region = requestContext.get('region'); return [ { model: ({ requestContext }) => { // Select model variant based on region return region === 'eu' ? 'openai/gpt-4-eu' : 'openai/gpt-4'; }, maxRetries: 2, }, { model: 'anthropic/claude-3-opus', maxRetries: 1 }, ]; }, maxRetries: 1, // Agent-level default for models without explicit maxRetries }); ``` ### Async dynamic selection ```typescript const agent = new Agent({ model: async ({ requestContext }) => { // Fetch user's tier from database const userId = requestContext.get('userId'); const user = await db.users.findById(userId); if (user.tier === 'enterprise') { return [ { model: 'openai/gpt-4', maxRetries: 3 }, { model: 'anthropic/claude-3-opus', maxRetries: 2 }, ]; } return [{ model: 'openai/gpt-3.5-turbo', maxRetries: 1 }]; }, }); ``` ## Technical Details - Functions can return `MastraModelConfig` (single model) or `ModelWithRetries[]` (array) - Models without explicit `maxRetries` inherit agent-level `maxRetries` default - Each model in returned array can also be a dynamic function for nested selection - Empty arrays are validated and throw errors early - Arrays are normalized to `ModelFallbacks` with all required fields filled in - Performance optimization: Already-normalized arrays skip re-normalization ## Fixes and Improvements - Dynamic model fallbacks now properly inherit agent-level `maxRetries` when not explicitly specified - `getModelList()` now correctly handles dynamic functions that return arrays - Added validation for empty arrays returned from dynamic functions - Added type guard optimization to prevent double normalization of static arrays - Preserved backward-compatible `getLLM()` and `getModel()` return behavior while adding dynamic fallback array support - Comprehensive test coverage for edge cases (async functions, nested dynamics, error handling) ## Documentation - Added dynamic fallback array example in Models docs under **Model fallbacks** ## Migration Guide No breaking changes. All existing model configurations continue to work: - Static single models: `model: 'openai/gpt-4'` - Static arrays: `model: [{ model: 'openai/gpt-4', maxRetries: 2 }]` - Dynamic single: `model: ({ requestContext }) => 'openai/gpt-4'` - Dynamic arrays (NEW): `model: ({ requestContext }) => [{ model: 'openai/gpt-4', maxRetries: 2 }]` Closes #11951 - Added `onValidationError` hook to `ServerConfig` and `createRoute()`. When a request fails Zod schema validation (query parameters, request body, or path parameters), this hook lets you customize the error response — including the HTTP status code and response body — instead of the default 400 response. Set it on the server config to apply globally, or on individual routes to override per-route. All server adapters (Hono, Express, Fastify, Koa) support this hook. ([#13477](https://github.com/mastra-ai/mastra/pull/13477)) ```ts const mastra = new Mastra({ server: { onValidationError: (error, context) => ({ status: 422, body: { ok: false, errors: error.issues.map(i => ({ path: i.path.join('.'), message: i.message, })), source: context, }, }), }, }); ``` - Added `requestContext` field to tracing spans. Each span now automatically captures a snapshot of the active `RequestContext`, making request-scoped values like user IDs, tenant IDs, and feature flags available when viewing traces. ([#14020](https://github.com/mastra-ai/mastra/pull/14020)) - Added `allowedWorkspaceTools` to `HarnessSubagent`. Subagents now automatically inherit the parent agent's workspace. Use `allowedWorkspaceTools` to restrict which workspace tools a subagent can see: ([#13940](https://github.com/mastra-ai/mastra/pull/13940)) ```ts const subagent: HarnessSubagent = { id: 'explore', name: 'Explore', allowedWorkspaceTools: ['view', 'search_content', 'find_files'], }; ``` - Added `result` to `processOutputResult` args, providing resolved generation data (usage, text, steps, finishReason) directly. This replaces raw stream chunks with an easy-to-use `OutputResult` object containing the same data available in the `onFinish` callback. ([#13810](https://github.com/mastra-ai/mastra/pull/13810)) ```typescript const usageProcessor: Processor = { id: 'usage-processor', processOutputResult({ result, messages }) { console.log(`Text: ${result.text}`); console.log(`Tokens: ${result.usage.inputTokens} in, ${result.usage.outputTokens} out`); console.log(`Finish reason: ${result.finishReason}`); console.log(`Steps: ${result.steps.length}`); return messages; }, }; ``` - Added `requestContext` support for dataset items and experiments. ([#13938](https://github.com/mastra-ai/mastra/pull/13938)) **Dataset items** now accept an optional `requestContext` field when adding or updating items. This lets you store per-item request context alongside inputs and ground truths. **Datasets** now support a `requestContextSchema` field to describe the expected shape of request context on items. **Experiments** now accept a `requestContext` option that gets passed through to `agent.generate()` during execution. Per-item request context merges with (and takes precedence over) the experiment-level context. ```ts // Add item with request context await dataset.addItem({ input: messages, groundTruth: expectedOutput, requestContext: { userId: '123', locale: 'en' }, }); // Run experiment with global request context await runExperiment(mastra, { datasetId: 'my-dataset', targetType: 'agent', targetId: 'my-agent', requestContext: { environment: 'staging' }, }); ``` - Add Zod v4 and Standard Schema support ([#12238](https://github.com/mastra-ai/mastra/pull/12238)) ## Zod v4 Breaking Changes - Fix all `z.record()` calls to use 2-argument form (key + value schema) as required by Zod v4 - Update `ZodError.errors` to `ZodError.issues` (Zod v4 API change) - Update `@ai-sdk/provider` versions for Zod v4 compatibility ## Standard Schema Integration - Add `packages/core/src/schema/` module that re-exports from `@mastra/schema-compat` - Migrate codebase to use `PublicSchema` type for schema parameters - Use `toStandardSchema()` for normalizing schemas across Zod v3, Zod v4, AI SDK Schema, and JSON Schema - Use `standardSchemaToJSONSchema()` for JSON Schema conversion ## Schema Compatibility (@mastra/schema-compat) - Add new adapter exports: `@mastra/schema-compat/adapters/ai-sdk`, `@mastra/schema-compat/adapters/zod-v3`, `@mastra/schema-compat/adapters/json-schema` - Enhance test coverage with separate v3 and v4 test suites - Improve zod-to-json conversion with `unrepresentable: 'any'` support ## TypeScript Fixes - Resolve deep instantiation errors in client-js and model.ts - Add proper type assertions where Zod v4 inference differs **BREAKING CHANGE**: Minimum Zod version is now `^3.25.0` for v3 compatibility or `^4.0.0` for v4 ### Patch Changes - dependencies updates: ([#14085](https://github.com/mastra-ai/mastra/pull/14085)) - Updated dependency [`ajv@^8.18.0` ↗︎](https://www.npmjs.com/package/ajv/v/8.18.0) (from `^8.17.1`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`332c014`](https://github.com/mastra-ai/mastra/commit/332c014e076b81edf7fe45b58205882726415e90)) - fix(workflows): add generic bail signature with overloads. The bail() function now uses method overloads - bail(result: TStepOutput) for backward compatibility and bail(result: ...) for workflow type inference. This allows flexible early exits while maintaining type safety for workflow chaining. Runtime validation will be added in a follow-up. ([#12211](https://github.com/mastra-ai/mastra/pull/12211)) - Fixed structured output parsing when JSON string fields include fenced JSON examples. ([#13948](https://github.com/mastra-ai/mastra/pull/13948)) - Added JSON repair for malformed tool call arguments from LLM providers. When an LLM (e.g., Kimi/K2) generates broken JSON for tool call arguments, Mastra now attempts to fix common errors (missing quotes on property names, single quotes, trailing commas) before falling back to undefined. This reduces silent tool execution failures caused by minor JSON formatting issues. See https://github.com/mastra-ai/mastra/issues/11078 ([#14033](https://github.com/mastra-ai/mastra/pull/14033)) - Fixed Windows shell command execution to avoid visible cmd.exe popups and broken output piping. ([#13886](https://github.com/mastra-ai/mastra/pull/13886)) - Output processors can now inspect, modify, or block custom `data-*` chunks emitted by tools via `writer.custom()` during streaming. Processors must opt in by setting `processDataParts = true` to receive these chunks in `processOutputStream`. ([#13823](https://github.com/mastra-ai/mastra/pull/13823)) ```ts class MyDataProcessor extends Processor { processDataParts = true; processOutputStream(part, { abort }) { if (part.type === 'data-sensitive') { abort('Blocked sensitive data'); } return part; } } ``` - Fixed OpenAI strict mode schema rejection when using agent networks with structured output. The compat layer was skipped when modelId was undefined, causing optional fields to be missing from the required array. (Fixes #12284) ([#13695](https://github.com/mastra-ai/mastra/pull/13695)) - Fixed `activeTools` to also enforce at execution time, not just at the model prompt. Tool calls to tools not in the active set are now rejected with a `ToolNotFoundError`. ([#13949](https://github.com/mastra-ai/mastra/pull/13949)) - Fix build failures on Windows when running `build:patch-commonjs` during `pnpm run setup` ([#14029](https://github.com/mastra-ai/mastra/pull/14029)) - fix: respect `lastMessages: false` in `recall()` to disable conversation history ([#12951](https://github.com/mastra-ai/mastra/pull/12951)) Setting `lastMessages: false` in Memory options now correctly prevents `recall()` from returning previous messages. Previously, the agent would retain the full conversation history despite this setting being disabled. Callers can still pass `perPage: false` explicitly to `recall()` to retrieve all messages (e.g., for displaying thread history in a UI). - Fixed stream freezing when using Anthropic's Programmatic Tool Calling (PTC). Streams that contain only tool-input streaming chunks (without explicit tool-call chunks) now correctly synthesize tool-call events and complete without hanging. See [#12390](https://github.com/mastra-ai/mastra/issues/12390). ([#12400](https://github.com/mastra-ai/mastra/pull/12400)) - Fixed subagents receiving parent's tool call/result parts in their context messages. On subsequent queries in a conversation, these references to tools the subagent doesn't have caused models (especially via custom gateways) to return blank or incorrect results. Parent delegation tool artifacts are now stripped from context before forwarding to subagents. ([#13927](https://github.com/mastra-ai/mastra/pull/13927)) - Memory now automatically creates btree indexes on `thread_id` and `resource_id` metadata fields when using PgVector. This prevents sequential scans on the `memory_messages` vector table, resolving performance issues under high load. ([#14034](https://github.com/mastra-ai/mastra/pull/14034)) Fixes #12109 - Clarified the `idGenerator` documentation to reflect the current context-aware function signature and documented the available `IdGeneratorContext` fields used for type-specific ID generation. ([#14081](https://github.com/mastra-ai/mastra/pull/14081)) - Added `transient` option for data chunks to skip database persistence. Chunks marked as transient are streamed to the client for live display but not saved to storage, reducing bloat from large streaming outputs. ([#13869](https://github.com/mastra-ai/mastra/pull/13869)) ```ts await context.writer?.custom({ type: 'data-my-stream', data: { output: line }, transient: true, }); ``` Workspace tools now use this to mark stdout/stderr streaming chunks as transient. - Fixed message ID mismatch between generate/stream response and memory-stored messages. When an agent used memory, the message IDs returned in the response (e.g. `response.uiMessages[].id`) could differ from the IDs persisted to the database. This was caused by a format conversion that stripped message IDs during internal re-processing. Messages now retain their original IDs throughout the entire save pipeline. ([#13796](https://github.com/mastra-ai/mastra/pull/13796)) - Fixed assistant messages to persist `content.metadata.modelId` during streaming. ([#12969](https://github.com/mastra-ai/mastra/pull/12969)) This ensures stored and processed assistant messages keep the model identifier. Developers can now reliably read `content.metadata.modelId` from downstream storage adapters and processors. - Fixed `savePerStep: true` not actually persisting messages to storage during step execution. Previously, `onStepFinish` only accumulated messages in the in-memory MessageList but never flushed them to the storage backend. The only persistence path was `executeOnFinish`, which is skipped when the stream is aborted. Now messages are flushed to storage after each completed step, so they survive page refreshes and stream aborts. Fixes https://github.com/mastra-ai/mastra/issues/13984 ([#14030](https://github.com/mastra-ai/mastra/pull/14030)) - Fixed agentic loop continuing indefinitely when model hits max output tokens (finishReason: 'length'). Previously, only 'stop' and 'error' were treated as termination conditions, causing runaway token generation up to maxSteps when using structuredOutput with generate(). The loop now correctly stops on 'length' finish reason. Fixes #13012. ([#13861](https://github.com/mastra-ai/mastra/pull/13861)) - **Fixed tool-call arguments being silently lost when LLMs append internal tokens to JSON** ([#13400](https://github.com/mastra-ai/mastra/pull/13400)) LLMs (particularly via OpenRouter and OpenAI) sometimes append internal tokens like `<|call|>`, `<|endoftext|>`, or `<|end|>` to otherwise valid JSON in streamed tool-call arguments. Previously, these inputs would fail `JSON.parse` and the tool call would silently lose its arguments (set to `undefined`). Now, `sanitizeToolCallInput` strips these token patterns before parsing, recovering valid data that was previously discarded. Valid JSON containing `<|...|>` inside string values is left untouched. Truly malformed JSON still gracefully returns `undefined`. Fixes https://github.com/mastra-ai/mastra/issues/13185 and https://github.com/mastra-ai/mastra/issues/13261. - Fixed agent loop stopping prematurely when LLM returns tool calls with finishReason 'stop'. Some models (e.g., OpenAI gpt-5.3-codex) return 'stop' even when tool calls are present, causing the agent to halt instead of processing tool results and continuing. The agent now correctly continues the loop whenever tool calls exist, regardless of finishReason. ([#14043](https://github.com/mastra-ai/mastra/pull/14043)) - Fixed observational memory activation using outdated buffered observations in some long-running threads. Activation now uses the latest thread state so the correct observations are promoted. ([#13955](https://github.com/mastra-ai/mastra/pull/13955)) - Fixed model fallback retry behavior. Non-retryable errors (401, 403) are no longer retried on the same model before falling back. Retryable errors (429, 500) are now only retried by a single layer (p-retry) instead of being duplicated across two layers, preventing (maxRetries + 1)² total calls. The per-model maxRetries setting now correctly controls how many times p-retry retries on that specific model before the fallback loop moves to the next model. ([#14039](https://github.com/mastra-ai/mastra/pull/14039)) - Added processor-driven response message ID rotation so streamed assistant IDs use the rotated ID. ([#13887](https://github.com/mastra-ai/mastra/pull/13887)) Processors that run outside the agent loop no longer need synthetic response message IDs. - Fixed a regression where dynamic `model` functions returning a single v1 model were treated as model arrays. ([#14018](https://github.com/mastra-ai/mastra/pull/14018)) - Fixed `requestContext` not being forwarded to tools dynamically added by input processors. ([#13827](https://github.com/mastra-ai/mastra/pull/13827)) - Added 'sandbox_access_request' to the HarnessEvent union type, enabling type-safe handling of sandbox access request events without requiring type casts. ([#13648](https://github.com/mastra-ai/mastra/pull/13648)) - Fix wrong threadId and resourceId being sent to subagent ([#13868](https://github.com/mastra-ai/mastra/pull/13868)) - Updated dependencies [[`fb58ce1`](https://github.com/mastra-ai/mastra/commit/fb58ce1de85d57f142005c4b3b7559f909167a3f), [`aae2295`](https://github.com/mastra-ai/mastra/commit/aae2295838a2d329ad6640829e87934790ffe5b8), [`17c4145`](https://github.com/mastra-ai/mastra/commit/17c4145166099354545582335b5252bdfdfd908b)]: - @mastra/schema-compat@1.2.0-alpha.0 ## 1.10.0 ### Minor Changes - Added `editor` shorthand to `MastraCompositeStore` for routing all editor-related domains (agents, prompt blocks, scorer definitions, MCP clients, MCP servers, workspaces, skills) to a single storage backend. Priority: `domains` > `editor` > `default`. ([#13727](https://github.com/mastra-ai/mastra/pull/13727)) ```typescript import { MastraCompositeStore } from '@mastra/core/storage'; new MastraCompositeStore({ id: 'composite', default: postgresStore, editor: filesystemStore, }); ``` Improved code-agent editing so editor overrides can be applied and reverted without losing original dynamic values for fields like instructions, model, and tools. - Added `FilesystemStore`, a file-based storage adapter for editor domains. Stores agent configurations, prompt blocks, scorer definitions, MCP clients, MCP servers, workspaces, and skills as JSON files in a local directory (default: `.mastra-storage/`). Only published snapshots are written to disk — version history is kept in memory. Use with `MastraCompositeStore`'s `editor` shorthand to enable Git-friendly editor configurations. ([#13727](https://github.com/mastra-ai/mastra/pull/13727)) ```typescript import { FilesystemStore, MastraCompositeStore } from '@mastra/core/storage'; import { PostgresStore } from '@mastra/pg'; export const mastra = new Mastra({ storage: new MastraCompositeStore({ id: 'composite', default: new PostgresStore({ id: 'pg', connectionString: process.env.DATABASE_URL }), editor: new FilesystemStore({ dir: '.mastra-storage' }), }), }); ``` Added `applyStoredOverrides` to the editor agent namespace. When a stored configuration exists for a code-defined agent, the editor merges the stored **instructions** and **tools** on top of the code agent's values at runtime. Model, memory, workspace, and other code-defined fields are never overridden — they may contain SDK instances or dynamic functions that cannot be safely serialized. Original code-defined values are preserved via a WeakMap and restored if the stored override is deleted. - Add `inputExamples` support on tool definitions to show AI models what valid tool inputs look like. Models that support this (e.g., Anthropic's `input_examples`) will receive the examples alongside the tool schema, improving tool call accuracy. ([#12932](https://github.com/mastra-ai/mastra/pull/12932)) - Added optional `inputExamples` field to `ToolAction`, `CoreTool`, and `Tool` class ```ts const weatherTool = createTool({ id: 'get-weather', description: 'Get weather for a location', inputSchema: z.object({ city: z.string(), units: z.enum(['celsius', 'fahrenheit']), }), inputExamples: [ { input: { city: 'New York', units: 'fahrenheit' } }, { input: { city: 'Tokyo', units: 'celsius' } }, ], execute: async ({ city, units }) => { return await fetchWeather(city, units); }, }); ``` ### Patch Changes - dependencies updates: ([#13209](https://github.com/mastra-ai/mastra/pull/13209)) - Updated dependency [`p-map@^7.0.4` ↗︎](https://www.npmjs.com/package/p-map/v/7.0.4) (from `^7.0.3`, in `dependencies`) - dependencies updates: ([#13210](https://github.com/mastra-ai/mastra/pull/13210)) - Updated dependency [`p-retry@^7.1.1` ↗︎](https://www.npmjs.com/package/p-retry/v/7.1.1) (from `^7.1.0`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`33e2fd5`](https://github.com/mastra-ai/mastra/commit/33e2fd5088f83666df17401e2da68c943dbc0448)) - Fixed execute_command tool timeout parameter to accept seconds instead of milliseconds, preventing agents from accidentally setting extremely short timeouts ([#13799](https://github.com/mastra-ai/mastra/pull/13799)) - **Skill tools are now stable across conversation turns and prompt-cache friendly.** ([#13744](https://github.com/mastra-ai/mastra/pull/13744)) - Renamed `skill-activate` → `skill` — returns full skill instructions directly in the tool result - Consolidated `skill-read-reference`, `skill-read-script`, `skill-read-asset` → `skill_read` - Renamed `skill-search` → `skill_search` - `` in the system message is now sorted deterministically - Fixed Cloudflare Workers build failures when using `@mastra/core`. Local process execution now loads its runtime dependency lazily, preventing incompatible Node-only modules from being bundled during worker builds. ([#13813](https://github.com/mastra-ai/mastra/pull/13813)) - Fix `mimeType` → `mediaType` typo in `sendMessage` file part construction. This caused file attachments to be routed through the V4 adapter instead of V5, preventing them from being correctly processed by AI SDK v5 providers. ([#13833](https://github.com/mastra-ai/mastra/pull/13833)) - Fixed onIterationComplete feedback being discarded when it returns `{ continue: false }` — feedback is now added to the conversation and the model gets one final turn to produce a text response before the loop stops. ([#13759](https://github.com/mastra-ai/mastra/pull/13759)) - Fixed `generate()` and `resumeGenerate()` to always throw provider stream errors. Previously, certain provider errors were silently swallowed, returning false "successful" empty responses. Now errors are always surfaced to the caller, making retry logic reliable when providers fail transiently. ([#13802](https://github.com/mastra-ai/mastra/pull/13802)) - Remove the default maxSteps limit so stopWhen can control sub-agent execution ([#13764](https://github.com/mastra-ai/mastra/pull/13764)) - Fix suspendedToolRunId required error when it shouldn't be required ([#13722](https://github.com/mastra-ai/mastra/pull/13722)) - Fixed subagent tool defaulting maxSteps to 50 when no stop condition is configured, preventing unbounded execution loops. When stopWhen is set, maxSteps is left to the caller. ([#13777](https://github.com/mastra-ai/mastra/pull/13777)) - Fixed prompt failures by removing assistant messages that only contain sources before model calls. ([#13790](https://github.com/mastra-ai/mastra/pull/13790)) - - Fixed experiment pending count showing negative values when experiments are triggered from the Studio ([#13831](https://github.com/mastra-ai/mastra/pull/13831)) - Fixed scorer prompt metadata (analysis context, generated prompts) being lost when saving experiment scores - Fixed RequestContext constructor crashing when constructed from a deserialized plain object. ([#13856](https://github.com/mastra-ai/mastra/pull/13856)) - Fixed LLM errors (generateText, generateObject, streamText, streamObject) being swallowed by the AI SDK's default handler instead of being routed through the Mastra logger. Errors now appear with structured context (runId, modelId, provider, etc.) in your logger, and streaming errors are captured via onError callbacks. ([#13857](https://github.com/mastra-ai/mastra/pull/13857)) - Fixed workspace tool output truncation so it no longer gets prematurely cut off when short lines precede a very long line (e.g. minified JSON). Output now uses the full token budget instead of stopping at line boundaries, resulting in more complete tool results. ([#13828](https://github.com/mastra-ai/mastra/pull/13828)) - Fixed subagent tool to default maxSteps to 50 when no stopWhen condition is configured, preventing unbounded agent loops. When stopWhen is set, maxSteps remains unset so the stop condition controls termination. ([#13777](https://github.com/mastra-ai/mastra/pull/13777)) ## 1.10.0-alpha.0 ### Minor Changes - Added `editor` shorthand to `MastraCompositeStore` for routing all editor-related domains (agents, prompt blocks, scorer definitions, MCP clients, MCP servers, workspaces, skills) to a single storage backend. Priority: `domains` > `editor` > `default`. ([#13727](https://github.com/mastra-ai/mastra/pull/13727)) ```typescript import { MastraCompositeStore } from '@mastra/core/storage'; new MastraCompositeStore({ id: 'composite', default: postgresStore, editor: filesystemStore, }); ``` Improved code-agent editing so editor overrides can be applied and reverted without losing original dynamic values for fields like instructions, model, and tools. - Added `FilesystemStore`, a file-based storage adapter for editor domains. Stores agent configurations, prompt blocks, scorer definitions, MCP clients, MCP servers, workspaces, and skills as JSON files in a local directory (default: `.mastra-storage/`). Only published snapshots are written to disk — version history is kept in memory. Use with `MastraCompositeStore`'s `editor` shorthand to enable Git-friendly editor configurations. ([#13727](https://github.com/mastra-ai/mastra/pull/13727)) ```typescript import { FilesystemStore, MastraCompositeStore } from '@mastra/core/storage'; import { PostgresStore } from '@mastra/pg'; export const mastra = new Mastra({ storage: new MastraCompositeStore({ id: 'composite', default: new PostgresStore({ id: 'pg', connectionString: process.env.DATABASE_URL }), editor: new FilesystemStore({ dir: '.mastra-storage' }), }), }); ``` Added `applyStoredOverrides` to the editor agent namespace. When a stored configuration exists for a code-defined agent, the editor merges the stored **instructions** and **tools** on top of the code agent's values at runtime. Model, memory, workspace, and other code-defined fields are never overridden — they may contain SDK instances or dynamic functions that cannot be safely serialized. Original code-defined values are preserved via a WeakMap and restored if the stored override is deleted. - Add `inputExamples` support on tool definitions to show AI models what valid tool inputs look like. Models that support this (e.g., Anthropic's `input_examples`) will receive the examples alongside the tool schema, improving tool call accuracy. ([#12932](https://github.com/mastra-ai/mastra/pull/12932)) - Added optional `inputExamples` field to `ToolAction`, `CoreTool`, and `Tool` class ```ts const weatherTool = createTool({ id: 'get-weather', description: 'Get weather for a location', inputSchema: z.object({ city: z.string(), units: z.enum(['celsius', 'fahrenheit']), }), inputExamples: [ { input: { city: 'New York', units: 'fahrenheit' } }, { input: { city: 'Tokyo', units: 'celsius' } }, ], execute: async ({ city, units }) => { return await fetchWeather(city, units); }, }); ``` ### Patch Changes - dependencies updates: ([#13209](https://github.com/mastra-ai/mastra/pull/13209)) - Updated dependency [`p-map@^7.0.4` ↗︎](https://www.npmjs.com/package/p-map/v/7.0.4) (from `^7.0.3`, in `dependencies`) - dependencies updates: ([#13210](https://github.com/mastra-ai/mastra/pull/13210)) - Updated dependency [`p-retry@^7.1.1` ↗︎](https://www.npmjs.com/package/p-retry/v/7.1.1) (from `^7.1.0`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`33e2fd5`](https://github.com/mastra-ai/mastra/commit/33e2fd5088f83666df17401e2da68c943dbc0448)) - Fixed execute_command tool timeout parameter to accept seconds instead of milliseconds, preventing agents from accidentally setting extremely short timeouts ([#13799](https://github.com/mastra-ai/mastra/pull/13799)) - **Skill tools are now stable across conversation turns and prompt-cache friendly.** ([#13744](https://github.com/mastra-ai/mastra/pull/13744)) - Renamed `skill-activate` → `skill` — returns full skill instructions directly in the tool result - Consolidated `skill-read-reference`, `skill-read-script`, `skill-read-asset` → `skill_read` - Renamed `skill-search` → `skill_search` - `` in the system message is now sorted deterministically - Fixed Cloudflare Workers build failures when using `@mastra/core`. Local process execution now loads its runtime dependency lazily, preventing incompatible Node-only modules from being bundled during worker builds. ([#13813](https://github.com/mastra-ai/mastra/pull/13813)) - Fix `mimeType` → `mediaType` typo in `sendMessage` file part construction. This caused file attachments to be routed through the V4 adapter instead of V5, preventing them from being correctly processed by AI SDK v5 providers. ([#13833](https://github.com/mastra-ai/mastra/pull/13833)) - Fixed onIterationComplete feedback being discarded when it returns `{ continue: false }` — feedback is now added to the conversation and the model gets one final turn to produce a text response before the loop stops. ([#13759](https://github.com/mastra-ai/mastra/pull/13759)) - Fixed `generate()` and `resumeGenerate()` to always throw provider stream errors. Previously, certain provider errors were silently swallowed, returning false "successful" empty responses. Now errors are always surfaced to the caller, making retry logic reliable when providers fail transiently. ([#13802](https://github.com/mastra-ai/mastra/pull/13802)) - Remove the default maxSteps limit so stopWhen can control sub-agent execution ([#13764](https://github.com/mastra-ai/mastra/pull/13764)) - Fix suspendedToolRunId required error when it shouldn't be required ([#13722](https://github.com/mastra-ai/mastra/pull/13722)) - Fixed subagent tool defaulting maxSteps to 50 when no stop condition is configured, preventing unbounded execution loops. When stopWhen is set, maxSteps is left to the caller. ([#13777](https://github.com/mastra-ai/mastra/pull/13777)) - Fixed prompt failures by removing assistant messages that only contain sources before model calls. ([#13790](https://github.com/mastra-ai/mastra/pull/13790)) - - Fixed experiment pending count showing negative values when experiments are triggered from the Studio ([#13831](https://github.com/mastra-ai/mastra/pull/13831)) - Fixed scorer prompt metadata (analysis context, generated prompts) being lost when saving experiment scores - Fixed RequestContext constructor crashing when constructed from a deserialized plain object. ([#13856](https://github.com/mastra-ai/mastra/pull/13856)) - Fixed LLM errors (generateText, generateObject, streamText, streamObject) being swallowed by the AI SDK's default handler instead of being routed through the Mastra logger. Errors now appear with structured context (runId, modelId, provider, etc.) in your logger, and streaming errors are captured via onError callbacks. ([#13857](https://github.com/mastra-ai/mastra/pull/13857)) - Fixed workspace tool output truncation so it no longer gets prematurely cut off when short lines precede a very long line (e.g. minified JSON). Output now uses the full token budget instead of stopping at line boundaries, resulting in more complete tool results. ([#13828](https://github.com/mastra-ai/mastra/pull/13828)) - Fixed subagent tool to default maxSteps to 50 when no stopWhen condition is configured, preventing unbounded agent loops. When stopWhen is set, maxSteps remains unset so the stop condition controls termination. ([#13777](https://github.com/mastra-ai/mastra/pull/13777)) ## 1.9.0 ### Minor Changes - Added `onStepFinish` and `onError` callbacks to `NetworkOptions`, allowing per-LLM-step progress monitoring and custom error handling during network execution. Closes #13362. ([#13370](https://github.com/mastra-ai/mastra/pull/13370)) **Before:** No way to observe per-step progress or handle errors during network execution. ```typescript const stream = await agent.network('Research AI trends', { memory: { thread: 'my-thread', resource: 'my-resource' }, }); ``` **After:** `onStepFinish` and `onError` are now available in `NetworkOptions`. ```typescript const stream = await agent.network('Research AI trends', { onStepFinish: event => { console.log('Step completed:', event.finishReason, event.usage); }, onError: ({ error }) => { console.error('Network error:', error); }, memory: { thread: 'my-thread', resource: 'my-resource' }, }); ``` - Add workflow execution path tracking and optimize execution logs ([#11755](https://github.com/mastra-ai/mastra/pull/11755)) Workflow results now include a `stepExecutionPath` array showing the IDs of each step that executed during a workflow run. You can use this to understand exactly which path your workflow took. ```ts // Before: no execution path in results const result = await workflow.execute({ triggerData }); // result.stepExecutionPath → undefined // After: stepExecutionPath is available in workflow results const result = await workflow.execute({ triggerData }); console.log(result.stepExecutionPath); // → ['step1', 'step2', 'step4'] — the actual steps that ran ``` `stepExecutionPath` is available in: - **Workflow results** (`WorkflowResult.stepExecutionPath`) — see which steps ran after execution completes - **Execution context** (`ExecutionContext.stepExecutionPath`) — access the path mid-execution inside your steps - **Resume and restart operations** — execution path persists across suspend/resume and restart cycles Workflow execution logs are now more compact and easier to read. Step outputs are no longer duplicated as the next step's input, reducing the size of execution results while maintaining full visibility. **Key improvements:** - Track which steps executed in your workflows with `stepExecutionPath` - Smaller, more readable execution logs with automatic duplicate payload removal - Execution path preserved when resuming or restarting workflows This is particularly beneficial for AI agents and LLM-based workflows where reducing context size improves performance and cost efficiency. Related: `#8951` - Added authentication interfaces and Enterprise Edition RBAC support. ([#13163](https://github.com/mastra-ai/mastra/pull/13163)) **New `@mastra/core/auth` export** with pluggable interfaces for building auth providers: - `IUserProvider` — user lookup and management - `ISessionProvider` — session creation, validation, and cookie handling - `ISSOProvider` — SSO login and callback flows - `ICredentialsProvider` — username/password authentication **Default implementations** included out of the box: - Cookie-based session provider with configurable TTL and secure defaults - In-memory session provider for development and testing **Enterprise Edition (`@mastra/core/auth/ee`)** adds RBAC, ACL, and license validation: ```ts import { buildCapabilities } from '@mastra/core/auth/ee'; const capabilities = buildCapabilities({ rbac: myRBACProvider, acl: myACLProvider, }); ``` Built-in role definitions (owner, admin, editor, viewer) and a static RBAC provider are included for quick setup. Enterprise features require a valid license key via the `MASTRA_EE_LICENSE` environment variable. - Workspace sandbox tool results (`execute_command`, `kill_process`, `get_process_output`) sent to the model now strip ANSI color codes via `toModelOutput`, while streamed output to the user keeps colors. This reduces token usage and improves model readability. ([#13440](https://github.com/mastra-ai/mastra/pull/13440)) Workspace `execute_command` tool now extracts trailing `| tail -N` pipes from commands so output streams live to the user, while the final result sent to the model is still truncated to the last N lines. Workspace tools that return potentially large output now enforce a token-based output limit (~3k tokens by default) using tiktoken for accurate counting. The limit is configurable per-tool via `maxOutputTokens` in `WorkspaceToolConfig`. Each tool uses a truncation strategy suited to its output: - `read_file`, `grep`, `list_files` — truncate from the end (keep imports, first matches, top-level tree) - `execute_command`, `get_process_output`, `kill_process` — head+tail sandwich (keep early output + final status) ```ts const workspace = new Workspace({ tools: { mastra_workspace_execute_command: { maxOutputTokens: 5000, // override default 3k }, }, }); ``` - Workspace tools (list_files, grep) now automatically respect .gitignore, filtering out directories like node_modules and dist from results. Explicitly targeting an ignored path still works. Also lowered the default tree depth from 3 to 2 to reduce token usage. ([#13724](https://github.com/mastra-ai/mastra/pull/13724)) - Added `maxSteps` and `stopWhen` support to `HarnessSubagent`. ([#13653](https://github.com/mastra-ai/mastra/pull/13653)) You can now define `maxSteps` and `stopWhen` on a harness subagent so spawned subagents can use custom loop limits instead of relying only on the default `maxSteps: 50` fallback. ```ts const harness = new Harness({ id: 'dev-harness', modes: [{ id: 'build', default: true, agent: buildAgent }], subagents: [ { id: 'explore', name: 'Explore', description: 'Inspect the codebase', instructions: 'Investigate and summarize findings.', defaultModelId: 'openai/gpt-4o', maxSteps: 7, stopWhen: ({ steps }) => steps.length >= 3, }, ], }); ``` - Added OpenAI WebSocket transport for streaming responses with auto-close and manual transport access ([#13531](https://github.com/mastra-ai/mastra/pull/13531)) - Added `name` property to `WorkspaceToolConfig` for remapping workspace tool names. Tools can now be exposed under custom names to the LLM while keeping the original constant as the config key. ([#13687](https://github.com/mastra-ai/mastra/pull/13687)) ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './project' }), tools: { mastra_workspace_read_file: { name: 'view' }, mastra_workspace_grep: { name: 'search_content' }, mastra_workspace_edit_file: { name: 'string_replace_lsp' }, }, }); ``` Also removed hardcoded tool-name cross-references from edit-file and ast-edit tool descriptions, since tools can be renamed or disabled. - Adds requestContext passthrough to Harness runtime APIs. ([#13650](https://github.com/mastra-ai/mastra/pull/13650)) **Added** You can now pass `requestContext` to Harness runtime methods so tools and subagents receive request-scoped values. - Added `binaryOverrides`, `searchPaths`, and `packageRunner` options to `LSPConfig` to support flexible language server binary resolution. ([#13677](https://github.com/mastra-ai/mastra/pull/13677)) Previously, workspace LSP diagnostics only worked when language server binaries were installed in the project's `node_modules/.bin/`. There was no way to use globally installed binaries or point to a custom install. **New `LSPConfig` fields:** - `binaryOverrides`: Override the binary command for a specific server, bypassing the default lookup. Useful when the binary is installed in a non-standard location. - `searchPaths`: Additional directories to search when resolving Node.js modules (e.g. `typescript/lib/tsserver.js`). Each entry should be a directory whose `node_modules` contains the required packages. - `packageRunner`: Package runner to use as a last-resort fallback when no binary is found (e.g. `'npx --yes'`, `'pnpm dlx'`, `'bunx'`). Off by default — package runners can hang in monorepos with workspace links. Binary resolution order per server: explicit `binaryOverrides` override → project `node_modules/.bin/` → `process.cwd()` `node_modules/.bin/` → `searchPaths` `node_modules/.bin/` → global PATH → `packageRunner` fallback. ```ts const workspace = new Workspace({ lsp: { // Point to a globally installed binary binaryOverrides: { typescript: '/usr/local/bin/typescript-language-server --stdio', }, // Resolve typescript/lib/tsserver.js from a tool's own node_modules searchPaths: ['/path/to/my-tool'], // Use a package runner as last resort (off by default) packageRunner: 'npx --yes', }, }); ``` Also exported `buildServerDefs(config?)` for building config-aware server definitions, and `LSPConfig` / `LSPServerDef` types from `@mastra/core/workspace`. - Added a unified observability type system with interfaces for structured logging, metrics (counters, gauges, histograms), scores, and feedback alongside the existing tracing infrastructure. ([#13058](https://github.com/mastra-ai/mastra/pull/13058)) **Why?** Previously, only tracing flowed through execution contexts. Logging was ad-hoc and metrics did not exist. This change establishes the type system and context plumbing so that when concrete implementations land, logging and metrics will flow through execute callbacks automatically — no migration needed. **What changed:** - New `ObservabilityContext` interface combining tracing, logging, and metrics contexts - New type definitions for `LoggerContext`, `MetricsContext`, `ScoreInput`, `FeedbackInput`, and `ObservabilityEventBus` - `createObservabilityContext()` factory and `resolveObservabilityContext()` resolver with no-op defaults for graceful degradation - Future logging and metrics signals will propagate automatically through execution contexts — no migration needed - Added `loggerVNext` and `metrics` getters to the `Mastra` class - Added `setServer()` public method to the Mastra class, enabling post-construction configuration of server settings. This allows platform tooling to inject server defaults (e.g. auth) into user-created Mastra instances at deploy time. ([#13729](https://github.com/mastra-ai/mastra/pull/13729)) ```typescript const mastra = new Mastra({ agents: { myAgent } }); // Platform tooling can inject server config after construction mastra.setServer({ ...mastra.getServer(), auth: new MastraAuthWorkos() }); ``` - **Added** local symlink mounts in `LocalSandbox` so sandboxed commands can access locally-mounted filesystem paths. ([#13474](https://github.com/mastra-ai/mastra/pull/13474)) **Improved** mounted paths so commands resolve consistently in local sandboxes. **Improved** workspace instructions so developers can quickly find mounted data paths. **Why:** Local sandboxes can now run commands against locally-mounted data without manual path workarounds. **Usage example:** ```typescript const workspace = new Workspace({ mounts: { '/data': new LocalFilesystem({ basePath: '/path/to/data' }), }, sandbox: new LocalSandbox({ workingDirectory: './workspace' }), }); await workspace.init(); // Sandboxed commands can access the mount path via symlink await workspace.sandbox.executeCommand('ls data'); ``` - Abort signal and background process callbacks ([#13597](https://github.com/mastra-ai/mastra/pull/13597)) - Sandbox commands and spawned processes can now be cancelled via `abortSignal` in command options - Background processes spawned via `execute_command` now support `onStdout`, `onStderr`, and `onExit` callbacks for streaming output and exit notifications - New `backgroundProcesses` config in workspace tool options for wiring up background process callbacks ### Patch Changes - Added `supportsConcurrentUpdates()` method to `WorkflowsStorage` base class and abstract `updateWorkflowResults`/`updateWorkflowState` methods for atomic workflow state updates. The evented workflow engine now checks `supportsConcurrentUpdates()` and throws a clear error if the storage backend does not support concurrent updates. ([#12575](https://github.com/mastra-ai/mastra/pull/12575)) - Update provider registry and model documentation with latest models and providers ([`edee4b3`](https://github.com/mastra-ai/mastra/commit/edee4b37dff0af515fc7cc0e8d71ee39e6a762f0)) - Fixed sandbox command execution crashing the parent process on some Node.js versions by explicitly setting stdio to pipe for detached child processes. ([#13697](https://github.com/mastra-ai/mastra/pull/13697)) - Fixed an issue where generating a response in an empty thread (system-only messages) would throw an error. Providers that support system-only prompts like Anthropic and OpenAI now work as expected. A warning is logged for providers that require at least one user message (e.g. Gemini). Fixes #13045. ([#13164](https://github.com/mastra-ai/mastra/pull/13164)) - Sanitize invalid tool names in agent history so Bedrock retries continue instead of failing request validation. ([#13633](https://github.com/mastra-ai/mastra/pull/13633)) - Fixed path matching for auto-indexing and skills discovery. ([#13511](https://github.com/mastra-ai/mastra/pull/13511)) Single file paths, directory globs, and `SKILL.md` file globs now resolve consistently. Trailing slashes are now handled correctly. - `Harness.cloneThread()` now resolves dynamic memory factories before cloning, fixing "cloneThread is not a function" errors when memory is provided as a factory function. `HarnessConfig.memory` type widened to `DynamicArgument`. ([#13569](https://github.com/mastra-ai/mastra/pull/13569)) - Fixed workspace tools being callable by their old default names (e.g. mastra_workspace_edit_file) when renamed via tools config. The tool's internal id is now updated to match the remapped name, preventing fallback resolution from bypassing the rename. ([#13694](https://github.com/mastra-ai/mastra/pull/13694)) - Reduced default max output tokens from 3000 to 2000 for all workspace tools. List files tool uses a 1000 token limit. Suppressed "No errors or warnings" LSP diagnostic message when there are no issues. ([#13730](https://github.com/mastra-ai/mastra/pull/13730)) - Add first-class custom provider support for MastraCode model selection and routing. ([#13682](https://github.com/mastra-ai/mastra/pull/13682)) - Add `/custom-providers` command to create, edit, and delete custom OpenAI-compatible providers and manage model IDs under each provider. - Persist custom providers and model IDs in `settings.json` with schema parsing/validation updates. - Extend Harness model catalog listing with `customModelCatalogProvider` so custom models appear in existing selectors (`/models`, `/subagents`). - Route configured custom provider model IDs through `ModelRouterLanguageModel` using provider-specific URL and optional API key settings. - **`sendMessage` now accepts `files` instead of `images`**, supporting any file type with optional `filename`. ([#13574](https://github.com/mastra-ai/mastra/pull/13574)) **Breaking change:** Rename `images` to `files` when calling `harness.sendMessage()`: ```ts // Before await harness.sendMessage({ content: 'Analyze this', images: [{ data: base64Data, mimeType: 'image/png' }], }); // After await harness.sendMessage({ content: 'Analyze this', files: [{ data: base64Data, mediaType: 'image/png', filename: 'screenshot.png' }], }); ``` - `files` accepts `{ data, mediaType, filename? }` — filenames are now preserved through storage and message history - Text-based files (`text/*`, `application/json`) are automatically decoded to readable text content instead of being sent as binary, which models could not process - `HarnessMessageContent` now includes a `file` type, so file parts round-trip correctly through message history - Fixed Agent Network routing failures for users running Claude models through AWS Bedrock by removing trailing whitespace from the routing assistant message. ([#13624](https://github.com/mastra-ai/mastra/pull/13624)) - Fixed thread title generation when user messages include file parts (for example, images). ([#13671](https://github.com/mastra-ai/mastra/pull/13671)) Titles now generate reliably instead of becoming empty. - Fixed parallel workflow tool calls so each call runs independently. ([#13478](https://github.com/mastra-ai/mastra/pull/13478)) When an agent starts multiple tool calls to the same workflow at the same time, each call now runs with its own workflow run context. This prevents duplicated results across parallel calls and ensures each call returns output for its own input. Also ensures workflow tool suspension and manual resumption correctly preserves the run context. - Fixed sub-agent instructions being overridden when the parent agent uses an OpenAI model. Previously, OpenAI models would fill in the optional `instructions` parameter when calling a sub-agent tool, completely replacing the sub-agent's own instructions. Now, any LLM-provided instructions are appended to the sub-agent's configured instructions instead of replacing them. ([#13578](https://github.com/mastra-ai/mastra/pull/13578)) - Tool lifecycle hooks (`onInputStart`, `onInputDelta`, `onInputAvailable`, `onOutput`) now fire correctly during agent execution for tools created via `createTool()`. Previously these hooks were silently ignored. Affected: `createTool`, `Tool`, `CoreToolBuilder.build`, `CoreTool`. ([#13708](https://github.com/mastra-ai/mastra/pull/13708)) - Fixed an issue where sub-agent messages inside a workflow tool would corrupt the parent agent's memory context. When an agent calls a workflow as a tool and the workflow runs sub-agents with their own memory threads, the parent's thread identity on the shared request context is now correctly saved before the workflow executes and restored afterward, preventing messages from being written to the wrong thread. ([#13637](https://github.com/mastra-ai/mastra/pull/13637)) - Fix workspace tool output truncation to handle tokenizer special tokens ([#13725](https://github.com/mastra-ai/mastra/pull/13725)) - Added a warning when a `LocalFilesystem` mount uses `contained: false`, alerting users to path resolution issues in mount-based workspaces. Use `contained: true` (default) or `allowedPaths` to allow specific host paths. ([#13474](https://github.com/mastra-ai/mastra/pull/13474)) - Fixed harness handling for observational memory failures so streams stop immediately when OM reports a failed run or buffering cycle. ([#13563](https://github.com/mastra-ai/mastra/pull/13563)) The harness now emits the existing OM failure event (`om_observation_failed`, `om_reflection_failed`, or `om_buffering_failed`), emits a top-level error with OM context, and aborts the active stream. This prevents normal assistant output from continuing after an OM model failure. - Fixed subagents being unable to access files outside the project root. Subagents now inherit both user-approved sandbox paths and skill paths (e.g. `~/.claude/skills`) from the parent agent. ([#13700](https://github.com/mastra-ai/mastra/pull/13700)) - Fixed agent-as-tools schema generation so Gemini accepts tool definitions for suspend/resume flows. ([#13715](https://github.com/mastra-ai/mastra/pull/13715)) This prevents schema validation failures when `resumeData` is present. - Fixed tool approval resume failing when Agent is used without an explicit Mastra instance. The Harness now creates an internal Mastra instance with storage and registers it on mode agents, ensuring workflow snapshots persist and load correctly. Also fixed requestContext serialization using toJSON() to prevent circular reference errors during snapshot persistence. ([#13519](https://github.com/mastra-ai/mastra/pull/13519)) - Fixed spawn error handling in LocalSandbox by switching to execa. Previously, spawning a process with an invalid working directory or missing command could crash with an unhandled Node.js exception. Now returns descriptive error messages instead. Also fixed timeout handling to properly kill the entire process group for compound commands. ([#13734](https://github.com/mastra-ai/mastra/pull/13734)) - HTTP request logging can now be configured in detail via `apiReqLogs` in the server config. The new `HttpLoggingConfig` type is exported from `@mastra/core/server`. ([#11907](https://github.com/mastra-ai/mastra/pull/11907)) ```ts import type { HttpLoggingConfig } from '@mastra/core/server'; const loggingConfig: HttpLoggingConfig = { enabled: true, level: 'info', excludePaths: ['/health', '/metrics'], includeHeaders: true, includeQueryParams: true, redactHeaders: ['authorization', 'cookie'], }; ``` - Remove internal `processes` field from sandbox provider options ([#13597](https://github.com/mastra-ai/mastra/pull/13597)) The `processes` field is no longer exposed in constructor options for E2B, Daytona, and Blaxel sandbox providers. This field is managed internally and was not intended to be user-configurable. - Fixed abort signal propagation in agent networks. When using `abortSignal` with `agent.network()`, the signal now correctly prevents tool execution when abort fires during routing, and no longer saves partial results to memory when sub-agents, tools, or workflows are aborted. ([#13491](https://github.com/mastra-ai/mastra/pull/13491)) - Fixed Memory.recall() to include pagination metadata (total, page, perPage, hasMore) in its response, ensuring consistent pagination regardless of whether agentId is provided. Fixes #13277 ([#13278](https://github.com/mastra-ai/mastra/pull/13278)) - Fixed harness getTokenUsage() returning zeros when using AI SDK v5/v6. The token usage extraction now correctly reads both inputTokens/outputTokens (v5/v6) and promptTokens/completionTokens (v4) field names from the usage object. ([#13622](https://github.com/mastra-ai/mastra/pull/13622)) - Model pack selection is now more consistent and reliable in mastracode. ([#13512](https://github.com/mastra-ai/mastra/pull/13512)) - `/models` is now the single command for choosing and managing model packs. - Model picker ranking now learns from your recent selections and keeps those preferences across sessions. - Pack choice now restores correctly per thread when switching between threads. - Custom packs now support full create, rename, targeted edit, and delete workflows. - The built-in **Varied** option has been retired; users who had it selected are automatically migrated to a saved custom pack named `varied`. - Added support for reading resource IDs from `Harness`. ([#13690](https://github.com/mastra-ai/mastra/pull/13690)) You can now get the default resource ID and list known resource IDs from stored threads. ```ts const defaultId = harness.getDefaultResourceId(); const knownIds = await harness.getKnownResourceIds(); ``` - chore(harness): Update harness sub-agent instructions type to be dynamic ([#13706](https://github.com/mastra-ai/mastra/pull/13706)) - Added `MastraMessagePart` to the public type exports of `@mastra/core/agent`, allowing it to be imported directly in downstream packages. ([#13297](https://github.com/mastra-ai/mastra/pull/13297)) - Added `deleteThread({ threadId })` method to the Harness class for deleting threads and their messages from storage. Releases the thread lock and clears the active thread when deleting the current thread. Emits a `thread_deleted` event. ([#13625](https://github.com/mastra-ai/mastra/pull/13625)) - Fixed tilde (~) paths not expanding to the home directory in LocalFilesystem and LocalSandbox. Paths like `~/my-project` were silently treated as relative paths, creating a literal `~/` directory instead of resolving to `$HOME`. This affects `basePath`, `allowedPaths`, `setAllowedPaths()`, all file operations in LocalFilesystem, and `workingDirectory` in LocalSandbox. ([#13739](https://github.com/mastra-ai/mastra/pull/13739)) - Switched Mastra Code to workspace tools and enabled LSP by default ([#13437](https://github.com/mastra-ai/mastra/pull/13437)) - Switched from built-in tool implementations to workspace tools for file operations, search, edit, write, and command execution - Enabled LSP (language server) by default with automatic package runner detection and bundled binary resolution - Added real-time stdout/stderr streaming in the TUI for workspace command execution - Added TUI rendering for process management tools (view output, kill processes) - Fixed edit diff preview in the TUI to work with workspace tool arg names (`old_string`/`new_string`) - Fixed tilde paths (`~/foo`) in contained `LocalFilesystem` silently writing to the wrong location. Previously, `~/foo` would expand and then nest under basePath (e.g. `basePath/home/user/foo`). Tilde paths are now treated as real absolute paths, and throw `PermissionError` when the expanded path is outside `basePath` and `allowedPaths`. ([#13741](https://github.com/mastra-ai/mastra/pull/13741)) ## 1.9.0-alpha.0 ### Minor Changes - Added `onStepFinish` and `onError` callbacks to `NetworkOptions`, allowing per-LLM-step progress monitoring and custom error handling during network execution. Closes #13362. ([#13370](https://github.com/mastra-ai/mastra/pull/13370)) **Before:** No way to observe per-step progress or handle errors during network execution. ```typescript const stream = await agent.network('Research AI trends', { memory: { thread: 'my-thread', resource: 'my-resource' }, }); ``` **After:** `onStepFinish` and `onError` are now available in `NetworkOptions`. ```typescript const stream = await agent.network('Research AI trends', { onStepFinish: event => { console.log('Step completed:', event.finishReason, event.usage); }, onError: ({ error }) => { console.error('Network error:', error); }, memory: { thread: 'my-thread', resource: 'my-resource' }, }); ``` - Add workflow execution path tracking and optimize execution logs ([#11755](https://github.com/mastra-ai/mastra/pull/11755)) Workflow results now include a `stepExecutionPath` array showing the IDs of each step that executed during a workflow run. You can use this to understand exactly which path your workflow took. ```ts // Before: no execution path in results const result = await workflow.execute({ triggerData }); // result.stepExecutionPath → undefined // After: stepExecutionPath is available in workflow results const result = await workflow.execute({ triggerData }); console.log(result.stepExecutionPath); // → ['step1', 'step2', 'step4'] — the actual steps that ran ``` `stepExecutionPath` is available in: - **Workflow results** (`WorkflowResult.stepExecutionPath`) — see which steps ran after execution completes - **Execution context** (`ExecutionContext.stepExecutionPath`) — access the path mid-execution inside your steps - **Resume and restart operations** — execution path persists across suspend/resume and restart cycles Workflow execution logs are now more compact and easier to read. Step outputs are no longer duplicated as the next step's input, reducing the size of execution results while maintaining full visibility. **Key improvements:** - Track which steps executed in your workflows with `stepExecutionPath` - Smaller, more readable execution logs with automatic duplicate payload removal - Execution path preserved when resuming or restarting workflows This is particularly beneficial for AI agents and LLM-based workflows where reducing context size improves performance and cost efficiency. Related: `#8951` - Added authentication interfaces and Enterprise Edition RBAC support. ([#13163](https://github.com/mastra-ai/mastra/pull/13163)) **New `@mastra/core/auth` export** with pluggable interfaces for building auth providers: - `IUserProvider` — user lookup and management - `ISessionProvider` — session creation, validation, and cookie handling - `ISSOProvider` — SSO login and callback flows - `ICredentialsProvider` — username/password authentication **Default implementations** included out of the box: - Cookie-based session provider with configurable TTL and secure defaults - In-memory session provider for development and testing **Enterprise Edition (`@mastra/core/auth/ee`)** adds RBAC, ACL, and license validation: ```ts import { buildCapabilities } from '@mastra/core/auth/ee'; const capabilities = buildCapabilities({ rbac: myRBACProvider, acl: myACLProvider, }); ``` Built-in role definitions (owner, admin, editor, viewer) and a static RBAC provider are included for quick setup. Enterprise features require a valid license key via the `MASTRA_EE_LICENSE` environment variable. - Workspace sandbox tool results (`execute_command`, `kill_process`, `get_process_output`) sent to the model now strip ANSI color codes via `toModelOutput`, while streamed output to the user keeps colors. This reduces token usage and improves model readability. ([#13440](https://github.com/mastra-ai/mastra/pull/13440)) Workspace `execute_command` tool now extracts trailing `| tail -N` pipes from commands so output streams live to the user, while the final result sent to the model is still truncated to the last N lines. Workspace tools that return potentially large output now enforce a token-based output limit (~3k tokens by default) using tiktoken for accurate counting. The limit is configurable per-tool via `maxOutputTokens` in `WorkspaceToolConfig`. Each tool uses a truncation strategy suited to its output: - `read_file`, `grep`, `list_files` — truncate from the end (keep imports, first matches, top-level tree) - `execute_command`, `get_process_output`, `kill_process` — head+tail sandwich (keep early output + final status) ```ts const workspace = new Workspace({ tools: { mastra_workspace_execute_command: { maxOutputTokens: 5000, // override default 3k }, }, }); ``` - Workspace tools (list_files, grep) now automatically respect .gitignore, filtering out directories like node_modules and dist from results. Explicitly targeting an ignored path still works. Also lowered the default tree depth from 3 to 2 to reduce token usage. ([#13724](https://github.com/mastra-ai/mastra/pull/13724)) - Added `maxSteps` and `stopWhen` support to `HarnessSubagent`. ([#13653](https://github.com/mastra-ai/mastra/pull/13653)) You can now define `maxSteps` and `stopWhen` on a harness subagent so spawned subagents can use custom loop limits instead of relying only on the default `maxSteps: 50` fallback. ```ts const harness = new Harness({ id: 'dev-harness', modes: [{ id: 'build', default: true, agent: buildAgent }], subagents: [ { id: 'explore', name: 'Explore', description: 'Inspect the codebase', instructions: 'Investigate and summarize findings.', defaultModelId: 'openai/gpt-4o', maxSteps: 7, stopWhen: ({ steps }) => steps.length >= 3, }, ], }); ``` - Added OpenAI WebSocket transport for streaming responses with auto-close and manual transport access ([#13531](https://github.com/mastra-ai/mastra/pull/13531)) - Added `name` property to `WorkspaceToolConfig` for remapping workspace tool names. Tools can now be exposed under custom names to the LLM while keeping the original constant as the config key. ([#13687](https://github.com/mastra-ai/mastra/pull/13687)) ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './project' }), tools: { mastra_workspace_read_file: { name: 'view' }, mastra_workspace_grep: { name: 'search_content' }, mastra_workspace_edit_file: { name: 'string_replace_lsp' }, }, }); ``` Also removed hardcoded tool-name cross-references from edit-file and ast-edit tool descriptions, since tools can be renamed or disabled. - Adds requestContext passthrough to Harness runtime APIs. ([#13650](https://github.com/mastra-ai/mastra/pull/13650)) **Added** You can now pass `requestContext` to Harness runtime methods so tools and subagents receive request-scoped values. - Added `binaryOverrides`, `searchPaths`, and `packageRunner` options to `LSPConfig` to support flexible language server binary resolution. ([#13677](https://github.com/mastra-ai/mastra/pull/13677)) Previously, workspace LSP diagnostics only worked when language server binaries were installed in the project's `node_modules/.bin/`. There was no way to use globally installed binaries or point to a custom install. **New `LSPConfig` fields:** - `binaryOverrides`: Override the binary command for a specific server, bypassing the default lookup. Useful when the binary is installed in a non-standard location. - `searchPaths`: Additional directories to search when resolving Node.js modules (e.g. `typescript/lib/tsserver.js`). Each entry should be a directory whose `node_modules` contains the required packages. - `packageRunner`: Package runner to use as a last-resort fallback when no binary is found (e.g. `'npx --yes'`, `'pnpm dlx'`, `'bunx'`). Off by default — package runners can hang in monorepos with workspace links. Binary resolution order per server: explicit `binaryOverrides` override → project `node_modules/.bin/` → `process.cwd()` `node_modules/.bin/` → `searchPaths` `node_modules/.bin/` → global PATH → `packageRunner` fallback. ```ts const workspace = new Workspace({ lsp: { // Point to a globally installed binary binaryOverrides: { typescript: '/usr/local/bin/typescript-language-server --stdio', }, // Resolve typescript/lib/tsserver.js from a tool's own node_modules searchPaths: ['/path/to/my-tool'], // Use a package runner as last resort (off by default) packageRunner: 'npx --yes', }, }); ``` Also exported `buildServerDefs(config?)` for building config-aware server definitions, and `LSPConfig` / `LSPServerDef` types from `@mastra/core/workspace`. - Added a unified observability type system with interfaces for structured logging, metrics (counters, gauges, histograms), scores, and feedback alongside the existing tracing infrastructure. ([#13058](https://github.com/mastra-ai/mastra/pull/13058)) **Why?** Previously, only tracing flowed through execution contexts. Logging was ad-hoc and metrics did not exist. This change establishes the type system and context plumbing so that when concrete implementations land, logging and metrics will flow through execute callbacks automatically — no migration needed. **What changed:** - New `ObservabilityContext` interface combining tracing, logging, and metrics contexts - New type definitions for `LoggerContext`, `MetricsContext`, `ScoreInput`, `FeedbackInput`, and `ObservabilityEventBus` - `createObservabilityContext()` factory and `resolveObservabilityContext()` resolver with no-op defaults for graceful degradation - Future logging and metrics signals will propagate automatically through execution contexts — no migration needed - Added `loggerVNext` and `metrics` getters to the `Mastra` class - Added `setServer()` public method to the Mastra class, enabling post-construction configuration of server settings. This allows platform tooling to inject server defaults (e.g. auth) into user-created Mastra instances at deploy time. ([#13729](https://github.com/mastra-ai/mastra/pull/13729)) ```typescript const mastra = new Mastra({ agents: { myAgent } }); // Platform tooling can inject server config after construction mastra.setServer({ ...mastra.getServer(), auth: new MastraAuthWorkos() }); ``` - **Added** local symlink mounts in `LocalSandbox` so sandboxed commands can access locally-mounted filesystem paths. ([#13474](https://github.com/mastra-ai/mastra/pull/13474)) **Improved** mounted paths so commands resolve consistently in local sandboxes. **Improved** workspace instructions so developers can quickly find mounted data paths. **Why:** Local sandboxes can now run commands against locally-mounted data without manual path workarounds. **Usage example:** ```typescript const workspace = new Workspace({ mounts: { '/data': new LocalFilesystem({ basePath: '/path/to/data' }), }, sandbox: new LocalSandbox({ workingDirectory: './workspace' }), }); await workspace.init(); // Sandboxed commands can access the mount path via symlink await workspace.sandbox.executeCommand('ls data'); ``` - Abort signal and background process callbacks ([#13597](https://github.com/mastra-ai/mastra/pull/13597)) - Sandbox commands and spawned processes can now be cancelled via `abortSignal` in command options - Background processes spawned via `execute_command` now support `onStdout`, `onStderr`, and `onExit` callbacks for streaming output and exit notifications - New `backgroundProcesses` config in workspace tool options for wiring up background process callbacks ### Patch Changes - Added `supportsConcurrentUpdates()` method to `WorkflowsStorage` base class and abstract `updateWorkflowResults`/`updateWorkflowState` methods for atomic workflow state updates. The evented workflow engine now checks `supportsConcurrentUpdates()` and throws a clear error if the storage backend does not support concurrent updates. ([#12575](https://github.com/mastra-ai/mastra/pull/12575)) - Update provider registry and model documentation with latest models and providers ([`edee4b3`](https://github.com/mastra-ai/mastra/commit/edee4b37dff0af515fc7cc0e8d71ee39e6a762f0)) - Fixed sandbox command execution crashing the parent process on some Node.js versions by explicitly setting stdio to pipe for detached child processes. ([#13697](https://github.com/mastra-ai/mastra/pull/13697)) - Fixed an issue where generating a response in an empty thread (system-only messages) would throw an error. Providers that support system-only prompts like Anthropic and OpenAI now work as expected. A warning is logged for providers that require at least one user message (e.g. Gemini). Fixes #13045. ([#13164](https://github.com/mastra-ai/mastra/pull/13164)) - Sanitize invalid tool names in agent history so Bedrock retries continue instead of failing request validation. ([#13633](https://github.com/mastra-ai/mastra/pull/13633)) - Fixed path matching for auto-indexing and skills discovery. ([#13511](https://github.com/mastra-ai/mastra/pull/13511)) Single file paths, directory globs, and `SKILL.md` file globs now resolve consistently. Trailing slashes are now handled correctly. - `Harness.cloneThread()` now resolves dynamic memory factories before cloning, fixing "cloneThread is not a function" errors when memory is provided as a factory function. `HarnessConfig.memory` type widened to `DynamicArgument`. ([#13569](https://github.com/mastra-ai/mastra/pull/13569)) - Fixed workspace tools being callable by their old default names (e.g. mastra_workspace_edit_file) when renamed via tools config. The tool's internal id is now updated to match the remapped name, preventing fallback resolution from bypassing the rename. ([#13694](https://github.com/mastra-ai/mastra/pull/13694)) - Reduced default max output tokens from 3000 to 2000 for all workspace tools. List files tool uses a 1000 token limit. Suppressed "No errors or warnings" LSP diagnostic message when there are no issues. ([#13730](https://github.com/mastra-ai/mastra/pull/13730)) - Add first-class custom provider support for MastraCode model selection and routing. ([#13682](https://github.com/mastra-ai/mastra/pull/13682)) - Add `/custom-providers` command to create, edit, and delete custom OpenAI-compatible providers and manage model IDs under each provider. - Persist custom providers and model IDs in `settings.json` with schema parsing/validation updates. - Extend Harness model catalog listing with `customModelCatalogProvider` so custom models appear in existing selectors (`/models`, `/subagents`). - Route configured custom provider model IDs through `ModelRouterLanguageModel` using provider-specific URL and optional API key settings. - **`sendMessage` now accepts `files` instead of `images`**, supporting any file type with optional `filename`. ([#13574](https://github.com/mastra-ai/mastra/pull/13574)) **Breaking change:** Rename `images` to `files` when calling `harness.sendMessage()`: ```ts // Before await harness.sendMessage({ content: 'Analyze this', images: [{ data: base64Data, mimeType: 'image/png' }], }); // After await harness.sendMessage({ content: 'Analyze this', files: [{ data: base64Data, mediaType: 'image/png', filename: 'screenshot.png' }], }); ``` - `files` accepts `{ data, mediaType, filename? }` — filenames are now preserved through storage and message history - Text-based files (`text/*`, `application/json`) are automatically decoded to readable text content instead of being sent as binary, which models could not process - `HarnessMessageContent` now includes a `file` type, so file parts round-trip correctly through message history - Fixed Agent Network routing failures for users running Claude models through AWS Bedrock by removing trailing whitespace from the routing assistant message. ([#13624](https://github.com/mastra-ai/mastra/pull/13624)) - Fixed thread title generation when user messages include file parts (for example, images). ([#13671](https://github.com/mastra-ai/mastra/pull/13671)) Titles now generate reliably instead of becoming empty. - Fixed parallel workflow tool calls so each call runs independently. ([#13478](https://github.com/mastra-ai/mastra/pull/13478)) When an agent starts multiple tool calls to the same workflow at the same time, each call now runs with its own workflow run context. This prevents duplicated results across parallel calls and ensures each call returns output for its own input. Also ensures workflow tool suspension and manual resumption correctly preserves the run context. - Fixed sub-agent instructions being overridden when the parent agent uses an OpenAI model. Previously, OpenAI models would fill in the optional `instructions` parameter when calling a sub-agent tool, completely replacing the sub-agent's own instructions. Now, any LLM-provided instructions are appended to the sub-agent's configured instructions instead of replacing them. ([#13578](https://github.com/mastra-ai/mastra/pull/13578)) - Tool lifecycle hooks (`onInputStart`, `onInputDelta`, `onInputAvailable`, `onOutput`) now fire correctly during agent execution for tools created via `createTool()`. Previously these hooks were silently ignored. Affected: `createTool`, `Tool`, `CoreToolBuilder.build`, `CoreTool`. ([#13708](https://github.com/mastra-ai/mastra/pull/13708)) - Fixed an issue where sub-agent messages inside a workflow tool would corrupt the parent agent's memory context. When an agent calls a workflow as a tool and the workflow runs sub-agents with their own memory threads, the parent's thread identity on the shared request context is now correctly saved before the workflow executes and restored afterward, preventing messages from being written to the wrong thread. ([#13637](https://github.com/mastra-ai/mastra/pull/13637)) - Fix workspace tool output truncation to handle tokenizer special tokens ([#13725](https://github.com/mastra-ai/mastra/pull/13725)) - Added a warning when a `LocalFilesystem` mount uses `contained: false`, alerting users to path resolution issues in mount-based workspaces. Use `contained: true` (default) or `allowedPaths` to allow specific host paths. ([#13474](https://github.com/mastra-ai/mastra/pull/13474)) - Fixed harness handling for observational memory failures so streams stop immediately when OM reports a failed run or buffering cycle. ([#13563](https://github.com/mastra-ai/mastra/pull/13563)) The harness now emits the existing OM failure event (`om_observation_failed`, `om_reflection_failed`, or `om_buffering_failed`), emits a top-level error with OM context, and aborts the active stream. This prevents normal assistant output from continuing after an OM model failure. - Fixed subagents being unable to access files outside the project root. Subagents now inherit both user-approved sandbox paths and skill paths (e.g. `~/.claude/skills`) from the parent agent. ([#13700](https://github.com/mastra-ai/mastra/pull/13700)) - Fixed agent-as-tools schema generation so Gemini accepts tool definitions for suspend/resume flows. ([#13715](https://github.com/mastra-ai/mastra/pull/13715)) This prevents schema validation failures when `resumeData` is present. - Fixed tool approval resume failing when Agent is used without an explicit Mastra instance. The Harness now creates an internal Mastra instance with storage and registers it on mode agents, ensuring workflow snapshots persist and load correctly. Also fixed requestContext serialization using toJSON() to prevent circular reference errors during snapshot persistence. ([#13519](https://github.com/mastra-ai/mastra/pull/13519)) - Fixed spawn error handling in LocalSandbox by switching to execa. Previously, spawning a process with an invalid working directory or missing command could crash with an unhandled Node.js exception. Now returns descriptive error messages instead. Also fixed timeout handling to properly kill the entire process group for compound commands. ([#13734](https://github.com/mastra-ai/mastra/pull/13734)) - HTTP request logging can now be configured in detail via `apiReqLogs` in the server config. The new `HttpLoggingConfig` type is exported from `@mastra/core/server`. ([#11907](https://github.com/mastra-ai/mastra/pull/11907)) ```ts import type { HttpLoggingConfig } from '@mastra/core/server'; const loggingConfig: HttpLoggingConfig = { enabled: true, level: 'info', excludePaths: ['/health', '/metrics'], includeHeaders: true, includeQueryParams: true, redactHeaders: ['authorization', 'cookie'], }; ``` - Remove internal `processes` field from sandbox provider options ([#13597](https://github.com/mastra-ai/mastra/pull/13597)) The `processes` field is no longer exposed in constructor options for E2B, Daytona, and Blaxel sandbox providers. This field is managed internally and was not intended to be user-configurable. - Fixed abort signal propagation in agent networks. When using `abortSignal` with `agent.network()`, the signal now correctly prevents tool execution when abort fires during routing, and no longer saves partial results to memory when sub-agents, tools, or workflows are aborted. ([#13491](https://github.com/mastra-ai/mastra/pull/13491)) - Fixed Memory.recall() to include pagination metadata (total, page, perPage, hasMore) in its response, ensuring consistent pagination regardless of whether agentId is provided. Fixes #13277 ([#13278](https://github.com/mastra-ai/mastra/pull/13278)) - Fixed harness getTokenUsage() returning zeros when using AI SDK v5/v6. The token usage extraction now correctly reads both inputTokens/outputTokens (v5/v6) and promptTokens/completionTokens (v4) field names from the usage object. ([#13622](https://github.com/mastra-ai/mastra/pull/13622)) - Model pack selection is now more consistent and reliable in mastracode. ([#13512](https://github.com/mastra-ai/mastra/pull/13512)) - `/models` is now the single command for choosing and managing model packs. - Model picker ranking now learns from your recent selections and keeps those preferences across sessions. - Pack choice now restores correctly per thread when switching between threads. - Custom packs now support full create, rename, targeted edit, and delete workflows. - The built-in **Varied** option has been retired; users who had it selected are automatically migrated to a saved custom pack named `varied`. - Added support for reading resource IDs from `Harness`. ([#13690](https://github.com/mastra-ai/mastra/pull/13690)) You can now get the default resource ID and list known resource IDs from stored threads. ```ts const defaultId = harness.getDefaultResourceId(); const knownIds = await harness.getKnownResourceIds(); ``` - chore(harness): Update harness sub-agent instructions type to be dynamic ([#13706](https://github.com/mastra-ai/mastra/pull/13706)) - Added `MastraMessagePart` to the public type exports of `@mastra/core/agent`, allowing it to be imported directly in downstream packages. ([#13297](https://github.com/mastra-ai/mastra/pull/13297)) - Added `deleteThread({ threadId })` method to the Harness class for deleting threads and their messages from storage. Releases the thread lock and clears the active thread when deleting the current thread. Emits a `thread_deleted` event. ([#13625](https://github.com/mastra-ai/mastra/pull/13625)) - Fixed tilde (~) paths not expanding to the home directory in LocalFilesystem and LocalSandbox. Paths like `~/my-project` were silently treated as relative paths, creating a literal `~/` directory instead of resolving to `$HOME`. This affects `basePath`, `allowedPaths`, `setAllowedPaths()`, all file operations in LocalFilesystem, and `workingDirectory` in LocalSandbox. ([#13739](https://github.com/mastra-ai/mastra/pull/13739)) - Switched Mastra Code to workspace tools and enabled LSP by default ([#13437](https://github.com/mastra-ai/mastra/pull/13437)) - Switched from built-in tool implementations to workspace tools for file operations, search, edit, write, and command execution - Enabled LSP (language server) by default with automatic package runner detection and bundled binary resolution - Added real-time stdout/stderr streaming in the TUI for workspace command execution - Added TUI rendering for process management tools (view output, kill processes) - Fixed edit diff preview in the TUI to work with workspace tool arg names (`old_string`/`new_string`) - Fixed tilde paths (`~/foo`) in contained `LocalFilesystem` silently writing to the wrong location. Previously, `~/foo` would expand and then nest under basePath (e.g. `basePath/home/user/foo`). Tilde paths are now treated as real absolute paths, and throw `PermissionError` when the expanded path is outside `basePath` and `allowedPaths`. ([#13741](https://github.com/mastra-ai/mastra/pull/13741)) ## 1.8.0 ### Minor Changes - Make `queryVector` optional in the `QueryVectorParams` interface to support metadata-only queries. At least one of `queryVector` or `filter` must be provided. Not all vector store backends support metadata-only queries — check your store's documentation for details. ([#13286](https://github.com/mastra-ai/mastra/pull/13286)) Also fixes documentation where the `query()` parameter was incorrectly named `vector` instead of `queryVector`. - Added `targetOptions` parameter to `runEvals` that is forwarded directly to `agent.generate()` (modern path) or `workflow.run.start()`. Also added per-item `startOptions` field to `RunEvalsDataItem` for per-item workflow options like `initialState`. ([#13366](https://github.com/mastra-ai/mastra/pull/13366)) **New feature: `targetOptions`** Pass agent execution options (e.g. `maxSteps`, `modelSettings`, `instructions`) through to `agent.generate()`, or workflow run options (e.g. `perStep`, `outputOptions`) through to `workflow.run.start()`: ```ts // Agent - pass modelSettings or maxSteps await runEvals({ data, scorers, target: myAgent, targetOptions: { maxSteps: 5, modelSettings: { temperature: 0 } }, }); // Workflow - pass run options await runEvals({ data, scorers, target: myWorkflow, targetOptions: { perStep: true }, }); ``` **New feature: per-item `startOptions`** Supply per-item workflow options (e.g. `initialState`) directly on each data item: ```ts await runEvals({ data: [ { input: { query: 'hello' }, startOptions: { initialState: { counter: 1 } } }, { input: { query: 'world' }, startOptions: { initialState: { counter: 2 } } }, ], scorers, target: myWorkflow, }); ``` Per-item `startOptions` take precedence over global `targetOptions` for the same key. Runeval-managed options (`scorers`, `returnScorerData`, `requestContext`) cannot be overridden via `targetOptions`. - Add supervisor pattern for multi-agent coordination using `stream()` and `generate()`. Includes delegation hooks, iteration monitoring, completion scoring, memory isolation, tool approval propagation, context filtering, and bail mechanism. ([#13323](https://github.com/mastra-ai/mastra/pull/13323)) - Add LSP diagnostics to workspace edit tools ([#13441](https://github.com/mastra-ai/mastra/pull/13441)) Language Server Protocol (LSP) diagnostics now appear after edits made with write_file, edit_file, and ast_edit. Seeing type and lint errors immediately helps catch issues before the next tool call. Edits still work without diagnostics when language servers are not installed. Supports TypeScript, Python (Pyright), Go (gopls), Rust (rust-analyzer), and ESLint. **Example** Before: ```ts const workspace = new Workspace({ sandbox, filesystem }); ``` After: ```ts const workspace = new Workspace({ sandbox, filesystem, lsp: true }); ``` ### Patch Changes - Propagate tripwire's that are thrown from a nested workflow. ([#13502](https://github.com/mastra-ai/mastra/pull/13502)) - Added `isProviderDefinedTool` helper to detect provider-defined AI SDK tools (e.g. `google.tools.googleSearch()`, `openai.tools.webSearch()`) for proper schema handling during serialization. ([#13507](https://github.com/mastra-ai/mastra/pull/13507)) - Fixed `ModelRouterEmbeddingModel.doEmbed()` crashing with `TypeError: result.warnings is not iterable` when used with AI SDK v6's `embedMany`. The result now always includes a `warnings` array, ensuring forward compatibility across AI SDK versions. ([#13369](https://github.com/mastra-ai/mastra/pull/13369)) - Fixed build error in `ModelRouterEmbeddingModel.doEmbed()` caused by `warnings` not existing on the return type. ([#13461](https://github.com/mastra-ai/mastra/pull/13461)) - Fixed `Harness.createThread()` defaulting the thread title to `"New Thread"` which prevented `generateTitle` from working (see #13391). Threads created without an explicit title now have an empty string title, allowing the agent's title generation to produce a title from the first user message. ([#13393](https://github.com/mastra-ai/mastra/pull/13393)) - Prevent unknown model IDs from being sorted to the front in `reorderModels()`. Models not present in the `modelIds` parameter are now moved to the end of the array. Fixes #13410. ([#13445](https://github.com/mastra-ai/mastra/pull/13445)) - Include traceId on scores generated during experiment runs to restore traceability of experiment results ([#13464](https://github.com/mastra-ai/mastra/pull/13464)) - Fixed `skill-read-reference` (and `getReference`, `getScript`, `getAsset` in `WorkspaceSkillsImpl`) to resolve file paths relative to the **skill root** instead of hardcoded subdirectories (`references/`, `scripts/`, `assets/`). ([#13363](https://github.com/mastra-ai/mastra/pull/13363)) Previously, calling `skill-read-reference` with `referencePath: "docs/schema.md"` would silently fail because it resolved to `/references/docs/schema.md` instead of `/docs/schema.md`. Now all paths like `references/colors.md`, `docs/schema.md`, and `./config.json` resolve correctly relative to the skill root. Path traversal attacks (e.g. `../../etc/passwd`) are still blocked. - Fixed workspace listing to show whether each workspace is global or agent-owned. ([#13468](https://github.com/mastra-ai/mastra/pull/13468)) Agent-owned workspaces now include the owning agent's ID and name so clients can distinguish them from global workspaces. - Fixed Observational Memory not working with AI SDK v4 models (legacy path). The legacy stream/generate path now calls processInputStep, enabling processors like Observational Memory to inject conversation history and observations. ([#13358](https://github.com/mastra-ai/mastra/pull/13358)) - Added `resolveWorkspace()` so callers can access a dynamic workspace before the first request. ([#13457](https://github.com/mastra-ai/mastra/pull/13457)) - Fixed abortSignal not stopping LLM generation or preventing memory persistence. When aborting a stream (e.g., client disconnect), the LLM response no longer continues processing in the background and partial/full responses are no longer saved to memory. Fixes #13117. ([#13206](https://github.com/mastra-ai/mastra/pull/13206)) - Fixed observation activation to always preserve a minimum amount of context. Previously, swapping buffered observation chunks could unexpectedly drop the context window to near-zero tokens. ([#13476](https://github.com/mastra-ai/mastra/pull/13476)) - Fixed a crash where the Node.js process would terminate with an unhandled TypeError when an LLM stream encountered an error. The ReadableStreamDefaultController would throw "Controller is already closed" when chunks were enqueued after a downstream consumer cancelled or terminated the stream. All controller.enqueue(), controller.close(), and controller.error() calls now check if the controller is still open before attempting operations. (https://github.com/mastra-ai/mastra/issues/13107) ([#13206](https://github.com/mastra-ai/mastra/pull/13206)) - Updated dependencies [[`8d14a59`](https://github.com/mastra-ai/mastra/commit/8d14a591d46fbbbe81baa33c9c267d596f790329)]: - @mastra/schema-compat@1.1.3 ## 1.8.0-alpha.0 ### Minor Changes - Make `queryVector` optional in the `QueryVectorParams` interface to support metadata-only queries. At least one of `queryVector` or `filter` must be provided. Not all vector store backends support metadata-only queries — check your store's documentation for details. ([#13286](https://github.com/mastra-ai/mastra/pull/13286)) Also fixes documentation where the `query()` parameter was incorrectly named `vector` instead of `queryVector`. - Added `targetOptions` parameter to `runEvals` that is forwarded directly to `agent.generate()` (modern path) or `workflow.run.start()`. Also added per-item `startOptions` field to `RunEvalsDataItem` for per-item workflow options like `initialState`. ([#13366](https://github.com/mastra-ai/mastra/pull/13366)) **New feature: `targetOptions`** Pass agent execution options (e.g. `maxSteps`, `modelSettings`, `instructions`) through to `agent.generate()`, or workflow run options (e.g. `perStep`, `outputOptions`) through to `workflow.run.start()`: ```ts // Agent - pass modelSettings or maxSteps await runEvals({ data, scorers, target: myAgent, targetOptions: { maxSteps: 5, modelSettings: { temperature: 0 } }, }); // Workflow - pass run options await runEvals({ data, scorers, target: myWorkflow, targetOptions: { perStep: true }, }); ``` **New feature: per-item `startOptions`** Supply per-item workflow options (e.g. `initialState`) directly on each data item: ```ts await runEvals({ data: [ { input: { query: 'hello' }, startOptions: { initialState: { counter: 1 } } }, { input: { query: 'world' }, startOptions: { initialState: { counter: 2 } } }, ], scorers, target: myWorkflow, }); ``` Per-item `startOptions` take precedence over global `targetOptions` for the same key. Runeval-managed options (`scorers`, `returnScorerData`, `requestContext`) cannot be overridden via `targetOptions`. - Add supervisor pattern for multi-agent coordination using `stream()` and `generate()`. Includes delegation hooks, iteration monitoring, completion scoring, memory isolation, tool approval propagation, context filtering, and bail mechanism. ([#13323](https://github.com/mastra-ai/mastra/pull/13323)) - Add LSP diagnostics to workspace edit tools ([#13441](https://github.com/mastra-ai/mastra/pull/13441)) Language Server Protocol (LSP) diagnostics now appear after edits made with write_file, edit_file, and ast_edit. Seeing type and lint errors immediately helps catch issues before the next tool call. Edits still work without diagnostics when language servers are not installed. Supports TypeScript, Python (Pyright), Go (gopls), Rust (rust-analyzer), and ESLint. **Example** Before: ```ts const workspace = new Workspace({ sandbox, filesystem }); ``` After: ```ts const workspace = new Workspace({ sandbox, filesystem, lsp: true }); ``` ### Patch Changes - Propagate tripwire's that are thrown from a nested workflow. ([#13502](https://github.com/mastra-ai/mastra/pull/13502)) - Added `isProviderDefinedTool` helper to detect provider-defined AI SDK tools (e.g. `google.tools.googleSearch()`, `openai.tools.webSearch()`) for proper schema handling during serialization. ([#13507](https://github.com/mastra-ai/mastra/pull/13507)) - Fixed `ModelRouterEmbeddingModel.doEmbed()` crashing with `TypeError: result.warnings is not iterable` when used with AI SDK v6's `embedMany`. The result now always includes a `warnings` array, ensuring forward compatibility across AI SDK versions. ([#13369](https://github.com/mastra-ai/mastra/pull/13369)) - Fixed build error in `ModelRouterEmbeddingModel.doEmbed()` caused by `warnings` not existing on the return type. ([#13461](https://github.com/mastra-ai/mastra/pull/13461)) - Fixed `Harness.createThread()` defaulting the thread title to `"New Thread"` which prevented `generateTitle` from working (see #13391). Threads created without an explicit title now have an empty string title, allowing the agent's title generation to produce a title from the first user message. ([#13393](https://github.com/mastra-ai/mastra/pull/13393)) - Prevent unknown model IDs from being sorted to the front in `reorderModels()`. Models not present in the `modelIds` parameter are now moved to the end of the array. Fixes #13410. ([#13445](https://github.com/mastra-ai/mastra/pull/13445)) - Include traceId on scores generated during experiment runs to restore traceability of experiment results ([#13464](https://github.com/mastra-ai/mastra/pull/13464)) - Fixed `skill-read-reference` (and `getReference`, `getScript`, `getAsset` in `WorkspaceSkillsImpl`) to resolve file paths relative to the **skill root** instead of hardcoded subdirectories (`references/`, `scripts/`, `assets/`). ([#13363](https://github.com/mastra-ai/mastra/pull/13363)) Previously, calling `skill-read-reference` with `referencePath: "docs/schema.md"` would silently fail because it resolved to `/references/docs/schema.md` instead of `/docs/schema.md`. Now all paths like `references/colors.md`, `docs/schema.md`, and `./config.json` resolve correctly relative to the skill root. Path traversal attacks (e.g. `../../etc/passwd`) are still blocked. - Fixed workspace listing to show whether each workspace is global or agent-owned. ([#13468](https://github.com/mastra-ai/mastra/pull/13468)) Agent-owned workspaces now include the owning agent's ID and name so clients can distinguish them from global workspaces. - Fixed Observational Memory not working with AI SDK v4 models (legacy path). The legacy stream/generate path now calls processInputStep, enabling processors like Observational Memory to inject conversation history and observations. ([#13358](https://github.com/mastra-ai/mastra/pull/13358)) - Added `resolveWorkspace()` so callers can access a dynamic workspace before the first request. ([#13457](https://github.com/mastra-ai/mastra/pull/13457)) - Fixed abortSignal not stopping LLM generation or preventing memory persistence. When aborting a stream (e.g., client disconnect), the LLM response no longer continues processing in the background and partial/full responses are no longer saved to memory. Fixes #13117. ([#13206](https://github.com/mastra-ai/mastra/pull/13206)) - Fixed observation activation to always preserve a minimum amount of context. Previously, swapping buffered observation chunks could unexpectedly drop the context window to near-zero tokens. ([#13476](https://github.com/mastra-ai/mastra/pull/13476)) - Fixed a crash where the Node.js process would terminate with an unhandled TypeError when an LLM stream encountered an error. The ReadableStreamDefaultController would throw "Controller is already closed" when chunks were enqueued after a downstream consumer cancelled or terminated the stream. All controller.enqueue(), controller.close(), and controller.error() calls now check if the controller is still open before attempting operations. (https://github.com/mastra-ai/mastra/issues/13107) ([#13206](https://github.com/mastra-ai/mastra/pull/13206)) - Updated dependencies [[`8d14a59`](https://github.com/mastra-ai/mastra/commit/8d14a591d46fbbbe81baa33c9c267d596f790329)]: - @mastra/schema-compat@1.1.3-alpha.0 ## 1.7.0 ### Minor Changes - Added `getObservationalMemoryRecord()` method to the `Harness` class. Fixes #13392. ([#13395](https://github.com/mastra-ai/mastra/pull/13395)) This provides public access to the full `ObservationalMemoryRecord` for the current thread, including `activeObservations`, `generationCount`, and `observationTokenCount`. Previously, accessing raw observation text required bypassing the Harness abstraction by reaching into private storage internals. ```typescript const record = await harness.getObservationalMemoryRecord(); if (record) { console.log(record.activeObservations); } ``` - Added `Workspace.setToolsConfig()` method for dynamically updating per-tool configuration at runtime without recreating the workspace instance. Passing `undefined` re-enables all tools. ([#13439](https://github.com/mastra-ai/mastra/pull/13439)) ```ts const workspace = new Workspace({ filesystem, sandbox }); // Disable write tools (e.g., in plan/read-only mode) workspace.setToolsConfig({ mastra_workspace_write_file: { enabled: false }, mastra_workspace_edit_file: { enabled: false }, }); // Re-enable all tools workspace.setToolsConfig(undefined); ``` - Added `HarnessDisplayState` so any UI can read a single state snapshot instead of handling 35+ individual events. ([#13427](https://github.com/mastra-ai/mastra/pull/13427)) **Why:** Previously, every UI (TUI, web, desktop) had to subscribe to dozens of granular Harness events and independently reconstruct what to display. This led to duplicated state tracking and inconsistencies across UI implementations. Now the Harness maintains a single canonical display state that any UI can read. **Before:** UIs subscribed to raw events and built up display state locally: ```ts harness.subscribe((event) => { if (event.type === 'agent_start') localState.isRunning = true; if (event.type === 'agent_end') localState.isRunning = false; if (event.type === 'tool_start') localState.tools.set(event.toolCallId, ...); // ... 30+ more event types to handle }); ``` **After:** UIs read a single snapshot from the Harness: ```ts import type { HarnessDisplayState } from '@mastra/core/harness'; harness.subscribe(event => { const ds: HarnessDisplayState = harness.getDisplayState(); // ds.isRunning, ds.tokenUsage, ds.omProgress, ds.activeTools, etc. renderUI(ds); }); ``` - Prompt blocks can now define their own variables schema (`requestContextSchema`), allowing you to create reusable prompt blocks with typed variable placeholders. The server now correctly computes and returns draft/published status for prompt blocks. Existing databases are automatically migrated when upgrading. ([#13351](https://github.com/mastra-ai/mastra/pull/13351)) - **Workspace instruction improvements** ([#13304](https://github.com/mastra-ai/mastra/pull/13304)) - Added `Workspace.getInstructions()`: agents now receive accurate workspace context that distinguishes sandbox-accessible paths from workspace-only paths. - Added `WorkspaceInstructionsProcessor`: workspace context is injected directly into the agent system message instead of embedded in tool descriptions. - Deprecated `Workspace.getPathContext()` in favour of `getInstructions()`. Added `instructions` option to `LocalFilesystem` and `LocalSandbox`. Pass a string to fully replace default instructions, or a function to extend them with access to the current `requestContext` for per-request customization (e.g. by tenant or locale). ```typescript const filesystem = new LocalFilesystem({ basePath: './workspace', instructions: ({ defaultInstructions, requestContext }) => { const locale = requestContext?.get('locale') ?? 'en'; return `${defaultInstructions}\nLocale: ${locale}`; }, }); ``` - Added background process management to workspace sandboxes. ([#13293](https://github.com/mastra-ai/mastra/pull/13293)) You can now spawn, monitor, and manage long-running background processes (dev servers, watchers, REPLs) inside sandbox environments. ```typescript // Spawn a background process const handle = await sandbox.processes.spawn('node server.js'); // Stream output and wait for exit const result = await handle.wait({ onStdout: data => console.log(data), }); // List and manage running processes const procs = await sandbox.processes.list(); await sandbox.processes.kill(handle.pid); ``` - `SandboxProcessManager` abstract base class with `spawn()`, `list()`, `get(pid)`, `kill(pid)` - `ProcessHandle` base class with stdout/stderr accumulation, streaming callbacks, and `wait()` - `LocalProcessManager` implementation wrapping Node.js `child_process` - Node.js stream interop via `handle.reader` / `handle.writer` - Default `executeCommand` implementation built on process manager (spawn + wait) - Added workspace tools for background process management and improved sandbox execution UI. ([#13309](https://github.com/mastra-ai/mastra/pull/13309)) - `execute_command` now supports `background: true` to spawn long-running processes and return a PID - New `get_process_output` tool to check output/status of background processes (supports `wait` to block until exit) - New `kill_process` tool to terminate background processes - Output truncation helpers with configurable tail lines - Sandbox execution badge UI: terminal-style output display with streaming, exit codes, killed status, and workspace metadata ### Patch Changes - Fixed agents-as-tools failing with OpenAI when using the model router. The auto-injected `resumeData` field (from `z.any()`) produced a JSON Schema without a `type` key, which OpenAI rejects. Tool schemas are now post-processed to ensure all properties have valid type information. ([#13326](https://github.com/mastra-ai/mastra/pull/13326)) - Fixed `stopWhen` callback receiving empty `toolResults` on steps. `step.toolResults` now correctly reflects the tool results present in `step.content`. ([#13319](https://github.com/mastra-ai/mastra/pull/13319)) - Added `hasJudge` metadata to scorer records so the studio can distinguish code-based scorers (e.g., textual-difference, content-similarity) from LLM-based scorers. This metadata is now included in all four score-saving paths: `runEvals`, scorer hooks, trace scoring, and dataset experiments. ([#13386](https://github.com/mastra-ai/mastra/pull/13386)) - Fixed a bug where custom output processors could not emit stream events during final output processing. The `writer` object was always `undefined` when passed to output processors in the finish phase, preventing use cases like streaming moderation updates or custom UI events back to the client. ([#13454](https://github.com/mastra-ai/mastra/pull/13454)) - Added per-file write locking to workspace tools (edit_file, write_file, ast_edit, delete). Concurrent tool calls targeting the same file are now serialized, preventing race conditions where parallel edits could silently overwrite each other. ([#13302](https://github.com/mastra-ai/mastra/pull/13302)) ## 1.7.0-alpha.0 ### Minor Changes - Added `getObservationalMemoryRecord()` method to the `Harness` class. Fixes #13392. ([#13395](https://github.com/mastra-ai/mastra/pull/13395)) This provides public access to the full `ObservationalMemoryRecord` for the current thread, including `activeObservations`, `generationCount`, and `observationTokenCount`. Previously, accessing raw observation text required bypassing the Harness abstraction by reaching into private storage internals. ```typescript const record = await harness.getObservationalMemoryRecord(); if (record) { console.log(record.activeObservations); } ``` - Added `Workspace.setToolsConfig()` method for dynamically updating per-tool configuration at runtime without recreating the workspace instance. Passing `undefined` re-enables all tools. ([#13439](https://github.com/mastra-ai/mastra/pull/13439)) ```ts const workspace = new Workspace({ filesystem, sandbox }); // Disable write tools (e.g., in plan/read-only mode) workspace.setToolsConfig({ mastra_workspace_write_file: { enabled: false }, mastra_workspace_edit_file: { enabled: false }, }); // Re-enable all tools workspace.setToolsConfig(undefined); ``` - Added `HarnessDisplayState` so any UI can read a single state snapshot instead of handling 35+ individual events. ([#13427](https://github.com/mastra-ai/mastra/pull/13427)) **Why:** Previously, every UI (TUI, web, desktop) had to subscribe to dozens of granular Harness events and independently reconstruct what to display. This led to duplicated state tracking and inconsistencies across UI implementations. Now the Harness maintains a single canonical display state that any UI can read. **Before:** UIs subscribed to raw events and built up display state locally: ```ts harness.subscribe((event) => { if (event.type === 'agent_start') localState.isRunning = true; if (event.type === 'agent_end') localState.isRunning = false; if (event.type === 'tool_start') localState.tools.set(event.toolCallId, ...); // ... 30+ more event types to handle }); ``` **After:** UIs read a single snapshot from the Harness: ```ts import type { HarnessDisplayState } from '@mastra/core/harness'; harness.subscribe(event => { const ds: HarnessDisplayState = harness.getDisplayState(); // ds.isRunning, ds.tokenUsage, ds.omProgress, ds.activeTools, etc. renderUI(ds); }); ``` - Prompt blocks can now define their own variables schema (`requestContextSchema`), allowing you to create reusable prompt blocks with typed variable placeholders. The server now correctly computes and returns draft/published status for prompt blocks. Existing databases are automatically migrated when upgrading. ([#13351](https://github.com/mastra-ai/mastra/pull/13351)) - **Workspace instruction improvements** ([#13304](https://github.com/mastra-ai/mastra/pull/13304)) - Added `Workspace.getInstructions()`: agents now receive accurate workspace context that distinguishes sandbox-accessible paths from workspace-only paths. - Added `WorkspaceInstructionsProcessor`: workspace context is injected directly into the agent system message instead of embedded in tool descriptions. - Deprecated `Workspace.getPathContext()` in favour of `getInstructions()`. Added `instructions` option to `LocalFilesystem` and `LocalSandbox`. Pass a string to fully replace default instructions, or a function to extend them with access to the current `requestContext` for per-request customization (e.g. by tenant or locale). ```typescript const filesystem = new LocalFilesystem({ basePath: './workspace', instructions: ({ defaultInstructions, requestContext }) => { const locale = requestContext?.get('locale') ?? 'en'; return `${defaultInstructions}\nLocale: ${locale}`; }, }); ``` - Added background process management to workspace sandboxes. ([#13293](https://github.com/mastra-ai/mastra/pull/13293)) You can now spawn, monitor, and manage long-running background processes (dev servers, watchers, REPLs) inside sandbox environments. ```typescript // Spawn a background process const handle = await sandbox.processes.spawn('node server.js'); // Stream output and wait for exit const result = await handle.wait({ onStdout: data => console.log(data), }); // List and manage running processes const procs = await sandbox.processes.list(); await sandbox.processes.kill(handle.pid); ``` - `SandboxProcessManager` abstract base class with `spawn()`, `list()`, `get(pid)`, `kill(pid)` - `ProcessHandle` base class with stdout/stderr accumulation, streaming callbacks, and `wait()` - `LocalProcessManager` implementation wrapping Node.js `child_process` - Node.js stream interop via `handle.reader` / `handle.writer` - Default `executeCommand` implementation built on process manager (spawn + wait) - Added workspace tools for background process management and improved sandbox execution UI. ([#13309](https://github.com/mastra-ai/mastra/pull/13309)) - `execute_command` now supports `background: true` to spawn long-running processes and return a PID - New `get_process_output` tool to check output/status of background processes (supports `wait` to block until exit) - New `kill_process` tool to terminate background processes - Output truncation helpers with configurable tail lines - Sandbox execution badge UI: terminal-style output display with streaming, exit codes, killed status, and workspace metadata ### Patch Changes - Fixed agents-as-tools failing with OpenAI when using the model router. The auto-injected `resumeData` field (from `z.any()`) produced a JSON Schema without a `type` key, which OpenAI rejects. Tool schemas are now post-processed to ensure all properties have valid type information. ([#13326](https://github.com/mastra-ai/mastra/pull/13326)) - Fixed `stopWhen` callback receiving empty `toolResults` on steps. `step.toolResults` now correctly reflects the tool results present in `step.content`. ([#13319](https://github.com/mastra-ai/mastra/pull/13319)) - Added `hasJudge` metadata to scorer records so the studio can distinguish code-based scorers (e.g., textual-difference, content-similarity) from LLM-based scorers. This metadata is now included in all four score-saving paths: `runEvals`, scorer hooks, trace scoring, and dataset experiments. ([#13386](https://github.com/mastra-ai/mastra/pull/13386)) - Added per-file write locking to workspace tools (edit_file, write_file, ast_edit, delete). Concurrent tool calls targeting the same file are now serialized, preventing race conditions where parallel edits could silently overwrite each other. ([#13302](https://github.com/mastra-ai/mastra/pull/13302)) ## 1.6.0 ### Minor Changes - Added Processor Providers — a new system for configuring and hydrating processors on stored agents. Define custom processor types with config schemas, available phases, and a factory method, then compose them into serializable processor graphs that support sequential, parallel, and conditional execution. ([#13219](https://github.com/mastra-ai/mastra/pull/13219)) **Example — custom processor provider:** ```ts import { MastraEditor } from '@mastra/editor'; // Built-in processors (token-limiter, unicode-normalizer, etc.) are registered automatically. // Only register custom providers for your own processors: const editor = new MastraEditor({ processorProviders: { 'my-custom-filter': myCustomFilterProvider, }, }); ``` **Example — stored agent with a processor graph:** ```ts const agentConfig = { inputProcessors: { steps: [ { type: 'step', step: { id: 'norm', providerId: 'unicode-normalizer', config: {}, enabledPhases: ['processInput'] }, }, { type: 'step', step: { id: 'limit', providerId: 'token-limiter', config: { limit: 4000 }, enabledPhases: ['processInput', 'processOutputStream'], }, }, ], }, }; ``` - Added AST edit tool (`workspace_ast_edit`) for intelligent code transformations using AST analysis. Supports renaming identifiers, adding/removing/merging imports, and pattern-based find-and-replace with metavariable substitution. Automatically available when `@ast-grep/napi` is installed in the project. ([#13233](https://github.com/mastra-ai/mastra/pull/13233)) **Example:** ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: '/my/project' }), }); const tools = createWorkspaceTools(workspace); // Rename all occurrences of an identifier await tools['mastra_workspace_ast_edit'].execute({ path: '/src/utils.ts', transform: 'rename', targetName: 'oldName', newName: 'newName', }); // Add an import (merges into existing imports from the same module) await tools['mastra_workspace_ast_edit'].execute({ path: '/src/app.ts', transform: 'add-import', importSpec: { module: 'react', names: ['useState', 'useEffect'] }, }); // Pattern-based replacement with metavariables await tools['mastra_workspace_ast_edit'].execute({ path: '/src/app.ts', pattern: 'console.log($ARG)', replacement: 'logger.debug($ARG)', }); ``` - Added streaming tool argument previews across all tool renderers. Tool names, file paths, and commands now appear immediately as the model generates them, rather than waiting for the complete tool call. ([#13328](https://github.com/mastra-ai/mastra/pull/13328)) - **Generic tools** show live key/value argument previews as args stream in - **Edit tool** renders a bordered diff preview as soon as `old_str` and `new_str` are available, even before the tool result arrives - **Write tool** streams syntax-highlighted file content in a bordered box while args arrive - **Find files** shows the glob pattern in the pending header - **Task write** streams items directly into the pinned task list component in real-time All tools use partial JSON parsing to progressively display argument information. This is enabled automatically for all Harness-based agents — no configuration required. - Added MCP server storage and editor support. MCP server configurations can now be persisted in storage and managed through the editor CMS. The editor's `mcpServer` namespace provides full CRUD operations and automatically hydrates stored configs into running `MCPServer` instances by resolving tool, agent, and workflow references from the Mastra registry. ([#13285](https://github.com/mastra-ai/mastra/pull/13285)) ```ts const editor = new MastraEditor(); const mastra = new Mastra({ tools: { getWeather: weatherTool, calculate: calculatorTool }, storage: new LibSQLStore({ url: ':memory:' }), editor, }); // Store an MCP server config referencing tools by ID const server = await editor.mcpServer.create({ id: 'my-server', name: 'My MCP Server', version: '1.0.0', tools: { getWeather: {}, calculate: { description: 'Custom description' } }, }); // Retrieve — automatically hydrates into a real MCPServer with resolved tools const mcp = await editor.mcpServer.getById('my-server'); const tools = mcp.tools(); // { getWeather: ..., calculate: ... } ``` - **@mastra/core:** Added optional `threadLock` callbacks to `HarnessConfig` for preventing concurrent thread access across processes. The Harness calls `acquire`/`release` during `selectOrCreateThread`, `createThread`, and `switchThread` when configured. Locking is opt-in — when `threadLock` is not provided, behavior is unchanged. ([#13334](https://github.com/mastra-ai/mastra/pull/13334)) ```ts const harness = new Harness({ id: 'my-harness', storage: myStore, modes: [{ id: 'default', agent: myAgent }], threadLock: { acquire: threadId => acquireThreadLock(threadId), release: threadId => releaseThreadLock(threadId), }, }); ``` **mastracode:** Wires the existing filesystem-based thread lock (`thread-lock.ts`) into the new `threadLock` config, restoring the concurrent access protection that was lost during the monorepo migration. - Refactored all Harness class methods to accept object parameters instead of positional arguments, and standardized method naming. ([#13353](https://github.com/mastra-ai/mastra/pull/13353)) **Why:** Positional arguments make call sites harder to read, especially for methods with optional middle parameters or multiple string arguments. Object parameters are self-documenting and easier to extend without breaking changes. - Methods returning arrays use `list` prefix (`listModes`, `listAvailableModels`, `listMessages`, `listMessagesForThread`) - `persistThreadSetting` → `setThreadSetting` - `resolveToolApprovalDecision` → `respondToToolApproval` (consistent with `respondToQuestion` / `respondToPlanApproval`) - `setPermissionCategory` → `setPermissionForCategory` - `setPermissionTool` → `setPermissionForTool` **Before:** ```typescript await harness.switchMode('build'); await harness.sendMessage('Hello', { images }); const modes = harness.getModes(); const models = await harness.getAvailableModels(); harness.resolveToolApprovalDecision('approve'); ``` **After:** ```typescript await harness.switchMode({ modeId: 'build' }); await harness.sendMessage({ content: 'Hello', images }); const modes = harness.listModes(); const models = await harness.listAvailableModels(); harness.respondToToolApproval({ decision: 'approve' }); ``` The `HarnessRequestContext` interface methods (`registerQuestion`, `registerPlanApproval`, `getSubagentModelId`) are also updated to use object parameters. - Added `task_write` and `task_check` as built-in Harness tools. These tools are automatically injected into every agent call, allowing agents to track structured task lists without manual tool registration. ([#13344](https://github.com/mastra-ai/mastra/pull/13344)) ```ts // Agents can call task_write to create/update a task list await tools['task_write'].execute({ tasks: [ { content: 'Fix authentication bug', status: 'in_progress', activeForm: 'Fixing authentication bug' }, { content: 'Add unit tests', status: 'pending', activeForm: 'Adding unit tests' }, ], }); // Agents can call task_check to verify all tasks are complete before finishing await tools['task_check'].execute({}); // Returns: { completed: 1, inProgress: 0, pending: 1, allDone: false, incomplete: [...] } ``` ### Patch Changes - Fixed duplicate Vercel AI Gateway configuration that could cause incorrect API key resolution. Removed a redundant override that conflicted with the upstream models.dev registry. ([#13291](https://github.com/mastra-ai/mastra/pull/13291)) - Fixed Vercel AI Gateway failing when using the model router string format (e.g. `vercel/openai/gpt-oss-120b`). The provider registry was overriding `createGateway`'s base URL with an incorrect value, causing API requests to hit the wrong endpoint. Removed the URL override so the AI SDK uses its own correct default. Closes #13280. ([#13287](https://github.com/mastra-ai/mastra/pull/13287)) - Fixed recursive schema warnings for processor graph entries by unrolling to a fixed depth of 3 levels, matching the existing rule group pattern ([#13292](https://github.com/mastra-ai/mastra/pull/13292)) - Fixed Observational Memory status not updating during conversations. The harness was missing streaming handlers for OM data chunks (status, observation start/end, buffering, activation), so the TUI never received real-time OM progress updates. Also added switchObserverModel and switchReflectorModel methods so changing OM models properly emits events to subscribers. ([#13330](https://github.com/mastra-ai/mastra/pull/13330)) - Fixed thread resuming in git worktrees. Previously, starting mastracode in a new worktree would resume a thread from another worktree of the same repo. Threads are now auto-tagged with the project path and filtered on resume so each worktree gets its own thread scope. ([#13343](https://github.com/mastra-ai/mastra/pull/13343)) - Fixed a crash where the Node.js process would terminate with an unhandled TypeError when an LLM stream encountered an error. The ReadableStreamDefaultController would throw "Controller is already closed" when chunks were enqueued after a downstream consumer cancelled or terminated the stream. All controller.enqueue(), controller.close(), and controller.error() calls now check if the controller is still open before attempting operations. (https://github.com/mastra-ai/mastra/issues/13107) ([#13142](https://github.com/mastra-ai/mastra/pull/13142)) - Added `suggestedContinuation` and `currentTask` fields to the in-memory storage adapter's Observational Memory activation result, aligning it with the persistent storage implementations. ([#13354](https://github.com/mastra-ai/mastra/pull/13354)) - Fixed provider-executed tools (e.g. Anthropic web_search) causing stream bail when called in parallel with regular tools. The tool-call-step now provides a fallback result for provider-executed tools whose output was not propagated, preventing the mapping step from misidentifying them as pending HITL interactions. Fixes #13125. ([#13126](https://github.com/mastra-ai/mastra/pull/13126)) - Updated dependencies [[`7184d87`](https://github.com/mastra-ai/mastra/commit/7184d87c9237d26862f500ccfd0c9f9eadd38ddf)]: - @mastra/schema-compat@1.1.2 ## 1.6.0-alpha.0 ### Minor Changes - Added Processor Providers — a new system for configuring and hydrating processors on stored agents. Define custom processor types with config schemas, available phases, and a factory method, then compose them into serializable processor graphs that support sequential, parallel, and conditional execution. ([#13219](https://github.com/mastra-ai/mastra/pull/13219)) **Example — custom processor provider:** ```ts import { MastraEditor } from '@mastra/editor'; // Built-in processors (token-limiter, unicode-normalizer, etc.) are registered automatically. // Only register custom providers for your own processors: const editor = new MastraEditor({ processorProviders: { 'my-custom-filter': myCustomFilterProvider, }, }); ``` **Example — stored agent with a processor graph:** ```ts const agentConfig = { inputProcessors: { steps: [ { type: 'step', step: { id: 'norm', providerId: 'unicode-normalizer', config: {}, enabledPhases: ['processInput'] }, }, { type: 'step', step: { id: 'limit', providerId: 'token-limiter', config: { limit: 4000 }, enabledPhases: ['processInput', 'processOutputStream'], }, }, ], }, }; ``` - Added AST edit tool (`workspace_ast_edit`) for intelligent code transformations using AST analysis. Supports renaming identifiers, adding/removing/merging imports, and pattern-based find-and-replace with metavariable substitution. Automatically available when `@ast-grep/napi` is installed in the project. ([#13233](https://github.com/mastra-ai/mastra/pull/13233)) **Example:** ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: '/my/project' }), }); const tools = createWorkspaceTools(workspace); // Rename all occurrences of an identifier await tools['mastra_workspace_ast_edit'].execute({ path: '/src/utils.ts', transform: 'rename', targetName: 'oldName', newName: 'newName', }); // Add an import (merges into existing imports from the same module) await tools['mastra_workspace_ast_edit'].execute({ path: '/src/app.ts', transform: 'add-import', importSpec: { module: 'react', names: ['useState', 'useEffect'] }, }); // Pattern-based replacement with metavariables await tools['mastra_workspace_ast_edit'].execute({ path: '/src/app.ts', pattern: 'console.log($ARG)', replacement: 'logger.debug($ARG)', }); ``` - Added streaming tool argument previews across all tool renderers. Tool names, file paths, and commands now appear immediately as the model generates them, rather than waiting for the complete tool call. ([#13328](https://github.com/mastra-ai/mastra/pull/13328)) - **Generic tools** show live key/value argument previews as args stream in - **Edit tool** renders a bordered diff preview as soon as `old_str` and `new_str` are available, even before the tool result arrives - **Write tool** streams syntax-highlighted file content in a bordered box while args arrive - **Find files** shows the glob pattern in the pending header - **Task write** streams items directly into the pinned task list component in real-time All tools use partial JSON parsing to progressively display argument information. This is enabled automatically for all Harness-based agents — no configuration required. - Added MCP server storage and editor support. MCP server configurations can now be persisted in storage and managed through the editor CMS. The editor's `mcpServer` namespace provides full CRUD operations and automatically hydrates stored configs into running `MCPServer` instances by resolving tool, agent, and workflow references from the Mastra registry. ([#13285](https://github.com/mastra-ai/mastra/pull/13285)) ```ts const editor = new MastraEditor(); const mastra = new Mastra({ tools: { getWeather: weatherTool, calculate: calculatorTool }, storage: new LibSQLStore({ url: ':memory:' }), editor, }); // Store an MCP server config referencing tools by ID const server = await editor.mcpServer.create({ id: 'my-server', name: 'My MCP Server', version: '1.0.0', tools: { getWeather: {}, calculate: { description: 'Custom description' } }, }); // Retrieve — automatically hydrates into a real MCPServer with resolved tools const mcp = await editor.mcpServer.getById('my-server'); const tools = mcp.tools(); // { getWeather: ..., calculate: ... } ``` - **@mastra/core:** Added optional `threadLock` callbacks to `HarnessConfig` for preventing concurrent thread access across processes. The Harness calls `acquire`/`release` during `selectOrCreateThread`, `createThread`, and `switchThread` when configured. Locking is opt-in — when `threadLock` is not provided, behavior is unchanged. ([#13334](https://github.com/mastra-ai/mastra/pull/13334)) ```ts const harness = new Harness({ id: 'my-harness', storage: myStore, modes: [{ id: 'default', agent: myAgent }], threadLock: { acquire: threadId => acquireThreadLock(threadId), release: threadId => releaseThreadLock(threadId), }, }); ``` **mastracode:** Wires the existing filesystem-based thread lock (`thread-lock.ts`) into the new `threadLock` config, restoring the concurrent access protection that was lost during the monorepo migration. - Refactored all Harness class methods to accept object parameters instead of positional arguments, and standardized method naming. ([#13353](https://github.com/mastra-ai/mastra/pull/13353)) **Why:** Positional arguments make call sites harder to read, especially for methods with optional middle parameters or multiple string arguments. Object parameters are self-documenting and easier to extend without breaking changes. - Methods returning arrays use `list` prefix (`listModes`, `listAvailableModels`, `listMessages`, `listMessagesForThread`) - `persistThreadSetting` → `setThreadSetting` - `resolveToolApprovalDecision` → `respondToToolApproval` (consistent with `respondToQuestion` / `respondToPlanApproval`) - `setPermissionCategory` → `setPermissionForCategory` - `setPermissionTool` → `setPermissionForTool` **Before:** ```typescript await harness.switchMode('build'); await harness.sendMessage('Hello', { images }); const modes = harness.getModes(); const models = await harness.getAvailableModels(); harness.resolveToolApprovalDecision('approve'); ``` **After:** ```typescript await harness.switchMode({ modeId: 'build' }); await harness.sendMessage({ content: 'Hello', images }); const modes = harness.listModes(); const models = await harness.listAvailableModels(); harness.respondToToolApproval({ decision: 'approve' }); ``` The `HarnessRequestContext` interface methods (`registerQuestion`, `registerPlanApproval`, `getSubagentModelId`) are also updated to use object parameters. - Added `task_write` and `task_check` as built-in Harness tools. These tools are automatically injected into every agent call, allowing agents to track structured task lists without manual tool registration. ([#13344](https://github.com/mastra-ai/mastra/pull/13344)) ```ts // Agents can call task_write to create/update a task list await tools['task_write'].execute({ tasks: [ { content: 'Fix authentication bug', status: 'in_progress', activeForm: 'Fixing authentication bug' }, { content: 'Add unit tests', status: 'pending', activeForm: 'Adding unit tests' }, ], }); // Agents can call task_check to verify all tasks are complete before finishing await tools['task_check'].execute({}); // Returns: { completed: 1, inProgress: 0, pending: 1, allDone: false, incomplete: [...] } ``` ### Patch Changes - Fixed duplicate Vercel AI Gateway configuration that could cause incorrect API key resolution. Removed a redundant override that conflicted with the upstream models.dev registry. ([#13291](https://github.com/mastra-ai/mastra/pull/13291)) - Fixed Vercel AI Gateway failing when using the model router string format (e.g. `vercel/openai/gpt-oss-120b`). The provider registry was overriding `createGateway`'s base URL with an incorrect value, causing API requests to hit the wrong endpoint. Removed the URL override so the AI SDK uses its own correct default. Closes #13280. ([#13287](https://github.com/mastra-ai/mastra/pull/13287)) - Fixed recursive schema warnings for processor graph entries by unrolling to a fixed depth of 3 levels, matching the existing rule group pattern ([#13292](https://github.com/mastra-ai/mastra/pull/13292)) - Fixed Observational Memory status not updating during conversations. The harness was missing streaming handlers for OM data chunks (status, observation start/end, buffering, activation), so the TUI never received real-time OM progress updates. Also added switchObserverModel and switchReflectorModel methods so changing OM models properly emits events to subscribers. ([#13330](https://github.com/mastra-ai/mastra/pull/13330)) - Fixed thread resuming in git worktrees. Previously, starting mastracode in a new worktree would resume a thread from another worktree of the same repo. Threads are now auto-tagged with the project path and filtered on resume so each worktree gets its own thread scope. ([#13343](https://github.com/mastra-ai/mastra/pull/13343)) - Fixed a crash where the Node.js process would terminate with an unhandled TypeError when an LLM stream encountered an error. The ReadableStreamDefaultController would throw "Controller is already closed" when chunks were enqueued after a downstream consumer cancelled or terminated the stream. All controller.enqueue(), controller.close(), and controller.error() calls now check if the controller is still open before attempting operations. (https://github.com/mastra-ai/mastra/issues/13107) ([#13142](https://github.com/mastra-ai/mastra/pull/13142)) - Added `suggestedContinuation` and `currentTask` fields to the in-memory storage adapter's Observational Memory activation result, aligning it with the persistent storage implementations. ([#13354](https://github.com/mastra-ai/mastra/pull/13354)) - Fixed provider-executed tools (e.g. Anthropic web_search) causing stream bail when called in parallel with regular tools. The tool-call-step now provides a fallback result for provider-executed tools whose output was not propagated, preventing the mapping step from misidentifying them as pending HITL interactions. Fixes #13125. ([#13126](https://github.com/mastra-ai/mastra/pull/13126)) - Updated dependencies [[`7184d87`](https://github.com/mastra-ai/mastra/commit/7184d87c9237d26862f500ccfd0c9f9eadd38ddf)]: - @mastra/schema-compat@1.1.2-alpha.0 ## 1.5.0 ### Minor Changes - Added `allowedPaths` option to `LocalFilesystem` for granting agents access to specific directories outside `basePath` without disabling containment. ([#13054](https://github.com/mastra-ai/mastra/pull/13054)) ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace', allowedPaths: ['/home/user/.config', '/home/user/documents'], }), }); ``` Allowed paths can be updated at runtime using `setAllowedPaths()`: ```typescript workspace.filesystem.setAllowedPaths(prev => [...prev, '/home/user/new-dir']); ``` This is the recommended approach for least-privilege access — agents can only reach the specific directories you allow, while containment stays enabled for everything else. - Added generic Harness class to @mastra/core for orchestrating agents with modes, state management, built-in tools (ask_user, submit_plan), subagent support, Observational Memory integration, model discovery, and permission-aware tool approval. The Harness provides a reusable foundation for building agent-powered applications with features like thread management, heartbeat monitoring, and event-driven architecture. ([#13245](https://github.com/mastra-ai/mastra/pull/13245)) - Added glob pattern support for workspace configuration. The `list_files` tool now accepts a `pattern` parameter for filtering files (e.g., `**/*.ts`, `src/**/*.test.ts`). `autoIndexPaths` accepts glob patterns like `./docs/**/*.md` to selectively index files for BM25 search. Skills paths support globs like `./**/skills` to discover skill directories at any depth, including dot-directories like `.agents/skills`. ([#13023](https://github.com/mastra-ai/mastra/pull/13023)) **`list_files` tool with pattern:** ```typescript // Agent can now use glob patterns to filter files const result = await workspace.tools.workspace_list_files({ path: '/', pattern: '**/*.test.ts', }); ``` **`autoIndexPaths` with globs:** ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './project' }), bm25: true, // Only index markdown files under ./docs autoIndexPaths: ['./docs/**/*.md'], }); ``` **Skills paths with globs:** ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './project' }), // Discover any directory named 'skills' within 4 levels of depth skills: ['./**/skills'], }); ``` Note: Skills glob discovery walks up to 4 directory levels deep from the glob's static prefix. Use more specific patterns like `./src/**/skills` to narrow the search scope for large workspaces. - Added direct skill path discovery — you can now pass a path directly to a skill directory or SKILL.md file in the workspace skills configuration (e.g., `skills: ['/path/to/my-skill']` or `skills: ['/path/to/my-skill/SKILL.md']`). Previously only parent directories were supported. Also improved error handling when a configured skills path is inaccessible (e.g., permission denied), logging a warning instead of breaking discovery for all skills. ([#13031](https://github.com/mastra-ai/mastra/pull/13031)) - Add optional `instruction` field to ObservationalMemory config types ([#13240](https://github.com/mastra-ai/mastra/pull/13240)) Adds `instruction?: string` to `ObservationalMemoryObservationConfig` and `ObservationalMemoryReflectionConfig` interfaces, allowing external consumers to pass custom instructions to observational memory. - Added typed workspace providers — `workspace.filesystem` and `workspace.sandbox` now return the concrete types you passed to the constructor, improving autocomplete and eliminating casts. ([#13021](https://github.com/mastra-ai/mastra/pull/13021)) When mounts are configured, `workspace.filesystem` returns a typed `CompositeFilesystem` with per-key narrowing via `mounts.get()`. **Before:** ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: '/tmp' }), sandbox: new E2BSandbox({ timeout: 60000 }), }); workspace.filesystem; // WorkspaceFilesystem | undefined workspace.sandbox; // WorkspaceSandbox | undefined ``` **After:** ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: '/tmp' }), sandbox: new E2BSandbox({ timeout: 60000 }), }); workspace.filesystem; // LocalFilesystem workspace.sandbox; // E2BSandbox // Mount-aware workspaces get typed per-key access: const ws = new Workspace({ mounts: { '/local': new LocalFilesystem({ basePath: '/tmp' }) }, }); ws.filesystem.mounts.get('/local'); // LocalFilesystem ``` - Added support for Vercel AI Gateway in the model router. You can now use `model: 'vercel/google/gemini-3-flash'` with agents and it will route through the official AI SDK gateway provider. ([#13149](https://github.com/mastra-ai/mastra/pull/13149)) - Added workspace and skill storage domains with full CRUD, versioning, and implementations across LibSQL, Postgres, and MongoDB. Added `editor.workspace` and `editor.skill` namespaces for managing workspace configurations and skill definitions through the editor. Agents stored in the editor can now reference workspaces (by ID or inline config) and skills, with full hydration to runtime `Workspace` instances during agent resolution. ([#13156](https://github.com/mastra-ai/mastra/pull/13156)) **Filesystem-native skill versioning (draft → publish model):** Skills are versioned as filesystem trees with content-addressable blob storage. The editing surface (live filesystem) is separated from the serving surface (versioned blob store), enabling a `draft → publish` workflow: - `editor.skill.publish(skillId, source, skillPath)` — Snapshots a skill directory from the filesystem into blob storage, creates a new version with a tree manifest, and sets `activeVersionId` - Version switching via `editor.skill.update({ id, activeVersionId })` — Points the skill to a previous version without re-publishing - Publishing a skill automatically invalidates cached agents that reference it, so they re-hydrate with the updated version on next access **Agent skill resolution strategies:** Agents can reference skills with different resolution strategies: - `strategy: 'latest'` — Resolves the skill's active version (honors `activeVersionId` for rollback) - `pin: ''` — Pins to a specific version, immune to publishes - `strategy: 'live'` — Reads directly from the live filesystem (no blob store) **Blob storage infrastructure:** - `BlobStore` abstract class for content-addressable storage keyed by SHA-256 hash - `InMemoryBlobStore` for testing - LibSQL, Postgres, and MongoDB implementations - `S3BlobStore` for storing blobs in S3 or S3-compatible storage (AWS, R2, MinIO, DO Spaces) - `BlobStoreProvider` interface and `MastraEditorConfig.blobStores` registry for pluggable blob storage - `VersionedSkillSource` and `CompositeVersionedSkillSource` for reading skill files from the blob store at runtime **New storage types:** - `StorageWorkspaceSnapshotType` and `StorageSkillSnapshotType` with corresponding input/output types - `StorageWorkspaceRef` for ID-based or inline workspace references on agents - `StorageSkillConfig` for per-agent skill overrides (`pin`, `strategy`, description, instructions) - `SkillVersionTree` and `SkillVersionTreeEntry` for tree manifests - `StorageBlobEntry` for content-addressable blob entries - `SKILL_BLOBS_SCHEMA` for the `mastra_skill_blobs` table **New editor namespaces:** - `editor.workspace` — CRUD for workspace configs, plus `hydrateSnapshotToWorkspace()` for resolving to runtime `Workspace` instances - `editor.skill` — CRUD for skill definitions, plus `publish()` for filesystem-to-blob snapshots **Provider registries:** - `MastraEditorConfig` accepts `filesystems`, `sandboxes`, and `blobStores` provider registries (keyed by provider ID) - Built-in `local` filesystem and sandbox providers are auto-registered - `editor.resolveBlobStore()` resolves from provider registry or falls back to the storage backend's blobs domain - Providers expose `id`, `name`, `description`, `configSchema` (JSON Schema for UI form rendering), and a factory method **Storage adapter support:** - LibSQL: Full `workspaces`, `skills`, and `blobs` domain implementations - Postgres: Full `workspaces`, `skills`, and `blobs` domain implementations - MongoDB: Full `workspaces`, `skills`, and `blobs` domain implementations - All three include `workspace`, `skills`, and `skillsFormat` fields on agent versions **Server endpoints:** - `GET/POST/PATCH/DELETE /stored/workspaces` — CRUD for stored workspaces - `GET/POST/PATCH/DELETE /stored/skills` — CRUD for stored skills - `POST /stored/skills/:id/publish` — Publish a skill from a filesystem source ```ts import { MastraEditor } from '@mastra/editor'; import { s3FilesystemProvider, s3BlobStoreProvider } from '@mastra/s3'; import { e2bSandboxProvider } from '@mastra/e2b'; const editor = new MastraEditor({ filesystems: { s3: s3FilesystemProvider }, sandboxes: { e2b: e2bSandboxProvider }, blobStores: { s3: s3BlobStoreProvider }, }); // Create a skill and publish it const skill = await editor.skill.create({ name: 'Code Review', description: 'Reviews code for best practices', instructions: 'Analyze the code and provide feedback...', }); await editor.skill.publish(skill.id, source, 'skills/code-review'); // Agents resolve skills by strategy await editor.agent.create({ name: 'Dev Assistant', model: { provider: 'openai', name: 'gpt-4' }, workspace: { type: 'id', workspaceId: workspace.id }, skills: { [skill.id]: { strategy: 'latest' } }, skillsFormat: 'xml', }); ``` - Added draft/publish version management for all editor primitives (agents, scorers, MCP clients, prompt blocks). ([#13061](https://github.com/mastra-ai/mastra/pull/13061)) **Status filtering on list endpoints** — All list endpoints now accept a `?status=draft|published|archived` query parameter to filter by entity status. Defaults to `published` to preserve backward compatibility. **Draft vs published resolution on get-by-id endpoints** — All get-by-id endpoints now accept `?status=draft` to resolve the entity with its latest (unpublished) version, or `?status=published` (default) to resolve with the active published version. **Edits no longer auto-publish** — When updating any primitive, a new version is created but `activeVersionId` is no longer automatically updated. Edits stay as drafts until explicitly published via the activate endpoint. **Full version management for all primitives** — Scorers, MCP clients, and prompt blocks now have the same version management API that agents have: list versions, create version snapshots, get specific versions, activate/publish, restore from a previous version, delete versions, and compare versions. **New prompt block CRUD routes** — Prompt blocks now have full server routes (`GET /stored/prompt-blocks`, `GET /stored/prompt-blocks/:id`, `POST`, `PATCH`, `DELETE`). **New version endpoints** — Each primitive now exposes 7 version management endpoints under `/stored/{type}/:id/versions` (list, create, get, activate, restore, delete, compare). ```ts // Fetch the published version (default behavior, backward compatible) const published = await fetch('/api/stored/scorers/my-scorer'); // Fetch the draft version for editing in the UI const draft = await fetch('/api/stored/scorers/my-scorer?status=draft'); // Publish a specific version await fetch('/api/stored/scorers/my-scorer/versions/abc123/activate', { method: 'POST' }); // Compare two versions const diff = await fetch('/api/stored/scorers/my-scorer/versions/compare?from=v1&to=v2'); ``` - Added `toModelOutput` support to the agent loop. Tool definitions can now include a `toModelOutput` function that transforms the raw tool result before it's sent to the model, while preserving the raw result in storage. This matches the AI SDK `toModelOutput` convention — the function receives the raw output directly and returns `{ type: 'text', value: string }` or `{ type: 'content', value: ContentPart[] }`. ([#13171](https://github.com/mastra-ai/mastra/pull/13171)) ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const weatherTool = createTool({ id: 'weather', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ city, temperature: 72, conditions: 'sunny', humidity: 45, raw_sensor_data: [0.12, 0.45, 0.78], }), // The model sees a concise summary instead of the full JSON toModelOutput: output => ({ type: 'text', value: `${output.city}: ${output.temperature}°F, ${output.conditions}`, }), }); ``` - Added `mastra_workspace_grep` workspace tool for regex-based content search across files. This complements the existing semantic search tool by providing direct pattern matching with support for case-insensitive search, file filtering by extension, context lines, and result limiting. ([#13010](https://github.com/mastra-ai/mastra/pull/13010)) The tool is automatically available when a workspace has a filesystem configured: ```typescript import { Workspace, WORKSPACE_TOOLS } from '@mastra/core/workspace'; import { LocalFilesystem } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './my-project' }), }); // The grep tool is auto-injected and available as: // WORKSPACE_TOOLS.SEARCH.GREP → 'mastra_workspace_grep' ``` - Removed `outputSchema` from workspace tools to return raw text instead of JSON, optimizing for token usage and LLM performance. Structured metadata that was previously returned in tool output is now emitted as `data-workspace-metadata` chunks via `writer.custom()`, keeping it available for UI consumption without passing it to the LLM. Tools are also extracted into individual files and can be imported directly (e.g. `import { readFileTool } from '@mastra/core/workspace'`). ([#13166](https://github.com/mastra-ai/mastra/pull/13166)) ### Patch Changes - dependencies updates: ([#13127](https://github.com/mastra-ai/mastra/pull/13127)) - Updated dependency [`hono@^4.11.9` ↗︎](https://www.npmjs.com/package/hono/v/4.11.9) (from `^4.11.3`, in `dependencies`) - dependencies updates: ([#13167](https://github.com/mastra-ai/mastra/pull/13167)) - Updated dependency [`lru-cache@^11.2.6` ↗︎](https://www.npmjs.com/package/lru-cache/v/11.2.6) (from `^11.2.2`, in `dependencies`) - Export `AnyWorkspace` type from `@mastra/core/workspace` for accepting any Workspace regardless of generic parameters. Updates Agent and Mastra to use `AnyWorkspace` so workspaces with typed mounts/sandbox (e.g. E2BSandbox, GCSFilesystem) are accepted without type errors. ([#13155](https://github.com/mastra-ai/mastra/pull/13155)) - Update provider registry and model documentation with latest models and providers ([`e37ef84`](https://github.com/mastra-ai/mastra/commit/e37ef8404043c94ca0c8e35ecdedb093b8087878)) - Fixed skill processor tools (skill-activate, skill-search, skill-read-reference, skill-read-script, skill-read-asset) being incorrectly suspended for approval when `requireToolApproval: true` is set. These internal tools now bypass the approval check and execute directly. ([#13160](https://github.com/mastra-ai/mastra/pull/13160)) - Fixed a bug where `requestContext` metadata was not propagated to child spans. When using `requestContextKeys`, only root spans were enriched with request context values — child spans (e.g. `agent_run` inside a workflow) were missing them. All spans in a trace are now correctly enriched. Fixes #12818. ([#12819](https://github.com/mastra-ai/mastra/pull/12819)) - Fixed semantic recall search in Mastra Studio returning no results when using non-default embedding dimensions (e.g., fastembed with 384-dim). The SemanticRecall processor now probes the embedder for its actual output dimension, ensuring the vector index name matches between write and read paths. Previously, the processor defaulted to a 1536-dim index name regardless of the actual embedder, causing a mismatch with the dimension-aware index name used by Studio's search. Fixes #13039 ([#13059](https://github.com/mastra-ai/mastra/pull/13059)) - Fixed `CompositeFilesystem` instructions: agents and tools no longer receive an incorrect claim that files written via workspace tools are accessible at sandbox paths. The instructions now accurately describe only the available mounted filesystems. ([#13221](https://github.com/mastra-ai/mastra/pull/13221)) - Fixed onChunk callback to receive raw Mastra chunks instead of AI SDK v5 converted chunks for tool results. Also added missing onChunk calls for tool-error chunks and tool-result chunks in mixed-error scenarios. ([#13243](https://github.com/mastra-ai/mastra/pull/13243)) - Fixed tool execution errors stopping the agentic loop. The agent now continues after tool errors, allowing the model to see the error and retry with corrected arguments. ([#13242](https://github.com/mastra-ai/mastra/pull/13242)) - Fixed conditional rules not being persisted for workflows, agents, and scorers when creating or updating agents in the CMS. Rules configured on these entities are now correctly saved to storage. ([#13044](https://github.com/mastra-ai/mastra/pull/13044)) - Added runtime `requestContext` forwarding to tool executions. ([#13094](https://github.com/mastra-ai/mastra/pull/13094)) Tools invoked within agentic workflow steps now receive the caller's `requestContext` — including authenticated API clients, feature flags, and user metadata set by middleware. Runtime `requestContext` is preferred over build-time context when both are available. **Why:** Previously, `requestContext` values were silently dropped in two places: (1) the workflow loop stream created a new empty `RequestContext` instead of forwarding the caller's, and (2) `createToolCallStep` didn't pass `requestContext` in tool options. This aligns both the agent generate/stream and agentic workflow paths with the agent network path, where `requestContext` was already forwarded correctly. **Before:** Tools received an empty `requestContext`, losing all values set by the workflow step. ```ts // requestContext with auth data set in workflow step requestContext.set('apiClient', authedClient); // tool receives empty RequestContext — apiClient is undefined ``` **After:** Pass `requestContext` via `MastraToolInvocationOptions` and tools receive it. ```ts // requestContext with auth data flows through to the tool requestContext.set('apiClient', authedClient); // tool receives the same RequestContext — apiClient is available ``` Fixes #13088 - Fixed tsc out-of-memory crash caused by step-schema.d.ts expanding to 50k lines. Added explicit type annotations to all exported Zod schema constants, reducing declaration output from 49,729 to ~500 lines without changing runtime behavior. ([#13229](https://github.com/mastra-ai/mastra/pull/13229)) - Fixed TypeScript type generation hanging or running out of memory when packages depend on @mastra/core tool types. Changed ZodLikeSchema from a nominal union type to structural typing, which prevents TypeScript from performing deep comparisons of zod v3/v4 type trees during generic inference. ([#13239](https://github.com/mastra-ai/mastra/pull/13239)) - Fixed tool execution errors being emitted as `tool-result` instead of `tool-error` in fullStream. Previously, when a tool's execute function threw an error, the error was caught and returned as a value, causing the stream to emit a `tool-result` chunk containing the error object. Now errors are properly propagated, so the stream emits `tool-error` chunks, allowing consumers (including the `@mastra/ai-sdk` conversion pipeline) to correctly distinguish between successful tool results and failed tool executions. Fixes #13123. ([#13147](https://github.com/mastra-ai/mastra/pull/13147)) - Fixed thread title not being generated for pre-created threads. When threads were created before starting a conversation (e.g., for URL routing or storing metadata), the title stayed as a placeholder because the title generation condition checked whether the thread existed rather than whether it had a title. Threads created without an explicit title now get an empty title instead of a placeholder, and title generation fires whenever a thread has no title. Resolves #13145. ([#13151](https://github.com/mastra-ai/mastra/pull/13151)) - Fixed `inputData` in `dowhile` and `dountil` loop condition functions to be properly typed as the step's output schema instead of `any`. This means you no longer need to manually cast `inputData` in your loop conditions — TypeScript will now correctly infer the type from your step's `outputSchema`. ([#12977](https://github.com/mastra-ai/mastra/pull/12977)) - Migrated MastraCode from the prototype harness to the generic CoreHarness from @mastra/core. The createMastraCode function is now fully configurable with optional parameters for modes, subagents, storage, tools, and more. Removed the deprecated prototype harness implementation. ([#13245](https://github.com/mastra-ai/mastra/pull/13245)) - Fixed the writer object being undefined in processOutputStream, allowing output processors to emit custom events to the stream during chunk processing. This enables use cases like streaming moderation results back to the client. ([#13056](https://github.com/mastra-ai/mastra/pull/13056)) - Fixed sub-agent tool approval resume flow. When a sub-agent tool required approval and was approved, the agent would restart from scratch instead of resuming, causing an infinite loop. The resume data is now correctly passed through for agent tools so they properly resume after approval. ([#13241](https://github.com/mastra-ai/mastra/pull/13241)) - Dataset schemas now appear in the Edit Dataset dialog. Previously the `inputSchema` and `groundTruthSchema` fields were not passed to the dialog, so editing a dataset always showed empty schemas. ([#13175](https://github.com/mastra-ai/mastra/pull/13175)) Schema edits in the JSON editor no longer cause the cursor to jump to the top of the field. Typing `{"type": "object"}` in the schema editor now behaves like a normal text input instead of resetting on every keystroke. Validation errors are now surfaced when updating a dataset schema that conflicts with existing items. For example, adding a `required: ["name"]` constraint when existing items lack a `name` field now shows "2 existing item(s) fail validation" in the dialog instead of silently dropping the error. Disabling a dataset schema from the Studio UI now correctly clears it. Previously the server converted `null` (disable) to `undefined` (no change), so the old schema persisted and validation continued. Workflow schemas fetched via `client.getWorkflow().getSchema()` are now correctly parsed. The server serializes schemas with `superjson`, but the client was using plain `JSON.parse`, yielding a `{json: {...}}` wrapper instead of the actual JSON Schema object. - Fixed network mode messages missing metadata for filtering. All internal network messages (sub-agent results, tool execution results, workflow results) now include `metadata.mode: 'network'` in their content metadata, making it possible to filter them from user-facing messages without parsing JSON content. Previously, consumers had to parse the JSON body of each message to check for `isNetwork: true` — now they can simply check `message.content.metadata.mode === 'network'`. Fixes #13106. ([#13144](https://github.com/mastra-ai/mastra/pull/13144)) - Fixed sub-agent memory context pollution that caused 'Exhausted all fallback models' errors when using Observational Memory with sub-agents. The parent agent's memory context is now preserved across sub-agent tool execution. ([#13051](https://github.com/mastra-ai/mastra/pull/13051)) - CMS draft support with status badges for agents. ([#13194](https://github.com/mastra-ai/mastra/pull/13194)) - Agent list now resolves the latest (draft) version for each stored agent, showing current edits rather than the last published state. - Added `hasDraft` and `activeVersionId` fields to the agent list API response. - Agent list badges: "Published" (green) when a published version exists, "Draft" (colored when unpublished changes exist, grayed out otherwise). - Added `resolvedVersionId` to all `StorageResolved*Type` types so the server can detect whether the latest version differs from the active version. - Added `status` option to `GetByIdOptions` to allow resolving draft vs published versions through the editor layer. - Fixed editor cache not being cleared on version activate, restore, and delete — all four versioned domains (agents, scorers, prompt-blocks, mcp-clients) now clear the cache after version mutations. - Added `ALTER TABLE` migration for `mastra_agent_versions` in libsql and pg to add newer columns (`mcpClients`, `requestContextSchema`, `workspace`, `skills`, `skillsFormat`). - Added scorer version management and CMS draft/publish flow for scorers. ([#13194](https://github.com/mastra-ai/mastra/pull/13194)) - Added scorer version methods to the client SDK: `listVersions`, `createVersion`, `getVersion`, `activateVersion`, `restoreVersion`, `deleteVersion`, `compareVersions`. - Added `ScorerVersionCombobox` for navigating scorer versions with Published/Draft labels. - Scorer edit page now supports Save (draft) and Publish workflows with an "Unpublished changes" indicator. - Storage list methods for agents and scorers no longer default to filtering only published entities, allowing drafts to appear in the playground. - Fixed CompositeAuth losing public and protected route configurations from underlying auth providers. Routes marked as public or protected now work correctly when deployed to Mastra Cloud. ([#13086](https://github.com/mastra-ai/mastra/pull/13086)) - Trimmed the agent experiment result `output` to only persist relevant fields instead of the entire `FullOutput` blob. The stored output now contains: `text`, `object`, `toolCalls`, `toolResults`, `sources`, `files`, `usage`, `reasoningText`, `traceId`, and `error`. ([#13158](https://github.com/mastra-ai/mastra/pull/13158)) Dropped fields like `steps`, `response`, `messages`, `rememberedMessages`, `request`, `providerMetadata`, `warnings`, `scoringData`, `suspendPayload`, and other provider/debugging internals that were duplicated elsewhere or not useful for experiment evaluation. - Fixed `.branch()` condition receiving `undefined` inputData when resuming a suspended nested workflow after `.map()`. ([#13055](https://github.com/mastra-ai/mastra/pull/13055)) Previously, when a workflow used `.map()` followed by `.branch()` and a nested workflow inside the branch called `suspend()`, resuming would fail with `TypeError: Cannot read properties of undefined` because the branch conditions were unnecessarily re-evaluated with stale data. Resume now skips condition re-evaluation for `.branch()` entries and goes directly to the correct suspended branch, matching the existing behavior for `.parallel()` entries. Fixes #12982 - Updated dependencies [[`1415bcd`](https://github.com/mastra-ai/mastra/commit/1415bcd894baba03e07640b3b1986037db49559d)]: - @mastra/schema-compat@1.1.1 ## 1.5.0-alpha.1 ### Patch Changes - Updated dependencies [[`1415bcd`](https://github.com/mastra-ai/mastra/commit/1415bcd894baba03e07640b3b1986037db49559d)]: - @mastra/schema-compat@1.1.1-alpha.0 ## 1.5.0-alpha.0 ### Minor Changes - Added `allowedPaths` option to `LocalFilesystem` for granting agents access to specific directories outside `basePath` without disabling containment. ([#13054](https://github.com/mastra-ai/mastra/pull/13054)) ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace', allowedPaths: ['/home/user/.config', '/home/user/documents'], }), }); ``` Allowed paths can be updated at runtime using `setAllowedPaths()`: ```typescript workspace.filesystem.setAllowedPaths(prev => [...prev, '/home/user/new-dir']); ``` This is the recommended approach for least-privilege access — agents can only reach the specific directories you allow, while containment stays enabled for everything else. - Added generic Harness class to @mastra/core for orchestrating agents with modes, state management, built-in tools (ask_user, submit_plan), subagent support, Observational Memory integration, model discovery, and permission-aware tool approval. The Harness provides a reusable foundation for building agent-powered applications with features like thread management, heartbeat monitoring, and event-driven architecture. ([#13245](https://github.com/mastra-ai/mastra/pull/13245)) - Added glob pattern support for workspace configuration. The `list_files` tool now accepts a `pattern` parameter for filtering files (e.g., `**/*.ts`, `src/**/*.test.ts`). `autoIndexPaths` accepts glob patterns like `./docs/**/*.md` to selectively index files for BM25 search. Skills paths support globs like `./**/skills` to discover skill directories at any depth, including dot-directories like `.agents/skills`. ([#13023](https://github.com/mastra-ai/mastra/pull/13023)) **`list_files` tool with pattern:** ```typescript // Agent can now use glob patterns to filter files const result = await workspace.tools.workspace_list_files({ path: '/', pattern: '**/*.test.ts', }); ``` **`autoIndexPaths` with globs:** ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './project' }), bm25: true, // Only index markdown files under ./docs autoIndexPaths: ['./docs/**/*.md'], }); ``` **Skills paths with globs:** ```typescript const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './project' }), // Discover any directory named 'skills' within 4 levels of depth skills: ['./**/skills'], }); ``` Note: Skills glob discovery walks up to 4 directory levels deep from the glob's static prefix. Use more specific patterns like `./src/**/skills` to narrow the search scope for large workspaces. - Added direct skill path discovery — you can now pass a path directly to a skill directory or SKILL.md file in the workspace skills configuration (e.g., `skills: ['/path/to/my-skill']` or `skills: ['/path/to/my-skill/SKILL.md']`). Previously only parent directories were supported. Also improved error handling when a configured skills path is inaccessible (e.g., permission denied), logging a warning instead of breaking discovery for all skills. ([#13031](https://github.com/mastra-ai/mastra/pull/13031)) - Add optional `instruction` field to ObservationalMemory config types ([#13240](https://github.com/mastra-ai/mastra/pull/13240)) Adds `instruction?: string` to `ObservationalMemoryObservationConfig` and `ObservationalMemoryReflectionConfig` interfaces, allowing external consumers to pass custom instructions to observational memory. - Added typed workspace providers — `workspace.filesystem` and `workspace.sandbox` now return the concrete types you passed to the constructor, improving autocomplete and eliminating casts. ([#13021](https://github.com/mastra-ai/mastra/pull/13021)) When mounts are configured, `workspace.filesystem` returns a typed `CompositeFilesystem` with per-key narrowing via `mounts.get()`. **Before:** ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: '/tmp' }), sandbox: new E2BSandbox({ timeout: 60000 }), }); workspace.filesystem; // WorkspaceFilesystem | undefined workspace.sandbox; // WorkspaceSandbox | undefined ``` **After:** ```ts const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: '/tmp' }), sandbox: new E2BSandbox({ timeout: 60000 }), }); workspace.filesystem; // LocalFilesystem workspace.sandbox; // E2BSandbox // Mount-aware workspaces get typed per-key access: const ws = new Workspace({ mounts: { '/local': new LocalFilesystem({ basePath: '/tmp' }) }, }); ws.filesystem.mounts.get('/local'); // LocalFilesystem ``` - Added support for Vercel AI Gateway in the model router. You can now use `model: 'vercel/google/gemini-3-flash'` with agents and it will route through the official AI SDK gateway provider. ([#13149](https://github.com/mastra-ai/mastra/pull/13149)) - Added workspace and skill storage domains with full CRUD, versioning, and implementations across LibSQL, Postgres, and MongoDB. Added `editor.workspace` and `editor.skill` namespaces for managing workspace configurations and skill definitions through the editor. Agents stored in the editor can now reference workspaces (by ID or inline config) and skills, with full hydration to runtime `Workspace` instances during agent resolution. ([#13156](https://github.com/mastra-ai/mastra/pull/13156)) **Filesystem-native skill versioning (draft → publish model):** Skills are versioned as filesystem trees with content-addressable blob storage. The editing surface (live filesystem) is separated from the serving surface (versioned blob store), enabling a `draft → publish` workflow: - `editor.skill.publish(skillId, source, skillPath)` — Snapshots a skill directory from the filesystem into blob storage, creates a new version with a tree manifest, and sets `activeVersionId` - Version switching via `editor.skill.update({ id, activeVersionId })` — Points the skill to a previous version without re-publishing - Publishing a skill automatically invalidates cached agents that reference it, so they re-hydrate with the updated version on next access **Agent skill resolution strategies:** Agents can reference skills with different resolution strategies: - `strategy: 'latest'` — Resolves the skill's active version (honors `activeVersionId` for rollback) - `pin: ''` — Pins to a specific version, immune to publishes - `strategy: 'live'` — Reads directly from the live filesystem (no blob store) **Blob storage infrastructure:** - `BlobStore` abstract class for content-addressable storage keyed by SHA-256 hash - `InMemoryBlobStore` for testing - LibSQL, Postgres, and MongoDB implementations - `S3BlobStore` for storing blobs in S3 or S3-compatible storage (AWS, R2, MinIO, DO Spaces) - `BlobStoreProvider` interface and `MastraEditorConfig.blobStores` registry for pluggable blob storage - `VersionedSkillSource` and `CompositeVersionedSkillSource` for reading skill files from the blob store at runtime **New storage types:** - `StorageWorkspaceSnapshotType` and `StorageSkillSnapshotType` with corresponding input/output types - `StorageWorkspaceRef` for ID-based or inline workspace references on agents - `StorageSkillConfig` for per-agent skill overrides (`pin`, `strategy`, description, instructions) - `SkillVersionTree` and `SkillVersionTreeEntry` for tree manifests - `StorageBlobEntry` for content-addressable blob entries - `SKILL_BLOBS_SCHEMA` for the `mastra_skill_blobs` table **New editor namespaces:** - `editor.workspace` — CRUD for workspace configs, plus `hydrateSnapshotToWorkspace()` for resolving to runtime `Workspace` instances - `editor.skill` — CRUD for skill definitions, plus `publish()` for filesystem-to-blob snapshots **Provider registries:** - `MastraEditorConfig` accepts `filesystems`, `sandboxes`, and `blobStores` provider registries (keyed by provider ID) - Built-in `local` filesystem and sandbox providers are auto-registered - `editor.resolveBlobStore()` resolves from provider registry or falls back to the storage backend's blobs domain - Providers expose `id`, `name`, `description`, `configSchema` (JSON Schema for UI form rendering), and a factory method **Storage adapter support:** - LibSQL: Full `workspaces`, `skills`, and `blobs` domain implementations - Postgres: Full `workspaces`, `skills`, and `blobs` domain implementations - MongoDB: Full `workspaces`, `skills`, and `blobs` domain implementations - All three include `workspace`, `skills`, and `skillsFormat` fields on agent versions **Server endpoints:** - `GET/POST/PATCH/DELETE /stored/workspaces` — CRUD for stored workspaces - `GET/POST/PATCH/DELETE /stored/skills` — CRUD for stored skills - `POST /stored/skills/:id/publish` — Publish a skill from a filesystem source ```ts import { MastraEditor } from '@mastra/editor'; import { s3FilesystemProvider, s3BlobStoreProvider } from '@mastra/s3'; import { e2bSandboxProvider } from '@mastra/e2b'; const editor = new MastraEditor({ filesystems: { s3: s3FilesystemProvider }, sandboxes: { e2b: e2bSandboxProvider }, blobStores: { s3: s3BlobStoreProvider }, }); // Create a skill and publish it const skill = await editor.skill.create({ name: 'Code Review', description: 'Reviews code for best practices', instructions: 'Analyze the code and provide feedback...', }); await editor.skill.publish(skill.id, source, 'skills/code-review'); // Agents resolve skills by strategy await editor.agent.create({ name: 'Dev Assistant', model: { provider: 'openai', name: 'gpt-4' }, workspace: { type: 'id', workspaceId: workspace.id }, skills: { [skill.id]: { strategy: 'latest' } }, skillsFormat: 'xml', }); ``` - Added draft/publish version management for all editor primitives (agents, scorers, MCP clients, prompt blocks). ([#13061](https://github.com/mastra-ai/mastra/pull/13061)) **Status filtering on list endpoints** — All list endpoints now accept a `?status=draft|published|archived` query parameter to filter by entity status. Defaults to `published` to preserve backward compatibility. **Draft vs published resolution on get-by-id endpoints** — All get-by-id endpoints now accept `?status=draft` to resolve the entity with its latest (unpublished) version, or `?status=published` (default) to resolve with the active published version. **Edits no longer auto-publish** — When updating any primitive, a new version is created but `activeVersionId` is no longer automatically updated. Edits stay as drafts until explicitly published via the activate endpoint. **Full version management for all primitives** — Scorers, MCP clients, and prompt blocks now have the same version management API that agents have: list versions, create version snapshots, get specific versions, activate/publish, restore from a previous version, delete versions, and compare versions. **New prompt block CRUD routes** — Prompt blocks now have full server routes (`GET /stored/prompt-blocks`, `GET /stored/prompt-blocks/:id`, `POST`, `PATCH`, `DELETE`). **New version endpoints** — Each primitive now exposes 7 version management endpoints under `/stored/{type}/:id/versions` (list, create, get, activate, restore, delete, compare). ```ts // Fetch the published version (default behavior, backward compatible) const published = await fetch('/api/stored/scorers/my-scorer'); // Fetch the draft version for editing in the UI const draft = await fetch('/api/stored/scorers/my-scorer?status=draft'); // Publish a specific version await fetch('/api/stored/scorers/my-scorer/versions/abc123/activate', { method: 'POST' }); // Compare two versions const diff = await fetch('/api/stored/scorers/my-scorer/versions/compare?from=v1&to=v2'); ``` - Added `toModelOutput` support to the agent loop. Tool definitions can now include a `toModelOutput` function that transforms the raw tool result before it's sent to the model, while preserving the raw result in storage. This matches the AI SDK `toModelOutput` convention — the function receives the raw output directly and returns `{ type: 'text', value: string }` or `{ type: 'content', value: ContentPart[] }`. ([#13171](https://github.com/mastra-ai/mastra/pull/13171)) ```ts import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const weatherTool = createTool({ id: 'weather', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ city, temperature: 72, conditions: 'sunny', humidity: 45, raw_sensor_data: [0.12, 0.45, 0.78], }), // The model sees a concise summary instead of the full JSON toModelOutput: output => ({ type: 'text', value: `${output.city}: ${output.temperature}°F, ${output.conditions}`, }), }); ``` - Added `mastra_workspace_grep` workspace tool for regex-based content search across files. This complements the existing semantic search tool by providing direct pattern matching with support for case-insensitive search, file filtering by extension, context lines, and result limiting. ([#13010](https://github.com/mastra-ai/mastra/pull/13010)) The tool is automatically available when a workspace has a filesystem configured: ```typescript import { Workspace, WORKSPACE_TOOLS } from '@mastra/core/workspace'; import { LocalFilesystem } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './my-project' }), }); // The grep tool is auto-injected and available as: // WORKSPACE_TOOLS.SEARCH.GREP → 'mastra_workspace_grep' ``` - Removed `outputSchema` from workspace tools to return raw text instead of JSON, optimizing for token usage and LLM performance. Structured metadata that was previously returned in tool output is now emitted as `data-workspace-metadata` chunks via `writer.custom()`, keeping it available for UI consumption without passing it to the LLM. Tools are also extracted into individual files and can be imported directly (e.g. `import { readFileTool } from '@mastra/core/workspace'`). ([#13166](https://github.com/mastra-ai/mastra/pull/13166)) ### Patch Changes - dependencies updates: ([#13127](https://github.com/mastra-ai/mastra/pull/13127)) - Updated dependency [`hono@^4.11.9` ↗︎](https://www.npmjs.com/package/hono/v/4.11.9) (from `^4.11.3`, in `dependencies`) - dependencies updates: ([#13167](https://github.com/mastra-ai/mastra/pull/13167)) - Updated dependency [`lru-cache@^11.2.6` ↗︎](https://www.npmjs.com/package/lru-cache/v/11.2.6) (from `^11.2.2`, in `dependencies`) - Export `AnyWorkspace` type from `@mastra/core/workspace` for accepting any Workspace regardless of generic parameters. Updates Agent and Mastra to use `AnyWorkspace` so workspaces with typed mounts/sandbox (e.g. E2BSandbox, GCSFilesystem) are accepted without type errors. ([#13155](https://github.com/mastra-ai/mastra/pull/13155)) - Update provider registry and model documentation with latest models and providers ([`e37ef84`](https://github.com/mastra-ai/mastra/commit/e37ef8404043c94ca0c8e35ecdedb093b8087878)) - Fixed skill processor tools (skill-activate, skill-search, skill-read-reference, skill-read-script, skill-read-asset) being incorrectly suspended for approval when `requireToolApproval: true` is set. These internal tools now bypass the approval check and execute directly. ([#13160](https://github.com/mastra-ai/mastra/pull/13160)) - Fixed a bug where `requestContext` metadata was not propagated to child spans. When using `requestContextKeys`, only root spans were enriched with request context values — child spans (e.g. `agent_run` inside a workflow) were missing them. All spans in a trace are now correctly enriched. Fixes #12818. ([#12819](https://github.com/mastra-ai/mastra/pull/12819)) - Fixed semantic recall search in Mastra Studio returning no results when using non-default embedding dimensions (e.g., fastembed with 384-dim). The SemanticRecall processor now probes the embedder for its actual output dimension, ensuring the vector index name matches between write and read paths. Previously, the processor defaulted to a 1536-dim index name regardless of the actual embedder, causing a mismatch with the dimension-aware index name used by Studio's search. Fixes #13039 ([#13059](https://github.com/mastra-ai/mastra/pull/13059)) - Fixed `CompositeFilesystem` instructions: agents and tools no longer receive an incorrect claim that files written via workspace tools are accessible at sandbox paths. The instructions now accurately describe only the available mounted filesystems. ([#13221](https://github.com/mastra-ai/mastra/pull/13221)) - Fixed onChunk callback to receive raw Mastra chunks instead of AI SDK v5 converted chunks for tool results. Also added missing onChunk calls for tool-error chunks and tool-result chunks in mixed-error scenarios. ([#13243](https://github.com/mastra-ai/mastra/pull/13243)) - Fixed tool execution errors stopping the agentic loop. The agent now continues after tool errors, allowing the model to see the error and retry with corrected arguments. ([#13242](https://github.com/mastra-ai/mastra/pull/13242)) - Fixed conditional rules not being persisted for workflows, agents, and scorers when creating or updating agents in the CMS. Rules configured on these entities are now correctly saved to storage. ([#13044](https://github.com/mastra-ai/mastra/pull/13044)) - Added runtime `requestContext` forwarding to tool executions. ([#13094](https://github.com/mastra-ai/mastra/pull/13094)) Tools invoked within agentic workflow steps now receive the caller's `requestContext` — including authenticated API clients, feature flags, and user metadata set by middleware. Runtime `requestContext` is preferred over build-time context when both are available. **Why:** Previously, `requestContext` values were silently dropped in two places: (1) the workflow loop stream created a new empty `RequestContext` instead of forwarding the caller's, and (2) `createToolCallStep` didn't pass `requestContext` in tool options. This aligns both the agent generate/stream and agentic workflow paths with the agent network path, where `requestContext` was already forwarded correctly. **Before:** Tools received an empty `requestContext`, losing all values set by the workflow step. ```ts // requestContext with auth data set in workflow step requestContext.set('apiClient', authedClient); // tool receives empty RequestContext — apiClient is undefined ``` **After:** Pass `requestContext` via `MastraToolInvocationOptions` and tools receive it. ```ts // requestContext with auth data flows through to the tool requestContext.set('apiClient', authedClient); // tool receives the same RequestContext — apiClient is available ``` Fixes #13088 - Fixed tsc out-of-memory crash caused by step-schema.d.ts expanding to 50k lines. Added explicit type annotations to all exported Zod schema constants, reducing declaration output from 49,729 to ~500 lines without changing runtime behavior. ([#13229](https://github.com/mastra-ai/mastra/pull/13229)) - Fixed TypeScript type generation hanging or running out of memory when packages depend on @mastra/core tool types. Changed ZodLikeSchema from a nominal union type to structural typing, which prevents TypeScript from performing deep comparisons of zod v3/v4 type trees during generic inference. ([#13239](https://github.com/mastra-ai/mastra/pull/13239)) - Fixed tool execution errors being emitted as `tool-result` instead of `tool-error` in fullStream. Previously, when a tool's execute function threw an error, the error was caught and returned as a value, causing the stream to emit a `tool-result` chunk containing the error object. Now errors are properly propagated, so the stream emits `tool-error` chunks, allowing consumers (including the `@mastra/ai-sdk` conversion pipeline) to correctly distinguish between successful tool results and failed tool executions. Fixes #13123. ([#13147](https://github.com/mastra-ai/mastra/pull/13147)) - Fixed thread title not being generated for pre-created threads. When threads were created before starting a conversation (e.g., for URL routing or storing metadata), the title stayed as a placeholder because the title generation condition checked whether the thread existed rather than whether it had a title. Threads created without an explicit title now get an empty title instead of a placeholder, and title generation fires whenever a thread has no title. Resolves #13145. ([#13151](https://github.com/mastra-ai/mastra/pull/13151)) - Fixed `inputData` in `dowhile` and `dountil` loop condition functions to be properly typed as the step's output schema instead of `any`. This means you no longer need to manually cast `inputData` in your loop conditions — TypeScript will now correctly infer the type from your step's `outputSchema`. ([#12977](https://github.com/mastra-ai/mastra/pull/12977)) - Migrated MastraCode from the prototype harness to the generic CoreHarness from @mastra/core. The createMastraCode function is now fully configurable with optional parameters for modes, subagents, storage, tools, and more. Removed the deprecated prototype harness implementation. ([#13245](https://github.com/mastra-ai/mastra/pull/13245)) - Fixed the writer object being undefined in processOutputStream, allowing output processors to emit custom events to the stream during chunk processing. This enables use cases like streaming moderation results back to the client. ([#13056](https://github.com/mastra-ai/mastra/pull/13056)) - Fixed sub-agent tool approval resume flow. When a sub-agent tool required approval and was approved, the agent would restart from scratch instead of resuming, causing an infinite loop. The resume data is now correctly passed through for agent tools so they properly resume after approval. ([#13241](https://github.com/mastra-ai/mastra/pull/13241)) - Dataset schemas now appear in the Edit Dataset dialog. Previously the `inputSchema` and `groundTruthSchema` fields were not passed to the dialog, so editing a dataset always showed empty schemas. ([#13175](https://github.com/mastra-ai/mastra/pull/13175)) Schema edits in the JSON editor no longer cause the cursor to jump to the top of the field. Typing `{"type": "object"}` in the schema editor now behaves like a normal text input instead of resetting on every keystroke. Validation errors are now surfaced when updating a dataset schema that conflicts with existing items. For example, adding a `required: ["name"]` constraint when existing items lack a `name` field now shows "2 existing item(s) fail validation" in the dialog instead of silently dropping the error. Disabling a dataset schema from the Studio UI now correctly clears it. Previously the server converted `null` (disable) to `undefined` (no change), so the old schema persisted and validation continued. Workflow schemas fetched via `client.getWorkflow().getSchema()` are now correctly parsed. The server serializes schemas with `superjson`, but the client was using plain `JSON.parse`, yielding a `{json: {...}}` wrapper instead of the actual JSON Schema object. - Fixed network mode messages missing metadata for filtering. All internal network messages (sub-agent results, tool execution results, workflow results) now include `metadata.mode: 'network'` in their content metadata, making it possible to filter them from user-facing messages without parsing JSON content. Previously, consumers had to parse the JSON body of each message to check for `isNetwork: true` — now they can simply check `message.content.metadata.mode === 'network'`. Fixes #13106. ([#13144](https://github.com/mastra-ai/mastra/pull/13144)) - Fixed sub-agent memory context pollution that caused 'Exhausted all fallback models' errors when using Observational Memory with sub-agents. The parent agent's memory context is now preserved across sub-agent tool execution. ([#13051](https://github.com/mastra-ai/mastra/pull/13051)) - CMS draft support with status badges for agents. ([#13194](https://github.com/mastra-ai/mastra/pull/13194)) - Agent list now resolves the latest (draft) version for each stored agent, showing current edits rather than the last published state. - Added `hasDraft` and `activeVersionId` fields to the agent list API response. - Agent list badges: "Published" (green) when a published version exists, "Draft" (colored when unpublished changes exist, grayed out otherwise). - Added `resolvedVersionId` to all `StorageResolved*Type` types so the server can detect whether the latest version differs from the active version. - Added `status` option to `GetByIdOptions` to allow resolving draft vs published versions through the editor layer. - Fixed editor cache not being cleared on version activate, restore, and delete — all four versioned domains (agents, scorers, prompt-blocks, mcp-clients) now clear the cache after version mutations. - Added `ALTER TABLE` migration for `mastra_agent_versions` in libsql and pg to add newer columns (`mcpClients`, `requestContextSchema`, `workspace`, `skills`, `skillsFormat`). - Added scorer version management and CMS draft/publish flow for scorers. ([#13194](https://github.com/mastra-ai/mastra/pull/13194)) - Added scorer version methods to the client SDK: `listVersions`, `createVersion`, `getVersion`, `activateVersion`, `restoreVersion`, `deleteVersion`, `compareVersions`. - Added `ScorerVersionCombobox` for navigating scorer versions with Published/Draft labels. - Scorer edit page now supports Save (draft) and Publish workflows with an "Unpublished changes" indicator. - Storage list methods for agents and scorers no longer default to filtering only published entities, allowing drafts to appear in the playground. - Fixed CompositeAuth losing public and protected route configurations from underlying auth providers. Routes marked as public or protected now work correctly when deployed to Mastra Cloud. ([#13086](https://github.com/mastra-ai/mastra/pull/13086)) - Trimmed the agent experiment result `output` to only persist relevant fields instead of the entire `FullOutput` blob. The stored output now contains: `text`, `object`, `toolCalls`, `toolResults`, `sources`, `files`, `usage`, `reasoningText`, `traceId`, and `error`. ([#13158](https://github.com/mastra-ai/mastra/pull/13158)) Dropped fields like `steps`, `response`, `messages`, `rememberedMessages`, `request`, `providerMetadata`, `warnings`, `scoringData`, `suspendPayload`, and other provider/debugging internals that were duplicated elsewhere or not useful for experiment evaluation. - Fixed `.branch()` condition receiving `undefined` inputData when resuming a suspended nested workflow after `.map()`. ([#13055](https://github.com/mastra-ai/mastra/pull/13055)) Previously, when a workflow used `.map()` followed by `.branch()` and a nested workflow inside the branch called `suspend()`, resuming would fail with `TypeError: Cannot read properties of undefined` because the branch conditions were unnecessarily re-evaluated with stale data. Resume now skips condition re-evaluation for `.branch()` entries and goes directly to the correct suspended branch, matching the existing behavior for `.parallel()` entries. Fixes #12982 ## 1.4.0 ### Minor Changes - Added Datasets and Experiments to core. Datasets let you store and version collections of test inputs with JSON Schema validation. Experiments let you run AI outputs against dataset items with configurable scorers to track quality over time. ([#12747](https://github.com/mastra-ai/mastra/pull/12747)) **New exports from `@mastra/core/datasets`:** - `DatasetsManager` — orchestrates dataset CRUD, item versioning (SCD-2), and experiment execution - `Dataset` — single-dataset handle for adding items and running experiments **New storage domains:** - `DatasetsStorage` — abstract base class for dataset persistence (datasets, items, versions) - `ExperimentsStorage` — abstract base class for experiment lifecycle and result tracking **Example:** ```ts import { Mastra } from '@mastra/core'; const mastra = new Mastra({/* ... */}); const dataset = await mastra.datasets.create({ name: 'my-eval-set' }); await dataset.addItems([{ input: { query: 'What is 2+2?' }, groundTruth: { answer: '4' } }]); const result = await dataset.runExperiment({ targetType: 'agent', targetId: 'my-agent', scorerIds: ['accuracy'], }); ``` - Fix LocalFilesystem.resolvePath handling of absolute paths and improve filesystem info. ([#12971](https://github.com/mastra-ai/mastra/pull/12971)) - Fix absolute path resolution: paths were incorrectly stripped of leading slashes and resolved relative to basePath, causing PermissionError for valid paths (e.g. skills processor accessing project-local skills directories). - Make `FilesystemInfo` generic (`FilesystemInfo`) so providers can type their metadata. - Move provider-specific fields (`basePath`, `contained`) to metadata in LocalFilesystem.getInfo(). - Update LocalFilesystem.getInstructions() for uncontained filesystems to warn agents against listing /. - Use FilesystemInfo type in WorkspaceInfo instead of duplicated inline shape. - Add `workflow-step-progress` stream event for foreach workflow steps. Each iteration emits a progress event with `completedCount`, `totalCount`, `currentIndex`, `iterationStatus` (`success` | `failed` | `suspended`), and optional `iterationOutput`. Both the default and evented execution engines emit these events. ([#12838](https://github.com/mastra-ai/mastra/pull/12838)) The Mastra Studio UI now renders a progress bar with an N/total counter on foreach nodes, updating in real time as iterations complete: ```ts // Consuming progress events from the workflow stream const run = workflow.createRun(); const result = await run.start({ inputData }); const stream = result.stream; for await (const chunk of stream) { if (chunk.type === 'workflow-step-progress') { console.log(`${chunk.payload.completedCount}/${chunk.payload.totalCount} - ${chunk.payload.iterationStatus}`); } } ``` `@mastra/react`: The `mapWorkflowStreamChunkToWatchResult` reducer now accumulates `foreachProgress` from `workflow-step-progress` events into step state, making progress data available to React consumers via the existing workflow watch hooks. - Added observational memory configuration support for stored agents. When creating or editing a stored agent in the playground, you can now enable observational memory and configure its settings including model provider/name, scope (thread or resource), share token budget, and detailed observer/reflector parameters like token limits, buffer settings, and blocking thresholds. The configuration is serialized as part of the agent's memory config and round-trips through storage. ([#12962](https://github.com/mastra-ai/mastra/pull/12962)) **Example usage in the playground:** Enable the Observational Memory toggle in the Memory section, then configure: - Top-level model (provider + model) used by both observer and reflector - Scope: `thread` (per-conversation) or `resource` (shared across threads) - Expand **Observer** or **Reflector** sections to override models and tune token budgets **Programmatic usage via client SDK:** ```ts await client.createStoredAgent({ name: 'My Agent', // ...other config memory: { observationalMemory: true, // enable with defaults options: { lastMessages: 40 }, }, }); // Or with custom configuration: await client.createStoredAgent({ name: 'My Agent', memory: { observationalMemory: { model: 'google/gemini-2.5-flash', scope: 'resource', shareTokenBudget: true, observation: { messageTokens: 50000 }, reflection: { observationTokens: 60000 }, }, options: { lastMessages: 40 }, }, }); ``` **Programmatic usage via editor:** ```ts await editor.agent.create({ name: 'My Agent', // ...other config memory: { observationalMemory: true, // enable with defaults options: { lastMessages: 40 }, }, }); // Or with custom configuration: await editor.agent.create({ name: 'My Agent', memory: { observationalMemory: { model: 'google/gemini-2.5-flash', scope: 'resource', shareTokenBudget: true, observation: { messageTokens: 50000 }, reflection: { observationTokens: 60000 }, }, options: { lastMessages: 40 }, }, }); ``` - Added MCP client storage domain and ToolProvider interface for integrating external tool catalogs with stored agents. ([#12974](https://github.com/mastra-ai/mastra/pull/12974)) **MCP Client Storage** New storage domain for persisting MCP client configurations with CRUD operations. Each MCP client can contain multiple servers with independent tool selection: ```ts // Store an MCP client with multiple servers await storage.mcpClients.create({ id: 'my-mcp', name: 'My MCP Client', servers: { 'github-server': { url: 'https://mcp.github.com/sse' }, 'slack-server': { url: 'https://mcp.slack.com/sse' }, }, }); ``` LibSQL, PostgreSQL, and MongoDB storage adapters all implement the new MCP client domain. **ToolProvider Interface** New `ToolProvider` interface at `@mastra/core/tool-provider` enables third-party tool catalog integration (e.g., Composio, Arcade AI): ```ts import type { ToolProvider } from '@mastra/core/tool-provider'; # Providers implement: listToolkits(), listTools(), getToolSchema(), resolveTools() ``` `resolveTools()` receives `requestContext` from the current request, enabling per-user API keys and credentials in multi-tenant setups: ```ts const tools = await provider.resolveTools(slugs, configs, { requestContext: { apiKey: 'user-specific-key', userId: 'tenant-123' }, }); ``` **Tool Selection Semantics** Both `mcpClients` and `integrationTools` on stored agents follow consistent three-state selection: - `{ tools: undefined }` — provider registered, no tools selected - `{ tools: {} }` — all tools from provider included - `{ tools: { 'TOOL_SLUG': { description: '...' } } }` — specific tools with optional overrides - **Added** ([#12764](https://github.com/mastra-ai/mastra/pull/12764)) Added a `suppressFeedback` option to hide internal completion‑check messages from the stream. This keeps the conversation history clean while leaving existing behavior unchanged by default. **Example** Before: ```ts const agent = await mastra.createAgent({ completion: { validate: true }, }); ``` After: ```ts const agent = await mastra.createAgent({ completion: { validate: true, suppressFeedback: true }, }); ``` - **Split workspace lifecycle interfaces** ([#12978](https://github.com/mastra-ai/mastra/pull/12978)) The shared `Lifecycle` interface has been split into provider-specific types that match actual usage: - `FilesystemLifecycle` — two-phase: `init()` → `destroy()` - `SandboxLifecycle` — three-phase: `start()` → `stop()` → `destroy()` The base `Lifecycle` type is still exported for backward compatibility. **Added `onInit` / `onDestroy` callbacks to `MastraFilesystem`** The `MastraFilesystem` base class now accepts optional lifecycle callbacks via `MastraFilesystemOptions`, matching the existing `onStart` / `onStop` / `onDestroy` callbacks on `MastraSandbox`. ```ts const fs = new LocalFilesystem({ basePath: './data', onInit: ({ filesystem }) => { console.log('Filesystem ready:', filesystem.status); }, onDestroy: ({ filesystem }) => { console.log('Cleaning up...'); }, }); ``` `onInit` fires after the filesystem reaches `ready` status (non-fatal on failure). `onDestroy` fires before the filesystem is torn down. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7ef618f`](https://github.com/mastra-ai/mastra/commit/7ef618f3c49c27e2f6b27d7f564c557c0734325b)) - Fixed agent version storage to persist the requestContextSchema field. Previously, requestContextSchema was defined on the agent snapshot type but was not included in the database schema, INSERT statements, or row parsing logic, causing it to be silently dropped when saving and loading agent versions. ([#13003](https://github.com/mastra-ai/mastra/pull/13003)) - Fixed Anthropic API rejection errors caused by empty text content blocks in assistant messages. During streaming with web search citations, empty text parts could be persisted to the database and then rejected by Anthropic's API with 'text content blocks must be non-empty' errors. The fix filters out these empty text blocks before persistence, ensuring stored conversation history remains valid for Anthropic models. Fixes `#12553`. ([#12711](https://github.com/mastra-ai/mastra/pull/12711)) - Improve error messages when processor workflows or model fallback retries fail. ([#12970](https://github.com/mastra-ai/mastra/pull/12970)) - Include the last error message and cause when all fallback models are exhausted, instead of the generic "Exhausted all fallback models" message. - Extract error details from failed workflow results and individual step failures when a processor workflow fails, instead of just reporting "failed with status: failed". - Fixed tool-not-found errors crashing the agentic loop. When a model hallucinates a tool name (e.g., Gemini 3 Flash adding prefixes like `creating:view` instead of `view`), the error is now returned to the model as a tool result instead of throwing. This allows the model to self-correct and retry with the correct tool name on the next turn. The error message includes available tool names to help the model recover. Fixes #12895. ([#12961](https://github.com/mastra-ai/mastra/pull/12961)) - Fixed structured output failing with Anthropic models when memory is enabled. The error "assistant message in the final position" occurred because the prompt sent to Anthropic ended with an assistant-role message, which is not supported when using output format. Resolves https://github.com/mastra-ai/mastra/issues/12800 ([#12835](https://github.com/mastra-ai/mastra/pull/12835)) ## 1.4.0-alpha.0 ### Minor Changes - Added Datasets and Experiments to core. Datasets let you store and version collections of test inputs with JSON Schema validation. Experiments let you run AI outputs against dataset items with configurable scorers to track quality over time. ([#12747](https://github.com/mastra-ai/mastra/pull/12747)) **New exports from `@mastra/core/datasets`:** - `DatasetsManager` — orchestrates dataset CRUD, item versioning (SCD-2), and experiment execution - `Dataset` — single-dataset handle for adding items and running experiments **New storage domains:** - `DatasetsStorage` — abstract base class for dataset persistence (datasets, items, versions) - `ExperimentsStorage` — abstract base class for experiment lifecycle and result tracking **Example:** ```ts import { Mastra } from '@mastra/core'; const mastra = new Mastra({/* ... */}); const dataset = await mastra.datasets.create({ name: 'my-eval-set' }); await dataset.addItems([{ input: { query: 'What is 2+2?' }, groundTruth: { answer: '4' } }]); const result = await dataset.runExperiment({ targetType: 'agent', targetId: 'my-agent', scorerIds: ['accuracy'], }); ``` - Fix LocalFilesystem.resolvePath handling of absolute paths and improve filesystem info. ([#12971](https://github.com/mastra-ai/mastra/pull/12971)) - Fix absolute path resolution: paths were incorrectly stripped of leading slashes and resolved relative to basePath, causing PermissionError for valid paths (e.g. skills processor accessing project-local skills directories). - Make `FilesystemInfo` generic (`FilesystemInfo`) so providers can type their metadata. - Move provider-specific fields (`basePath`, `contained`) to metadata in LocalFilesystem.getInfo(). - Update LocalFilesystem.getInstructions() for uncontained filesystems to warn agents against listing /. - Use FilesystemInfo type in WorkspaceInfo instead of duplicated inline shape. - Add `workflow-step-progress` stream event for foreach workflow steps. Each iteration emits a progress event with `completedCount`, `totalCount`, `currentIndex`, `iterationStatus` (`success` | `failed` | `suspended`), and optional `iterationOutput`. Both the default and evented execution engines emit these events. ([#12838](https://github.com/mastra-ai/mastra/pull/12838)) The Mastra Studio UI now renders a progress bar with an N/total counter on foreach nodes, updating in real time as iterations complete: ```ts // Consuming progress events from the workflow stream const run = workflow.createRun(); const result = await run.start({ inputData }); const stream = result.stream; for await (const chunk of stream) { if (chunk.type === 'workflow-step-progress') { console.log(`${chunk.payload.completedCount}/${chunk.payload.totalCount} - ${chunk.payload.iterationStatus}`); } } ``` `@mastra/react`: The `mapWorkflowStreamChunkToWatchResult` reducer now accumulates `foreachProgress` from `workflow-step-progress` events into step state, making progress data available to React consumers via the existing workflow watch hooks. - Added observational memory configuration support for stored agents. When creating or editing a stored agent in the playground, you can now enable observational memory and configure its settings including model provider/name, scope (thread or resource), share token budget, and detailed observer/reflector parameters like token limits, buffer settings, and blocking thresholds. The configuration is serialized as part of the agent's memory config and round-trips through storage. ([#12962](https://github.com/mastra-ai/mastra/pull/12962)) **Example usage in the playground:** Enable the Observational Memory toggle in the Memory section, then configure: - Top-level model (provider + model) used by both observer and reflector - Scope: `thread` (per-conversation) or `resource` (shared across threads) - Expand **Observer** or **Reflector** sections to override models and tune token budgets **Programmatic usage via client SDK:** ```ts await client.createStoredAgent({ name: 'My Agent', // ...other config memory: { observationalMemory: true, // enable with defaults options: { lastMessages: 40 }, }, }); // Or with custom configuration: await client.createStoredAgent({ name: 'My Agent', memory: { observationalMemory: { model: 'google/gemini-2.5-flash', scope: 'resource', shareTokenBudget: true, observation: { messageTokens: 50000 }, reflection: { observationTokens: 60000 }, }, options: { lastMessages: 40 }, }, }); ``` **Programmatic usage via editor:** ```ts await editor.agent.create({ name: 'My Agent', // ...other config memory: { observationalMemory: true, // enable with defaults options: { lastMessages: 40 }, }, }); // Or with custom configuration: await editor.agent.create({ name: 'My Agent', memory: { observationalMemory: { model: 'google/gemini-2.5-flash', scope: 'resource', shareTokenBudget: true, observation: { messageTokens: 50000 }, reflection: { observationTokens: 60000 }, }, options: { lastMessages: 40 }, }, }); ``` - Added MCP client storage domain and ToolProvider interface for integrating external tool catalogs with stored agents. ([#12974](https://github.com/mastra-ai/mastra/pull/12974)) **MCP Client Storage** New storage domain for persisting MCP client configurations with CRUD operations. Each MCP client can contain multiple servers with independent tool selection: ```ts // Store an MCP client with multiple servers await storage.mcpClients.create({ id: 'my-mcp', name: 'My MCP Client', servers: { 'github-server': { url: 'https://mcp.github.com/sse' }, 'slack-server': { url: 'https://mcp.slack.com/sse' }, }, }); ``` LibSQL, PostgreSQL, and MongoDB storage adapters all implement the new MCP client domain. **ToolProvider Interface** New `ToolProvider` interface at `@mastra/core/tool-provider` enables third-party tool catalog integration (e.g., Composio, Arcade AI): ```ts import type { ToolProvider } from '@mastra/core/tool-provider'; # Providers implement: listToolkits(), listTools(), getToolSchema(), resolveTools() ``` `resolveTools()` receives `requestContext` from the current request, enabling per-user API keys and credentials in multi-tenant setups: ```ts const tools = await provider.resolveTools(slugs, configs, { requestContext: { apiKey: 'user-specific-key', userId: 'tenant-123' }, }); ``` **Tool Selection Semantics** Both `mcpClients` and `integrationTools` on stored agents follow consistent three-state selection: - `{ tools: undefined }` — provider registered, no tools selected - `{ tools: {} }` — all tools from provider included - `{ tools: { 'TOOL_SLUG': { description: '...' } } }` — specific tools with optional overrides - **Added** ([#12764](https://github.com/mastra-ai/mastra/pull/12764)) Added a `suppressFeedback` option to hide internal completion‑check messages from the stream. This keeps the conversation history clean while leaving existing behavior unchanged by default. **Example** Before: ```ts const agent = await mastra.createAgent({ completion: { validate: true }, }); ``` After: ```ts const agent = await mastra.createAgent({ completion: { validate: true, suppressFeedback: true }, }); ``` - **Split workspace lifecycle interfaces** ([#12978](https://github.com/mastra-ai/mastra/pull/12978)) The shared `Lifecycle` interface has been split into provider-specific types that match actual usage: - `FilesystemLifecycle` — two-phase: `init()` → `destroy()` - `SandboxLifecycle` — three-phase: `start()` → `stop()` → `destroy()` The base `Lifecycle` type is still exported for backward compatibility. **Added `onInit` / `onDestroy` callbacks to `MastraFilesystem`** The `MastraFilesystem` base class now accepts optional lifecycle callbacks via `MastraFilesystemOptions`, matching the existing `onStart` / `onStop` / `onDestroy` callbacks on `MastraSandbox`. ```ts const fs = new LocalFilesystem({ basePath: './data', onInit: ({ filesystem }) => { console.log('Filesystem ready:', filesystem.status); }, onDestroy: ({ filesystem }) => { console.log('Cleaning up...'); }, }); ``` `onInit` fires after the filesystem reaches `ready` status (non-fatal on failure). `onDestroy` fires before the filesystem is torn down. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`7ef618f`](https://github.com/mastra-ai/mastra/commit/7ef618f3c49c27e2f6b27d7f564c557c0734325b)) - Fixed agent version storage to persist the requestContextSchema field. Previously, requestContextSchema was defined on the agent snapshot type but was not included in the database schema, INSERT statements, or row parsing logic, causing it to be silently dropped when saving and loading agent versions. ([#13003](https://github.com/mastra-ai/mastra/pull/13003)) - Fixed Anthropic API rejection errors caused by empty text content blocks in assistant messages. During streaming with web search citations, empty text parts could be persisted to the database and then rejected by Anthropic's API with 'text content blocks must be non-empty' errors. The fix filters out these empty text blocks before persistence, ensuring stored conversation history remains valid for Anthropic models. Fixes `#12553`. ([#12711](https://github.com/mastra-ai/mastra/pull/12711)) - Improve error messages when processor workflows or model fallback retries fail. ([#12970](https://github.com/mastra-ai/mastra/pull/12970)) - Include the last error message and cause when all fallback models are exhausted, instead of the generic "Exhausted all fallback models" message. - Extract error details from failed workflow results and individual step failures when a processor workflow fails, instead of just reporting "failed with status: failed". - Fixed tool-not-found errors crashing the agentic loop. When a model hallucinates a tool name (e.g., Gemini 3 Flash adding prefixes like `creating:view` instead of `view`), the error is now returned to the model as a tool result instead of throwing. This allows the model to self-correct and retry with the correct tool name on the next turn. The error message includes available tool names to help the model recover. Fixes #12895. ([#12961](https://github.com/mastra-ai/mastra/pull/12961)) - Fixed structured output failing with Anthropic models when memory is enabled. The error "assistant message in the final position" occurred because the prompt sent to Anthropic ended with an assistant-role message, which is not supported when using output format. Resolves https://github.com/mastra-ai/mastra/issues/12800 ([#12835](https://github.com/mastra-ai/mastra/pull/12835)) ## 1.3.0 ### Minor Changes - Added mount support to workspaces, so you can combine multiple storage providers (S3, GCS, local disk, etc.) under a single directory tree. This lets agents access files from different sources through one unified filesystem. ([#12851](https://github.com/mastra-ai/mastra/pull/12851)) **Why:** Previously a workspace could only use one filesystem. With mounts, you can organize files from different providers under different paths — for example, S3 data at `/data` and GCS models at `/models` — without agents needing to know which provider backs each path. **What's new:** - Added `CompositeFilesystem` for combining multiple filesystems under one tree - Added descriptive error types for sandbox and mount failures (e.g., `SandboxTimeoutError`, `MountError`) - Improved `MastraFilesystem` and `MastraSandbox` base classes with safer concurrent lifecycle handling ```ts import { Workspace, CompositeFilesystem } from '@mastra/core/workspace'; // Mount multiple filesystems under one tree const composite = new CompositeFilesystem({ mounts: { '/data': s3Filesystem, '/models': gcsFilesystem, }, }); const workspace = new Workspace({ filesystem: composite, sandbox: e2bSandbox, }); ``` - Supporting work to allow for cloning agents via the client SDK ([#12796](https://github.com/mastra-ai/mastra/pull/12796)) - Added `requestContextSchema` and rule-based conditional fields for stored agents. ([#12896](https://github.com/mastra-ai/mastra/pull/12896)) Stored agent fields (`tools`, `model`, `workflows`, `agents`, `memory`, `scorers`, `inputProcessors`, `outputProcessors`, `defaultOptions`) can now be configured as conditional variants with rule groups that evaluate against request context at runtime. All matching variants accumulate — arrays are concatenated and objects are shallow-merged — so agents dynamically compose their configuration based on the incoming request context. **New `requestContextSchema` field** Stored agents now accept an optional `requestContextSchema` (JSON Schema) that is converted to a Zod schema and passed to the Agent constructor, enabling request context validation. **Conditional field example** ```ts await agentsStore.create({ agent: { id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model: { provider: 'openai', name: 'gpt-4' }, tools: [ { value: { 'basic-tool': {} } }, { value: { 'premium-tool': {} }, rules: { operator: 'AND', conditions: [{ field: 'tier', operator: 'equals', value: 'premium' }], }, }, ], requestContextSchema: { type: 'object', properties: { tier: { type: 'string' } }, }, }, }); ``` - Implement underlying storage changes to allow for dynamic instructions in stored agents (for `@mastra/editor`) ([#12776](https://github.com/mastra-ai/mastra/pull/12776)) - Add native `@ai-sdk/groq` support to model router. Groq models now use the official AI SDK package instead of falling back to OpenAI-compatible mode. ([#12741](https://github.com/mastra-ai/mastra/pull/12741)) - **Added stored scorer definitions, editor namespace pattern, and generic storage domains** ([#12846](https://github.com/mastra-ai/mastra/pull/12846)) - Added a new `scorer-definitions` storage domain for storing LLM-as-judge and preset scorer configurations in the database - Introduced a `VersionedStorageDomain` generic base class that unifies `AgentsStorage`, `PromptBlocksStorage`, and `ScorerDefinitionsStorage` with shared CRUD methods (`create`, `getById`, `getByIdResolved`, `update`, `delete`, `list`, `listResolved`) - Flattened stored scorer type system: replaced nested `preset`/`customLLMJudge` config with top-level `type`, `instructions`, `scoreRange`, and `presetConfig` fields - Refactored `MastraEditor` to use a namespace pattern (`editor.agent.*`, `editor.scorer.*`, `editor.prompt.*`) backed by a `CrudEditorNamespace` base class with built-in caching and an `onCacheEvict` hook - Added `rawConfig` support to `MastraBase` and `MastraScorer` via `toRawConfig()`, so hydrated primitives carry their stored configuration - Added prompt block and scorer registration to the `Mastra` class (`addPromptBlock`, `removePromptBlock`, `addScorer`, `removeScorer`) **Creating a stored scorer (LLM-as-judge):** ```ts const scorer = await editor.scorer.create({ id: 'my-scorer', name: 'Response Quality', type: 'llm-judge', instructions: 'Evaluate the response for accuracy and helpfulness.', model: { provider: 'openai', name: 'gpt-4o' }, scoreRange: { min: 0, max: 1 }, }); ``` **Retrieving and resolving a stored scorer:** ```ts // Fetch the stored definition from DB const definition = await editor.scorer.getById('my-scorer'); // Resolve it into a runnable MastraScorer instance const runnableScorer = editor.scorer.resolve(definition); // Execute the scorer const result = await runnableScorer.run({ input: 'What is the capital of France?', output: 'The capital of France is Paris.', }); ``` **Editor namespace pattern (before/after):** ```ts // Before const agent = await editor.getStoredAgentById('abc'); const prompts = await editor.listPromptBlocks(); // After const agent = await editor.agent.getById('abc'); const prompts = await editor.prompt.list(); ``` **Generic storage domain methods (before/after):** ```ts // Before const store = storage.getStore('agents'); await store.createAgent({ agent: input }); await store.getAgentById({ id: 'abc' }); await store.deleteAgent({ id: 'abc' }); // After const store = storage.getStore('agents'); await store.create({ agent: input }); await store.getById('abc'); await store.delete('abc'); ``` - Added mount status and error information to filesystem directory listings, so the UI can show whether each mount is healthy or has issues. Improved error handling when mount operations fail. Fixed tree formatter to use case-insensitive sorting to match native tree output. ([#12605](https://github.com/mastra-ai/mastra/pull/12605)) - Added workspace registration and tool context support. ([#12607](https://github.com/mastra-ai/mastra/pull/12607)) **Why** - Makes it easier to manage multiple workspaces at runtime and lets tools read/write files in the intended workspace. **Workspace Registration** - Added a workspace registry so you can list and fetch workspaces by id with `addWorkspace()`, `getWorkspaceById()`, and `listWorkspaces()`. Agent workspaces are auto-registered when adding agents. **Before** ```typescript const mastra = new Mastra({ workspace: myWorkspace }); // No way to look up workspaces by id or list all workspaces ``` **After** ```typescript const mastra = new Mastra({ workspace: myWorkspace }); // Look up by id const ws = mastra.getWorkspaceById('my-workspace'); // List all registered workspaces const allWorkspaces = mastra.listWorkspaces(); // Register additional workspaces mastra.addWorkspace(anotherWorkspace); ``` **Tool Workspace Access** - Tools can access the workspace through `context.workspace` during execution, enabling filesystem and sandbox operations. ```typescript const myTool = createTool({ id: 'file-reader', execute: async ({ context }) => { const fs = context.workspace?.filesystem; const content = await fs?.readFile('config.json'); return { content }; }, }); ``` **Dynamic Workspace Configuration** - Workspace can be configured dynamically via agent config functions. Dynamically created workspaces are auto-registered with Mastra, making them available via `listWorkspaces()`. ```typescript const agent = new Agent({ workspace: ({ mastra, requestContext }) => { // Return workspace dynamically based on context const workspaceId = requestContext?.get('workspaceId') || 'default'; return mastra.getWorkspaceById(workspaceId); }, }); ``` - Add tool description overrides for stored agents: ([#12794](https://github.com/mastra-ai/mastra/pull/12794)) - Changed stored agent `tools` field from `string[]` to `Record` to allow per-tool description overrides - When a stored agent specifies a custom `description` for a tool, the override is applied at resolution time - Updated server API schemas, client SDK types, and editor resolution logic accordingly - **Breaking:** Removed `cloneAgent()` from the `Agent` class. Agent cloning is now handled by the editor package via `editor.agent.clone()`. ([#12904](https://github.com/mastra-ai/mastra/pull/12904)) If you were calling `agent.cloneAgent()` directly, use the editor's agent namespace instead: ```ts // Before const result = await agent.cloneAgent({ newId: 'my-clone' }); // After const editor = mastra.getEditor(); const result = await editor.agent.clone(agent, { newId: 'my-clone' }); ``` **Why:** The `Agent` class should not be responsible for storage serialization. The editor package already handles converting between runtime agents and stored configurations, so cloning belongs there. **Added** `getConfiguredProcessorIds()` to the `Agent` class, which returns raw input/output processor IDs for the agent's configuration. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`717ffab`](https://github.com/mastra-ai/mastra/commit/717ffab42cfd58ff723b5c19ada4939997773004)) - Fixed observational memory progress bars resetting to zero after agent responses finish. ([#12934](https://github.com/mastra-ai/mastra/pull/12934)) - Fixed issues with stored agents ([#12790](https://github.com/mastra-ai/mastra/pull/12790)) - Fixed sub-agent tool approval and suspend events not being surfaced to the parent agent stream. This enables proper suspend/resume workflows and approval handling when nested agents require tool approvals. ([#12732](https://github.com/mastra-ai/mastra/pull/12732)) Related to issue #12552. - Fixed stale agent data in CMS pages by adding removeAgent method to Mastra and updating clearStoredAgentCache to clear both Editor cache and Mastra registry when stored agents are updated or deleted ([#12693](https://github.com/mastra-ai/mastra/pull/12693)) - Fixed stored scorers not being registered on the Mastra instance. Scorers created via the editor are now automatically discoverable through `mastra.getScorer()` and `mastra.getScorerById()`, matching the existing behavior of stored agents. Previously, stored scorers could only be resolved inline but were invisible to the runtime registry, causing lookups to fail. ([#12903](https://github.com/mastra-ai/mastra/pull/12903)) - Fixed `generateTitle` running on every conversation turn instead of only the first, which caused redundant title generation calls. This happened when `lastMessages` was disabled or set to `false`. Titles are now correctly generated only on the first turn. ([#12890](https://github.com/mastra-ai/mastra/pull/12890)) - Fixed workflow step errors not being propagated to the configured Mastra logger. The execution engine now properly propagates the Mastra logger through the inheritance chain, and the evented step executor logs errors with structured MastraError context (matching the default engine behavior). Closes #12793 ([#12834](https://github.com/mastra-ai/mastra/pull/12834)) - Update memory config and exports: ([#12704](https://github.com/mastra-ai/mastra/pull/12704)) - Updated `SerializedMemoryConfig` to allow `embedder?: EmbeddingModelId | string` for flexibility - Exported `EMBEDDING_MODELS` and `EmbeddingModelInfo` for use in server endpoints - Fixed a catch-22 where third-party AI SDK providers (like `ollama-ai-provider-v2`) were rejected by both `stream()` and `streamLegacy()` due to unrecognized `specificationVersion` values. ([#12856](https://github.com/mastra-ai/mastra/pull/12856)) When a model has a `specificationVersion` that isn't `'v1'`, `'v2'`, or `'v3'` (e.g., from a third-party provider), two fixes now apply: 1. **Auto-wrapping in `resolveModelConfig()`**: Models with unknown spec versions that have `doStream`/`doGenerate` methods are automatically wrapped as AI SDK v5 models, preventing the catch-22 entirely. 2. **Improved error messages**: If a model still reaches the version check, error messages now show the actual unrecognized `specificationVersion` instead of creating circular suggestions between `stream()` and `streamLegacy()`. - Fixed routing output so users only see the final answer when routing handles a request directly. Previously, an internal routing explanation appeared before the answer and was duplicated. Fixes #12545. ([#12786](https://github.com/mastra-ai/mastra/pull/12786)) - Supporting changes for async buffering in observational memory, including new config options, streaming events, and UI markers. ([#12891](https://github.com/mastra-ai/mastra/pull/12891)) - Fixed an issue where processor retry (via `abort({ retry: true })` in `processOutputStep`) would send the rejected assistant response back to the LLM on retry. This confused models and often caused empty text responses. The rejected response is now removed from the message list before the retry iteration. ([#12799](https://github.com/mastra-ai/mastra/pull/12799)) - Fixed Moonshot AI (moonshotai and moonshotai-cn) models using the wrong base URL. The Anthropic-compatible endpoint was not being applied, causing API calls to fail with an upstream LLM error. ([#12750](https://github.com/mastra-ai/mastra/pull/12750)) - Fixed messages not being persisted to the database when using the stream-legacy endpoint. The thread is now saved to the database immediately when created, preventing a race condition where storage backends like PostgreSQL would reject message inserts because the thread didn't exist yet. Fixes #12566. ([#12774](https://github.com/mastra-ai/mastra/pull/12774)) - When calling `mastra.setLogger()`, memory instances were not being updated with the new logger. This caused memory-related errors to be logged via the default ConsoleLogger instead of the configured logger. ([#12905](https://github.com/mastra-ai/mastra/pull/12905)) - Fixed tool input validation failing when LLMs return stringified JSON for array or object parameters. Some models (e.g., GLM4.7) send `"[\"file.py\"]"` instead of `["file.py"]` for array fields, which caused Zod validation to reject the input. The validation pipeline now automatically detects and parses stringified JSON values when the schema expects an array or object. (GitHub #12757) ([#12771](https://github.com/mastra-ai/mastra/pull/12771)) - Fixed working memory tools being injected when no thread or resource context is provided. Made working memory tool execute scope-aware: thread-scoped requires threadId, resource-scoped requires resourceId (previously both were always required regardless of scope). ([#12831](https://github.com/mastra-ai/mastra/pull/12831)) - Fixed a crash when using agent workflows that have no input schema. Input now passes through on first invocation, so workflows run instead of failing. (#12739) ([#12785](https://github.com/mastra-ai/mastra/pull/12785)) - Fixes issue where client tools could not be used with agent.network(). Client tools configured in an agent's defaultOptions will now be available during network execution. ([#12821](https://github.com/mastra-ai/mastra/pull/12821)) Fixes #12752 - Steps now support an optional `metadata` property for storing arbitrary key-value data. This metadata is preserved through step serialization and is available in the workflow graph, enabling use cases like UI annotations or custom step categorization. ([#12861](https://github.com/mastra-ai/mastra/pull/12861)) ```diff import { createStep } from "@mastra/core/workflows"; import { z } from "zod"; const step = createStep({ //...step information + metadata: { + category: "orders", + priority: "high", + version: "1.0.0", + }, }); ``` Metadata values must be serializable (no functions or circular references). - Fixed: You can now pass workflows with a `requestContextSchema` to the Mastra constructor without a type error. Related: #12773. ([#12857](https://github.com/mastra-ai/mastra/pull/12857)) - Fixed TypeScript type errors when using `.optional().default()` in workflow input schemas. Workflows with default values in their schemas no longer produce false type errors when chaining steps with `.then()`. Fixes #12634 ([#12778](https://github.com/mastra-ai/mastra/pull/12778)) - Fix setLogger to update workflow loggers ([#12889](https://github.com/mastra-ai/mastra/pull/12889)) When calling `mastra.setLogger()`, workflows were not being updated with the new logger. This caused workflow errors to be logged via the default ConsoleLogger instead of the configured logger (e.g., PinoLogger with HttpTransport), resulting in missing error logs in Cloud deployments. ## 1.3.0-alpha.2 ### Patch Changes - Fixed observational memory progress bars resetting to zero after agent responses finish. The messages and observations sidebar bars now retain their values on stream completion, cancellation, and page reload. Also added a buffer-status endpoint so buffering badges resolve with accurate token counts instead of spinning forever when buffering outlives the stream. ([#12934](https://github.com/mastra-ai/mastra/pull/12934)) ## 1.3.0-alpha.1 ### Minor Changes - Added mount support to workspaces, so you can combine multiple storage providers (S3, GCS, local disk, etc.) under a single directory tree. This lets agents access files from different sources through one unified filesystem. ([#12851](https://github.com/mastra-ai/mastra/pull/12851)) **Why:** Previously a workspace could only use one filesystem. With mounts, you can organize files from different providers under different paths — for example, S3 data at `/data` and GCS models at `/models` — without agents needing to know which provider backs each path. **What's new:** - Added `CompositeFilesystem` for combining multiple filesystems under one tree - Added descriptive error types for sandbox and mount failures (e.g., `SandboxTimeoutError`, `MountError`) - Improved `MastraFilesystem` and `MastraSandbox` base classes with safer concurrent lifecycle handling ```ts import { Workspace, CompositeFilesystem } from '@mastra/core/workspace'; // Mount multiple filesystems under one tree const composite = new CompositeFilesystem({ mounts: { '/data': s3Filesystem, '/models': gcsFilesystem, }, }); const workspace = new Workspace({ filesystem: composite, sandbox: e2bSandbox, }); ``` - Supporting work to allow for cloning agents via the client SDK ([#12796](https://github.com/mastra-ai/mastra/pull/12796)) - Added `requestContextSchema` and rule-based conditional fields for stored agents. ([#12896](https://github.com/mastra-ai/mastra/pull/12896)) Stored agent fields (`tools`, `model`, `workflows`, `agents`, `memory`, `scorers`, `inputProcessors`, `outputProcessors`, `defaultOptions`) can now be configured as conditional variants with rule groups that evaluate against request context at runtime. All matching variants accumulate — arrays are concatenated and objects are shallow-merged — so agents dynamically compose their configuration based on the incoming request context. **New `requestContextSchema` field** Stored agents now accept an optional `requestContextSchema` (JSON Schema) that is converted to a Zod schema and passed to the Agent constructor, enabling request context validation. **Conditional field example** ```ts await agentsStore.create({ agent: { id: 'my-agent', name: 'My Agent', instructions: 'You are a helpful assistant', model: { provider: 'openai', name: 'gpt-4' }, tools: [ { value: { 'basic-tool': {} } }, { value: { 'premium-tool': {} }, rules: { operator: 'AND', conditions: [{ field: 'tier', operator: 'equals', value: 'premium' }], }, }, ], requestContextSchema: { type: 'object', properties: { tier: { type: 'string' } }, }, }, }); ``` - Implement underlying storage changes to allow for dynamic instructions in stored agents (for `@mastra/editor`) ([#12776](https://github.com/mastra-ai/mastra/pull/12776)) - Add native `@ai-sdk/groq` support to model router. Groq models now use the official AI SDK package instead of falling back to OpenAI-compatible mode. ([#12741](https://github.com/mastra-ai/mastra/pull/12741)) - **Added stored scorer definitions, editor namespace pattern, and generic storage domains** ([#12846](https://github.com/mastra-ai/mastra/pull/12846)) - Added a new `scorer-definitions` storage domain for storing LLM-as-judge and preset scorer configurations in the database - Introduced a `VersionedStorageDomain` generic base class that unifies `AgentsStorage`, `PromptBlocksStorage`, and `ScorerDefinitionsStorage` with shared CRUD methods (`create`, `getById`, `getByIdResolved`, `update`, `delete`, `list`, `listResolved`) - Flattened stored scorer type system: replaced nested `preset`/`customLLMJudge` config with top-level `type`, `instructions`, `scoreRange`, and `presetConfig` fields - Refactored `MastraEditor` to use a namespace pattern (`editor.agent.*`, `editor.scorer.*`, `editor.prompt.*`) backed by a `CrudEditorNamespace` base class with built-in caching and an `onCacheEvict` hook - Added `rawConfig` support to `MastraBase` and `MastraScorer` via `toRawConfig()`, so hydrated primitives carry their stored configuration - Added prompt block and scorer registration to the `Mastra` class (`addPromptBlock`, `removePromptBlock`, `addScorer`, `removeScorer`) **Creating a stored scorer (LLM-as-judge):** ```ts const scorer = await editor.scorer.create({ id: 'my-scorer', name: 'Response Quality', type: 'llm-judge', instructions: 'Evaluate the response for accuracy and helpfulness.', model: { provider: 'openai', name: 'gpt-4o' }, scoreRange: { min: 0, max: 1 }, }); ``` **Retrieving and resolving a stored scorer:** ```ts // Fetch the stored definition from DB const definition = await editor.scorer.getById('my-scorer'); // Resolve it into a runnable MastraScorer instance const runnableScorer = editor.scorer.resolve(definition); // Execute the scorer const result = await runnableScorer.run({ input: 'What is the capital of France?', output: 'The capital of France is Paris.', }); ``` **Editor namespace pattern (before/after):** ```ts // Before const agent = await editor.getStoredAgentById('abc'); const prompts = await editor.listPromptBlocks(); // After const agent = await editor.agent.getById('abc'); const prompts = await editor.prompt.list(); ``` **Generic storage domain methods (before/after):** ```ts // Before const store = storage.getStore('agents'); await store.createAgent({ agent: input }); await store.getAgentById({ id: 'abc' }); await store.deleteAgent({ id: 'abc' }); // After const store = storage.getStore('agents'); await store.create({ agent: input }); await store.getById('abc'); await store.delete('abc'); ``` - Added mount status and error information to filesystem directory listings, so the UI can show whether each mount is healthy or has issues. Improved error handling when mount operations fail. Fixed tree formatter to use case-insensitive sorting to match native tree output. ([#12605](https://github.com/mastra-ai/mastra/pull/12605)) - Added workspace registration and tool context support. ([#12607](https://github.com/mastra-ai/mastra/pull/12607)) **Why** - Makes it easier to manage multiple workspaces at runtime and lets tools read/write files in the intended workspace. **Workspace Registration** - Added a workspace registry so you can list and fetch workspaces by id with `addWorkspace()`, `getWorkspaceById()`, and `listWorkspaces()`. Agent workspaces are auto-registered when adding agents. **Before** ```typescript const mastra = new Mastra({ workspace: myWorkspace }); // No way to look up workspaces by id or list all workspaces ``` **After** ```typescript const mastra = new Mastra({ workspace: myWorkspace }); // Look up by id const ws = mastra.getWorkspaceById('my-workspace'); // List all registered workspaces const allWorkspaces = mastra.listWorkspaces(); // Register additional workspaces mastra.addWorkspace(anotherWorkspace); ``` **Tool Workspace Access** - Tools can access the workspace through `context.workspace` during execution, enabling filesystem and sandbox operations. ```typescript const myTool = createTool({ id: 'file-reader', execute: async ({ context }) => { const fs = context.workspace?.filesystem; const content = await fs?.readFile('config.json'); return { content }; }, }); ``` **Dynamic Workspace Configuration** - Workspace can be configured dynamically via agent config functions. Dynamically created workspaces are auto-registered with Mastra, making them available via `listWorkspaces()`. ```typescript const agent = new Agent({ workspace: ({ mastra, requestContext }) => { // Return workspace dynamically based on context const workspaceId = requestContext?.get('workspaceId') || 'default'; return mastra.getWorkspaceById(workspaceId); }, }); ``` - Add tool description overrides for stored agents: ([#12794](https://github.com/mastra-ai/mastra/pull/12794)) - Changed stored agent `tools` field from `string[]` to `Record` to allow per-tool description overrides - When a stored agent specifies a custom `description` for a tool, the override is applied at resolution time - Updated server API schemas, client SDK types, and editor resolution logic accordingly - **Breaking:** Removed `cloneAgent()` from the `Agent` class. Agent cloning is now handled by the editor package via `editor.agent.clone()`. ([#12904](https://github.com/mastra-ai/mastra/pull/12904)) If you were calling `agent.cloneAgent()` directly, use the editor's agent namespace instead: ```ts // Before const result = await agent.cloneAgent({ newId: 'my-clone' }); // After const editor = mastra.getEditor(); const result = await editor.agent.clone(agent, { newId: 'my-clone' }); ``` **Why:** The `Agent` class should not be responsible for storage serialization. The editor package already handles converting between runtime agents and stored configurations, so cloning belongs there. **Added** `getConfiguredProcessorIds()` to the `Agent` class, which returns raw input/output processor IDs for the agent's configuration. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`717ffab`](https://github.com/mastra-ai/mastra/commit/717ffab42cfd58ff723b5c19ada4939997773004)) - Fixed issues with stored agents ([#12790](https://github.com/mastra-ai/mastra/pull/12790)) - Fixed sub-agent tool approval and suspend events not being surfaced to the parent agent stream. This enables proper suspend/resume workflows and approval handling when nested agents require tool approvals. ([#12732](https://github.com/mastra-ai/mastra/pull/12732)) Related to issue #12552. - Fixed stored scorers not being registered on the Mastra instance. Scorers created via the editor are now automatically discoverable through `mastra.getScorer()` and `mastra.getScorerById()`, matching the existing behavior of stored agents. Previously, stored scorers could only be resolved inline but were invisible to the runtime registry, causing lookups to fail. ([#12903](https://github.com/mastra-ai/mastra/pull/12903)) - Fixed `generateTitle` running on every conversation turn instead of only the first, which caused redundant title generation calls. This happened when `lastMessages` was disabled or set to `false`. Titles are now correctly generated only on the first turn. ([#12890](https://github.com/mastra-ai/mastra/pull/12890)) - Fixed workflow step errors not being propagated to the configured Mastra logger. The execution engine now properly propagates the Mastra logger through the inheritance chain, and the evented step executor logs errors with structured MastraError context (matching the default engine behavior). Closes #12793 ([#12834](https://github.com/mastra-ai/mastra/pull/12834)) - Fixed a catch-22 where third-party AI SDK providers (like `ollama-ai-provider-v2`) were rejected by both `stream()` and `streamLegacy()` due to unrecognized `specificationVersion` values. ([#12856](https://github.com/mastra-ai/mastra/pull/12856)) When a model has a `specificationVersion` that isn't `'v1'`, `'v2'`, or `'v3'` (e.g., from a third-party provider), two fixes now apply: 1. **Auto-wrapping in `resolveModelConfig()`**: Models with unknown spec versions that have `doStream`/`doGenerate` methods are automatically wrapped as AI SDK v5 models, preventing the catch-22 entirely. 2. **Improved error messages**: If a model still reaches the version check, error messages now show the actual unrecognized `specificationVersion` instead of creating circular suggestions between `stream()` and `streamLegacy()`. - Fixed routing output so users only see the final answer when routing handles a request directly. Previously, an internal routing explanation appeared before the answer and was duplicated. Fixes #12545. ([#12786](https://github.com/mastra-ai/mastra/pull/12786)) - **Async buffering for observational memory is now enabled by default.** Observations are pre-computed in the background as conversations grow — when the context window fills up, buffered observations activate instantly with no blocking LLM call. This keeps agents responsive during long conversations. ([#12891](https://github.com/mastra-ai/mastra/pull/12891)) **Default settings:** - `observation.bufferTokens: 0.2` — buffer every 20% of `messageTokens` (~6k tokens with the default 30k threshold) - `observation.bufferActivation: 0.8` — on activation, retain 20% of the message window - `reflection.bufferActivation: 0.5` — start background reflection at 50% of the observation threshold **Disabling async buffering:** Set `observation.bufferTokens: false` to disable async buffering for both observations and reflections: ```ts const memory = new Memory({ options: { observationalMemory: { model: 'google/gemini-2.5-flash', observation: { bufferTokens: false, }, }, }, }); ``` **Model is now required** when passing an observational memory config object. Use `observationalMemory: true` for the default (google/gemini-2.5-flash), or set a model explicitly: ```ts // Uses default model (google/gemini-2.5-flash) observationalMemory: true // Explicit model observationalMemory: { model: "google/gemini-2.5-flash", } ``` **`shareTokenBudget` requires `bufferTokens: false`** (temporary limitation). If you use `shareTokenBudget: true`, you must explicitly disable async buffering: ```ts observationalMemory: { model: "google/gemini-2.5-flash", shareTokenBudget: true, observation: { bufferTokens: false }, } ``` **New streaming event:** `data-om-status` replaces `data-om-progress` with a structured status object containing active window usage, buffered observation/reflection state, and projected activation impact. **Buffering markers:** New `data-om-buffering-start`, `data-om-buffering-end`, and `data-om-buffering-failed` streaming events for UI feedback during background operations. - Fixed an issue where processor retry (via `abort({ retry: true })` in `processOutputStep`) would send the rejected assistant response back to the LLM on retry. This confused models and often caused empty text responses. The rejected response is now removed from the message list before the retry iteration. ([#12799](https://github.com/mastra-ai/mastra/pull/12799)) - Fixed Moonshot AI (moonshotai and moonshotai-cn) models using the wrong base URL. The Anthropic-compatible endpoint was not being applied, causing API calls to fail with an upstream LLM error. ([#12750](https://github.com/mastra-ai/mastra/pull/12750)) - Fixed messages not being persisted to the database when using the stream-legacy endpoint. The thread is now saved to the database immediately when created, preventing a race condition where storage backends like PostgreSQL would reject message inserts because the thread didn't exist yet. Fixes #12566. ([#12774](https://github.com/mastra-ai/mastra/pull/12774)) - fix(core): update memory loggers in setLogger ([#12905](https://github.com/mastra-ai/mastra/pull/12905)) When calling `mastra.setLogger()`, memory instances were not being updated with the new logger. This caused memory-related errors to be logged via the default ConsoleLogger instead of the configured logger. - Fixed tool input validation failing when LLMs return stringified JSON for array or object parameters. Some models (e.g., GLM4.7) send `"[\"file.py\"]"` instead of `["file.py"]` for array fields, which caused Zod validation to reject the input. The validation pipeline now automatically detects and parses stringified JSON values when the schema expects an array or object. (GitHub #12757) ([#12771](https://github.com/mastra-ai/mastra/pull/12771)) - Fixed working memory tools being injected when no thread or resource context is provided. Made working memory tool execute scope-aware: thread-scoped requires threadId, resource-scoped requires resourceId (previously both were always required regardless of scope). ([#12831](https://github.com/mastra-ai/mastra/pull/12831)) - Fixed a crash when using agent workflows that have no input schema. Input now passes through on first invocation, so workflows run instead of failing. (#12739) ([#12785](https://github.com/mastra-ai/mastra/pull/12785)) - Fixes issue where client tools could not be used with agent.network(). Client tools configured in an agent's defaultOptions will now be available during network execution. ([#12821](https://github.com/mastra-ai/mastra/pull/12821)) Fixes #12752 - Steps now support an optional `metadata` property for storing arbitrary key-value data. This metadata is preserved through step serialization and is available in the workflow graph, enabling use cases like UI annotations or custom step categorization. ([#12861](https://github.com/mastra-ai/mastra/pull/12861)) ```diff import { createStep } from "@mastra/core/workflows"; import { z } from "zod"; const step = createStep({ //...step information + metadata: { + category: "orders", + priority: "high", + version: "1.0.0", + }, }); ``` Metadata values must be serializable (no functions or circular references). - Fixed: You can now pass workflows with a `requestContextSchema` to the Mastra constructor without a type error. Related: #12773. ([#12857](https://github.com/mastra-ai/mastra/pull/12857)) - Fixed TypeScript type errors when using `.optional().default()` in workflow input schemas. Workflows with default values in their schemas no longer produce false type errors when chaining steps with `.then()`. Fixes #12634 ([#12778](https://github.com/mastra-ai/mastra/pull/12778)) - Fix setLogger to update workflow loggers ([#12889](https://github.com/mastra-ai/mastra/pull/12889)) When calling `mastra.setLogger()`, workflows were not being updated with the new logger. This caused workflow errors to be logged via the default ConsoleLogger instead of the configured logger (e.g., PinoLogger with HttpTransport), resulting in missing error logs in Cloud deployments. ## 1.2.1-alpha.0 ### Patch Changes - Fixed stale agent data in CMS pages by adding removeAgent method to Mastra and updating clearStoredAgentCache to clear both Editor cache and Mastra registry when stored agents are updated or deleted ([#12693](https://github.com/mastra-ai/mastra/pull/12693)) - Update memory config and exports: ([#12704](https://github.com/mastra-ai/mastra/pull/12704)) - Updated `SerializedMemoryConfig` to allow `embedder?: EmbeddingModelId | string` for flexibility - Exported `EMBEDDING_MODELS` and `EmbeddingModelInfo` for use in server endpoints ## 1.2.0 ### Minor Changes - Added Observational Memory — a new memory system that keeps your agent's context window small while preserving long-term memory across conversations. ([#12599](https://github.com/mastra-ai/mastra/pull/12599)) **Why:** Long conversations cause context rot and waste tokens. Observational Memory compresses conversation history into observations (5–40x compression) and periodically condenses those into reflections. Your agent stays fast and focused, even after thousands of messages. **Usage:** ```ts import { Memory } from '@mastra/memory'; import { PostgresStore } from '@mastra/pg'; const memory = new Memory({ storage: new PostgresStore({ connectionString: process.env.DATABASE_URL }), options: { observationalMemory: true, }, }); const agent = new Agent({ name: 'my-agent', model: openai('gpt-4o'), memory, }); ``` **What's new:** - `observationalMemory: true` enables the three-tier memory system (recent messages → observations → reflections) - Thread-scoped (per-conversation) and resource-scoped (shared across all threads for a user) modes - Manual `observe()` API for triggering observation outside the normal agent loop - New OM storage methods for pg, libsql, and mongodb adapters (conditionally enabled) - `Agent.findProcessor()` method for looking up processors by ID - `processorStates` for persisting processor state across loop iterations - Abort signal propagation to processors - `ProcessorStreamWriter` for custom stream events from processors - Created @mastra/editor package for managing and resolving stored agent configurations ([#12631](https://github.com/mastra-ai/mastra/pull/12631)) This major addition introduces the editor package, which provides a complete solution for storing, versioning, and instantiating agent configurations from a database. The editor seamlessly integrates with Mastra's storage layer to enable dynamic agent management. **Key Features:** - **Agent Storage & Retrieval**: Store complete agent configurations including instructions, model settings, tools, workflows, nested agents, scorers, processors, and memory configuration - **Version Management**: Create and manage multiple versions of agents, with support for activating specific versions - **Dependency Resolution**: Automatically resolves and instantiates all agent dependencies (tools, workflows, sub-agents, etc.) from the Mastra registry - **Caching**: Built-in caching for improved performance when repeatedly accessing stored agents - **Type Safety**: Full TypeScript support with proper typing for stored configurations **Usage Example:** ```typescript import { MastraEditor } from '@mastra/editor'; import { Mastra } from '@mastra/core'; // Initialize editor with Mastra const mastra = new Mastra({ /* config */ editor: new MastraEditor(), }); // Store an agent configuration const agentId = await mastra.storage.stores?.agents?.createAgent({ name: 'customer-support', instructions: 'Help customers with inquiries', model: { provider: 'openai', name: 'gpt-4' }, tools: ['search-kb', 'create-ticket'], workflows: ['escalation-flow'], memory: { vector: 'pinecone-db' }, }); // Retrieve and use the stored agent const agent = await mastra.getEditor()?.getStoredAgentById(agentId); const response = await agent?.generate('How do I reset my password?'); // List all stored agents const agents = await mastra.getEditor()?.listStoredAgents({ pageSize: 10 }); ``` **Storage Improvements:** - Fixed JSONB handling in LibSQL, PostgreSQL, and MongoDB adapters - Improved agent resolution queries to properly merge version data - Enhanced type safety for serialized configurations - Added logger support to Workspace filesystem and sandbox providers. Providers extending MastraFilesystem or MastraSandbox now automatically receive the Mastra logger for consistent logging of file operations and command executions. ([#12606](https://github.com/mastra-ai/mastra/pull/12606)) - Added ToolSearchProcessor for dynamic tool discovery. ([#12290](https://github.com/mastra-ai/mastra/pull/12290)) Agents can now discover and load tools on demand instead of having all tools available upfront. This reduces context token usage by ~94% when working with large tool libraries. **New API:** ```typescript import { ToolSearchProcessor } from '@mastra/core/processors'; import { Agent } from '@mastra/core'; // Create a processor with searchable tools const toolSearch = new ToolSearchProcessor({ tools: { createIssue: githubTools.createIssue, sendEmail: emailTools.send, // ... hundreds of tools }, search: { topK: 5, // Return top 5 results (default: 5) minScore: 0.1, // Filter results below this score (default: 0) }, }); // Attach processor to agent const agent = new Agent({ name: 'my-agent', inputProcessors: [toolSearch], tools: {/* always-available tools */}, }); ``` **How it works:** The processor automatically provides two meta-tools to the agent: - `search_tools` - Search for available tools by keyword relevance - `load_tool` - Load a specific tool into the conversation The agent discovers what it needs via search and loads tools on demand. Loaded tools are available immediately and persist within the conversation thread. **Why:** When agents have access to 100+ tools (from MCP servers or integrations), including all tool definitions in the context can consume significant tokens (~1,500 tokens per tool). This pattern reduces context usage by giving agents only the tools they need, when they need them. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`e6fc281`](https://github.com/mastra-ai/mastra/commit/e6fc281896a3584e9e06465b356a44fe7faade65)) - Fixed processors returning `{ tools: {}, toolChoice: 'none' }` being ignored. Previously, when a processor returned empty tools with an explicit `toolChoice: 'none'` to prevent tool calls, the toolChoice was discarded and defaulted to 'auto'. This fix preserves the explicit 'none' value, enabling patterns like ensuring a final text response when `maxSteps` is reached. ([#12601](https://github.com/mastra-ai/mastra/pull/12601)) - Fix moonshotai/kimi-k2.5 multi-step tool calling failing with "reasoning_content is missing in assistant tool call message" ([#12530](https://github.com/mastra-ai/mastra/pull/12530)) - Changed moonshotai and moonshotai-cn (China version) providers to use Anthropic-compatible API endpoints instead of OpenAI-compatible - moonshotai: `https://api.moonshot.ai/anthropic/v1` - moonshotai-cn: `https://api.moonshot.cn/anthropic/v1` - This properly handles reasoning_content for kimi-k2.5 model - Fixed custom input processors from disabling workspace skill tools in generate() and stream(). Custom processors now replace only the processors you configured, while memory and skills remain available. Fixes #12612. ([#12676](https://github.com/mastra-ai/mastra/pull/12676)) - **Fixed** ([#12673](https://github.com/mastra-ai/mastra/pull/12673)) Workspace search index names now use underscores so they work with SQL-based vector stores (PgVector, LibSQL). **Added** You can now set a custom index name with `searchIndexName`. **Why** Some SQL vector stores reject hyphens in index names. **Example** ```ts // Before - would fail with PgVector new Workspace({ id: 'my-workspace', vectorStore, embedder }); // After - works with all vector stores new Workspace({ id: 'my-workspace', vectorStore, embedder }); // Or use a custom index name new Workspace({ vectorStore, embedder, searchIndexName: 'my_workspace_vectors' }); ``` Fixes #12656 - Catch up evented workflows on parity with default execution engine ([#12555](https://github.com/mastra-ai/mastra/pull/12555)) - Expose token usage from embedding operations ([#12556](https://github.com/mastra-ai/mastra/pull/12556)) - `saveMessages` now returns `usage: { tokens: number }` with aggregated token count from all embeddings - `recall` now returns `usage: { tokens: number }` from the vector search query embedding - Updated abstract method signatures in `MastraMemory` to include optional `usage` in return types This allows users to track embedding token usage when using the Memory class. - Fixed a security issue where sensitive observability credentials (such as Langfuse API keys) could be exposed in tool execution error logs. The tracingContext is now properly excluded from logged data. ([#12669](https://github.com/mastra-ai/mastra/pull/12669)) - Fixed issue where some models incorrectly call skill names directly as tools instead of using skill-activate. Added clearer system instructions that explicitly state skills are NOT tools and must be activated via skill-activate with the skill name as the "name" parameter. Fixes #12654. ([#12677](https://github.com/mastra-ai/mastra/pull/12677)) - Improved workspace filesystem error handling: return 404 for not-found errors instead of 500, show user-friendly error messages in UI, and add MastraClientError class with status/body properties for better error handling ([#12533](https://github.com/mastra-ai/mastra/pull/12533)) - Improved workspace tool descriptions with clearer usage guidance for read_file, edit_file, and execute_command tools. ([#12640](https://github.com/mastra-ai/mastra/pull/12640)) - Fixed JSON parsing in agent network to handle malformed LLM output. Uses parsePartialJson from AI SDK to recover truncated JSON, missing braces, and unescaped control characters instead of failing immediately. This reduces unnecessary retry round-trips when the routing agent generates slightly malformed JSON for tool/workflow prompts. Fixes #12519. ([#12526](https://github.com/mastra-ai/mastra/pull/12526)) - Updated dependencies [[`abae238`](https://github.com/mastra-ai/mastra/commit/abae238c755ebaf867bbfa1a3a219ef003a1021a)]: - @mastra/schema-compat@1.1.0 ## 1.2.0-alpha.1 ### Minor Changes - Added Observational Memory — a new memory system that keeps your agent's context window small while preserving long-term memory across conversations. ([#12599](https://github.com/mastra-ai/mastra/pull/12599)) **Why:** Long conversations cause context rot and waste tokens. Observational Memory compresses conversation history into observations (5–40x compression) and periodically condenses those into reflections. Your agent stays fast and focused, even after thousands of messages. **Usage:** ```ts import { Memory } from '@mastra/memory'; import { PostgresStore } from '@mastra/pg'; const memory = new Memory({ storage: new PostgresStore({ connectionString: process.env.DATABASE_URL }), options: { observationalMemory: true, }, }); const agent = new Agent({ name: 'my-agent', model: openai('gpt-4o'), memory, }); ``` **What's new:** - `observationalMemory: true` enables the three-tier memory system (recent messages → observations → reflections) - Thread-scoped (per-conversation) and resource-scoped (shared across all threads for a user) modes - Manual `observe()` API for triggering observation outside the normal agent loop - New OM storage methods for pg, libsql, and mongodb adapters (conditionally enabled) - `Agent.findProcessor()` method for looking up processors by ID - `processorStates` for persisting processor state across loop iterations - Abort signal propagation to processors - `ProcessorStreamWriter` for custom stream events from processors - Created @mastra/editor package for managing and resolving stored agent configurations ([#12631](https://github.com/mastra-ai/mastra/pull/12631)) This major addition introduces the editor package, which provides a complete solution for storing, versioning, and instantiating agent configurations from a database. The editor seamlessly integrates with Mastra's storage layer to enable dynamic agent management. **Key Features:** - **Agent Storage & Retrieval**: Store complete agent configurations including instructions, model settings, tools, workflows, nested agents, scorers, processors, and memory configuration - **Version Management**: Create and manage multiple versions of agents, with support for activating specific versions - **Dependency Resolution**: Automatically resolves and instantiates all agent dependencies (tools, workflows, sub-agents, etc.) from the Mastra registry - **Caching**: Built-in caching for improved performance when repeatedly accessing stored agents - **Type Safety**: Full TypeScript support with proper typing for stored configurations **Usage Example:** ```typescript import { MastraEditor } from '@mastra/editor'; import { Mastra } from '@mastra/core'; // Initialize editor with Mastra const mastra = new Mastra({ /* config */ editor: new MastraEditor(), }); // Store an agent configuration const agentId = await mastra.storage.stores?.agents?.createAgent({ name: 'customer-support', instructions: 'Help customers with inquiries', model: { provider: 'openai', name: 'gpt-4' }, tools: ['search-kb', 'create-ticket'], workflows: ['escalation-flow'], memory: { vector: 'pinecone-db' }, }); // Retrieve and use the stored agent const agent = await mastra.getEditor()?.getStoredAgentById(agentId); const response = await agent?.generate('How do I reset my password?'); // List all stored agents const agents = await mastra.getEditor()?.listStoredAgents({ pageSize: 10 }); ``` **Storage Improvements:** - Fixed JSONB handling in LibSQL, PostgreSQL, and MongoDB adapters - Improved agent resolution queries to properly merge version data - Enhanced type safety for serialized configurations - Added logger support to Workspace filesystem and sandbox providers. Providers extending MastraFilesystem or MastraSandbox now automatically receive the Mastra logger for consistent logging of file operations and command executions. ([#12606](https://github.com/mastra-ai/mastra/pull/12606)) ### Patch Changes - Fixed custom input processors from disabling workspace skill tools in generate() and stream(). Custom processors now replace only the processors you configured, while memory and skills remain available. Fixes #12612. ([#12676](https://github.com/mastra-ai/mastra/pull/12676)) - **Fixed** ([#12673](https://github.com/mastra-ai/mastra/pull/12673)) Workspace search index names now use underscores so they work with SQL-based vector stores (PgVector, LibSQL). **Added** You can now set a custom index name with `searchIndexName`. **Why** Some SQL vector stores reject hyphens in index names. **Example** ```ts // Before - would fail with PgVector new Workspace({ id: 'my-workspace', vectorStore, embedder }); // After - works with all vector stores new Workspace({ id: 'my-workspace', vectorStore, embedder }); // Or use a custom index name new Workspace({ vectorStore, embedder, searchIndexName: 'my_workspace_vectors' }); ``` Fixes #12656 - Fixed a security issue where sensitive observability credentials (such as Langfuse API keys) could be exposed in tool execution error logs. The tracingContext is now properly excluded from logged data. ([#12669](https://github.com/mastra-ai/mastra/pull/12669)) - Fixed issue where some models incorrectly call skill names directly as tools instead of using skill-activate. Added clearer system instructions that explicitly state skills are NOT tools and must be activated via skill-activate with the skill name as the "name" parameter. Fixes #12654. ([#12677](https://github.com/mastra-ai/mastra/pull/12677)) - Improved workspace tool descriptions with clearer usage guidance for read_file, edit_file, and execute_command tools. ([#12640](https://github.com/mastra-ai/mastra/pull/12640)) - Updated dependencies [[`abae238`](https://github.com/mastra-ai/mastra/commit/abae238c755ebaf867bbfa1a3a219ef003a1021a)]: - @mastra/schema-compat@1.1.0-alpha.0 ## 1.2.0-alpha.0 ### Minor Changes - Added ToolSearchProcessor for dynamic tool discovery. ([#12290](https://github.com/mastra-ai/mastra/pull/12290)) Agents can now discover and load tools on demand instead of having all tools available upfront. This reduces context token usage by ~94% when working with large tool libraries. **New API:** ```typescript import { ToolSearchProcessor } from '@mastra/core/processors'; import { Agent } from '@mastra/core'; // Create a processor with searchable tools const toolSearch = new ToolSearchProcessor({ tools: { createIssue: githubTools.createIssue, sendEmail: emailTools.send, // ... hundreds of tools }, search: { topK: 5, // Return top 5 results (default: 5) minScore: 0.1, // Filter results below this score (default: 0) }, }); // Attach processor to agent const agent = new Agent({ name: 'my-agent', inputProcessors: [toolSearch], tools: {/* always-available tools */}, }); ``` **How it works:** The processor automatically provides two meta-tools to the agent: - `search_tools` - Search for available tools by keyword relevance - `load_tool` - Load a specific tool into the conversation The agent discovers what it needs via search and loads tools on demand. Loaded tools are available immediately and persist within the conversation thread. **Why:** When agents have access to 100+ tools (from MCP servers or integrations), including all tool definitions in the context can consume significant tokens (~1,500 tokens per tool). This pattern reduces context usage by giving agents only the tools they need, when they need them. ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`e6fc281`](https://github.com/mastra-ai/mastra/commit/e6fc281896a3584e9e06465b356a44fe7faade65)) - Fixed processors returning `{ tools: {}, toolChoice: 'none' }` being ignored. Previously, when a processor returned empty tools with an explicit `toolChoice: 'none'` to prevent tool calls, the toolChoice was discarded and defaulted to 'auto'. This fix preserves the explicit 'none' value, enabling patterns like ensuring a final text response when `maxSteps` is reached. ([#12601](https://github.com/mastra-ai/mastra/pull/12601)) - Fix moonshotai/kimi-k2.5 multi-step tool calling failing with "reasoning_content is missing in assistant tool call message" ([#12530](https://github.com/mastra-ai/mastra/pull/12530)) - Changed moonshotai and moonshotai-cn (China version) providers to use Anthropic-compatible API endpoints instead of OpenAI-compatible - moonshotai: `https://api.moonshot.ai/anthropic/v1` - moonshotai-cn: `https://api.moonshot.cn/anthropic/v1` - This properly handles reasoning_content for kimi-k2.5 model - Catch up evented workflows on parity with default execution engine ([#12555](https://github.com/mastra-ai/mastra/pull/12555)) - Expose token usage from embedding operations ([#12556](https://github.com/mastra-ai/mastra/pull/12556)) - `saveMessages` now returns `usage: { tokens: number }` with aggregated token count from all embeddings - `recall` now returns `usage: { tokens: number }` from the vector search query embedding - Updated abstract method signatures in `MastraMemory` to include optional `usage` in return types This allows users to track embedding token usage when using the Memory class. - Improved workspace filesystem error handling: return 404 for not-found errors instead of 500, show user-friendly error messages in UI, and add MastraClientError class with status/body properties for better error handling ([#12533](https://github.com/mastra-ai/mastra/pull/12533)) - Fixed JSON parsing in agent network to handle malformed LLM output. Uses parsePartialJson from AI SDK to recover truncated JSON, missing braces, and unescaped control characters instead of failing immediately. This reduces unnecessary retry round-trips when the routing agent generates slightly malformed JSON for tool/workflow prompts. Fixes #12519. ([#12526](https://github.com/mastra-ai/mastra/pull/12526)) ## 1.1.0 ### Minor Changes - Restructured stored agents to use a thin metadata record with versioned configuration snapshots. ([#12488](https://github.com/mastra-ai/mastra/pull/12488)) The agent record now only stores metadata fields (id, status, activeVersionId, authorId, metadata, timestamps). All configuration fields (name, instructions, model, tools, etc.) live exclusively in version snapshot rows, enabling full version history and rollback. **Key changes:** - Stored Agent records are now thin metadata-only (StorageAgentType) - All config lives in version snapshots (StorageAgentSnapshotType) - New resolved type (StorageResolvedAgentType) merges agent record + active version config - Renamed `ownerId` to `authorId` for multi-tenant filtering - Changed `memory` field type from `string` to `Record` - Added `status` field ('draft' | 'published') to agent records - Flattened CreateAgent/UpdateAgent input types (config fields at top level, no nested snapshot) - Version config columns are top-level in the agent_versions table (no single snapshot jsonb column) - List endpoints return resolved agents (thin record + active version config) - Auto-versioning on update with retention limits and race condition handling - Added dynamic agent management with CRUD operations and version tracking ([#12038](https://github.com/mastra-ai/mastra/pull/12038)) **New Features:** - Create, edit, and delete agents directly from the Mastra Studio UI - Full version history for agents with compare and restore capabilities - Visual diff viewer to compare agent configurations across versions - Agent creation modal with comprehensive configuration options (model selection, instructions, tools, workflows, sub-agents, memory) - AI-powered instruction enhancement **Storage:** - New storage interfaces for stored agents and agent versions - PostgreSQL, LibSQL, and MongoDB implementations included - In-memory storage for development and testing **API:** - RESTful endpoints for agent CRUD operations - Version management endpoints (create, list, activate, restore, delete, compare) - Automatic versioning on agent updates when enabled **Client SDK:** - JavaScript client with full support for stored agents and versions - Type-safe methods for all CRUD and version operations **Usage Example:** ```typescript // Server-side: Configure storage import { Mastra } from '@mastra/core'; import { PgAgentsStorage } from '@mastra/pg'; const mastra = new Mastra({ agents: { agentOne }, storage: { agents: new PgAgentsStorage({ connectionString: process.env.DATABASE_URL, }), }, }); // Client-side: Use the SDK import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:3000' }); // Create a stored agent const agent = await client.createStoredAgent({ name: 'Customer Support Agent', description: 'Handles customer inquiries', model: { provider: 'ANTHROPIC', name: 'claude-sonnet-4-5' }, instructions: 'You are a helpful customer support agent...', tools: ['search', 'email'], }); // Create a version snapshot await client.storedAgent(agent.id).createVersion({ name: 'v1.0 - Initial release', changeMessage: 'First production version', }); // Compare versions const diff = await client.storedAgent(agent.id).compareVersions('version-1', 'version-2'); ``` **Why:** This feature enables teams to manage agents dynamically without code changes, making it easier to iterate on agent configurations and maintain a complete audit trail of changes. - Added unified Workspace API for agent filesystem access, code execution, and search capabilities. ([#11986](https://github.com/mastra-ai/mastra/pull/11986)) **New Workspace class** combines filesystem, sandbox, and search into a single interface that agents can use for file operations, command execution, and content search. **Key features:** - Filesystem operations (read, write, copy, move, delete) through pluggable providers - Code and command execution in secure sandboxed environments with optional OS-level isolation - Keyword search, semantic search, and hybrid search modes - Skills system for discovering and using SKILL.md instruction files - Safety controls including read-before-write guards, approval flows, and read-only mode **Usage:** ```typescript import { Workspace, LocalFilesystem, LocalSandbox } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), sandbox: new LocalSandbox({ workingDirectory: './workspace' }), bm25: true, }); const agent = new Agent({ workspace, // Agent automatically receives workspace tools }); ``` - Added `status` field to `listTraces` response. The status field indicates the trace state: `success` (completed without error), `error` (has error), or `running` (still in progress). This makes it easier to filter and display traces by their current state without having to derive it from the `error` and `endedAt` fields. ([#12213](https://github.com/mastra-ai/mastra/pull/12213)) - Added `RequestContext.all` to access the entire `RequestContext` object values. ([#12259](https://github.com/mastra-ai/mastra/pull/12259)) ```typescript const { userId, featureFlags } = requestContext.all; ``` Added `requestContextSchema` support to tools, agents, workflows, and steps. Define a Zod schema to validate and type requestContext values at runtime. **Tool example:** ```typescript import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const myTool = createTool({ id: 'my-tool', inputSchema: z.object({ query: z.string() }), requestContextSchema: z.object({ userId: z.string(), apiKey: z.string(), }), execute: async (input, context) => { // context.requestContext is typed as RequestContext<{ userId: string, apiKey: string }> const userId = context.requestContext?.get('userId'); return { result: 'success' }; }, }); ``` **Agent example:** ```typescript import { Agent } from '@mastra/core/agent'; import { z } from 'zod'; const agent = new Agent({ name: 'my-agent', model: openai('gpt-4o'), requestContextSchema: z.object({ userId: z.string(), featureFlags: z .object({ debugMode: z.boolean().optional(), enableSearch: z.boolean().optional(), }) .optional(), }), instructions: ({ requestContext }) => { // Access validated context values with type safety const { userId, featureFlags } = requestContext.all; const baseInstructions = `You are a helpful assistant. The current user ID is: ${userId}.`; if (featureFlags?.debugMode) { return `${baseInstructions} Debug mode is enabled - provide verbose responses.`; } return baseInstructions; }, tools: ({ requestContext }) => { const tools: Record = { weatherInfo, }; // Conditionally add tools based on validated feature flags const { featureFlags } = requestContext.all; if (featureFlags?.enableSearch) { tools['web_search_preview'] = openai.tools.webSearchPreview(); } return tools; }, }); ``` **Workflow example:** ```typescript import { createWorkflow } from '@mastra/core/workflows'; import { z } from 'zod'; const workflow = createWorkflow({ id: 'my-workflow', inputSchema: z.object({ data: z.string() }), requestContextSchema: z.object({ tenantId: z.string(), }), }); const step = createStep({ id: 'my-step', description: 'My step description', inputSchema: z.object({ data: z.string() }), outputSchema: z.object({ result: z.string() }), requestContextSchema: z.object({ userId: z.string(), }), execute: async ({ inputData, requestContext }) => { const userId = requestContext?.get('userId'); return { result: 'some result here', }; }, }); workflow.then(step).commit(); ``` When requestContextSchema is defined, validation runs automatically and throws an error if required context values are missing or invalid. ### Patch Changes - dependencies updates: ([#10184](https://github.com/mastra-ai/mastra/pull/10184)) - Updated dependency [`@isaacs/ttlcache@^2.1.4` ↗︎](https://www.npmjs.com/package/@isaacs/ttlcache/v/2.1.4) (from `^1.4.1`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`1cf5d2e`](https://github.com/mastra-ai/mastra/commit/1cf5d2ea1b085be23e34fb506c80c80a4e6d9c2b)) - Fixed skill loading error caused by Zod version conflicts between v3 and v4. Replaced Zod schemas with plain TypeScript validation functions in skill metadata validation. ([#12485](https://github.com/mastra-ai/mastra/pull/12485)) - Fix model router routing providers that use non-default AI SDK packages (e.g. `@ai-sdk/anthropic`, `@ai-sdk/openai`) to their correct SDK instead of falling back to `openai-compatible`. Add `cerebras`, `togetherai`, and `deepinfra` as native SDK providers. ([#12450](https://github.com/mastra-ai/mastra/pull/12450)) - Make suspendedToolRunId nullable to fix the null issue in tool input validation ([#12303](https://github.com/mastra-ai/mastra/pull/12303)) - Fixed agent.network() to properly pass requestContext to workflow runs. Workflow execution now includes user metadata (userId, resourceId) for observability and analytics. (Fixes #12330) ([#12379](https://github.com/mastra-ai/mastra/pull/12379)) - Fix ModelRouterLanguageModel to propagate supportedUrls from underlying model providers ([#12167](https://github.com/mastra-ai/mastra/pull/12167)) Previously, `ModelRouterLanguageModel` (used when specifying models as strings like `"mistral/mistral-large-latest"` or `"openai/gpt-4o"`) had `supportedUrls` hardcoded as an empty object. This caused Mastra to download all file URLs and convert them to bytes/base64, even when the model provider supports URLs natively. This fix: - Changes `supportedUrls` to a lazy `PromiseLike` that resolves the underlying model's supported URL patterns - Updates `llm-execution-step.ts` to properly await `supportedUrls` when preparing messages **Impact:** - Mistral: PDF URLs are now passed directly (fixes #12152) - OpenAI: Image URLs (and PDF URLs in response models) are now passed directly - Anthropic: Image URLs are now passed directly - Google: Files from Google endpoints are now passed directly **Note:** Users who were relying on Mastra to download files from URLs that model providers cannot directly access (internal URLs, auth-protected URLs) may need to adjust their approach by either using base64-encoded content or ensuring URLs are publicly accessible to the model provider. - Extended readOnly memory option to also apply to working memory. When readOnly: true, working memory data is provided as context but the updateWorkingMemory tool is not available. ([#12471](https://github.com/mastra-ai/mastra/pull/12471)) **Example:** ```typescript // Working memory is loaded but agent cannot update it const response = await agent.generate('What do you know about me?', { memory: { thread: 'conversation-123', resource: 'user-alice-456', options: { readOnly: true }, }, }); ``` - fix(core): skip non-serializable values in RequestContext.toJSON ([#12344](https://github.com/mastra-ai/mastra/pull/12344)) - Fixed TypeScript error when calling bail() in workflow steps. bail() now accepts any value, so workflows can exit early with a custom result. Fixes #12424. ([#12429](https://github.com/mastra-ai/mastra/pull/12429)) - Fixed type error when using createTool with Agent when exactOptionalPropertyTypes is enabled in TypeScript config. The ProviderDefinedTool structural type now correctly marks inputSchema as optional and allows execute to be undefined, matching the ToolAction interface. Fixes #12281 ([#12325](https://github.com/mastra-ai/mastra/pull/12325)) - Fixed tracingOptions.tags not being preserved when merging defaultOptions with call-site options. Tags set in agent's defaultOptions.tracingOptions are now correctly passed to all observability exporters (Langfuse, Langsmith, Braintrust, Datadog, etc.). Fixes #12209. ([#12220](https://github.com/mastra-ai/mastra/pull/12220)) - Added activeTools parameter support to model loop stream. The activeTools parameter can now be passed through the ModelLoopStreamArgs to control which tools are available during LLM execution. ([#12082](https://github.com/mastra-ai/mastra/pull/12082)) - Fixed agent network crashing with 'Invalid task input' error when routing agent returns malformed JSON for tool/workflow prompts. The error is now fed back to the routing agent, allowing it to retry with valid JSON on the next iteration. ([#12486](https://github.com/mastra-ai/mastra/pull/12486)) - Fixed type error when passing MastraVoice implementations (like OpenAIVoice) directly to Agent's voice config. Previously, the voice property only accepted CompositeVoice, requiring users to wrap their voice provider. Now you can pass any MastraVoice implementation directly. ([#12329](https://github.com/mastra-ai/mastra/pull/12329)) **Before (required wrapper):** ```typescript const agent = new Agent({ voice: new CompositeVoice({ output: new OpenAIVoice() }), }); ``` **After (direct usage):** ```typescript const agent = new Agent({ voice: new OpenAIVoice(), }); ``` Fixes #12293 - Fixed output processors not being applied to messages saved during network execution. When using agent.network(), configured output processors (like TraceIdInjector for feedback attribution) are now correctly applied to all messages before they are saved to storage. ([#12346](https://github.com/mastra-ai/mastra/pull/12346)) - Removed deprecated Google `text-embedding-004` embedding model from the model router. Google shut down this model on January 14, 2026. Use `google/gemini-embedding-001` instead. ([#12433](https://github.com/mastra-ai/mastra/pull/12433)) - Fixed tool input validation failing when LLMs send null for optional fields (#12362). Zod's .optional() only accepts undefined, not null, causing validation errors with Gemini and other LLMs. Validation now retries with null values stripped when the initial attempt fails, so .optional() fields accept null while .nullable() fields continue to work correctly. ([#12396](https://github.com/mastra-ai/mastra/pull/12396)) - Tracing fixes: ([#12370](https://github.com/mastra-ai/mastra/pull/12370)) - Spans now inherit entityType/entityId from the closest non-internal parent (#12250) - Processor spans correctly track separate input and output data - Model chunk spans are now emitted for all streaming chunks - Internal framework spans no longer appear in exported traces - Fixed generated provider types so IDs starting with digits no longer break TypeScript builds ([#12418](https://github.com/mastra-ai/mastra/pull/12418)) - Fixed network mode not applying user-configured input/output processors (like token limiters) to the routing agent. This caused unbounded context growth during network iterations. ([#12074](https://github.com/mastra-ai/mastra/pull/12074)) User-configured processors are now correctly passed to the routing agent, while memory-derived processors (which could interfere with routing logic) are excluded. Fixes #12016 - Fixed repeated build failures caused by stale global cache (~/.cache/mastra/) containing invalid TypeScript in provider-types.generated.d.ts. Provider names starting with digits (e.g. 302ai) are now properly quoted, and the global cache sync validates .d.ts files before copying to prevent corrupted files from overwriting correct ones. ([#12425](https://github.com/mastra-ai/mastra/pull/12425)) - Update @isaacs/ttlcache to v2 and fix import for v2 compatibility (changed from default to named export) ([#10184](https://github.com/mastra-ai/mastra/pull/10184)) - Fixed custom data parts from writer.custom() breaking subsequent messages with Gemini. Messages containing only data-\* parts no longer produce empty content arrays that cause Gemini to fail with 'must include at least one parts field'. ([#12373](https://github.com/mastra-ai/mastra/pull/12373)) - Improve autoresume prompt sent to LLM to ensure gemini resumes well. ([#12320](https://github.com/mastra-ai/mastra/pull/12320)) Gemini sometimes doesn't use the previous messages to create inputData for the tool to resume, the prompt was updated to make sure it gets the inputData from the suspended tool call. - Fixed sub-agents in Agent Networks seeing completion-check feedback from prior iterations, so delegated agents stay focused on their task. Fixes #12224 ([#12338](https://github.com/mastra-ai/mastra/pull/12338)) - Fix TypeScript types for custom API route handlers to include `requestContext` in Hono context Variables. Previously, only `mastra` was typed, causing TypeScript errors when accessing `c.get('requestContext')` even though the runtime correctly provided this context. ([#12419](https://github.com/mastra-ai/mastra/pull/12419)) - Let callers cancel a running agent network call and handle abort callbacks. ([#12351](https://github.com/mastra-ai/mastra/pull/12351)) **Example** Before: ```ts const stream = await agent.network(task); ``` After: ```ts const controller = new AbortController(); const stream = await agent.network(task, { abortSignal: controller.signal, onAbort: ({ primitiveType, primitiveId }) => { logger.info(`Aborted ${primitiveType}:${primitiveId}`); }, }); controller.abort(); ``` Related issue: `#12282` ## 1.1.0-alpha.2 ## 1.1.0-alpha.1 ### Minor Changes - Restructured stored agents to use a thin metadata record with versioned configuration snapshots. ([#12488](https://github.com/mastra-ai/mastra/pull/12488)) The agent record now only stores metadata fields (id, status, activeVersionId, authorId, metadata, timestamps). All configuration fields (name, instructions, model, tools, etc.) live exclusively in version snapshot rows, enabling full version history and rollback. **Key changes:** - Stored Agent records are now thin metadata-only (StorageAgentType) - All config lives in version snapshots (StorageAgentSnapshotType) - New resolved type (StorageResolvedAgentType) merges agent record + active version config - Renamed `ownerId` to `authorId` for multi-tenant filtering - Changed `memory` field type from `string` to `Record` - Added `status` field ('draft' | 'published') to agent records - Flattened CreateAgent/UpdateAgent input types (config fields at top level, no nested snapshot) - Version config columns are top-level in the agent_versions table (no single snapshot jsonb column) - List endpoints return resolved agents (thin record + active version config) - Auto-versioning on update with retention limits and race condition handling ### Patch Changes - Fixed skill loading error caused by Zod version conflicts between v3 and v4. Replaced Zod schemas with plain TypeScript validation functions in skill metadata validation. ([#12485](https://github.com/mastra-ai/mastra/pull/12485)) - Fixed agent network crashing with 'Invalid task input' error when routing agent returns malformed JSON for tool/workflow prompts. The error is now fed back to the routing agent, allowing it to retry with valid JSON on the next iteration. ([#12486](https://github.com/mastra-ai/mastra/pull/12486)) ## 1.1.0-alpha.0 ### Minor Changes - Added dynamic agent management with CRUD operations and version tracking ([#12038](https://github.com/mastra-ai/mastra/pull/12038)) **New Features:** - Create, edit, and delete agents directly from the Mastra Studio UI - Full version history for agents with compare and restore capabilities - Visual diff viewer to compare agent configurations across versions - Agent creation modal with comprehensive configuration options (model selection, instructions, tools, workflows, sub-agents, memory) - AI-powered instruction enhancement **Storage:** - New storage interfaces for stored agents and agent versions - PostgreSQL, LibSQL, and MongoDB implementations included - In-memory storage for development and testing **API:** - RESTful endpoints for agent CRUD operations - Version management endpoints (create, list, activate, restore, delete, compare) - Automatic versioning on agent updates when enabled **Client SDK:** - JavaScript client with full support for stored agents and versions - Type-safe methods for all CRUD and version operations **Usage Example:** ```typescript // Server-side: Configure storage import { Mastra } from '@mastra/core'; import { PgAgentsStorage } from '@mastra/pg'; const mastra = new Mastra({ agents: { agentOne }, storage: { agents: new PgAgentsStorage({ connectionString: process.env.DATABASE_URL, }), }, }); // Client-side: Use the SDK import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:3000' }); // Create a stored agent const agent = await client.createStoredAgent({ name: 'Customer Support Agent', description: 'Handles customer inquiries', model: { provider: 'ANTHROPIC', name: 'claude-sonnet-4-5' }, instructions: 'You are a helpful customer support agent...', tools: ['search', 'email'], }); // Create a version snapshot await client.storedAgent(agent.id).createVersion({ name: 'v1.0 - Initial release', changeMessage: 'First production version', }); // Compare versions const diff = await client.storedAgent(agent.id).compareVersions('version-1', 'version-2'); ``` **Why:** This feature enables teams to manage agents dynamically without code changes, making it easier to iterate on agent configurations and maintain a complete audit trail of changes. - Added unified Workspace API for agent filesystem access, code execution, and search capabilities. ([#11986](https://github.com/mastra-ai/mastra/pull/11986)) **New Workspace class** combines filesystem, sandbox, and search into a single interface that agents can use for file operations, command execution, and content search. **Key features:** - Filesystem operations (read, write, copy, move, delete) through pluggable providers - Code and command execution in secure sandboxed environments with optional OS-level isolation - Keyword search, semantic search, and hybrid search modes - Skills system for discovering and using SKILL.md instruction files - Safety controls including read-before-write guards, approval flows, and read-only mode **Usage:** ```typescript import { Workspace, LocalFilesystem, LocalSandbox } from '@mastra/core/workspace'; const workspace = new Workspace({ filesystem: new LocalFilesystem({ basePath: './workspace' }), sandbox: new LocalSandbox({ workingDirectory: './workspace' }), bm25: true, }); const agent = new Agent({ workspace, // Agent automatically receives workspace tools }); ``` - Added `status` field to `listTraces` response. The status field indicates the trace state: `success` (completed without error), `error` (has error), or `running` (still in progress). This makes it easier to filter and display traces by their current state without having to derive it from the `error` and `endedAt` fields. ([#12213](https://github.com/mastra-ai/mastra/pull/12213)) - Added `RequestContext.all` to access the entire `RequestContext` object values. ([#12259](https://github.com/mastra-ai/mastra/pull/12259)) ```typescript const { userId, featureFlags } = requestContext.all; ``` Added `requestContextSchema` support to tools, agents, workflows, and steps. Define a Zod schema to validate and type requestContext values at runtime. **Tool example:** ```typescript import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; const myTool = createTool({ id: 'my-tool', inputSchema: z.object({ query: z.string() }), requestContextSchema: z.object({ userId: z.string(), apiKey: z.string(), }), execute: async (input, context) => { // context.requestContext is typed as RequestContext<{ userId: string, apiKey: string }> const userId = context.requestContext?.get('userId'); return { result: 'success' }; }, }); ``` **Agent example:** ```typescript import { Agent } from '@mastra/core/agent'; import { z } from 'zod'; const agent = new Agent({ name: 'my-agent', model: openai('gpt-4o'), requestContextSchema: z.object({ userId: z.string(), featureFlags: z .object({ debugMode: z.boolean().optional(), enableSearch: z.boolean().optional(), }) .optional(), }), instructions: ({ requestContext }) => { // Access validated context values with type safety const { userId, featureFlags } = requestContext.all; const baseInstructions = `You are a helpful assistant. The current user ID is: ${userId}.`; if (featureFlags?.debugMode) { return `${baseInstructions} Debug mode is enabled - provide verbose responses.`; } return baseInstructions; }, tools: ({ requestContext }) => { const tools: Record = { weatherInfo, }; // Conditionally add tools based on validated feature flags const { featureFlags } = requestContext.all; if (featureFlags?.enableSearch) { tools['web_search_preview'] = openai.tools.webSearchPreview(); } return tools; }, }); ``` **Workflow example:** ```typescript import { createWorkflow } from '@mastra/core/workflows'; import { z } from 'zod'; const workflow = createWorkflow({ id: 'my-workflow', inputSchema: z.object({ data: z.string() }), requestContextSchema: z.object({ tenantId: z.string(), }), }); const step = createStep({ id: 'my-step', description: 'My step description', inputSchema: z.object({ data: z.string() }), outputSchema: z.object({ result: z.string() }), requestContextSchema: z.object({ userId: z.string(), }), execute: async ({ inputData, requestContext }) => { const userId = requestContext?.get('userId'); return { result: 'some result here', }; }, }); workflow.then(step).commit(); ``` When requestContextSchema is defined, validation runs automatically and throws an error if required context values are missing or invalid. ### Patch Changes - dependencies updates: ([#10184](https://github.com/mastra-ai/mastra/pull/10184)) - Updated dependency [`@isaacs/ttlcache@^2.1.4` ↗︎](https://www.npmjs.com/package/@isaacs/ttlcache/v/2.1.4) (from `^1.4.1`, in `dependencies`) - Update provider registry and model documentation with latest models and providers ([`1cf5d2e`](https://github.com/mastra-ai/mastra/commit/1cf5d2ea1b085be23e34fb506c80c80a4e6d9c2b)) - Fix model router routing providers that use non-default AI SDK packages (e.g. `@ai-sdk/anthropic`, `@ai-sdk/openai`) to their correct SDK instead of falling back to `openai-compatible`. Add `cerebras`, `togetherai`, and `deepinfra` as native SDK providers. ([#12450](https://github.com/mastra-ai/mastra/pull/12450)) - Make suspendedToolRunId nullable to fix the null issue in tool input validation ([#12303](https://github.com/mastra-ai/mastra/pull/12303)) - Fixed agent.network() to properly pass requestContext to workflow runs. Workflow execution now includes user metadata (userId, resourceId) for observability and analytics. (Fixes #12330) ([#12379](https://github.com/mastra-ai/mastra/pull/12379)) - Fix ModelRouterLanguageModel to propagate supportedUrls from underlying model providers ([#12167](https://github.com/mastra-ai/mastra/pull/12167)) Previously, `ModelRouterLanguageModel` (used when specifying models as strings like `"mistral/mistral-large-latest"` or `"openai/gpt-4o"`) had `supportedUrls` hardcoded as an empty object. This caused Mastra to download all file URLs and convert them to bytes/base64, even when the model provider supports URLs natively. This fix: - Changes `supportedUrls` to a lazy `PromiseLike` that resolves the underlying model's supported URL patterns - Updates `llm-execution-step.ts` to properly await `supportedUrls` when preparing messages **Impact:** - Mistral: PDF URLs are now passed directly (fixes #12152) - OpenAI: Image URLs (and PDF URLs in response models) are now passed directly - Anthropic: Image URLs are now passed directly - Google: Files from Google endpoints are now passed directly **Note:** Users who were relying on Mastra to download files from URLs that model providers cannot directly access (internal URLs, auth-protected URLs) may need to adjust their approach by either using base64-encoded content or ensuring URLs are publicly accessible to the model provider. - Extended readOnly memory option to also apply to working memory. When readOnly: true, working memory data is provided as context but the updateWorkingMemory tool is not available. ([#12471](https://github.com/mastra-ai/mastra/pull/12471)) **Example:** ```typescript // Working memory is loaded but agent cannot update it const response = await agent.generate('What do you know about me?', { memory: { thread: 'conversation-123', resource: 'user-alice-456', options: { readOnly: true }, }, }); ``` - fix(core): skip non-serializable values in RequestContext.toJSON ([#12344](https://github.com/mastra-ai/mastra/pull/12344)) - Fixed TypeScript error when calling bail() in workflow steps. bail() now accepts any value, so workflows can exit early with a custom result. Fixes #12424. ([#12429](https://github.com/mastra-ai/mastra/pull/12429)) - Fixed type error when using createTool with Agent when exactOptionalPropertyTypes is enabled in TypeScript config. The ProviderDefinedTool structural type now correctly marks inputSchema as optional and allows execute to be undefined, matching the ToolAction interface. Fixes #12281 ([#12325](https://github.com/mastra-ai/mastra/pull/12325)) - Fixed tracingOptions.tags not being preserved when merging defaultOptions with call-site options. Tags set in agent's defaultOptions.tracingOptions are now correctly passed to all observability exporters (Langfuse, Langsmith, Braintrust, Datadog, etc.). Fixes #12209. ([#12220](https://github.com/mastra-ai/mastra/pull/12220)) - Added activeTools parameter support to model loop stream. The activeTools parameter can now be passed through the ModelLoopStreamArgs to control which tools are available during LLM execution. ([#12082](https://github.com/mastra-ai/mastra/pull/12082)) - Fixed type error when passing MastraVoice implementations (like OpenAIVoice) directly to Agent's voice config. Previously, the voice property only accepted CompositeVoice, requiring users to wrap their voice provider. Now you can pass any MastraVoice implementation directly. ([#12329](https://github.com/mastra-ai/mastra/pull/12329)) **Before (required wrapper):** ```typescript const agent = new Agent({ voice: new CompositeVoice({ output: new OpenAIVoice() }), }); ``` **After (direct usage):** ```typescript const agent = new Agent({ voice: new OpenAIVoice(), }); ``` Fixes #12293 - Fixed output processors not being applied to messages saved during network execution. When using agent.network(), configured output processors (like TraceIdInjector for feedback attribution) are now correctly applied to all messages before they are saved to storage. ([#12346](https://github.com/mastra-ai/mastra/pull/12346)) - Removed deprecated Google `text-embedding-004` embedding model from the model router. Google shut down this model on January 14, 2026. Use `google/gemini-embedding-001` instead. ([#12433](https://github.com/mastra-ai/mastra/pull/12433)) - Fixed tool input validation failing when LLMs send null for optional fields (#12362). Zod's .optional() only accepts undefined, not null, causing validation errors with Gemini and other LLMs. Validation now retries with null values stripped when the initial attempt fails, so .optional() fields accept null while .nullable() fields continue to work correctly. ([#12396](https://github.com/mastra-ai/mastra/pull/12396)) - Tracing fixes: ([#12370](https://github.com/mastra-ai/mastra/pull/12370)) - Spans now inherit entityType/entityId from the closest non-internal parent (#12250) - Processor spans correctly track separate input and output data - Model chunk spans are now emitted for all streaming chunks - Internal framework spans no longer appear in exported traces - Fixed generated provider types so IDs starting with digits no longer break TypeScript builds ([#12418](https://github.com/mastra-ai/mastra/pull/12418)) - Fixed network mode not applying user-configured input/output processors (like token limiters) to the routing agent. This caused unbounded context growth during network iterations. ([#12074](https://github.com/mastra-ai/mastra/pull/12074)) User-configured processors are now correctly passed to the routing agent, while memory-derived processors (which could interfere with routing logic) are excluded. Fixes #12016 - Fixed repeated build failures caused by stale global cache (~/.cache/mastra/) containing invalid TypeScript in provider-types.generated.d.ts. Provider names starting with digits (e.g. 302ai) are now properly quoted, and the global cache sync validates .d.ts files before copying to prevent corrupted files from overwriting correct ones. ([#12425](https://github.com/mastra-ai/mastra/pull/12425)) - Update @isaacs/ttlcache to v2 and fix import for v2 compatibility (changed from default to named export) ([#10184](https://github.com/mastra-ai/mastra/pull/10184)) - Fixed custom data parts from writer.custom() breaking subsequent messages with Gemini. Messages containing only data-\* parts no longer produce empty content arrays that cause Gemini to fail with 'must include at least one parts field'. ([#12373](https://github.com/mastra-ai/mastra/pull/12373)) - Improve autoresume prompt sent to LLM to ensure gemini resumes well. ([#12320](https://github.com/mastra-ai/mastra/pull/12320)) Gemini sometimes doesn't use the previous messages to create inputData for the tool to resume, the prompt was updated to make sure it gets the inputData from the suspended tool call. - Fixed sub-agents in Agent Networks seeing completion-check feedback from prior iterations, so delegated agents stay focused on their task. Fixes #12224 ([#12338](https://github.com/mastra-ai/mastra/pull/12338)) - Fix TypeScript types for custom API route handlers to include `requestContext` in Hono context Variables. Previously, only `mastra` was typed, causing TypeScript errors when accessing `c.get('requestContext')` even though the runtime correctly provided this context. ([#12419](https://github.com/mastra-ai/mastra/pull/12419)) - Let callers cancel a running agent network call and handle abort callbacks. ([#12351](https://github.com/mastra-ai/mastra/pull/12351)) **Example** Before: ```ts const stream = await agent.network(task); ``` After: ```ts const controller = new AbortController(); const stream = await agent.network(task, { abortSignal: controller.signal, onAbort: ({ primitiveType, primitiveId }) => { logger.info(`Aborted ${primitiveType}:${primitiveId}`); }, }); controller.abort(); ``` Related issue: `#12282` ## 1.0.4 ## 1.0.4-alpha.0 ## 1.0.0 ### Major Changes - Moving scorers under the eval domain, api method consistency, prebuilt evals, scorers require ids. ([#9589](https://github.com/mastra-ai/mastra/pull/9589)) - Scorers for Agents will now use `MastraDBMessage` instead of `UIMessage` ([#9702](https://github.com/mastra-ai/mastra/pull/9702)) - Scorer input/output types now use `MastraDBMessage[]` with nested `content` object structure - Added `getTextContentFromMastraDBMessage()` helper function to extract text content from `MastraDBMessage` objects - Added `createTestMessage()` helper function for creating `MastraDBMessage` objects in tests with optional tool invocations support - Updated `extractToolCalls()` to access tool invocations from nested `content` structure - Updated `getUserMessageFromRunInput()` and `getAssistantMessageFromRunOutput()` to use new message structure - Removed `createUIMessage()` - Every Mastra primitive (agent, MCPServer, workflow, tool, processor, scorer, and vector) now has a get, list, and add method associated with it. Each primitive also now requires an id to be set. ([#9675](https://github.com/mastra-ai/mastra/pull/9675)) Primitives that are added to other primitives are also automatically added to the Mastra instance - Update handlers to use `listWorkflowRuns` instead of `getWorkflowRuns`. Fix type names from `StoragelistThreadsByResourceIdInput/Output` to `StorageListThreadsByResourceIdInput/Output`. ([#9507](https://github.com/mastra-ai/mastra/pull/9507)) - Refactor workflow and tool types to remove Zod-specific constraints ([#11814](https://github.com/mastra-ai/mastra/pull/11814)) Removed Zod-specific type constraints across all workflow implementations and tool types, replacing them with generic types. This ensures type consistency across default, evented, and inngest workflows while preparing for Zod v4 migration. **Workflow Changes:** - Removed `z.ZodObject` and `z.ZodType` constraints from all workflow generic types - Updated method signatures to use `TInput` and `TState` directly instead of `z.infer` and `z.infer` - Aligned conditional types across all workflow implementations using `TInput extends unknown` pattern - Fixed `TSteps` generic to properly use `TEngineType` instead of `any` **Tool Changes:** - Removed Zod schema constraints from `ToolExecutionContext` and related interfaces - Simplified type parameters from `TSuspendSchema extends ZodLikeSchema` to `TSuspend` and `TResume` - Updated tool execution context types to use generic types **Type Utilities:** - Refactored type helpers to work with generic schemas instead of Zod-specific types - Updated type extraction utilities for better compatibility This change maintains backward compatibility while improving type consistency and preparing for Zod v4 support across all affected packages. - Remove `getMessagesPaginated()` and add `perPage: false` support ([#9670](https://github.com/mastra-ai/mastra/pull/9670)) Removes deprecated `getMessagesPaginated()` method. The `listMessages()` API and score handlers now support `perPage: false` to fetch all records without pagination limits. **Storage changes:** - `StoragePagination.perPage` type changed from `number` to `number | false` - All storage implementations support `perPage: false`: - Memory: `listMessages()` - Scores: `listScoresBySpan()`, `listScoresByRunId()`, `listScoresByExecutionId()` - HTTP query parser accepts `"false"` string (e.g., `?perPage=false`) **Memory changes:** - `memory.query()` parameter type changed from `StorageGetMessagesArg` to `StorageListMessagesInput` - Uses flat parameters (`page`, `perPage`, `include`, `filter`, `vectorSearchString`) instead of `selectBy` object **Stricter validation:** - `listMessages()` requires non-empty, non-whitespace `threadId` (throws error instead of returning empty results) **Migration:** ```typescript // Storage/Memory: Replace getMessagesPaginated with listMessages - storage.getMessagesPaginated({ threadId, selectBy: { pagination: { page: 0, perPage: 20 } } }) + storage.listMessages({ threadId, page: 0, perPage: 20 }) + storage.listMessages({ threadId, page: 0, perPage: false }) // Fetch all // Memory: Replace selectBy with flat parameters - memory.query({ threadId, selectBy: { last: 20, include: [...] } }) + memory.query({ threadId, perPage: 20, include: [...] }) // Client SDK - thread.getMessagesPaginated({ selectBy: { pagination: { page: 0 } } }) + thread.listMessages({ page: 0, perPage: 20 }) ``` - **Removed `storage.getMessages()`** ([#9695](https://github.com/mastra-ai/mastra/pull/9695)) The `getMessages()` method has been removed from all storage implementations. Use `listMessages()` instead, which provides pagination support. **Migration:** ```typescript // Before const messages = await storage.getMessages({ threadId: 'thread-1' }); // After const result = await storage.listMessages({ threadId: 'thread-1', page: 0, perPage: 50, }); const messages = result.messages; // Access messages array console.log(result.total); // Total count console.log(result.hasMore); // Whether more pages exist ``` **Message ordering default** `listMessages()` defaults to ASC (oldest first) ordering by `createdAt`, matching the previous `getMessages()` behavior. **To use DESC ordering (newest first):** ```typescript const result = await storage.listMessages({ threadId: 'thread-1', orderBy: { field: 'createdAt', direction: 'DESC' }, }); ``` **Renamed `client.getThreadMessages()` → `client.listThreadMessages()`** **Migration:** ```typescript // Before const response = await client.getThreadMessages(threadId, { agentId }); // After const response = await client.listThreadMessages(threadId, { agentId }); ``` The response format remains the same. **Removed `StorageGetMessagesArg` type** Use `StorageListMessagesInput` instead: ```typescript // Before import type { StorageGetMessagesArg } from '@mastra/core'; // After import type { StorageListMessagesInput } from '@mastra/core'; ``` - - Removes modelSettings.abortSignal in favour of top-level abortSignal only. Also removes the deprecated output field - use structuredOutput.schema instead. ([`9e1911d`](https://github.com/mastra-ai/mastra/commit/9e1911db2b4db85e0e768c3f15e0d61e319869f6)) - The deprecated generateVNext() and streamVNext() methods have been removed since they're now the stable generate() and stream() methods. - The deprecated `output` option has been removed entirely, in favour of `structuredOutput`. Method renames to clarify the API surface: - getDefaultGenerateOptions → getDefaultGenerateOptionsLegacy - getDefaultStreamOptions → getDefaultStreamOptionsLegacy - getDefaultVNextStreamOptions → getDefaultStreamOptions - Bump minimum required Node.js version to 22.13.0 ([#9706](https://github.com/mastra-ai/mastra/pull/9706)) - Replace `getThreadsByResourceIdPaginated` with `listThreadsByResourceId` across memory handlers. Update client SDK to use `listThreads()` with `offset`/`limit` parameters instead of deprecated `getMemoryThreads()`. Consolidate `/api/memory/threads` routes to single paginated endpoint. ([#9508](https://github.com/mastra-ai/mastra/pull/9508)) - Add new list methods to storage API: `listMessages`, `listMessagesById`, `listThreadsByResourceId`, and `listWorkflowRuns`. Most methods are currently wrappers around existing methods. Full implementations will be added when migrating away from legacy methods. ([#9489](https://github.com/mastra-ai/mastra/pull/9489)) - Update tool execution signature ([#9587](https://github.com/mastra-ai/mastra/pull/9587)) Consolidated the 3 different execution contexts to one ```typescript // before depending on the context the tool was executed in tool.execute({ context: data }); tool.execute({ context: { inputData: data } }); tool.execute(data); // now, for all contexts tool.execute(data, context); ``` **Before:** ```typescript inputSchema: z.object({ something: z.string() }), execute: async ({ context, tracingContext, runId, ... }) => { return doSomething(context.string); } ``` **After:** ```typescript inputSchema: z.object({ something: z.string() }), execute: async (inputData, context) => { const { agent, mcp, workflow, ...sharedContext } = context // context that only an agent would get like toolCallId, messages, suspend, resume, etc if (agent) { doSomething(inputData.something, agent) // context that only a workflow would get like runId, state, suspend, resume, etc } else if (workflow) { doSomething(inputData.something, workflow) // context that only a workflow would get like "extra", "elicitation" } else if (mcp) { doSomething(inputData.something, mcp) } else { // Running a tool in no execution context return doSomething(inputData.something); } } ``` - The `@mastra/core` package no longer allows top-level imports except for `Mastra` and `type Config`. You must use subpath imports for all other imports. ([#9544](https://github.com/mastra-ai/mastra/pull/9544)) For example: ```diff import { Mastra, type Config } from "@mastra/core"; - import { Agent } from "@mastra/core"; - import { createTool } from "@mastra/core"; - import { createStep } from "@mastra/core"; + import { Agent } from "@mastra/core/agent"; + import { createTool } from "@mastra/core/tools"; + import { createStep } from "@mastra/core/workflows"; ``` - This simplifies the Memory API by removing the confusing rememberMessages method and renaming query to recall for better clarity. ([#9701](https://github.com/mastra-ai/mastra/pull/9701)) The rememberMessages method name implied it might persist data when it was actually just retrieving messages, same as query. Having two methods that did essentially the same thing was unnecessary. Before: ```typescript // Two methods that did the same thing memory.rememberMessages({ threadId, resourceId, config, vectorMessageSearch }); memory.query({ threadId, resourceId, perPage, vectorSearchString }); ``` After: ```typescript // Single unified method with clear purpose memory.recall({ threadId, resourceId, perPage, vectorMessageSearch, threadConfig }); ``` All usages have been updated across the codebase including tests. The agent now calls recall directly with the appropriate parameters. - Rename RuntimeContext to RequestContext ([#9511](https://github.com/mastra-ai/mastra/pull/9511)) - Implement listMessages API for replacing previous methods ([#9531](https://github.com/mastra-ai/mastra/pull/9531)) - Rename `defaultVNextStreamOptions` to `defaultOptions`. Add "Legacy" suffix to v1 option properties and methods (`defaultGenerateOptions` → `defaultGenerateOptionsLegacy`, `defaultStreamOptions` → `defaultStreamOptionsLegacy`). ([#9535](https://github.com/mastra-ai/mastra/pull/9535)) - **Breaking Change**: Convert OUTPUT generic from `OutputSchema` constraint to plain generic ([#11741](https://github.com/mastra-ai/mastra/pull/11741)) This change removes the direct dependency on Zod typings in the public API by converting all `OUTPUT extends OutputSchema` generic constraints to plain `OUTPUT` generics throughout the codebase. This is preparation for moving to a standard schema approach. - All generic type parameters previously constrained to `OutputSchema` (e.g., ``) are now plain generics with defaults (e.g., ``) - Affects all public APIs including `Agent`, `MastraModelOutput`, `AgentExecutionOptions`, and stream/generate methods - `InferSchemaOutput` replaced with `OUTPUT` throughout - `PartialSchemaOutput` replaced with `Partial` - Schema fields now use `NonNullable>` instead of `OUTPUT` directly - Added `FullOutput` type representing complete output with all fields - Added `AgentExecutionOptionsBase` type - `getFullOutput()` method now returns `Promise>` - `Agent` class now generic: `Agent` - `agent.generate()` and `agent.stream()` methods have updated signatures - `MastraModelOutput` no longer requires `OutputSchema` constraint - Network route and streaming APIs updated to use plain OUTPUT generic **Before:** ```typescript const output = await agent.generate([...], { structuredOutput: { schema: mySchema } }); **After:** const output = await agent.generate>([...], { structuredOutput: { schema: mySchema } }); // Or rely on type inference: const output = await agent.generate([...], { structuredOutput: { schema: mySchema } }); ``` - Remove `getThreadsByResourceId` and `getThreadsByResourceIdPaginated` methods from storage interfaces in favor of `listThreadsByResourceId`. The new method uses `offset`/`limit` pagination and a nested `orderBy` object structure (`{ field, direction }`). ([#9536](https://github.com/mastra-ai/mastra/pull/9536)) - Remove `getMessagesById` method from storage interfaces in favor of `listMessagesById`. The new method only returns V2-format messages and removes the format parameter, simplifying the API surface. Users should migrate from `getMessagesById({ messageIds, format })` to `listMessagesById({ messageIds })`. ([#9534](https://github.com/mastra-ai/mastra/pull/9534)) - Experimental auth -> auth ([#9660](https://github.com/mastra-ai/mastra/pull/9660)) - Renamed a bunch of observability/tracing-related things to drop the AI prefix. ([#9744](https://github.com/mastra-ai/mastra/pull/9744)) - Removed MastraMessageV3 intermediary format, now we go from MastraDBMessage->aiv5 formats and back directly ([#9094](https://github.com/mastra-ai/mastra/pull/9094)) - **Breaking Change**: Remove legacy v1 watch events and consolidate on v2 implementation. ([#9252](https://github.com/mastra-ai/mastra/pull/9252)) This change simplifies the workflow watching API by removing the legacy v1 event system and promoting v2 as the standard (renamed to just `watch`). **What's Changed** - Removed legacy v1 watch event handlers and types - Renamed `watch-v2` to `watch` throughout the codebase - Removed `.watch()` method from client-js SDK (`Workflow` and `AgentBuilder` classes) - Removed `/watch` HTTP endpoints from server and deployer - Removed `WorkflowWatchResult` and v1 `WatchEvent` types - Remove various deprecated APIs from agent class. ([#9257](https://github.com/mastra-ai/mastra/pull/9257)) - `agent.llm` → `agent.getLLM()` - `agent.tools` → `agent.getTools()` - `agent.instructions` → `agent.getInstructions()` - `agent.speak()` → `agent.voice.speak()` - `agent.getSpeakers()` → `agent.voice.getSpeakers()` - `agent.listen` → `agent.voice.listen()` - `agent.fetchMemory` → `(await agent.getMemory()).query()` - `agent.toStep` → Add agent directly to the step, workflows handle the transformation - Pagination APIs now use `page`/`perPage` instead of `offset`/`limit` ([#9592](https://github.com/mastra-ai/mastra/pull/9592)) All storage and memory pagination APIs have been updated to use `page` (0-indexed) and `perPage` instead of `offset` and `limit`, aligning with standard REST API patterns. **Affected APIs:** - `Memory.listThreadsByResourceId()` - `Memory.listMessages()` - `Storage.listWorkflowRuns()` **Migration:** ```typescript // Before await memory.listThreadsByResourceId({ resourceId: 'user-123', offset: 20, limit: 10, }); // After await memory.listThreadsByResourceId({ resourceId: 'user-123', page: 2, // page = Math.floor(offset / limit) perPage: 10, }); // Before await memory.listMessages({ threadId: 'thread-456', offset: 20, limit: 10, }); // After await memory.listMessages({ threadId: 'thread-456', page: 2, perPage: 10, }); // Before await storage.listWorkflowRuns({ workflowName: 'my-workflow', offset: 20, limit: 10, }); // After await storage.listWorkflowRuns({ workflowName: 'my-workflow', page: 2, perPage: 10, }); ``` **Additional improvements:** - Added validation for negative `page` values in all storage implementations - Improved `perPage` validation to handle edge cases (negative values, `0`, `false`) - Added reusable query parser utilities for consistent validation in handlers - ```ts ([#9709](https://github.com/mastra-ai/mastra/pull/9709)) import { Mastra } from '@mastra/core'; import { Observability } from '@mastra/observability'; // Explicit import const mastra = new Mastra({ ...other_config, observability: new Observability({ default: { enabled: true }, }), // Instance }); ``` Instead of: ```ts import { Mastra } from '@mastra/core'; import '@mastra/observability/init'; // Explicit import const mastra = new Mastra({ ...other_config, observability: { default: { enabled: true }, }, }); ``` Also renamed a bunch of: - `Tracing` things to `Observability` things. - `AI-` things to just things. - Changing getAgents -> listAgents, getTools -> listTools, getWorkflows -> listWorkflows ([#9495](https://github.com/mastra-ai/mastra/pull/9495)) - Removed old tracing code based on OpenTelemetry ([#9237](https://github.com/mastra-ai/mastra/pull/9237)) - Remove deprecated vector prompts and cohere provider from code ([#9596](https://github.com/mastra-ai/mastra/pull/9596)) - Renamed MastraStorage to MastraCompositeStore for better clarity. The old MastraStorage name remains available as a deprecated alias for backward compatibility, but will be removed in a future version. ([#12093](https://github.com/mastra-ai/mastra/pull/12093)) **Migration:** Update your imports and usage: ```typescript // Before import { MastraStorage } from '@mastra/core/storage'; const storage = new MastraStorage({ id: 'composite', domains: { ... } }); // After import { MastraCompositeStore } from '@mastra/core/storage'; const storage = new MastraCompositeStore({ id: 'composite', domains: { ... } }); ``` The new name better reflects that this is a composite storage implementation that routes different domains (workflows, traces, messages) to different underlying stores, avoiding confusion with the general "Mastra Storage" concept. - Mark as stable ([`83d5942`](https://github.com/mastra-ai/mastra/commit/83d5942669ce7bba4a6ca4fd4da697a10eb5ebdc)) - Changed `.branch()` result schema to make all branch output fields optional. ([#10693](https://github.com/mastra-ai/mastra/pull/10693)) **Breaking change**: Branch outputs are now optional since only one branch executes at runtime. Update your workflow schemas to handle optional branch results. **Before:** ```typescript const workflow = createWorkflow({...}) .branch([ [condition1, stepA], // outputSchema: { result: z.string() } [condition2, stepB], // outputSchema: { data: z.number() } ]) .map({ finalResult: { step: stepA, path: 'result' } // Expected non-optional }); ``` **After:** ```typescript const workflow = createWorkflow({...}) .branch([ [condition1, stepA], [condition2, stepB], ]) .map({ finalResult: { step: stepA, path: 'result' // Now optional - provide fallback } }); ``` **Why**: Branch conditionals execute only one path, so non-executed branches don't produce outputs. The type system now correctly reflects this runtime behavior. Related issue: https://github.com/mastra-ai/mastra/issues/10642 - Enforcing id required on Processor primitive ([#9591](https://github.com/mastra-ai/mastra/pull/9591)) - **Breaking Changes:** ([#9045](https://github.com/mastra-ai/mastra/pull/9045)) - Moved `generateTitle` from `threads.generateTitle` to top-level memory option - Changed default value from `true` to `false` - Using `threads.generateTitle` now throws an error **Migration:** Replace `threads: { generateTitle: true }` with `generateTitle: true` at the top level of memory options. **Playground:** The playground UI now displays thread IDs instead of "Chat from" when titles aren't generated. - Renamed `MastraMessageV2` to `MastraDBMessage` ([#9255](https://github.com/mastra-ai/mastra/pull/9255)) Made the return format of all methods that return db messages consistent. It's always `{ messages: MastraDBMessage[] }` now, and messages can be converted after that using `@mastra/ai-sdk/ui`'s `toAISdkV4/5Messages()` function - moved ai-tracing code into @mastra/observability ([#9661](https://github.com/mastra-ai/mastra/pull/9661)) - Remove legacy evals from Mastra ([#9491](https://github.com/mastra-ai/mastra/pull/9491)) - Use tool's outputSchema to validate results and return an error object if schema does not match output results. ([#9664](https://github.com/mastra-ai/mastra/pull/9664)) ```typescript const getUserTool = createTool({ id: 'get-user', outputSchema: z.object({ id: z.string(), name: z.string(), email: z.string().email(), }), execute: async inputData => { return { id: '123', name: 'John' }; }, }); ``` When validation fails, the tool returns a `ValidationError`: ```typescript // Before v1 - invalid output would silently pass through await getUserTool.execute({}); // { id: "123", name: "John" } - missing email // After v1 - validation error is returned await getUserTool.execute({}); // { // error: true, // message: "Tool output validation failed for get-user. The tool returned invalid output:\n- email: Required\n\nReturned output: {...}", // validationErrors: { ... } // } ``` - Removes deprecated input-processor type and processors. ([#9200](https://github.com/mastra-ai/mastra/pull/9200)) ### Minor Changes - Add context parameter to `idGenerator` to enable deterministic ID generation based on context. ([#10964](https://github.com/mastra-ai/mastra/pull/10964)) The `idGenerator` function now receives optional context about what type of ID is being generated and from which Mastra primitive. This allows generating IDs that can be shared with external databases. ```typescript const mastra = new Mastra({ idGenerator: context => { // context.idType: 'thread' | 'message' | 'run' | 'step' | 'generic' // context.source: 'agent' | 'workflow' | 'memory' // context.entityId: the agent/workflow id // context.threadId, context.resourceId, context.role, context.stepType if (context?.idType === 'message' && context?.threadId) { return `msg-${context.threadId}-${Date.now()}`; } if (context?.idType === 'run' && context?.source === 'agent') { return `run-${context.entityId}-${Date.now()}`; } return crypto.randomUUID(); }, }); ``` Existing `idGenerator` functions without parameters continue to work since the context is optional. Fixes #8131 - Added `flush()` method to `ObservabilityExporter`, `ObservabilityBridge`, and `ObservabilityInstance` interfaces ([#12003](https://github.com/mastra-ai/mastra/pull/12003)) - Add `hideInput` and `hideOutput` options to `TracingOptions` for protecting sensitive data in traces. ([#11969](https://github.com/mastra-ai/mastra/pull/11969)) When set to `true`, these options hide input/output data from all spans in a trace, including child spans. This is useful for protecting sensitive information from being logged to observability platforms. ```typescript const agent = mastra.getAgent('myAgent'); await agent.generate('Process this sensitive data', { tracingOptions: { hideInput: true, // Input will be hidden from all spans hideOutput: true, // Output will be hidden from all spans }, }); ``` The options can be used independently (hide only input or only output) or together. The settings are propagated to all child spans via `TraceState`, ensuring consistent behavior across the entire trace. Fixes #10888 - Add `onError` hook to server configuration for custom error handling. ([#11403](https://github.com/mastra-ai/mastra/pull/11403)) You can now provide a custom error handler through the Mastra server config to catch errors, format responses, or send them to external services like Sentry: ```typescript import { Mastra } from '@mastra/core/mastra'; const mastra = new Mastra({ server: { onError: (err, c) => { // Send to Sentry Sentry.captureException(err); // Return custom formatted response return c.json( { error: err.message, timestamp: new Date().toISOString(), }, 500, ); }, }, }); ``` If no `onError` is provided, the default error handler is used. Fixes #9610 - Add stored agents support ([#10953](https://github.com/mastra-ai/mastra/pull/10953)) Agents can now be stored in the database and loaded at runtime. This lets you persist agent configurations and dynamically create executable Agent instances from storage. ```typescript import { Mastra } from '@mastra/core'; import { LibSQLStore } from '@mastra/libsql'; const mastra = new Mastra({ storage: new LibSQLStore({ url: ':memory:' }), tools: { myTool }, scorers: { myScorer }, }); // Create agent in storage via API or directly await mastra.getStorage().createAgent({ agent: { id: 'my-agent', name: 'My Agent', instructions: 'You are helpful', model: { provider: 'openai', name: 'gpt-4' }, tools: { myTool: {} }, scorers: { myScorer: { sampling: { type: 'ratio', rate: 0.5 } } }, }, }); // Load and use the agent const agent = await mastra.getStoredAgentById('my-agent'); const response = await agent.generate('Hello!'); // List all stored agents with pagination const { agents, total, hasMore } = await mastra.listStoredAgents({ page: 0, perPage: 10, }); ``` Also adds a memory registry to Mastra so stored agents can reference memory instances by key. - Added human-in-the-loop (HITL) tool approval support for `generate()` method. ([#12056](https://github.com/mastra-ai/mastra/pull/12056)) **Why:** This provides parity between `stream()` and `generate()` for tool approval flows, allowing non-streaming use cases to leverage `requireToolApproval` without needing to switch to streaming. Previously, tool approval with `requireToolApproval` only worked with `stream()`. Now you can use the same approval flow with `generate()` for non-streaming use cases. **Using tool approval with generate()** ```typescript const output = await agent.generate('Find user John', { requireToolApproval: true, }); // Check if a tool is waiting for approval if (output.finishReason === 'suspended') { console.log('Tool requires approval:', output.suspendPayload.toolName); // Approve the tool call const result = await agent.approveToolCallGenerate({ runId: output.runId, toolCallId: output.suspendPayload.toolCallId, }); console.log(result.text); } ``` **Declining a tool call** ```typescript if (output.finishReason === 'suspended') { const result = await agent.declineToolCallGenerate({ runId: output.runId, toolCallId: output.suspendPayload.toolCallId, }); } ``` **New methods added:** - `agent.approveToolCallGenerate({ runId, toolCallId })` - Approves a pending tool call and returns the complete result - `agent.declineToolCallGenerate({ runId, toolCallId })` - Declines a pending tool call and returns the complete result **Server routes added:** - `POST /api/agents/:agentId/approve-tool-call-generate` - `POST /api/agents/:agentId/decline-tool-call-generate` The playground UI now also supports tool approval when using generate mode. - Memory system now uses processors. Memory processors (`MessageHistory`, `SemanticRecall`, `WorkingMemory`) are now exported from `@mastra/memory/processors` and automatically added to the agent pipeline based on your memory config. Core processors (`ToolCallFilter`, `TokenLimiter`) remain in `@mastra/core/processors`. ([#9254](https://github.com/mastra-ai/mastra/pull/9254)) - Add reserved keys in RequestContext for secure resourceId/threadId setting from middleware ([#10657](https://github.com/mastra-ai/mastra/pull/10657)) This allows middleware to securely set `resourceId` and `threadId` via reserved keys in RequestContext (`MASTRA_RESOURCE_ID_KEY` and `MASTRA_THREAD_ID_KEY`), which take precedence over client-provided values for security. - Removed the deprecated `AISDKV5OutputStream` class from the public API. ([#11845](https://github.com/mastra-ai/mastra/pull/11845)) **What changed:** The `AISDKV5OutputStream` class is no longer exported from `@mastra/core`. This class was previously used with the `format: 'aisdk'` option, which has already been removed from `.stream()` and `.generate()` methods. **Who is affected:** Only users who were directly importing `AISDKV5OutputStream` from `@mastra/core`. If you were using the standard `.stream()` or `.generate()` methods without the `format` option, no changes are needed. **Migration:** If you were importing this class directly, switch to using `MastraModelOutput` which provides the same streaming functionality: ```typescript // Before import { AISDKV5OutputStream } from '@mastra/core'; // After import { MastraModelOutput } from '@mastra/core'; ``` - Changed JSON columns from TEXT to JSONB in `mastra_threads` and `mastra_workflow_snapshot` tables. ([#11853](https://github.com/mastra-ai/mastra/pull/11853)) **Why this change?** These were the last remaining columns storing JSON as TEXT. This change aligns them with other tables that already use JSONB, enabling native JSON operators and improved performance. See [#8978](https://github.com/mastra-ai/mastra/issues/8978) for details. **Columns Changed:** - `mastra_threads.metadata` - Thread metadata - `mastra_workflow_snapshot.snapshot` - Workflow run state **PostgreSQL** Migration Required - PostgreSQL enforces column types, so existing tables must be migrated. Note: Migration will fail if existing column values contain invalid JSON. ```sql ALTER TABLE mastra_threads ALTER COLUMN metadata TYPE jsonb USING metadata::jsonb; ALTER TABLE mastra_workflow_snapshot ALTER COLUMN snapshot TYPE jsonb USING snapshot::jsonb; ``` **LibSQL** No Migration Required - LibSQL now uses native SQLite JSONB format (added in SQLite 3.45) for ~3x performance improvement on JSON operations. The changes are fully backwards compatible: - Existing TEXT JSON data continues to work - New data is stored in binary JSONB format - Both formats can coexist in the same table - All JSON functions (`json_extract`, etc.) work on both formats New installations automatically use JSONB. Existing applications continue to work without any changes. - Memory scope defaults changed from 'thread' to 'resource' ([#8983](https://github.com/mastra-ai/mastra/pull/8983)) Both `workingMemory.scope` and `semanticRecall.scope` now default to `'resource'` instead of `'thread'`. This means: - Working memory persists across all conversations for the same user/resource - Semantic recall searches across all threads for the same user/resource **Migration**: To maintain the previous thread-scoped behavior, explicitly set `scope: 'thread'`: ```typescript memory: new Memory({ storage, workingMemory: { enabled: true, scope: 'thread', // Explicitly set for thread-scoped behavior }, semanticRecall: { scope: 'thread', // Explicitly set for thread-scoped behavior }, }), ``` Also fixed issues where playground semantic recall search could show missing or incorrect results in certain cases. - Add support for AI SDK v6 (LanguageModelV3) ([#11191](https://github.com/mastra-ai/mastra/pull/11191)) Agents can now use `LanguageModelV3` models from AI SDK v6 beta providers like `@ai-sdk/openai@^3.0.0-beta`. **New features:** - Usage normalization: V3's nested usage format is normalized to Mastra's flat format with `reasoningTokens`, `cachedInputTokens`, and raw data preserved in a `raw` field **Backward compatible:** All existing V1 and V2 models continue to work unchanged. - Respect structured outputs for v2 models so tool schemas aren’t stripped ([#11038](https://github.com/mastra-ai/mastra/pull/11038)) - Deprecate `default: { enabled: true }` observability configuration ([#11674](https://github.com/mastra-ai/mastra/pull/11674)) The shorthand `default: { enabled: true }` configuration is now deprecated and will be removed in a future version. Users should migrate to explicit configuration with `DefaultExporter`, `CloudExporter`, and `SensitiveDataFilter`. **Before (deprecated):** ```typescript import { Observability } from '@mastra/observability'; const mastra = new Mastra({ observability: new Observability({ default: { enabled: true }, }), }); ``` **After (recommended):** ```typescript import { Observability, DefaultExporter, CloudExporter, SensitiveDataFilter } from '@mastra/observability'; const mastra = new Mastra({ observability: new Observability({ configs: { default: { serviceName: 'mastra', exporters: [new DefaultExporter(), new CloudExporter()], spanOutputProcessors: [new SensitiveDataFilter()], }, }, }), }); ``` The explicit configuration makes it clear exactly what exporters and processors are being used, improving code readability and maintainability. A deprecation warning will be logged when using the old configuration pattern. - Exported `isProcessorWorkflow` function from @mastra/core/processors. Added `getConfiguredProcessorWorkflows()` method to agents and `listProcessors()` method to the Mastra class for programmatic access to processor information. ([#12059](https://github.com/mastra-ai/mastra/pull/12059)) - Adds a new `suspendData` parameter to workflow step execute functions that provides access to the data originally passed to `suspend()` when the step was suspended. This enables steps to access context about why they were suspended when they are later resumed. ([#10734](https://github.com/mastra-ai/mastra/pull/10734)) **New Features:** - `suspendData` parameter automatically populated in step execute function when resuming - Type-safe access to suspend data matching the step's `suspendSchema` - Backward compatible - existing workflows continue to work unchanged **Example:** ```typescript const step = createStep({ suspendSchema: z.object({ reason: z.string() }), resumeSchema: z.object({ approved: z.boolean() }), execute: async ({ suspend, suspendData, resumeData }) => { if (!resumeData?.approved) { return await suspend({ reason: 'Approval required' }); } // Access original suspend data when resuming console.log(`Resuming after: ${suspendData?.reason}`); return { result: 'Approved' }; }, }); ``` - - Fixed TypeScript errors where `threadId: string | string[]` was being passed to places expecting `Scalar` type ([#10663](https://github.com/mastra-ai/mastra/pull/10663)) - Added proper multi-thread support for `listMessages` across all adapters when `threadId` is an array - Updated `_getIncludedMessages` to look up message threadId by ID (since message IDs are globally unique) - **upstash**: Added `msg-idx:{messageId}` index for O(1) message lookups (backwards compatible with fallback to scan for old messages, with automatic backfill) - Added `TrackingExporter` base class with improved handling for: ([#11870](https://github.com/mastra-ai/mastra/pull/11870)) - **Out-of-order span processing**: Spans that arrive before their parents are now queued and processed once dependencies are available - **Delayed cleanup**: Trace data is retained briefly after spans end to handle late-arriving updates - **Memory management**: Configurable limits on pending and total traces to prevent memory leaks New configuration options on `TrackingExporterConfig`: - `earlyQueueMaxAttempts` - Max retry attempts for queued events (default: 5) - `earlyQueueTTLMs` - TTL for queued events in ms (default: 30000) - `traceCleanupDelayMs` - Delay before cleaning up completed traces (default: 30000) - `maxPendingCleanupTraces` - Soft cap on traces awaiting cleanup (default: 100) - `maxTotalTraces` - Hard cap on total traces (default: 500) Updated @mastra/braintrust, @mastra/langfuse, @mastra/langsmith, @mastra/posthog to use the new TrackingExporter - Introduce StorageDomain base class for composite storage support ([#11249](https://github.com/mastra-ai/mastra/pull/11249)) Storage adapters now use a domain-based architecture where each domain (memory, workflows, scores, observability, agents) extends a `StorageDomain` base class with `init()` and `dangerouslyClearAll()` methods. **Key changes:** - Add `StorageDomain` abstract base class that all domain storage classes extend - Add `InMemoryDB` class for shared state across in-memory domain implementations - All storage domains now implement `dangerouslyClearAll()` for test cleanup - Remove `operations` from public `StorageDomains` type (now internal to each adapter) - Add flexible client/config patterns - domains accept either an existing database client or config to create one internally **Why this matters:** This enables composite storage where you can use different database adapters per domain: ```typescript import { Mastra } from '@mastra/core'; import { PostgresStore } from '@mastra/pg'; import { ClickhouseStore } from '@mastra/clickhouse'; // Use Postgres for most domains but Clickhouse for observability const mastra = new Mastra({ storage: new PostgresStore({ connectionString: 'postgres://...', }), // Future: override specific domains // observability: new ClickhouseStore({ ... }).getStore('observability'), }); ``` **Standalone domain usage:** Domains can now be used independently with flexible configuration: ```typescript import { MemoryLibSQL } from '@mastra/libsql/memory'; // Option 1: Pass config to create client internally const memory = new MemoryLibSQL({ url: 'file:./local.db', }); // Option 2: Pass existing client for shared connections import { createClient } from '@libsql/client'; const client = createClient({ url: 'file:./local.db' }); const memory = new MemoryLibSQL({ client }); ``` **Breaking changes:** - `StorageDomains` type no longer includes `operations` - access via `getStore()` instead - Domain base classes now require implementing `dangerouslyClearAll()` method - Refactor storage architecture to use domain-specific stores via `getStore()` pattern ([#11361](https://github.com/mastra-ai/mastra/pull/11361)) ### Summary This release introduces a new storage architecture that replaces passthrough methods on `MastraStorage` with domain-specific storage interfaces accessed via `getStore()`. This change reduces code duplication across storage adapters and provides a cleaner, more modular API. ### Migration Guide All direct method calls on storage instances should be updated to use `getStore()`: ```typescript // Before const thread = await storage.getThreadById({ threadId }); await storage.persistWorkflowSnapshot({ workflowName, runId, snapshot }); await storage.createSpan(span); // After const memory = await storage.getStore('memory'); const thread = await memory?.getThreadById({ threadId }); const workflows = await storage.getStore('workflows'); await workflows?.persistWorkflowSnapshot({ workflowName, runId, snapshot }); const observability = await storage.getStore('observability'); await observability?.createSpan(span); ``` ### Available Domains - **`memory`**: Thread and message operations (`getThreadById`, `saveThread`, `saveMessages`, etc.) - **`workflows`**: Workflow state persistence (`persistWorkflowSnapshot`, `loadWorkflowSnapshot`, `getWorkflowRunById`, etc.) - **`scores`**: Evaluation scores (`saveScore`, `listScoresByScorerId`, etc.) - **`observability`**: Tracing and spans (`createSpan`, `updateSpan`, `getTrace`, etc.) - **`agents`**: Stored agent configurations (`createAgent`, `getAgentById`, `listAgents`, etc.) ### Breaking Changes - Passthrough methods have been removed from `MastraStorage` base class - All storage adapters now require accessing domains via `getStore()` - The `stores` property on storage instances is now the canonical way to access domain storage ### Internal Changes - Each storage adapter now initializes domain-specific stores in its constructor - Domain stores share database connections and handle their own table initialization - Adds trace tagging support to the BrainTrust and Langfuse tracing exporters. ([#10765](https://github.com/mastra-ai/mastra/pull/10765)) - Add support for AI SDK v6 ToolLoopAgent in Mastra ([#11254](https://github.com/mastra-ai/mastra/pull/11254)) You can now pass an AI SDK v6 `ToolLoopAgent` directly to Mastra's agents configuration. The agent will be automatically converted to a Mastra Agent while preserving all ToolLoopAgent lifecycle hooks: - `prepareCall` - Called once at the start of generate/stream - `prepareStep` - Called before each step in the agentic loop - `stopWhen` - Custom stop conditions for the loop Example: ```typescript import { ToolLoopAgent } from 'ai'; import { Mastra } from '@mastra/core/mastra'; const toolLoopAgent = new ToolLoopAgent({ model: openai('gpt-4o'), instructions: 'You are a helpful assistant.', tools: { weather: weatherTool }, prepareStep: async ({ stepNumber }) => { if (stepNumber === 0) { return { toolChoice: 'required' }; } }, }); const mastra = new Mastra({ agents: { toolLoopAgent }, }); // Use like any other Mastra agent const agent = mastra.getAgent('toolLoopAgent'); const result = await agent.generate('What is the weather?'); ``` - Unified observability schema with entity-based span identification ([#11132](https://github.com/mastra-ai/mastra/pull/11132)) ## What changed Spans now use a unified identification model with `entityId`, `entityType`, and `entityName` instead of separate `agentId`, `toolId`, `workflowId` fields. **Before:** ```typescript // Old span structure span.agentId; // 'my-agent' span.toolId; // undefined span.workflowId; // undefined ``` **After:** ```typescript // New span structure span.entityType; // EntityType.AGENT span.entityId; // 'my-agent' span.entityName; // 'My Agent' ``` ## New `listTraces()` API Query traces with filtering, pagination, and sorting: ```typescript const { spans, pagination } = await storage.listTraces({ filters: { entityType: EntityType.AGENT, entityId: 'my-agent', userId: 'user-123', environment: 'production', status: TraceStatus.SUCCESS, startedAt: { start: new Date('2024-01-01'), end: new Date('2024-01-31') }, }, pagination: { page: 0, perPage: 50 }, orderBy: { field: 'startedAt', direction: 'DESC' }, }); ``` **Available filters:** date ranges (`startedAt`, `endedAt`), entity (`entityType`, `entityId`, `entityName`), identity (`userId`, `organizationId`), correlation IDs (`runId`, `sessionId`, `threadId`), deployment (`environment`, `source`, `serviceName`), `tags`, `metadata`, and `status`. ## New retrieval methods - `getSpan({ traceId, spanId })` - Get a single span - `getRootSpan({ traceId })` - Get the root span of a trace - `getTrace({ traceId })` - Get all spans for a trace ## Backward compatibility The legacy `getTraces()` method continues to work. When you pass `name: "agent run: my-agent"`, it automatically transforms to `entityId: "my-agent", entityType: AGENT`. ## Migration **Automatic:** SQL-based stores (PostgreSQL, LibSQL, MSSQL) automatically add new columns to existing `spans` tables on initialization. Existing data is preserved with new columns set to `NULL`. **No action required:** Your existing code continues to work. Adopt the new fields and `listTraces()` API at your convenience. - Add embedderOptions support to Memory for AI SDK 5+ provider-specific embedding options ([#11462](https://github.com/mastra-ai/mastra/pull/11462)) With AI SDK 5+, embedding models no longer accept options in their constructor. Options like `outputDimensionality` for Google embedding models must now be passed when calling `embed()` or `embedMany()`. This change adds `embedderOptions` to Memory configuration to enable passing these provider-specific options. You can now configure embedder options when creating Memory: ```typescript import { Memory } from '@mastra/core'; import { google } from '@ai-sdk/google'; // Before: No way to specify providerOptions const memory = new Memory({ embedder: google.textEmbeddingModel('text-embedding-004'), }); // After: Pass embedderOptions with providerOptions const memory = new Memory({ embedder: google.textEmbeddingModel('text-embedding-004'), embedderOptions: { providerOptions: { google: { outputDimensionality: 768, taskType: 'RETRIEVAL_DOCUMENT', }, }, }, }); ``` This is especially important for: - Google `text-embedding-004`: Control output dimensions (default 768) - Google `gemini-embedding-001`: Reduce from default 3072 dimensions to avoid pgvector's 2000 dimension limit for HNSW indexes Fixes #8248 - Add `messageList` parameter to `processOutputStream` for accessing remembered messages during streaming ([#10608](https://github.com/mastra-ai/mastra/pull/10608)) - Fix processor tracing to create individual spans per processor ([#11683](https://github.com/mastra-ai/mastra/pull/11683)) - Processor spans now correctly show processor IDs (e.g., `input processor: validator`) instead of combined workflow IDs - Each processor in a chain gets its own trace span, improving observability into processor execution - Spans are only created for phases a processor actually implements, eliminating empty spans - Internal agent calls within processors now properly nest under their processor span - Added `INPUT_STEP_PROCESSOR` and `OUTPUT_STEP_PROCESSOR` entity types for finer-grained tracing - Changed `processorType` span attribute to `processorExecutor` with values `'workflow'` or `'legacy'` - Added a unified `transformScoreRow` function in `@mastra/core/storage` that provides schema-driven row transformation for score data. This eliminates code duplication across 10 storage adapters while maintaining store-specific behavior through configurable options: ([#10648](https://github.com/mastra-ai/mastra/pull/10648)) - `preferredTimestampFields`: Preferred source fields for timestamps (PostgreSQL, Cloudflare D1) - `convertTimestamps`: Convert timestamp strings to Date objects (MSSQL, MongoDB, ClickHouse) - `nullValuePattern`: Skip values matching pattern (ClickHouse's `'_null_'`) - `fieldMappings`: Map source column names to schema fields (LibSQL's `additionalLLMContext`) Each store adapter now uses the unified function with appropriate options, reducing ~200 lines of duplicate transformation logic while ensuring consistent behavior across all storage backends. - Added new `listThreads` method for flexible thread filtering across all storage adapters. ([#11832](https://github.com/mastra-ai/mastra/pull/11832)) **New Features** - Filter threads by `resourceId`, `metadata`, or both (with AND logic for metadata key-value pairs) - All filter parameters are optional, allowing you to list all threads or filter as needed - Full pagination and sorting support **Example Usage** ```typescript // List all threads const allThreads = await memory.listThreads({}); // Filter by resourceId only const userThreads = await memory.listThreads({ filter: { resourceId: 'user-123' }, }); // Filter by metadata only const supportThreads = await memory.listThreads({ filter: { metadata: { category: 'support' } }, }); // Filter by both with pagination const filteredThreads = await memory.listThreads({ filter: { resourceId: 'user-123', metadata: { priority: 'high', status: 'open' }, }, orderBy: { field: 'updatedAt', direction: 'DESC' }, page: 0, perPage: 20, }); ``` **Security Improvements** - Added validation to prevent SQL injection via malicious metadata keys - Added pagination parameter validation to prevent integer overflow attacks - Add completion validation to agent networks using custom scorers ([#11562](https://github.com/mastra-ai/mastra/pull/11562)) You can now validate whether an agent network has completed its task by passing MastraScorers to `agent.network()`. When validation fails, the network automatically retries with feedback injected into the conversation. **Example: Creating a scorer to verify test coverage** ```ts import { createScorer } from '@mastra/core/evals'; import { z } from 'zod'; // Create a scorer that checks if tests were written const testsScorer = createScorer({ id: 'tests-written', description: 'Validates that unit tests were included in the response', type: 'agent', }).generateScore({ description: 'Return 1 if tests are present, 0 if missing', outputSchema: z.number(), createPrompt: ({ run }) => ` Does this response include unit tests? Response: ${run.output} Return 1 if tests are present, 0 if not. `, }); // Use the scorer with agent.network() const stream = await agent.network('Implement a fibonacci function with tests', { completion: { scorers: [testsScorer], strategy: 'all', // all scorers must pass (score >= 0.5) }, maxSteps: 3, }); ``` **What this enables:** - **Programmatic completion checks**: Define objective criteria for task completion instead of relying on the default LLM-based check - **Automatic retry with feedback**: When a scorer returns `score: 0`, its reason is injected into the conversation so the network can address the gap on the next iteration - **Composable validation**: Combine multiple scorers with `strategy: 'all'` (all must pass) or `strategy: 'any'` (at least one must pass) This replaces guesswork with reliable, repeatable validation that ensures agent networks produce outputs meeting your specific requirements. - Add MCP tool annotations and metadata support to `ToolAction` and `Tool` ([#11841](https://github.com/mastra-ai/mastra/pull/11841)) Tools can now surface UI hints like `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint` via the `mcp.annotations` field, and pass arbitrary metadata to MCP clients via `mcp._meta`. These MCP-specific properties are grouped under the `mcp` property to clearly indicate they only apply when tools are exposed via MCP. ```typescript import { createTool } from '@mastra/core/tools'; const myTool = createTool({ id: 'weather', description: 'Get weather for a location', mcp: { annotations: { title: 'Weather Lookup', readOnlyHint: true, destructiveHint: false, }, _meta: { version: '1.0.0' }, }, execute: async ({ location }) => fetchWeather(location), }); ``` - Add `disableInit` option to all storage adapters ([#10851](https://github.com/mastra-ai/mastra/pull/10851)) Adds a new `disableInit` config option to all storage providers that allows users to disable automatic table creation/migrations at runtime. This is useful for CI/CD pipelines where you want to run migrations during deployment with elevated credentials, then run the application with `disableInit: true` so it doesn't attempt schema changes at runtime. ```typescript // CI/CD script - run migrations const storage = new PostgresStore({ connectionString: DATABASE_URL, id: 'pg-storage', }); await storage.init(); // Runtime - skip auto-init const storage = new PostgresStore({ connectionString: DATABASE_URL, id: 'pg-storage', disableInit: true, }); ``` - Add structured output support to agent.network() method. Users can now pass a `structuredOutput` option with a Zod schema to get typed results from network execution. ([#11701](https://github.com/mastra-ai/mastra/pull/11701)) The stream exposes `.object` (Promise) and `.objectStream` (ReadableStream) getters, and emits `network-object` and `network-object-result` chunk types. The structured output is generated after task completion using the provided schema. ```typescript const stream = await agent.network('Research AI trends', { structuredOutput: { schema: z.object({ summary: z.string(), recommendations: z.array(z.string()), }), }, }); const result = await stream.object; // result is typed: { summary: string; recommendations: string[] } ``` - Unified `getWorkflowRunById` and `getWorkflowRunExecutionResult` into a single API that returns `WorkflowState` with both metadata and execution state. ([#11429](https://github.com/mastra-ai/mastra/pull/11429)) **What changed:** - `getWorkflowRunById` now returns a unified `WorkflowState` object containing metadata (runId, workflowName, resourceId, createdAt, updatedAt) along with processed execution state (status, result, error, payload, steps) - Added optional `fields` parameter to request only specific fields for better performance - Added optional `withNestedWorkflows` parameter to control nested workflow step inclusion - Removed `getWorkflowRunExecutionResult` - use `getWorkflowRunById` instead (breaking change) - Removed `/execution-result` API endpoints from server (breaking change) - Removed `runExecutionResult()` method from client SDK (breaking change) - Removed `GetWorkflowRunExecutionResultResponse` type from client SDK (breaking change) **Before:** ```typescript // Had to call two different methods for different data const run = await workflow.getWorkflowRunById(runId); // Returns raw WorkflowRun with snapshot const result = await workflow.getWorkflowRunExecutionResult(runId); // Returns processed execution state ``` **After:** ```typescript // Single method returns everything const run = await workflow.getWorkflowRunById(runId); // Returns: { runId, workflowName, resourceId, createdAt, updatedAt, status, result, error, payload, steps } // Request only specific fields for better performance (avoids expensive step fetching) const status = await workflow.getWorkflowRunById(runId, { fields: ['status'] }); // Skip nested workflow steps for faster response const run = await workflow.getWorkflowRunById(runId, { withNestedWorkflows: false }); ``` **Why:** The previous API required calling two separate methods to get complete workflow run information. This unification simplifies the API surface and gives users control over performance - fetching all steps (especially nested workflows) can be expensive, so the `fields` and `withNestedWorkflows` options let users request only what they need. - Rename LLM span types and attributes to use Model prefix ([#9105](https://github.com/mastra-ai/mastra/pull/9105)) BREAKING CHANGE: This release renames tracing span types and attribute interfaces to use the "Model" prefix instead of "LLM": - `AISpanType.LLM_GENERATION` → `AISpanType.MODEL_GENERATION` - `AISpanType.LLM_STEP` → `AISpanType.MODEL_STEP` - `AISpanType.LLM_CHUNK` → `AISpanType.MODEL_CHUNK` - `LLMGenerationAttributes` → `ModelGenerationAttributes` - `LLMStepAttributes` → `ModelStepAttributes` - `LLMChunkAttributes` → `ModelChunkAttributes` - `InternalSpans.LLM` → `InternalSpans.MODEL` This change better reflects that these span types apply to all AI models, not just Large Language Models. Migration guide: - Update all imports: `import { ModelGenerationAttributes } from '@mastra/core/ai-tracing'` - Update span type references: `AISpanType.MODEL_GENERATION` - Update InternalSpans usage: `InternalSpans.MODEL` ### Patch Changes - dependencies updates: ([#10110](https://github.com/mastra-ai/mastra/pull/10110)) - Updated dependency [`hono-openapi@^1.1.1` ↗︎](https://www.npmjs.com/package/hono-openapi/v/1.1.1) (from `^0.4.8`, in `dependencies`) - dependencies updates: ([#10131](https://github.com/mastra-ai/mastra/pull/10131)) - Updated dependency [`hono@^4.10.5` ↗︎](https://www.npmjs.com/package/hono/v/4.10.5) (from `^4.9.7`, in `dependencies`) - dependencies updates: ([#10133](https://github.com/mastra-ai/mastra/pull/10133)) - Updated dependency [`js-tiktoken@^1.0.21` ↗︎](https://www.npmjs.com/package/js-tiktoken/v/1.0.21) (from `^1.0.20`, in `dependencies`) - dependencies updates: ([#10191](https://github.com/mastra-ai/mastra/pull/10191)) - Updated dependency [`dotenv@^17.2.3` ↗︎](https://www.npmjs.com/package/dotenv/v/17.2.3) (from `^16.6.1`, in `dependencies`) - Add agentId and agentName attributes to MODEL_GENERATION spans. This allows users to correlate gen_ai.usage metrics with specific agents when analyzing LLM operation spans. The attributes are exported as gen_ai.agent.id and gen_ai.agent.name in the OtelExporter. ([#10984](https://github.com/mastra-ai/mastra/pull/10984)) - Added `customSpanFormatter` option to exporters for per-exporter span transformation. This allows different formatting per exporter and supports both synchronous and asynchronous operations, including async data enrichment. ([#11985](https://github.com/mastra-ai/mastra/pull/11985)) **Configuration example:** ```ts import { DefaultExporter } from '@mastra/observability'; import { SpanType } from '@mastra/core/observability'; import type { CustomSpanFormatter } from '@mastra/core/observability'; // Sync formatter const plainTextFormatter: CustomSpanFormatter = span => { if (span.type === SpanType.AGENT_RUN && Array.isArray(span.input)) { const userMessage = span.input.find(m => m.role === 'user'); return { ...span, input: userMessage?.content ?? span.input }; } return span; }; // Async formatter for data enrichment const enrichmentFormatter: CustomSpanFormatter = async span => { const userData = await fetchUserData(span.metadata?.userId); return { ...span, metadata: { ...span.metadata, userName: userData.name } }; }; const exporter = new DefaultExporter({ customSpanFormatter: plainTextFormatter, }); ``` Also added `chainFormatters` utility to combine multiple formatters (supports mixed sync/async): ```ts import { chainFormatters } from '@mastra/observability'; const exporter = new BraintrustExporter({ customSpanFormatter: chainFormatters([syncFormatter, asyncFormatter]), }); ``` - Add embedded documentation support for Mastra packages ([#11472](https://github.com/mastra-ai/mastra/pull/11472)) Mastra packages now include embedded documentation in the published npm package under `dist/docs/`. This enables coding agents and AI assistants to understand and use the framework by reading documentation directly from `node_modules`. Each package includes: - **SKILL.md** - Entry point explaining the package's purpose and capabilities - **SOURCE_MAP.json** - Machine-readable index mapping exports to types and implementation files - **Topic folders** - Conceptual documentation organized by feature area Documentation is driven by the `packages` frontmatter field in MDX files, which maps docs to their corresponding packages. CI validation ensures all docs include this field. - Add exponential backoff to model retry logic to prevent cascading failures ([#9798](https://github.com/mastra-ai/mastra/pull/9798)) When AI model calls fail, the system now implements exponential backoff (1s, 2s, 4s, 8s, max 10s) before retrying instead of immediately hammering the API. This prevents: - Rate limit violations from getting worse - Cascading failures across all fallback models - Wasted API quota by burning through retries instantly - Production outages when all models fail due to rate limits The backoff gives APIs time to recover from transient failures and rate limiting. - Add support for `retries` and `scorers` parameters across all `createStep` overloads. ([#11495](https://github.com/mastra-ai/mastra/pull/11495)) The `createStep` function now includes support for the `retries` and `scorers` fields across all step creation patterns, enabling step-level retry configuration and AI evaluation support for regular steps, agent-based steps, and tool-based steps. ```typescript import { init } from '@mastra/inngest'; import { z } from 'zod'; const { createStep } = init(inngest); // 1. Regular step with retries const regularStep = createStep({ id: 'api-call', inputSchema: z.object({ url: z.string() }), outputSchema: z.object({ data: z.any() }), retries: 3, // ← Will retry up to 3 times on failure execute: async ({ inputData }) => { const response = await fetch(inputData.url); return { data: await response.json() }; }, }); // 2. Agent step with retries and scorers const agentStep = createStep(myAgent, { retries: 3, scorers: [{ id: 'accuracy-scorer', scorer: myAccuracyScorer }], }); // 3. Tool step with retries and scorers const toolStep = createStep(myTool, { retries: 2, scorers: [{ id: 'quality-scorer', scorer: myQualityScorer }], }); ``` This change ensures API consistency across all `createStep` overloads. All step types now support retry and evaluation configurations. This is a non-breaking change - steps without these parameters continue to work exactly as before. Fixes #9351 - Add time-to-first-token (TTFT) support for Langfuse integration ([#10781](https://github.com/mastra-ai/mastra/pull/10781)) Adds `completionStartTime` to model generation spans, which Langfuse uses to calculate TTFT metrics. The timestamp is automatically captured when the first content chunk arrives during streaming. ```typescript // completionStartTime is now automatically captured and sent to Langfuse // enabling TTFT metrics in your Langfuse dashboard const result = await agent.stream('Hello'); ``` - When using agent networks, the routing agent could fail with a cryptic `TypeError: Cannot read properties of undefined` if the generation response was missing or malformed. This made it difficult to diagnose why routing failed. The release now throws a descriptive error with debugging details (response text, finish reason, usage) to help identify the root cause. ([#12028](https://github.com/mastra-ai/mastra/pull/12028)) Fixes #11749 - Remove `streamVNext`, `resumeStreamVNext`, and `observeStreamVNext` methods, call `stream`, `resumeStream` and `observeStream` directly ([#11499](https://github.com/mastra-ai/mastra/pull/11499)) ```diff + const run = await workflow.createRun({ runId: '123' }); - const stream = await run.streamVNext({ inputData: { ... } }); + const stream = await run.stream({ inputData: { ... } }); ``` - Update provider registry and model documentation with latest models and providers ([`f743dbb`](https://github.com/mastra-ai/mastra/commit/f743dbb8b40d1627b5c10c0e6fc154f4ebb6e394)) - Add Azure OpenAI gateway ([#9990](https://github.com/mastra-ai/mastra/pull/9990)) The Azure OpenAI gateway supports three configuration modes: 1. **Static deployments**: Provide deployment names from Azure Portal 2. **Dynamic discovery**: Query Azure Management API for available deployments 3. **Manual**: Specify deployment names when creating agents **Usage** ```typescript import { Mastra } from '@mastra/core'; import { AzureOpenAIGateway } from '@mastra/core/llm'; // Static mode (recommended) export const mastra = new Mastra({ gateways: [ new AzureOpenAIGateway({ resourceName: process.env.AZURE_RESOURCE_NAME!, apiKey: process.env.AZURE_API_KEY!, deployments: ['gpt-4-prod', 'gpt-35-turbo-dev'], }), ], }); // Dynamic discovery mode export const mastra = new Mastra({ gateways: [ new AzureOpenAIGateway({ resourceName: process.env.AZURE_RESOURCE_NAME!, apiKey: process.env.AZURE_API_KEY!, management: { tenantId: process.env.AZURE_TENANT_ID!, clientId: process.env.AZURE_CLIENT_ID!, clientSecret: process.env.AZURE_CLIENT_SECRET!, subscriptionId: process.env.AZURE_SUBSCRIPTION_ID!, resourceGroup: 'my-resource-group', }, }), ], }); // Use Azure OpenAI models const agent = new Agent({ model: 'azure-openai/gpt-4-deployment', instructions: 'You are a helpful assistant', }); ``` - Fix Anthropic API error when tool calls have empty input objects ([#11474](https://github.com/mastra-ai/mastra/pull/11474)) Fixes issue #11376 where Anthropic models would fail with error "messages.17.content.2.tool_use.input: Field required" when a tool call in a previous step had an empty object `{}` as input. The fix adds proper reconstruction of tool call arguments when converting messages to AIV5 model format. Tool-result parts now correctly include the `input` field from the matching tool call, which is required by Anthropic's API validation. Changes: - Added `findToolCallArgs()` helper method to search through messages and retrieve original tool call arguments - Enhanced `aiV5UIMessagesToAIV5ModelMessages()` to populate the `input` field on tool-result parts - Added comprehensive test coverage for empty object inputs, parameterized inputs, and multi-turn conversations - Add additional context to workflow `onFinish` and `onError` callbacks ([#11705](https://github.com/mastra-ai/mastra/pull/11705)) The `onFinish` and `onError` lifecycle callbacks now receive additional properties: - `runId` - The unique identifier for the workflow run - `workflowId` - The workflow's identifier - `resourceId` - Optional resource identifier (if provided when creating the run) - `getInitData()` - Function that returns the initial input data passed to the workflow - `mastra` - The Mastra instance (if workflow is registered with Mastra) - `requestContext` - Request-scoped context data - `logger` - The workflow's logger instance - `state` - The workflow's current state object ```typescript const workflow = createWorkflow({ id: 'order-processing', inputSchema: z.object({ orderId: z.string() }), outputSchema: z.object({ status: z.string() }), options: { onFinish: async ({ runId, workflowId, getInitData, logger, state, mastra }) => { const inputData = getInitData(); logger.info(`Workflow ${workflowId} run ${runId} completed`, { orderId: inputData.orderId, finalState: state, }); // Access other Mastra components if needed const agent = mastra?.getAgent('notification-agent'); }, onError: async ({ runId, workflowId, error, logger, requestContext }) => { logger.error(`Workflow ${workflowId} run ${runId} failed: ${error?.message}`); // Access request context for additional debugging const userId = requestContext.get('userId'); }, }, }); ``` - Fix workflow tool not executing when requireApproval is true and tool call is approved ([#11538](https://github.com/mastra-ai/mastra/pull/11538)) - unexpected json parse issue, log error but dont fail ([#10241](https://github.com/mastra-ai/mastra/pull/10241)) - Updated OtelExporters, Bridge, and Arize packages to better implement GenAI v1.38.0 Otel Semantic Conventions. See: ([#10591](https://github.com/mastra-ai/mastra/pull/10591)) https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/gen-ai/README.md - Standardize error IDs across all storage and vector stores using centralized helper functions (`createStorageErrorId` and `createVectorErrorId`). This ensures consistent error ID patterns (`MASTRA_STORAGE_{STORE}_{OPERATION}_{STATUS}` and `MASTRA_VECTOR_{STORE}_{OPERATION}_{STATUS}`) across the codebase for better error tracking and debugging. ([#10913](https://github.com/mastra-ai/mastra/pull/10913)) - Remove redundant toolCalls from network agent finalResult ([#11189](https://github.com/mastra-ai/mastra/pull/11189)) The network agent's `finalResult` was storing `toolCalls` separately even though all tool call information is already present in the `messages` array (as `tool-call` and `tool-result` type messages). This caused significant token waste since the routing agent reads this data from memory on every iteration. **Before:** `finalResult: { text, toolCalls, messages }` **After:** `finalResult: { text, messages }` +**Migration:** If you were accessing `finalResult.toolCalls`, retrieve tool calls from `finalResult.messages` by filtering for messages with `type: 'tool-call'`. Updated `@mastra/react` to extract tool calls directly from the `messages` array instead of the removed `toolCalls` field when resolving initial messages from memory. Fixes #11059 - Fixed a bug in agent networks where sometimes the task name was empty ([#10629](https://github.com/mastra-ai/mastra/pull/10629)) - Fix agent onChunk callback receiving wrapped chunk instead of direct chunk ([#9350](https://github.com/mastra-ai/mastra/pull/9350)) - When calling `abort()` inside a `processInputStep` processor, the TripWire was being caught by the model retry logic instead of emitting a tripwire chunk to the stream. ([#11343](https://github.com/mastra-ai/mastra/pull/11343)) Before this fix, processors using `processInputStep` with abort would see errors like: ``` Error executing model gpt-4o-mini, attempt 1==== TripWire [Error]: Potentially harmful content detected ``` Now the TripWire is properly handled - it emits a tripwire chunk and signals the abort correctly, - Embed AI types to fix peerdeps mismatches ([`9650cce`](https://github.com/mastra-ai/mastra/commit/9650cce52a1d917ff9114653398e2a0f5c3ba808)) - Deprecate `runCount` parameter in favor of `retryCount` for better naming clarity. The name `runCount` was misleading as it doesn't represent the total number of times a step has run, but rather the number of retry attempts made for a step. ([#9153](https://github.com/mastra-ai/mastra/pull/9153)) `runCount` is available in `execute()` functions and methods that interact with the step execution. This also applies to condition functions and loop condition functions that use this parameter. If your code uses `runCount`, change the name to `retryCount`. Here's an example migration: ```diff const myStep = createStep({ // Rest of step... - execute: async ({ runCount, ...params }) => { + execute: async ({ retryCount, ...params }) => { // ... rest of your logic } }); ``` - Set correct peer dependency range for `@mastra/observability` ([#9908](https://github.com/mastra-ai/mastra/pull/9908)) - Add requestContext column if it does not exist ([#9786](https://github.com/mastra-ai/mastra/pull/9786)) - Only handle download image asset transformation if needed ([#10122](https://github.com/mastra-ai/mastra/pull/10122)) - Fix model-level and runtime header support for LLM calls ([#11275](https://github.com/mastra-ai/mastra/pull/11275)) This fixes a bug where custom headers configured on models (like `anthropic-beta`) were not being passed through to the underlying AI SDK calls. The fix properly handles headers from multiple sources with correct priority: **Header Priority (low to high):** 1. Model config headers - Headers set in model configuration 2. ModelSettings headers - Runtime headers that override model config 3. Provider-level headers - Headers baked into AI SDK providers (not overridden) **Examples that now work:** ```typescript // Model config headers new Agent({ model: { id: 'anthropic/claude-4-5-sonnet', headers: { 'anthropic-beta': 'context-1m-2025-08-07' }, }, }); // Runtime headers override config agent.generate('...', { modelSettings: { headers: { 'x-custom': 'runtime-value' } }, }); // Provider-level headers preserved const openai = createOpenAI({ headers: { 'openai-organization': 'org-123' } }); new Agent({ model: openai('gpt-4o-mini') }); ``` - - Fix tool suspension throwing error when `outputSchema` is passed to tool during creation ([#10444](https://github.com/mastra-ai/mastra/pull/10444)) - Pass `suspendSchema` and `resumeSchema` from tool into step created when creating step from tool - Consolidate memory integration tests and fix working memory filtering in MessageHistory processor ([#11367](https://github.com/mastra-ai/mastra/pull/11367)) Moved `extractWorkingMemoryTags`, `removeWorkingMemoryTags`, and `extractWorkingMemoryContent` utilities from `@mastra/memory` to `@mastra/core/memory` so they can be used by the `MessageHistory` processor. Updated `MessageHistory.filterMessagesForPersistence()` to properly filter out `updateWorkingMemory` tool invocations and strip working memory tags from text content, fixing an issue where working memory tool call arguments were polluting saved message history for v5+ models. Also consolidated integration tests for agent-memory, working-memory, and pg-storage into shared test functions that can run against multiple model versions (v4, v5, v6). - Fix type safety for message ordering - restrict `orderBy` to only accept `'createdAt'` field ([#11069](https://github.com/mastra-ai/mastra/pull/11069)) Messages don't have an `updatedAt` field, but the previous type allowed ordering by it, which would return empty results. This change adds compile-time type safety by making `StorageOrderBy` generic and restricting `StorageListMessagesInput.orderBy` to only accept `'createdAt'`. The API validation schemas have also been updated to reject invalid orderBy values at runtime. - Fixed agent network mode failing with "Cannot read properties of undefined" error when tools or workflows don't have an `inputSchema` defined. ([#12063](https://github.com/mastra-ai/mastra/pull/12063)) - **@mastra/core:** Fixed `getRoutingAgent()` to handle tools and workflows without `inputSchema` by providing a default empty schema fallback. - **@mastra/schema-compat:** Fixed Zod v4 optional/nullable fields producing invalid JSON schema for OpenAI structured outputs. OpenAI now correctly receives `type: ["string", "null"]` instead of `anyOf` patterns that were rejected with "must have a 'type' key" error. - Fix invalid state: Controller is already closed ([`932d63d`](https://github.com/mastra-ai/mastra/commit/932d63dd51be9c8bf1e00e3671fe65606c6fb9cd)) Fixes #11005 - Add support for AI SDK's `needsApproval` in tools. ([#11388](https://github.com/mastra-ai/mastra/pull/11388)) **AI SDK tools with static approval:** ```typescript import { tool } from 'ai'; import { z } from 'zod'; const weatherTool = tool({ description: 'Get weather information', inputSchema: z.object({ city: z.string() }), needsApproval: true, execute: async ({ city }) => { return { weather: 'sunny', temp: 72 }; }, }); ``` **AI SDK tools with dynamic approval:** ```typescript const paymentTool = tool({ description: 'Process payment', inputSchema: z.object({ amount: z.number() }), needsApproval: async ({ amount }) => amount > 1000, execute: async ({ amount }) => { return { success: true, amount }; }, }); ``` **Mastra tools continue to work with `requireApproval`:** ```typescript import { createTool } from '@mastra/core'; const deleteTool = createTool({ id: 'delete-file', description: 'Delete a file', requireApproval: true, inputSchema: z.object({ path: z.string() }), execute: async ({ path }) => { return { deleted: true }; }, }); ``` - Add serializedStepGraph to runExecutionResult response ([#10004](https://github.com/mastra-ai/mastra/pull/10004)) - Add `onFinish` and `onError` lifecycle callbacks to workflow options ([#11200](https://github.com/mastra-ai/mastra/pull/11200)) Workflows now support lifecycle callbacks for server-side handling of workflow completion and errors: - `onFinish`: Called when workflow completes with any status (success, failed, suspended, tripwire) - `onError`: Called only when workflow fails (failed or tripwire status) ```typescript const workflow = createWorkflow({ id: 'my-workflow', inputSchema: z.object({ ... }), outputSchema: z.object({ ... }), options: { onFinish: async (result) => { // Handle any workflow completion await updateJobStatus(result.status); }, onError: async (errorInfo) => { // Handle workflow failures await logError(errorInfo.error); }, }, }); ``` Both callbacks support sync and async functions. Callback errors are caught and logged, not propagated to the workflow result. - Adds `tool-result` and `tool-error` chunks to the processor.processOutputStream path. Processors now have access to these two chunks. ([#10645](https://github.com/mastra-ai/mastra/pull/10645)) - Add `onOutput` hook for tools ([#10466](https://github.com/mastra-ai/mastra/pull/10466)) Tools now support an `onOutput` lifecycle hook that is invoked after successful tool execution. This complements the existing `onInputStart`, `onInputDelta`, and `onInputAvailable` hooks to provide complete visibility into the tool execution lifecycle. The `onOutput` hook receives: - `output`: The tool's return value (typed according to `outputSchema`) - `toolCallId`: Unique identifier for the tool call - `toolName`: The name of the tool that was executed - `abortSignal`: Signal for detecting if the operation should be cancelled Example usage: ```typescript import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; export const weatherTool = createTool({ id: 'weather-tool', description: 'Get weather information', outputSchema: z.object({ temperature: z.number(), conditions: z.string(), }), execute: async input => { return { temperature: 72, conditions: 'sunny' }; }, onOutput: ({ output, toolCallId, toolName }) => { console.log(`${toolName} completed:`, output); // output is fully typed based on outputSchema }, }); ``` Hook execution order: 1. `onInputStart` - Input streaming begins 2. `onInputDelta` - Input chunks arrive (called multiple times) 3. `onInputAvailable` - Complete input parsed and validated 4. Tool's `execute` function runs 5. `onOutput` - Tool completed successfully (NEW) - Fix saveScore not persisting ID correctly, breaking getScoreById retrieval ([#10915](https://github.com/mastra-ai/mastra/pull/10915)) **What Changed** - saveScore now correctly returns scores that can be retrieved with getScoreById - Validation errors now include contextual information (scorer, entity, trace details) for easier debugging **Impact** Previously, calling getScoreById after saveScore would return null because the generated ID wasn't persisted to the database. This is now fixed across all store implementations, ensuring consistent behavior and data integrity. - Fix HITL (Human-In-The-Loop) tool execution bug when mixing tools with and without execute functions. ([#11178](https://github.com/mastra-ai/mastra/pull/11178)) When an agent called multiple tools simultaneously where some had `execute` functions and others didn't (HITL tools expecting `addToolResult` from the frontend), the HITL tools would incorrectly receive `result: undefined` and be marked as "output-available" instead of "input-available". This caused the agent to continue instead of pausing for user input. - Track usage in workflow and agent network ([#9649](https://github.com/mastra-ai/mastra/pull/9649)) - Add resourceId to workflow routes ([#11166](https://github.com/mastra-ai/mastra/pull/11166)) - Fixed AbortSignal not propagating from parent workflows to nested sub-workflows in the evented workflow engine. ([#11142](https://github.com/mastra-ai/mastra/pull/11142)) Previously, canceling a parent workflow did not stop nested sub-workflows, causing them to continue running and consuming resources after the parent was canceled. Now, when you cancel a parent workflow, all nested sub-workflows are automatically canceled as well, ensuring clean termination of the entire workflow tree. **Example:** ```typescript const parentWorkflow = createWorkflow({ id: 'parent-workflow' }).then(someStep).then(nestedChildWorkflow).commit(); const run = await parentWorkflow.createRun(); const resultPromise = run.start({ inputData: { value: 5 } }); // Cancel the parent workflow - nested workflows will also be canceled await run.cancel(); // or use: run.abortController.abort(); const result = await resultPromise; // result.status === 'canceled' // All nested child workflows are also canceled ``` Related to #11063 - Add new deleteVectors, updateVector by filter ([#10408](https://github.com/mastra-ai/mastra/pull/10408)) - Include `.input` in workflow results for both engines and remove the option to omit them from Inngest workflows. ([#10688](https://github.com/mastra-ai/mastra/pull/10688)) - Add support for typed structured output in agent workflow steps ([#11014](https://github.com/mastra-ai/mastra/pull/11014)) When wrapping an agent with `createStep()` and providing a `structuredOutput.schema`, the step's `outputSchema` is now correctly inferred from the provided schema instead of defaulting to `{ text: string }`. This enables type-safe chaining of agent steps with structured output to subsequent steps: ```typescript const articleSchema = z.object({ title: z.string(), summary: z.string(), tags: z.array(z.string()), }); // Agent step with structured output - outputSchema is now articleSchema const agentStep = createStep(agent, { structuredOutput: { schema: articleSchema }, }); // Next step can receive the structured output directly const processStep = createStep({ id: 'process', inputSchema: articleSchema, // Matches agent's outputSchema outputSchema: z.object({ tagCount: z.number() }), execute: async ({ inputData }) => ({ tagCount: inputData.tags.length, // Fully typed! }), }); workflow.then(agentStep).then(processStep).commit(); ``` When `structuredOutput` is not provided, the agent step continues to use the default `{ text: string }` output schema. - **Breaking Change:** `memory.readOnly` has been moved to `memory.options.readOnly` ([#11523](https://github.com/mastra-ai/mastra/pull/11523)) The `readOnly` option now lives inside `memory.options` alongside other memory configuration like `lastMessages` and `semanticRecall`. **Before:** ```typescript agent.stream('Hello', { memory: { thread: threadId, resource: resourceId, readOnly: true, }, }); ``` **After:** ```typescript agent.stream('Hello', { memory: { thread: threadId, resource: resourceId, options: { readOnly: true, }, }, }); ``` **Migration:** Run the codemod to update your code automatically: ```shell npx @mastra/codemod@beta v1/memory-readonly-to-options . ``` This also fixes issue #11519 where `readOnly: true` was being ignored and messages were saved to memory anyway. - `getSpeakers` endpoint returns an empty array if voice is not configured on the agent and `getListeners` endpoint returns `{ enabled: false }` if voice is not figured on the agent. ([#10560](https://github.com/mastra-ai/mastra/pull/10560)) When no voice is set on agent don't throw error, by default set voice to undefined rather than DefaultVoice which throws errors when it is accessed. - Auto resume suspended tools if `autoResumeSuspendedTools: true` ([#11157](https://github.com/mastra-ai/mastra/pull/11157)) The flag can be added to `defaultAgentOptions` when creating the agent or to options in `agent.stream` or `agent.generate` ```typescript const agent = new Agent({ //...agent information, defaultAgentOptions: { autoResumeSuspendedTools: true, }, }); ``` - Allow resuming nested workflow step with chained id ([#9459](https://github.com/mastra-ai/mastra/pull/9459)) Example, you have a workflow like this ``` export const supportWorkflow = mainWorkflow.then(nestedWorkflow).commit(); ``` And a step in `nestedWorkflow` is supsended, you can now also resume it any of these ways: ``` run.resume({ step: "nestedWorkflow.suspendedStep", //chained nested workflow step id and suspended step id //other resume params }) ``` OR ``` run.resume({ step: "nestedWorkflow", // just the nested workflow step/step id //other resume params }) ``` - Preserve error details when thrown from workflow steps ([#10992](https://github.com/mastra-ai/mastra/pull/10992)) - Errors thrown in workflow steps now preserve full error details including `cause` chain and custom properties - Added `SerializedError` type with proper cause chain support - Added `SerializedStepResult` and `SerializedStepFailure` types for handling errors loaded from storage - Enhanced `addErrorToJSON` to recursively serialize error cause chains with max depth protection - Added `hydrateSerializedStepErrors` to convert serialized errors back to Error instances - Fixed Inngest workflow error handling to extract original error from `NonRetriableError.cause` - Fix base64 encoded images with threads - issue #10480 ([#10483](https://github.com/mastra-ai/mastra/pull/10483)) Fixed "Invalid URL" error when using base64 encoded images (without `data:` prefix) in agent calls with threads and resources. Raw base64 strings are now automatically converted to proper data URIs before being processed. **Changes:** - Updated `attachments-to-parts.ts` to detect and convert raw base64 strings to data URIs - Fixed `MessageList` image processing to handle raw base64 in two locations: - Image part conversion in `aiV4CoreMessageToV1PromptMessage` - File part to experimental_attachments conversion in `mastraDBMessageToAIV4UIMessage` - Added comprehensive tests for base64 images, data URIs, and HTTP URLs with threads **Breaking Change:** None - this is a bug fix that maintains backward compatibility while adding support for raw base64 strings. - Fix OpenAI schema validation errors in processors ([#9093](https://github.com/mastra-ai/mastra/pull/9093)) - Fix dimension mismatch error when switching embedders in SemanticRecall. The processor now properly validates vector index dimensions when an index already exists, preventing runtime errors when switching between embedders with different dimensions (e.g., fastembed 384 dims → OpenAI 1536 dims). ([#11893](https://github.com/mastra-ai/mastra/pull/11893)) - SimpleAuth and improved CloudAuth ([#10490](https://github.com/mastra-ai/mastra/pull/10490)) - Move `@ai-sdk/azure` to devDependencies ([#10218](https://github.com/mastra-ai/mastra/pull/10218)) - When LLMs like Claude Sonnet 4.5 and Gemini 2.4 call tools with all-optional parameters, they send `args: undefined` instead of `args: {}`. This caused validation to fail with "root: Required". ([#10728](https://github.com/mastra-ai/mastra/pull/10728)) The fix normalizes `undefined`/`null` to `{}` for object schemas and `[]` for array schemas before validation. - Removes the deprecated `threadId` and `resourceId` options from `AgentExecutionOptions`. These have been deprecated for months in favour of the `memory` option. ([#11897](https://github.com/mastra-ai/mastra/pull/11897)) ### Breaking Changes #### `@mastra/core` The `threadId` and `resourceId` options have been removed from `agent.generate()` and `agent.stream()`. Use the `memory` option instead: ```ts // Before await agent.stream('Hello', { threadId: 'thread-123', resourceId: 'user-456', }); // After await agent.stream('Hello', { memory: { thread: 'thread-123', resource: 'user-456', }, }); ``` #### `@mastra/server` The `threadId`, `resourceId`, and `resourceid` fields have been removed from the main agent execution body schema. The server now expects the `memory` option format in request bodies. Legacy routes (`/api/agents/:agentId/generate-legacy` and `/api/agents/:agentId/stream-legacy`) continue to support the deprecated fields. #### `@mastra/react` The `useChat` hook now internally converts `threadId` to the `memory` option format when making API calls. No changes needed in component code - the hook handles the conversion automatically. #### `@mastra/client-js` When using the client SDK agent methods, use the `memory` option instead of `threadId`/`resourceId`: ```ts const agent = client.getAgent('my-agent'); // Before await agent.generate([...], { threadId: 'thread-123', resourceId: 'user-456', }); // After await agent.generate([...], { memory: { thread: 'thread-123', resource: 'user-456', }, }); ``` - Loosen tools types in processInputStep / prepareStep. ([#11071](https://github.com/mastra-ai/mastra/pull/11071)) - Breaking change to move mcp related tool execute arguments nested under an `mcp` argument that is only populated if the tool is passed to an MCPServer. This simpliflies tool definitions and gives you the correct types when working with tools meant for MCP servers. ([#9134](https://github.com/mastra-ai/mastra/pull/9134)) - Refactor internal event system from Emitter to PubSub abstraction for workflow event handling. This change replaces the EventEmitter-based event system with a pluggable PubSub interface, enabling support for distributed workflow execution backends like Inngest. Adds `close()` method to PubSub implementations for proper cleanup. ([#11052](https://github.com/mastra-ai/mastra/pull/11052)) - Fix stopWhen type to accept AI SDK v6 StopCondition functions like `stepCountIs()` ([#11402](https://github.com/mastra-ai/mastra/pull/11402)) - Add human-in-the-loop (HITL) support to agent networks ([#11678](https://github.com/mastra-ai/mastra/pull/11678)) - Add suspend/resume capabilities to agent network - Enable auto-resume for suspended network execution via `autoResumeSuspendedTools` `agent.resumeNetwork`, `agent.approveNetworkToolCall`, `agent.declineNetworkToolCall` - Fixed tool validation error messages so logs show Zod validation errors directly instead of hiding them inside structured JSON. ([#10579](https://github.com/mastra-ai/mastra/pull/10579)) - Add `startAsync()` method and fix Inngest duplicate workflow execution bug ([#11093](https://github.com/mastra-ai/mastra/pull/11093)) **New Feature: `startAsync()` for fire-and-forget workflow execution** - Add `Run.startAsync()` to base workflow class - starts workflow in background and returns `{ runId }` immediately - Add `EventedRun.startAsync()` - publishes workflow start event without subscribing for completion - Add `InngestRun.startAsync()` - sends Inngest event without polling for result **Prevent duplicate Inngest workflow executions** - Fix `getRuns()` to properly handle rate limits (429), empty responses, and JSON parse errors with retry logic and exponential backoff - Fix `getRunOutput()` to throw `NonRetriableError` when polling fails, preventing Inngest from retrying the parent function and re-triggering the workflow - Add timeout to `getRunOutput()` polling (default 5 minutes) with `NonRetriableError` on timeout This fixes a production issue where polling failures after successful workflow completion caused Inngest to retry the parent function, which fired a new workflow event and resulted in duplicate executions (e.g., duplicate Slack messages). - Fixed AI SDK v6 provider tools (like `openai.tools.webSearch()`) not being invoked correctly. These tools are now properly recognized and executed instead of causing failures or hallucinated tool calls. ([#11946](https://github.com/mastra-ai/mastra/pull/11946)) Resolves `#11781`. - Fix agent runs with multiple steps only showing last text chunk in observability tools ([#11672](https://github.com/mastra-ai/mastra/pull/11672)) When an agent model executes multiple steps and generates multiple text chunks, the onFinish payload was only receiving the text from the last step instead of all accumulated text. This caused observability tools like Braintrust to only display the final text chunk. The fix now correctly concatenates all text chunks from all steps. - Adds validation guards to handle undefined/null values that can occur when config objects are spread (`{ ...config }`). Previously, if getters or non-enumerable properties resulted in undefined values during spread, the constructor would throw cryptic errors when accessing `.id` or `.name` on undefined objects. ([#10718](https://github.com/mastra-ai/mastra/pull/10718)) - Fix missing `title` field in Convex threads table schema ([#11356](https://github.com/mastra-ai/mastra/pull/11356)) The Convex schema was hardcoded and out of sync with the core `TABLE_SCHEMAS`, causing errors when creating threads: ``` Error: Failed to insert or update a document in table "mastra_threads" because it does not match the schema: Object contains extra field `title` that is not in the validator. ``` Now the Convex schema dynamically builds from `TABLE_SCHEMAS` via a new `@mastra/core/storage/constants` export path that doesn't pull in Node.js dependencies (safe for Convex's sandboxed schema evaluation). ```typescript // Users can now import schema tables without Node.js dependency issues import { mastraThreadsTable, mastraMessagesTable } from '@mastra/convex/schema'; export default defineSchema({ mastra_threads: mastraThreadsTable, mastra_messages: mastraMessagesTable, }); ``` Fixes #11319 - Fix tool input validation destroying non-plain objects ([#11541](https://github.com/mastra-ai/mastra/pull/11541)) The `convertUndefinedToNull` function in tool input validation was treating all objects as plain objects and recursively processing them. For objects like `Date`, `Map`, `URL`, and class instances, this resulted in empty objects `{}` because they have no enumerable own properties. This fix changes the approach to only recurse into plain objects (objects with `Object.prototype` or `null` prototype). All other objects (Date, Map, Set, URL, RegExp, Error, custom class instances, etc.) are now preserved as-is. Fixes #11502 - Fixed an issue where deprecated Groq models were shown during template creation. The model selection now filters out models marked as deprecated, displaying only active and supported models. ([#11445](https://github.com/mastra-ai/mastra/pull/11445)) - Workaround for duplicate text-start/text-end IDs in multi-step agentic flows. ([#10740](https://github.com/mastra-ai/mastra/pull/10740)) The `@ai-sdk/anthropic` and `@ai-sdk/google` providers use numeric indices ("0", "1", etc.) for text block IDs that reset for each LLM call. This caused duplicate IDs when an agent does TEXT → TOOL → TEXT, breaking message ordering and storage. The fix replaces numeric IDs with UUIDs, maintaining a map per step so text-start, text-delta, and text-end chunks for the same block share the same UUID. OpenAI's UUIDs pass through unchanged. - Added support for AI SDK v6 embedding models (specification version v3) in memory and vector modules. Fixed TypeScript error where `ModelRouterEmbeddingModel` was trying to implement a union type instead of `EmbeddingModelV2` directly. ([#11362](https://github.com/mastra-ai/mastra/pull/11362)) - Fix empty overrideScorers causing error instead of skipping scoring ([#11257](https://github.com/mastra-ai/mastra/pull/11257)) When `overrideScorers` was passed as an empty object `{}`, the agent would throw a "No scorers found" error. Now an empty object explicitly skips scoring, while `undefined` continues to use default scorers. - Fix GPT-5/o3 reasoning models failing with "required reasoning item" errors when using memory with tools. Empty reasoning is now stored with providerMetadata to preserve OpenAI's item_reference. ([#10585](https://github.com/mastra-ai/mastra/pull/10585)) - Fix generateTitle model type to accept AI SDK LanguageModelV2 ([#10541](https://github.com/mastra-ai/mastra/pull/10541)) Updated the `generateTitle.model` config option to accept `MastraModelConfig` instead of `MastraLanguageModel`. This allows users to pass raw AI SDK `LanguageModelV2` models (e.g., `anthropic.languageModel('claude-3-5-haiku-20241022')`) directly without type errors. Previously, passing a standard `LanguageModelV2` would fail because `MastraLanguageModelV2` has different `doGenerate`/`doStream` return types. Now `MastraModelConfig` is used consistently across: - `memory/types.ts` - `generateTitle.model` config - `agent.ts` - `genTitle`, `generateTitleFromUserMessage`, `resolveTitleGenerationConfig` - `agent-legacy.ts` - `AgentLegacyCapabilities` interface - fix: support gs:// and s3:// cloud storage URLs in attachmentsToParts ([#11398](https://github.com/mastra-ai/mastra/pull/11398)) - Some LLMs (particularly when not using native JSON mode) output actual newline characters inside JSON string values instead of properly escaped `\n` sequences. This breaks JSON parsing and causes structured output to fail. ([#10965](https://github.com/mastra-ai/mastra/pull/10965)) This change adds preprocessing to escape unescaped control characters (`\n`, `\r`, `\t`) within JSON string values before parsing, making structured output more robust across different LLM providers. - Add validation to detect when a function is passed as a tool instead of a tool object. Previously, passing a tool factory function (e.g., `tools: { myTool }` instead of `tools: { myTool: myTool() }`) would silently fail - the LLM would request tool calls but nothing would execute. Now throws a clear error with guidance on how to fix it. ([#11288](https://github.com/mastra-ai/mastra/pull/11288)) - Fixed client-side tool invocations not being stored in memory. Previously, tool invocations with state 'call' were filtered out before persistence, which incorrectly removed client-side tools. Now only streaming intermediate states ('partial-call') are filtered. ([#11630](https://github.com/mastra-ai/mastra/pull/11630)) Fixed a crash when updating working memory with an empty or null update; existing data is now preserved. - Fixed memory readOnly option not being respected when agents share a RequestContext. Previously, when output processors were resolved, the readOnly check happened too early - before the agent could set its own MastraMemory context. This caused child agents to inherit their parent's readOnly setting when sharing a RequestContext. ([#11653](https://github.com/mastra-ai/mastra/pull/11653)) The readOnly check is now only done at execution time in each processor's processOutputResult method, allowing proper isolation. - Messages without `createdAt` timestamps were getting shuffled because they all received identical timestamps during conversion. Now messages are assigned monotonically increasing timestamps via `generateCreatedAt()`, preserving input order. ([#10686](https://github.com/mastra-ai/mastra/pull/10686)) Before: ``` Input: [user: "hello", assistant: "Hi!", user: "bye"] Output: [user: "bye", assistant: "Hi!", user: "hello"] // shuffled! ``` After: ``` Input: [user: "hello", assistant: "Hi!", user: "bye"] Output: [user: "hello", assistant: "Hi!", user: "bye"] // correct order ``` - fix(observability): start MODEL_STEP span at beginning of LLM execution ([#11409](https://github.com/mastra-ai/mastra/pull/11409)) The MODEL_STEP span was being created when the step-start chunk arrived (after the model API call completed), causing the span's startTime to be close to its endTime instead of accurately reflecting when the step began. This fix ensures MODEL_STEP spans capture the full duration of each LLM execution step, including the API call latency, by starting the span at the beginning of the step execution rather than when the response starts streaming. Fixes #11271 - Fix network validation not seeing previous iteration results in multi-step tasks ([#11691](https://github.com/mastra-ai/mastra/pull/11691)) The validation LLM was unable to determine task completion for multi-step tasks because it couldn't see what primitives had already executed. Now includes a compact list of completed primitives in the validation prompt. - Fix tool outputSchema validation to allow unsupported Zod types like ZodTuple. The outputSchema is only used for internal validation and never sent to the LLM, so model compatibility checks are not needed. ([#9409](https://github.com/mastra-ai/mastra/pull/9409)) - Fix provider-executed tools (like `openai.tools.webSearch()`) not working correctly with AI SDK v6 models. The agent's `generate()` method was ending prematurely with `finishReason: 'tool-calls'` instead of completing with a text response after tool execution. ([#11622](https://github.com/mastra-ai/mastra/pull/11622)) The issue was that V6 provider tools have `type: 'provider'` while V5 uses `type: 'provider-defined'`. The tool preparation code now detects the model version and uses the correct type. - Fix reasoning providerMetadata leaking into text parts when using memory with OpenAI reasoning models. The runState.providerOptions is now cleared after reasoning-end to prevent text parts from inheriting the reasoning's itemId. ([#11380](https://github.com/mastra-ai/mastra/pull/11380)) - Scorers now have access to custom gateways when resolving models. Previously, calling `resolveModelConfig` in the scorer didn't pass the Mastra instance, so custom gateways were never available. ([#10778](https://github.com/mastra-ai/mastra/pull/10778)) - Ensure model_generation spans end before agent_run spans. ([#9251](https://github.com/mastra-ai/mastra/pull/9251)) - Sub-agents with dynamic model configurations were broken because `requestContext` was not being passed to `getModel()` when creating agent tools. This caused sub-agents using function-based model configurations to receive an empty context instead of the parent's context. ([#10844](https://github.com/mastra-ai/mastra/pull/10844)) No code changes required for consumers - this fix restores expected behavior for dynamic model configurations in sub-agents. - Fixed `TokenLimiterProcessor` not filtering memory messages when limiting tokens. ([#11941](https://github.com/mastra-ai/mastra/pull/11941)) Previously, the processor only received the latest user input messages, missing the conversation history from memory. This meant token limiting couldn't filter historical messages to fit within the context window. The processor now correctly: - Accesses all messages (memory + input) when calculating token budgets - Accounts for system messages in the token budget - Filters older messages to prioritize recent conversation context Fixes #11902 - Fixed inline type narrowing for `tool.execute()` return type when using `outputSchema`. ([#11420](https://github.com/mastra-ai/mastra/pull/11420)) **Problem:** When calling `tool.execute()`, TypeScript couldn't narrow the `ValidationError | OutputType` union after checking `'error' in result && result.error`, causing type errors when accessing output properties. **Solution:** - Added `{ error?: never }` to the success type, enabling proper discriminated union narrowing - Simplified `createTool` generics so `inputData` is correctly typed based on `inputSchema` **Note:** Tool output schemas should not use `error` as a field name since it's reserved for ValidationError discrimination. Use `errorMessage` or similar instead. **Usage:** ```typescript const result = await myTool.execute({ firstName: 'Hans' }); if ('error' in result && result.error) { console.error('Validation failed:', result.message); return; } // ✅ TypeScript now correctly narrows result return { fullName: result.fullName }; ``` - Fix toolCallId propagation in agent network tool execution. The toolCallId property was undefined at runtime despite being required by TypeScript type definitions in AgentToolExecutionContext. Now properly passes the toolCallId through to the tool's context during network tool execution. ([#10951](https://github.com/mastra-ai/mastra/pull/10951)) - Changes `ToolStream` to extend `WritableStream` instead of `WritableStream`. This fixes the TypeScript error when piping `objectStream` or `fullStream` to `writer` in workflow steps. ([#10845](https://github.com/mastra-ai/mastra/pull/10845)) Before: ```typescript // TypeError: ToolStream is not assignable to WritableStream> await response.objectStream.pipeTo(writer); ``` After: ```typescript // Works without type errors await response.objectStream.pipeTo(writer); ``` - Fix AI SDK v6 (specificationVersion: "v3") model support in sub-agent calls. Previously, when a parent agent invoked a sub-agent with a v3 model through the `agents` property, the version check only matched "v2", causing v3 models to incorrectly fall back to legacy streaming methods and throw "V2 models are not supported for streamLegacy" error. ([#11452](https://github.com/mastra-ai/mastra/pull/11452)) The fix updates version checks in `listAgentTools` and `llm-mapping-step.ts` to use the centralized `supportedLanguageModelSpecifications` array which includes both v2 and v3. Also adds missing v3 test coverage to tool-handling.test.ts to prevent regression. - Ensure that when resuming a workflow from a snapshot, the input property is correctly set from the snapshot's context input rather than from resume data. This prevents the loss of original workflow input data during suspend/resume cycles. ([#9380](https://github.com/mastra-ai/mastra/pull/9380)) - When createRun is called with an existing runId, it now correctly updates the run's status from the storage snapshot. This fixes the issue where different workflow instances (e.g., different API requests) would get a run with 'pending' status instead of the correct status from storage (e.g., 'suspended'). ([#10664](https://github.com/mastra-ai/mastra/pull/10664)) - Fixed "Transforms cannot be represented in JSON Schema" error when using Zod v4 with structuredOutput ([#11466](https://github.com/mastra-ai/mastra/pull/11466)) When using schemas with `.optional()`, `.nullable()`, `.default()`, or `.nullish().default("")` patterns with `structuredOutput` and Zod v4, users would encounter an error because OpenAI schema compatibility layer adds transforms that Zod v4's native `toJSONSchema()` cannot handle. The fix uses Mastra's transform-safe `zodToJsonSchema` function which gracefully handles transforms by using the `unrepresentable: 'any'` option. Also exported `isZodType` utility from `@mastra/schema-compat` and updated it to detect both Zod v3 (`_def`) and Zod v4 (`_zod`) schemas. - Fix crash in `mastraDBMessageToAIV4UIMessage` when `content.parts` is undefined or null. ([#11550](https://github.com/mastra-ai/mastra/pull/11550)) This resolves an issue where `ModerationProcessor` (and other code paths using `MessageList.get.*.ui()`) would throw `TypeError: Cannot read properties of undefined (reading 'length')` when processing messages with missing `parts` array. This commonly occurred when using AI SDK v4 (LanguageModelV1) models with input/output processors. The fix adds null coalescing (`?? []`) to safely handle undefined/null `parts` in the message conversion method. - Upgrade AI SDK v6 from beta to stable (6.0.1) and fix finishReason breaking change. ([#11351](https://github.com/mastra-ai/mastra/pull/11351)) AI SDK v6 stable changed finishReason from a string to an object with `unified` and `raw` properties. Added `normalizeFinishReason()` helper to handle both v5 (string) and v6 (object) formats at the stream transform layer - Improve autoResumeSuspendedTools instruction for tool approval ([#11338](https://github.com/mastra-ai/mastra/pull/11338)) - Fix vector definition to fix pinecone ([#10150](https://github.com/mastra-ai/mastra/pull/10150)) - Preserve error details when thrown from workflow steps ([#10992](https://github.com/mastra-ai/mastra/pull/10992)) Workflow errors now retain custom properties like `statusCode`, `responseHeaders`, and `cause` chains. This enables error-specific recovery logic in your applications. **Before:** ```typescript const result = await workflow.execute({ input }); if (result.status === 'failed') { // Custom error properties were lost console.log(result.error); // "Step execution failed" (just a string) } ``` **After:** ```typescript const result = await workflow.execute({ input }); if (result.status === 'failed') { // Custom properties are preserved console.log(result.error.message); // "Step execution failed" console.log(result.error.statusCode); // 429 console.log(result.error.cause?.name); // "RateLimitError" } ``` **Type change:** `WorkflowState.error` and `WorkflowRunState.error` types changed from `string | Error` to `SerializedError`. Other changes: - Added `UpdateWorkflowStateOptions` type for workflow state updates - Fix message metadata not persisting when using simple message format. Previously, custom metadata passed in messages (e.g., `{role: 'user', content: 'text', metadata: {userId: '123'}}`) was not being saved to the database. This occurred because the CoreMessage conversion path didn't preserve metadata fields. ([#10488](https://github.com/mastra-ai/mastra/pull/10488)) Now metadata is properly preserved for all message input formats: - Simple CoreMessage format: `{role, content, metadata}` - Full UIMessage format: `{role, content, parts, metadata}` - AI SDK v5 ModelMessage format with metadata Fixes #8556 - Fix Zod 4 compatibility for storage schema detection ([#11431](https://github.com/mastra-ai/mastra/pull/11431)) If you're using Zod 4, `buildStorageSchema` was failing to detect nullable and optional fields correctly. This caused `NOT NULL constraint failed` errors when storing observability spans and other data. This fix enables proper schema detection for Zod 4 users, ensuring nullable fields like `parentSpanId` are correctly identified and don't cause database constraint violations. - Add native Perplexity provider support ([#10885](https://github.com/mastra-ai/mastra/pull/10885)) - Added `startExclusive` and `endExclusive` options to `dateRange` filter for message queries. ([#11479](https://github.com/mastra-ai/mastra/pull/11479)) **What changed:** The `filter.dateRange` parameter in `listMessages()` and `Memory.recall()` now supports `startExclusive` and `endExclusive` boolean options. When set to `true`, messages with timestamps exactly matching the boundary are excluded from results. **Why this matters:** Enables cursor-based pagination for chat applications. When new messages arrive during a session, offset-based pagination can skip or duplicate messages. Using `endExclusive: true` with the oldest message's timestamp as a cursor ensures consistent pagination without gaps or duplicates. **Example:** ```typescript // Get first page const page1 = await memory.recall({ threadId: 'thread-123', perPage: 10, orderBy: { field: 'createdAt', direction: 'DESC' }, }); // Get next page using cursor-based pagination const oldestMessage = page1.messages[page1.messages.length - 1]; const page2 = await memory.recall({ threadId: 'thread-123', perPage: 10, orderBy: { field: 'createdAt', direction: 'DESC' }, filter: { dateRange: { end: oldestMessage.createdAt, endExclusive: true, // Excludes the cursor message }, }, }); ``` - Improved TypeScript type inference for workflow steps. ([#11953](https://github.com/mastra-ai/mastra/pull/11953)) **What changed:** - Step input/output type mismatches are now caught at compile time when chaining steps with `.then()` - The `execute` function now properly infers types from `inputSchema`, `outputSchema`, `stateSchema`, and other schema parameters - Clearer error messages when step types don't match workflow requirements **Why:** Previously, type errors in workflow step chains would only surface at runtime. Now TypeScript validates that each step's input requirements are satisfied by the previous step's output, helping you catch integration issues earlier in development. - Fix Zod 4 compatibility issue with structuredOutput in agent.generate() ([#11133](https://github.com/mastra-ai/mastra/pull/11133)) Users with Zod 4 installed would see `TypeError: undefined is not an object (evaluating 'def.valueType._zod')` when using `structuredOutput` with agent.generate(). This happened because ProcessorStepSchema contains `z.custom()` fields that hold user-provided Zod schemas, and the workflow validation was trying to deeply validate these schemas causing version conflicts. The fix disables input validation for processor workflows since `z.custom()` fields are meant to pass through arbitrary types without deep validation. - Truncate map config when too long ([#11175](https://github.com/mastra-ai/mastra/pull/11175)) - fix resumeStream type to use resumeSchema ([#10202](https://github.com/mastra-ai/mastra/pull/10202)) - Pass resourceId and threadId to network agent's subAgent when it has its own memory ([#10592](https://github.com/mastra-ai/mastra/pull/10592)) - use `agent.getMemory` to fetch the memory instance on the Agent class to make sure that storage gets set if memory doesn't set it itself. ([#10556](https://github.com/mastra-ai/mastra/pull/10556)) - Built-in processors that use internal agents (PromptInjectionDetector, ModerationProcessor, PIIDetector, LanguageDetector, StructuredOutputProcessor) now accept `providerOptions` to control model behavior. ([#10651](https://github.com/mastra-ai/mastra/pull/10651)) This lets you pass provider-specific settings like `reasoningEffort` for OpenAI thinking models: ```typescript const processor = new PromptInjectionDetector({ model: 'openai/o1-mini', threshold: 0.7, strategy: 'block', providerOptions: { openai: { reasoningEffort: 'low', }, }, }); ``` - Improved typing for `workflow.then` to allow the provided steps `inputSchema` to be a subset of the previous steps `outputSchema`. Also errors if the provided steps `inputSchema` is a superset of the previous steps outputSchema. ([#10763](https://github.com/mastra-ai/mastra/pull/10763)) - Composite auth implementation ([#10359](https://github.com/mastra-ai/mastra/pull/10359)) - Fix message list provider metadata handling and reasoning text optimization ([#10281](https://github.com/mastra-ai/mastra/pull/10281)) - Improved provider metadata preservation across message transformations - Optimized reasoning text storage to avoid duplication (using `details` instead of `reasoning` field) - Fixed test snapshots for timestamp precision and metadata handling - Fix requireApproval property being ignored for tools passed via toolsets, clientTools, and memoryTools parameters. The requireApproval flag now correctly propagates through all tool conversion paths, ensuring tools requiring approval will properly request user approval before execution. ([#10464](https://github.com/mastra-ai/mastra/pull/10464)) - fix(core): support LanguageModelV3 in MastraModelGateway.resolveLanguageModel ([#11489](https://github.com/mastra-ai/mastra/pull/11489)) - Fixed duplicate storage initialization when init() is called explicitly before other methods. The augmentWithInit proxy now tracks when init() is called directly, preventing subsequent method calls from triggering init() again. This resolves the high volume of requests to storage backends (like Turso) during agent streaming with memory enabled. ([#12067](https://github.com/mastra-ai/mastra/pull/12067)) - Add timeTravel APIs and add timeTravel feature to studio ([#10361](https://github.com/mastra-ai/mastra/pull/10361)) - Add helpful JSDoc comments to `BundlerConfig` properties (used with `bundler` option) ([#10218](https://github.com/mastra-ai/mastra/pull/10218)) - Improve type handling with Zod ([#12091](https://github.com/mastra-ai/mastra/pull/12091)) - Added the ability to provide a base path for Mastra Studio. ([#10441](https://github.com/mastra-ai/mastra/pull/10441)) ```ts import { Mastra } from '@mastra/core'; export const mastra = new Mastra({ server: { studioBase: '/my-mastra-studio', }, }); ``` This will make Mastra Studio available at `http://localhost:4111/my-mastra-studio`. - Fix Azure Foundry rate limit handling for -1 values ([#10409](https://github.com/mastra-ai/mastra/pull/10409)) - Improve error messaging for LLM API errors. When an error originates from an LLM provider (e.g., rate limits, overloaded, auth failures), the console now indicates it's an upstream API error and includes the provider and model information. ([#12022](https://github.com/mastra-ai/mastra/pull/12022)) Before: ``` ERROR (Mastra): Error in agent stream error: { "message": "Overloaded", "type": "overloaded_error" } ``` After: ``` ERROR (Mastra): Upstream LLM API error from anthropic (model: claude-3-opus) error: { "message": "Overloaded", "type": "overloaded_error" } ``` - feat: Add field filtering and nested workflow control to workflow execution result endpoint ([#11246](https://github.com/mastra-ai/mastra/pull/11246)) Adds two optional query parameters to `/api/workflows/:workflowId/runs/:runId/execution-result` endpoint: - `fields`: Request only specific fields (e.g., `status`, `result`, `error`) - `withNestedWorkflows`: Control whether to fetch nested workflow data This significantly reduces response payload size and improves response times for large workflows. ## Server Endpoint Usage ```http # Get only status (minimal payload - fastest) GET /api/workflows/:workflowId/runs/:runId/execution-result?fields=status # Get status and result GET /api/workflows/:workflowId/runs/:runId/execution-result?fields=status,result # Get all fields but without nested workflow data (faster) GET /api/workflows/:workflowId/runs/:runId/execution-result?withNestedWorkflows=false # Get only specific fields without nested workflow data GET /api/workflows/:workflowId/runs/:runId/execution-result?fields=status,steps&withNestedWorkflows=false # Get full data (default behavior) GET /api/workflows/:workflowId/runs/:runId/execution-result ``` ## Client SDK Usage ```typescript import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); const workflow = client.getWorkflow('myWorkflow'); // Get only status (minimal payload - fastest) const statusOnly = await workflow.runExecutionResult(runId, { fields: ['status'], }); console.log(statusOnly.status); // 'success' | 'failed' | 'running' | etc. // Get status and result const statusAndResult = await workflow.runExecutionResult(runId, { fields: ['status', 'result'], }); // Get all fields but without nested workflow data (faster) const resultWithoutNested = await workflow.runExecutionResult(runId, { withNestedWorkflows: false, }); // Get specific fields without nested workflow data const optimized = await workflow.runExecutionResult(runId, { fields: ['status', 'steps'], withNestedWorkflows: false, }); // Get full execution result (default behavior) const fullResult = await workflow.runExecutionResult(runId); ``` ## Core API Changes The `Workflow.getWorkflowRunExecutionResult` method now accepts an options object: ```typescript await workflow.getWorkflowRunExecutionResult(runId, { withNestedWorkflows: false, // default: true, set to false to skip nested workflow data fields: ['status', 'result'], // optional field filtering }); ``` ## Inngest Compatibility The `@mastra/inngest` package has been updated to use the new options object API. This is a non-breaking internal change - no action required from inngest workflow users. ## Performance Impact For workflows with large step outputs: - Requesting only `status`: ~99% reduction in payload size - Requesting `status,result,error`: ~95% reduction in payload size - Using `withNestedWorkflows=false`: Avoids expensive nested workflow data fetching - Combining both: Maximum performance optimization - Fix model headers not being passed through gateway system ([#10465](https://github.com/mastra-ai/mastra/pull/10465)) Previously, custom headers specified in `MastraModelConfig` were not being passed through the gateway system to model providers. This affected: - OpenRouter (preventing activity tracking with `HTTP-Referer` and `X-Title`) - Custom providers using custom URLs (headers not passed to `createOpenAICompatible`) - Custom gateway implementations (headers not available in `resolveLanguageModel`) Now headers are correctly passed through the entire gateway system: - Base `MastraModelGateway` interface updated to accept headers - `ModelRouterLanguageModel` passes headers from config to all gateways - OpenRouter receives headers for activity tracking - Custom URL providers receive headers via `createOpenAICompatible` - Custom gateways can access headers in their `resolveLanguageModel` implementation Example usage: ```typescript // Works with OpenRouter const agent = new Agent({ name: 'my-agent', instructions: 'You are a helpful assistant.', model: { id: 'openrouter/anthropic/claude-3-5-sonnet', headers: { 'HTTP-Referer': 'https://myapp.com', 'X-Title': 'My Application', }, }, }); // Also works with custom providers const customAgent = new Agent({ name: 'custom-agent', instructions: 'You are a helpful assistant.', model: { id: 'custom-provider/model', url: 'https://api.custom.com/v1', apiKey: 'key', headers: { 'X-Custom-Header': 'custom-value', }, }, }); ``` Fixes https://github.com/mastra-ai/mastra/issues/9760 - Add type bailed to workflowRunStatus ([#10091](https://github.com/mastra-ai/mastra/pull/10091)) - Add tool call approval ([#8649](https://github.com/mastra-ai/mastra/pull/8649)) - Fixed a bug where multiple tools streaming output simultaneously could fail with "WritableStreamDefaultWriter is locked" errors. Tool streaming now works reliably during concurrent tool executions. ([#10830](https://github.com/mastra-ai/mastra/pull/10830)) - default validate inputs to true in Workflow execute ([#10222](https://github.com/mastra-ai/mastra/pull/10222)) - Fixes issues where thread and messages were not saved before suspension when tools require approval or call suspend() during execution. This caused conversation history to be lost if users refreshed during tool approval or suspension. ([#10369](https://github.com/mastra-ai/mastra/pull/10369)) **Backend changes (@mastra/core):** - Add assistant messages to messageList immediately after LLM execution - Flush messages synchronously before suspension to persist state - Create thread if it doesn't exist before flushing - Add metadata helpers to persist and remove tool approval state - Pass saveQueueManager and memory context through workflow for immediate persistence **Frontend changes (@mastra/react):** - Extract runId from pending approvals to enable resumption after refresh - Convert `pendingToolApprovals` (DB format) to `requireApprovalMetadata` (runtime format) - Handle both `dynamic-tool` and `tool-{NAME}` part types for approval state - Change runId from hardcoded `agentId` to unique `uuid()` **UI changes (@mastra/playground-ui):** - Handle tool calls awaiting approval in message initialization - Convert approval metadata format when loading initial messages Fixes #9745, #9906 - Fix error handling and serialization in agent streaming to ensure errors are consistently exposed and preserved. ([#9144](https://github.com/mastra-ai/mastra/pull/9144)) - Update MockMemory to work with new storage API changes. MockMemory now properly implements all abstract MastraMemory methods. This includes proper thread management, message saving with MessageList conversion, working memory operations with scope support, and resource listing. ([#10368](https://github.com/mastra-ai/mastra/pull/10368)) Add Zod v4 support for working memory schemas. Memory implementations now check for Zod v4's built-in `.toJsonSchema()` method before falling back to the `zodToJsonSchema` compatibility function, improving performance and forward compatibility while maintaining backward compatibility with Zod v3. Add Gemini 3 Pro test coverage in agent-gemini.test.ts to validate the latest Gemini model integration. - Fixes issue where clicking the reset button in the model picker would fail to restore the original LanguageModelV2 (or any other types) object that was passed during agent construction. ([#9481](https://github.com/mastra-ai/mastra/pull/9481)) - Fix a bug where streaming didn't output the final chunk ([#9546](https://github.com/mastra-ai/mastra/pull/9546)) - Expand `processInputStep` processor method and integrate `prepareStep` as a processor ([#10774](https://github.com/mastra-ai/mastra/pull/10774)) **New Features:** - `prepareStep` callback now runs through the standard `processInputStep` pipeline - Processors can now modify per-step: `model`, `tools`, `toolChoice`, `activeTools`, `messages`, `systemMessages`, `providerOptions`, `modelSettings`, and `structuredOutput` - Processor chaining: each processor receives accumulated state from previous processors - System messages are isolated per-step (reset at start of each step) **Breaking Change:** - `prepareStep` messages format changed from AI SDK v5 model messages to `MastraDBMessage` format - Migration: Use `messageList.get.all.aiV5.model()` if you need the old format - Fix type issue with workflow `.parallel()` when passing multiple steps, one or more of which has a `resumeSchema` provided. ([#10708](https://github.com/mastra-ai/mastra/pull/10708)) - Add support for `instructions` field in MCPServer ([#11421](https://github.com/mastra-ai/mastra/pull/11421)) Implements the official MCP specification's `instructions` field, which allows MCP servers to provide system-wide prompts that are automatically sent to clients during initialization. This eliminates the need for per-project configuration files (like AGENTS.md) by centralizing the system prompt in the server definition. **What's New:** - Added `instructions` optional field to `MCPServerConfig` type - Instructions are passed to the underlying MCP SDK Server during initialization - Instructions are sent to clients in the `InitializeResult` response - Fully compatible with all MCP clients (Cursor, Windsurf, Claude Desktop, etc.) **Example Usage:** ```typescript const server = new MCPServer({ name: 'GitHub MCP Server', version: '1.0.0', instructions: 'Use the available tools to help users manage GitHub repositories, issues, and pull requests. Always search before creating to avoid duplicates.', tools: { searchIssues, createIssue, listPRs }, }); ``` - Fix race condition in parallel tool stream writes ([#10463](https://github.com/mastra-ai/mastra/pull/10463)) Introduces a write queue to ToolStream to serialize access to the underlying stream, preventing writer locked errors - Remove unneeded console warning when flushing messages and no threadId or saveQueueManager is found. ([#10498](https://github.com/mastra-ai/mastra/pull/10498)) - Don't call `os.homedir()` at top level (but lazy invoke it) to accommodate sandboxed environments ([#9211](https://github.com/mastra-ai/mastra/pull/9211)) - Don't download unsupported media ([#9209](https://github.com/mastra-ai/mastra/pull/9209)) - Add support for doGenerate in LanguageModelV2. This change fixes issues with OpenAI stream permissions. ([#10239](https://github.com/mastra-ai/mastra/pull/10239)) - Added new abstraction over LanguageModelV2 - Add storage composition to MastraStorage ([#11401](https://github.com/mastra-ai/mastra/pull/11401)) `MastraStorage` can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability. ```typescript import { MastraStorage } from '@mastra/core/storage'; import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg'; import { MemoryLibSQL } from '@mastra/libsql'; // Compose domains from different stores const storage = new MastraStorage({ id: 'composite', domains: { memory: new MemoryLibSQL({ url: 'file:./local.db' }), workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }), scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }), }, }); ``` **Breaking changes:** - `storage.supports` property no longer exists - `StorageSupports` type is no longer exported from `@mastra/core/storage` All stores now support the same features. For domain availability, use `getStore()`: ```typescript const store = await storage.getStore('memory'); if (store) { // domain is available } ``` - Fixes .network() method ignores MASTRA_RESOURCE_ID_KEY from requestContext ([`4524734`](https://github.com/mastra-ai/mastra/commit/45247343e384717a7c8404296275c56201d6470f)) - Fixed the type of targetResult in the onItemComplete callback for runEvals. The parameter was incorrectly typed as a Promise, but the actual value passed is already resolved. Users no longer need to await targetResult inside their callback. ([#12030](https://github.com/mastra-ai/mastra/pull/12030)) - Detect thenable objects returned by AI model providers ([#8905](https://github.com/mastra-ai/mastra/pull/8905)) - - PostgreSQL: use `getSqlType()` in `createTable` instead of `toUpperCase()` ([#11112](https://github.com/mastra-ai/mastra/pull/11112)) - LibSQL: use `getSqlType()` in `createTable`, return `JSONB` for jsonb type (matches SQLite 3.45+ support) - ClickHouse: use `getSqlType()` in `createTable` instead of `COLUMN_TYPES` constant, add missing types (uuid, float, boolean) - Remove unused `getSqlType()` and `getDefaultValue()` from `MastraStorage` base class (all stores use `StoreOperations` versions) - Fixed agent network not returning text response when routing agent handles requests without delegation. ([#11497](https://github.com/mastra-ai/mastra/pull/11497)) **What changed:** - Agent networks now correctly stream text responses when the routing agent decides to handle a request itself instead of delegating to sub-agents, workflows, or tools - Added fallback in transformers to ensure text is always returned even if core events are missing **Why this matters:** Previously, when using `toAISdkV5Stream` or `networkRoute()` outside of the Mastra Studio UI, no text content was returned when the routing agent handled requests directly. This fix ensures consistent behavior across all API routes. Fixes #11219 - chore(core): MessageHistory input processor pass resourceId for storage ([#11910](https://github.com/mastra-ai/mastra/pull/11910)) - Add debugger-like click-through UI to workflow graph ([#11350](https://github.com/mastra-ai/mastra/pull/11350)) - Adds bidirectional integration with otel tracing via a new @mastra/otel-bridge package. ([#10482](https://github.com/mastra-ai/mastra/pull/10482)) - Add delete workflow run API ([#10991](https://github.com/mastra-ai/mastra/pull/10991)) ```typescript await workflow.deleteWorkflowRunById(runId); ``` - Add optional includeRawChunks parameter to agent execution options, ([#10456](https://github.com/mastra-ai/mastra/pull/10456)) allowing users to include raw chunks in stream output where supported by the model provider. - Add initial state input to workflow form in studio ([#11560](https://github.com/mastra-ai/mastra/pull/11560)) - Fix input tool validation when no inputSchema is provided ([#9941](https://github.com/mastra-ai/mastra/pull/9941)) - When `mastra dev` runs, multiple processes can write to `provider-registry.json` concurrently (auto-refresh, syncGateways, syncGlobalCacheToLocal). This causes file corruption where the end of the JSON appears twice, making it unparseable. ([#10455](https://github.com/mastra-ai/mastra/pull/10455)) The fix uses atomic writes via the write-to-temp-then-rename pattern. Instead of: ```ts fs.writeFileSync(filePath, content, 'utf-8'); ``` We now do: ```ts const tempPath = `${filePath}.${process.pid}.${Date.now()}.${randomSuffix}.tmp`; fs.writeFileSync(tempPath, content, 'utf-8'); fs.renameSync(tempPath, filePath); // atomic on POSIX ``` `fs.rename()` is atomic on POSIX systems when both paths are on the same filesystem, so concurrent writes will each complete fully rather than interleaving. - Adds `processInputStep` method to the Processor interface. Unlike `processInput` which runs once at the start, this runs at each step of the agentic loop (including tool call continuations). ([#10650](https://github.com/mastra-ai/mastra/pull/10650)) ```ts const processor: Processor = { id: 'my-processor', processInputStep: async ({ messages, messageList, stepNumber, systemMessages }) => { // Transform messages at each step before LLM call return messageList; }, }; ``` - Added missing stream types to @mastra/core/stream for better TypeScript support ([#11513](https://github.com/mastra-ai/mastra/pull/11513)) **New types available:** - Chunk types: `ToolCallChunk`, `ToolResultChunk`, `SourceChunk`, `FileChunk`, `ReasoningChunk` - Payload types: `ToolCallPayload`, `ToolResultPayload`, `TextDeltaPayload`, `ReasoningDeltaPayload`, `FilePayload`, `SourcePayload` - JSON utilities: `JSONValue`, `JSONObject`, `JSONArray` and readonly variants These types are now properly exported, enabling full TypeScript IntelliSense when working with streaming data. - - `Run.cancel()` now updates workflow status to 'canceled' in storage, resolving the issue where suspended workflows remained in 'suspended' status after cancellation ([#11139](https://github.com/mastra-ai/mastra/pull/11139)) - Cancellation status is immediately persisted and reflected to observers - When using output processors with `agent.generate()`, `result.text` was returning the unprocessed LLM response instead of the processed text. ([#10735](https://github.com/mastra-ai/mastra/pull/10735)) **Before:** ```ts const result = await agent.generate('hello'); result.text; // "hello world" (unprocessed) result.response.messages[0].content[0].text; // "HELLO WORLD" (processed) ``` **After:** ```ts const result = await agent.generate('hello'); result.text; // "HELLO WORLD" (processed) ``` The bug was caused by the `text` delayed promise being resolved twice - first correctly with the processed text, then overwritten with the unprocessed buffered text. - Add `response` to finish chunk payload for output processor metadata access ([#11549](https://github.com/mastra-ai/mastra/pull/11549)) When using output processors with streaming, metadata added via `processOutputResult` is now accessible in the finish chunk's `payload.response.uiMessages`. This allows clients consuming streams over HTTP (e.g., via `/stream/ui`) to access processor-added metadata. ```typescript for await (const chunk of stream.fullStream) { if (chunk.type === 'finish') { const uiMessages = chunk.payload.response?.uiMessages; const metadata = uiMessages?.find(m => m.role === 'assistant')?.metadata; } } ``` Fixes #11454 - Make initialState optional in studio ([#11744](https://github.com/mastra-ai/mastra/pull/11744)) - Refactored default engine to fit durable execution better, and the inngest engine to match. ([#10627](https://github.com/mastra-ai/mastra/pull/10627)) Also fixes requestContext persistence by relying on inngest step memoization. Unifies some of the stepResults and error formats in both engines. - Removed a debug log that printed large Zod schemas, resulting in cleaner console output when using agents with memory enabled. ([#11279](https://github.com/mastra-ai/mastra/pull/11279)) - Fix type recursion by importing from 'zod' instead of 'zod/v3' ([#12009](https://github.com/mastra-ai/mastra/pull/12009)) - Fixed formatting of model_step, model_chunk, and tool_call spans in Arize Exporter. ([#11922](https://github.com/mastra-ai/mastra/pull/11922)) Also removed `tools` output from `model_step` spans for all exporters. - Set `externals: true` as the default for `mastra build` and cloud-deployer to reduce bundle issues with native dependencies. ([`0dbf199`](https://github.com/mastra-ai/mastra/commit/0dbf199110f22192ce5c95b1c8148d4872b4d119)) **Note:** If you previously relied on the default bundling behavior (all dependencies bundled), you can explicitly set `externals: false` in your bundler configuration. - Fixes incorrect tool invocation format in message list that was causing client tools to fail during message format conversions. ([#9590](https://github.com/mastra-ai/mastra/pull/9590)) - Allow direct access to server app handle directly from Mastra instance. ([#10598](https://github.com/mastra-ai/mastra/pull/10598)) ```ts // Before: HTTP request to localhost const response = await fetch(`http://localhost:5000/api/tools`); // After: Direct call via app.fetch() const app = mastra.getServerApp(); const response = await app.fetch(new Request('http://internal/api/tools')); ``` - Added `mastra.getServerApp()` to access the underlying Hono/Express app - Added `mastra.getMastraServer()` and `mastra.setMastraServer()` for adapter access - Added `MastraServerBase` class in `@mastra/core/server` for adapter implementations - Server adapters now auto-register with Mastra in their constructor - Allow provider to pass through options to the auth config ([#10284](https://github.com/mastra-ai/mastra/pull/10284)) - When sending the first message to a new thread with PostgresStore, users would get a "Thread not found" error. This happened because the thread was created in memory but not persisted to the database before the MessageHistory output processor tried to save messages. ([#10881](https://github.com/mastra-ai/mastra/pull/10881)) **Before:** ```ts threadObject = await memory.createThread({ // ... saveThread: false, // thread not in DB yet }); // Later: MessageHistory calls saveMessages() -> PostgresStore throws "Thread not found" ``` **After:** ```ts threadObject = await memory.createThread({ // ... saveThread: true, // thread persisted immediately }); // MessageHistory can now save messages without error ``` - Fix network agent not getting `text-delta` from subAgent when `.stream` is used ([#10533](https://github.com/mastra-ai/mastra/pull/10533)) - Fix .map when placed at the beginning of a workflow or nested workflow ([#10457](https://github.com/mastra-ai/mastra/pull/10457)) - Fix various places in core package where we were logging with console.error instead of the mastra logger. ([#11425](https://github.com/mastra-ai/mastra/pull/11425)) - Use input processors that are passed in generate or stream agent options rather than always defaulting to the processors set on the Agent class. ([#9407](https://github.com/mastra-ai/mastra/pull/9407)) - Add `perStep` option to workflow run methods, allowing a workflow to run just a step instead of all the workflow steps ([#11276](https://github.com/mastra-ai/mastra/pull/11276)) - Previously, tool input validation used the original Zod schema while the LLM received a schema-compat transformed version. This caused validation failures when LLMs (like OpenAI o3 or Claude 3.5 Haiku) sent arguments matching the transformed schema but not the original. ([#9258](https://github.com/mastra-ai/mastra/pull/9258)) For example: - OpenAI o3 reasoning models convert `.optional()` to `.nullable()`, sending `null` values - Claude 3.5 Haiku strips `min`/`max` string constraints, sending shorter strings - Validation would reject these valid responses because it checked against the original schema The fix ensures validation uses the same schema-compat processed schema that was sent to the LLM, eliminating this mismatch. - Improved tracing by filtering infrastructure chunks from model streams and adding success attribute to tool spans. ([#11943](https://github.com/mastra-ai/mastra/pull/11943)) Added generic input/output attribute mapping for additional span types in Arize exporter. - Add import for WriteableStream in execution-engine and dedupe llm.getModel in agent.ts ([#9185](https://github.com/mastra-ai/mastra/pull/9185)) - Fix deprecation warning when agent network executes workflows by using `.fullStream` instead of iterating `WorkflowRunOutput` directly ([#10285](https://github.com/mastra-ai/mastra/pull/10285)) - Internal refactor to `MessageList` for improved maintainability and modularity. This change does not affect external APIs or functionality. ([#11658](https://github.com/mastra-ai/mastra/pull/11658)) - Ensures that data chunks written via `writer.custom()` always bubble up directly to the top-level stream, even when nested in sub-agents. This allows tools to emit custom progress updates, metrics, and other data that can be consumed at any level of the agent hierarchy. ([#10309](https://github.com/mastra-ai/mastra/pull/10309)) - **Added bubbling logic in sub-agent execution**: When sub-agents execute, data chunks (chunks with type starting with `data-`) are detected and written via `writer.custom()` instead of `writer.write()`, ensuring they bubble up directly without being wrapped in `tool-output` chunks. - **Added comprehensive tests**: - Test for `writer.custom()` with direct tool execution - Test for `writer.custom()` with sub-agent tools (nested execution) - Test for mixed usage of `writer.write()` and `writer.custom()` in the same tool When a sub-agent's tool uses `writer.custom()` to write data chunks, those chunks appear in the sub-agent's stream. The parent agent's execution logic now detects these chunks and uses `writer.custom()` to bubble them up directly, preserving their structure and making them accessible at the top level. This ensures that: - Data chunks from tools always appear directly in the stream (not wrapped) - Data chunks bubble up correctly through nested agent hierarchies - Regular chunks continue to be wrapped in `tool-output` as expected - Emit error chunk and call onError when agent workflow step fails ([#10907](https://github.com/mastra-ai/mastra/pull/10907)) When a workflow step fails (e.g., tool not found), the error is now properly emitted as an error chunk to the stream and the onError callback is called. This fixes the issue where agent.generate() would throw "promise 'text' was not resolved or rejected" instead of the actual error message. - Fix discriminatedUnion schema information lost when json schema is converted to zod ([#10500](https://github.com/mastra-ai/mastra/pull/10500)) - `setState` is now async ([#10944](https://github.com/mastra-ai/mastra/pull/10944)) - `setState` must now be awaited: `await setState({ key: value })` - State updates are merged automatically—no need to spread the previous state - State data is validated against the step's `stateSchema` when `validateInputs` is enabled (default: `true`) - Use agent description when converting agent to tool ([#10879](https://github.com/mastra-ai/mastra/pull/10879)) - Fix generate toolResults and mismatch in provider tool names ([#10282](https://github.com/mastra-ai/mastra/pull/10282)) - Adds ability to create custom `MastraModelGateway`'s that can be added to the `Mastra` class instance under the `gateways` property. Giving you typescript autocompletion in any model picker string. ([#10180](https://github.com/mastra-ai/mastra/pull/10180)) ```typescript import { MastraModelGateway, type ProviderConfig } from '@mastra/core/llm'; import { createOpenAICompatible } from '@ai-sdk/openai-compatible-v5'; import type { LanguageModelV2 } from '@ai-sdk/provider-v5'; class MyCustomGateway extends MastraModelGateway { readonly id = 'my-custom-gateway'; readonly name = 'My Custom Gateway'; readonly prefix = 'custom'; async fetchProviders(): Promise> { return { 'my-provider': { name: 'My Provider', models: ['model-1', 'model-2'], apiKeyEnvVar: 'MY_API_KEY', gateway: this.id, }, }; } buildUrl(modelId: string, envVars?: Record): string { return 'https://api.my-provider.com/v1'; } async getApiKey(modelId: string): Promise { const apiKey = process.env.MY_API_KEY; if (!apiKey) throw new Error('MY_API_KEY not set'); return apiKey; } async resolveLanguageModel({ modelId, providerId, apiKey, }: { modelId: string; providerId: string; apiKey: string; }): Promise { const baseURL = this.buildUrl(`${providerId}/${modelId}`); return createOpenAICompatible({ name: providerId, apiKey, baseURL, }).chatModel(modelId); } } new Mastra({ gateways: { myGateway: new MyCustomGateway(), }, }); ``` - Support AI SDK voice models ([#10304](https://github.com/mastra-ai/mastra/pull/10304)) Mastra now supports AI SDK's transcription and speech models directly in `CompositeVoice`, enabling seamless integration with a wide range of voice providers through the AI SDK ecosystem. This allows you to use models from OpenAI, ElevenLabs, Groq, Deepgram, LMNT, Hume, and many more for both speech-to-text (transcription) and text-to-speech capabilities. AI SDK models are automatically wrapped when passed to `CompositeVoice`, so you can mix and match AI SDK models with existing Mastra voice providers for maximum flexibility. **Usage Example** ```typescript import { CompositeVoice } from '@mastra/core/voice'; import { openai } from '@ai-sdk/openai'; import { elevenlabs } from '@ai-sdk/elevenlabs'; // Use AI SDK models directly with CompositeVoice const voice = new CompositeVoice({ input: openai.transcription('whisper-1'), // AI SDK transcription model output: elevenlabs.speech('eleven_turbo_v2'), // AI SDK speech model }); // Convert text to speech const audioStream = await voice.speak('Hello from AI SDK!'); // Convert speech to text const transcript = await voice.listen(audioStream); console.log(transcript); ``` Fixes #9947 - Refactor: consolidate duplicate applyMessages helpers in workflow.ts ([#11688](https://github.com/mastra-ai/mastra/pull/11688)) - Added optional `defaultSource` parameter to `ProcessorRunner.applyMessagesToMessageList` to support both 'input' and 'response' default sources - Removed 3 duplicate inline `applyMessages` helper functions from workflow.ts (in input, outputResult, and outputStep phases) - All phases now use the shared `ProcessorRunner.applyMessagesToMessageList` static method This is an internal refactoring with no changes to external behavior. - Resolve suspendPayload when tripwire is set off in agentic loop to prevent unresolved promises hanging. ([#11621](https://github.com/mastra-ai/mastra/pull/11621)) - Use a shared `getAllToolPaths()` method from the bundler to discover tool paths. ([#9204](https://github.com/mastra-ai/mastra/pull/9204)) - Add an additional check to determine whether the model natively supports specific file types. Only download the file if the model does not support it natively. ([#9790](https://github.com/mastra-ai/mastra/pull/9790)) - Fix OpenAI reasoning model + memory failing on second generate with "missing item" error ([#11492](https://github.com/mastra-ai/mastra/pull/11492)) When using OpenAI reasoning models with memory enabled, the second `agent.generate()` call would fail with: "Item 'rs\_...' of type 'reasoning' was provided without its required following item." The issue was that `text-start` events contain `providerMetadata` with the text's `itemId` (e.g., `msg_xxx`), but this metadata was not being captured. When memory replayed the conversation, the reasoning part had its `rs_` ID but the text part was missing its `msg_` ID, causing OpenAI to reject the request. The fix adds handlers for `text-start` (to capture text providerMetadata) and `text-end` (to clear it and prevent leaking into subsequent parts). Fixes #11481 - Update agent workflow and sub-agent tool transformations to accept more input arguments. ([#10278](https://github.com/mastra-ai/mastra/pull/10278)) These tools now accept the following ```ts workflowTool.execute({ inputData, initialState }, context); agentTool.execute({ prompt, threadId, resourceId, instructions, maxSteps }, context); ``` Workflow tools now also properly return errors when the workflow run fails ```ts const workflowResult = await workflowTool.execute({ inputData, initialState }, context); console.log(workflowResult.error); // error msg if error console.log(workflowResult.result); // result of the workflow if success ``` Workflows passed to agents do not properly handle suspend/resume`, they only handle success or error. - Fixed OpenAI schema compatibility when using `agent.generate()` or `agent.stream()` with `structuredOutput`. ([#10366](https://github.com/mastra-ai/mastra/pull/10366)) **Changes** - **Automatic transformation**: Zod schemas are now automatically transformed for OpenAI strict mode compatibility when using OpenAI models (including reasoning models like o1, o3, o4) - **Optional field handling**: `.optional()` fields are converted to `.nullable()` with a transform that converts `null` → `undefined`, preserving optional semantics while satisfying OpenAI's strict mode requirements - **Preserves nullable fields**: Intentionally `.nullable()` fields remain unchanged - **Deep transformation**: Handles `.optional()` fields at any nesting level (objects, arrays, unions, etc.) - **JSON Schema objects**: Not transformed, only Zod schemas **Example** ```typescript const agent = new Agent({ name: 'data-extractor', model: { provider: 'openai', modelId: 'gpt-4o' }, instructions: 'Extract user information', }); const schema = z.object({ name: z.string(), age: z.number().optional(), deletedAt: z.date().nullable(), }); // Schema is automatically transformed for OpenAI compatibility const result = await agent.generate('Extract: John, deleted yesterday', { structuredOutput: { schema }, }); // Result: { name: 'John', age: undefined, deletedAt: null } ``` - When a workflow step is resumed, the writer parameter was not being properly passed through, causing writer.custom() calls to fail. This fix ensures the writableStream parameter is correctly passed to both run.resume() and run.start() calls in the workflow execution engine, allowing custom events to be emitted properly during resume operations. ([#10720](https://github.com/mastra-ai/mastra/pull/10720)) - Added support for .streamVNext and .stream that uses it in the inngest execution engine ([#9434](https://github.com/mastra-ai/mastra/pull/9434)) - Fix reasoning content being lost when text-start chunk arrives before reasoning-end ([#11494](https://github.com/mastra-ai/mastra/pull/11494)) Some model providers (e.g., ZAI/glm-4.6) return streaming chunks where `text-start` arrives before `reasoning-end`. Previously, this would clear the accumulated reasoning deltas, resulting in empty reasoning content in the final message. Now `text-start` is properly excluded from triggering the reasoning state reset, allowing `reasoning-end` to correctly save the reasoning content. - pass writableStream parameter to workflow execution ([#9139](https://github.com/mastra-ai/mastra/pull/9139)) - Adds native @ai-sdk/deepseek provider support instead of using the OpenAI-compatible fallback. ([#10822](https://github.com/mastra-ai/mastra/pull/10822)) ```typescript const agent = new Agent({ model: 'deepseek/deepseek-reasoner', }); // With provider options for reasoning const response = await agent.generate('Solve this problem', { providerOptions: { deepseek: { thinking: { type: 'enabled' }, }, }, }); ``` Also updates the doc generation scripts so DeepSeek provider options show up in the generated docs. - Fix workflow throwing error when using .map after .foreach ([#11352](https://github.com/mastra-ai/mastra/pull/11352)) - Previously, network execution steps were not being tracked correctly in the AI SDK stream transformation. Steps were being duplicated rather than updated, and critical metadata like step IDs, iterations, and task information was missing or incorrectly structured. ([#10432](https://github.com/mastra-ai/mastra/pull/10432)) **Changes:** - Enhanced step tracking in `AgentNetworkToAISDKTransformer` to properly maintain step state throughout execution lifecycle - Steps are now identified by unique IDs and updated in place rather than creating duplicates - Added proper iteration and task metadata to each step in the network execution flow - Fixed agent, workflow, and tool execution events to correctly populate step data - Updated network stream event types to include `networkId`, `workflowId`, and consistent `runId` tracking - Added test coverage for network custom data chunks with comprehensive validation This ensures the AI SDK correctly represents the full execution flow of agent networks with accurate step sequencing and metadata. - Add `resumeGenerate` method for resuming agent via generate ([#11503](https://github.com/mastra-ai/mastra/pull/11503)) Add `runId` and `suspendPayload` to fullOutput of agent stream Default `suspendedToolRunId` to empty string to prevent `null` issue - Remove tools passed to the Routing Agent in .network() ([#9374](https://github.com/mastra-ai/mastra/pull/9374)) - Fix corrupted provider-registry.json file in global cache and regenerate corrupted files ([#10606](https://github.com/mastra-ai/mastra/pull/10606)) - The iteration counter in agent networks was stuck at 0 due to a faulty ternary operator that treated 0 as falsy. This prevented `maxSteps` from working correctly, causing infinite loops when the routing agent kept selecting primitives instead of returning "none". ([#9762](https://github.com/mastra-ai/mastra/pull/9762)) **Changes:** - Fixed iteration counter logic in `loop/network/index.ts` from `(inputData.iteration ? inputData.iteration : -1) + 1` to `(inputData.iteration ?? -1) + 1` - Changed initial iteration value from `0` to `-1` so first iteration correctly starts at 0 - Added `checkIterations()` helper to validate iteration counting in all network tests Fixes #9314 - Fixed CachedToken tracking in all Observability Exporters. Also fixed TimeToFirstToken in Langfuse, Braintrust, PostHog exporters. Fixed trace formatting in Posthog Exporter. ([#11029](https://github.com/mastra-ai/mastra/pull/11029)) - Fix generating provider-registry.json ([#10392](https://github.com/mastra-ai/mastra/pull/10392)) - Fix types from ai v4 ([#9818](https://github.com/mastra-ai/mastra/pull/9818)) - Add restart method to workflow run that allows restarting an active workflow run ([#9750](https://github.com/mastra-ai/mastra/pull/9750)) Add status filter to `listWorkflowRuns` Add automatic restart to restart active workflow runs when server starts - Save correct status in snapshot for all workflow parallel steps. ([#9379](https://github.com/mastra-ai/mastra/pull/9379)) This ensures when you poll workflow run result using `getWorkflowRunExecutionResult(runId)`, you get the right status for all parallel steps - Prevent changing workflow status to suspended when some parallel steps are still running ([#9431](https://github.com/mastra-ai/mastra/pull/9431)) - Add ability to pass agent options when wrapping an agent with createStep. This allows configuring agent execution settings when using agents as workflow steps. ([#9199](https://github.com/mastra-ai/mastra/pull/9199)) - Validate schemas by default in workflow. Previously, if you want schemas in the workflow to be validated, you'd have to add `validateInputs` option, now, this will be done by default but can be disabled. ([#10186](https://github.com/mastra-ai/mastra/pull/10186)) For workflows whose schemas and step schemas you don't want validated, do this ```diff createWorkflow({ + options: { + validateInputs: false + } }) ``` - Fix TypeScript error when using Zod schemas in `defaultOptions.structuredOutput` ([#10710](https://github.com/mastra-ai/mastra/pull/10710)) Previously, defining `structuredOutput.schema` in `defaultOptions` would cause a TypeScript error because the type only accepted `undefined`. Now any valid `OutputSchema` is correctly accepted. - Return state too if `includeState: true` is in `outputOptions` and workflow run is not successful ([#10806](https://github.com/mastra-ai/mastra/pull/10806)) - Fix MCP server registration ([#9802](https://github.com/mastra-ai/mastra/pull/9802)) - Fix network loop iteration counter and usage promise handling: ([#9408](https://github.com/mastra-ai/mastra/pull/9408)) - Fixed iteration counter in network loop that was stuck at 0 due to falsy check. Properly handled zero values to ensure maxSteps is correctly enforced. - Fixed usage promise resolution in RunOutput stream by properly resolving or rejecting the promise on stream close, preventing hanging promises when streams complete. - Fixed migration CLI failing with MIGRATION_REQUIRED error during Mastra import. Added MASTRA_DISABLE_STORAGE_INIT environment variable to skip auto-initialization of storage, allowing the migration command to import user's Mastra config without triggering the migration check. Also improved the migration prompt display to show warning messages before the confirmation dialog. ([#12100](https://github.com/mastra-ai/mastra/pull/12100)) - What changed: ([#10998](https://github.com/mastra-ai/mastra/pull/10998)) Support for sequential tool execution was added. Tool call concurrency is now set conditionally, defaulting to 1 when sequential execution is needed (to avoid race conditions that interfere with human-in-the-loop approval during the workflow) rather than the default of 10 when concurrency is acceptable. How it was changed: A `sequentialExecutionRequired` constant was set to a boolean depending on whether any of the tools involved in a returned agentic execution workflow would require approval. If any tool has a 'suspendSchema' property (used for conditionally suspending execution and waiting for human input), or if they have their `requireApproval` property set to `true`, then the concurrency property used in the toolCallStep is set to 1, causing sequential execution. The old default of 10 remains otherwise. - Fix generateTitle for pre-created threads ([#11771](https://github.com/mastra-ai/mastra/pull/11771)) - Title generation now works automatically for pre-created threads (via client SDK) - When `generateTitle: true` is configured, titles are generated on the first user message - Detection is based on message history: if no existing user messages in memory, it's the first message - No metadata flags required - works seamlessly with optimistic UI patterns Fixes #11757 - Fix inngest parallel workflow ([#10169](https://github.com/mastra-ai/mastra/pull/10169)) Fix tool as step in inngest Fix inngest nested workflow - Adds thread cloning to create independent copies of conversations that can diverge. ([#11517](https://github.com/mastra-ai/mastra/pull/11517)) ```typescript // Clone a thread const { thread, clonedMessages } = await memory.cloneThread({ sourceThreadId: 'thread-123', title: 'My Clone', options: { messageLimit: 10, // optional: only copy last N messages }, }); // Check if a thread is a clone if (memory.isClone(thread)) { const source = await memory.getSourceThread(thread.id); } // List all clones of a thread const clones = await memory.listClones('thread-123'); ``` Includes: - Storage implementations for InMemory, PostgreSQL, LibSQL, Upstash - API endpoint: `POST /api/memory/threads/:threadId/clone` - Embeddings created for cloned messages (semantic recall) - Clone button in playground UI Memory tab - Exports `convertFullStreamChunkToMastra` from the stream module for AI SDK stream chunk transformations. ([#10911](https://github.com/mastra-ai/mastra/pull/10911)) - Add support for `providerOptions` when defining tools. This allows developers to specify provider-specific configurations (like Anthropic's `cacheControl`) per tool. ([#10649](https://github.com/mastra-ai/mastra/pull/10649)) ```typescript createTool({ id: 'my-tool', providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, // ... }); ``` - Remove `waitForEvent` from workflows. `waitForEvent` is now removed, please use suspend & resume flow instead. See https://mastra.ai/en/docs/workflows/suspend-and-resume for more details on suspend & resume flow. ([#9214](https://github.com/mastra-ai/mastra/pull/9214)) - Fix delayed promises rejecting when stream suspends on tool-call-approval ([#11278](https://github.com/mastra-ai/mastra/pull/11278)) When a stream ends in suspended state (e.g., requiring tool approval), the delayed promises like `toolResults`, `toolCalls`, `text`, etc. now resolve with partial results instead of rejecting with an error. This allows consumers to access data that was produced before the suspension. Also improves generic type inference for `LLMStepResult` and related types throughout the streaming infrastructure. - Workflow validation zod v4 support ([#9319](https://github.com/mastra-ai/mastra/pull/9319)) - Fixed semantic recall fetching all thread messages instead of only matched ones. ([#11435](https://github.com/mastra-ai/mastra/pull/11435)) When using `semanticRecall` with `scope: 'thread'`, the processor was incorrectly fetching all messages from the thread instead of just the semantically matched messages with their context. This caused memory to return far more messages than expected when `topK` and `messageRange` were set to small values. Fixes #11428 - Improved test description in ModelsDevGateway to clearly reflect the behavior being tested ([#11460](https://github.com/mastra-ai/mastra/pull/11460)) - Add optional `partial` query parameter to `/api/agents` and `/api/workflows` endpoints to return minimal data without schemas, reducing payload size for list views: ([#10886](https://github.com/mastra-ai/mastra/pull/10886)) - When `partial=true`: tool schemas (inputSchema, outputSchema) are omitted - When `partial=true`: workflow steps are replaced with stepCount integer - When `partial=true`: workflow root schemas (inputSchema, outputSchema) are omitted - Maintains backward compatibility when partial parameter is not provided **Server Endpoint Usage** ```bash # Get partial agent data (no tool schemas) GET /api/agents?partial=true # Get full agent data (default behavior) GET /api/agents # Get partial workflow data (stepCount instead of steps, no schemas) GET /api/workflows?partial=true # Get full workflow data (default behavior) GET /api/workflows ``` **Client SDK Usage** ```typescript import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); // Get partial agent list (smaller payload) const partialAgents = await client.listAgents({ partial: true }); // Get full agent list with tool schemas const fullAgents = await client.listAgents(); // Get partial workflow list (smaller payload) const partialWorkflows = await client.listWorkflows({ partial: true }); // Get full workflow list with steps and schemas const fullWorkflows = await client.listWorkflows(); ``` - Use memory mock in server tests ([#9486](https://github.com/mastra-ai/mastra/pull/9486)) - Add human-in-the-loop support for workflows used in agent ([#10871](https://github.com/mastra-ai/mastra/pull/10871)) - Real-time span export for Inngest workflow engine ([#11973](https://github.com/mastra-ai/mastra/pull/11973)) - Spans are now exported immediately when created and ended, instead of being batched at workflow completion - Added durable span lifecycle hooks (`createStepSpan`, `endStepSpan`, `errorStepSpan`, `createChildSpan`, `endChildSpan`, `errorChildSpan`) that wrap span operations in Inngest's `step.run()` for memoization - Added `rebuildSpan()` method to reconstruct span objects from exported data after Inngest replay - Fixed nested workflow step spans missing output data - Spans correctly maintain parent-child relationships across Inngest's durable execution boundaries using `tracingIds` - Fix network routing agent smoothstreaming ([#9247](https://github.com/mastra-ai/mastra/pull/9247)) - Adds type inference for `mastra.get*ById` functions. Only those registered at the top level mastra instance will get inferred. MCP and tool id's do not get inferred yet, those need additional changes. ([#10199](https://github.com/mastra-ai/mastra/pull/10199)) - Cache processor instances in MastraMemory to preserve embedding cache across calls ([#11720](https://github.com/mastra-ai/mastra/pull/11720)) Fixed issue where getInputProcessors() and getOutputProcessors() created new processor instances on each call, causing the SemanticRecall embedding cache to be discarded. Processor instances (SemanticRecall, WorkingMemory, MessageHistory) are now cached and reused, reducing unnecessary embedding API calls and improving latency. Also added cache invalidation when setStorage(), setVector(), or setEmbedder() are called to ensure processors use updated dependencies. Fixes #11455 - Bump @ai-sdk/openai from 3.0.0-beta.102 to 3.0.1 ([#11377](https://github.com/mastra-ai/mastra/pull/11377)) - Add timeTravel to workflows. This makes it possible to start a workflow run from a particular step in the workflow ([#9994](https://github.com/mastra-ai/mastra/pull/9994)) Example code: ```ts const result = await run.timeTravel({ step: 'step2', inputData: { value: 'input', }, }); ``` - Fixed duplicate assistant messages appearing when using `useChat` with memory enabled. ([#11195](https://github.com/mastra-ai/mastra/pull/11195)) **What was happening:** When using `useChat` with `chatRoute` and memory, assistant messages were being duplicated in storage after multiple conversation turns. This occurred because the backend-generated message ID wasn't being sent back to `useChat`, causing ID mismatches during deduplication. **What changed:** - The backend now sends the assistant message ID in the stream's start event, so `useChat` uses the same ID as storage - Custom `data-*` parts (from `writer.custom()`) are now preserved when messages contain V5 tool parts Fixes #11091 - Fixed sub-agents in `agent.network()` not receiving conversation history. ([#11825](https://github.com/mastra-ai/mastra/pull/11825)) Sub-agents now have access to previous user messages from the conversation, enabling them to understand context from earlier exchanges. This resolves the issue where sub-agents would respond without knowledge of prior conversation turns. Fixes #11468 - Remove format from stream/generate ([#9577](https://github.com/mastra-ai/mastra/pull/9577)) - - Add persistence for custom data chunks (`data-*` parts) emitted via `writer.custom()` in tools ([#10884](https://github.com/mastra-ai/mastra/pull/10884)) - Data chunks are now saved to message storage so they survive page refreshes - Update `@assistant-ui/react` to v0.11.47 with native `DataMessagePart` support - Convert `data-*` parts to `DataMessagePart` format (`{ type: 'data', name: string, data: T }`) - Update related `@assistant-ui/*` packages for compatibility - Fix processInputStep so it runs correctly. ([#10909](https://github.com/mastra-ai/mastra/pull/10909)) - Fix agent network working memory tool routing. Memory tools are now included in routing agent instructions but excluded from its direct tool calls, allowing the routing agent to properly route to tool execution steps for memory updates. ([#9428](https://github.com/mastra-ai/mastra/pull/9428)) - Fix working memory zod to json schema conversion to use schema-compat zodtoJsonSchema fn. ([#10391](https://github.com/mastra-ai/mastra/pull/10391)) - Fix TypeScript type narrowing when iterating over typed RequestContext ([#10850](https://github.com/mastra-ai/mastra/pull/10850)) The `set()` and `get()` methods on a typed `RequestContext` already provide full type safety. However, when iterating with `entries()`, `keys()`, `values()`, or `forEach()`, TypeScript couldn't narrow the value type based on key checks. Now it can: ```typescript const ctx = new RequestContext<{ userId: string; maxTokens: number }>(); // Direct access: const tokens = ctx.get('maxTokens'); // number // Iteration now works too: for (const [key, value] of ctx.entries()) { if (key === 'maxTokens') { value.toFixed(0); // TypeScript knows value is number } } ``` - Fixes parallel tool call issue with Gemini 3 Pro by preventing step-start parts from being inserted between consecutive tool parts in the `addStartStepPartsForAIV5` function. This ensures that the AI SDK's `convertToModelMessages` correctly preserves the order of parallel tool calls and maintains the `thought_signature` on the first tool call as required by Gemini's API. ([#10372](https://github.com/mastra-ai/mastra/pull/10372)) - Fixes assets not being downloaded when available ([#10079](https://github.com/mastra-ai/mastra/pull/10079)) - Fixed OpenAI reasoning message merging so distinct reasoning items are no longer dropped when they share a message ID. Prevents downstream errors where a function call is missing its required "reasoning" item. See #9005. ([#10614](https://github.com/mastra-ai/mastra/pull/10614)) - Fix creating system messages from inside processors using processInput. ([#9469](https://github.com/mastra-ai/mastra/pull/9469)) - Fix usage tracking with agent network ([#9226](https://github.com/mastra-ai/mastra/pull/9226)) - Remove unused dependencies ([#10019](https://github.com/mastra-ai/mastra/pull/10019)) - fix(workflows): ensure writer.custom() bubbles up from nested workflows and loops ([#11422](https://github.com/mastra-ai/mastra/pull/11422)) Previously, when using `writer.custom()` in steps within nested sub-workflows or loops (like `dountil`), the custom data events would not properly bubble up to the top-level workflow stream. This fix ensures that custom events are now correctly propagated through the nested workflow hierarchy without modification, allowing them to be consumed at the top level. This brings workflows in line with the existing behavior for agents, where custom data chunks properly bubble up through sub-agent execution. **What changed:** - Modified the `nestedWatchCb` function in workflow event handling to detect and preserve `data-*` custom events - Custom events now bubble up directly without being wrapped or modified - Regular workflow events continue to work as before with proper step ID prefixing **Example:** ```typescript const subStep = createStep({ id: 'subStep', execute: async ({ writer }) => { await writer.custom({ type: 'custom-progress', data: { status: 'processing' }, }); return { result: 'done' }; }, }); const subWorkflow = createWorkflow({ id: 'sub' }).then(subStep).commit(); const topWorkflow = createWorkflow({ id: 'top' }).then(subWorkflow).commit(); const run = await topWorkflow.createRun(); const stream = run.stream({ inputData: {} }); // Custom events from subStep now properly appear in the top-level stream for await (const event of stream) { if (event.type === 'custom-progress') { console.log(event.data); // { status: 'processing' } } } ``` - Fix message conversion for incomplete client-side tool calls ([#9749](https://github.com/mastra-ai/mastra/pull/9749)) Fixed handling of `input-available` tool state in `sanitizeV5UIMessages()` to differentiate between two use cases: 1. **Response messages FROM the LLM**: Keep `input-available` states (tool calls waiting for client-side execution) in `response.messages` for proper message history. 2. **Prompt messages TO the LLM**: Filter out `input-available` states when sending historical messages back to the LLM, as these incomplete tool calls (without results) cause errors in the OpenAI Responses API. The fix adds a `filterIncompleteToolCalls` parameter to control this behavior based on whether messages are being sent to or received from the LLM. - Fix `runEvals()` to automatically save scores to storage, making them visible in Studio observability. ([#11516](https://github.com/mastra-ai/mastra/pull/11516)) Previously, `runEvals()` would calculate scores but not persist them to storage, requiring users to manually implement score saving via the `onItemComplete` callback. Scores now automatically save when the target (Agent/Workflow) has an associated Mastra instance with storage configured. **What changed:** - Scores are now automatically saved to storage after each evaluation run - Fixed compatibility with both Agent (`getMastraInstance()`) and Workflow (`.mastra` getter) - Saved scores include complete context: `groundTruth` (in `additionalContext`), `requestContext`, `traceId`, and `spanId` - Scores are marked with `source: 'TEST'` to distinguish them from live scoring **Migration:** No action required. The `onItemComplete` workaround for saving scores can be removed if desired, but will continue to work for custom logic. **Example:** ```typescript const result = await runEvals({ target: mastra.getWorkflow("myWorkflow"), data: [{ input: {...}, groundTruth: {...} }], scorers: [myScorer], }); // Scores are now automatically saved and visible in Studio! ``` - Make suspendPayload optional when calling `suspend()` ([#9926](https://github.com/mastra-ai/mastra/pull/9926)) Save value returned as `suspendOutput` if user returns data still after calling `suspend()` Automatically call `commit()` on uncommitted workflows when registering in Mastra instance Show actual suspendPayload on Studio in suspend/resume flow - Add `initialState` and `outputOptions` to run.stream() call. ([#9238](https://github.com/mastra-ai/mastra/pull/9238)) Example code ```ts const run = await workflow.createRunAsync(); const streamResult = run.stream({ inputData: {}, initialState: { value: 'test-state', otherValue: 'test-other-state' }, outputOptions: { includeState: true }, }); ``` Then the result from the stream will include the final state information ```ts const executionResult = await streamResult.result; console.log(executionResult.state); ``` - Fixed double validation bug that prevented Zod transforms from working correctly in tool schemas. ([#11025](https://github.com/mastra-ai/mastra/pull/11025)) When tools with Zod `.transform()` or `.pipe()` in their `outputSchema` were executed through the Agent pipeline, validation was happening twice - once in Tool.execute() (correct) and again in CoreToolBuilder (incorrect). The second validation received already-transformed data but expected pre-transform data, causing validation errors. This fix enables proper use of Zod transforms in both `inputSchema` (for normalizing/cleaning input data) and `outputSchema` (for transforming output data to be LLM-friendly). - Add visual styles and labels for more workflow node types ([#9777](https://github.com/mastra-ai/mastra/pull/9777)) - Fix autoresume not working fine in useChat ([#11486](https://github.com/mastra-ai/mastra/pull/11486)) - Multiple Processor improvements including: ([#10947](https://github.com/mastra-ai/mastra/pull/10947)) - Workflows can now return tripwires, they bubble up from agents that return tripwires in a step - You can write processors as workflows using the existing Workflow primitive, every processor flow is now a workflow. - tripwires that you throw can now return additional information including ability to retry the step - New processor method `processOutputStep` added which runs after every step. **What's new:** **1. Retry mechanism with LLM feedback** - Processors can now request retries with feedback that gets sent back to the LLM: ```typescript processOutputStep: async ({ text, abort, retryCount }) => { if (isLowQuality(text)) { abort('Response quality too low', { retry: true, metadata: { score: 0.6 } }); } return []; }; ``` Configure with `maxProcessorRetries` (default: 3). Rejected steps are preserved in `result.steps[n].tripwire`. Retries are only available in `processOutputStep` and `processInputStep`. It will replay the step with additional context added. **2. Workflow orchestration for processors** - Processors can now be composed using workflow primitives: ```typescript import { createStep, createWorkflow } from '@mastra/core/workflows'; import { ProcessorStepSchema, } from '@mastra/core/processors'; const moderationWorkflow = createWorkflow({ id: 'moderation', inputSchema: ProcessorStepSchema, outputSchema: ProcessorStepSchema }) .then(createStep(new lengthValidator({...}))) .parallel([createStep(new piiDetector({...}), createStep(new toxicityChecker({...}))]) .commit(); const agent = new Agent({ inputProcessors: [moderationWorkflow] }); ``` Every processor array that gets passed to an agent gets added as a workflow image **3. Extended tripwire API** - `abort()` now accepts options for retry control and typed metadata: ```typescript abort('reason', { retry: true, metadata: { score: 0.8, category: 'quality' } }); ``` **4. New `processOutputStep` method** - Per-step output processing with access to step number, finish reason, tool calls, and retry count. **5. Workflow tripwire status** - Workflows now have a `'tripwire'` status distinct from `'failed'`, properly bubbling up processor rejections. - Updated dependencies [[`b9b7ffd`](https://github.com/mastra-ai/mastra/commit/b9b7ffdad6936a7d50b6b814b5bbe54e19087f66), [`9650cce`](https://github.com/mastra-ai/mastra/commit/9650cce52a1d917ff9114653398e2a0f5c3ba808), [`6833c69`](https://github.com/mastra-ai/mastra/commit/6833c69607418d257750bbcdd84638993d343539), [`dd1c38d`](https://github.com/mastra-ai/mastra/commit/dd1c38d1b75f1b695c27b40d8d9d6ed00d5e0f6f), [`f93e2f5`](https://github.com/mastra-ai/mastra/commit/f93e2f575e775e627e5c1927cefdd72db07858ed), [`d07b568`](https://github.com/mastra-ai/mastra/commit/d07b5687819ea8cb1dffa776d0c1765faf4aa1ae), [`51acef9`](https://github.com/mastra-ai/mastra/commit/51acef95b5977826594fe3ee24475842bd3d5780), [`af56599`](https://github.com/mastra-ai/mastra/commit/af56599d73244ae3bf0d7bcade656410f8ded37b), [`70b300e`](https://github.com/mastra-ai/mastra/commit/70b300ebc631dfc0aa14e61547fef7994adb4ea6), [`f03ae60`](https://github.com/mastra-ai/mastra/commit/f03ae60500fe350c9d828621006cdafe1975fdd8), [`3bf08bf`](https://github.com/mastra-ai/mastra/commit/3bf08bf9c7c73818ac937b5a69d90e205653115f), [`bae33d9`](https://github.com/mastra-ai/mastra/commit/bae33d91a63fbb64d1e80519e1fc1acaed1e9013), [`83d5942`](https://github.com/mastra-ai/mastra/commit/83d5942669ce7bba4a6ca4fd4da697a10eb5ebdc)]: - @mastra/schema-compat@1.0.0 ## 1.0.0-beta.27 ### Patch Changes - Fixed migration CLI failing with MIGRATION_REQUIRED error during Mastra import. Added MASTRA_DISABLE_STORAGE_INIT environment variable to skip auto-initialization of storage, allowing the migration command to import user's Mastra config without triggering the migration check. Also improved the migration prompt display to show warning messages before the confirmation dialog. ([#12100](https://github.com/mastra-ai/mastra/pull/12100)) ## 1.0.0-beta.26 ### Major Changes - Renamed MastraStorage to MastraCompositeStore for better clarity. The old MastraStorage name remains available as a deprecated alias for backward compatibility, but will be removed in a future version. ([#12093](https://github.com/mastra-ai/mastra/pull/12093)) **Migration:** Update your imports and usage: ```typescript // Before import { MastraStorage } from '@mastra/core/storage'; const storage = new MastraStorage({ id: 'composite', domains: { ... } }); // After import { MastraCompositeStore } from '@mastra/core/storage'; const storage = new MastraCompositeStore({ id: 'composite', domains: { ... } }); ``` The new name better reflects that this is a composite storage implementation that routes different domains (workflows, traces, messages) to different underlying stores, avoiding confusion with the general "Mastra Storage" concept. ### Patch Changes - Improve type handling with Zod ([#12091](https://github.com/mastra-ai/mastra/pull/12091)) ## 1.0.0-beta.25 ### Minor Changes - Added human-in-the-loop (HITL) tool approval support for `generate()` method. ([#12056](https://github.com/mastra-ai/mastra/pull/12056)) **Why:** This provides parity between `stream()` and `generate()` for tool approval flows, allowing non-streaming use cases to leverage `requireToolApproval` without needing to switch to streaming. Previously, tool approval with `requireToolApproval` only worked with `stream()`. Now you can use the same approval flow with `generate()` for non-streaming use cases. **Using tool approval with generate()** ```typescript const output = await agent.generate('Find user John', { requireToolApproval: true, }); // Check if a tool is waiting for approval if (output.finishReason === 'suspended') { console.log('Tool requires approval:', output.suspendPayload.toolName); // Approve the tool call const result = await agent.approveToolCallGenerate({ runId: output.runId, toolCallId: output.suspendPayload.toolCallId, }); console.log(result.text); } ``` **Declining a tool call** ```typescript if (output.finishReason === 'suspended') { const result = await agent.declineToolCallGenerate({ runId: output.runId, toolCallId: output.suspendPayload.toolCallId, }); } ``` **New methods added:** - `agent.approveToolCallGenerate({ runId, toolCallId })` - Approves a pending tool call and returns the complete result - `agent.declineToolCallGenerate({ runId, toolCallId })` - Declines a pending tool call and returns the complete result **Server routes added:** - `POST /api/agents/:agentId/approve-tool-call-generate` - `POST /api/agents/:agentId/decline-tool-call-generate` The playground UI now also supports tool approval when using generate mode. - Exported `isProcessorWorkflow` function from @mastra/core/processors. Added `getConfiguredProcessorWorkflows()` method to agents and `listProcessors()` method to the Mastra class for programmatic access to processor information. ([#12059](https://github.com/mastra-ai/mastra/pull/12059)) - Added new `listThreads` method for flexible thread filtering across all storage adapters. ([#11832](https://github.com/mastra-ai/mastra/pull/11832)) **New Features** - Filter threads by `resourceId`, `metadata`, or both (with AND logic for metadata key-value pairs) - All filter parameters are optional, allowing you to list all threads or filter as needed - Full pagination and sorting support **Example Usage** ```typescript // List all threads const allThreads = await memory.listThreads({}); // Filter by resourceId only const userThreads = await memory.listThreads({ filter: { resourceId: 'user-123' }, }); // Filter by metadata only const supportThreads = await memory.listThreads({ filter: { metadata: { category: 'support' } }, }); // Filter by both with pagination const filteredThreads = await memory.listThreads({ filter: { resourceId: 'user-123', metadata: { priority: 'high', status: 'open' }, }, orderBy: { field: 'updatedAt', direction: 'DESC' }, page: 0, perPage: 20, }); ``` **Security Improvements** - Added validation to prevent SQL injection via malicious metadata keys - Added pagination parameter validation to prevent integer overflow attacks ### Patch Changes - Fixed agent network mode failing with "Cannot read properties of undefined" error when tools or workflows don't have an `inputSchema` defined. ([#12063](https://github.com/mastra-ai/mastra/pull/12063)) - **@mastra/core:** Fixed `getRoutingAgent()` to handle tools and workflows without `inputSchema` by providing a default empty schema fallback. - **@mastra/schema-compat:** Fixed Zod v4 optional/nullable fields producing invalid JSON schema for OpenAI structured outputs. OpenAI now correctly receives `type: ["string", "null"]` instead of `anyOf` patterns that were rejected with "must have a 'type' key" error. - Fixed duplicate storage initialization when init() is called explicitly before other methods. The augmentWithInit proxy now tracks when init() is called directly, preventing subsequent method calls from triggering init() again. This resolves the high volume of requests to storage backends (like Turso) during agent streaming with memory enabled. ([#12067](https://github.com/mastra-ai/mastra/pull/12067)) - chore(core): MessageHistory input processor pass resourceId for storage ([#11910](https://github.com/mastra-ai/mastra/pull/11910)) - Updated dependencies [[`6833c69`](https://github.com/mastra-ai/mastra/commit/6833c69607418d257750bbcdd84638993d343539)]: - @mastra/schema-compat@1.0.0-beta.8 ## 1.0.0-beta.24 ### Minor Changes - Added `flush()` method to `ObservabilityExporter`, `ObservabilityBridge`, and `ObservabilityInstance` interfaces ([#12003](https://github.com/mastra-ai/mastra/pull/12003)) ### Patch Changes - When using agent networks, the routing agent could fail with a cryptic `TypeError: Cannot read properties of undefined` if the generation response was missing or malformed. This made it difficult to diagnose why routing failed. The release now throws a descriptive error with debugging details (response text, finish reason, usage) to help identify the root cause. ([#12028](https://github.com/mastra-ai/mastra/pull/12028)) Fixes #11749 - Improve error messaging for LLM API errors. When an error originates from an LLM provider (e.g., rate limits, overloaded, auth failures), the console now indicates it's an upstream API error and includes the provider and model information. ([#12022](https://github.com/mastra-ai/mastra/pull/12022)) Before: ``` ERROR (Mastra): Error in agent stream error: { "message": "Overloaded", "type": "overloaded_error" } ``` After: ``` ERROR (Mastra): Upstream LLM API error from anthropic (model: claude-3-opus) error: { "message": "Overloaded", "type": "overloaded_error" } ``` - Fixed the type of targetResult in the onItemComplete callback for runEvals. The parameter was incorrectly typed as a Promise, but the actual value passed is already resolved. Users no longer need to await targetResult inside their callback. ([#12030](https://github.com/mastra-ai/mastra/pull/12030)) - Updated dependencies [[`f93e2f5`](https://github.com/mastra-ai/mastra/commit/f93e2f575e775e627e5c1927cefdd72db07858ed)]: - @mastra/schema-compat@1.0.0-beta.7 ## 1.0.0-beta.23 ### Patch Changes - Added `customSpanFormatter` option to exporters for per-exporter span transformation. This allows different formatting per exporter and supports both synchronous and asynchronous operations, including async data enrichment. ([#11985](https://github.com/mastra-ai/mastra/pull/11985)) **Configuration example:** ```ts import { DefaultExporter } from '@mastra/observability'; import { SpanType } from '@mastra/core/observability'; import type { CustomSpanFormatter } from '@mastra/core/observability'; // Sync formatter const plainTextFormatter: CustomSpanFormatter = span => { if (span.type === SpanType.AGENT_RUN && Array.isArray(span.input)) { const userMessage = span.input.find(m => m.role === 'user'); return { ...span, input: userMessage?.content ?? span.input }; } return span; }; // Async formatter for data enrichment const enrichmentFormatter: CustomSpanFormatter = async span => { const userData = await fetchUserData(span.metadata?.userId); return { ...span, metadata: { ...span.metadata, userName: userData.name } }; }; const exporter = new DefaultExporter({ customSpanFormatter: plainTextFormatter, }); ``` Also added `chainFormatters` utility to combine multiple formatters (supports mixed sync/async): ```ts import { chainFormatters } from '@mastra/observability'; const exporter = new BraintrustExporter({ customSpanFormatter: chainFormatters([syncFormatter, asyncFormatter]), }); ``` - Fix type recursion by importing from 'zod' instead of 'zod/v3' ([#12009](https://github.com/mastra-ai/mastra/pull/12009)) ## 1.0.0-beta.22 ### Major Changes - Refactor workflow and tool types to remove Zod-specific constraints ([#11814](https://github.com/mastra-ai/mastra/pull/11814)) Removed Zod-specific type constraints across all workflow implementations and tool types, replacing them with generic types. This ensures type consistency across default, evented, and inngest workflows while preparing for Zod v4 migration. **Workflow Changes:** - Removed `z.ZodObject` and `z.ZodType` constraints from all workflow generic types - Updated method signatures to use `TInput` and `TState` directly instead of `z.infer` and `z.infer` - Aligned conditional types across all workflow implementations using `TInput extends unknown` pattern - Fixed `TSteps` generic to properly use `TEngineType` instead of `any` **Tool Changes:** - Removed Zod schema constraints from `ToolExecutionContext` and related interfaces - Simplified type parameters from `TSuspendSchema extends ZodLikeSchema` to `TSuspend` and `TResume` - Updated tool execution context types to use generic types **Type Utilities:** - Refactored type helpers to work with generic schemas instead of Zod-specific types - Updated type extraction utilities for better compatibility This change maintains backward compatibility while improving type consistency and preparing for Zod v4 support across all affected packages. - **Breaking Change**: Convert OUTPUT generic from `OutputSchema` constraint to plain generic ([#11741](https://github.com/mastra-ai/mastra/pull/11741)) This change removes the direct dependency on Zod typings in the public API by converting all `OUTPUT extends OutputSchema` generic constraints to plain `OUTPUT` generics throughout the codebase. This is preparation for moving to a standard schema approach. - All generic type parameters previously constrained to `OutputSchema` (e.g., ``) are now plain generics with defaults (e.g., ``) - Affects all public APIs including `Agent`, `MastraModelOutput`, `AgentExecutionOptions`, and stream/generate methods - `InferSchemaOutput` replaced with `OUTPUT` throughout - `PartialSchemaOutput` replaced with `Partial` - Schema fields now use `NonNullable>` instead of `OUTPUT` directly - Added `FullOutput` type representing complete output with all fields - Added `AgentExecutionOptionsBase` type - `getFullOutput()` method now returns `Promise>` - `Agent` class now generic: `Agent` - `agent.generate()` and `agent.stream()` methods have updated signatures - `MastraModelOutput` no longer requires `OutputSchema` constraint - Network route and streaming APIs updated to use plain OUTPUT generic **Before:** ```typescript const output = await agent.generate({ messages: [...], structuredOutput: { schema: mySchema } }); **After:** const output = await agent.generate>({ messages: [...], structuredOutput: { schema: mySchema } }); // Or rely on type inference: const output = await agent.generate({ messages: [...], structuredOutput: { schema: mySchema } }); ``` ### Minor Changes - Add context parameter to `idGenerator` to enable deterministic ID generation based on context. ([#10964](https://github.com/mastra-ai/mastra/pull/10964)) The `idGenerator` function now receives optional context about what type of ID is being generated and from which Mastra primitive. This allows generating IDs that can be shared with external databases. ```typescript const mastra = new Mastra({ idGenerator: context => { // context.idType: 'thread' | 'message' | 'run' | 'step' | 'generic' // context.source: 'agent' | 'workflow' | 'memory' // context.entityId: the agent/workflow id // context.threadId, context.resourceId, context.role, context.stepType if (context?.idType === 'message' && context?.threadId) { return `msg-${context.threadId}-${Date.now()}`; } if (context?.idType === 'run' && context?.source === 'agent') { return `run-${context.entityId}-${Date.now()}`; } return crypto.randomUUID(); }, }); ``` Existing `idGenerator` functions without parameters continue to work since the context is optional. Fixes #8131 - Add `hideInput` and `hideOutput` options to `TracingOptions` for protecting sensitive data in traces. ([#11969](https://github.com/mastra-ai/mastra/pull/11969)) When set to `true`, these options hide input/output data from all spans in a trace, including child spans. This is useful for protecting sensitive information from being logged to observability platforms. ```typescript const agent = mastra.getAgent('myAgent'); await agent.generate('Process this sensitive data', { tracingOptions: { hideInput: true, // Input will be hidden from all spans hideOutput: true, // Output will be hidden from all spans }, }); ``` The options can be used independently (hide only input or only output) or together. The settings are propagated to all child spans via `TraceState`, ensuring consistent behavior across the entire trace. Fixes #10888 - Removed the deprecated `AISDKV5OutputStream` class from the public API. ([#11845](https://github.com/mastra-ai/mastra/pull/11845)) **What changed:** The `AISDKV5OutputStream` class is no longer exported from `@mastra/core`. This class was previously used with the `format: 'aisdk'` option, which has already been removed from `.stream()` and `.generate()` methods. **Who is affected:** Only users who were directly importing `AISDKV5OutputStream` from `@mastra/core`. If you were using the standard `.stream()` or `.generate()` methods without the `format` option, no changes are needed. **Migration:** If you were importing this class directly, switch to using `MastraModelOutput` which provides the same streaming functionality: ```typescript // Before import { AISDKV5OutputStream } from '@mastra/core'; // After import { MastraModelOutput } from '@mastra/core'; ``` - Changed JSON columns from TEXT to JSONB in `mastra_threads` and `mastra_workflow_snapshot` tables. ([#11853](https://github.com/mastra-ai/mastra/pull/11853)) **Why this change?** These were the last remaining columns storing JSON as TEXT. This change aligns them with other tables that already use JSONB, enabling native JSON operators and improved performance. See [#8978](https://github.com/mastra-ai/mastra/issues/8978) for details. **Columns Changed:** - `mastra_threads.metadata` - Thread metadata - `mastra_workflow_snapshot.snapshot` - Workflow run state **PostgreSQL** Migration Required - PostgreSQL enforces column types, so existing tables must be migrated. Note: Migration will fail if existing column values contain invalid JSON. ```sql ALTER TABLE mastra_threads ALTER COLUMN metadata TYPE jsonb USING metadata::jsonb; ALTER TABLE mastra_workflow_snapshot ALTER COLUMN snapshot TYPE jsonb USING snapshot::jsonb; ``` **LibSQL** No Migration Required - LibSQL now uses native SQLite JSONB format (added in SQLite 3.45) for ~3x performance improvement on JSON operations. The changes are fully backwards compatible: - Existing TEXT JSON data continues to work - New data is stored in binary JSONB format - Both formats can coexist in the same table - All JSON functions (`json_extract`, etc.) work on both formats New installations automatically use JSONB. Existing applications continue to work without any changes. - Added `TrackingExporter` base class with improved handling for: ([#11870](https://github.com/mastra-ai/mastra/pull/11870)) - **Out-of-order span processing**: Spans that arrive before their parents are now queued and processed once dependencies are available - **Delayed cleanup**: Trace data is retained briefly after spans end to handle late-arriving updates - **Memory management**: Configurable limits on pending and total traces to prevent memory leaks New configuration options on `TrackingExporterConfig`: - `earlyQueueMaxAttempts` - Max retry attempts for queued events (default: 5) - `earlyQueueTTLMs` - TTL for queued events in ms (default: 30000) - `traceCleanupDelayMs` - Delay before cleaning up completed traces (default: 30000) - `maxPendingCleanupTraces` - Soft cap on traces awaiting cleanup (default: 100) - `maxTotalTraces` - Hard cap on total traces (default: 500) Updated @mastra/braintrust, @mastra/langfuse, @mastra/langsmith, @mastra/posthog to use the new TrackingExporter - Add MCP tool annotations and metadata support to `ToolAction` and `Tool` ([#11841](https://github.com/mastra-ai/mastra/pull/11841)) Tools can now surface UI hints like `title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint` via the `mcp.annotations` field, and pass arbitrary metadata to MCP clients via `mcp._meta`. These MCP-specific properties are grouped under the `mcp` property to clearly indicate they only apply when tools are exposed via MCP. ```typescript import { createTool } from '@mastra/core/tools'; const myTool = createTool({ id: 'weather', description: 'Get weather for a location', mcp: { annotations: { title: 'Weather Lookup', readOnlyHint: true, destructiveHint: false, }, _meta: { version: '1.0.0' }, }, execute: async ({ location }) => fetchWeather(location), }); ``` ### Patch Changes - Fix dimension mismatch error when switching embedders in SemanticRecall. The processor now properly validates vector index dimensions when an index already exists, preventing runtime errors when switching between embedders with different dimensions (e.g., fastembed 384 dims → OpenAI 1536 dims). ([#11893](https://github.com/mastra-ai/mastra/pull/11893)) - Removes the deprecated `threadId` and `resourceId` options from `AgentExecutionOptions`. These have been deprecated for months in favour of the `memory` option. ([#11897](https://github.com/mastra-ai/mastra/pull/11897)) ### Breaking Changes #### `@mastra/core` The `threadId` and `resourceId` options have been removed from `agent.generate()` and `agent.stream()`. Use the `memory` option instead: ```ts // Before await agent.stream('Hello', { threadId: 'thread-123', resourceId: 'user-456', }); // After await agent.stream('Hello', { memory: { thread: 'thread-123', resource: 'user-456', }, }); ``` #### `@mastra/server` The `threadId`, `resourceId`, and `resourceid` fields have been removed from the main agent execution body schema. The server now expects the `memory` option format in request bodies. Legacy routes (`/api/agents/:agentId/generate-legacy` and `/api/agents/:agentId/stream-legacy`) continue to support the deprecated fields. #### `@mastra/react` The `useChat` hook now internally converts `threadId` to the `memory` option format when making API calls. No changes needed in component code - the hook handles the conversion automatically. #### `@mastra/client-js` When using the client SDK agent methods, use the `memory` option instead of `threadId`/`resourceId`: ```ts const agent = client.getAgent('my-agent'); // Before await agent.generate({ messages: [...], threadId: 'thread-123', resourceId: 'user-456', }); // After await agent.generate({ messages: [...], memory: { thread: 'thread-123', resource: 'user-456', }, }); ``` - Add human-in-the-loop (HITL) support to agent networks ([#11678](https://github.com/mastra-ai/mastra/pull/11678)) - Add suspend/resume capabilities to agent network - Enable auto-resume for suspended network execution via `autoResumeSuspendedTools` `agent.resumeNetwork`, `agent.approveNetworkToolCall`, `agent.declineNetworkToolCall` - Fixed AI SDK v6 provider tools (like `openai.tools.webSearch()`) not being invoked correctly. These tools are now properly recognized and executed instead of causing failures or hallucinated tool calls. ([#11946](https://github.com/mastra-ai/mastra/pull/11946)) Resolves `#11781`. - Fixed `TokenLimiterProcessor` not filtering memory messages when limiting tokens. ([#11941](https://github.com/mastra-ai/mastra/pull/11941)) Previously, the processor only received the latest user input messages, missing the conversation history from memory. This meant token limiting couldn't filter historical messages to fit within the context window. The processor now correctly: - Accesses all messages (memory + input) when calculating token budgets - Accounts for system messages in the token budget - Filters older messages to prioritize recent conversation context Fixes #11902 - Fix crash in `mastraDBMessageToAIV4UIMessage` when `content.parts` is undefined or null. ([#11550](https://github.com/mastra-ai/mastra/pull/11550)) This resolves an issue where `ModerationProcessor` (and other code paths using `MessageList.get.*.ui()`) would throw `TypeError: Cannot read properties of undefined (reading 'length')` when processing messages with missing `parts` array. This commonly occurred when using AI SDK v4 (LanguageModelV1) models with input/output processors. The fix adds null coalescing (`?? []`) to safely handle undefined/null `parts` in the message conversion method. - Improved TypeScript type inference for workflow steps. ([#11953](https://github.com/mastra-ai/mastra/pull/11953)) **What changed:** - Step input/output type mismatches are now caught at compile time when chaining steps with `.then()` - The `execute` function now properly infers types from `inputSchema`, `outputSchema`, `stateSchema`, and other schema parameters - Clearer error messages when step types don't match workflow requirements **Why:** Previously, type errors in workflow step chains would only surface at runtime. Now TypeScript validates that each step's input requirements are satisfied by the previous step's output, helping you catch integration issues earlier in development. - Add `response` to finish chunk payload for output processor metadata access ([#11549](https://github.com/mastra-ai/mastra/pull/11549)) When using output processors with streaming, metadata added via `processOutputResult` is now accessible in the finish chunk's `payload.response.uiMessages`. This allows clients consuming streams over HTTP (e.g., via `/stream/ui`) to access processor-added metadata. ```typescript for await (const chunk of stream.fullStream) { if (chunk.type === 'finish') { const uiMessages = chunk.payload.response?.uiMessages; const metadata = uiMessages?.find(m => m.role === 'assistant')?.metadata; } } ``` Fixes #11454 - Fixed formatting of model_step, model_chunk, and tool_call spans in Arize Exporter. ([#11922](https://github.com/mastra-ai/mastra/pull/11922)) Also removed `tools` output from `model_step` spans for all exporters. - Improved tracing by filtering infrastructure chunks from model streams and adding success attribute to tool spans. ([#11943](https://github.com/mastra-ai/mastra/pull/11943)) Added generic input/output attribute mapping for additional span types in Arize exporter. - Fix generateTitle for pre-created threads ([#11771](https://github.com/mastra-ai/mastra/pull/11771)) - Title generation now works automatically for pre-created threads (via client SDK) - When `generateTitle: true` is configured, titles are generated on the first user message - Detection is based on message history: if no existing user messages in memory, it's the first message - No metadata flags required - works seamlessly with optimistic UI patterns Fixes #11757 - Real-time span export for Inngest workflow engine ([#11973](https://github.com/mastra-ai/mastra/pull/11973)) - Spans are now exported immediately when created and ended, instead of being batched at workflow completion - Added durable span lifecycle hooks (`createStepSpan`, `endStepSpan`, `errorStepSpan`, `createChildSpan`, `endChildSpan`, `errorChildSpan`) that wrap span operations in Inngest's `step.run()` for memoization - Added `rebuildSpan()` method to reconstruct span objects from exported data after Inngest replay - Fixed nested workflow step spans missing output data - Spans correctly maintain parent-child relationships across Inngest's durable execution boundaries using `tracingIds` - Fixed sub-agents in `agent.network()` not receiving conversation history. ([#11825](https://github.com/mastra-ai/mastra/pull/11825)) Sub-agents now have access to previous user messages from the conversation, enabling them to understand context from earlier exchanges. This resolves the issue where sub-agents would respond without knowledge of prior conversation turns. Fixes #11468 - Fix TypeScript type narrowing when iterating over typed RequestContext ([#10850](https://github.com/mastra-ai/mastra/pull/10850)) The `set()` and `get()` methods on a typed `RequestContext` already provide full type safety. However, when iterating with `entries()`, `keys()`, `values()`, or `forEach()`, TypeScript couldn't narrow the value type based on key checks. Now it can: ```typescript const ctx = new RequestContext<{ userId: string; maxTokens: number }>(); // Direct access: const tokens = ctx.get('maxTokens'); // number // Iteration now works too: for (const [key, value] of ctx.entries()) { if (key === 'maxTokens') { value.toFixed(0); // TypeScript knows value is number } } ``` ## 1.0.0-beta.21 ### Minor Changes - Add structured output support to agent.network() method. Users can now pass a `structuredOutput` option with a Zod schema to get typed results from network execution. ([#11701](https://github.com/mastra-ai/mastra/pull/11701)) The stream exposes `.object` (Promise) and `.objectStream` (ReadableStream) getters, and emits `network-object` and `network-object-result` chunk types. The structured output is generated after task completion using the provided schema. ```typescript const stream = await agent.network('Research AI trends', { structuredOutput: { schema: z.object({ summary: z.string(), recommendations: z.array(z.string()), }), }, }); const result = await stream.object; // result is typed: { summary: string; recommendations: string[] } ``` ### Patch Changes - dependencies updates: ([#10191](https://github.com/mastra-ai/mastra/pull/10191)) - Updated dependency [`dotenv@^17.2.3` ↗︎](https://www.npmjs.com/package/dotenv/v/17.2.3) (from `^16.6.1`, in `dependencies`) - Add additional context to workflow `onFinish` and `onError` callbacks ([#11705](https://github.com/mastra-ai/mastra/pull/11705)) The `onFinish` and `onError` lifecycle callbacks now receive additional properties: - `runId` - The unique identifier for the workflow run - `workflowId` - The workflow's identifier - `resourceId` - Optional resource identifier (if provided when creating the run) - `getInitData()` - Function that returns the initial input data passed to the workflow - `mastra` - The Mastra instance (if workflow is registered with Mastra) - `requestContext` - Request-scoped context data - `logger` - The workflow's logger instance - `state` - The workflow's current state object ```typescript const workflow = createWorkflow({ id: 'order-processing', inputSchema: z.object({ orderId: z.string() }), outputSchema: z.object({ status: z.string() }), options: { onFinish: async ({ runId, workflowId, getInitData, logger, state, mastra }) => { const inputData = getInitData(); logger.info(`Workflow ${workflowId} run ${runId} completed`, { orderId: inputData.orderId, finalState: state, }); // Access other Mastra components if needed const agent = mastra?.getAgent('notification-agent'); }, onError: async ({ runId, workflowId, error, logger, requestContext }) => { logger.error(`Workflow ${workflowId} run ${runId} failed: ${error?.message}`); // Access request context for additional debugging const userId = requestContext.get('userId'); }, }, }); ``` - Make initialState optional in studio ([#11744](https://github.com/mastra-ai/mastra/pull/11744)) - Refactor: consolidate duplicate applyMessages helpers in workflow.ts ([#11688](https://github.com/mastra-ai/mastra/pull/11688)) - Added optional `defaultSource` parameter to `ProcessorRunner.applyMessagesToMessageList` to support both 'input' and 'response' default sources - Removed 3 duplicate inline `applyMessages` helper functions from workflow.ts (in input, outputResult, and outputStep phases) - All phases now use the shared `ProcessorRunner.applyMessagesToMessageList` static method This is an internal refactoring with no changes to external behavior. - Cache processor instances in MastraMemory to preserve embedding cache across calls ([#11720](https://github.com/mastra-ai/mastra/pull/11720)) Fixed issue where getInputProcessors() and getOutputProcessors() created new processor instances on each call, causing the SemanticRecall embedding cache to be discarded. Processor instances (SemanticRecall, WorkingMemory, MessageHistory) are now cached and reused, reducing unnecessary embedding API calls and improving latency. Also added cache invalidation when setStorage(), setVector(), or setEmbedder() are called to ensure processors use updated dependencies. Fixes #11455 - Updated dependencies [[`3bf08bf`](https://github.com/mastra-ai/mastra/commit/3bf08bf9c7c73818ac937b5a69d90e205653115f)]: - @mastra/schema-compat@1.0.0-beta.6 ## 1.0.0-beta.20 ### Minor Changes - Deprecate `default: { enabled: true }` observability configuration ([#11674](https://github.com/mastra-ai/mastra/pull/11674)) The shorthand `default: { enabled: true }` configuration is now deprecated and will be removed in a future version. Users should migrate to explicit configuration with `DefaultExporter`, `CloudExporter`, and `SensitiveDataFilter`. **Before (deprecated):** ```typescript import { Observability } from '@mastra/observability'; const mastra = new Mastra({ observability: new Observability({ default: { enabled: true }, }), }); ``` **After (recommended):** ```typescript import { Observability, DefaultExporter, CloudExporter, SensitiveDataFilter } from '@mastra/observability'; const mastra = new Mastra({ observability: new Observability({ configs: { default: { serviceName: 'mastra', exporters: [new DefaultExporter(), new CloudExporter()], spanOutputProcessors: [new SensitiveDataFilter()], }, }, }), }); ``` The explicit configuration makes it clear exactly what exporters and processors are being used, improving code readability and maintainability. A deprecation warning will be logged when using the old configuration pattern. - Fix processor tracing to create individual spans per processor ([#11683](https://github.com/mastra-ai/mastra/pull/11683)) - Processor spans now correctly show processor IDs (e.g., `input processor: validator`) instead of combined workflow IDs - Each processor in a chain gets its own trace span, improving observability into processor execution - Spans are only created for phases a processor actually implements, eliminating empty spans - Internal agent calls within processors now properly nest under their processor span - Added `INPUT_STEP_PROCESSOR` and `OUTPUT_STEP_PROCESSOR` entity types for finer-grained tracing - Changed `processorType` span attribute to `processorExecutor` with values `'workflow'` or `'legacy'` - Add completion validation to agent networks using custom scorers ([#11562](https://github.com/mastra-ai/mastra/pull/11562)) You can now validate whether an agent network has completed its task by passing MastraScorers to `agent.network()`. When validation fails, the network automatically retries with feedback injected into the conversation. **Example: Creating a scorer to verify test coverage** ```ts import { createScorer } from '@mastra/core/evals'; import { z } from 'zod'; // Create a scorer that checks if tests were written const testsScorer = createScorer({ id: 'tests-written', description: 'Validates that unit tests were included in the response', type: 'agent', }).generateScore({ description: 'Return 1 if tests are present, 0 if missing', outputSchema: z.number(), createPrompt: ({ run }) => ` Does this response include unit tests? Response: ${run.output} Return 1 if tests are present, 0 if not. `, }); // Use the scorer with agent.network() const stream = await agent.network('Implement a fibonacci function with tests', { completion: { scorers: [testsScorer], strategy: 'all', // all scorers must pass (score >= 0.5) }, maxSteps: 3, }); ``` **What this enables:** - **Programmatic completion checks**: Define objective criteria for task completion instead of relying on the default LLM-based check - **Automatic retry with feedback**: When a scorer returns `score: 0`, its reason is injected into the conversation so the network can address the gap on the next iteration - **Composable validation**: Combine multiple scorers with `strategy: 'all'` (all must pass) or `strategy: 'any'` (at least one must pass) This replaces guesswork with reliable, repeatable validation that ensures agent networks produce outputs meeting your specific requirements. - Unified `getWorkflowRunById` and `getWorkflowRunExecutionResult` into a single API that returns `WorkflowState` with both metadata and execution state. ([#11429](https://github.com/mastra-ai/mastra/pull/11429)) **What changed:** - `getWorkflowRunById` now returns a unified `WorkflowState` object containing metadata (runId, workflowName, resourceId, createdAt, updatedAt) along with processed execution state (status, result, error, payload, steps) - Added optional `fields` parameter to request only specific fields for better performance - Added optional `withNestedWorkflows` parameter to control nested workflow step inclusion - Removed `getWorkflowRunExecutionResult` - use `getWorkflowRunById` instead (breaking change) - Removed `/execution-result` API endpoints from server (breaking change) - Removed `runExecutionResult()` method from client SDK (breaking change) - Removed `GetWorkflowRunExecutionResultResponse` type from client SDK (breaking change) **Before:** ```typescript // Had to call two different methods for different data const run = await workflow.getWorkflowRunById(runId); // Returns raw WorkflowRun with snapshot const result = await workflow.getWorkflowRunExecutionResult(runId); // Returns processed execution state ``` **After:** ```typescript // Single method returns everything const run = await workflow.getWorkflowRunById(runId); // Returns: { runId, workflowName, resourceId, createdAt, updatedAt, status, result, error, payload, steps } // Request only specific fields for better performance (avoids expensive step fetching) const status = await workflow.getWorkflowRunById(runId, { fields: ['status'] }); // Skip nested workflow steps for faster response const run = await workflow.getWorkflowRunById(runId, { withNestedWorkflows: false }); ``` **Why:** The previous API required calling two separate methods to get complete workflow run information. This unification simplifies the API surface and gives users control over performance - fetching all steps (especially nested workflows) can be expensive, so the `fields` and `withNestedWorkflows` options let users request only what they need. ### Patch Changes - dependencies updates: ([#10133](https://github.com/mastra-ai/mastra/pull/10133)) - Updated dependency [`js-tiktoken@^1.0.21` ↗︎](https://www.npmjs.com/package/js-tiktoken/v/1.0.21) (from `^1.0.20`, in `dependencies`) - Add embedded documentation support for Mastra packages ([#11472](https://github.com/mastra-ai/mastra/pull/11472)) Mastra packages now include embedded documentation in the published npm package under `dist/docs/`. This enables coding agents and AI assistants to understand and use the framework by reading documentation directly from `node_modules`. Each package includes: - **SKILL.md** - Entry point explaining the package's purpose and capabilities - **SOURCE_MAP.json** - Machine-readable index mapping exports to types and implementation files - **Topic folders** - Conceptual documentation organized by feature area Documentation is driven by the `packages` frontmatter field in MDX files, which maps docs to their corresponding packages. CI validation ensures all docs include this field. - Add support for `retries` and `scorers` parameters across all `createStep` overloads. ([#11495](https://github.com/mastra-ai/mastra/pull/11495)) The `createStep` function now includes support for the `retries` and `scorers` fields across all step creation patterns, enabling step-level retry configuration and AI evaluation support for regular steps, agent-based steps, and tool-based steps. ```typescript import { init } from '@mastra/inngest'; import { z } from 'zod'; const { createStep } = init(inngest); // 1. Regular step with retries const regularStep = createStep({ id: 'api-call', inputSchema: z.object({ url: z.string() }), outputSchema: z.object({ data: z.any() }), retries: 3, // ← Will retry up to 3 times on failure execute: async ({ inputData }) => { const response = await fetch(inputData.url); return { data: await response.json() }; }, }); // 2. Agent step with retries and scorers const agentStep = createStep(myAgent, { retries: 3, scorers: [{ id: 'accuracy-scorer', scorer: myAccuracyScorer }], }); // 3. Tool step with retries and scorers const toolStep = createStep(myTool, { retries: 2, scorers: [{ id: 'quality-scorer', scorer: myQualityScorer }], }); ``` This change ensures API consistency across all `createStep` overloads. All step types now support retry and evaluation configurations. This is a non-breaking change - steps without these parameters continue to work exactly as before. Fixes #9351 - Remove `streamVNext`, `resumeStreamVNext`, and `observeStreamVNext` methods, call `stream`, `resumeStream` and `observeStream` directly ([#11499](https://github.com/mastra-ai/mastra/pull/11499)) ```diff + const run = await workflow.createRun({ runId: '123' }); - const stream = await run.streamVNext({ inputData: { ... } }); + const stream = await run.stream({ inputData: { ... } }); ``` - Fix workflow tool not executing when requireApproval is true and tool call is approved ([#11538](https://github.com/mastra-ai/mastra/pull/11538)) - **Breaking Change:** `memory.readOnly` has been moved to `memory.options.readOnly` ([#11523](https://github.com/mastra-ai/mastra/pull/11523)) The `readOnly` option now lives inside `memory.options` alongside other memory configuration like `lastMessages` and `semanticRecall`. **Before:** ```typescript agent.stream('Hello', { memory: { thread: threadId, resource: resourceId, readOnly: true, }, }); ``` **After:** ```typescript agent.stream('Hello', { memory: { thread: threadId, resource: resourceId, options: { readOnly: true, }, }, }); ``` **Migration:** Run the codemod to update your code automatically: ```shell npx @mastra/codemod@beta v1/memory-readonly-to-options . ``` This also fixes issue #11519 where `readOnly: true` was being ignored and messages were saved to memory anyway. - Fix agent runs with multiple steps only showing last text chunk in observability tools ([#11672](https://github.com/mastra-ai/mastra/pull/11672)) When an agent model executes multiple steps and generates multiple text chunks, the onFinish payload was only receiving the text from the last step instead of all accumulated text. This caused observability tools like Braintrust to only display the final text chunk. The fix now correctly concatenates all text chunks from all steps. - Fix tool input validation destroying non-plain objects ([#11541](https://github.com/mastra-ai/mastra/pull/11541)) The `convertUndefinedToNull` function in tool input validation was treating all objects as plain objects and recursively processing them. For objects like `Date`, `Map`, `URL`, and class instances, this resulted in empty objects `{}` because they have no enumerable own properties. This fix changes the approach to only recurse into plain objects (objects with `Object.prototype` or `null` prototype). All other objects (Date, Map, Set, URL, RegExp, Error, custom class instances, etc.) are now preserved as-is. Fixes #11502 - Fixed client-side tool invocations not being stored in memory. Previously, tool invocations with state 'call' were filtered out before persistence, which incorrectly removed client-side tools. Now only streaming intermediate states ('partial-call') are filtered. ([#11630](https://github.com/mastra-ai/mastra/pull/11630)) Fixed a crash when updating working memory with an empty or null update; existing data is now preserved. - Fixed memory readOnly option not being respected when agents share a RequestContext. Previously, when output processors were resolved, the readOnly check happened too early - before the agent could set its own MastraMemory context. This caused child agents to inherit their parent's readOnly setting when sharing a RequestContext. ([#11653](https://github.com/mastra-ai/mastra/pull/11653)) The readOnly check is now only done at execution time in each processor's processOutputResult method, allowing proper isolation. - Fix network validation not seeing previous iteration results in multi-step tasks ([#11691](https://github.com/mastra-ai/mastra/pull/11691)) The validation LLM was unable to determine task completion for multi-step tasks because it couldn't see what primitives had already executed. Now includes a compact list of completed primitives in the validation prompt. - Fix provider-executed tools (like `openai.tools.webSearch()`) not working correctly with AI SDK v6 models. The agent's `generate()` method was ending prematurely with `finishReason: 'tool-calls'` instead of completing with a text response after tool execution. ([#11622](https://github.com/mastra-ai/mastra/pull/11622)) The issue was that V6 provider tools have `type: 'provider'` while V5 uses `type: 'provider-defined'`. The tool preparation code now detects the model version and uses the correct type. - Added `startExclusive` and `endExclusive` options to `dateRange` filter for message queries. ([#11479](https://github.com/mastra-ai/mastra/pull/11479)) **What changed:** The `filter.dateRange` parameter in `listMessages()` and `Memory.recall()` now supports `startExclusive` and `endExclusive` boolean options. When set to `true`, messages with timestamps exactly matching the boundary are excluded from results. **Why this matters:** Enables cursor-based pagination for chat applications. When new messages arrive during a session, offset-based pagination can skip or duplicate messages. Using `endExclusive: true` with the oldest message's timestamp as a cursor ensures consistent pagination without gaps or duplicates. **Example:** ```typescript // Get first page const page1 = await memory.recall({ threadId: 'thread-123', perPage: 10, orderBy: { field: 'createdAt', direction: 'DESC' }, }); // Get next page using cursor-based pagination const oldestMessage = page1.messages[page1.messages.length - 1]; const page2 = await memory.recall({ threadId: 'thread-123', perPage: 10, orderBy: { field: 'createdAt', direction: 'DESC' }, filter: { dateRange: { end: oldestMessage.createdAt, endExclusive: true, // Excludes the cursor message }, }, }); ``` - fix(core): support LanguageModelV3 in MastraModelGateway.resolveLanguageModel ([#11489](https://github.com/mastra-ai/mastra/pull/11489)) - Fixed agent network not returning text response when routing agent handles requests without delegation. ([#11497](https://github.com/mastra-ai/mastra/pull/11497)) **What changed:** - Agent networks now correctly stream text responses when the routing agent decides to handle a request itself instead of delegating to sub-agents, workflows, or tools - Added fallback in transformers to ensure text is always returned even if core events are missing **Why this matters:** Previously, when using `toAISdkV5Stream` or `networkRoute()` outside of the Mastra Studio UI, no text content was returned when the routing agent handled requests directly. This fix ensures consistent behavior across all API routes. Fixes #11219 - Add initial state input to workflow form in studio ([#11560](https://github.com/mastra-ai/mastra/pull/11560)) - Added missing stream types to @mastra/core/stream for better TypeScript support ([#11513](https://github.com/mastra-ai/mastra/pull/11513)) **New types available:** - Chunk types: `ToolCallChunk`, `ToolResultChunk`, `SourceChunk`, `FileChunk`, `ReasoningChunk` - Payload types: `ToolCallPayload`, `ToolResultPayload`, `TextDeltaPayload`, `ReasoningDeltaPayload`, `FilePayload`, `SourcePayload` - JSON utilities: `JSONValue`, `JSONObject`, `JSONArray` and readonly variants These types are now properly exported, enabling full TypeScript IntelliSense when working with streaming data. - Refactor the MessageList class from ~4000 LOC monolith to ~850 LOC with focused, single-responsibility modules. This improves maintainability, testability, and makes the codebase easier to understand. ([#11658](https://github.com/mastra-ai/mastra/pull/11658)) - Extract message format adapters (AIV4Adapter, AIV5Adapter) for SDK conversions - Extract TypeDetector for centralized message format identification - Extract MessageStateManager for tracking message sources and persistence - Extract MessageMerger for streaming message merge logic - Extract StepContentExtractor for step content extraction - Extract CacheKeyGenerator for message deduplication - Consolidate provider compatibility utilities (Gemini, Anthropic, OpenAI) ``` message-list/ ├── message-list.ts # Main class (~850 LOC, down from ~4000) ├── adapters/ # SDK format conversions │ ├── AIV4Adapter.ts # MastraDBMessage <-> AI SDK V4 │ └── AIV5Adapter.ts # MastraDBMessage <-> AI SDK V5 ├── cache/ │ └── CacheKeyGenerator.ts # Deduplication keys ├── conversion/ │ ├── input-converter.ts # Any format -> MastraDBMessage │ ├── output-converter.ts # MastraDBMessage -> SDK formats │ ├── step-content.ts # Step content extraction │ └── to-prompt.ts # LLM prompt formatting ├── detection/ │ └── TypeDetector.ts # Format identification ├── merge/ │ └── MessageMerger.ts # Streaming merge logic ├── state/ │ └── MessageStateManager.ts # Source & persistence tracking └── utils/ └── provider-compat.ts # Provider-specific fixes ``` - Resolve suspendPayload when tripwire is set off in agentic loop to prevent unresolved promises hanging. ([#11621](https://github.com/mastra-ai/mastra/pull/11621)) - Fix OpenAI reasoning model + memory failing on second generate with "missing item" error ([#11492](https://github.com/mastra-ai/mastra/pull/11492)) When using OpenAI reasoning models with memory enabled, the second `agent.generate()` call would fail with: "Item 'rs\_...' of type 'reasoning' was provided without its required following item." The issue was that `text-start` events contain `providerMetadata` with the text's `itemId` (e.g., `msg_xxx`), but this metadata was not being captured. When memory replayed the conversation, the reasoning part had its `rs_` ID but the text part was missing its `msg_` ID, causing OpenAI to reject the request. The fix adds handlers for `text-start` (to capture text providerMetadata) and `text-end` (to clear it and prevent leaking into subsequent parts). Fixes #11481 - Fix reasoning content being lost when text-start chunk arrives before reasoning-end ([#11494](https://github.com/mastra-ai/mastra/pull/11494)) Some model providers (e.g., ZAI/glm-4.6) return streaming chunks where `text-start` arrives before `reasoning-end`. Previously, this would clear the accumulated reasoning deltas, resulting in empty reasoning content in the final message. Now `text-start` is properly excluded from triggering the reasoning state reset, allowing `reasoning-end` to correctly save the reasoning content. - Add `resumeGenerate` method for resuming agent via generate ([#11503](https://github.com/mastra-ai/mastra/pull/11503)) Add `runId` and `suspendPayload` to fullOutput of agent stream Default `suspendedToolRunId` to empty string to prevent `null` issue - Adds thread cloning to create independent copies of conversations that can diverge. ([#11517](https://github.com/mastra-ai/mastra/pull/11517)) ```typescript // Clone a thread const { thread, clonedMessages } = await memory.cloneThread({ sourceThreadId: 'thread-123', title: 'My Clone', options: { messageLimit: 10, // optional: only copy last N messages }, }); // Check if a thread is a clone if (memory.isClone(thread)) { const source = await memory.getSourceThread(thread.id); } // List all clones of a thread const clones = await memory.listClones('thread-123'); ``` Includes: - Storage implementations for InMemory, PostgreSQL, LibSQL, Upstash - API endpoint: `POST /api/memory/threads/:threadId/clone` - Embeddings created for cloned messages (semantic recall) - Clone button in playground UI Memory tab - Fix `runEvals()` to automatically save scores to storage, making them visible in Studio observability. ([#11516](https://github.com/mastra-ai/mastra/pull/11516)) Previously, `runEvals()` would calculate scores but not persist them to storage, requiring users to manually implement score saving via the `onItemComplete` callback. Scores now automatically save when the target (Agent/Workflow) has an associated Mastra instance with storage configured. **What changed:** - Scores are now automatically saved to storage after each evaluation run - Fixed compatibility with both Agent (`getMastraInstance()`) and Workflow (`.mastra` getter) - Saved scores include complete context: `groundTruth` (in `additionalContext`), `requestContext`, `traceId`, and `spanId` - Scores are marked with `source: 'TEST'` to distinguish them from live scoring **Migration:** No action required. The `onItemComplete` workaround for saving scores can be removed if desired, but will continue to work for custom logic. **Example:** ```typescript const result = await runEvals({ target: mastra.getWorkflow("myWorkflow"), data: [{ input: {...}, groundTruth: {...} }], scorers: [myScorer], }); // Scores are now automatically saved and visible in Studio! ``` - Fix autoresume not working fine in useChat ([#11486](https://github.com/mastra-ai/mastra/pull/11486)) ## 1.0.0-beta.19 ### Minor Changes - Add embedderOptions support to Memory for AI SDK 5+ provider-specific embedding options ([#11462](https://github.com/mastra-ai/mastra/pull/11462)) With AI SDK 5+, embedding models no longer accept options in their constructor. Options like `outputDimensionality` for Google embedding models must now be passed when calling `embed()` or `embedMany()`. This change adds `embedderOptions` to Memory configuration to enable passing these provider-specific options. You can now configure embedder options when creating Memory: ```typescript import { Memory } from '@mastra/core'; import { google } from '@ai-sdk/google'; // Before: No way to specify providerOptions const memory = new Memory({ embedder: google.textEmbeddingModel('text-embedding-004'), }); // After: Pass embedderOptions with providerOptions const memory = new Memory({ embedder: google.textEmbeddingModel('text-embedding-004'), embedderOptions: { providerOptions: { google: { outputDimensionality: 768, taskType: 'RETRIEVAL_DOCUMENT', }, }, }, }); ``` This is especially important for: - Google `text-embedding-004`: Control output dimensions (default 768) - Google `gemini-embedding-001`: Reduce from default 3072 dimensions to avoid pgvector's 2000 dimension limit for HNSW indexes Fixes #8248 ### Patch Changes - Fix Anthropic API error when tool calls have empty input objects ([#11474](https://github.com/mastra-ai/mastra/pull/11474)) Fixes issue #11376 where Anthropic models would fail with error "messages.17.content.2.tool_use.input: Field required" when a tool call in a previous step had an empty object `{}` as input. The fix adds proper reconstruction of tool call arguments when converting messages to AIV5 model format. Tool-result parts now correctly include the `input` field from the matching tool call, which is required by Anthropic's API validation. Changes: - Added `findToolCallArgs()` helper method to search through messages and retrieve original tool call arguments - Enhanced `aiV5UIMessagesToAIV5ModelMessages()` to populate the `input` field on tool-result parts - Added comprehensive test coverage for empty object inputs, parameterized inputs, and multi-turn conversations - Fixed an issue where deprecated Groq models were shown during template creation. The model selection now filters out models marked as deprecated, displaying only active and supported models. ([#11445](https://github.com/mastra-ai/mastra/pull/11445)) - Fix AI SDK v6 (specificationVersion: "v3") model support in sub-agent calls. Previously, when a parent agent invoked a sub-agent with a v3 model through the `agents` property, the version check only matched "v2", causing v3 models to incorrectly fall back to legacy streaming methods and throw "V2 models are not supported for streamLegacy" error. ([#11452](https://github.com/mastra-ai/mastra/pull/11452)) The fix updates version checks in `listAgentTools` and `llm-mapping-step.ts` to use the centralized `supportedLanguageModelSpecifications` array which includes both v2 and v3. Also adds missing v3 test coverage to tool-handling.test.ts to prevent regression. - Fixed "Transforms cannot be represented in JSON Schema" error when using Zod v4 with structuredOutput ([#11466](https://github.com/mastra-ai/mastra/pull/11466)) When using schemas with `.optional()`, `.nullable()`, `.default()`, or `.nullish().default("")` patterns with `structuredOutput` and Zod v4, users would encounter an error because OpenAI schema compatibility layer adds transforms that Zod v4's native `toJSONSchema()` cannot handle. The fix uses Mastra's transform-safe `zodToJsonSchema` function which gracefully handles transforms by using the `unrepresentable: 'any'` option. Also exported `isZodType` utility from `@mastra/schema-compat` and updated it to detect both Zod v3 (`_def`) and Zod v4 (`_zod`) schemas. - Improved test description in ModelsDevGateway to clearly reflect the behavior being tested ([#11460](https://github.com/mastra-ai/mastra/pull/11460)) - Updated dependencies [[`d07b568`](https://github.com/mastra-ai/mastra/commit/d07b5687819ea8cb1dffa776d0c1765faf4aa1ae), [`70b300e`](https://github.com/mastra-ai/mastra/commit/70b300ebc631dfc0aa14e61547fef7994adb4ea6)]: - @mastra/schema-compat@1.0.0-beta.5 ## 1.0.0-beta.18 ### Patch Changes - Fixed semantic recall fetching all thread messages instead of only matched ones. ([#11435](https://github.com/mastra-ai/mastra/pull/11435)) When using `semanticRecall` with `scope: 'thread'`, the processor was incorrectly fetching all messages from the thread instead of just the semantically matched messages with their context. This caused memory to return far more messages than expected when `topK` and `messageRange` were set to small values. Fixes #11428 ## 1.0.0-beta.17 ### Patch Changes - Fix Zod 4 compatibility for storage schema detection ([#11431](https://github.com/mastra-ai/mastra/pull/11431)) If you're using Zod 4, `buildStorageSchema` was failing to detect nullable and optional fields correctly. This caused `NOT NULL constraint failed` errors when storing observability spans and other data. This fix enables proper schema detection for Zod 4 users, ensuring nullable fields like `parentSpanId` are correctly identified and don't cause database constraint violations. - Updated dependencies [[`af56599`](https://github.com/mastra-ai/mastra/commit/af56599d73244ae3bf0d7bcade656410f8ded37b)]: - @mastra/schema-compat@1.0.0-beta.4 ## 1.0.0-beta.16 ### Minor Changes - Add `onError` hook to server configuration for custom error handling. ([#11403](https://github.com/mastra-ai/mastra/pull/11403)) You can now provide a custom error handler through the Mastra server config to catch errors, format responses, or send them to external services like Sentry: ```typescript import { Mastra } from '@mastra/core/mastra'; const mastra = new Mastra({ server: { onError: (err, c) => { // Send to Sentry Sentry.captureException(err); // Return custom formatted response return c.json( { error: err.message, timestamp: new Date().toISOString(), }, 500, ); }, }, }); ``` If no `onError` is provided, the default error handler is used. Fixes #9610 ### Patch Changes - fix(observability): start MODEL_STEP span at beginning of LLM execution ([#11409](https://github.com/mastra-ai/mastra/pull/11409)) The MODEL_STEP span was being created when the step-start chunk arrived (after the model API call completed), causing the span's startTime to be close to its endTime instead of accurately reflecting when the step began. This fix ensures MODEL_STEP spans capture the full duration of each LLM execution step, including the API call latency, by starting the span at the beginning of the step execution rather than when the response starts streaming. Fixes #11271 - Fixed inline type narrowing for `tool.execute()` return type when using `outputSchema`. ([#11420](https://github.com/mastra-ai/mastra/pull/11420)) **Problem:** When calling `tool.execute()`, TypeScript couldn't narrow the `ValidationError | OutputType` union after checking `'error' in result && result.error`, causing type errors when accessing output properties. **Solution:** - Added `{ error?: never }` to the success type, enabling proper discriminated union narrowing - Simplified `createTool` generics so `inputData` is correctly typed based on `inputSchema` **Note:** Tool output schemas should not use `error` as a field name since it's reserved for ValidationError discrimination. Use `errorMessage` or similar instead. **Usage:** ```typescript const result = await myTool.execute({ firstName: 'Hans' }); if ('error' in result && result.error) { console.error('Validation failed:', result.message); return; } // ✅ TypeScript now correctly narrows result return { fullName: result.fullName }; ``` - Add support for `instructions` field in MCPServer ([#11421](https://github.com/mastra-ai/mastra/pull/11421)) Implements the official MCP specification's `instructions` field, which allows MCP servers to provide system-wide prompts that are automatically sent to clients during initialization. This eliminates the need for per-project configuration files (like AGENTS.md) by centralizing the system prompt in the server definition. **What's New:** - Added `instructions` optional field to `MCPServerConfig` type - Instructions are passed to the underlying MCP SDK Server during initialization - Instructions are sent to clients in the `InitializeResult` response - Fully compatible with all MCP clients (Cursor, Windsurf, Claude Desktop, etc.) **Example Usage:** ```typescript const server = new MCPServer({ name: 'GitHub MCP Server', version: '1.0.0', instructions: 'Use the available tools to help users manage GitHub repositories, issues, and pull requests. Always search before creating to avoid duplicates.', tools: { searchIssues, createIssue, listPRs }, }); ``` - Add storage composition to MastraStorage ([#11401](https://github.com/mastra-ai/mastra/pull/11401)) `MastraStorage` can now compose storage domains from different adapters. Use it when you need different databases for different purposes - for example, PostgreSQL for memory and workflows, but a different database for observability. ```typescript import { MastraStorage } from '@mastra/core/storage'; import { MemoryPG, WorkflowsPG, ScoresPG } from '@mastra/pg'; import { MemoryLibSQL } from '@mastra/libsql'; // Compose domains from different stores const storage = new MastraStorage({ id: 'composite', domains: { memory: new MemoryLibSQL({ url: 'file:./local.db' }), workflows: new WorkflowsPG({ connectionString: process.env.DATABASE_URL }), scores: new ScoresPG({ connectionString: process.env.DATABASE_URL }), }, }); ``` **Breaking changes:** - `storage.supports` property no longer exists - `StorageSupports` type is no longer exported from `@mastra/core/storage` All stores now support the same features. For domain availability, use `getStore()`: ```typescript const store = await storage.getStore('memory'); if (store) { // domain is available } ``` - Fix various places in core package where we were logging with console.error instead of the mastra logger. ([#11425](https://github.com/mastra-ai/mastra/pull/11425)) - fix(workflows): ensure writer.custom() bubbles up from nested workflows and loops ([#11422](https://github.com/mastra-ai/mastra/pull/11422)) Previously, when using `writer.custom()` in steps within nested sub-workflows or loops (like `dountil`), the custom data events would not properly bubble up to the top-level workflow stream. This fix ensures that custom events are now correctly propagated through the nested workflow hierarchy without modification, allowing them to be consumed at the top level. This brings workflows in line with the existing behavior for agents, where custom data chunks properly bubble up through sub-agent execution. **What changed:** - Modified the `nestedWatchCb` function in workflow event handling to detect and preserve `data-*` custom events - Custom events now bubble up directly without being wrapped or modified - Regular workflow events continue to work as before with proper step ID prefixing **Example:** ```typescript const subStep = createStep({ id: 'subStep', execute: async ({ writer }) => { await writer.custom({ type: 'custom-progress', data: { status: 'processing' }, }); return { result: 'done' }; }, }); const subWorkflow = createWorkflow({ id: 'sub' }).then(subStep).commit(); const topWorkflow = createWorkflow({ id: 'top' }).then(subWorkflow).commit(); const run = await topWorkflow.createRun(); const stream = run.stream({ inputData: {} }); // Custom events from subStep now properly appear in the top-level stream for await (const event of stream) { if (event.type === 'custom-progress') { console.log(event.data); // { status: 'processing' } } } ``` ## 1.0.0-beta.15 ### Minor Changes - Introduce StorageDomain base class for composite storage support ([#11249](https://github.com/mastra-ai/mastra/pull/11249)) Storage adapters now use a domain-based architecture where each domain (memory, workflows, scores, observability, agents) extends a `StorageDomain` base class with `init()` and `dangerouslyClearAll()` methods. **Key changes:** - Add `StorageDomain` abstract base class that all domain storage classes extend - Add `InMemoryDB` class for shared state across in-memory domain implementations - All storage domains now implement `dangerouslyClearAll()` for test cleanup - Remove `operations` from public `StorageDomains` type (now internal to each adapter) - Add flexible client/config patterns - domains accept either an existing database client or config to create one internally **Why this matters:** This enables composite storage where you can use different database adapters per domain: ```typescript import { Mastra } from '@mastra/core'; import { PostgresStore } from '@mastra/pg'; import { ClickhouseStore } from '@mastra/clickhouse'; // Use Postgres for most domains but Clickhouse for observability const mastra = new Mastra({ storage: new PostgresStore({ connectionString: 'postgres://...', }), // Future: override specific domains // observability: new ClickhouseStore({ ... }).getStore('observability'), }); ``` **Standalone domain usage:** Domains can now be used independently with flexible configuration: ```typescript import { MemoryLibSQL } from '@mastra/libsql/memory'; // Option 1: Pass config to create client internally const memory = new MemoryLibSQL({ url: 'file:./local.db', }); // Option 2: Pass existing client for shared connections import { createClient } from '@libsql/client'; const client = createClient({ url: 'file:./local.db' }); const memory = new MemoryLibSQL({ client }); ``` **Breaking changes:** - `StorageDomains` type no longer includes `operations` - access via `getStore()` instead - Domain base classes now require implementing `dangerouslyClearAll()` method - Refactor storage architecture to use domain-specific stores via `getStore()` pattern ([#11361](https://github.com/mastra-ai/mastra/pull/11361)) ### Summary This release introduces a new storage architecture that replaces passthrough methods on `MastraStorage` with domain-specific storage interfaces accessed via `getStore()`. This change reduces code duplication across storage adapters and provides a cleaner, more modular API. ### Migration Guide All direct method calls on storage instances should be updated to use `getStore()`: ```typescript // Before const thread = await storage.getThreadById({ threadId }); await storage.persistWorkflowSnapshot({ workflowName, runId, snapshot }); await storage.createSpan(span); // After const memory = await storage.getStore('memory'); const thread = await memory?.getThreadById({ threadId }); const workflows = await storage.getStore('workflows'); await workflows?.persistWorkflowSnapshot({ workflowName, runId, snapshot }); const observability = await storage.getStore('observability'); await observability?.createSpan(span); ``` ### Available Domains - **`memory`**: Thread and message operations (`getThreadById`, `saveThread`, `saveMessages`, etc.) - **`workflows`**: Workflow state persistence (`persistWorkflowSnapshot`, `loadWorkflowSnapshot`, `getWorkflowRunById`, etc.) - **`scores`**: Evaluation scores (`saveScore`, `listScoresByScorerId`, etc.) - **`observability`**: Tracing and spans (`createSpan`, `updateSpan`, `getTrace`, etc.) - **`agents`**: Stored agent configurations (`createAgent`, `getAgentById`, `listAgents`, etc.) ### Breaking Changes - Passthrough methods have been removed from `MastraStorage` base class - All storage adapters now require accessing domains via `getStore()` - The `stores` property on storage instances is now the canonical way to access domain storage ### Internal Changes - Each storage adapter now initializes domain-specific stores in its constructor - Domain stores share database connections and handle their own table initialization - Add support for AI SDK v6 ToolLoopAgent in Mastra ([#11254](https://github.com/mastra-ai/mastra/pull/11254)) You can now pass an AI SDK v6 `ToolLoopAgent` directly to Mastra's agents configuration. The agent will be automatically converted to a Mastra Agent while preserving all ToolLoopAgent lifecycle hooks: - `prepareCall` - Called once at the start of generate/stream - `prepareStep` - Called before each step in the agentic loop - `stopWhen` - Custom stop conditions for the loop Example: ```typescript import { ToolLoopAgent } from 'ai'; import { Mastra } from '@mastra/core/mastra'; const toolLoopAgent = new ToolLoopAgent({ model: openai('gpt-4o'), instructions: 'You are a helpful assistant.', tools: { weather: weatherTool }, prepareStep: async ({ stepNumber }) => { if (stepNumber === 0) { return { toolChoice: 'required' }; } }, }); const mastra = new Mastra({ agents: { toolLoopAgent }, }); // Use like any other Mastra agent const agent = mastra.getAgent('toolLoopAgent'); const result = await agent.generate('What is the weather?'); ``` - Unified observability schema with entity-based span identification ([#11132](https://github.com/mastra-ai/mastra/pull/11132)) ## What changed Spans now use a unified identification model with `entityId`, `entityType`, and `entityName` instead of separate `agentId`, `toolId`, `workflowId` fields. **Before:** ```typescript // Old span structure span.agentId; // 'my-agent' span.toolId; // undefined span.workflowId; // undefined ``` **After:** ```typescript // New span structure span.entityType; // EntityType.AGENT span.entityId; // 'my-agent' span.entityName; // 'My Agent' ``` ## New `listTraces()` API Query traces with filtering, pagination, and sorting: ```typescript const { spans, pagination } = await storage.listTraces({ filters: { entityType: EntityType.AGENT, entityId: 'my-agent', userId: 'user-123', environment: 'production', status: TraceStatus.SUCCESS, startedAt: { start: new Date('2024-01-01'), end: new Date('2024-01-31') }, }, pagination: { page: 0, perPage: 50 }, orderBy: { field: 'startedAt', direction: 'DESC' }, }); ``` **Available filters:** date ranges (`startedAt`, `endedAt`), entity (`entityType`, `entityId`, `entityName`), identity (`userId`, `organizationId`), correlation IDs (`runId`, `sessionId`, `threadId`), deployment (`environment`, `source`, `serviceName`), `tags`, `metadata`, and `status`. ## New retrieval methods - `getSpan({ traceId, spanId })` - Get a single span - `getRootSpan({ traceId })` - Get the root span of a trace - `getTrace({ traceId })` - Get all spans for a trace ## Backward compatibility The legacy `getTraces()` method continues to work. When you pass `name: "agent run: my-agent"`, it automatically transforms to `entityId: "my-agent", entityType: AGENT`. ## Migration **Automatic:** SQL-based stores (PostgreSQL, LibSQL, MSSQL) automatically add new columns to existing `spans` tables on initialization. Existing data is preserved with new columns set to `NULL`. **No action required:** Your existing code continues to work. Adopt the new fields and `listTraces()` API at your convenience. ### Patch Changes - When calling `abort()` inside a `processInputStep` processor, the TripWire was being caught by the model retry logic instead of emitting a tripwire chunk to the stream. ([#11343](https://github.com/mastra-ai/mastra/pull/11343)) Before this fix, processors using `processInputStep` with abort would see errors like: ``` Error executing model gpt-4o-mini, attempt 1==== TripWire [Error]: Potentially harmful content detected ``` Now the TripWire is properly handled - it emits a tripwire chunk and signals the abort correctly, - Consolidate memory integration tests and fix working memory filtering in MessageHistory processor ([#11367](https://github.com/mastra-ai/mastra/pull/11367)) Moved `extractWorkingMemoryTags`, `removeWorkingMemoryTags`, and `extractWorkingMemoryContent` utilities from `@mastra/memory` to `@mastra/core/memory` so they can be used by the `MessageHistory` processor. Updated `MessageHistory.filterMessagesForPersistence()` to properly filter out `updateWorkingMemory` tool invocations and strip working memory tags from text content, fixing an issue where working memory tool call arguments were polluting saved message history for v5+ models. Also consolidated integration tests for agent-memory, working-memory, and pg-storage into shared test functions that can run against multiple model versions (v4, v5, v6). - Add support for AI SDK's `needsApproval` in tools. ([#11388](https://github.com/mastra-ai/mastra/pull/11388)) **AI SDK tools with static approval:** ```typescript import { tool } from 'ai'; import { z } from 'zod'; const weatherTool = tool({ description: 'Get weather information', inputSchema: z.object({ city: z.string() }), needsApproval: true, execute: async ({ city }) => { return { weather: 'sunny', temp: 72 }; }, }); ``` **AI SDK tools with dynamic approval:** ```typescript const paymentTool = tool({ description: 'Process payment', inputSchema: z.object({ amount: z.number() }), needsApproval: async ({ amount }) => amount > 1000, execute: async ({ amount }) => { return { success: true, amount }; }, }); ``` **Mastra tools continue to work with `requireApproval`:** ```typescript import { createTool } from '@mastra/core'; const deleteTool = createTool({ id: 'delete-file', description: 'Delete a file', requireApproval: true, inputSchema: z.object({ path: z.string() }), execute: async ({ path }) => { return { deleted: true }; }, }); ``` - Fix stopWhen type to accept AI SDK v6 StopCondition functions like `stepCountIs()` ([#11402](https://github.com/mastra-ai/mastra/pull/11402)) - Fix missing `title` field in Convex threads table schema ([#11356](https://github.com/mastra-ai/mastra/pull/11356)) The Convex schema was hardcoded and out of sync with the core `TABLE_SCHEMAS`, causing errors when creating threads: ``` Error: Failed to insert or update a document in table "mastra_threads" because it does not match the schema: Object contains extra field `title` that is not in the validator. ``` Now the Convex schema dynamically builds from `TABLE_SCHEMAS` via a new `@mastra/core/storage/constants` export path that doesn't pull in Node.js dependencies (safe for Convex's sandboxed schema evaluation). ```typescript // Users can now import schema tables without Node.js dependency issues import { mastraThreadsTable, mastraMessagesTable } from '@mastra/convex/schema'; export default defineSchema({ mastra_threads: mastraThreadsTable, mastra_messages: mastraMessagesTable, }); ``` Fixes #11319 - Added support for AI SDK v6 embedding models (specification version v3) in memory and vector modules. Fixed TypeScript error where `ModelRouterEmbeddingModel` was trying to implement a union type instead of `EmbeddingModelV2` directly. ([#11362](https://github.com/mastra-ai/mastra/pull/11362)) - fix: support gs:// and s3:// cloud storage URLs in attachmentsToParts ([#11398](https://github.com/mastra-ai/mastra/pull/11398)) - Add validation to detect when a function is passed as a tool instead of a tool object. Previously, passing a tool factory function (e.g., `tools: { myTool }` instead of `tools: { myTool: myTool() }`) would silently fail - the LLM would request tool calls but nothing would execute. Now throws a clear error with guidance on how to fix it. ([#11288](https://github.com/mastra-ai/mastra/pull/11288)) - Fix reasoning providerMetadata leaking into text parts when using memory with OpenAI reasoning models. The runState.providerOptions is now cleared after reasoning-end to prevent text parts from inheriting the reasoning's itemId. ([#11380](https://github.com/mastra-ai/mastra/pull/11380)) - Upgrade AI SDK v6 from beta to stable (6.0.1) and fix finishReason breaking change. ([#11351](https://github.com/mastra-ai/mastra/pull/11351)) AI SDK v6 stable changed finishReason from a string to an object with `unified` and `raw` properties. Added `normalizeFinishReason()` helper to handle both v5 (string) and v6 (object) formats at the stream transform layer - Improve autoResumeSuspendedTools instruction for tool approval ([#11338](https://github.com/mastra-ai/mastra/pull/11338)) - Add debugger-like click-through UI to workflow graph ([#11350](https://github.com/mastra-ai/mastra/pull/11350)) - Add `perStep` option to workflow run methods, allowing a workflow to run just a step instead of all the workflow steps ([#11276](https://github.com/mastra-ai/mastra/pull/11276)) - Fix workflow throwing error when using .map after .foreach ([#11352](https://github.com/mastra-ai/mastra/pull/11352)) - Bump @ai-sdk/openai from 3.0.0-beta.102 to 3.0.1 ([#11377](https://github.com/mastra-ai/mastra/pull/11377)) ## 1.0.0-beta.14 ### Minor Changes - Add support for AI SDK v6 (LanguageModelV3) ([#11191](https://github.com/mastra-ai/mastra/pull/11191)) Agents can now use `LanguageModelV3` models from AI SDK v6 beta providers like `@ai-sdk/openai@^3.0.0-beta`. **New features:** - Usage normalization: V3's nested usage format is normalized to Mastra's flat format with `reasoningTokens`, `cachedInputTokens`, and raw data preserved in a `raw` field **Backward compatible:** All existing V1 and V2 models continue to work unchanged. ### Patch Changes - Fix model-level and runtime header support for LLM calls ([#11275](https://github.com/mastra-ai/mastra/pull/11275)) This fixes a bug where custom headers configured on models (like `anthropic-beta`) were not being passed through to the underlying AI SDK calls. The fix properly handles headers from multiple sources with correct priority: **Header Priority (low to high):** 1. Model config headers - Headers set in model configuration 2. ModelSettings headers - Runtime headers that override model config 3. Provider-level headers - Headers baked into AI SDK providers (not overridden) **Examples that now work:** ```typescript // Model config headers new Agent({ model: { id: 'anthropic/claude-4-5-sonnet', headers: { 'anthropic-beta': 'context-1m-2025-08-07' }, }, }); // Runtime headers override config agent.generate('...', { modelSettings: { headers: { 'x-custom': 'runtime-value' } }, }); // Provider-level headers preserved const openai = createOpenAI({ headers: { 'openai-organization': 'org-123' } }); new Agent({ model: openai('gpt-4o-mini') }); ``` - Fixed AbortSignal not propagating from parent workflows to nested sub-workflows in the evented workflow engine. ([#11142](https://github.com/mastra-ai/mastra/pull/11142)) Previously, canceling a parent workflow did not stop nested sub-workflows, causing them to continue running and consuming resources after the parent was canceled. Now, when you cancel a parent workflow, all nested sub-workflows are automatically canceled as well, ensuring clean termination of the entire workflow tree. **Example:** ```typescript const parentWorkflow = createWorkflow({ id: 'parent-workflow' }).then(someStep).then(nestedChildWorkflow).commit(); const run = await parentWorkflow.createRun(); const resultPromise = run.start({ inputData: { value: 5 } }); // Cancel the parent workflow - nested workflows will also be canceled await run.cancel(); // or use: run.abortController.abort(); const result = await resultPromise; // result.status === 'canceled' // All nested child workflows are also canceled ``` Related to #11063 - Fix empty overrideScorers causing error instead of skipping scoring ([#11257](https://github.com/mastra-ai/mastra/pull/11257)) When `overrideScorers` was passed as an empty object `{}`, the agent would throw a "No scorers found" error. Now an empty object explicitly skips scoring, while `undefined` continues to use default scorers. - feat: Add field filtering and nested workflow control to workflow execution result endpoint ([#11246](https://github.com/mastra-ai/mastra/pull/11246)) Adds two optional query parameters to `/api/workflows/:workflowId/runs/:runId/execution-result` endpoint: - `fields`: Request only specific fields (e.g., `status`, `result`, `error`) - `withNestedWorkflows`: Control whether to fetch nested workflow data This significantly reduces response payload size and improves response times for large workflows. ## Server Endpoint Usage ```http # Get only status (minimal payload - fastest) GET /api/workflows/:workflowId/runs/:runId/execution-result?fields=status # Get status and result GET /api/workflows/:workflowId/runs/:runId/execution-result?fields=status,result # Get all fields but without nested workflow data (faster) GET /api/workflows/:workflowId/runs/:runId/execution-result?withNestedWorkflows=false # Get only specific fields without nested workflow data GET /api/workflows/:workflowId/runs/:runId/execution-result?fields=status,steps&withNestedWorkflows=false # Get full data (default behavior) GET /api/workflows/:workflowId/runs/:runId/execution-result ``` ## Client SDK Usage ```typescript import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); const workflow = client.getWorkflow('myWorkflow'); // Get only status (minimal payload - fastest) const statusOnly = await workflow.runExecutionResult(runId, { fields: ['status'], }); console.log(statusOnly.status); // 'success' | 'failed' | 'running' | etc. // Get status and result const statusAndResult = await workflow.runExecutionResult(runId, { fields: ['status', 'result'], }); // Get all fields but without nested workflow data (faster) const resultWithoutNested = await workflow.runExecutionResult(runId, { withNestedWorkflows: false, }); // Get specific fields without nested workflow data const optimized = await workflow.runExecutionResult(runId, { fields: ['status', 'steps'], withNestedWorkflows: false, }); // Get full execution result (default behavior) const fullResult = await workflow.runExecutionResult(runId); ``` ## Core API Changes The `Workflow.getWorkflowRunExecutionResult` method now accepts an options object: ```typescript await workflow.getWorkflowRunExecutionResult(runId, { withNestedWorkflows: false, // default: true, set to false to skip nested workflow data fields: ['status', 'result'], // optional field filtering }); ``` ## Inngest Compatibility The `@mastra/inngest` package has been updated to use the new options object API. This is a non-breaking internal change - no action required from inngest workflow users. ## Performance Impact For workflows with large step outputs: - Requesting only `status`: ~99% reduction in payload size - Requesting `status,result,error`: ~95% reduction in payload size - Using `withNestedWorkflows=false`: Avoids expensive nested workflow data fetching - Combining both: Maximum performance optimization - Removed a debug log that printed large Zod schemas, resulting in cleaner console output when using agents with memory enabled. ([#11279](https://github.com/mastra-ai/mastra/pull/11279)) - Set `externals: true` as the default for `mastra build` and cloud-deployer to reduce bundle issues with native dependencies. ([`0dbf199`](https://github.com/mastra-ai/mastra/commit/0dbf199110f22192ce5c95b1c8148d4872b4d119)) **Note:** If you previously relied on the default bundling behavior (all dependencies bundled), you can explicitly set `externals: false` in your bundler configuration. - Fix delayed promises rejecting when stream suspends on tool-call-approval ([#11278](https://github.com/mastra-ai/mastra/pull/11278)) When a stream ends in suspended state (e.g., requiring tool approval), the delayed promises like `toolResults`, `toolCalls`, `text`, etc. now resolve with partial results instead of rejecting with an error. This allows consumers to access data that was produced before the suspension. Also improves generic type inference for `LLMStepResult` and related types throughout the streaming infrastructure. ## 1.0.0-beta.13 ### Patch Changes - Add `onFinish` and `onError` lifecycle callbacks to workflow options ([#11200](https://github.com/mastra-ai/mastra/pull/11200)) Workflows now support lifecycle callbacks for server-side handling of workflow completion and errors: - `onFinish`: Called when workflow completes with any status (success, failed, suspended, tripwire) - `onError`: Called only when workflow fails (failed or tripwire status) ```typescript const workflow = createWorkflow({ id: 'my-workflow', inputSchema: z.object({ ... }), outputSchema: z.object({ ... }), options: { onFinish: async (result) => { // Handle any workflow completion await updateJobStatus(result.status); }, onError: async (errorInfo) => { // Handle workflow failures await logError(errorInfo.error); }, }, }); ``` Both callbacks support sync and async functions. Callback errors are caught and logged, not propagated to the workflow result. ## 1.0.0-beta.12 ### Patch Changes - Remove redundant toolCalls from network agent finalResult ([#11189](https://github.com/mastra-ai/mastra/pull/11189)) The network agent's `finalResult` was storing `toolCalls` separately even though all tool call information is already present in the `messages` array (as `tool-call` and `tool-result` type messages). This caused significant token waste since the routing agent reads this data from memory on every iteration. **Before:** `finalResult: { text, toolCalls, messages }` **After:** `finalResult: { text, messages }` +**Migration:** If you were accessing `finalResult.toolCalls`, retrieve tool calls from `finalResult.messages` by filtering for messages with `type: 'tool-call'`. Updated `@mastra/react` to extract tool calls directly from the `messages` array instead of the removed `toolCalls` field when resolving initial messages from memory. Fixes #11059 - Embed AI types to fix peerdeps mismatches ([`9650cce`](https://github.com/mastra-ai/mastra/commit/9650cce52a1d917ff9114653398e2a0f5c3ba808)) - Fix invalid state: Controller is already closed ([`932d63d`](https://github.com/mastra-ai/mastra/commit/932d63dd51be9c8bf1e00e3671fe65606c6fb9cd)) Fixes #11005 - Fix HITL (Human-In-The-Loop) tool execution bug when mixing tools with and without execute functions. ([#11178](https://github.com/mastra-ai/mastra/pull/11178)) When an agent called multiple tools simultaneously where some had `execute` functions and others didn't (HITL tools expecting `addToolResult` from the frontend), the HITL tools would incorrectly receive `result: undefined` and be marked as "output-available" instead of "input-available". This caused the agent to continue instead of pausing for user input. - Add resourceId to workflow routes ([#11166](https://github.com/mastra-ai/mastra/pull/11166)) - Auto resume suspended tools if `autoResumeSuspendedTools: true` ([#11157](https://github.com/mastra-ai/mastra/pull/11157)) The flag can be added to `defaultAgentOptions` when creating the agent or to options in `agent.stream` or `agent.generate` ```typescript const agent = new Agent({ //...agent information, defaultAgentOptions: { autoResumeSuspendedTools: true, }, }); ``` - Preserve error details when thrown from workflow steps ([#10992](https://github.com/mastra-ai/mastra/pull/10992)) - Errors thrown in workflow steps now preserve full error details including `cause` chain and custom properties - Added `SerializedError` type with proper cause chain support - Added `SerializedStepResult` and `SerializedStepFailure` types for handling errors loaded from storage - Enhanced `addErrorToJSON` to recursively serialize error cause chains with max depth protection - Added `hydrateSerializedStepErrors` to convert serialized errors back to Error instances - Fixed Inngest workflow error handling to extract original error from `NonRetriableError.cause` - Move `@ai-sdk/azure` to devDependencies ([#10218](https://github.com/mastra-ai/mastra/pull/10218)) - Refactor internal event system from Emitter to PubSub abstraction for workflow event handling. This change replaces the EventEmitter-based event system with a pluggable PubSub interface, enabling support for distributed workflow execution backends like Inngest. Adds `close()` method to PubSub implementations for proper cleanup. ([#11052](https://github.com/mastra-ai/mastra/pull/11052)) - Add `startAsync()` method and fix Inngest duplicate workflow execution bug ([#11093](https://github.com/mastra-ai/mastra/pull/11093)) **New Feature: `startAsync()` for fire-and-forget workflow execution** - Add `Run.startAsync()` to base workflow class - starts workflow in background and returns `{ runId }` immediately - Add `EventedRun.startAsync()` - publishes workflow start event without subscribing for completion - Add `InngestRun.startAsync()` - sends Inngest event without polling for result **Bug Fix: Prevent duplicate Inngest workflow executions** - Fix `getRuns()` to properly handle rate limits (429), empty responses, and JSON parse errors with retry logic and exponential backoff - Fix `getRunOutput()` to throw `NonRetriableError` when polling fails, preventing Inngest from retrying the parent function and re-triggering the workflow - Add timeout to `getRunOutput()` polling (default 5 minutes) with `NonRetriableError` on timeout This fixes a production issue where polling failures after successful workflow completion caused Inngest to retry the parent function, which fired a new workflow event and resulted in duplicate executions (e.g., duplicate Slack messages). - Preserve error details when thrown from workflow steps ([#10992](https://github.com/mastra-ai/mastra/pull/10992)) Workflow errors now retain custom properties like `statusCode`, `responseHeaders`, and `cause` chains. This enables error-specific recovery logic in your applications. **Before:** ```typescript const result = await workflow.execute({ input }); if (result.status === 'failed') { // Custom error properties were lost console.log(result.error); // "Step execution failed" (just a string) } ``` **After:** ```typescript const result = await workflow.execute({ input }); if (result.status === 'failed') { // Custom properties are preserved console.log(result.error.message); // "Step execution failed" console.log(result.error.statusCode); // 429 console.log(result.error.cause?.name); // "RateLimitError" } ``` **Type change:** `WorkflowState.error` and `WorkflowRunState.error` types changed from `string | Error` to `SerializedError`. Other changes: - Added `UpdateWorkflowStateOptions` type for workflow state updates - Fix Zod 4 compatibility issue with structuredOutput in agent.generate() ([#11133](https://github.com/mastra-ai/mastra/pull/11133)) Users with Zod 4 installed would see `TypeError: undefined is not an object (evaluating 'def.valueType._zod')` when using `structuredOutput` with agent.generate(). This happened because ProcessorStepSchema contains `z.custom()` fields that hold user-provided Zod schemas, and the workflow validation was trying to deeply validate these schemas causing version conflicts. The fix disables input validation for processor workflows since `z.custom()` fields are meant to pass through arbitrary types without deep validation. - Truncate map config when too long ([#11175](https://github.com/mastra-ai/mastra/pull/11175)) - Add helpful JSDoc comments to `BundlerConfig` properties (used with `bundler` option) ([#10218](https://github.com/mastra-ai/mastra/pull/10218)) - Fixes .network() method ignores MASTRA_RESOURCE_ID_KEY from requestContext ([`4524734`](https://github.com/mastra-ai/mastra/commit/45247343e384717a7c8404296275c56201d6470f)) - fix: make getSqlType consistent across storage adapters ([#11112](https://github.com/mastra-ai/mastra/pull/11112)) - PostgreSQL: use `getSqlType()` in `createTable` instead of `toUpperCase()` - LibSQL: use `getSqlType()` in `createTable`, return `JSONB` for jsonb type (matches SQLite 3.45+ support) - ClickHouse: use `getSqlType()` in `createTable` instead of `COLUMN_TYPES` constant, add missing types (uuid, float, boolean) - Remove unused `getSqlType()` and `getDefaultValue()` from `MastraStorage` base class (all stores use `StoreOperations` versions) - Fix workflow cancel not updating status when workflow is suspended ([#11139](https://github.com/mastra-ai/mastra/pull/11139)) - `Run.cancel()` now updates workflow status to 'canceled' in storage, resolving the issue where suspended workflows remained in 'suspended' status after cancellation - Cancellation status is immediately persisted and reflected to observers - What changed: ([#10998](https://github.com/mastra-ai/mastra/pull/10998)) Support for sequential tool execution was added. Tool call concurrency is now set conditionally, defaulting to 1 when sequential execution is needed (to avoid race conditions that interfere with human-in-the-loop approval during the workflow) rather than the default of 10 when concurrency is acceptable. How it was changed: A `sequentialExecutionRequired` constant was set to a boolean depending on whether any of the tools involved in a returned agentic execution workflow would require approval. If any tool has a 'suspendSchema' property (used for conditionally suspending execution and waiting for human input), or if they have their `requireApproval` property set to `true`, then the concurrency property used in the toolCallStep is set to 1, causing sequential execution. The old default of 10 remains otherwise. - Fixed duplicate assistant messages appearing when using `useChat` with memory enabled. ([#11195](https://github.com/mastra-ai/mastra/pull/11195)) **What was happening:** When using `useChat` with `chatRoute` and memory, assistant messages were being duplicated in storage after multiple conversation turns. This occurred because the backend-generated message ID wasn't being sent back to `useChat`, causing ID mismatches during deduplication. **What changed:** - The backend now sends the assistant message ID in the stream's start event, so `useChat` uses the same ID as storage - Custom `data-*` parts (from `writer.custom()`) are now preserved when messages contain V5 tool parts Fixes #11091 - Updated dependencies [[`9650cce`](https://github.com/mastra-ai/mastra/commit/9650cce52a1d917ff9114653398e2a0f5c3ba808), [`5a632bd`](https://github.com/mastra-ai/mastra/commit/5a632bdf7b78953b664f5e038e98d4ba5f971e47)]: - @mastra/schema-compat@1.0.0-beta.3 - @mastra/observability@1.0.0-beta.5 ## 1.0.0-beta.11 ### Minor Changes - Respect structured outputs for v2 models so tool schemas aren’t stripped ([#11038](https://github.com/mastra-ai/mastra/pull/11038)) ### Patch Changes - Fix type safety for message ordering - restrict `orderBy` to only accept `'createdAt'` field ([#11069](https://github.com/mastra-ai/mastra/pull/11069)) Messages don't have an `updatedAt` field, but the previous type allowed ordering by it, which would return empty results. This change adds compile-time type safety by making `StorageOrderBy` generic and restricting `StorageListMessagesInput.orderBy` to only accept `'createdAt'`. The API validation schemas have also been updated to reject invalid orderBy values at runtime. - Loosen tools types in processInputStep / prepareStep. ([#11071](https://github.com/mastra-ai/mastra/pull/11071)) - Added the ability to provide a base path for Mastra Studio. ([#10441](https://github.com/mastra-ai/mastra/pull/10441)) ```ts import { Mastra } from '@mastra/core'; export const mastra = new Mastra({ server: { studioBase: '/my-mastra-studio', }, }); ``` This will make Mastra Studio available at `http://localhost:4111/my-mastra-studio`. - Expand `processInputStep` processor method and integrate `prepareStep` as a processor ([#10774](https://github.com/mastra-ai/mastra/pull/10774)) **New Features:** - `prepareStep` callback now runs through the standard `processInputStep` pipeline - Processors can now modify per-step: `model`, `tools`, `toolChoice`, `activeTools`, `messages`, `systemMessages`, `providerOptions`, `modelSettings`, and `structuredOutput` - Processor chaining: each processor receives accumulated state from previous processors - System messages are isolated per-step (reset at start of each step) **Breaking Change:** - `prepareStep` messages format changed from AI SDK v5 model messages to `MastraDBMessage` format - Migration: Use `messageList.get.all.aiV5.model()` if you need the old format - Multiple Processor improvements including: ([#10947](https://github.com/mastra-ai/mastra/pull/10947)) - Workflows can now return tripwires, they bubble up from agents that return tripwires in a step - You can write processors as workflows using the existing Workflow primitive, every processor flow is now a workflow. - tripwires that you throw can now return additional information including ability to retry the step - New processor method `processOutputStep` added which runs after every step. **What's new:** **1. Retry mechanism with LLM feedback** - Processors can now request retries with feedback that gets sent back to the LLM: ```typescript processOutputStep: async ({ text, abort, retryCount }) => { if (isLowQuality(text)) { abort('Response quality too low', { retry: true, metadata: { score: 0.6 } }); } return []; }; ``` Configure with `maxProcessorRetries` (default: 3). Rejected steps are preserved in `result.steps[n].tripwire`. Retries are only available in `processOutputStep` and `processInputStep`. It will replay the step with additional context added. **2. Workflow orchestration for processors** - Processors can now be composed using workflow primitives: ```typescript import { createStep, createWorkflow } from '@mastra/core/workflows'; import { ProcessorStepSchema, } from '@mastra/core/processors'; const moderationWorkflow = createWorkflow({ id: 'moderation', inputSchema: ProcessorStepSchema, outputSchema: ProcessorStepSchema }) .then(createStep(new lengthValidator({...}))) .parallel([createStep(new piiDetector({...}), createStep(new toxicityChecker({...}))]) .commit(); const agent = new Agent({ inputProcessors: [moderationWorkflow] }); ``` Every processor array that gets passed to an agent gets added as a workflow image **3. Extended tripwire API** - `abort()` now accepts options for retry control and typed metadata: ```typescript abort('reason', { retry: true, metadata: { score: 0.8, category: 'quality' } }); ``` **4. New `processOutputStep` method** - Per-step output processing with access to step number, finish reason, tool calls, and retry count. **5. Workflow tripwire status** - Workflows now have a `'tripwire'` status distinct from `'failed'`, properly bubbling up processor rejections. ## 1.0.0-beta.10 ### Patch Changes - Add support for typed structured output in agent workflow steps ([#11014](https://github.com/mastra-ai/mastra/pull/11014)) When wrapping an agent with `createStep()` and providing a `structuredOutput.schema`, the step's `outputSchema` is now correctly inferred from the provided schema instead of defaulting to `{ text: string }`. This enables type-safe chaining of agent steps with structured output to subsequent steps: ```typescript const articleSchema = z.object({ title: z.string(), summary: z.string(), tags: z.array(z.string()), }); // Agent step with structured output - outputSchema is now articleSchema const agentStep = createStep(agent, { structuredOutput: { schema: articleSchema }, }); // Next step can receive the structured output directly const processStep = createStep({ id: 'process', inputSchema: articleSchema, // Matches agent's outputSchema outputSchema: z.object({ tagCount: z.number() }), execute: async ({ inputData }) => ({ tagCount: inputData.tags.length, // Fully typed! }), }); workflow.then(agentStep).then(processStep).commit(); ``` When `structuredOutput` is not provided, the agent step continues to use the default `{ text: string }` output schema. - Fixed a bug where multiple tools streaming output simultaneously could fail with "WritableStreamDefaultWriter is locked" errors. Tool streaming now works reliably during concurrent tool executions. ([#10830](https://github.com/mastra-ai/mastra/pull/10830)) - Add delete workflow run API ([#10991](https://github.com/mastra-ai/mastra/pull/10991)) ```typescript await workflow.deleteWorkflowRunById(runId); ``` - Fixed CachedToken tracking in all Observability Exporters. Also fixed TimeToFirstToken in Langfuse, Braintrust, PostHog exporters. Fixed trace formatting in Posthog Exporter. ([#11029](https://github.com/mastra-ai/mastra/pull/11029)) - fix: persist data-\* chunks from writer.custom() to memory storage ([#10884](https://github.com/mastra-ai/mastra/pull/10884)) - Add persistence for custom data chunks (`data-*` parts) emitted via `writer.custom()` in tools - Data chunks are now saved to message storage so they survive page refreshes - Update `@assistant-ui/react` to v0.11.47 with native `DataMessagePart` support - Convert `data-*` parts to `DataMessagePart` format (`{ type: 'data', name: string, data: T }`) - Update related `@assistant-ui/*` packages for compatibility - Fixed double validation bug that prevented Zod transforms from working correctly in tool schemas. ([#11025](https://github.com/mastra-ai/mastra/pull/11025)) When tools with Zod `.transform()` or `.pipe()` in their `outputSchema` were executed through the Agent pipeline, validation was happening twice - once in Tool.execute() (correct) and again in CoreToolBuilder (incorrect). The second validation received already-transformed data but expected pre-transform data, causing validation errors. This fix enables proper use of Zod transforms in both `inputSchema` (for normalizing/cleaning input data) and `outputSchema` (for transforming output data to be LLM-friendly). - Updated dependencies [[`5d7000f`](https://github.com/mastra-ai/mastra/commit/5d7000f757cd65ea9dc5b05e662fd83dfd44e932)]: - @mastra/observability@1.0.0-beta.4 ## 1.0.0-beta.9 ### Minor Changes - Add stored agents support ([#10953](https://github.com/mastra-ai/mastra/pull/10953)) Agents can now be stored in the database and loaded at runtime. This lets you persist agent configurations and dynamically create executable Agent instances from storage. ```typescript import { Mastra } from '@mastra/core'; import { LibSQLStore } from '@mastra/libsql'; const mastra = new Mastra({ storage: new LibSQLStore({ url: ':memory:' }), tools: { myTool }, scorers: { myScorer }, }); // Create agent in storage via API or directly await mastra.getStorage().createAgent({ agent: { id: 'my-agent', name: 'My Agent', instructions: 'You are helpful', model: { provider: 'openai', name: 'gpt-4' }, tools: { myTool: {} }, scorers: { myScorer: { sampling: { type: 'ratio', rate: 0.5 } } }, }, }); // Load and use the agent const agent = await mastra.getStoredAgentById('my-agent'); const response = await agent.generate({ messages: 'Hello!' }); // List all stored agents with pagination const { agents, total, hasMore } = await mastra.listStoredAgents({ page: 0, perPage: 10, }); ``` Also adds a memory registry to Mastra so stored agents can reference memory instances by key. ### Patch Changes - Add agentId and agentName attributes to MODEL_GENERATION spans. This allows users to correlate gen_ai.usage metrics with specific agents when analyzing LLM operation spans. The attributes are exported as gen_ai.agent.id and gen_ai.agent.name in the OtelExporter. ([#10984](https://github.com/mastra-ai/mastra/pull/10984)) - Fix JSON parsing errors when LLMs output unescaped newlines in structured output strings ([#10965](https://github.com/mastra-ai/mastra/pull/10965)) Some LLMs (particularly when not using native JSON mode) output actual newline characters inside JSON string values instead of properly escaped `\n` sequences. This breaks JSON parsing and causes structured output to fail. This change adds preprocessing to escape unescaped control characters (`\n`, `\r`, `\t`) within JSON string values before parsing, making structured output more robust across different LLM providers. - Fix toolCallId propagation in agent network tool execution. The toolCallId property was undefined at runtime despite being required by TypeScript type definitions in AgentToolExecutionContext. Now properly passes the toolCallId through to the tool's context during network tool execution. ([#10951](https://github.com/mastra-ai/mastra/pull/10951)) - Exports `convertFullStreamChunkToMastra` from the stream module for AI SDK stream chunk transformations. ([#10911](https://github.com/mastra-ai/mastra/pull/10911)) ## 1.0.0-beta.8 ### Patch Changes - Fix saveScore not persisting ID correctly, breaking getScoreById retrieval ([#10915](https://github.com/mastra-ai/mastra/pull/10915)) **What Changed** - saveScore now correctly returns scores that can be retrieved with getScoreById - Validation errors now include contextual information (scorer, entity, trace details) for easier debugging **Impact** Previously, calling getScoreById after saveScore would return null because the generated ID wasn't persisted to the database. This is now fixed across all store implementations, ensuring consistent behavior and data integrity. - `setState` is now async ([#10944](https://github.com/mastra-ai/mastra/pull/10944)) - `setState` must now be awaited: `await setState({ key: value })` - State updates are merged automatically—no need to spread the previous state - State data is validated against the step's `stateSchema` when `validateInputs` is enabled (default: `true`) - Add human-in-the-loop support for workflows used in agent ([#10871](https://github.com/mastra-ai/mastra/pull/10871)) ## 1.0.0-beta.7 ### Minor Changes - Add `disableInit` option to all storage adapters ([#10851](https://github.com/mastra-ai/mastra/pull/10851)) Adds a new `disableInit` config option to all storage providers that allows users to disable automatic table creation/migrations at runtime. This is useful for CI/CD pipelines where you want to run migrations during deployment with elevated credentials, then run the application with `disableInit: true` so it doesn't attempt schema changes at runtime. ```typescript // CI/CD script - run migrations const storage = new PostgresStore({ connectionString: DATABASE_URL, id: 'pg-storage', }); await storage.init(); // Runtime - skip auto-init const storage = new PostgresStore({ connectionString: DATABASE_URL, id: 'pg-storage', disableInit: true, }); ``` ### Patch Changes - Add time-to-first-token (TTFT) support for Langfuse integration ([#10781](https://github.com/mastra-ai/mastra/pull/10781)) Adds `completionStartTime` to model generation spans, which Langfuse uses to calculate TTFT metrics. The timestamp is automatically captured when the first content chunk arrives during streaming. ```typescript // completionStartTime is now automatically captured and sent to Langfuse // enabling TTFT metrics in your Langfuse dashboard const result = await agent.stream('Hello'); ``` - Updated OtelExporters, Bridge, and Arize packages to better implement GenAI v1.38.0 Otel Semantic Conventions. See: ([#10591](https://github.com/mastra-ai/mastra/pull/10591)) https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/gen-ai/README.md - Standardize error IDs across all storage and vector stores using centralized helper functions (`createStorageErrorId` and `createVectorErrorId`). This ensures consistent error ID patterns (`MASTRA_STORAGE_{STORE}_{OPERATION}_{STATUS}` and `MASTRA_VECTOR_{STORE}_{OPERATION}_{STATUS}`) across the codebase for better error tracking and debugging. ([#10913](https://github.com/mastra-ai/mastra/pull/10913)) - fix: generate unique text IDs for Anthropic/Google providers ([#10740](https://github.com/mastra-ai/mastra/pull/10740)) Workaround for duplicate text-start/text-end IDs in multi-step agentic flows. The `@ai-sdk/anthropic` and `@ai-sdk/google` providers use numeric indices ("0", "1", etc.) for text block IDs that reset for each LLM call. This caused duplicate IDs when an agent does TEXT → TOOL → TEXT, breaking message ordering and storage. The fix replaces numeric IDs with UUIDs, maintaining a map per step so text-start, text-delta, and text-end chunks for the same block share the same UUID. OpenAI's UUIDs pass through unchanged. Related: #9909 - Fix sub-agent requestContext propagation in listAgentTools ([#10844](https://github.com/mastra-ai/mastra/pull/10844)) Sub-agents with dynamic model configurations were broken because `requestContext` was not being passed to `getModel()` when creating agent tools. This caused sub-agents using function-based model configurations to receive an empty context instead of the parent's context. No code changes required for consumers - this fix restores expected behavior for dynamic model configurations in sub-agents. - Fix ToolStream type error when piping streams with different types ([#10845](https://github.com/mastra-ai/mastra/pull/10845)) Changes `ToolStream` to extend `WritableStream` instead of `WritableStream`. This fixes the TypeScript error when piping `objectStream` or `fullStream` to `writer` in workflow steps. Before: ```typescript // TypeError: ToolStream is not assignable to WritableStream> await response.objectStream.pipeTo(writer); ``` After: ```typescript // Works without type errors await response.objectStream.pipeTo(writer); ``` - feat: add native Perplexity provider support ([#10885](https://github.com/mastra-ai/mastra/pull/10885)) - When sending the first message to a new thread with PostgresStore, users would get a "Thread not found" error. This happened because the thread was created in memory but not persisted to the database before the MessageHistory output processor tried to save messages. ([#10881](https://github.com/mastra-ai/mastra/pull/10881)) **Before:** ```ts threadObject = await memory.createThread({ // ... saveThread: false, // thread not in DB yet }); // Later: MessageHistory calls saveMessages() -> PostgresStore throws "Thread not found" ``` **After:** ```ts threadObject = await memory.createThread({ // ... saveThread: true, // thread persisted immediately }); // MessageHistory can now save messages without error ``` - Emit error chunk and call onError when agent workflow step fails ([#10907](https://github.com/mastra-ai/mastra/pull/10907)) When a workflow step fails (e.g., tool not found), the error is now properly emitted as an error chunk to the stream and the onError callback is called. This fixes the issue where agent.generate() would throw "promise 'text' was not resolved or rejected" instead of the actual error message. - fix(core): use agent description when converting agent to tool ([#10879](https://github.com/mastra-ai/mastra/pull/10879)) - Adds native @ai-sdk/deepseek provider support instead of using the OpenAI-compatible fallback. ([#10822](https://github.com/mastra-ai/mastra/pull/10822)) ```typescript const agent = new Agent({ model: 'deepseek/deepseek-reasoner', }); // With provider options for reasoning const response = await agent.generate('Solve this problem', { providerOptions: { deepseek: { thinking: { type: 'enabled' }, }, }, }); ``` Also updates the doc generation scripts so DeepSeek provider options show up in the generated docs. - Return state too if `includeState: true` is in `outputOptions` and workflow run is not successful ([#10806](https://github.com/mastra-ai/mastra/pull/10806)) - feat: Add partial response support for agent and workflow list endpoints ([#10886](https://github.com/mastra-ai/mastra/pull/10886)) Add optional `partial` query parameter to `/api/agents` and `/api/workflows` endpoints to return minimal data without schemas, reducing payload size for list views: - When `partial=true`: tool schemas (inputSchema, outputSchema) are omitted - When `partial=true`: workflow steps are replaced with stepCount integer - When `partial=true`: workflow root schemas (inputSchema, outputSchema) are omitted - Maintains backward compatibility when partial parameter is not provided ## Server Endpoint Usage ```http # Get partial agent data (no tool schemas) GET /api/agents?partial=true # Get full agent data (default behavior) GET /api/agents # Get partial workflow data (stepCount instead of steps, no schemas) GET /api/workflows?partial=true # Get full workflow data (default behavior) GET /api/workflows ``` ## Client SDK Usage ```typescript import { MastraClient } from '@mastra/client-js'; const client = new MastraClient({ baseUrl: 'http://localhost:4111' }); // Get partial agent list (smaller payload) const partialAgents = await client.listAgents({ partial: true }); // Get full agent list with tool schemas const fullAgents = await client.listAgents(); // Get partial workflow list (smaller payload) const partialWorkflows = await client.listWorkflows({ partial: true }); // Get full workflow list with steps and schemas const fullWorkflows = await client.listWorkflows(); ``` - Fix processInputStep so it runs correctly. ([#10909](https://github.com/mastra-ai/mastra/pull/10909)) - Updated dependencies [[`6c59a40`](https://github.com/mastra-ai/mastra/commit/6c59a40e0ad160467bd13d63a8a287028d75b02d), [`3076c67`](https://github.com/mastra-ai/mastra/commit/3076c6778b18988ae7d5c4c5c466366974b2d63f), [`0bada2f`](https://github.com/mastra-ai/mastra/commit/0bada2f2c1234932cf30c1c47a719ffb64b801c5), [`cc60ff6`](https://github.com/mastra-ai/mastra/commit/cc60ff616541a3b0fb531a7e469bf9ae7bb90528)]: - @mastra/observability@1.0.0-beta.3 ## 1.0.0-beta.6 ### Major Changes - Changed `.branch()` result schema to make all branch output fields optional. ([#10693](https://github.com/mastra-ai/mastra/pull/10693)) **Breaking change**: Branch outputs are now optional since only one branch executes at runtime. Update your workflow schemas to handle optional branch results. **Before:** ```typescript const workflow = createWorkflow({...}) .branch([ [condition1, stepA], // outputSchema: { result: z.string() } [condition2, stepB], // outputSchema: { data: z.number() } ]) .map({ finalResult: { step: stepA, path: 'result' } // Expected non-optional }); ``` **After:** ```typescript const workflow = createWorkflow({...}) .branch([ [condition1, stepA], [condition2, stepB], ]) .map({ finalResult: { step: stepA, path: 'result' // Now optional - provide fallback } }); ``` **Why**: Branch conditionals execute only one path, so non-executed branches don't produce outputs. The type system now correctly reflects this runtime behavior. Related issue: https://github.com/mastra-ai/mastra/issues/10642 ### Minor Changes - Memory system now uses processors. Memory processors (`MessageHistory`, `SemanticRecall`, `WorkingMemory`) are now exported from `@mastra/memory/processors` and automatically added to the agent pipeline based on your memory config. Core processors (`ToolCallFilter`, `TokenLimiter`) remain in `@mastra/core/processors`. ([#9254](https://github.com/mastra-ai/mastra/pull/9254)) - Add reserved keys in RequestContext for secure resourceId/threadId setting from middleware ([#10657](https://github.com/mastra-ai/mastra/pull/10657)) This allows middleware to securely set `resourceId` and `threadId` via reserved keys in RequestContext (`MASTRA_RESOURCE_ID_KEY` and `MASTRA_THREAD_ID_KEY`), which take precedence over client-provided values for security. - feat(workflows): add suspendData parameter to step execute function ([#10734](https://github.com/mastra-ai/mastra/pull/10734)) Adds a new `suspendData` parameter to workflow step execute functions that provides access to the data originally passed to `suspend()` when the step was suspended. This enables steps to access context about why they were suspended when they are later resumed. **New Features:** - `suspendData` parameter automatically populated in step execute function when resuming - Type-safe access to suspend data matching the step's `suspendSchema` - Backward compatible - existing workflows continue to work unchanged **Example:** ```typescript const step = createStep({ suspendSchema: z.object({ reason: z.string() }), resumeSchema: z.object({ approved: z.boolean() }), execute: async ({ suspend, suspendData, resumeData }) => { if (!resumeData?.approved) { return await suspend({ reason: 'Approval required' }); } // Access original suspend data when resuming console.log(`Resuming after: ${suspendData?.reason}`); return { result: 'Approved' }; }, }); ``` - feat(storage): support querying messages from multiple threads ([#10663](https://github.com/mastra-ai/mastra/pull/10663)) - Fixed TypeScript errors where `threadId: string | string[]` was being passed to places expecting `Scalar` type - Added proper multi-thread support for `listMessages` across all adapters when `threadId` is an array - Updated `_getIncludedMessages` to look up message threadId by ID (since message IDs are globally unique) - **upstash**: Added `msg-idx:{messageId}` index for O(1) message lookups (backwards compatible with fallback to scan for old messages, with automatic backfill) - Adds trace tagging support to the BrainTrust and Langfuse tracing exporters. ([#10765](https://github.com/mastra-ai/mastra/pull/10765)) - Add `messageList` parameter to `processOutputStream` for accessing remembered messages during streaming ([#10608](https://github.com/mastra-ai/mastra/pull/10608)) - Unify transformScoreRow functions across storage adapters ([#10648](https://github.com/mastra-ai/mastra/pull/10648)) Added a unified `transformScoreRow` function in `@mastra/core/storage` that provides schema-driven row transformation for score data. This eliminates code duplication across 10 storage adapters while maintaining store-specific behavior through configurable options: - `preferredTimestampFields`: Preferred source fields for timestamps (PostgreSQL, Cloudflare D1) - `convertTimestamps`: Convert timestamp strings to Date objects (MSSQL, MongoDB, ClickHouse) - `nullValuePattern`: Skip values matching pattern (ClickHouse's `'_null_'`) - `fieldMappings`: Map source column names to schema fields (LibSQL's `additionalLLMContext`) Each store adapter now uses the unified function with appropriate options, reducing ~200 lines of duplicate transformation logic while ensuring consistent behavior across all storage backends. ### Patch Changes - dependencies updates: ([#10110](https://github.com/mastra-ai/mastra/pull/10110)) - Updated dependency [`hono-openapi@^1.1.1` ↗︎](https://www.npmjs.com/package/hono-openapi/v/1.1.1) (from `^0.4.8`, in `dependencies`) - unexpected json parse issue, log error but dont fail ([#10241](https://github.com/mastra-ai/mastra/pull/10241)) - Fixed a bug in agent networks where sometimes the task name was empty ([#10629](https://github.com/mastra-ai/mastra/pull/10629)) - Adds `tool-result` and `tool-error` chunks to the processor.processOutputStream path. Processors now have access to these two chunks. ([#10645](https://github.com/mastra-ai/mastra/pull/10645)) - Include `.input` in workflow results for both engines and remove the option to omit them from Inngest workflows. ([#10688](https://github.com/mastra-ai/mastra/pull/10688)) - `getSpeakers` endpoint returns an empty array if voice is not configured on the agent and `getListeners` endpoint returns `{ enabled: false }` if voice is not figured on the agent. ([#10560](https://github.com/mastra-ai/mastra/pull/10560)) When no voice is set on agent don't throw error, by default set voice to undefined rather than DefaultVoice which throws errors when it is accessed. - SimpleAuth and improved CloudAuth ([#10490](https://github.com/mastra-ai/mastra/pull/10490)) - When LLMs like Claude Sonnet 4.5 and Gemini 2.4 call tools with all-optional parameters, they send `args: undefined` instead of `args: {}`. This caused validation to fail with "root: Required". ([#10728](https://github.com/mastra-ai/mastra/pull/10728)) The fix normalizes `undefined`/`null` to `{}` for object schemas and `[]` for array schemas before validation. - Fixed tool validation error messages so logs show Zod validation errors directly instead of hiding them inside structured JSON. ([#10579](https://github.com/mastra-ai/mastra/pull/10579)) - Fix error when spreading config objects in Mastra constructor ([#10718](https://github.com/mastra-ai/mastra/pull/10718)) Adds validation guards to handle undefined/null values that can occur when config objects are spread (`{ ...config }`). Previously, if getters or non-enumerable properties resulted in undefined values during spread, the constructor would throw cryptic errors when accessing `.id` or `.name` on undefined objects. - Fix GPT-5/o3 reasoning models failing with "required reasoning item" errors when using memory with tools. Empty reasoning is now stored with providerMetadata to preserve OpenAI's item_reference. ([#10585](https://github.com/mastra-ai/mastra/pull/10585)) - Fix generateTitle model type to accept AI SDK LanguageModelV2 ([#10541](https://github.com/mastra-ai/mastra/pull/10541)) Updated the `generateTitle.model` config option to accept `MastraModelConfig` instead of `MastraLanguageModel`. This allows users to pass raw AI SDK `LanguageModelV2` models (e.g., `anthropic.languageModel('claude-3-5-haiku-20241022')`) directly without type errors. Previously, passing a standard `LanguageModelV2` would fail because `MastraLanguageModelV2` has different `doGenerate`/`doStream` return types. Now `MastraModelConfig` is used consistently across: - `memory/types.ts` - `generateTitle.model` config - `agent.ts` - `genTitle`, `generateTitleFromUserMessage`, `resolveTitleGenerationConfig` - `agent-legacy.ts` - `AgentLegacyCapabilities` interface - Fix message ordering when using toAISdkV5Messages or prepareStep ([#10686](https://github.com/mastra-ai/mastra/pull/10686)) Messages without `createdAt` timestamps were getting shuffled because they all received identical timestamps during conversion. Now messages are assigned monotonically increasing timestamps via `generateCreatedAt()`, preserving input order. Before: ``` Input: [user: "hello", assistant: "Hi!", user: "bye"] Output: [user: "bye", assistant: "Hi!", user: "hello"] // shuffled! ``` After: ``` Input: [user: "hello", assistant: "Hi!", user: "bye"] Output: [user: "hello", assistant: "Hi!", user: "bye"] // correct order ``` - Fix Scorer not using custom gateways registered with Mastra ([#10778](https://github.com/mastra-ai/mastra/pull/10778)) Scorers now have access to custom gateways when resolving models. Previously, calling `resolveModelConfig` in the scorer didn't pass the Mastra instance, so custom gateways were never available. - Fix workflow run status not being updated from storage snapshot in createRun ([#10664](https://github.com/mastra-ai/mastra/pull/10664)) When createRun is called with an existing runId, it now correctly updates the run's status from the storage snapshot. This fixes the issue where different workflow instances (e.g., different API requests) would get a run with 'pending' status instead of the correct status from storage (e.g., 'suspended'). - Pass resourceId and threadId to network agent's subAgent when it has its own memory ([#10592](https://github.com/mastra-ai/mastra/pull/10592)) - use `agent.getMemory` to fetch the memory instance on the Agent class to make sure that storage gets set if memory doesn't set it itself. ([#10556](https://github.com/mastra-ai/mastra/pull/10556)) - Built-in processors that use internal agents (PromptInjectionDetector, ModerationProcessor, PIIDetector, LanguageDetector, StructuredOutputProcessor) now accept `providerOptions` to control model behavior. ([#10651](https://github.com/mastra-ai/mastra/pull/10651)) This lets you pass provider-specific settings like `reasoningEffort` for OpenAI thinking models: ```typescript const processor = new PromptInjectionDetector({ model: 'openai/o1-mini', threshold: 0.7, strategy: 'block', providerOptions: { openai: { reasoningEffort: 'low', }, }, }); ``` - Improved typing for `workflow.then` to allow the provided steps `inputSchema` to be a subset of the previous steps `outputSchema`. Also errors if the provided steps `inputSchema` is a superset of the previous steps outputSchema. ([#10763](https://github.com/mastra-ai/mastra/pull/10763)) - Fix type issue with workflow `.parallel()` when passing multiple steps, one or more of which has a `resumeSchema` provided. ([#10708](https://github.com/mastra-ai/mastra/pull/10708)) - Adds bidirectional integration with otel tracing via a new @mastra/otel-bridge package. ([#10482](https://github.com/mastra-ai/mastra/pull/10482)) - Adds `processInputStep` method to the Processor interface. Unlike `processInput` which runs once at the start, this runs at each step of the agentic loop (including tool call continuations). ([#10650](https://github.com/mastra-ai/mastra/pull/10650)) ```ts const processor: Processor = { id: 'my-processor', processInputStep: async ({ messages, messageList, stepNumber, systemMessages }) => { // Transform messages at each step before LLM call return messageList; }, }; ``` - When using output processors with `agent.generate()`, `result.text` was returning the unprocessed LLM response instead of the processed text. ([#10735](https://github.com/mastra-ai/mastra/pull/10735)) **Before:** ```ts const result = await agent.generate('hello'); result.text; // "hello world" (unprocessed) result.response.messages[0].content[0].text; // "HELLO WORLD" (processed) ``` **After:** ```ts const result = await agent.generate('hello'); result.text; // "HELLO WORLD" (processed) ``` The bug was caused by the `text` delayed promise being resolved twice - first correctly with the processed text, then overwritten with the unprocessed buffered text. - Refactored default engine to fit durable execution better, and the inngest engine to match. ([#10627](https://github.com/mastra-ai/mastra/pull/10627)) Also fixes requestContext persistence by relying on inngest step memoization. Unifies some of the stepResults and error formats in both engines. - Allow direct access to server app handle directly from Mastra instance. ([#10598](https://github.com/mastra-ai/mastra/pull/10598)) ```ts // Before: HTTP request to localhost const response = await fetch(`http://localhost:5000/api/tools`); // After: Direct call via app.fetch() const app = mastra.getServerApp(); const response = await app.fetch(new Request('http://internal/api/tools')); ``` - Added `mastra.getServerApp()` to access the underlying Hono/Express app - Added `mastra.getMastraServer()` and `mastra.setMastraServer()` for adapter access - Added `MastraServerBase` class in `@mastra/core/server` for adapter implementations - Server adapters now auto-register with Mastra in their constructor - Fix network agent not getting `text-delta` from subAgent when `.stream` is used ([#10533](https://github.com/mastra-ai/mastra/pull/10533)) - Fix discriminatedUnion schema information lost when json schema is converted to zod ([#10500](https://github.com/mastra-ai/mastra/pull/10500)) - Fix writer.custom not working during workflow resume operations ([#10720](https://github.com/mastra-ai/mastra/pull/10720)) When a workflow step is resumed, the writer parameter was not being properly passed through, causing writer.custom() calls to fail. This fix ensures the writableStream parameter is correctly passed to both run.resume() and run.start() calls in the workflow execution engine, allowing custom events to be emitted properly during resume operations. - Fix corrupted provider-registry.json file in global cache and regenerate corrupted files ([#10606](https://github.com/mastra-ai/mastra/pull/10606)) - Fix TypeScript error when using Zod schemas in `defaultOptions.structuredOutput` ([#10710](https://github.com/mastra-ai/mastra/pull/10710)) Previously, defining `structuredOutput.schema` in `defaultOptions` would cause a TypeScript error because the type only accepted `undefined`. Now any valid `OutputSchema` is correctly accepted. - Add support for `providerOptions` when defining tools. This allows developers to specify provider-specific configurations (like Anthropic's `cacheControl`) per tool. ([#10649](https://github.com/mastra-ai/mastra/pull/10649)) ```typescript createTool({ id: 'my-tool', providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, // ... }); ``` - Fixed OpenAI reasoning message merging so distinct reasoning items are no longer dropped when they share a message ID. Prevents downstream errors where a function call is missing its required "reasoning" item. See #9005. ([#10614](https://github.com/mastra-ai/mastra/pull/10614)) - Updated dependencies [[`103586c`](https://github.com/mastra-ai/mastra/commit/103586cb23ebcd2466c7f68a71674d37cc10e263), [`61a5705`](https://github.com/mastra-ai/mastra/commit/61a570551278b6743e64243b3ce7d73de915ca8a), [`db70a48`](https://github.com/mastra-ai/mastra/commit/db70a48aeeeeb8e5f92007e8ede52c364ce15287), [`f03ae60`](https://github.com/mastra-ai/mastra/commit/f03ae60500fe350c9d828621006cdafe1975fdd8)]: - @mastra/observability@1.0.0-beta.2 - @mastra/schema-compat@1.0.0-beta.2 ## 1.0.0-beta.5 ### Patch Changes - Add Azure OpenAI gateway ([#9990](https://github.com/mastra-ai/mastra/pull/9990)) The Azure OpenAI gateway supports three configuration modes: 1. **Static deployments**: Provide deployment names from Azure Portal 2. **Dynamic discovery**: Query Azure Management API for available deployments 3. **Manual**: Specify deployment names when creating agents ## Usage ```typescript import { Mastra } from '@mastra/core'; import { AzureOpenAIGateway } from '@mastra/core/llm'; // Static mode (recommended) export const mastra = new Mastra({ gateways: [ new AzureOpenAIGateway({ resourceName: process.env.AZURE_RESOURCE_NAME!, apiKey: process.env.AZURE_API_KEY!, deployments: ['gpt-4-prod', 'gpt-35-turbo-dev'], }), ], }); // Dynamic discovery mode export const mastra = new Mastra({ gateways: [ new AzureOpenAIGateway({ resourceName: process.env.AZURE_RESOURCE_NAME!, apiKey: process.env.AZURE_API_KEY!, management: { tenantId: process.env.AZURE_TENANT_ID!, clientId: process.env.AZURE_CLIENT_ID!, clientSecret: process.env.AZURE_CLIENT_SECRET!, subscriptionId: process.env.AZURE_SUBSCRIPTION_ID!, resourceGroup: 'my-resource-group', }, }), ], }); // Use Azure OpenAI models const agent = new Agent({ model: 'azure-openai/gpt-4-deployment', instructions: 'You are a helpful assistant', }); ``` - - Fix tool suspension throwing error when `outputSchema` is passed to tool during creation ([#10444](https://github.com/mastra-ai/mastra/pull/10444)) - Pass `suspendSchema` and `resumeSchema` from tool into step created when creating step from tool - Add `onOutput` hook for tools ([#10466](https://github.com/mastra-ai/mastra/pull/10466)) Tools now support an `onOutput` lifecycle hook that is invoked after successful tool execution. This complements the existing `onInputStart`, `onInputDelta`, and `onInputAvailable` hooks to provide complete visibility into the tool execution lifecycle. The `onOutput` hook receives: - `output`: The tool's return value (typed according to `outputSchema`) - `toolCallId`: Unique identifier for the tool call - `toolName`: The name of the tool that was executed - `abortSignal`: Signal for detecting if the operation should be cancelled Example usage: ```typescript import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; export const weatherTool = createTool({ id: 'weather-tool', description: 'Get weather information', outputSchema: z.object({ temperature: z.number(), conditions: z.string(), }), execute: async input => { return { temperature: 72, conditions: 'sunny' }; }, onOutput: ({ output, toolCallId, toolName }) => { console.log(`${toolName} completed:`, output); // output is fully typed based on outputSchema }, }); ``` Hook execution order: 1. `onInputStart` - Input streaming begins 2. `onInputDelta` - Input chunks arrive (called multiple times) 3. `onInputAvailable` - Complete input parsed and validated 4. Tool's `execute` function runs 5. `onOutput` - Tool completed successfully (NEW) - Add new deleteVectors, updateVector by filter ([#10408](https://github.com/mastra-ai/mastra/pull/10408)) - Fix base64 encoded images with threads - issue #10480 ([#10483](https://github.com/mastra-ai/mastra/pull/10483)) Fixed "Invalid URL" error when using base64 encoded images (without `data:` prefix) in agent calls with threads and resources. Raw base64 strings are now automatically converted to proper data URIs before being processed. **Changes:** - Updated `attachments-to-parts.ts` to detect and convert raw base64 strings to data URIs - Fixed `MessageList` image processing to handle raw base64 in two locations: - Image part conversion in `aiV4CoreMessageToV1PromptMessage` - File part to experimental_attachments conversion in `mastraDBMessageToAIV4UIMessage` - Added comprehensive tests for base64 images, data URIs, and HTTP URLs with threads **Breaking Change:** None - this is a bug fix that maintains backward compatibility while adding support for raw base64 strings. - Fix message metadata not persisting when using simple message format. Previously, custom metadata passed in messages (e.g., `{role: 'user', content: 'text', metadata: {userId: '123'}}`) was not being saved to the database. This occurred because the CoreMessage conversion path didn't preserve metadata fields. ([#10488](https://github.com/mastra-ai/mastra/pull/10488)) Now metadata is properly preserved for all message input formats: - Simple CoreMessage format: `{role, content, metadata}` - Full UIMessage format: `{role, content, parts, metadata}` - AI SDK v5 ModelMessage format with metadata Fixes #8556 - feat: Composite auth implementation ([#10359](https://github.com/mastra-ai/mastra/pull/10359)) - Fix requireApproval property being ignored for tools passed via toolsets, clientTools, and memoryTools parameters. The requireApproval flag now correctly propagates through all tool conversion paths, ensuring tools requiring approval will properly request user approval before execution. ([#10464](https://github.com/mastra-ai/mastra/pull/10464)) - Add timeTravel APIs and add timeTravel feature to studio ([#10361](https://github.com/mastra-ai/mastra/pull/10361)) - Fix Azure Foundry rate limit handling for -1 values ([#10409](https://github.com/mastra-ai/mastra/pull/10409)) - Fix model headers not being passed through gateway system ([#10465](https://github.com/mastra-ai/mastra/pull/10465)) Previously, custom headers specified in `MastraModelConfig` were not being passed through the gateway system to model providers. This affected: - OpenRouter (preventing activity tracking with `HTTP-Referer` and `X-Title`) - Custom providers using custom URLs (headers not passed to `createOpenAICompatible`) - Custom gateway implementations (headers not available in `resolveLanguageModel`) Now headers are correctly passed through the entire gateway system: - Base `MastraModelGateway` interface updated to accept headers - `ModelRouterLanguageModel` passes headers from config to all gateways - OpenRouter receives headers for activity tracking - Custom URL providers receive headers via `createOpenAICompatible` - Custom gateways can access headers in their `resolveLanguageModel` implementation Example usage: ```typescript // Works with OpenRouter const agent = new Agent({ name: 'my-agent', instructions: 'You are a helpful assistant.', model: { id: 'openrouter/anthropic/claude-3-5-sonnet', headers: { 'HTTP-Referer': 'https://myapp.com', 'X-Title': 'My Application', }, }, }); // Also works with custom providers const customAgent = new Agent({ name: 'custom-agent', instructions: 'You are a helpful assistant.', model: { id: 'custom-provider/model', url: 'https://api.custom.com/v1', apiKey: 'key', headers: { 'X-Custom-Header': 'custom-value', }, }, }); ``` Fixes https://github.com/mastra-ai/mastra/issues/9760 - fix(agent): persist messages before tool suspension ([#10369](https://github.com/mastra-ai/mastra/pull/10369)) Fixes issues where thread and messages were not saved before suspension when tools require approval or call suspend() during execution. This caused conversation history to be lost if users refreshed during tool approval or suspension. **Backend changes (@mastra/core):** - Add assistant messages to messageList immediately after LLM execution - Flush messages synchronously before suspension to persist state - Create thread if it doesn't exist before flushing - Add metadata helpers to persist and remove tool approval state - Pass saveQueueManager and memory context through workflow for immediate persistence **Frontend changes (@mastra/react):** - Extract runId from pending approvals to enable resumption after refresh - Convert `pendingToolApprovals` (DB format) to `requireApprovalMetadata` (runtime format) - Handle both `dynamic-tool` and `tool-{NAME}` part types for approval state - Change runId from hardcoded `agentId` to unique `uuid()` **UI changes (@mastra/playground-ui):** - Handle tool calls awaiting approval in message initialization - Convert approval metadata format when loading initial messages Fixes #9745, #9906 - Update MockMemory to work with new storage API changes. MockMemory now properly implements all abstract MastraMemory methods. This includes proper thread management, message saving with MessageList conversion, working memory operations with scope support, and resource listing. ([#10368](https://github.com/mastra-ai/mastra/pull/10368)) Add Zod v4 support for working memory schemas. Memory implementations now check for Zod v4's built-in `.toJsonSchema()` method before falling back to the `zodToJsonSchema` compatibility function, improving performance and forward compatibility while maintaining backward compatibility with Zod v3. Add Gemini 3 Pro test coverage in agent-gemini.test.ts to validate the latest Gemini model integration. - Fix race condition in parallel tool stream writes ([#10463](https://github.com/mastra-ai/mastra/pull/10463)) Introduces a write queue to ToolStream to serialize access to the underlying stream, preventing writer locked errors - Remove unneeded console warning when flushing messages and no threadId or saveQueueManager is found. ([#10498](https://github.com/mastra-ai/mastra/pull/10498)) - Add optional includeRawChunks parameter to agent execution options, ([#10456](https://github.com/mastra-ai/mastra/pull/10456)) allowing users to include raw chunks in stream output where supported by the model provider. - When `mastra dev` runs, multiple processes can write to `provider-registry.json` concurrently (auto-refresh, syncGateways, syncGlobalCacheToLocal). This causes file corruption where the end of the JSON appears twice, making it unparseable. ([#10455](https://github.com/mastra-ai/mastra/pull/10455)) The fix uses atomic writes via the write-to-temp-then-rename pattern. Instead of: ```ts fs.writeFileSync(filePath, content, 'utf-8'); ``` We now do: ```ts const tempPath = `${filePath}.${process.pid}.${Date.now()}.${randomSuffix}.tmp`; fs.writeFileSync(tempPath, content, 'utf-8'); fs.renameSync(tempPath, filePath); // atomic on POSIX ``` `fs.rename()` is atomic on POSIX systems when both paths are on the same filesystem, so concurrent writes will each complete fully rather than interleaving. - Fix .map when placed at the beginning of a workflow or nested workflow ([#10457](https://github.com/mastra-ai/mastra/pull/10457)) - Ensures that data chunks written via `writer.custom()` always bubble up directly to the top-level stream, even when nested in sub-agents. This allows tools to emit custom progress updates, metrics, and other data that can be consumed at any level of the agent hierarchy. ([#10309](https://github.com/mastra-ai/mastra/pull/10309)) - **Added bubbling logic in sub-agent execution**: When sub-agents execute, data chunks (chunks with type starting with `data-`) are detected and written via `writer.custom()` instead of `writer.write()`, ensuring they bubble up directly without being wrapped in `tool-output` chunks. - **Added comprehensive tests**: - Test for `writer.custom()` with direct tool execution - Test for `writer.custom()` with sub-agent tools (nested execution) - Test for mixed usage of `writer.write()` and `writer.custom()` in the same tool When a sub-agent's tool uses `writer.custom()` to write data chunks, those chunks appear in the sub-agent's stream. The parent agent's execution logic now detects these chunks and uses `writer.custom()` to bubble them up directly, preserving their structure and making them accessible at the top level. This ensures that: - Data chunks from tools always appear directly in the stream (not wrapped) - Data chunks bubble up correctly through nested agent hierarchies - Regular chunks continue to be wrapped in `tool-output` as expected - Update agent workflow and sub-agent tool transformations to accept more input arguments. ([#10278](https://github.com/mastra-ai/mastra/pull/10278)) These tools now accept the following ``` workflowTool.execute({ inputData, initialState }, context) agentTool.execute({ prompt, threadId, resourceId, instructions, maxSteps }, context) ``` Workflow tools now also properly return errors when the workflow run fails ``` const workflowResult = await workflowTool.execute({ inputData, initialState }, context) console.log(workflowResult.error) // error msg if error console.log(workflowResult.result) // result of the workflow if success ``` Workflows passed to agents do not properly handle suspend/resume`, they only handle success or error. - Fixed OpenAI schema compatibility when using `agent.generate()` or `agent.stream()` with `structuredOutput`. ([#10366](https://github.com/mastra-ai/mastra/pull/10366)) ## Changes - **Automatic transformation**: Zod schemas are now automatically transformed for OpenAI strict mode compatibility when using OpenAI models (including reasoning models like o1, o3, o4) - **Optional field handling**: `.optional()` fields are converted to `.nullable()` with a transform that converts `null` → `undefined`, preserving optional semantics while satisfying OpenAI's strict mode requirements - **Preserves nullable fields**: Intentionally `.nullable()` fields remain unchanged - **Deep transformation**: Handles `.optional()` fields at any nesting level (objects, arrays, unions, etc.) - **JSON Schema objects**: Not transformed, only Zod schemas ## Example ```typescript const agent = new Agent({ name: 'data-extractor', model: { provider: 'openai', modelId: 'gpt-4o' }, instructions: 'Extract user information', }); const schema = z.object({ name: z.string(), age: z.number().optional(), deletedAt: z.date().nullable(), }); // Schema is automatically transformed for OpenAI compatibility const result = await agent.generate('Extract: John, deleted yesterday', { structuredOutput: { schema }, }); // Result: { name: 'John', age: undefined, deletedAt: null } ``` - Fix network data step formatting in AI SDK stream transformation ([#10432](https://github.com/mastra-ai/mastra/pull/10432)) Previously, network execution steps were not being tracked correctly in the AI SDK stream transformation. Steps were being duplicated rather than updated, and critical metadata like step IDs, iterations, and task information was missing or incorrectly structured. **Changes:** - Enhanced step tracking in `AgentNetworkToAISDKTransformer` to properly maintain step state throughout execution lifecycle - Steps are now identified by unique IDs and updated in place rather than creating duplicates - Added proper iteration and task metadata to each step in the network execution flow - Fixed agent, workflow, and tool execution events to correctly populate step data - Updated network stream event types to include `networkId`, `workflowId`, and consistent `runId` tracking - Added test coverage for network custom data chunks with comprehensive validation This ensures the AI SDK correctly represents the full execution flow of agent networks with accurate step sequencing and metadata. - Fix generating provider-registry.json ([#10392](https://github.com/mastra-ai/mastra/pull/10392)) - Adds type inference for `mastra.get*ById` functions. Only those registered at the top level mastra instance will get inferred. MCP and tool id's do not get inferred yet, those need additional changes. ([#10199](https://github.com/mastra-ai/mastra/pull/10199)) - Fix working memory zod to json schema conversion to use schema-compat zodtoJsonSchema fn. ([#10391](https://github.com/mastra-ai/mastra/pull/10391)) - Fixes parallel tool call issue with Gemini 3 Pro by preventing step-start parts from being inserted between consecutive tool parts in the `addStartStepPartsForAIV5` function. This ensures that the AI SDK's `convertToModelMessages` correctly preserves the order of parallel tool calls and maintains the `thought_signature` on the first tool call as required by Gemini's API. ([#10372](https://github.com/mastra-ai/mastra/pull/10372)) - Updated dependencies [[`bae33d9`](https://github.com/mastra-ai/mastra/commit/bae33d91a63fbb64d1e80519e1fc1acaed1e9013)]: - @mastra/schema-compat@1.0.0-beta.1 ## 1.0.0-beta.4 ### Patch Changes - Fix message list provider metadata handling and reasoning text optimization ([#10281](https://github.com/mastra-ai/mastra/pull/10281)) - Improved provider metadata preservation across message transformations - Optimized reasoning text storage to avoid duplication (using `details` instead of `reasoning` field) - Fixed test snapshots for timestamp precision and metadata handling - Allow provider to pass through options to the auth config ([#10284](https://github.com/mastra-ai/mastra/pull/10284)) - Fix deprecation warning when agent network executes workflows by using `.fullStream` instead of iterating `WorkflowRunOutput` directly ([#10285](https://github.com/mastra-ai/mastra/pull/10285)) - Fix generate toolResults and mismatch in provider tool names ([#10282](https://github.com/mastra-ai/mastra/pull/10282)) - Support AI SDK voice models ([#10304](https://github.com/mastra-ai/mastra/pull/10304)) Mastra now supports AI SDK's transcription and speech models directly in `CompositeVoice`, enabling seamless integration with a wide range of voice providers through the AI SDK ecosystem. This allows you to use models from OpenAI, ElevenLabs, Groq, Deepgram, LMNT, Hume, and many more for both speech-to-text (transcription) and text-to-speech capabilities. AI SDK models are automatically wrapped when passed to `CompositeVoice`, so you can mix and match AI SDK models with existing Mastra voice providers for maximum flexibility. ## Usage Example ```typescript import { CompositeVoice } from '@mastra/core/voice'; import { openai } from '@ai-sdk/openai'; import { elevenlabs } from '@ai-sdk/elevenlabs'; // Use AI SDK models directly with CompositeVoice const voice = new CompositeVoice({ input: openai.transcription('whisper-1'), // AI SDK transcription model output: elevenlabs.speech('eleven_turbo_v2'), // AI SDK speech model }); // Convert text to speech const audioStream = await voice.speak('Hello from AI SDK!'); // Convert speech to text const transcript = await voice.listen(audioStream); console.log(transcript); ``` Fixes #9947 ## 1.0.0-beta.3 ### Major Changes - Use tool's outputSchema to validate results and return an error object if schema does not match output results. ([#9664](https://github.com/mastra-ai/mastra/pull/9664)) ```typescript const getUserTool = createTool({ id: 'get-user', outputSchema: z.object({ id: z.string(), name: z.string(), email: z.string().email(), }), execute: async inputData => { return { id: '123', name: 'John' }; }, }); ``` When validation fails, the tool returns a `ValidationError`: ```typescript // Before v1 - invalid output would silently pass through await getUserTool.execute({}); // { id: "123", name: "John" } - missing email // After v1 - validation error is returned await getUserTool.execute({}); // { // error: true, // message: "Tool output validation failed for get-user. The tool returned invalid output:\n- email: Required\n\nReturned output: {...}", // validationErrors: { ... } // } ``` ### Patch Changes - dependencies updates: ([#10131](https://github.com/mastra-ai/mastra/pull/10131)) - Updated dependency [`hono@^4.10.5` ↗︎](https://www.npmjs.com/package/hono/v/4.10.5) (from `^4.9.7`, in `dependencies`) - Only handle download image asset transformation if needed ([#10122](https://github.com/mastra-ai/mastra/pull/10122)) - Add serializedStepGraph to runExecutionResult response ([#10004](https://github.com/mastra-ai/mastra/pull/10004)) - Fix tool outputSchema validation to allow unsupported Zod types like ZodTuple. The outputSchema is only used for internal validation and never sent to the LLM, so model compatibility checks are not needed. ([#9409](https://github.com/mastra-ai/mastra/pull/9409)) - Fix vector definition to fix pinecone ([#10150](https://github.com/mastra-ai/mastra/pull/10150)) - fix resumeStream type to use resumeSchema ([#10202](https://github.com/mastra-ai/mastra/pull/10202)) - Add type bailed to workflowRunStatus ([#10091](https://github.com/mastra-ai/mastra/pull/10091)) - default validate inputs to true in Workflow execute ([#10222](https://github.com/mastra-ai/mastra/pull/10222)) - Add support for doGenerate in LanguageModelV2. This change fixes issues with OpenAI stream permissions. ([#10239](https://github.com/mastra-ai/mastra/pull/10239)) - Added new abstraction over LanguageModelV2 - Fix input tool validation when no inputSchema is provided ([#9941](https://github.com/mastra-ai/mastra/pull/9941)) - Adds ability to create custom `MastraModelGateway`'s that can be added to the `Mastra` class instance under the `gateways` property. Giving you typescript autocompletion in any model picker string. ([#10180](https://github.com/mastra-ai/mastra/pull/10180)) ```typescript import { MastraModelGateway, type ProviderConfig } from '@mastra/core/llm'; import { createOpenAICompatible } from '@ai-sdk/openai-compatible-v5'; import type { LanguageModelV2 } from '@ai-sdk/provider-v5'; class MyCustomGateway extends MastraModelGateway { readonly id = 'my-custom-gateway'; readonly name = 'My Custom Gateway'; readonly prefix = 'custom'; async fetchProviders(): Promise> { return { 'my-provider': { name: 'My Provider', models: ['model-1', 'model-2'], apiKeyEnvVar: 'MY_API_KEY', gateway: this.id, }, }; } buildUrl(modelId: string, envVars?: Record): string { return 'https://api.my-provider.com/v1'; } async getApiKey(modelId: string): Promise { const apiKey = process.env.MY_API_KEY; if (!apiKey) throw new Error('MY_API_KEY not set'); return apiKey; } async resolveLanguageModel({ modelId, providerId, apiKey, }: { modelId: string; providerId: string; apiKey: string; }): Promise { const baseURL = this.buildUrl(`${providerId}/${modelId}`); return createOpenAICompatible({ name: providerId, apiKey, baseURL, }).chatModel(modelId); } } new Mastra({ gateways: { myGateway: new MyCustomGateway(), }, }); ``` - Add an additional check to determine whether the model natively supports specific file types. Only download the file if the model does not support it natively. ([#9790](https://github.com/mastra-ai/mastra/pull/9790)) - Add restart method to workflow run that allows restarting an active workflow run ([#9750](https://github.com/mastra-ai/mastra/pull/9750)) Add status filter to `listWorkflowRuns` Add automatic restart to restart active workflow runs when server starts - Validate schemas by default in workflow. Previously, if you want schemas in the workflow to be validated, you'd have to add `validateInputs` option, now, this will be done by default but can be disabled. ([#10186](https://github.com/mastra-ai/mastra/pull/10186)) For workflows whose schemas and step schemas you don't want validated, do this ```diff createWorkflow({ + options: { + validateInputs: false + } }) ``` - Fix inngest parallel workflow ([#10169](https://github.com/mastra-ai/mastra/pull/10169)) Fix tool as step in inngest Fix inngest nested workflow - Add timeTravel to workflows. This makes it possible to start a workflow run from a particular step in the workflow ([#9994](https://github.com/mastra-ai/mastra/pull/9994)) Example code: ```ts const result = await run.timeTravel({ step: 'step2', inputData: { value: 'input', }, }); ``` - Fixes assets not being downloaded when available ([#10079](https://github.com/mastra-ai/mastra/pull/10079)) - Remove unused dependencies ([#10019](https://github.com/mastra-ai/mastra/pull/10019)) - Updated dependencies [[`a64d16a`](https://github.com/mastra-ai/mastra/commit/a64d16aedafe57ee5707bdcc25f96e07fa1a0233)]: - @mastra/observability@1.0.0-beta.1 ## 1.0.0-beta.2 ### Patch Changes - Make suspendPayload optional when calling `suspend()` ([#9926](https://github.com/mastra-ai/mastra/pull/9926)) Save value returned as `suspendOutput` if user returns data still after calling `suspend()` Automatically call `commit()` on uncommitted workflows when registering in Mastra instance Show actual suspendPayload on Studio in suspend/resume flow ## 1.0.0-beta.1 ### Patch Changes - Set correct peer dependency range for `@mastra/observability` ([#9908](https://github.com/mastra-ai/mastra/pull/9908)) - Add visual styles and labels for more workflow node types ([#9777](https://github.com/mastra-ai/mastra/pull/9777)) - `registerApiRoute` now accepts a `requiresAuth` option, so custom endpoints can opt in/out of Mastra auth without mutating the returned route object. ## 1.0.0-beta.0 ### Major Changes - Moving scorers under the eval domain, api method consistency, prebuilt evals, scorers require ids. ([#9589](https://github.com/mastra-ai/mastra/pull/9589)) - **BREAKING CHANGE**: Scorers for Agents will now use `MastraDBMessage` instead of `UIMessage` ([#9702](https://github.com/mastra-ai/mastra/pull/9702)) - Scorer input/output types now use `MastraDBMessage[]` with nested `content` object structure - Added `getTextContentFromMastraDBMessage()` helper function to extract text content from `MastraDBMessage` objects - Added `createTestMessage()` helper function for creating `MastraDBMessage` objects in tests with optional tool invocations support - Updated `extractToolCalls()` to access tool invocations from nested `content` structure - Updated `getUserMessageFromRunInput()` and `getAssistantMessageFromRunOutput()` to use new message structure - Removed `createUIMessage()` - Every Mastra primitive (agent, MCPServer, workflow, tool, processor, scorer, and vector) now has a get, list, and add method associated with it. Each primitive also now requires an id to be set. ([#9675](https://github.com/mastra-ai/mastra/pull/9675)) Primitives that are added to other primitives are also automatically added to the Mastra instance - Update handlers to use `listWorkflowRuns` instead of `getWorkflowRuns`. Fix type names from `StoragelistThreadsByResourceIdInput/Output` to `StorageListThreadsByResourceIdInput/Output`. ([#9507](https://github.com/mastra-ai/mastra/pull/9507)) - **BREAKING:** Remove `getMessagesPaginated()` and add `perPage: false` support ([#9670](https://github.com/mastra-ai/mastra/pull/9670)) Removes deprecated `getMessagesPaginated()` method. The `listMessages()` API and score handlers now support `perPage: false` to fetch all records without pagination limits. **Storage changes:** - `StoragePagination.perPage` type changed from `number` to `number | false` - All storage implementations support `perPage: false`: - Memory: `listMessages()` - Scores: `listScoresBySpan()`, `listScoresByRunId()`, `listScoresByExecutionId()` - HTTP query parser accepts `"false"` string (e.g., `?perPage=false`) **Memory changes:** - `memory.query()` parameter type changed from `StorageGetMessagesArg` to `StorageListMessagesInput` - Uses flat parameters (`page`, `perPage`, `include`, `filter`, `vectorSearchString`) instead of `selectBy` object **Stricter validation:** - `listMessages()` requires non-empty, non-whitespace `threadId` (throws error instead of returning empty results) **Migration:** ```typescript // Storage/Memory: Replace getMessagesPaginated with listMessages - storage.getMessagesPaginated({ threadId, selectBy: { pagination: { page: 0, perPage: 20 } } }) + storage.listMessages({ threadId, page: 0, perPage: 20 }) + storage.listMessages({ threadId, page: 0, perPage: false }) // Fetch all // Memory: Replace selectBy with flat parameters - memory.query({ threadId, selectBy: { last: 20, include: [...] } }) + memory.query({ threadId, perPage: 20, include: [...] }) // Client SDK - thread.getMessagesPaginated({ selectBy: { pagination: { page: 0 } } }) + thread.listMessages({ page: 0, perPage: 20 }) ``` - # Major Changes ([#9695](https://github.com/mastra-ai/mastra/pull/9695)) ## Storage Layer ### BREAKING: Removed `storage.getMessages()` The `getMessages()` method has been removed from all storage implementations. Use `listMessages()` instead, which provides pagination support. **Migration:** ```typescript // Before const messages = await storage.getMessages({ threadId: 'thread-1' }); // After const result = await storage.listMessages({ threadId: 'thread-1', page: 0, perPage: 50, }); const messages = result.messages; // Access messages array console.log(result.total); // Total count console.log(result.hasMore); // Whether more pages exist ``` ### Message ordering default `listMessages()` defaults to ASC (oldest first) ordering by `createdAt`, matching the previous `getMessages()` behavior. **To use DESC ordering (newest first):** ```typescript const result = await storage.listMessages({ threadId: 'thread-1', orderBy: { field: 'createdAt', direction: 'DESC' }, }); ``` ## Client SDK ### BREAKING: Renamed `client.getThreadMessages()` → `client.listThreadMessages()` **Migration:** ```typescript // Before const response = await client.getThreadMessages(threadId, { agentId }); // After const response = await client.listThreadMessages(threadId, { agentId }); ``` The response format remains the same. ## Type Changes ### BREAKING: Removed `StorageGetMessagesArg` type Use `StorageListMessagesInput` instead: ```typescript // Before import type { StorageGetMessagesArg } from '@mastra/core'; // After import type { StorageListMessagesInput } from '@mastra/core'; ``` - - Removes modelSettings.abortSignal in favour of top-level abortSignal only. Also removes the deprecated output field - use structuredOutput.schema instead. ([`9e1911d`](https://github.com/mastra-ai/mastra/commit/9e1911db2b4db85e0e768c3f15e0d61e319869f6)) - The deprecated generateVNext() and streamVNext() methods have been removed since they're now the stable generate() and stream() methods. - The deprecated `output` option has been removed entirely, in favour of `structuredOutput`. Method renames to clarify the API surface: - getDefaultGenerateOptions → getDefaultGenerateOptionsLegacy - getDefaultStreamOptions → getDefaultStreamOptionsLegacy - getDefaultVNextStreamOptions → getDefaultStreamOptions - Bump minimum required Node.js version to 22.13.0 ([#9706](https://github.com/mastra-ai/mastra/pull/9706)) - Replace `getThreadsByResourceIdPaginated` with `listThreadsByResourceId` across memory handlers. Update client SDK to use `listThreads()` with `offset`/`limit` parameters instead of deprecated `getMemoryThreads()`. Consolidate `/api/memory/threads` routes to single paginated endpoint. ([#9508](https://github.com/mastra-ai/mastra/pull/9508)) - Add new list methods to storage API: `listMessages`, `listMessagesById`, `listThreadsByResourceId`, and `listWorkflowRuns`. Most methods are currently wrappers around existing methods. Full implementations will be added when migrating away from legacy methods. ([#9489](https://github.com/mastra-ai/mastra/pull/9489)) - Update tool execution signature ([#9587](https://github.com/mastra-ai/mastra/pull/9587)) Consolidated the 3 different execution contexts to one ```typescript // before depending on the context the tool was executed in tool.execute({ context: data }); tool.execute({ context: { inputData: data } }); tool.execute(data); // now, for all contexts tool.execute(data, context); ``` **Before:** ```typescript inputSchema: z.object({ something: z.string() }), execute: async ({ context, tracingContext, runId, ... }) => { return doSomething(context.string); } ``` **After:** ```typescript inputSchema: z.object({ something: z.string() }), execute: async (inputData, context) => { const { agent, mcp, workflow, ...sharedContext } = context // context that only an agent would get like toolCallId, messages, suspend, resume, etc if (agent) { doSomething(inputData.something, agent) // context that only a workflow would get like runId, state, suspend, resume, etc } else if (workflow) { doSomething(inputData.something, workflow) // context that only a workflow would get like "extra", "elicitation" } else if (mcp) { doSomething(inputData.something, mcp) } else { // Running a tool in no execution context return doSomething(inputData.something); } } ``` - The `@mastra/core` package no longer allows top-level imports except for `Mastra` and `type Config`. You must use subpath imports for all other imports. ([#9544](https://github.com/mastra-ai/mastra/pull/9544)) For example: ```diff import { Mastra, type Config } from "@mastra/core"; - import { Agent } from "@mastra/core"; - import { createTool } from "@mastra/core"; - import { createStep } from "@mastra/core"; + import { Agent } from "@mastra/core/agent"; + import { createTool } from "@mastra/core/tools"; + import { createStep } from "@mastra/core/workflows"; ``` - This simplifies the Memory API by removing the confusing rememberMessages method and renaming query to recall for better clarity. ([#9701](https://github.com/mastra-ai/mastra/pull/9701)) The rememberMessages method name implied it might persist data when it was actually just retrieving messages, same as query. Having two methods that did essentially the same thing was unnecessary. Before: ```typescript // Two methods that did the same thing memory.rememberMessages({ threadId, resourceId, config, vectorMessageSearch }); memory.query({ threadId, resourceId, perPage, vectorSearchString }); ``` After: ```typescript // Single unified method with clear purpose memory.recall({ threadId, resourceId, perPage, vectorMessageSearch, threadConfig }); ``` All usages have been updated across the codebase including tests. The agent now calls recall directly with the appropriate parameters. - Rename RuntimeContext to RequestContext ([#9511](https://github.com/mastra-ai/mastra/pull/9511)) - Implement listMessages API for replacing previous methods ([#9531](https://github.com/mastra-ai/mastra/pull/9531)) - Rename `defaultVNextStreamOptions` to `defaultOptions`. Add "Legacy" suffix to v1 option properties and methods (`defaultGenerateOptions` → `defaultGenerateOptionsLegacy`, `defaultStreamOptions` → `defaultStreamOptionsLegacy`). ([#9535](https://github.com/mastra-ai/mastra/pull/9535)) - Remove `getThreadsByResourceId` and `getThreadsByResourceIdPaginated` methods from storage interfaces in favor of `listThreadsByResourceId`. The new method uses `offset`/`limit` pagination and a nested `orderBy` object structure (`{ field, direction }`). ([#9536](https://github.com/mastra-ai/mastra/pull/9536)) - Remove `getMessagesById` method from storage interfaces in favor of `listMessagesById`. The new method only returns V2-format messages and removes the format parameter, simplifying the API surface. Users should migrate from `getMessagesById({ messageIds, format })` to `listMessagesById({ messageIds })`. ([#9534](https://github.com/mastra-ai/mastra/pull/9534)) - Experimental auth -> auth ([#9660](https://github.com/mastra-ai/mastra/pull/9660)) - Renamed a bunch of observability/tracing-related things to drop the AI prefix. ([#9744](https://github.com/mastra-ai/mastra/pull/9744)) - Removed MastraMessageV3 intermediary format, now we go from MastraDBMessage->aiv5 formats and back directly ([#9094](https://github.com/mastra-ai/mastra/pull/9094)) - **Breaking Change**: Remove legacy v1 watch events and consolidate on v2 implementation. ([#9252](https://github.com/mastra-ai/mastra/pull/9252)) This change simplifies the workflow watching API by removing the legacy v1 event system and promoting v2 as the standard (renamed to just `watch`). ### What's Changed - Removed legacy v1 watch event handlers and types - Renamed `watch-v2` to `watch` throughout the codebase - Removed `.watch()` method from client-js SDK (`Workflow` and `AgentBuilder` classes) - Removed `/watch` HTTP endpoints from server and deployer - Removed `WorkflowWatchResult` and v1 `WatchEvent` types - Remove various deprecated APIs from agent class. ([#9257](https://github.com/mastra-ai/mastra/pull/9257)) - `agent.llm` → `agent.getLLM()` - `agent.tools` → `agent.getTools()` - `agent.instructions` → `agent.getInstructions()` - `agent.speak()` → `agent.voice.speak()` - `agent.getSpeakers()` → `agent.voice.getSpeakers()` - `agent.listen` → `agent.voice.listen()` - `agent.fetchMemory` → `(await agent.getMemory()).query()` - `agent.toStep` → Add agent directly to the step, workflows handle the transformation - **BREAKING CHANGE**: Pagination APIs now use `page`/`perPage` instead of `offset`/`limit` ([#9592](https://github.com/mastra-ai/mastra/pull/9592)) All storage and memory pagination APIs have been updated to use `page` (0-indexed) and `perPage` instead of `offset` and `limit`, aligning with standard REST API patterns. **Affected APIs:** - `Memory.listThreadsByResourceId()` - `Memory.listMessages()` - `Storage.listWorkflowRuns()` **Migration:** ```typescript // Before await memory.listThreadsByResourceId({ resourceId: 'user-123', offset: 20, limit: 10, }); // After await memory.listThreadsByResourceId({ resourceId: 'user-123', page: 2, // page = Math.floor(offset / limit) perPage: 10, }); // Before await memory.listMessages({ threadId: 'thread-456', offset: 20, limit: 10, }); // After await memory.listMessages({ threadId: 'thread-456', page: 2, perPage: 10, }); // Before await storage.listWorkflowRuns({ workflowName: 'my-workflow', offset: 20, limit: 10, }); // After await storage.listWorkflowRuns({ workflowName: 'my-workflow', page: 2, perPage: 10, }); ``` **Additional improvements:** - Added validation for negative `page` values in all storage implementations - Improved `perPage` validation to handle edge cases (negative values, `0`, `false`) - Added reusable query parser utilities for consistent validation in handlers - ```([#9709](https://github.com/mastra-ai/mastra/pull/9709)) import { Mastra } from '@mastra/core'; import { Observability } from '@mastra/observability'; // Explicit import const mastra = new Mastra({ ...other_config, observability: new Observability({ default: { enabled: true } }) // Instance }); ``` Instead of: ``` import { Mastra } from '@mastra/core'; import '@mastra/observability/init'; // Explicit import const mastra = new Mastra({ ...other_config, observability: { default: { enabled: true } } }); ``` Also renamed a bunch of: - `Tracing` things to `Observability` things. - `AI-` things to just things. - Changing getAgents -> listAgents, getTools -> listTools, getWorkflows -> listWorkflows ([#9495](https://github.com/mastra-ai/mastra/pull/9495)) - Removed old tracing code based on OpenTelemetry ([#9237](https://github.com/mastra-ai/mastra/pull/9237)) - Remove deprecated vector prompts and cohere provider from code ([#9596](https://github.com/mastra-ai/mastra/pull/9596)) - Mark as stable ([`83d5942`](https://github.com/mastra-ai/mastra/commit/83d5942669ce7bba4a6ca4fd4da697a10eb5ebdc)) - Enforcing id required on Processor primitive ([#9591](https://github.com/mastra-ai/mastra/pull/9591)) - **Breaking Changes:** ([#9045](https://github.com/mastra-ai/mastra/pull/9045)) - Moved `generateTitle` from `threads.generateTitle` to top-level memory option - Changed default value from `true` to `false` - Using `threads.generateTitle` now throws an error **Migration:** Replace `threads: { generateTitle: true }` with `generateTitle: true` at the top level of memory options. **Playground:** The playground UI now displays thread IDs instead of "Chat from" when titles aren't generated. - Renamed `MastraMessageV2` to `MastraDBMessage` ([#9255](https://github.com/mastra-ai/mastra/pull/9255)) Made the return format of all methods that return db messages consistent. It's always `{ messages: MastraDBMessage[] }` now, and messages can be converted after that using `@mastra/ai-sdk/ui`'s `toAISdkV4/5Messages()` function - moved ai-tracing code into @mastra/observability ([#9661](https://github.com/mastra-ai/mastra/pull/9661)) - Remove legacy evals from Mastra ([#9491](https://github.com/mastra-ai/mastra/pull/9491)) - Removes deprecated input-processor type and processors. ([#9200](https://github.com/mastra-ai/mastra/pull/9200)) ### Minor Changes - **BREAKING CHANGE**: Memory scope defaults changed from 'thread' to 'resource' ([#8983](https://github.com/mastra-ai/mastra/pull/8983)) Both `workingMemory.scope` and `semanticRecall.scope` now default to `'resource'` instead of `'thread'`. This means: - Working memory persists across all conversations for the same user/resource - Semantic recall searches across all threads for the same user/resource **Migration**: To maintain the previous thread-scoped behavior, explicitly set `scope: 'thread'`: ```typescript memory: new Memory({ storage, workingMemory: { enabled: true, scope: 'thread', // Explicitly set for thread-scoped behavior }, semanticRecall: { scope: 'thread', // Explicitly set for thread-scoped behavior }, }), ``` See the [migration guide](https://mastra.ai/docs/guides/migrations/memory-scope-defaults) for more details. Also fixed issues where playground semantic recall search could show missing or incorrect results in certain cases. - Rename LLM span types and attributes to use Model prefix ([#9105](https://github.com/mastra-ai/mastra/pull/9105)) BREAKING CHANGE: This release renames tracing span types and attribute interfaces to use the "Model" prefix instead of "LLM": - `AISpanType.LLM_GENERATION` → `AISpanType.MODEL_GENERATION` - `AISpanType.LLM_STEP` → `AISpanType.MODEL_STEP` - `AISpanType.LLM_CHUNK` → `AISpanType.MODEL_CHUNK` - `LLMGenerationAttributes` → `ModelGenerationAttributes` - `LLMStepAttributes` → `ModelStepAttributes` - `LLMChunkAttributes` → `ModelChunkAttributes` - `InternalSpans.LLM` → `InternalSpans.MODEL` This change better reflects that these span types apply to all AI models, not just Large Language Models. Migration guide: - Update all imports: `import { ModelGenerationAttributes } from '@mastra/core/ai-tracing'` - Update span type references: `AISpanType.MODEL_GENERATION` - Update InternalSpans usage: `InternalSpans.MODEL` ### Patch Changes - Add exponential backoff to model retry logic to prevent cascading failures ([#9798](https://github.com/mastra-ai/mastra/pull/9798)) When AI model calls fail, the system now implements exponential backoff (1s, 2s, 4s, 8s, max 10s) before retrying instead of immediately hammering the API. This prevents: - Rate limit violations from getting worse - Cascading failures across all fallback models - Wasted API quota by burning through retries instantly - Production outages when all models fail due to rate limits The backoff gives APIs time to recover from transient failures and rate limiting. - Update provider registry and model documentation with latest models and providers ([`f743dbb`](https://github.com/mastra-ai/mastra/commit/f743dbb8b40d1627b5c10c0e6fc154f4ebb6e394)) - Fix agent onChunk callback receiving wrapped chunk instead of direct chunk ([#9350](https://github.com/mastra-ai/mastra/pull/9350)) - Deprecate `runCount` parameter in favor of `retryCount` for better naming clarity. The name `runCount` was misleading as it doesn't represent the total number of times a step has run, but rather the number of retry attempts made for a step. ([#9153](https://github.com/mastra-ai/mastra/pull/9153)) `runCount` is available in `execute()` functions and methods that interact with the step execution. This also applies to condition functions and loop condition functions that use this parameter. If your code uses `runCount`, change the name to `retryCount`. Here's an example migration: ```diff const myStep = createStep({ // Rest of step... - execute: async ({ runCount, ...params }) => { + execute: async ({ retryCount, ...params }) => { // ... rest of your logic } }); ``` - Add requestContext column if it does not exist ([#9786](https://github.com/mastra-ai/mastra/pull/9786)) - Track usage in workflow and agent network ([#9649](https://github.com/mastra-ai/mastra/pull/9649)) - Allow resuming nested workflow step with chained id ([#9459](https://github.com/mastra-ai/mastra/pull/9459)) Example, you have a workflow like this ``` export const supportWorkflow = mainWorkflow.then(nestedWorkflow).commit(); ``` And a step in `nestedWorkflow` is supsended, you can now also resume it any of these ways: ``` run.resume({ step: "nestedWorkflow.suspendedStep", //chained nested workflow step id and suspended step id //other resume params }) ``` OR ``` run.resume({ step: "nestedWorkflow", // just the nested workflow step/step id //other resume params }) ``` - Fix OpenAI schema validation errors in processors ([#9093](https://github.com/mastra-ai/mastra/pull/9093)) - Breaking change to move mcp related tool execute arguments nested under an `mcp` argument that is only populated if the tool is passed to an MCPServer. This simpliflies tool definitions and gives you the correct types when working with tools meant for MCP servers. ([#9134](https://github.com/mastra-ai/mastra/pull/9134)) - Ensure model_generation spans end before agent_run spans. ([#9251](https://github.com/mastra-ai/mastra/pull/9251)) - Fix workflow input property preservation after resume from snapshot ([#9380](https://github.com/mastra-ai/mastra/pull/9380)) Ensure that when resuming a workflow from a snapshot, the input property is correctly set from the snapshot's context input rather than from resume data. This prevents the loss of original workflow input data during suspend/resume cycles. - Add tool call approval ([#8649](https://github.com/mastra-ai/mastra/pull/8649)) - Fix error handling and serialization in agent streaming to ensure errors are consistently exposed and preserved. ([#9144](https://github.com/mastra-ai/mastra/pull/9144)) - Fixes issue where clicking the reset button in the model picker would fail to restore the original LanguageModelV2 (or any other types) object that was passed during agent construction. ([#9481](https://github.com/mastra-ai/mastra/pull/9481)) - Fix a bug where streaming didn't output the final chunk ([#9546](https://github.com/mastra-ai/mastra/pull/9546)) - Don't call `os.homedir()` at top level (but lazy invoke it) to accommodate sandboxed environments ([#9211](https://github.com/mastra-ai/mastra/pull/9211)) - Fix: Don't download unsupported media ([#9209](https://github.com/mastra-ai/mastra/pull/9209)) - Detect thenable objects returned by AI model providers ([#8905](https://github.com/mastra-ai/mastra/pull/8905)) - Fixes incorrect tool invocation format in message list that was causing client tools to fail during message format conversions. ([#9590](https://github.com/mastra-ai/mastra/pull/9590)) - Bug fix: Use input processors that are passed in generate or stream agent options rather than always defaulting to the processors set on the Agent class. ([#9407](https://github.com/mastra-ai/mastra/pull/9407)) - Fix tool input validation to use schema-compat transformed schemas ([#9258](https://github.com/mastra-ai/mastra/pull/9258)) Previously, tool input validation used the original Zod schema while the LLM received a schema-compat transformed version. This caused validation failures when LLMs (like OpenAI o3 or Claude 3.5 Haiku) sent arguments matching the transformed schema but not the original. For example: - OpenAI o3 reasoning models convert `.optional()` to `.nullable()`, sending `null` values - Claude 3.5 Haiku strips `min`/`max` string constraints, sending shorter strings - Validation would reject these valid responses because it checked against the original schema The fix ensures validation uses the same schema-compat processed schema that was sent to the LLM, eliminating this mismatch. - Add import for WriteableStream in execution-engine and dedupe llm.getModel in agent.ts ([#9185](https://github.com/mastra-ai/mastra/pull/9185)) - Use a shared `getAllToolPaths()` method from the bundler to discover tool paths. ([#9204](https://github.com/mastra-ai/mastra/pull/9204)) - Added support for .streamVNext and .stream that uses it in the inngest execution engine ([#9434](https://github.com/mastra-ai/mastra/pull/9434)) - pass writableStream parameter to workflow execution ([#9139](https://github.com/mastra-ai/mastra/pull/9139)) - Remove tools passed to the Routing Agent in .network() ([#9374](https://github.com/mastra-ai/mastra/pull/9374)) - Fix agent network iteration counter bug causing infinite loops ([#9762](https://github.com/mastra-ai/mastra/pull/9762)) The iteration counter in agent networks was stuck at 0 due to a faulty ternary operator that treated 0 as falsy. This prevented `maxSteps` from working correctly, causing infinite loops when the routing agent kept selecting primitives instead of returning "none". **Changes:** - Fixed iteration counter logic in `loop/network/index.ts` from `(inputData.iteration ? inputData.iteration : -1) + 1` to `(inputData.iteration ?? -1) + 1` - Changed initial iteration value from `0` to `-1` so first iteration correctly starts at 0 - Added `checkIterations()` helper to validate iteration counting in all network tests Fixes #9314 - Fix types from ai v4 ([#9818](https://github.com/mastra-ai/mastra/pull/9818)) - Save correct status in snapshot for all workflow parallel steps. ([#9379](https://github.com/mastra-ai/mastra/pull/9379)) This ensures when you poll workflow run result using `getWorkflowRunExecutionResult(runId)`, you get the right status for all parallel steps - Prevent changing workflow status to suspended when some parallel steps are still running ([#9431](https://github.com/mastra-ai/mastra/pull/9431)) - Add ability to pass agent options when wrapping an agent with createStep. This allows configuring agent execution settings when using agents as workflow steps. ([#9199](https://github.com/mastra-ai/mastra/pull/9199)) - Fix MCP server registration ([#9802](https://github.com/mastra-ai/mastra/pull/9802)) - Fix network loop iteration counter and usage promise handling: ([#9408](https://github.com/mastra-ai/mastra/pull/9408)) - Fixed iteration counter in network loop that was stuck at 0 due to falsy check. Properly handled zero values to ensure maxSteps is correctly enforced. - Fixed usage promise resolution in RunOutput stream by properly resolving or rejecting the promise on stream close, preventing hanging promises when streams complete. - Remove `waitForEvent` from workflows. `waitForEvent` is now removed, please use suspend & resume flow instead. See https://mastra.ai/en/docs/workflows/suspend-and-resume for more details on suspend & resume flow. ([#9214](https://github.com/mastra-ai/mastra/pull/9214)) - Workflow validation zod v4 support ([#9319](https://github.com/mastra-ai/mastra/pull/9319)) - Use memory mock in server tests ([#9486](https://github.com/mastra-ai/mastra/pull/9486)) - Fix network routing agent smoothstreaming ([#9247](https://github.com/mastra-ai/mastra/pull/9247)) - Remove format from stream/generate ([#9577](https://github.com/mastra-ai/mastra/pull/9577)) - Fix agent network working memory tool routing. Memory tools are now included in routing agent instructions but excluded from its direct tool calls, allowing the routing agent to properly route to tool execution steps for memory updates. ([#9428](https://github.com/mastra-ai/mastra/pull/9428)) - Fix creating system messages from inside processors using processInput. ([#9469](https://github.com/mastra-ai/mastra/pull/9469)) - Fix usage tracking with agent network ([#9226](https://github.com/mastra-ai/mastra/pull/9226)) - Fix message conversion for incomplete client-side tool calls ([#9749](https://github.com/mastra-ai/mastra/pull/9749)) Fixed handling of `input-available` tool state in `sanitizeV5UIMessages()` to differentiate between two use cases: 1. **Response messages FROM the LLM**: Keep `input-available` states (tool calls waiting for client-side execution) in `response.messages` for proper message history. 2. **Prompt messages TO the LLM**: Filter out `input-available` states when sending historical messages back to the LLM, as these incomplete tool calls (without results) cause errors in the OpenAI Responses API. The fix adds a `filterIncompleteToolCalls` parameter to control this behavior based on whether messages are being sent to or received from the LLM. - Add `initialState` and `outputOptions` to run.stream() call. ([#9238](https://github.com/mastra-ai/mastra/pull/9238)) Example code ``` const run = await workflow.createRunAsync(); const streamResult = run.stream({ inputData: {}, initialState: { value: 'test-state', otherValue: 'test-other-state' }, outputOptions: { includeState: true }, }); ``` Then the result from the stream will include the final state information ``` const executionResult = await streamResult.result; console.log(executionResult.state) ``` - Updated dependencies [[`b9b7ffd`](https://github.com/mastra-ai/mastra/commit/b9b7ffdad6936a7d50b6b814b5bbe54e19087f66), [`dd1c38d`](https://github.com/mastra-ai/mastra/commit/dd1c38d1b75f1b695c27b40d8d9d6ed00d5e0f6f), [`83b08dc`](https://github.com/mastra-ai/mastra/commit/83b08dcf1bfcc915efab23c09207df90fa247908), [`f0f8f12`](https://github.com/mastra-ai/mastra/commit/f0f8f125c308f2d0fd36942ef652fd852df7522f), [`f111eac`](https://github.com/mastra-ai/mastra/commit/f111eac5de509b2e5fccfc1882e7f74cda264c74), [`51acef9`](https://github.com/mastra-ai/mastra/commit/51acef95b5977826594fe3ee24475842bd3d5780), [`eb09742`](https://github.com/mastra-ai/mastra/commit/eb09742197f66c4c38154c3beec78313e69760b2), [`354ad0b`](https://github.com/mastra-ai/mastra/commit/354ad0b7b1b8183ac567f236a884fc7ede6d7138), [`83d5942`](https://github.com/mastra-ai/mastra/commit/83d5942669ce7bba4a6ca4fd4da697a10eb5ebdc), [`a0c8c1b`](https://github.com/mastra-ai/mastra/commit/a0c8c1b87d4fee252aebda73e8637fbe01d761c9)]: - @mastra/schema-compat@1.0.0-beta.0 - @mastra/observability@1.0.0-beta.0 ## 0.22.2 ### Patch Changes - Fix nested workflow events and networks ([#9132](https://github.com/mastra-ai/mastra/pull/9132)) ## 0.22.2-alpha.0 ### Patch Changes - Fix nested workflow events and networks ([#9132](https://github.com/mastra-ai/mastra/pull/9132)) ## 0.22.1 ## 0.22.1-alpha.0 ## 0.22.0 ### Minor Changes - Consolidate streamVNext logic into stream, move old stream function into streamLegacy ([#9092](https://github.com/mastra-ai/mastra/pull/9092)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`c67ca32`](https://github.com/mastra-ai/mastra/commit/c67ca32e3c2cf69bfc146580770c720220ca44ac)) - Update provider registry and model documentation with latest models and providers ([`efb5ed9`](https://github.com/mastra-ai/mastra/commit/efb5ed946ae7f410bc68c9430beb4b010afd25ec)) - Add deprecation warnings for format:ai-sdk ([#9018](https://github.com/mastra-ai/mastra/pull/9018)) - network routing agent text delta ai-sdk streaming ([#8979](https://github.com/mastra-ai/mastra/pull/8979)) - Support writing custom top level stream chunks ([#8922](https://github.com/mastra-ai/mastra/pull/8922)) - Fix incorrect type assertions in Tool class. Created `MastraToolInvocationOptions` type to properly extend AI SDK's `ToolInvocationOptions` with Mastra-specific properties (`suspend`, `resumeData`, `writableStream`). Removed unsafe type assertions from tool execution code. ([#9033](https://github.com/mastra-ai/mastra/pull/9033)) - fix(core): Fix Gemini message ordering validation errors (#7287, #8053) ([#8069](https://github.com/mastra-ai/mastra/pull/8069)) Fixes Gemini API "single turn requests" validation error by ensuring the first non-system message is from the user role. This resolves errors when: - Messages start with assistant role (e.g., from memory truncation) - Tool-call sequences begin with assistant messages **Breaking Change**: Empty or system-only message lists now throw an error instead of adding a placeholder user message, preventing confusing LLM responses. This fix handles both issue #7287 (tool-call ordering) and #8053 (single-turn validation) by inserting a placeholder user message when needed. - Add support for external trace and parent span IDs in TracingOptions. This enables integration with external tracing systems by allowing new AI traces to be started with existing traceId and parentSpanId values. The implementation includes OpenTelemetry-compatible ID validation (32 hex chars for trace IDs, 16 hex chars for span IDs). ([#9053](https://github.com/mastra-ai/mastra/pull/9053)) - Updated `watch` and `watchAsync` methods to use proper function overloads instead of generic conditional types, ensuring compatibility with the base Run class signatures. ([#9048](https://github.com/mastra-ai/mastra/pull/9048)) - Fix tracing context propagation to agent steps in workflows ([#9074](https://github.com/mastra-ai/mastra/pull/9074)) When creating a workflow step from an agent using `createStep(myAgent)`, the tracing context was not being passed to the agent's `stream()` and `streamLegacy()` methods. This caused tracing spans to break in the workflow chain. This fix ensures that `tracingContext` is properly propagated to both agent.stream() and agent.streamLegacy() calls, matching the behavior of tool steps which already propagate tracingContext correctly. - Fixes how reasoning chunks are stored in memory to prevent data loss and ensure they are consolidated as single message parts rather than split into word-level fragments. ([#9041](https://github.com/mastra-ai/mastra/pull/9041)) - fixes an issue where input processors couldn't add system or assistant messages. Previously all messages from input processors were forced to be user messages, causing an error when trying to add other role types. ([#8835](https://github.com/mastra-ai/mastra/pull/8835)) - fix(core): Validate structured output at text-end instead of flush ([#8934](https://github.com/mastra-ai/mastra/pull/8934)) Fixes structured output validation for Bedrock and LMStudio by moving validation from `flush()` to `text-end` chunk. Eliminates `finishReason` heuristics, adds special token extraction for LMStudio, and validates at the correct point in stream lifecycle. - fix model.loop.test.ts tests to use structuredOutput.schema and add assertions ([#8926](https://github.com/mastra-ai/mastra/pull/8926)) - Add `initialState` as an option to `.streamVNext()` ([#9071](https://github.com/mastra-ai/mastra/pull/9071)) - added resourceId and runId to workflow_run metadata in ai tracing ([#9031](https://github.com/mastra-ai/mastra/pull/9031)) - When using OpenAI models with JSON response format, automatically enable strict schema validation. ([#8924](https://github.com/mastra-ai/mastra/pull/8924)) - Fix custom metadata preservation in UIMessages when loading threads. The `getMessagesHandler` now converts `messagesV2` (V2 format with metadata) instead of `messages` (V1 format without metadata) to AIV5.UI format. Also updates the abstract `MastraMemory.query()` return type to include `messagesV2` for proper type safety. ([#9029](https://github.com/mastra-ai/mastra/pull/9029)) - Fix TypeScript type errors when using provider-defined tools from external AI SDK packages. ([#8940](https://github.com/mastra-ai/mastra/pull/8940)) Agents can now accept provider tools like `google.tools.googleSearch()` without type errors. Creates new `@internal/external-types` package to centralize AI SDK type re-exports and adds `ProviderDefinedTool` structural type to handle tools from different package versions/instances due to TypeScript's module path discrimination. - feat(ai-tracing): Add automatic metadata extraction from RuntimeContext to spans ([#9072](https://github.com/mastra-ai/mastra/pull/9072)) Enables automatic extraction of RuntimeContext values as metadata for AI tracing spans across entire traces. Key features: - Configure `runtimeContextKeys` in TracingConfig to extract specific keys from RuntimeContext - Add per-request keys via `tracingOptions.runtimeContextKeys` for trace-specific additions - Supports dot notation for nested values (e.g., 'user.id', 'session.data.experimentId') - TraceState computed once at root span and inherited by all child spans - Explicit metadata in span options takes precedence over extracted metadata Example: ```typescript const mastra = new Mastra({ observability: { configs: { default: { runtimeContextKeys: ['userId', 'environment', 'tenantId'], }, }, }, }); await agent.generate({ messages, runtimeContext, tracingOptions: { runtimeContextKeys: ['experimentId'], // Adds to configured keys }, }); ``` - Fix provider tools for popular providers and add support for anthropic/claude skills. ([#9038](https://github.com/mastra-ai/mastra/pull/9038)) - Refactor workflowstream into workflow output with fullStream property ([#9048](https://github.com/mastra-ai/mastra/pull/9048)) - Added the ability to use model router configs for embedders (eg "openai/text-embedding-ada-002") ([#8992](https://github.com/mastra-ai/mastra/pull/8992)) - Always set supportsStructuredOutputs true for openai compatible provider. ([#8933](https://github.com/mastra-ai/mastra/pull/8933)) - Support for custom resume labels mapping to step to be resumed ([#8941](https://github.com/mastra-ai/mastra/pull/8941)) - added tracing of LLM steps & chunks ([#9058](https://github.com/mastra-ai/mastra/pull/9058)) - Fixed an issue where a custom URL in model router still validated unknown providers against the known providers list. Custom URL means we don't necessarily know the provider. This allows local providers like Ollama to work properly ([#8989](https://github.com/mastra-ai/mastra/pull/8989)) - Show agent tool output better in playground ([#9021](https://github.com/mastra-ai/mastra/pull/9021)) - feat: inject schema context into main agent for processor mode structured output ([#8886](https://github.com/mastra-ai/mastra/pull/8886)) - Added providerOptions types to generate/stream for main builtin model router providers (openai/anthropic/google/xai) ([#8995](https://github.com/mastra-ai/mastra/pull/8995)) - Generate a title for Agent.network() threads ([#8853](https://github.com/mastra-ai/mastra/pull/8853)) ## 0.22.0-alpha.1 ### Minor Changes - Consolidate streamVNext logic into stream, move old stream function into streamLegacy ([#9092](https://github.com/mastra-ai/mastra/pull/9092)) ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`efb5ed9`](https://github.com/mastra-ai/mastra/commit/efb5ed946ae7f410bc68c9430beb4b010afd25ec)) - Fix incorrect type assertions in Tool class. Created `MastraToolInvocationOptions` type to properly extend AI SDK's `ToolInvocationOptions` with Mastra-specific properties (`suspend`, `resumeData`, `writableStream`). Removed unsafe type assertions from tool execution code. ([#9033](https://github.com/mastra-ai/mastra/pull/9033)) - Add support for external trace and parent span IDs in TracingOptions. This enables integration with external tracing systems by allowing new AI traces to be started with existing traceId and parentSpanId values. The implementation includes OpenTelemetry-compatible ID validation (32 hex chars for trace IDs, 16 hex chars for span IDs). ([#9053](https://github.com/mastra-ai/mastra/pull/9053)) - Updated `watch` and `watchAsync` methods to use proper function overloads instead of generic conditional types, ensuring compatibility with the base Run class signatures. ([#9048](https://github.com/mastra-ai/mastra/pull/9048)) - Fix tracing context propagation to agent steps in workflows ([#9074](https://github.com/mastra-ai/mastra/pull/9074)) When creating a workflow step from an agent using `createStep(myAgent)`, the tracing context was not being passed to the agent's `stream()` and `streamLegacy()` methods. This caused tracing spans to break in the workflow chain. This fix ensures that `tracingContext` is properly propagated to both agent.stream() and agent.streamLegacy() calls, matching the behavior of tool steps which already propagate tracingContext correctly. - Fixes how reasoning chunks are stored in memory to prevent data loss and ensure they are consolidated as single message parts rather than split into word-level fragments. ([#9041](https://github.com/mastra-ai/mastra/pull/9041)) - fixes an issue where input processors couldn't add system or assistant messages. Previously all messages from input processors were forced to be user messages, causing an error when trying to add other role types. ([#8835](https://github.com/mastra-ai/mastra/pull/8835)) - Add `initialState` as an option to `.streamVNext()` ([#9071](https://github.com/mastra-ai/mastra/pull/9071)) - added resourceId and runId to workflow_run metadata in ai tracing ([#9031](https://github.com/mastra-ai/mastra/pull/9031)) - Fix custom metadata preservation in UIMessages when loading threads. The `getMessagesHandler` now converts `messagesV2` (V2 format with metadata) instead of `messages` (V1 format without metadata) to AIV5.UI format. Also updates the abstract `MastraMemory.query()` return type to include `messagesV2` for proper type safety. ([#9029](https://github.com/mastra-ai/mastra/pull/9029)) - Fix TypeScript type errors when using provider-defined tools from external AI SDK packages. ([#8940](https://github.com/mastra-ai/mastra/pull/8940)) Agents can now accept provider tools like `google.tools.googleSearch()` without type errors. Creates new `@internal/external-types` package to centralize AI SDK type re-exports and adds `ProviderDefinedTool` structural type to handle tools from different package versions/instances due to TypeScript's module path discrimination. - feat(ai-tracing): Add automatic metadata extraction from RuntimeContext to spans ([#9072](https://github.com/mastra-ai/mastra/pull/9072)) Enables automatic extraction of RuntimeContext values as metadata for AI tracing spans across entire traces. Key features: - Configure `runtimeContextKeys` in TracingConfig to extract specific keys from RuntimeContext - Add per-request keys via `tracingOptions.runtimeContextKeys` for trace-specific additions - Supports dot notation for nested values (e.g., 'user.id', 'session.data.experimentId') - TraceState computed once at root span and inherited by all child spans - Explicit metadata in span options takes precedence over extracted metadata Example: ```typescript const mastra = new Mastra({ observability: { configs: { default: { runtimeContextKeys: ['userId', 'environment', 'tenantId'], }, }, }, }); await agent.generate({ messages, runtimeContext, tracingOptions: { runtimeContextKeys: ['experimentId'], // Adds to configured keys }, }); ``` - Fix provider tools for popular providers and add support for anthropic/claude skills. ([#9038](https://github.com/mastra-ai/mastra/pull/9038)) - Refactor workflowstream into workflow output with fullStream property ([#9048](https://github.com/mastra-ai/mastra/pull/9048)) - added tracing of LLM steps & chunks ([#9058](https://github.com/mastra-ai/mastra/pull/9058)) - Show agent tool output better in playground ([#9021](https://github.com/mastra-ai/mastra/pull/9021)) ## 0.21.2-alpha.0 ### Patch Changes - Update provider registry and model documentation with latest models and providers ([`c67ca32`](https://github.com/mastra-ai/mastra/commit/c67ca32e3c2cf69bfc146580770c720220ca44ac)) - Add deprecation warnings for format:ai-sdk ([#9018](https://github.com/mastra-ai/mastra/pull/9018)) - network routing agent text delta ai-sdk streaming ([#8979](https://github.com/mastra-ai/mastra/pull/8979)) - Support writing custom top level stream chunks ([#8922](https://github.com/mastra-ai/mastra/pull/8922)) - fix(core): Fix Gemini message ordering validation errors (#7287, #8053) ([#8069](https://github.com/mastra-ai/mastra/pull/8069)) Fixes Gemini API "single turn requests" validation error by ensuring the first non-system message is from the user role. This resolves errors when: - Messages start with assistant role (e.g., from memory truncation) - Tool-call sequences begin with assistant messages **Breaking Change**: Empty or system-only message lists now throw an error instead of adding a placeholder user message, preventing confusing LLM responses. This fix handles both issue #7287 (tool-call ordering) and #8053 (single-turn validation) by inserting a placeholder user message when needed. - fix(core): Validate structured output at text-end instead of flush ([#8934](https://github.com/mastra-ai/mastra/pull/8934)) Fixes structured output validation for Bedrock and LMStudio by moving validation from `flush()` to `text-end` chunk. Eliminates `finishReason` heuristics, adds special token extraction for LMStudio, and validates at the correct point in stream lifecycle. - fix model.loop.test.ts tests to use structuredOutput.schema and add assertions ([#8926](https://github.com/mastra-ai/mastra/pull/8926)) - When using OpenAI models with JSON response format, automatically enable strict schema validation. ([#8924](https://github.com/mastra-ai/mastra/pull/8924)) - Added the ability to use model router configs for embedders (eg "openai/text-embedding-ada-002") ([#8992](https://github.com/mastra-ai/mastra/pull/8992)) - Always set supportsStructuredOutputs true for openai compatible provider. ([#8933](https://github.com/mastra-ai/mastra/pull/8933)) - Support for custom resume labels mapping to step to be resumed ([#8941](https://github.com/mastra-ai/mastra/pull/8941)) - Fixed an issue where a custom URL in model router still validated unknown providers against the known providers list. Custom URL means we don't necessarily know the provider. This allows local providers like Ollama to work properly ([#8989](https://github.com/mastra-ai/mastra/pull/8989)) - feat: inject schema context into main agent for processor mode structured output ([#8886](https://github.com/mastra-ai/mastra/pull/8886)) - Added providerOptions types to generate/stream for main builtin model router providers (openai/anthropic/google/xai) ([#8995](https://github.com/mastra-ai/mastra/pull/8995)) - Generate a title for Agent.network() threads ([#8853](https://github.com/mastra-ai/mastra/pull/8853)) ## 0.21.1 ### Patch Changes - Update provider registry with latest models and providers ([`ca85c93`](https://github.com/mastra-ai/mastra/commit/ca85c932b232e6ad820c811ec176d98e68c59b0a)) - Update provider registry and model documentation with latest models and providers ([`a1d40f8`](https://github.com/mastra-ai/mastra/commit/a1d40f88d4ce42c4508774ad22e38ac582157af2)) - Ability to call agents as tools with .generate()/.stream() ([#8863](https://github.com/mastra-ai/mastra/pull/8863)) - Add runId to `agent-execution-event-{string}` and `workflow-execution-event-{string}` event streamed in network ([#8862](https://github.com/mastra-ai/mastra/pull/8862)) ## 0.21.1-alpha.0 ### Patch Changes - Update provider registry with latest models and providers ([`ca85c93`](https://github.com/mastra-ai/mastra/commit/ca85c932b232e6ad820c811ec176d98e68c59b0a)) - Update provider registry and model documentation with latest models and providers ([`a1d40f8`](https://github.com/mastra-ai/mastra/commit/a1d40f88d4ce42c4508774ad22e38ac582157af2)) - Ability to call agents as tools with .generate()/.stream() ([#8863](https://github.com/mastra-ai/mastra/pull/8863)) - Add runId to `agent-execution-event-{string}` and `workflow-execution-event-{string}` event streamed in network ([#8862](https://github.com/mastra-ai/mastra/pull/8862)) ## 0.21.0 ### Minor Changes - Standardize model configuration across all Mastra components ([#8626](https://github.com/mastra-ai/mastra/pull/8626)) All model configuration points now accept the same flexible `MastraModelConfig` type as the `Agent` class: - **Scorers**: Judge models now support magic strings, config objects, and dynamic functions - **Input/Output Processors**: ModerationProcessor and PIIDetector accept flexible model configs - **Relevance Scorers**: MastraAgentRelevanceScorer supports all model config types This change provides: - Consistent API across all components - Support for magic strings (e.g., `"openai/gpt-4o"`) - Support for OpenAI-compatible configs with custom URLs - Support for dynamic model resolution functions - Full backward compatibility with existing code Example: ```typescript // All of these now work everywhere models are accepted const scorer = createScorer({ judge: { model: 'openai/gpt-4o' }, // Magic string }); const processor = new ModerationProcessor({ model: { id: 'custom/model', url: 'https://...' }, // Custom config }); const relevanceScorer = new MastraAgentRelevanceScorer( async ctx => ctx.getModel(), // Dynamic function ); ``` - support model router in structured output and client-js ([#8686](https://github.com/mastra-ai/mastra/pull/8686)) - Update structuredOutput to use response format by default with an opt in to json prompt injection. ([#8557](https://github.com/mastra-ai/mastra/pull/8557)) Replaced internal usage of output with structuredOutput. - Standardize model configuration across all components to support flexible model resolution ([#8626](https://github.com/mastra-ai/mastra/pull/8626)) All model configuration points now accept `MastraModelConfig`, enabling consistent model specification across: - Scorers (`createScorer` and all built-in scorers) - Input/Output Processors (`ModerationProcessor`, `PIIDetector`) - Relevance Scorers (`MastraAgentRelevanceScorer`) **Supported formats:** - Magic strings: `'openai/gpt-4o-mini'` - Config objects: `{ id: 'openai/gpt-4o-mini' }` or `{ providerId: 'openai', modelId: 'gpt-4o-mini' }` - Custom endpoints: `{ id: 'custom/model', url: 'https://...', apiKey: '...' }` - Dynamic resolution: `(ctx) => 'openai/gpt-4o-mini'` This change provides a unified model configuration experience matching the `Agent` class, making it easier to switch models and use custom providers across all Mastra components. ### Patch Changes - Fix aisdk format in workflow breaking stream ([#8716](https://github.com/mastra-ai/mastra/pull/8716)) - fix: preserve providerOptions through message list conversions ([#8837](https://github.com/mastra-ai/mastra/pull/8837)) - improve error propagation in agent stream failures ([#8733](https://github.com/mastra-ai/mastra/pull/8733)) - prevent duplicate deprecation warning logs and deprecate modelSettings.abortSignal in favor of top-level abortSignal ([#8840](https://github.com/mastra-ai/mastra/pull/8840)) - Removed logging of massive model objects in tool failures ([#8839](https://github.com/mastra-ai/mastra/pull/8839)) - Create unified Sidebar component to use on Playground and Cloud ([#8655](https://github.com/mastra-ai/mastra/pull/8655)) - Added tracing of input & output processors (this includes using structuredOutput) ([#8623](https://github.com/mastra-ai/mastra/pull/8623)) - ai-sdk workflow route, agent network route ([#8672](https://github.com/mastra-ai/mastra/pull/8672)) - Handle maxRetries in agent.generate/stream properly. Add deprecation warning to top level abortSignal in AgentExecuteOptions as that property is duplicated inside of modelSettings as well. ([#8729](https://github.com/mastra-ai/mastra/pull/8729)) - Include span id and trace id when running live scorers ([#8842](https://github.com/mastra-ai/mastra/pull/8842)) - Added deprecation warnings for stream and observeStream. We will switch the implementation to streamVNext/observeStreamVNext in the future. ([#8701](https://github.com/mastra-ai/mastra/pull/8701)) - Add div wrapper around entity tables to fix table vertical position ([#8758](https://github.com/mastra-ai/mastra/pull/8758)) - Customize AITraces type to seamlessly work on Cloud too ([#8759](https://github.com/mastra-ai/mastra/pull/8759)) - Refactor EntryList component and Scorer and Observability pages ([#8652](https://github.com/mastra-ai/mastra/pull/8652)) - Add support for exporting scores for external observability providers ([#8335](https://github.com/mastra-ai/mastra/pull/8335)) - Stream finalResult from network loop ([#8795](https://github.com/mastra-ai/mastra/pull/8795)) - Fix broken `generateTitle` behaviour #8726, make `generateTitle: true` default memory setting ([#8800](https://github.com/mastra-ai/mastra/pull/8800)) - Improve README ([#8819](https://github.com/mastra-ai/mastra/pull/8819)) - nested ai-sdk workflows and networks streaming support ([#8614](https://github.com/mastra-ai/mastra/pull/8614)) ## 0.21.0-alpha.4 ### Patch Changes - Include span id and trace id when running live scorers ([#8842](https://github.com/mastra-ai/mastra/pull/8842)) ## 0.21.0-alpha.3 ### Patch Changes - prevent duplicate deprecation warning logs and deprecate modelSettings.abortSignal in favor of top-level abortSignal ([#8840](https://github.com/mastra-ai/mastra/pull/8840)) - Removed logging of massive model objects in tool failures ([#8839](https://github.com/mastra-ai/mastra/pull/8839)) ## 0.21.0-alpha.2 ### Patch Changes - fix: preserve providerOptions through message list conversions ([#8837](https://github.com/mastra-ai/mastra/pull/8837)) ## 0.21.0-alpha.1 ### Patch Changes - Fix aisdk format in workflow breaking stream ([#8716](https://github.com/mastra-ai/mastra/pull/8716)) - improve error propagation in agent stream failures ([#8733](https://github.com/mastra-ai/mastra/pull/8733)) - Create unified Sidebar component to use on Playground and Cloud ([#8655](https://github.com/mastra-ai/mastra/pull/8655)) - Added tracing of input & output processors (this includes using structuredOutput) ([#8623](https://github.com/mastra-ai/mastra/pull/8623)) - ai-sdk workflow route, agent network route ([#8672](https://github.com/mastra-ai/mastra/pull/8672)) - Handle maxRetries in agent.generate/stream properly. Add deprecation warning to top level abortSignal in AgentExecuteOptions as that property is duplicated inside of modelSettings as well. ([#8729](https://github.com/mastra-ai/mastra/pull/8729)) - Added deprecation warnings for stream and observeStream. We will switch the implementation to streamVNext/observeStreamVNext in the future. ([#8701](https://github.com/mastra-ai/mastra/pull/8701)) - Add div wrapper around entity tables to fix table vertical position ([#8758](https://github.com/mastra-ai/mastra/pull/8758)) - Customize AITraces type to seamlessly work on Cloud too ([#8759](https://github.com/mastra-ai/mastra/pull/8759)) - Stream finalResult from network loop ([#8795](https://github.com/mastra-ai/mastra/pull/8795)) - Fix broken `generateTitle` behaviour #8726, make `generateTitle: true` default memory setting ([#8800](https://github.com/mastra-ai/mastra/pull/8800)) - Improve README ([#8819](https://github.com/mastra-ai/mastra/pull/8819)) ## 0.21.0-alpha.0 ### Minor Changes - Standardize model configuration across all Mastra components ([#8626](https://github.com/mastra-ai/mastra/pull/8626)) All model configuration points now accept the same flexible `MastraModelConfig` type as the `Agent` class: - **Scorers**: Judge models now support magic strings, config objects, and dynamic functions - **Input/Output Processors**: ModerationProcessor and PIIDetector accept flexible model configs - **Relevance Scorers**: MastraAgentRelevanceScorer supports all model config types This change provides: - Consistent API across all components - Support for magic strings (e.g., `"openai/gpt-4o"`) - Support for OpenAI-compatible configs with custom URLs - Support for dynamic model resolution functions - Full backward compatibility with existing code Example: ```typescript // All of these now work everywhere models are accepted const scorer = createScorer({ judge: { model: 'openai/gpt-4o' }, // Magic string }); const processor = new ModerationProcessor({ model: { id: 'custom/model', url: 'https://...' }, // Custom config }); const relevanceScorer = new MastraAgentRelevanceScorer( async ctx => ctx.getModel(), // Dynamic function ); ``` - support model router in structured output and client-js ([#8686](https://github.com/mastra-ai/mastra/pull/8686)) - Update structuredOutput to use response format by default with an opt in to json prompt injection. ([#8557](https://github.com/mastra-ai/mastra/pull/8557)) Replaced internal usage of output with structuredOutput. - Standardize model configuration across all components to support flexible model resolution ([#8626](https://github.com/mastra-ai/mastra/pull/8626)) All model configuration points now accept `MastraModelConfig`, enabling consistent model specification across: - Scorers (`createScorer` and all built-in scorers) - Input/Output Processors (`ModerationProcessor`, `PIIDetector`) - Relevance Scorers (`MastraAgentRelevanceScorer`) **Supported formats:** - Magic strings: `'openai/gpt-4o-mini'` - Config objects: `{ id: 'openai/gpt-4o-mini' }` or `{ providerId: 'openai', modelId: 'gpt-4o-mini' }` - Custom endpoints: `{ id: 'custom/model', url: 'https://...', apiKey: '...' }` - Dynamic resolution: `(ctx) => 'openai/gpt-4o-mini'` This change provides a unified model configuration experience matching the `Agent` class, making it easier to switch models and use custom providers across all Mastra components. ### Patch Changes - Refactor EntryList component and Scorer and Observability pages ([#8652](https://github.com/mastra-ai/mastra/pull/8652)) - Add support for exporting scores for external observability providers ([#8335](https://github.com/mastra-ai/mastra/pull/8335)) - nested ai-sdk workflows and networks streaming support ([#8614](https://github.com/mastra-ai/mastra/pull/8614)) - Rename internal ai-sdk packages to have ai-v5 versions as default and ai-v4 versions as npm namespaced. Also moves ai-sdk provider packages to devDeps. ([#8687](https://github.com/mastra-ai/mastra/pull/8687)) ## 0.20.2 ### Patch Changes - Pass through input/output processors to the server agent endpoints ([#8546](https://github.com/mastra-ai/mastra/pull/8546)) - Add structuredOutput data to response message metadata so it will be persisted. ([#8588](https://github.com/mastra-ai/mastra/pull/8588)) - Add shouldPersistSnapshot to control when to persist run snapshot ([#8617](https://github.com/mastra-ai/mastra/pull/8617)) - moved ai tracing startup logs to debug level ([#8625](https://github.com/mastra-ai/mastra/pull/8625)) ## 0.20.2-alpha.1 ### Patch Changes - Pass through input/output processors to the server agent endpoints ([#8546](https://github.com/mastra-ai/mastra/pull/8546)) - moved ai tracing startup logs to debug level ([#8625](https://github.com/mastra-ai/mastra/pull/8625)) ## 0.20.2-alpha.0 ### Patch Changes - Add structuredOutput data to response message metadata so it will be persisted. ([#8588](https://github.com/mastra-ai/mastra/pull/8588)) - Add shouldPersistSnapshot to control when to persist run snapshot ([#8617](https://github.com/mastra-ai/mastra/pull/8617)) ## 0.20.1 ### Patch Changes - workflow run thread more visible ([#8539](https://github.com/mastra-ai/mastra/pull/8539)) - Add iterationCount to loop condition params ([#8579](https://github.com/mastra-ai/mastra/pull/8579)) - Mutable shared workflow run state ([#8545](https://github.com/mastra-ai/mastra/pull/8545)) - avoid refetching memory threads and messages on window focus ([#8519](https://github.com/mastra-ai/mastra/pull/8519)) - add tripwire reason in playground ([#8568](https://github.com/mastra-ai/mastra/pull/8568)) - Add validation for index creation ([#8552](https://github.com/mastra-ai/mastra/pull/8552)) - Save waiting step status in snapshot ([#8576](https://github.com/mastra-ai/mastra/pull/8576)) - Added AI SDK provider packages to model router for anthropic/google/openai/openrouter/xai ([#8559](https://github.com/mastra-ai/mastra/pull/8559)) - type fixes and missing changeset ([#8545](https://github.com/mastra-ai/mastra/pull/8545)) - Convert WorkflowWatchResult to WorkflowResult in workflow graph ([#8541](https://github.com/mastra-ai/mastra/pull/8541)) - add new deploy to cloud button ([#8549](https://github.com/mastra-ai/mastra/pull/8549)) - remove icons in entity lists ([#8520](https://github.com/mastra-ai/mastra/pull/8520)) - add client search to all entities ([#8523](https://github.com/mastra-ai/mastra/pull/8523)) - Fixed an issue where model router was adding /chat/completions to API urls when it shouldn't. ([#8589](https://github.com/mastra-ai/mastra/pull/8589)) fixed an issue with provider ID rendering in playground UI - Improve JSDoc documentation for Agent ([#8389](https://github.com/mastra-ai/mastra/pull/8389)) - Properly fix cloudflare randomUUID in global scope issue ([#8450](https://github.com/mastra-ai/mastra/pull/8450)) - Marked OTEL based telemetry as deprecated. ([#8586](https://github.com/mastra-ai/mastra/pull/8586)) - Add support for streaming nested agent tools ([#8580](https://github.com/mastra-ai/mastra/pull/8580)) - Fix TypeScript errors with provider-defined tools by updating ai-v5 and openai-v5 to matching provider-utils versions. This ensures npm deduplicates to a single provider-utils instance, resolving type incompatibility issues when passing provider tools to Agent. ([#8584](https://github.com/mastra-ai/mastra/pull/8584)) Also adds deprecation warning to Agent import from root path to encourage using the recommended subpath import. - UX for the agents page ([#8517](https://github.com/mastra-ai/mastra/pull/8517)) - add icons into playground titles + a link to the entity doc ([#8518](https://github.com/mastra-ai/mastra/pull/8518)) ## 0.20.1-alpha.4 ### Patch Changes - Fixed an issue where model router was adding /chat/completions to API urls when it shouldn't. ([#8589](https://github.com/mastra-ai/mastra/pull/8589)) fixed an issue with provider ID rendering in playground UI ## 0.20.1-alpha.3 ### Patch Changes - Marked OTEL based telemetry as deprecated. ([#8586](https://github.com/mastra-ai/mastra/pull/8586)) - Add support for streaming nested agent tools ([#8580](https://github.com/mastra-ai/mastra/pull/8580)) - Fix TypeScript errors with provider-defined tools by updating ai-v5 and openai-v5 to matching provider-utils versions. This ensures npm deduplicates to a single provider-utils instance, resolving type incompatibility issues when passing provider tools to Agent. ([#8584](https://github.com/mastra-ai/mastra/pull/8584)) Also adds deprecation warning to Agent import from root path to encourage using the recommended subpath import. ## 0.20.1-alpha.2 ### Patch Changes - Added AI SDK provider packages to model router for anthropic/google/openai/openrouter/xai ([#8559](https://github.com/mastra-ai/mastra/pull/8559)) ## 0.20.1-alpha.1 ### Patch Changes - workflow run thread more visible ([#8539](https://github.com/mastra-ai/mastra/pull/8539)) - Add iterationCount to loop condition params ([#8579](https://github.com/mastra-ai/mastra/pull/8579)) - Mutable shared workflow run state ([#8545](https://github.com/mastra-ai/mastra/pull/8545)) - avoid refetching memory threads and messages on window focus ([#8519](https://github.com/mastra-ai/mastra/pull/8519)) - add tripwire reason in playground ([#8568](https://github.com/mastra-ai/mastra/pull/8568)) - Add validation for index creation ([#8552](https://github.com/mastra-ai/mastra/pull/8552)) - Save waiting step status in snapshot ([#8576](https://github.com/mastra-ai/mastra/pull/8576)) - type fixes and missing changeset ([#8545](https://github.com/mastra-ai/mastra/pull/8545)) - Convert WorkflowWatchResult to WorkflowResult in workflow graph ([#8541](https://github.com/mastra-ai/mastra/pull/8541)) - add new deploy to cloud button ([#8549](https://github.com/mastra-ai/mastra/pull/8549)) - remove icons in entity lists ([#8520](https://github.com/mastra-ai/mastra/pull/8520)) - add client search to all entities ([#8523](https://github.com/mastra-ai/mastra/pull/8523)) - Improve JSDoc documentation for Agent ([#8389](https://github.com/mastra-ai/mastra/pull/8389)) - UX for the agents page ([#8517](https://github.com/mastra-ai/mastra/pull/8517)) - add icons into playground titles + a link to the entity doc ([#8518](https://github.com/mastra-ai/mastra/pull/8518)) ## 0.20.1-alpha.0 ### Patch Changes - Properly fix cloudflare randomUUID in global scope issue ([#8450](https://github.com/mastra-ai/mastra/pull/8450)) ## 0.20.0 ### Minor Changes - Breaking change to move the agent.streamVNext/generateVNext implementation to the default stream/generate. The old stream/generate have now been moved to streamLegacy and generateLegacy ([#8097](https://github.com/mastra-ai/mastra/pull/8097)) ### Patch Changes - Remove log drains UI from the playground ([#8379](https://github.com/mastra-ai/mastra/pull/8379)) - add refetch interval to traces to make it feel "instant" ([#8386](https://github.com/mastra-ai/mastra/pull/8386)) - better memory message ([#8382](https://github.com/mastra-ai/mastra/pull/8382)) - Add doc url to netlify gateway ([#8356](https://github.com/mastra-ai/mastra/pull/8356)) - fix codeblock line number color contrast for legacy traces ([#8385](https://github.com/mastra-ai/mastra/pull/8385)) - Fixes two issues, one where finish chunks were passed to output processors after every step, and the other where the processorState would get reset after every step, meaning that the final StructuredOutput process prompt was missing lots of context from the previous steps. ([#8373](https://github.com/mastra-ai/mastra/pull/8373)) - Convert structured output to a stream processor ([#8229](https://github.com/mastra-ai/mastra/pull/8229)) - Model router documentation and playground UI improvements ([#8372](https://github.com/mastra-ai/mastra/pull/8372)) **Documentation generation (`@mastra/core`):** - Fixed inverted dynamic model selection logic in provider examples - Improved copy: replaced marketing language with action-oriented descriptions - Added generated file comments with timestamps to all MDX outputs so maintainers know not to directly edit generated files **Playground UI model picker (`@mastra/playground-ui`):** - Fixed provider field clearing when typing in model input - Added responsive layout (stacks on mobile, side-by-side on desktop) - Improved general styling of provider/model pickers **Environment variables (`@mastra/deployer`):** - Properly handle array of env vars (e.g., NETLIFY_TOKEN, NETLIFY_SITE_ID) - Added correct singular/plural handling for "environment variable(s)" - Add approve and decline tool calls to mastra server pkg ([#8360](https://github.com/mastra-ai/mastra/pull/8360)) - Fix/8219 preserve resourceid on resume ([#8359](https://github.com/mastra-ai/mastra/pull/8359)) - Fix ai-sdk custom data output ([#8414](https://github.com/mastra-ai/mastra/pull/8414)) - show thread list in desc order ([#8381](https://github.com/mastra-ai/mastra/pull/8381)) - Fix an issue preventing showing working memory and semantic recall in the playground ([#8358](https://github.com/mastra-ai/mastra/pull/8358)) - Add observe strean to get streans after workflow has been interrupted ([#8318](https://github.com/mastra-ai/mastra/pull/8318)) ## 0.20.0-alpha.0 ### Minor Changes - Breaking change to move the agent.streamVNext/generateVNext implementation to the default stream/generate. The old stream/generate have now been moved to streamLegacy and generateLegacy ([#8097](https://github.com/mastra-ai/mastra/pull/8097)) ### Patch Changes - Remove log drains UI from the playground ([#8379](https://github.com/mastra-ai/mastra/pull/8379)) - add refetch interval to traces to make it feel "instant" ([#8386](https://github.com/mastra-ai/mastra/pull/8386)) - better memory message ([#8382](https://github.com/mastra-ai/mastra/pull/8382)) - Add doc url to netlify gateway ([#8356](https://github.com/mastra-ai/mastra/pull/8356)) - fix codeblock line number color contrast for legacy traces ([#8385](https://github.com/mastra-ai/mastra/pull/8385)) - Fixes two issues, one where finish chunks were passed to output processors after every step, and the other where the processorState would get reset after every step, meaning that the final StructuredOutput process prompt was missing lots of context from the previous steps. ([#8373](https://github.com/mastra-ai/mastra/pull/8373)) - Convert structured output to a stream processor ([#8229](https://github.com/mastra-ai/mastra/pull/8229)) - Model router documentation and playground UI improvements ([#8372](https://github.com/mastra-ai/mastra/pull/8372)) **Documentation generation (`@mastra/core`):** - Fixed inverted dynamic model selection logic in provider examples - Improved copy: replaced marketing language with action-oriented descriptions - Added generated file comments with timestamps to all MDX outputs so maintainers know not to directly edit generated files **Playground UI model picker (`@mastra/playground-ui`):** - Fixed provider field clearing when typing in model input - Added responsive layout (stacks on mobile, side-by-side on desktop) - Improved general styling of provider/model pickers **Environment variables (`@mastra/deployer`):** - Properly handle array of env vars (e.g., NETLIFY_TOKEN, NETLIFY_SITE_ID) - Added correct singular/plural handling for "environment variable(s)" - Add approve and decline tool calls to mastra server pkg ([#8360](https://github.com/mastra-ai/mastra/pull/8360)) - Fix/8219 preserve resourceid on resume ([#8359](https://github.com/mastra-ai/mastra/pull/8359)) - Fix ai-sdk custom data output ([#8414](https://github.com/mastra-ai/mastra/pull/8414)) - show thread list in desc order ([#8381](https://github.com/mastra-ai/mastra/pull/8381)) - Fix an issue preventing showing working memory and semantic recall in the playground ([#8358](https://github.com/mastra-ai/mastra/pull/8358)) - Add observe strean to get streans after workflow has been interrupted ([#8318](https://github.com/mastra-ai/mastra/pull/8318)) ## 0.19.1 ### Patch Changes - disable network label when memory is not enabled OR the agent has no subagents ([#8341](https://github.com/mastra-ai/mastra/pull/8341)) - Added Mastra model router to Playground UI ([#8332](https://github.com/mastra-ai/mastra/pull/8332)) - Netlify gateway support to the model router. Now accepts strings like "netlify/openai/gpt-5". ([#8331](https://github.com/mastra-ai/mastra/pull/8331)) ## 0.19.1-alpha.1 ### Patch Changes - disable network label when memory is not enabled OR the agent has no subagents ([#8341](https://github.com/mastra-ai/mastra/pull/8341)) ## 0.19.1-alpha.0 ### Patch Changes - Added Mastra model router to Playground UI ([#8332](https://github.com/mastra-ai/mastra/pull/8332)) - Netlify gateway support to the model router. Now accepts strings like "netlify/openai/gpt-5". ([#8331](https://github.com/mastra-ai/mastra/pull/8331)) ## 0.19.0 ### Minor Changes - Add spanId column to scores table ([#8154](https://github.com/mastra-ai/mastra/pull/8154)) - changed ai_trace_spans table schema to use text for span_type column. ([#8027](https://github.com/mastra-ai/mastra/pull/8027)) ### Patch Changes - Remove legacy helpers ([#8017](https://github.com/mastra-ai/mastra/pull/8017)) - add a way to hide the deploy mastra cloud button ([#8137](https://github.com/mastra-ai/mastra/pull/8137)) - Core error processing - safeParse error object ([#8312](https://github.com/mastra-ai/mastra/pull/8312)) - Fix score input and output types ([#8153](https://github.com/mastra-ai/mastra/pull/8153)) - fix cloudflare deployer build ([#8105](https://github.com/mastra-ai/mastra/pull/8105)) - make suspend optional and move types.ts containing DynamicArgument to types folder ([#8305](https://github.com/mastra-ai/mastra/pull/8305)) - When an error would happen in a function like onStepResult, there are other code that executes synchronously and will execute after the controller already closes. We need to make sure we're only trying to enqueue chunks when the controller is still open. ([#8186](https://github.com/mastra-ai/mastra/pull/8186)) - Bring back ToolInvocationOptions for createTool execute function ([#8206](https://github.com/mastra-ai/mastra/pull/8206)) - Throw is memory is not passed to the routing agent. ([#8313](https://github.com/mastra-ai/mastra/pull/8313)) - Return the selection reason as the result if the agent could not route and pick a primitive ([#8308](https://github.com/mastra-ai/mastra/pull/8308)) - Mastra model router ([#8235](https://github.com/mastra-ai/mastra/pull/8235)) - Fix generateVNext tripwire return value ([#8122](https://github.com/mastra-ai/mastra/pull/8122)) - Fixed createTool types due totight coupling to Zod's internal structure, which changed between v3 and v4. Instead of checking for exact Zod types, we now use structural typing - checking for the presence of parse/safeParse methods ([#8150](https://github.com/mastra-ai/mastra/pull/8150)) - Fixes agent.network() memory tools (working memory, vector search) as well as fixes tool calling and workflow calling in general. Various clean up for the agent.network() code path. ([#8157](https://github.com/mastra-ai/mastra/pull/8157)) - fixNetworkChunkType ([#8210](https://github.com/mastra-ai/mastra/pull/8210)) - Show model that worked when there are model fallbacks ([#8167](https://github.com/mastra-ai/mastra/pull/8167)) - Add input data validation to workflow step execution ([#7779](https://github.com/mastra-ai/mastra/pull/7779)) Add resume data validation to resume workflow method Add input data validation to start workflow method Use default value from inputSchema/resumeSchema - Add types in the streamVNext codepath, fixes for various issues across multiple packages surfaced from type issues, align return types. ([#8010](https://github.com/mastra-ai/mastra/pull/8010)) - Support tracing options for workflow streaming endpoints ([#8278](https://github.com/mastra-ai/mastra/pull/8278)) - Adjust deprecation warnings ([#8326](https://github.com/mastra-ai/mastra/pull/8326)) - Improve error processing -don't mask useful errors ([#8270](https://github.com/mastra-ai/mastra/pull/8270)) - When step is created from agent or tool, add the description and component key to show that ([#8151](https://github.com/mastra-ai/mastra/pull/8151)) - [CLOUD-500] Refactor trace transform to agent payload ([#8280](https://github.com/mastra-ai/mastra/pull/8280)) ## 0.19.0-alpha.1 ### Minor Changes - Add spanId column to scores table ([#8154](https://github.com/mastra-ai/mastra/pull/8154)) - changed ai_trace_spans table schema to use text for span_type column. ([#8027](https://github.com/mastra-ai/mastra/pull/8027)) ### Patch Changes - Core error processing - safeParse error object ([#8312](https://github.com/mastra-ai/mastra/pull/8312)) - Fix score input and output types ([#8153](https://github.com/mastra-ai/mastra/pull/8153)) - make suspend optional and move types.ts containing DynamicArgument to types folder ([#8305](https://github.com/mastra-ai/mastra/pull/8305)) - Bring back ToolInvocationOptions for createTool execute function ([#8206](https://github.com/mastra-ai/mastra/pull/8206)) - Throw is memory is not passed to the routing agent. ([#8313](https://github.com/mastra-ai/mastra/pull/8313)) - Return the selection reason as the result if the agent could not route and pick a primitive ([#8308](https://github.com/mastra-ai/mastra/pull/8308)) - Mastra model router ([#8235](https://github.com/mastra-ai/mastra/pull/8235)) - fixNetworkChunkType ([#8210](https://github.com/mastra-ai/mastra/pull/8210)) - Show model that worked when there are model fallbacks ([#8167](https://github.com/mastra-ai/mastra/pull/8167)) - Support tracing options for workflow streaming endpoints ([#8278](https://github.com/mastra-ai/mastra/pull/8278)) - Improve error processing -don't mask useful errors ([#8270](https://github.com/mastra-ai/mastra/pull/8270)) - [CLOUD-500] Refactor trace transform to agent payload ([#8280](https://github.com/mastra-ai/mastra/pull/8280)) ## 0.18.1-alpha.0 ### Patch Changes - Remove legacy helpers ([#8017](https://github.com/mastra-ai/mastra/pull/8017)) - add a way to hide the deploy mastra cloud button ([#8137](https://github.com/mastra-ai/mastra/pull/8137)) - fix cloudflare deployer build ([#8105](https://github.com/mastra-ai/mastra/pull/8105)) - When an error would happen in a function like onStepResult, there are other code that executes synchronously and will execute after the controller already closes. We need to make sure we're only trying to enqueue chunks when the controller is still open. ([#8186](https://github.com/mastra-ai/mastra/pull/8186)) - Fix generateVNext tripwire return value ([#8122](https://github.com/mastra-ai/mastra/pull/8122)) - Fixed createTool types due totight coupling to Zod's internal structure, which changed between v3 and v4. Instead of checking for exact Zod types, we now use structural typing - checking for the presence of parse/safeParse methods ([#8150](https://github.com/mastra-ai/mastra/pull/8150)) - Fixes agent.network() memory tools (working memory, vector search) as well as fixes tool calling and workflow calling in general. Various clean up for the agent.network() code path. ([#8157](https://github.com/mastra-ai/mastra/pull/8157)) - Add input data validation to workflow step execution ([#7779](https://github.com/mastra-ai/mastra/pull/7779)) Add resume data validation to resume workflow method Add input data validation to start workflow method Use default value from inputSchema/resumeSchema - Add types in the streamVNext codepath, fixes for various issues across multiple packages surfaced from type issues, align return types. ([#8010](https://github.com/mastra-ai/mastra/pull/8010)) - When step is created from agent or tool, add the description and component key to show that ([#8151](https://github.com/mastra-ai/mastra/pull/8151)) ## 0.18.0 ### Minor Changes - Allow agent instructions to accept SystemMessage types ([#7987](https://github.com/mastra-ai/mastra/pull/7987)) Agents can now use rich instruction formats beyond simple strings: - CoreSystemMessage and SystemModelMessage objects with provider-specific options - Arrays of strings or system messages - Dynamic instructions returning any SystemMessage type ### Patch Changes - Agent type fixes ([#8072](https://github.com/mastra-ai/mastra/pull/8072)) - Fixes for `getStepResult` in workflow steps ([#8065](https://github.com/mastra-ai/mastra/pull/8065)) - fix: result object type inference when using structuredOutput and unify output/structuredOutput types with single OUTPUT generic ([#7969](https://github.com/mastra-ai/mastra/pull/7969)) - feat: implement trace scoring with batch processing capabilities ([#8033](https://github.com/mastra-ai/mastra/pull/8033)) - Fix selection of agent method based on model version ([#8001](https://github.com/mastra-ai/mastra/pull/8001)) - show the tool-output stream in the playground for streamVNext ([#7983](https://github.com/mastra-ai/mastra/pull/7983)) - Add scorer type, for automatic type inferrence when creating scorers for agents ([#8032](https://github.com/mastra-ai/mastra/pull/8032)) - Get rid off swr one for all ([#7931](https://github.com/mastra-ai/mastra/pull/7931)) - Fix PostgreSQL vector index recreation issue and add optional index configuration ([#8020](https://github.com/mastra-ai/mastra/pull/8020)) - Fixed critical bug where memory vector indexes were unnecessarily recreated on every operation - Added support for configuring vector index types (HNSW, IVFFlat, flat) and parameters - Fix navigating between scores and entity types ([#8129](https://github.com/mastra-ai/mastra/pull/8129)) - Delayed streamVNext breaking change notice by 1 week ([#8121](https://github.com/mastra-ai/mastra/pull/8121)) - Tool hitl ([#8084](https://github.com/mastra-ai/mastra/pull/8084)) - Updated dependencies [[`b61b8e0`](https://github.com/mastra-ai/mastra/commit/b61b8e0b0e93a7e6e9d82e6f0b620bb919a20bdb)]: - @mastra/schema-compat@0.11.4 ## 0.18.0-alpha.3 ### Patch Changes - feat: implement trace scoring with batch processing capabilities ([#8033](https://github.com/mastra-ai/mastra/pull/8033)) - Fix PostgreSQL vector index recreation issue and add optional index configuration ([#8020](https://github.com/mastra-ai/mastra/pull/8020)) - Fixed critical bug where memory vector indexes were unnecessarily recreated on every operation - Added support for configuring vector index types (HNSW, IVFFlat, flat) and parameters - Fix navigating between scores and entity types ([#8129](https://github.com/mastra-ai/mastra/pull/8129)) - Delayed streamVNext breaking change notice by 1 week ([#8121](https://github.com/mastra-ai/mastra/pull/8121)) - Tool hitl ([#8084](https://github.com/mastra-ai/mastra/pull/8084)) - Updated dependencies [[`b61b8e0`](https://github.com/mastra-ai/mastra/commit/b61b8e0b0e93a7e6e9d82e6f0b620bb919a20bdb)]: - @mastra/schema-compat@0.11.4-alpha.0 ## 0.18.0-alpha.2 ### Minor Changes - Allow agent instructions to accept SystemMessage types ([#7987](https://github.com/mastra-ai/mastra/pull/7987)) Agents can now use rich instruction formats beyond simple strings: - CoreSystemMessage and SystemModelMessage objects with provider-specific options - Arrays of strings or system messages - Dynamic instructions returning any SystemMessage type ### Patch Changes - Agent type fixes ([#8072](https://github.com/mastra-ai/mastra/pull/8072)) - Fixes for `getStepResult` in workflow steps ([#8065](https://github.com/mastra-ai/mastra/pull/8065)) - Add scorer type, for automatic type inferrence when creating scorers for agents ([#8032](https://github.com/mastra-ai/mastra/pull/8032)) ## 0.17.2-alpha.1 ### Patch Changes - show the tool-output stream in the playground for streamVNext ([#7983](https://github.com/mastra-ai/mastra/pull/7983)) ## 0.17.2-alpha.0 ### Patch Changes - fix: result object type inference when using structuredOutput and unify output/structuredOutput types with single OUTPUT generic ([#7969](https://github.com/mastra-ai/mastra/pull/7969)) - Fix selection of agent method based on model version ([#8001](https://github.com/mastra-ai/mastra/pull/8001)) - Get rid off swr one for all ([#7931](https://github.com/mastra-ai/mastra/pull/7931)) ## 0.17.1 ### Patch Changes - Refactor agent.#execute fn workflow to make code easier to follow. ([#7964](https://github.com/mastra-ai/mastra/pull/7964)) - fix workflow resuming issue in the playground ([#7988](https://github.com/mastra-ai/mastra/pull/7988)) - feat: Add system option support to VNext methods ([#7925](https://github.com/mastra-ai/mastra/pull/7925)) ## 0.17.1-alpha.0 ### Patch Changes - Refactor agent.#execute fn workflow to make code easier to follow. ([#7964](https://github.com/mastra-ai/mastra/pull/7964)) - fix workflow resuming issue in the playground ([#7988](https://github.com/mastra-ai/mastra/pull/7988)) - feat: Add system option support to VNext methods ([#7925](https://github.com/mastra-ai/mastra/pull/7925)) ## 0.17.0 ### Minor Changes - Remove original AgentNetwork ([#7919](https://github.com/mastra-ai/mastra/pull/7919)) - Fully deprecated createRun (now throws an error) in favour of createRunAsync ([#7897](https://github.com/mastra-ai/mastra/pull/7897)) - Improved workspace dependency resolution during development and builds. This makes the build process more reliable when working with monorepos and workspace packages, reducing potential bundling errors and improving development experience. ([#7619](https://github.com/mastra-ai/mastra/pull/7619)) ### Patch Changes - dependencies updates: ([#7861](https://github.com/mastra-ai/mastra/pull/7861)) - Updated dependency [`hono@^4.9.7` ↗︎](https://www.npmjs.com/package/hono/v/4.9.7) (from `^4.9.6`, in `dependencies`) - Updated SensitiveDataFilter to be less greedy in its redacting ([#7840](https://github.com/mastra-ai/mastra/pull/7840)) - clean up console logs in monorepo ([#7926](https://github.com/mastra-ai/mastra/pull/7926)) - Update dependencies ai-v5 and @ai-sdk/provider-utils-v5 to latest ([#7884](https://github.com/mastra-ai/mastra/pull/7884)) - Added the ability to hide internal ai tracing spans (enabled by default) ([#7764](https://github.com/mastra-ai/mastra/pull/7764)) - "refactored ai tracing to commonize types" ([#7744](https://github.com/mastra-ai/mastra/pull/7744)) - Register server cache in Mastra ([#7946](https://github.com/mastra-ai/mastra/pull/7946)) - feat: add requiresAuth option for custom API routes ([#7703](https://github.com/mastra-ai/mastra/pull/7703)) Added a new `requiresAuth` option to the `ApiRoute` type that allows users to explicitly control authentication requirements for custom endpoints. - By default, all custom routes require authentication (`requiresAuth: true`) - Set `requiresAuth: false` to make a route publicly accessible without authentication - The auth middleware now checks this configuration before applying authentication Example usage: ```typescript const customRoutes: ApiRoute[] = [ { path: '/api/public-endpoint', method: 'GET', requiresAuth: false, // No authentication required handler: async c => c.json({ message: 'Public access' }), }, { path: '/api/protected-endpoint', method: 'GET', requiresAuth: true, // Authentication required (default) handler: async c => c.json({ message: 'Protected access' }), }, ]; ``` This addresses issue #7674 where custom endpoints were not being protected by the authentication system. - Resumable streams ([#7949](https://github.com/mastra-ai/mastra/pull/7949)) - Only log stream/generate deprecation warning once ([#7905](https://github.com/mastra-ai/mastra/pull/7905)) - Add support for running the Mastra dev server over HTTPS for local development. ([#7871](https://github.com/mastra-ai/mastra/pull/7871)) - Add `--https` flag for `mastra dev`. This automatically creates a local key and certificate for you. - Alternatively, you can provide your own key and cert through `server.https`: ```ts // src/mastra/index.ts import { Mastra } from '@mastra/core/mastra'; import fs from 'node:fs'; export const mastra = new Mastra({ server: { https: { key: fs.readFileSync('path/to/key.pem'), cert: fs.readFileSync('path/to/cert.pem'), }, }, }); ``` - refactored handling of internal ai spans to be more intelligent ([#7876](https://github.com/mastra-ai/mastra/pull/7876)) - Improve error message when using V1 model with streamVNext ([#7948](https://github.com/mastra-ai/mastra/pull/7948)) - prevent out-of-order span errors in ai-tracing DefaultExporter ([#7895](https://github.com/mastra-ai/mastra/pull/7895)) - move ToolExecutionOptions and ToolCallOptions to a union type (ToolInvocationOptions) for use in createTool, Tool, and ToolAction ([#7914](https://github.com/mastra-ai/mastra/pull/7914)) - avoid refetching on error when resolving a workflow in cloud ([#7842](https://github.com/mastra-ai/mastra/pull/7842)) - fix scorers table link full row ([#7915](https://github.com/mastra-ai/mastra/pull/7915)) - fix(core): handle JSON code blocks in structured output streaming ([#7864](https://github.com/mastra-ai/mastra/pull/7864)) - Postgresql Storage Query Index Performance: Adds index operations and automatic indexing for Postgresql ([#7757](https://github.com/mastra-ai/mastra/pull/7757)) - adjust the way we display scorers in agent metadata ([#7910](https://github.com/mastra-ai/mastra/pull/7910)) - fix: support destructuring of streamVNext return values ([#7920](https://github.com/mastra-ai/mastra/pull/7920)) - Fix VNext generate/stream usage tokens. They used to be undefined, now we are receiving the proper values. ([#7901](https://github.com/mastra-ai/mastra/pull/7901)) - Add model fallbacks ([#7126](https://github.com/mastra-ai/mastra/pull/7126)) - Add resource id to workflow run snapshots ([#7740](https://github.com/mastra-ai/mastra/pull/7740)) - Fixes assistant message ids when using toUIMessageStream, preserves the original messageId rather than creating a new id for this message. ([#7783](https://github.com/mastra-ai/mastra/pull/7783)) - Fixes multiple issues with stopWhen and step results. ([#7862](https://github.com/mastra-ai/mastra/pull/7862)) - fix error message when fetching observability things ([#7956](https://github.com/mastra-ai/mastra/pull/7956)) - Network stream class when calling agent.network() ([#7763](https://github.com/mastra-ai/mastra/pull/7763)) - fix workflows runs fetching and displaying ([#7852](https://github.com/mastra-ai/mastra/pull/7852)) - fix empty state for scorers on agent page ([#7846](https://github.com/mastra-ai/mastra/pull/7846)) - Remove extraneous console.log ([#7916](https://github.com/mastra-ai/mastra/pull/7916)) - Deprecate "output" in generate and stream VNext in favour of structuredOutput. When structuredOutput is used in tandem with maxSteps = 1, the structuredOutput processor won't run, it'll generate the output using the main agent, similar to how "output" used to work. ([#7750](https://github.com/mastra-ai/mastra/pull/7750)) - Fix switch in prompt-injection ([#7951](https://github.com/mastra-ai/mastra/pull/7951)) ## 0.17.0-alpha.8 ### Patch Changes - Improve error message when using V1 model with streamVNext ([#7948](https://github.com/mastra-ai/mastra/pull/7948)) - Fix VNext generate/stream usage tokens. They used to be undefined, now we are receiving the proper values. ([#7901](https://github.com/mastra-ai/mastra/pull/7901)) ## 0.17.0-alpha.7 ### Patch Changes - fix error message when fetching observability things ([#7956](https://github.com/mastra-ai/mastra/pull/7956)) ## 0.17.0-alpha.6 ### Minor Changes - Remove original AgentNetwork ([#7919](https://github.com/mastra-ai/mastra/pull/7919)) ### Patch Changes - dependencies updates: ([#7861](https://github.com/mastra-ai/mastra/pull/7861)) - Updated dependency [`hono@^4.9.7` ↗︎](https://www.npmjs.com/package/hono/v/4.9.7) (from `^4.9.6`, in `dependencies`) - clean up console logs in monorepo ([#7926](https://github.com/mastra-ai/mastra/pull/7926)) - Register server cache in Mastra ([#7946](https://github.com/mastra-ai/mastra/pull/7946)) - Resumable streams ([#7949](https://github.com/mastra-ai/mastra/pull/7949)) - move ToolExecutionOptions and ToolCallOptions to a union type (ToolInvocationOptions) for use in createTool, Tool, and ToolAction ([#7914](https://github.com/mastra-ai/mastra/pull/7914)) - fix scorers table link full row ([#7915](https://github.com/mastra-ai/mastra/pull/7915)) - adjust the way we display scorers in agent metadata ([#7910](https://github.com/mastra-ai/mastra/pull/7910)) - fix: support destructuring of streamVNext return values ([#7920](https://github.com/mastra-ai/mastra/pull/7920)) - Remove extraneous console.log ([#7916](https://github.com/mastra-ai/mastra/pull/7916)) - Fix switch in prompt-injection ([#7951](https://github.com/mastra-ai/mastra/pull/7951)) ## 0.17.0-alpha.5 ### Patch Changes - Only log stream/generate deprecation warning once ([#7905](https://github.com/mastra-ai/mastra/pull/7905)) ## 0.17.0-alpha.4 ### Minor Changes - Fully deprecated createRun (now throws an error) in favour of createRunAsync ([#7897](https://github.com/mastra-ai/mastra/pull/7897)) ### Patch Changes - Update dependencies ai-v5 and @ai-sdk/provider-utils-v5 to latest ([#7884](https://github.com/mastra-ai/mastra/pull/7884)) - refactored handling of internal ai spans to be more intelligent ([#7876](https://github.com/mastra-ai/mastra/pull/7876)) - prevent out-of-order span errors in ai-tracing DefaultExporter ([#7895](https://github.com/mastra-ai/mastra/pull/7895)) - Fixes multiple issues with stopWhen and step results. ([#7862](https://github.com/mastra-ai/mastra/pull/7862)) ## 0.17.0-alpha.3 ### Minor Changes - Improved workspace dependency resolution during development and builds. This makes the build process more reliable when working with monorepos and workspace packages, reducing potential bundling errors and improving development experience. ([#7619](https://github.com/mastra-ai/mastra/pull/7619)) ### Patch Changes - Updated SensitiveDataFilter to be less greedy in its redacting ([#7840](https://github.com/mastra-ai/mastra/pull/7840)) - Add support for running the Mastra dev server over HTTPS for local development. ([#7871](https://github.com/mastra-ai/mastra/pull/7871)) - Add `--https` flag for `mastra dev`. This automatically creates a local key and certificate for you. - Alternatively, you can provide your own key and cert through `server.https`: ```ts // src/mastra/index.ts import { Mastra } from '@mastra/core/mastra'; import fs from 'node:fs'; export const mastra = new Mastra({ server: { https: { key: fs.readFileSync('path/to/key.pem'), cert: fs.readFileSync('path/to/cert.pem'), }, }, }); ``` - avoid refetching on error when resolving a workflow in cloud ([#7842](https://github.com/mastra-ai/mastra/pull/7842)) - fix(core): handle JSON code blocks in structured output streaming ([#7864](https://github.com/mastra-ai/mastra/pull/7864)) - Add model fallbacks ([#7126](https://github.com/mastra-ai/mastra/pull/7126)) - fix workflows runs fetching and displaying ([#7852](https://github.com/mastra-ai/mastra/pull/7852)) - fix empty state for scorers on agent page ([#7846](https://github.com/mastra-ai/mastra/pull/7846)) ## 0.16.4-alpha.2 ### Patch Changes - Postgresql Storage Query Index Performance: Adds index operations and automatic indexing for Postgresql ([#7757](https://github.com/mastra-ai/mastra/pull/7757)) - Fixes assistant message ids when using toUIMessageStream, preserves the original messageId rather than creating a new id for this message. ([#7783](https://github.com/mastra-ai/mastra/pull/7783)) ## 0.16.4-alpha.1 ### Patch Changes - Add resource id to workflow run snapshots ([#7740](https://github.com/mastra-ai/mastra/pull/7740)) ## 0.16.4-alpha.0 ### Patch Changes - Added the ability to hide internal ai tracing spans (enabled by default) ([#7764](https://github.com/mastra-ai/mastra/pull/7764)) - "refactored ai tracing to commonize types" ([#7744](https://github.com/mastra-ai/mastra/pull/7744)) - feat: add requiresAuth option for custom API routes ([#7703](https://github.com/mastra-ai/mastra/pull/7703)) Added a new `requiresAuth` option to the `ApiRoute` type that allows users to explicitly control authentication requirements for custom endpoints. - By default, all custom routes require authentication (`requiresAuth: true`) - Set `requiresAuth: false` to make a route publicly accessible without authentication - The auth middleware now checks this configuration before applying authentication Example usage: ```typescript const customRoutes: ApiRoute[] = [ { path: '/api/public-endpoint', method: 'GET', requiresAuth: false, // No authentication required handler: async c => c.json({ message: 'Public access' }), }, { path: '/api/protected-endpoint', method: 'GET', requiresAuth: true, // Authentication required (default) handler: async c => c.json({ message: 'Protected access' }), }, ]; ``` This addresses issue #7674 where custom endpoints were not being protected by the authentication system. - Network stream class when calling agent.network() ([#7763](https://github.com/mastra-ai/mastra/pull/7763)) - Deprecate "output" in generate and stream VNext in favour of structuredOutput. When structuredOutput is used in tandem with maxSteps = 1, the structuredOutput processor won't run, it'll generate the output using the main agent, similar to how "output" used to work. ([#7750](https://github.com/mastra-ai/mastra/pull/7750)) ## 0.16.3 ### Patch Changes - dependencies updates: ([#7545](https://github.com/mastra-ai/mastra/pull/7545)) - Updated dependency [`hono@^4.9.6` ↗︎](https://www.npmjs.com/package/hono/v/4.9.6) (from `^4.8.12`, in `dependencies`) - Delayed deprecation notice for streamVNext() replacing stream() until Sept 23rd ([#7739](https://github.com/mastra-ai/mastra/pull/7739)) - Fix onFinish callback in VNext functions to properly resolve the result ([#7733](https://github.com/mastra-ai/mastra/pull/7733)) - support JSONSchema7 output option with generateVNext, streamVNext ([#7630](https://github.com/mastra-ai/mastra/pull/7630)) - various improvements to input & output data on ai spans ([#7636](https://github.com/mastra-ai/mastra/pull/7636)) - cleanup ([#7736](https://github.com/mastra-ai/mastra/pull/7736)) - add network method ([#7704](https://github.com/mastra-ai/mastra/pull/7704)) - Fix memory not being affected by agent output processors (#7087). Output processors now correctly modify messages before they are saved to memory storage. The fix ensures that any transformations applied by output processors (like redacting sensitive information) are properly propagated to the memory system. ([#7647](https://github.com/mastra-ai/mastra/pull/7647)) - Fix agent structuredOutput option types ([#7668](https://github.com/mastra-ai/mastra/pull/7668)) - "added output to agent spans in ai-tracing" ([#7717](https://github.com/mastra-ai/mastra/pull/7717)) - Ensure system messages are persisted in processedList ([#7715](https://github.com/mastra-ai/mastra/pull/7715)) - AN Merge pt 1 ([#7702](https://github.com/mastra-ai/mastra/pull/7702)) - Custom metadata for traces can now be set when starting agents or workflows ([#7689](https://github.com/mastra-ai/mastra/pull/7689)) - Workflow & Agent executions now return traceId. ([#7663](https://github.com/mastra-ai/mastra/pull/7663)) - fixed bugs in observability config parsing ([#7669](https://github.com/mastra-ai/mastra/pull/7669)) - fix playground UI issue about dynmic workflow exec in agent thread ([#7665](https://github.com/mastra-ai/mastra/pull/7665)) - Updated dependencies [[`779d469`](https://github.com/mastra-ai/mastra/commit/779d469366bb9f7fcb6d1638fdabb9f3acc49218)]: - @mastra/schema-compat@0.11.3 ## 0.16.3-alpha.1 ### Patch Changes - Delayed deprecation notice for streamVNext() replacing stream() until Sept 23rd ([#7739](https://github.com/mastra-ai/mastra/pull/7739)) - Fix onFinish callback in VNext functions to properly resolve the result ([#7733](https://github.com/mastra-ai/mastra/pull/7733)) - cleanup ([#7736](https://github.com/mastra-ai/mastra/pull/7736)) ## 0.16.3-alpha.0 ### Patch Changes - dependencies updates: ([#7545](https://github.com/mastra-ai/mastra/pull/7545)) - Updated dependency [`hono@^4.9.6` ↗︎](https://www.npmjs.com/package/hono/v/4.9.6) (from `^4.8.12`, in `dependencies`) - support JSONSchema7 output option with generateVNext, streamVNext ([#7630](https://github.com/mastra-ai/mastra/pull/7630)) - various improvements to input & output data on ai spans ([#7636](https://github.com/mastra-ai/mastra/pull/7636)) - add network method ([#7704](https://github.com/mastra-ai/mastra/pull/7704)) - Fix memory not being affected by agent output processors (#7087). Output processors now correctly modify messages before they are saved to memory storage. The fix ensures that any transformations applied by output processors (like redacting sensitive information) are properly propagated to the memory system. ([#7647](https://github.com/mastra-ai/mastra/pull/7647)) - Fix agent structuredOutput option types ([#7668](https://github.com/mastra-ai/mastra/pull/7668)) - "added output to agent spans in ai-tracing" ([#7717](https://github.com/mastra-ai/mastra/pull/7717)) - Ensure system messages are persisted in processedList ([#7715](https://github.com/mastra-ai/mastra/pull/7715)) - AN Merge pt 1 ([#7702](https://github.com/mastra-ai/mastra/pull/7702)) - Custom metadata for traces can now be set when starting agents or workflows ([#7689](https://github.com/mastra-ai/mastra/pull/7689)) - Workflow & Agent executions now return traceId. ([#7663](https://github.com/mastra-ai/mastra/pull/7663)) - fixed bugs in observability config parsing ([#7669](https://github.com/mastra-ai/mastra/pull/7669)) - fix playground UI issue about dynmic workflow exec in agent thread ([#7665](https://github.com/mastra-ai/mastra/pull/7665)) - Updated dependencies [[`779d469`](https://github.com/mastra-ai/mastra/commit/779d469366bb9f7fcb6d1638fdabb9f3acc49218)]: - @mastra/schema-compat@0.11.3-alpha.0 ## 0.16.2 ### Patch Changes - Export server types ([#7657](https://github.com/mastra-ai/mastra/pull/7657)) ## 0.16.2-alpha.0 ### Patch Changes - Export server types ([#7657](https://github.com/mastra-ai/mastra/pull/7657)) ## 0.16.1 ### Patch Changes - Fixed ai tracing for workflows nested directly in agents ([#7599](https://github.com/mastra-ai/mastra/pull/7599)) - Fixed provider defined tools for stream/generate vnext ([#7642](https://github.com/mastra-ai/mastra/pull/7642)) - Made tracing context optional on tool execute() ([#7532](https://github.com/mastra-ai/mastra/pull/7532)) - Fixed ai tracing context propagation in tool calls ([#7531](https://github.com/mastra-ai/mastra/pull/7531)) - Call getMemoryMessages even during first turn in a thread when semantic recall scope is resource ([#7529](https://github.com/mastra-ai/mastra/pull/7529)) - add usage and total usage to streamVNext onFinish callback ([#7598](https://github.com/mastra-ai/mastra/pull/7598)) - Add prepareStep to generate/stream VNext options. ([#7646](https://github.com/mastra-ai/mastra/pull/7646)) - Change to createRunAsync ([#7632](https://github.com/mastra-ai/mastra/pull/7632)) - Fix type in worfklow ([#7519](https://github.com/mastra-ai/mastra/pull/7519)) - Execute tool calls in parallel in generate/stream VNext methods ([#7524](https://github.com/mastra-ai/mastra/pull/7524)) - Allow streamVNext and generateVNext to use structuredOutputs from the MastraClient ([#7597](https://github.com/mastra-ai/mastra/pull/7597)) - Use workflow streamVNext in playground ([#7575](https://github.com/mastra-ai/mastra/pull/7575)) - Revert "feat(mcp): add createMCPTool helper for proper execute types" ([#7513](https://github.com/mastra-ai/mastra/pull/7513)) - Fix InvalidDataContentError when using image messages with AI SDK ([#7542](https://github.com/mastra-ai/mastra/pull/7542)) Resolves an issue where passing image content in messages would throw an InvalidDataContentError. The fix properly handles multi-part content arrays containing both text and image parts when converting between Mastra and AI SDK message formats. - Flatten loop config in stream options and pass to loop options ([#7643](https://github.com/mastra-ai/mastra/pull/7643)) - Pass mastra instance into MCP Server tools ([#7520](https://github.com/mastra-ai/mastra/pull/7520)) - Fix image input handling for Google Gemini models in AI SDK V5 ([#7490](https://github.com/mastra-ai/mastra/pull/7490)) Resolves issue #7362 where Gemini threw `AI_InvalidDataContentError` when receiving URLs in image parts. The fix properly handles V3 message file parts that contain both URL and data fields, ensuring URLs are passed as URLs rather than being incorrectly treated as base64 data. - Vnext output schema injection ([#6990](https://github.com/mastra-ai/mastra/pull/6990)) - removed duplicate 'float' switch case ([#7516](https://github.com/mastra-ai/mastra/pull/7516)) - Fix issue with response message id consistency between stream/generate response and the message ids saveed in the DB. Also fixed the custom generatorId implementation to work with this. ([#7606](https://github.com/mastra-ai/mastra/pull/7606)) ## 0.16.1-alpha.3 ### Patch Changes - Add prepareStep to generate/stream VNext options. ([#7646](https://github.com/mastra-ai/mastra/pull/7646)) ## 0.16.1-alpha.2 ### Patch Changes - Fixed provider defined tools for stream/generate vnext ([#7642](https://github.com/mastra-ai/mastra/pull/7642)) - Change to createRunAsync ([#7632](https://github.com/mastra-ai/mastra/pull/7632)) - Flatten loop config in stream options and pass to loop options ([#7643](https://github.com/mastra-ai/mastra/pull/7643)) - Fix issue with response message id consistency between stream/generate response and the message ids saveed in the DB. Also fixed the custom generatorId implementation to work with this. ([#7606](https://github.com/mastra-ai/mastra/pull/7606)) ## 0.16.1-alpha.1 ### Patch Changes - Fixed ai tracing for workflows nested directly in agents ([#7599](https://github.com/mastra-ai/mastra/pull/7599)) - Fixed ai tracing context propagation in tool calls ([#7531](https://github.com/mastra-ai/mastra/pull/7531)) - add usage and total usage to streamVNext onFinish callback ([#7598](https://github.com/mastra-ai/mastra/pull/7598)) - Allow streamVNext and generateVNext to use structuredOutputs from the MastraClient ([#7597](https://github.com/mastra-ai/mastra/pull/7597)) - Use workflow streamVNext in playground ([#7575](https://github.com/mastra-ai/mastra/pull/7575)) - Fix InvalidDataContentError when using image messages with AI SDK ([#7542](https://github.com/mastra-ai/mastra/pull/7542)) Resolves an issue where passing image content in messages would throw an InvalidDataContentError. The fix properly handles multi-part content arrays containing both text and image parts when converting between Mastra and AI SDK message formats. ## 0.16.1-alpha.0 ### Patch Changes - Made tracing context optional on tool execute() ([#7532](https://github.com/mastra-ai/mastra/pull/7532)) - Call getMemoryMessages even during first turn in a thread when semantic recall scope is resource ([#7529](https://github.com/mastra-ai/mastra/pull/7529)) - Execute tool calls in parallel in generate/stream VNext methods ([#7524](https://github.com/mastra-ai/mastra/pull/7524)) - Revert "feat(mcp): add createMCPTool helper for proper execute types" ([#7513](https://github.com/mastra-ai/mastra/pull/7513)) - Pass mastra instance into MCP Server tools ([#7520](https://github.com/mastra-ai/mastra/pull/7520)) - Fix image input handling for Google Gemini models in AI SDK V5 ([#7490](https://github.com/mastra-ai/mastra/pull/7490)) Resolves issue #7362 where Gemini threw `AI_InvalidDataContentError` when receiving URLs in image parts. The fix properly handles V3 message file parts that contain both URL and data fields, ensuring URLs are passed as URLs rather than being incorrectly treated as base64 data. - Vnext output schema injection ([#6990](https://github.com/mastra-ai/mastra/pull/6990)) - removed duplicate 'float' switch case ([#7516](https://github.com/mastra-ai/mastra/pull/7516)) ## 0.16.0 ### Minor Changes - a01cf14: Add workflow graph in agent (workflow as tool in agent) ### Patch Changes - 8fbf79e: Fix this to be not set when workflow is a step - fd83526: Stream agent events with workflow `.streamVNext()` - d0b90ab: Fix output processors to run before saving messages to memory - 6f5eb7a: Throw if an empty or whitespace-only threadId is passed when getting messages - a9e50ee: Allow both workflow stream message formats for now - 5397eb4: Add public URL support when adding files in Multi Modal - c9f4e4a: Pass tracing context to scorer run - 0acbc80: Add InferUITools and related type helpers for AI SDK compatibility Adds new type utility functions to help with type inference when using Mastra tools with the AI SDK's UI components: - `InferUITools` - Infers input/output types for a collection of tools - `InferUITool` - Infers input/output types for a single tool These type helpers allow developers to easily integrate Mastra tools with AI SDK UI components like `useChat` by providing proper type inference for tool inputs and outputs. ## 0.16.0-alpha.1 ### Patch Changes - 8fbf79e: Fix this to be not set when workflow is a step ## 0.16.0-alpha.0 ### Minor Changes - a01cf14: Add workflow graph in agent (workflow as tool in agent) ### Patch Changes - fd83526: Stream agent events with workflow `.streamVNext()` - d0b90ab: Fix output processors to run before saving messages to memory - 6f5eb7a: Throw if an empty or whitespace-only threadId is passed when getting messages - a9e50ee: Allow both workflow stream message formats for now - 5397eb4: Add public URL support when adding files in Multi Modal - c9f4e4a: Pass tracing context to scorer run - 0acbc80: Add InferUITools and related type helpers for AI SDK compatibility Adds new type utility functions to help with type inference when using Mastra tools with the AI SDK's UI components: - `InferUITools` - Infers input/output types for a collection of tools - `InferUITool` - Infers input/output types for a single tool These type helpers allow developers to easily integrate Mastra tools with AI SDK UI components like `useChat` by providing proper type inference for tool inputs and outputs. ## 0.15.3 ### Patch Changes - ab48c97: dependencies updates: - Updated dependency [`zod-to-json-schema@^3.24.6` ↗︎](https://www.npmjs.com/package/zod-to-json-schema/v/3.24.6) (from `^3.24.5`, in `dependencies`) - 85ef90b: Return nested workflow steps information in getWorkflowRunExecutionResult - aedbbfa: Fixed wrapping of models with AI Tracing when used with structured output. - ff89505: Add deprecation warnings and add legacy routes - 637f323: Fix issue with some compilers and calling zod v4's toJSONSchema function - de3cbc6: Update the `package.json` file to include additional fields like `repository`, `homepage` or `files`. - c19bcf7: stopped recording event spans for llm_chunks in ai-observability - 4474d04: fix: do not pass tracing context to score run - 183dc95: Added a fix to prevent filtering out injected initial default user messages. Related to issue 7231 - a1111e2: Fixes #7254 where the onFinish callback wasn't returning assistant messages when using format: 'aisdk' in streamVNext. The messageList was being updated with response messages but these weren't being passed to the user's onFinish callback. - b42a961: New createMCPTool helper for correct types for MCP Server tools - 61debef: Fix - add missing tool options to createTool - 9beaeff: Create new `@mastra/ai-sdk` package to better support `useChat()` - 29de0e1: MastraEmbeddingModel and ts hack - f643c65: Support file download - 00c74e7: Added a DefaultExporter for AI Tracing. - fef7375: Fix tool validation when schema uses context or inputData reserved keys - e3d8fea: Support Inngest flow control features for Mastra Inngest workflows - 45e4d39: Try fixing the `Attempted import error: 'z'.'toJSONSchema' is not exported from 'zod'` error by tricking the compiler - 9eee594: Fix passing providerOptions through in streamVNext, enabling reasoning-delta chunks to be receiving. - 7149d8d: Add tripwire chunk to streamVNext full stream - 822c2e8: Fix custom output (tool-output) in ai-sdk stream output - 979912c: "Updated langfuse exporter to handle Event spans" - 7dcf4c0: Ensure original stacktrace is preserved during workflow runs - 4106a58: Fix image handling for Google Gemini and other providers when using streamVNext (fixes #7362) - ad78bfc: "pipes tracingContext through all ai items: agents, workflows, tools, processors, scorers, etc.." - 0302f50: Some LLM providers (openrouter for ex) add response-metadata chunks after each text-delta, this was resulting in us flushing text deltas into parts after each delta, so our output messages (with streamVNext) would have a separate text part for each text delta, instead of one text part for the combined deltas - 6ac697e: improveEmbeddingModelStuff - 74db265: Adds handling for event-type spans to the default ai observability exporter - 0ce418a: upgrade ai v5 versions to latest for core and memory - af90672: Add maxSteps - 8387952: Register scorers on mastra instance to override per agent generate call - 7f3b8da: Automatically pipe writer to workflows as a tool. Also changed start, finish, step-output events to be workflow-start, workflow-finish and workflow-step-output - 905352b: Support AISDK models for runExperiment - 599d04c: follow up fix for scorers - 56041d0: Don't set supportsStructuredOutputs for every v2 model - 3412597: Pass provider options - 5eca5d2: Fixed wrapped mastra class inside workflow steps. - f2cda47: Fixed issue where multiple split messages were created with identical content instead of properly distributing different parts of the original message. - 5de1555: Fixed tracingContext on tool executions in AI tracing - cfd377a: fix default stream options onFinish being overridden - 1ed5a3e: Support workflows for run experiments - Updated dependencies [ab48c97] - Updated dependencies [637f323] - Updated dependencies [de3cbc6] - Updated dependencies [45e4d39] - @mastra/schema-compat@0.11.2 ## 0.15.3-alpha.9 ### Patch Changes - [#7401](https://github.com/mastra-ai/mastra/pull/7401) [`599d04c`](https://github.com/mastra-ai/mastra/commit/599d04cebe92c1d536fee3190434941b8c91548e) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - follow up fix for scorers ## 0.15.3-alpha.8 ### Patch Changes - [#7397](https://github.com/mastra-ai/mastra/pull/7397) [`4474d04`](https://github.com/mastra-ai/mastra/commit/4474d0489b1e152e0985c33a4f529207317d27b5) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - fix: do not pass tracing context to score run - [#7396](https://github.com/mastra-ai/mastra/pull/7396) [`4106a58`](https://github.com/mastra-ai/mastra/commit/4106a58b15b4c0a060a4a9ccab52d119d00d8edb) Thanks [@TylerBarnes](https://github.com/TylerBarnes)! - Fix image handling for Google Gemini and other providers when using streamVNext (fixes #7362) ## 0.15.3-alpha.7 ### Patch Changes - [#7392](https://github.com/mastra-ai/mastra/pull/7392) [`7149d8d`](https://github.com/mastra-ai/mastra/commit/7149d8d4bdc1edf0008e0ca9b7925eb0b8b60dbe) Thanks [@abhiaiyer91](https://github.com/abhiaiyer91)! - Add tripwire chunk to streamVNext full stream ## 0.15.3-alpha.6 ### Patch Changes - [#7361](https://github.com/mastra-ai/mastra/pull/7361) [`c19bcf7`](https://github.com/mastra-ai/mastra/commit/c19bcf7b43542b02157b5e17303e519933a153ab) Thanks [@epinzur](https://github.com/epinzur)! - stopped recording event spans for llm_chunks in ai-observability - [#7383](https://github.com/mastra-ai/mastra/pull/7383) [`b42a961`](https://github.com/mastra-ai/mastra/commit/b42a961a5aefd19d6e938a7705fc0ecc90e8f756) Thanks [@DanielSLew](https://github.com/DanielSLew)! - New createMCPTool helper for correct types for MCP Server tools - [#7350](https://github.com/mastra-ai/mastra/pull/7350) [`45e4d39`](https://github.com/mastra-ai/mastra/commit/45e4d391a2a09fc70c48e4d60f505586ada1ba0e) Thanks [@LekoArts](https://github.com/LekoArts)! - Try fixing the `Attempted import error: 'z'.'toJSONSchema' is not exported from 'zod'` error by tricking the compiler - [#7382](https://github.com/mastra-ai/mastra/pull/7382) [`0302f50`](https://github.com/mastra-ai/mastra/commit/0302f50861a53c66ff28801fc371b37c5f97e41e) Thanks [@TylerBarnes](https://github.com/TylerBarnes)! - Some LLM providers (openrouter for ex) add response-metadata chunks after each text-delta, this was resulting in us flushing text deltas into parts after each delta, so our output messages (with streamVNext) would have a separate text part for each text delta, instead of one text part for the combined deltas - [#7353](https://github.com/mastra-ai/mastra/pull/7353) [`74db265`](https://github.com/mastra-ai/mastra/commit/74db265b96aa01a72ffd91dcae0bc3b346cca0f2) Thanks [@epinzur](https://github.com/epinzur)! - Adds handling for event-type spans to the default ai observability exporter - [#7355](https://github.com/mastra-ai/mastra/pull/7355) [`7f3b8da`](https://github.com/mastra-ai/mastra/commit/7f3b8da6dd21c35d3672e44b4f5dd3502b8f8f92) Thanks [@rase-](https://github.com/rase-)! - Automatically pipe writer to workflows as a tool. Also changed start, finish, step-output events to be workflow-start, workflow-finish and workflow-step-output - [#7081](https://github.com/mastra-ai/mastra/pull/7081) [`905352b`](https://github.com/mastra-ai/mastra/commit/905352bcda134552400eb252bca1cb05a7975c14) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Support AISDK models for runExperiment - [#7321](https://github.com/mastra-ai/mastra/pull/7321) [`f2cda47`](https://github.com/mastra-ai/mastra/commit/f2cda47ae911038c5d5489f54c36517d6f15bdcc) Thanks [@TylerBarnes](https://github.com/TylerBarnes)! - Fixed issue where multiple split messages were created with identical content instead of properly distributing different parts of the original message. - [#7386](https://github.com/mastra-ai/mastra/pull/7386) [`cfd377a`](https://github.com/mastra-ai/mastra/commit/cfd377a3a33a9c88b644f6540feed9cd9832db47) Thanks [@NikAiyer](https://github.com/NikAiyer)! - fix default stream options onFinish being overridden - Updated dependencies [[`45e4d39`](https://github.com/mastra-ai/mastra/commit/45e4d391a2a09fc70c48e4d60f505586ada1ba0e)]: - @mastra/schema-compat@0.11.2-alpha.3 ## 0.15.3-alpha.5 ### Patch Changes - [#7272](https://github.com/mastra-ai/mastra/pull/7272) [`85ef90b`](https://github.com/mastra-ai/mastra/commit/85ef90bb2cd4ae4df855c7ac175f7d392c55c1bf) Thanks [@taofeeq-deru](https://github.com/taofeeq-deru)! - Return nested workflow steps information in getWorkflowRunExecutionResult - [#7343](https://github.com/mastra-ai/mastra/pull/7343) [`de3cbc6`](https://github.com/mastra-ai/mastra/commit/de3cbc61079211431bd30487982ea3653517278e) Thanks [@LekoArts](https://github.com/LekoArts)! - Update the `package.json` file to include additional fields like `repository`, `homepage` or `files`. - Updated dependencies [[`de3cbc6`](https://github.com/mastra-ai/mastra/commit/de3cbc61079211431bd30487982ea3653517278e)]: - @mastra/schema-compat@0.11.2-alpha.2 ## 0.15.3-alpha.4 ### Patch Changes - [#5816](https://github.com/mastra-ai/mastra/pull/5816) [`ab48c97`](https://github.com/mastra-ai/mastra/commit/ab48c979098ea571faf998a55d3a00e7acd7a715) Thanks [@dane-ai-mastra](https://github.com/apps/dane-ai-mastra)! - dependencies updates: - Updated dependency [`zod-to-json-schema@^3.24.6` ↗︎](https://www.npmjs.com/package/zod-to-json-schema/v/3.24.6) (from `^3.24.5`, in `dependencies`) - [#7269](https://github.com/mastra-ai/mastra/pull/7269) [`ff89505`](https://github.com/mastra-ai/mastra/commit/ff895057c8c7e91a5535faef46c5e5391085ddfa) Thanks [@wardpeet](https://github.com/wardpeet)! - Add deprecation warnings and add legacy routes - [#7317](https://github.com/mastra-ai/mastra/pull/7317) [`183dc95`](https://github.com/mastra-ai/mastra/commit/183dc95596f391b977bd1a2c050b8498dac74891) Thanks [@TylerBarnes](https://github.com/TylerBarnes)! - Added a fix to prevent filtering out injected initial default user messages. Related to issue 7231 - [#7327](https://github.com/mastra-ai/mastra/pull/7327) [`a1111e2`](https://github.com/mastra-ai/mastra/commit/a1111e24e705488adfe5e0a6f20c53bddf26cb22) Thanks [@TylerBarnes](https://github.com/TylerBarnes)! - Fixes #7254 where the onFinish callback wasn't returning assistant messages when using format: 'aisdk' in streamVNext. The messageList was being updated with response messages but these weren't being passed to the user's onFinish callback. - [#7267](https://github.com/mastra-ai/mastra/pull/7267) [`61debef`](https://github.com/mastra-ai/mastra/commit/61debefd80ad3a7ed5737e19df6a23d40091689a) Thanks [@TheIsrael1](https://github.com/TheIsrael1)! - Fix - add missing tool options to createTool - [#7263](https://github.com/mastra-ai/mastra/pull/7263) [`9beaeff`](https://github.com/mastra-ai/mastra/commit/9beaeffa4a97b1d5fd01a7f8af8708b16067f67c) Thanks [@wardpeet](https://github.com/wardpeet)! - Create new `@mastra/ai-sdk` package to better support `useChat()` - [#7323](https://github.com/mastra-ai/mastra/pull/7323) [`9eee594`](https://github.com/mastra-ai/mastra/commit/9eee594e35e0ca2a650fcc33fa82009a142b9ed0) Thanks [@DanielSLew](https://github.com/DanielSLew)! - Fix passing providerOptions through in streamVNext, enabling reasoning-delta chunks to be receiving. - [#7266](https://github.com/mastra-ai/mastra/pull/7266) [`979912c`](https://github.com/mastra-ai/mastra/commit/979912cfd180aad53287cda08af771df26454e2c) Thanks [@epinzur](https://github.com/epinzur)! - "Updated langfuse exporter to handle Event spans" - [#6966](https://github.com/mastra-ai/mastra/pull/6966) [`7dcf4c0`](https://github.com/mastra-ai/mastra/commit/7dcf4c04f44d9345b1f8bc5d41eae3f11ac61611) Thanks [@kaorukobo](https://github.com/kaorukobo)! - Ensure original stacktrace is preserved during workflow runs - [#7274](https://github.com/mastra-ai/mastra/pull/7274) [`ad78bfc`](https://github.com/mastra-ai/mastra/commit/ad78bfc4ea6a1fff140432bf4f638e01af7af668) Thanks [@epinzur](https://github.com/epinzur)! - "pipes tracingContext through all ai items: agents, workflows, tools, processors, scorers, etc.." - [#7219](https://github.com/mastra-ai/mastra/pull/7219) [`0ce418a`](https://github.com/mastra-ai/mastra/commit/0ce418a1ccaa5e125d4483a9651b635046152569) Thanks [@NikAiyer](https://github.com/NikAiyer)! - upgrade ai v5 versions to latest for core and memory - [#7039](https://github.com/mastra-ai/mastra/pull/7039) [`8387952`](https://github.com/mastra-ai/mastra/commit/838795227b4edf758c84a2adf6f7fba206c27719) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Register scorers on mastra instance to override per agent generate call - [#7246](https://github.com/mastra-ai/mastra/pull/7246) [`5eca5d2`](https://github.com/mastra-ai/mastra/commit/5eca5d2655788863ea0442a46c9ef5d3c6dbe0a8) Thanks [@epinzur](https://github.com/epinzur)! - Fixed wrapped mastra class inside workflow steps. - Updated dependencies [[`ab48c97`](https://github.com/mastra-ai/mastra/commit/ab48c979098ea571faf998a55d3a00e7acd7a715)]: - @mastra/schema-compat@0.11.2-alpha.1 ## 0.15.3-alpha.3 ### Patch Changes - [#7203](https://github.com/mastra-ai/mastra/pull/7203) [`aedbbfa`](https://github.com/mastra-ai/mastra/commit/aedbbfa064124ddde039111f12629daebfea7e48) Thanks [@epinzur](https://github.com/epinzur)! - Fixed wrapping of models with AI Tracing when used with structured output. - [#7127](https://github.com/mastra-ai/mastra/pull/7127) [`f643c65`](https://github.com/mastra-ai/mastra/commit/f643c651bdaf57c2343cf9dbfc499010495701fb) Thanks [@abhiaiyer91](https://github.com/abhiaiyer91)! - Support file download - [#7216](https://github.com/mastra-ai/mastra/pull/7216) [`fef7375`](https://github.com/mastra-ai/mastra/commit/fef737534574f41b432a7361a285f776c3bac42b) Thanks [@DanielSLew](https://github.com/DanielSLew)! - Fix tool validation when schema uses context or inputData reserved keys - [#7090](https://github.com/mastra-ai/mastra/pull/7090) [`e3d8fea`](https://github.com/mastra-ai/mastra/commit/e3d8feaacfb8b5c5c03c13604cc06ea2873d45fe) Thanks [@K-Mistele](https://github.com/K-Mistele)! - Support Inngest flow control features for Mastra Inngest workflows - [#7217](https://github.com/mastra-ai/mastra/pull/7217) [`3412597`](https://github.com/mastra-ai/mastra/commit/3412597a6644c0b6bf3236d6e319ed1450c5bae8) Thanks [@abhiaiyer91](https://github.com/abhiaiyer91)! - Pass provider options ## 0.15.3-alpha.2 ### Patch Changes - [#7129](https://github.com/mastra-ai/mastra/pull/7129) [`822c2e8`](https://github.com/mastra-ai/mastra/commit/822c2e88a3ecbffb7c680e6227976006ccefe6a8) Thanks [@wardpeet](https://github.com/wardpeet)! - Fix custom output (tool-output) in ai-sdk stream output ## 0.15.3-alpha.1 ### Patch Changes - [#7121](https://github.com/mastra-ai/mastra/pull/7121) [`637f323`](https://github.com/mastra-ai/mastra/commit/637f32371d79a8f78c52c0d53411af0915fcec67) Thanks [@DanielSLew](https://github.com/DanielSLew)! - Fix issue with some compilers and calling zod v4's toJSONSchema function - [#7124](https://github.com/mastra-ai/mastra/pull/7124) [`29de0e1`](https://github.com/mastra-ai/mastra/commit/29de0e1b0a7173317ae7d1ab0c0993167c659f2b) Thanks [@abhiaiyer91](https://github.com/abhiaiyer91)! - MastraEmbeddingModel and ts hack - [#7125](https://github.com/mastra-ai/mastra/pull/7125) [`6ac697e`](https://github.com/mastra-ai/mastra/commit/6ac697edcc2435482c247cba615277ec4765dcc4) Thanks [@abhiaiyer91](https://github.com/abhiaiyer91)! - improveEmbeddingModelStuff - Updated dependencies [[`637f323`](https://github.com/mastra-ai/mastra/commit/637f32371d79a8f78c52c0d53411af0915fcec67)]: - @mastra/schema-compat@0.11.2-alpha.0 ## 0.15.3-alpha.0 ### Patch Changes - [#7085](https://github.com/mastra-ai/mastra/pull/7085) [`00c74e7`](https://github.com/mastra-ai/mastra/commit/00c74e73b1926be0d475693bb886fb67a22ff352) Thanks [@epinzur](https://github.com/epinzur)! - Added a DefaultExporter for AI Tracing. - [#7030](https://github.com/mastra-ai/mastra/pull/7030) [`af90672`](https://github.com/mastra-ai/mastra/commit/af906722d8da28688882193b1e531026f9e2e81e) Thanks [@abhiaiyer91](https://github.com/abhiaiyer91)! - Add maxSteps - [#7116](https://github.com/mastra-ai/mastra/pull/7116) [`56041d0`](https://github.com/mastra-ai/mastra/commit/56041d018863a3da6b98c512e47348647c075fb3) Thanks [@DanielSLew](https://github.com/DanielSLew)! - Don't set supportsStructuredOutputs for every v2 model - [#7109](https://github.com/mastra-ai/mastra/pull/7109) [`5de1555`](https://github.com/mastra-ai/mastra/commit/5de15554d3d6695211945a36928f6657e76cddc9) Thanks [@epinzur](https://github.com/epinzur)! - Fixed tracingContext on tool executions in AI tracing - [#7025](https://github.com/mastra-ai/mastra/pull/7025) [`1ed5a3e`](https://github.com/mastra-ai/mastra/commit/1ed5a3e19330374c4347a4237cd2f4b9ffb60376) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Support workflows for run experiments ## 0.15.2 ### Patch Changes - Updated dependencies [[`c6113ed`](https://github.com/mastra-ai/mastra/commit/c6113ed7f9df297e130d94436ceee310273d6430)]: - @mastra/schema-compat@0.11.1 ## 0.15.0 ### Minor Changes - [#7032](https://github.com/mastra-ai/mastra/pull/7032) [`1191ce9`](https://github.com/mastra-ai/mastra/commit/1191ce946b40ed291e7877a349f8388e3cff7e5c) Thanks [@wardpeet](https://github.com/wardpeet)! - Bump zod peerdep to 3.25.0 to support both v3/v4 ### Patch Changes - [#6938](https://github.com/mastra-ai/mastra/pull/6938) [`0778757`](https://github.com/mastra-ai/mastra/commit/07787570e4addbd501522037bd2542c3d9e26822) Thanks [@dane-ai-mastra](https://github.com/apps/dane-ai-mastra)! - dependencies updates: - Updated dependency [`@opentelemetry/auto-instrumentations-node@^0.62.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node/v/0.62.1) (from `^0.62.0`, in `dependencies`) - [#6997](https://github.com/mastra-ai/mastra/pull/6997) [`943a7f3`](https://github.com/mastra-ai/mastra/commit/943a7f3dbc6a8ab3f9b7bc7c8a1c5b319c3d7f56) Thanks [@wardpeet](https://github.com/wardpeet)! - Bundle/mastra speed improvements - [#6933](https://github.com/mastra-ai/mastra/pull/6933) [`bf504a8`](https://github.com/mastra-ai/mastra/commit/bf504a833051f6f321d832cc7d631f3cb86d657b) Thanks [@NikAiyer](https://github.com/NikAiyer)! - Add util functions for workflow server handlers and made processor process function async - [#6954](https://github.com/mastra-ai/mastra/pull/6954) [`be49354`](https://github.com/mastra-ai/mastra/commit/be493546dca540101923ec700feb31f9a13939f2) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Add db schema and base storage apis for AI Tracing - [#6957](https://github.com/mastra-ai/mastra/pull/6957) [`d591ab3`](https://github.com/mastra-ai/mastra/commit/d591ab3ecc985c1870c0db347f8d7a20f7360536) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Implement Tracing API for inmemory(mock) storage - [#6923](https://github.com/mastra-ai/mastra/pull/6923) [`ba82abe`](https://github.com/mastra-ai/mastra/commit/ba82abe76e869316bb5a9c95e8ea3946f3436fae) Thanks [@rase-](https://github.com/rase-)! - Event based execution engine - [#6971](https://github.com/mastra-ai/mastra/pull/6971) [`727f7e5`](https://github.com/mastra-ai/mastra/commit/727f7e5086e62e0dfe3356fb6dcd8bcb420af246) Thanks [@epinzur](https://github.com/epinzur)! - "updated ai tracing in workflows" - [#6949](https://github.com/mastra-ai/mastra/pull/6949) [`e6f5046`](https://github.com/mastra-ai/mastra/commit/e6f50467aff317e67e8bd74c485c3fbe2a5a6db1) Thanks [@CalebBarnes](https://github.com/CalebBarnes)! - stream/generate vnext: simplify internal output schema handling, improve types and typescript generics, and add jsdoc comments - [#6993](https://github.com/mastra-ai/mastra/pull/6993) [`82d9f64`](https://github.com/mastra-ai/mastra/commit/82d9f647fbe4f0177320e7c05073fce88599aa95) Thanks [@wardpeet](https://github.com/wardpeet)! - Improve types and fix linting issues - [#7020](https://github.com/mastra-ai/mastra/pull/7020) [`2e58325`](https://github.com/mastra-ai/mastra/commit/2e58325beb170f5b92f856e27d915cd26917e5e6) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Add column to ai spans table to tell if it's an event - [#7011](https://github.com/mastra-ai/mastra/pull/7011) [`4189486`](https://github.com/mastra-ai/mastra/commit/4189486c6718fda78347bdf4ce4d3fc33b2236e1) Thanks [@epinzur](https://github.com/epinzur)! - Wrapped mastra objects in workflow steps to automatically pass on tracing context - [#6942](https://github.com/mastra-ai/mastra/pull/6942) [`ca8ec2f`](https://github.com/mastra-ai/mastra/commit/ca8ec2f61884b9dfec5fc0d5f4f29d281ad13c01) Thanks [@wardpeet](https://github.com/wardpeet)! - Add zod as peerdeps for all packages - [#6943](https://github.com/mastra-ai/mastra/pull/6943) [`9613558`](https://github.com/mastra-ai/mastra/commit/9613558e6475f4710e05d1be7553a32ee7bddc20) Thanks [@taofeeq-deru](https://github.com/taofeeq-deru)! - Persist to snapshot when step starts - Updated dependencies [[`da58ccc`](https://github.com/mastra-ai/mastra/commit/da58ccc1f2ac33da0cb97b00443fc6208b45bdec), [`94e9f54`](https://github.com/mastra-ai/mastra/commit/94e9f547d66ef7cd01d9075ab53b5ca9a1cae100), [`1191ce9`](https://github.com/mastra-ai/mastra/commit/1191ce946b40ed291e7877a349f8388e3cff7e5c), [`a93f3ba`](https://github.com/mastra-ai/mastra/commit/a93f3ba05eef4cf17f876d61d29cf0841a9e70b7)]: - @mastra/schema-compat@0.11.0 ## 0.15.0-alpha.4 ### Minor Changes - [#7032](https://github.com/mastra-ai/mastra/pull/7032) [`1191ce9`](https://github.com/mastra-ai/mastra/commit/1191ce946b40ed291e7877a349f8388e3cff7e5c) Thanks [@wardpeet](https://github.com/wardpeet)! - Bump zod peerdep to 3.25.0 to support both v3/v4 ### Patch Changes - Updated dependencies [[`1191ce9`](https://github.com/mastra-ai/mastra/commit/1191ce946b40ed291e7877a349f8388e3cff7e5c)]: - @mastra/schema-compat@0.11.0-alpha.2 ## 0.15.0-alpha.3 ### Patch Changes - Updated dependencies [[`da58ccc`](https://github.com/mastra-ai/mastra/commit/da58ccc1f2ac33da0cb97b00443fc6208b45bdec)]: - @mastra/schema-compat@0.10.6-alpha.1 ## 0.14.2-alpha.2 ### Patch Changes - [#7020](https://github.com/mastra-ai/mastra/pull/7020) [`2e58325`](https://github.com/mastra-ai/mastra/commit/2e58325beb170f5b92f856e27d915cd26917e5e6) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Add column to ai spans table to tell if it's an event ## 0.14.2-alpha.1 ### Patch Changes - [#6997](https://github.com/mastra-ai/mastra/pull/6997) [`943a7f3`](https://github.com/mastra-ai/mastra/commit/943a7f3dbc6a8ab3f9b7bc7c8a1c5b319c3d7f56) Thanks [@wardpeet](https://github.com/wardpeet)! - Bundle/mastra speed improvements - [#6954](https://github.com/mastra-ai/mastra/pull/6954) [`be49354`](https://github.com/mastra-ai/mastra/commit/be493546dca540101923ec700feb31f9a13939f2) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Add db schema and base storage apis for AI Tracing - [#6957](https://github.com/mastra-ai/mastra/pull/6957) [`d591ab3`](https://github.com/mastra-ai/mastra/commit/d591ab3ecc985c1870c0db347f8d7a20f7360536) Thanks [@YujohnNattrass](https://github.com/YujohnNattrass)! - Implement Tracing API for inmemory(mock) storage - [#6923](https://github.com/mastra-ai/mastra/pull/6923) [`ba82abe`](https://github.com/mastra-ai/mastra/commit/ba82abe76e869316bb5a9c95e8ea3946f3436fae) Thanks [@rase-](https://github.com/rase-)! - Event based execution engine - [#6971](https://github.com/mastra-ai/mastra/pull/6971) [`727f7e5`](https://github.com/mastra-ai/mastra/commit/727f7e5086e62e0dfe3356fb6dcd8bcb420af246) Thanks [@epinzur](https://github.com/epinzur)! - "updated ai tracing in workflows" - [#6993](https://github.com/mastra-ai/mastra/pull/6993) [`82d9f64`](https://github.com/mastra-ai/mastra/commit/82d9f647fbe4f0177320e7c05073fce88599aa95) Thanks [@wardpeet](https://github.com/wardpeet)! - Improve types and fix linting issues - [#7011](https://github.com/mastra-ai/mastra/pull/7011) [`4189486`](https://github.com/mastra-ai/mastra/commit/4189486c6718fda78347bdf4ce4d3fc33b2236e1) Thanks [@epinzur](https://github.com/epinzur)! - Wrapped mastra objects in workflow steps to automatically pass on tracing context - [#6942](https://github.com/mastra-ai/mastra/pull/6942) [`ca8ec2f`](https://github.com/mastra-ai/mastra/commit/ca8ec2f61884b9dfec5fc0d5f4f29d281ad13c01) Thanks [@wardpeet](https://github.com/wardpeet)! - Add zod as peerdeps for all packages - Updated dependencies [[`94e9f54`](https://github.com/mastra-ai/mastra/commit/94e9f547d66ef7cd01d9075ab53b5ca9a1cae100), [`a93f3ba`](https://github.com/mastra-ai/mastra/commit/a93f3ba05eef4cf17f876d61d29cf0841a9e70b7)]: - @mastra/schema-compat@0.10.6-alpha.0 ## 0.14.2-alpha.0 ### Patch Changes - [#6938](https://github.com/mastra-ai/mastra/pull/6938) [`0778757`](https://github.com/mastra-ai/mastra/commit/07787570e4addbd501522037bd2542c3d9e26822) Thanks [@dane-ai-mastra](https://github.com/apps/dane-ai-mastra)! - dependencies updates: - Updated dependency [`@opentelemetry/auto-instrumentations-node@^0.62.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node/v/0.62.1) (from `^0.62.0`, in `dependencies`) - [#6933](https://github.com/mastra-ai/mastra/pull/6933) [`bf504a8`](https://github.com/mastra-ai/mastra/commit/bf504a833051f6f321d832cc7d631f3cb86d657b) Thanks [@NikAiyer](https://github.com/NikAiyer)! - Add util functions for workflow server handlers and made processor process function async - [#6949](https://github.com/mastra-ai/mastra/pull/6949) [`e6f5046`](https://github.com/mastra-ai/mastra/commit/e6f50467aff317e67e8bd74c485c3fbe2a5a6db1) Thanks [@CalebBarnes](https://github.com/CalebBarnes)! - stream/generate vnext: simplify internal output schema handling, improve types and typescript generics, and add jsdoc comments - [#6943](https://github.com/mastra-ai/mastra/pull/6943) [`9613558`](https://github.com/mastra-ai/mastra/commit/9613558e6475f4710e05d1be7553a32ee7bddc20) Thanks [@taofeeq-deru](https://github.com/taofeeq-deru)! - Persist to snapshot when step starts ## 0.14.1 ### Patch Changes - [#6919](https://github.com/mastra-ai/mastra/pull/6919) [`6e7e120`](https://github.com/mastra-ai/mastra/commit/6e7e1207d6e8d8b838f9024f90bd10df1181ba27) Thanks [@dane-ai-mastra](https://github.com/apps/dane-ai-mastra)! - dependencies updates: - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.3` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.3) (from `npm:@ai-sdk/provider-utils@3.0.0`, in `dependencies`) - Updated dependency [`ai@^4.3.19` ↗︎](https://www.npmjs.com/package/ai/v/4.3.19) (from `^4.3.16`, in `dependencies`) - Updated dependency [`ai-v5@npm:ai@5.0.15` ↗︎](https://www.npmjs.com/package/ai-v5/v/5.0.15) (from `npm:ai@5.0.0`, in `dependencies`) - [#6864](https://github.com/mastra-ai/mastra/pull/6864) [`0f00e17`](https://github.com/mastra-ai/mastra/commit/0f00e172953ccdccadb35ed3d70f5e4d89115869) Thanks [@TylerBarnes](https://github.com/TylerBarnes)! - Added a convertMessages(from).to("Mastra.V2" | "AIV\*") util for operating on DB messages directly - [#6927](https://github.com/mastra-ai/mastra/pull/6927) [`217cd7a`](https://github.com/mastra-ai/mastra/commit/217cd7a4ce171e9a575c41bb8c83300f4db03236) Thanks [@DanielSLew](https://github.com/DanielSLew)! - Fix output processors to match new stream types. - [#6700](https://github.com/mastra-ai/mastra/pull/6700) [`a5a23d9`](https://github.com/mastra-ai/mastra/commit/a5a23d981920d458dc6078919992a5338931ef02) Thanks [@gpanakkal](https://github.com/gpanakkal)! - Add `getMessagesById` method to `MastraStorage` adapters ## 0.14.1-alpha.1 ### Patch Changes - [#6864](https://github.com/mastra-ai/mastra/pull/6864) [`0f00e17`](https://github.com/mastra-ai/mastra/commit/0f00e172953ccdccadb35ed3d70f5e4d89115869) Thanks [@TylerBarnes](https://github.com/TylerBarnes)! - Added a convertMessages(from).to("Mastra.V2" | "AIV\*") util for operating on DB messages directly - [#6927](https://github.com/mastra-ai/mastra/pull/6927) [`217cd7a`](https://github.com/mastra-ai/mastra/commit/217cd7a4ce171e9a575c41bb8c83300f4db03236) Thanks [@DanielSLew](https://github.com/DanielSLew)! - Fix output processors to match new stream types. ## 0.14.1-alpha.0 ### Patch Changes - [#6919](https://github.com/mastra-ai/mastra/pull/6919) [`6e7e120`](https://github.com/mastra-ai/mastra/commit/6e7e1207d6e8d8b838f9024f90bd10df1181ba27) Thanks [@dane-ai-mastra](https://github.com/apps/dane-ai-mastra)! - dependencies updates: - Updated dependency [`@ai-sdk/provider-utils-v5@npm:@ai-sdk/provider-utils@3.0.3` ↗︎](https://www.npmjs.com/package/@ai-sdk/provider-utils-v5/v/3.0.3) (from `npm:@ai-sdk/provider-utils@3.0.0`, in `dependencies`) - Updated dependency [`ai@^4.3.19` ↗︎](https://www.npmjs.com/package/ai/v/4.3.19) (from `^4.3.16`, in `dependencies`) - Updated dependency [`ai-v5@npm:ai@5.0.15` ↗︎](https://www.npmjs.com/package/ai-v5/v/5.0.15) (from `npm:ai@5.0.0`, in `dependencies`) - [#6700](https://github.com/mastra-ai/mastra/pull/6700) [`a5a23d9`](https://github.com/mastra-ai/mastra/commit/a5a23d981920d458dc6078919992a5338931ef02) Thanks [@gpanakkal](https://github.com/gpanakkal)! - Add `getMessagesById` method to `MastraStorage` adapters ## 0.14.0 ### Minor Changes - 3b5fec7: Added AIV5 support to internal MessageList, precursor to full AIV5 support in latest Mastra ### Patch Changes - 227c7e6: replace console.log with logger.debug in inmemory operations - 12cae67: fix: add threadId and resourceId to scorers - fd3a3eb: Add runExperments to run scorers in a test suite or in CI - 6faaee5: Reworks agent Processor API to include output processors. Adds structuredOutput property in agent.streamVNext and agent.generate to replace experimental_output. Move imports for processors to @mastra/core/processors. Adds 6 new output processors, BatchParts, StructuredOutputProcessor, TokenLimiter, SystemPromptScrubber, ModerationProcessor, PiiDetectorProcessor. - 4232b14: Fix provider metadata preservation during V5 message conversions Provider metadata (providerMetadata and callProviderMetadata) is now properly preserved when converting messages between AI SDK V5 and internal V2 formats. This ensures provider-specific information isn't lost during message transformations. - a89de7e: Adding a new agentic loop and streaming workflow system while working towards AI SDK v5 support. - 5a37d0c: Fix dev server bug related to p-map imports - 4bde0cb: Allow renaming .map functions in workflows - cf4f357: When using the Cloudflare deployer you might see a `[duplicate-case]` warning. The internal cause for this was fixed. - ad888a2: Stream vnext agent-network - 481751d: Tests `mitt.off` event handler removal - 2454423: Agentic loop and streaming workflow: generateVNext and streamVNext - 194e395: exclude \_wrapToolsWithAITracing from agent trace - a722c0b: Added a patch to filter out system messages that were stored in the db via an old memory bug that was patched long ago (see issue 6689). Users upgrading from the old version that still had the bug would see errors when the memory messages were retrieved from the db - c30bca8: Fix do while resume-suspend in simple workflow losing data - a8f129d: initial addition of experimental ai observability tracing features. ## 0.14.0-alpha.7 ## 0.14.0-alpha.6 ### Patch Changes - ad888a2: Stream vnext agent-network - 481751d: Tests `mitt.off` event handler removal - 194e395: exclude \_wrapToolsWithAITracing from agent trace ## 0.14.0-alpha.5 ## 0.14.0-alpha.4 ### Patch Changes - 0a7f675: Client JS vnext methods - 12cae67: fix: add threadId and resourceId to scorers - 5a37d0c: Fix dev server bug related to p-map imports - 4bde0cb: Allow renaming .map functions in workflows - 1a80071: loop code and tests - 36a3be8: Agent processors tests - 361757b: Execute method - 2bb9955: Model loop changes - 2454423: generateVNext and streamVNext - a44d91e: Message list changes - dfb91e9: Server handlers - a741dde: generateVNext plumbing - 7cb3fc0: Fix loop test - 195eabb: Process Mastra Stream - b78b95b: Support generateVNext in playground ## 0.14.0-alpha.3 ### Patch Changes - 227c7e6: replace console.log with logger.debug in inmemory operations - fd3a3eb: Add runExperments to run scorers in a test suite or in CI - a8f129d: "initial addition of experimental ai observability tracing features." ## 0.14.0-alpha.2 ## 0.14.0-alpha.1 ### Minor Changes - 3b5fec7: Added AIV5 support to internal MessageList, precursor to full AIV5 support in latest Mastra ### Patch Changes - 6faaee5: Reworks agent Processor API to include output processors. Adds structuredOutput property in agent.streamVNext and agent.generate to replace experimental_output. Move imports for processors to @mastra/core/processors. Adds 6 new output processors, BatchParts, StructuredOutputProcessor, TokenLimiter, SystemPromptScrubber, ModerationProcessor, PiiDetectorProcessor. - 4232b14: Fix provider metadata preservation during V5 message conversions Provider metadata (providerMetadata and callProviderMetadata) is now properly preserved when converting messages between AI SDK V5 and internal V2 formats. This ensures provider-specific information isn't lost during message transformations. - a89de7e: Adding a new agentic loop and streaming workflow system while working towards AI SDK v5 support. - cf4f357: When using the Cloudflare deployer you might see a `[duplicate-case]` warning. The internal cause for this was fixed. - a722c0b: Added a patch to filter out system messages that were stored in the db via an old memory bug that was patched long ago (see issue 6689). Users upgrading from the old version that still had the bug would see errors when the memory messages were retrieved from the db ## 0.13.3-alpha.0 ### Patch Changes - c30bca8: Fix do while resume-suspend in simple workflow losing data ## 0.13.2 ### Patch Changes - d5330bf: Allow agent model to be updated after the agent is created - 2e74797: Fix tool arguments being lost when tool-result messages arrive separately from tool-call messages or when messages are restored from database. Tool invocations now correctly preserve their arguments in all scenarios. - 8388649: Allow array of messages in vnext agent network - a239d41: Updated A2A syntax to v0.3.0 - dd94a26: Dont rely on the full language model for schema compat - 3ba6772: MastraModelInput - b5cf2a3: make system message always available during agent calls - 2fff911: Fix vnext working memory tool schema when model is incompatible with schema - b32c50d: Filter scores by source - 63449d0: Change the function signatures of `bundle`, `lint`, and internally `getToolsInputOptions` to expand the `toolsPaths` TypeScript type from `string[]` to `(string | string[])[]`. - 121a3f8: Fixed an issue where telemetry logs were displaying promise statuses when `agent.stream` is called - ec510e7: Tool input validation now returns errors as tool results instead of throwing, allowing agents to understand validation failures and retry with corrected parameters. - Updated dependencies [dd94a26] - Updated dependencies [2fff911] - Updated dependencies [ae2eb63] - @mastra/schema-compat@0.10.7 ## 0.13.2-alpha.3 ### Patch Changes - b5cf2a3: make system message always available during agent calls ## 0.13.2-alpha.2 ### Patch Changes - d5330bf: Allow agent model to be updated after the agent is created - a239d41: Updated A2A syntax to v0.3.0 - b32c50d: Filter scores by source - 121a3f8: Fixed an issue where telemetry logs were displaying promise statuses when `agent.stream` is called - ec510e7: Tool input validation now returns errors as tool results instead of throwing, allowing agents to understand validation failures and retry with corrected parameters. - Updated dependencies [ae2eb63] - @mastra/schema-compat@0.10.7-alpha.1 ## 0.13.2-alpha.1 ### Patch Changes - 2e74797: Fix tool arguments being lost when tool-result messages arrive separately from tool-call messages or when messages are restored from database. Tool invocations now correctly preserve their arguments in all scenarios. - 63449d0: Change the function signatures of `bundle`, `lint`, and internally `getToolsInputOptions` to expand the `toolsPaths` TypeScript type from `string[]` to `(string | string[])[]`. ## 0.13.2-alpha.0 ### Patch Changes - 8388649: Allow array of messages in vnext agent network - dd94a26: Dont rely on the full language model for schema compat - 3ba6772: MastraModelInput - 2fff911: Fix vnext working memory tool schema when model is incompatible with schema - Updated dependencies [dd94a26] - Updated dependencies [2fff911] - @mastra/schema-compat@0.10.7-alpha.0 ## 0.13.1 ### Patch Changes - cd0042e: Fix tool call history not being accessible in agent conversations When converting v2 messages (with combined tool calls and text) to v1 format for memory storage, split messages were all keeping the same ID. This caused later messages to replace earlier ones when added back to MessageList, losing tool history. The fix adds ID deduplication by appending `__split-N` suffixes to split messages and prevents double-suffixing when messages are re-converted between formats. ## 0.13.1-alpha.0 ### Patch Changes - cd0042e: Fix tool call history not being accessible in agent conversations When converting v2 messages (with combined tool calls and text) to v1 format for memory storage, split messages were all keeping the same ID. This caused later messages to replace earlier ones when added back to MessageList, losing tool history. The fix adds ID deduplication by appending `__split-N` suffixes to split messages and prevents double-suffixing when messages are re-converted between formats. ## 0.13.0 ### Minor Changes - ea0c5f2: Update scorer api ### Patch Changes - cb36de0: dependencies updates: - Updated dependency [`hono@^4.8.11` ↗︎](https://www.npmjs.com/package/hono/v/4.8.11) (from `^4.8.9`, in `dependencies`) - d0496e6: dependencies updates: - Updated dependency [`hono@^4.8.12` ↗︎](https://www.npmjs.com/package/hono/v/4.8.12) (from `^4.8.11`, in `dependencies`) - a82b851: Exclude getVoice, getScorers from agent trace - 41a0a0e: fixed a minor bug where ID generator wasn't being properly bound to instances of MessageList - 2871020: update safelyParseJSON to check for value of param when handling parse - 94f4812: lazy initialize Run's `AbortController` - e202b82: Add getThreadsByResourceIdPaginated to the Memory Class - e00f6a0: Fixed an issue where converting from v2->v1 messages would not properly split text and tool call parts into multiple messages - 4a406ec: fixes TypeScript declaration file imports to ensure proper ESM compatibility - b0e43c1: Fixed an issue where branching workflow steps maintained "suspended" status even after they've been successfully resumed and executed. - 5d377e5: Fix tracing of runtimeContext values" - 1fb812e: Fixed a bug in parallel workflow execution where resuming only one of multiple suspended parallel steps incorrectly completed the entire parallel block. The fix ensures proper execution and state management when resuming from suspension in parallel workflows. - 35c5798: Add support for transpilePackages option - Updated dependencies [4a406ec] - @mastra/schema-compat@0.10.6 ## 0.13.0-alpha.3 ### Patch Changes - d0496e6: dependencies updates: - Updated dependency [`hono@^4.8.12` ↗︎](https://www.npmjs.com/package/hono/v/4.8.12) (from `^4.8.11`, in `dependencies`) ## 0.13.0-alpha.2 ### Patch Changes - cb36de0: dependencies updates: - Updated dependency [`hono@^4.8.11` ↗︎](https://www.npmjs.com/package/hono/v/4.8.11) (from `^4.8.9`, in `dependencies`) - a82b851: Exclude getVoice, getScorers from agent trace - 41a0a0e: fixed a minor bug where ID generator wasn't being properly bound to instances of MessageList - 2871020: update safelyParseJSON to check for value of param when handling parse - 4a406ec: fixes TypeScript declaration file imports to ensure proper ESM compatibility - 5d377e5: Fix tracing of runtimeContext values" - Updated dependencies [4a406ec] - @mastra/schema-compat@0.10.6-alpha.0 ## 0.13.0-alpha.1 ### Minor Changes - ea0c5f2: Update scorer api ### Patch Changes - b0e43c1: Fixed an issue where branching workflow steps maintained "suspended" status even after they've been successfully resumed and executed. - 1fb812e: Fixed a bug in parallel workflow execution where resuming only one of multiple suspended parallel steps incorrectly completed the entire parallel block. The fix ensures proper execution and state management when resuming from suspension in parallel workflows. - 35c5798: Add support for transpilePackages option ## 0.12.2-alpha.0 ### Patch Changes - 94f4812: lazy initialize Run's `AbortController` - e202b82: Add getThreadsByResourceIdPaginated to the Memory Class - e00f6a0: Fixed an issue where converting from v2->v1 messages would not properly split text and tool call parts into multiple messages ## 0.12.1 ### Patch Changes - 33dcb07: dependencies updates: - Updated dependency [`@opentelemetry/auto-instrumentations-node@^0.62.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node/v/0.62.0) (from `^0.59.0`, in `dependencies`) - Updated dependency [`@opentelemetry/exporter-trace-otlp-grpc@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-grpc/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/exporter-trace-otlp-http@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-http/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-exporter-base@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-exporter-base/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-transformer@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-transformer/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/sdk-node@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/sdk-node/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/semantic-conventions@^1.36.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/semantic-conventions/v/1.36.0) (from `^1.34.0`, in `dependencies`) - d0d9500: Fixed an issue where AWS Bedrock is expecting a user message at the beginning of the message list - d30b1a0: Remove js-tiktoken as it's unused - bff87f7: fix issue where v1 messages from db wouldn't properly show tool calls in llm context window from historry - b4a8df0: Fixed an issue where memory instances were not being registered with Mastra and custom ID generators weren't being used ## 0.12.1-alpha.1 ### Patch Changes - d0d9500: Fixed an issue where AWS Bedrock is expecting a user message at the beginning of the message list ## 0.12.1-alpha.0 ### Patch Changes - 33dcb07: dependencies updates: - Updated dependency [`@opentelemetry/auto-instrumentations-node@^0.62.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node/v/0.62.0) (from `^0.59.0`, in `dependencies`) - Updated dependency [`@opentelemetry/exporter-trace-otlp-grpc@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-grpc/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/exporter-trace-otlp-http@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-http/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-exporter-base@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-exporter-base/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-transformer@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-transformer/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/sdk-node@^0.203.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/sdk-node/v/0.203.0) (from `^0.201.1`, in `dependencies`) - Updated dependency [`@opentelemetry/semantic-conventions@^1.36.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/semantic-conventions/v/1.36.0) (from `^1.34.0`, in `dependencies`) - d30b1a0: Remove js-tiktoken as it's unused - bff87f7: fix issue where v1 messages from db wouldn't properly show tool calls in llm context window from historry - b4a8df0: Fixed an issue where memory instances were not being registered with Mastra and custom ID generators weren't being used ## 0.12.0 ### Minor Changes - 2ecf658: Added the option to provide a custom ID generator when creating an instance of Mastra. If the generator is not provided, a fallback of using UUID is used to generate IDs instead. ### Patch Changes - 510e2c8: dependencies updates: - Updated dependency [`radash@^12.1.1` ↗︎](https://www.npmjs.com/package/radash/v/12.1.1) (from `^12.1.0`, in `dependencies`) - 2f72fb2: dependencies updates: - Updated dependency [`xstate@^5.20.1` ↗︎](https://www.npmjs.com/package/xstate/v/5.20.1) (from `^5.19.4`, in `dependencies`) - 27cc97a: dependencies updates: - Updated dependency [`hono@^4.8.9` ↗︎](https://www.npmjs.com/package/hono/v/4.8.9) (from `^4.8.4`, in `dependencies`) - 3f89307: improve registerApiRoute validation - 9eda7d4: Move createMockModel to test scope. This prevents test dependencies from leaking into production code. - 9d49408: Fix conditional branch execution after nested workflow resume. Now conditional branches properly re-evaluate their conditions during resume, ensuring only the correct branches execute. - 41daa63: Threads are no longer created until message generation is complete to avoid leaving orphaned empty threads in storage on failure - ad0a58b: Enhancements for premade input processors - 254a36b: Expose mastra instance on dynamic agent arguments - 7a7754f: Fast follow scorers fixing input types, improve llm scorer reliability, fix ui to display scores that are 0 - fc92d80: fix: GenerateReturn type - e0f73c6: Make input optional for scorer run - 0b89602: Fix workflow feedback loop crashes by preventing resume data reuse Fixes an issue where workflows with loops (dountil/dowhile) containing suspended steps would incorrectly reuse resume data across iterations. This caused human-in-the-loop workflows to crash or skip suspend points after resuming. The fix ensures resume data is cleared after a step completes (non-suspended status), allowing subsequent loop iterations to properly suspend for new input. Fixes #6014 - 4d37822: Fix workflow input property preservation after resume from snapshot Ensure that when resuming a workflow from a snapshot, the input property is correctly set from the snapshot's context input rather than from resume data. This prevents the loss of original workflow input data during suspend/resume cycles. - 23a6a7c: improve error message for missing memory ids - cda801d: Added the ability to pass in metadata for UIMessage and MastraMessageV2 in client-js and agent.stream/generate - a77c823: include PATCH method in default CORS configuration - ff9c125: enhance thread retrieval with sorting options in libsql and pg - 09bca64: Log warning when telemetry is enabled but not loaded - b8efbb9: feat: add flexible deleteMessages method to memory API - Added `memory.deleteMessages(input)` method that accepts multiple input types: - Single message ID as string: `deleteMessages('msg-123')` - Array of message IDs: `deleteMessages(['msg-1', 'msg-2'])` - Message object with id property: `deleteMessages({ id: 'msg-123' })` - Array of message objects: `deleteMessages([{ id: 'msg-1' }, { id: 'msg-2' }])` - Implemented in all storage adapters (LibSQL, PostgreSQL, Upstash, InMemory) - Added REST API endpoint: `POST /api/memory/messages/delete` - Updated client SDK: `thread.deleteMessages()` accepts all input types - Updates thread timestamps when messages are deleted - Added comprehensive test coverage and documentation - 71466e7: Adds traceId and resourceId to telemetry spans for agent invocations - 0c99fbe: [Feature] Add ability to include input processors to Agent primitive in order to add guardrails to incoming messages. ## 0.12.0-alpha.5 ## 0.12.0-alpha.4 ### Patch Changes - ad0a58b: Enhancements for premade input processors ## 0.12.0-alpha.3 ## 0.12.0-alpha.2 ### Patch Changes - 27cc97a: dependencies updates: - Updated dependency [`hono@^4.8.9` ↗︎](https://www.npmjs.com/package/hono/v/4.8.9) (from `^4.8.4`, in `dependencies`) - 41daa63: Threads are no longer created until message generation is complete to avoid leaving orphaned empty threads in storage on failure - 254a36b: Expose mastra instance on dynamic agent arguments - 0b89602: Fix workflow feedback loop crashes by preventing resume data reuse Fixes an issue where workflows with loops (dountil/dowhile) containing suspended steps would incorrectly reuse resume data across iterations. This caused human-in-the-loop workflows to crash or skip suspend points after resuming. The fix ensures resume data is cleared after a step completes (non-suspended status), allowing subsequent loop iterations to properly suspend for new input. Fixes #6014 - 4d37822: Fix workflow input property preservation after resume from snapshot Ensure that when resuming a workflow from a snapshot, the input property is correctly set from the snapshot's context input rather than from resume data. This prevents the loss of original workflow input data during suspend/resume cycles. - ff9c125: enhance thread retrieval with sorting options in libsql and pg - b8efbb9: feat: add flexible deleteMessages method to memory API - Added `memory.deleteMessages(input)` method that accepts multiple input types: - Single message ID as string: `deleteMessages('msg-123')` - Array of message IDs: `deleteMessages(['msg-1', 'msg-2'])` - Message object with id property: `deleteMessages({ id: 'msg-123' })` - Array of message objects: `deleteMessages([{ id: 'msg-1' }, { id: 'msg-2' }])` - Implemented in all storage adapters (LibSQL, PostgreSQL, Upstash, InMemory) - Added REST API endpoint: `POST /api/memory/messages/delete` - Updated client SDK: `thread.deleteMessages()` accepts all input types - Updates thread timestamps when messages are deleted - Added comprehensive test coverage and documentation - 71466e7: Adds traceId and resourceId to telemetry spans for agent invocations - 0c99fbe: [Feature] Add ability to include input processors to Agent primitive in order to add guardrails to incoming messages. ## 0.12.0-alpha.1 ### Patch Changes - e0f73c6: Make input optional for scorer run - cda801d: Added the ability to pass in metadata for UIMessage and MastraMessageV2 in client-js and agent.stream/generate - a77c823: include PATCH method in default CORS configuration ## 0.12.0-alpha.0 ### Minor Changes - 2ecf658: Added the option to provide a custom ID generator when creating an instance of Mastra. If the generator is not provided, a fallback of using UUID is used to generate IDs instead. ### Patch Changes - 510e2c8: dependencies updates: - Updated dependency [`radash@^12.1.1` ↗︎](https://www.npmjs.com/package/radash/v/12.1.1) (from `^12.1.0`, in `dependencies`) - 2f72fb2: dependencies updates: - Updated dependency [`xstate@^5.20.1` ↗︎](https://www.npmjs.com/package/xstate/v/5.20.1) (from `^5.19.4`, in `dependencies`) - 3f89307: improve registerApiRoute validation - 9eda7d4: Move createMockModel to test scope. This prevents test dependencies from leaking into production code. - 9d49408: Fix conditional branch execution after nested workflow resume. Now conditional branches properly re-evaluate their conditions during resume, ensuring only the correct branches execute. - 7a7754f: Fast follow scorers fixing input types, improve llm scorer reliability, fix ui to display scores that are 0 - fc92d80: fix: GenerateReturn type - 23a6a7c: improve error message for missing memory ids - 09bca64: Log warning when telemetry is enabled but not loaded ## 0.11.1 ### Patch Changes - f248d53: Adding `getMessagesPaginated` to the serve, deployer, and client-js - 2affc57: Fix output type of network loop - 66e13e3: Add methods to fetch workflow/agent by its true id - edd9482: Fix "workflow run was not suspended" error when attempting to resume a workflow with consecutive nested workflows. - 18344d7: Code and llm scorers - 9d372c2: Fix streamVNext error handling - 40c2525: Fix agent.generate error with experimental_output and clientTool - e473f27: Implement off the shelf Scorers - 032cb66: ClientJS - 703ac71: scores schema - a723d69: Pass workflowId through - 7827943: Handle streaming large data - 5889a31: Export storage domain types - bf1e7e7: Configure agent memory using runtimeContext - 65e3395: Add Scores playground-ui and add scorer hooks - 4933192: Update Message List to ensure correct order of message parts - d1c77a4: Scorer interface - bea9dd1: Refactor Agent class to consolidate LLM generate and stream methods and improve type safety. This includes extracting common logic into prepareLLMOptions(), enhancing type definitions, and fixing test annotations. This changeset entry follows the established format in your project: - Targets the @mastra/core package with a patch version bump - Provides a concise description of the refactoring and type safety improvements - Mentions the key changes without being too verbose - dcd4802: scores mastra server - cbddd18: Remove erroneous reassignment of `Mastra.prototype.#vectors` - 7ba91fa: Add scorer abstract methods for base storage ## 0.11.0-alpha.2 ### Patch Changes - f248d53: Adding `getMessagesPaginated` to the serve, deployer, and client-js - 2affc57: Fix output type of network loop - 66e13e3: Add methods to fetch workflow/agent by its true id - edd9482: Fix "workflow run was not suspended" error when attempting to resume a workflow with consecutive nested workflows. - 18344d7: Code and llm scorers - 9d372c2: Fix streamVNext error handling - 40c2525: Fix agent.generate error with experimental_output and clientTool - e473f27: Implement off the shelf Scorers - 032cb66: ClientJS - 703ac71: scores schema - a723d69: Pass workflowId through - 5889a31: Export storage domain types - 65e3395: Add Scores playground-ui and add scorer hooks - 4933192: Update Message List to ensure correct order of message parts - d1c77a4: Scorer interface - bea9dd1: Refactor Agent class to consolidate LLM generate and stream methods and improve type safety. This includes extracting common logic into prepareLLMOptions(), enhancing type definitions, and fixing test annotations. This changeset entry follows the established format in your project: - Targets the @mastra/core package with a patch version bump - Provides a concise description of the refactoring and type safety improvements - Mentions the key changes without being too verbose - dcd4802: scores mastra server - 7ba91fa: Add scorer abstract methods for base storage ## 0.11.0-alpha.1 ## 0.11.0-alpha.0 ### Patch Changes - 7827943: Handle streaming large data - bf1e7e7: Configure agent memory using runtimeContext - cbddd18: Remove erroneous reassignment of `Mastra.prototype.#vectors` ## 0.10.15 ### Patch Changes - 0b56518: Ensure removed runtimeContext values are not saved in snapshot - db5cc15: Create thread if it does not exist yet in agent network stream, generate and loopStream - 2ba5b76: Allow passing jsonSchema into workingMemory schema - 5237998: Fix foreach output - c3a30de: added new experimental vnext working memory - 37c1acd: Format semantic recall messages grouped by dates and labeled by if they're from a different thread or not, to improve longmemeval scores - 1aa60b1: Pipe runtimeContext to vNext network agent stream and generate steps, wire up runtimeContext for vNext Networks in cliet SDK & playground - 89ec9d4: remove cohere-ai client dependency and just make a fetch call - cf3a184: Add warning log when memory is not configured but threadId/resourceId are passed to agent - d6bfd60: Simplify Message-List Merge Logic and Updates - 626b0f4: [Cloud-126] Working Memory Playground - Added working memory to playground to allow users to view/edit working memory - c22a91f: Fix nested workflow resume in loop workflow breaking - f7403ab: Only change workflow status to success after all steps are successful - 6c89d7f: Save runtimeContext in snapshot - Updated dependencies [4da943f] - @mastra/schema-compat@0.10.5 ## 0.10.15-alpha.1 ### Patch Changes - 0b56518: Ensure removed runtimeContext values are not saved in snapshot - 2ba5b76: Allow passing jsonSchema into workingMemory schema - c3a30de: added new experimental vnext working memory - cf3a184: Add warning log when memory is not configured but threadId/resourceId are passed to agent - d6bfd60: Simplify Message-List Merge Logic and Updates - Updated dependencies [4da943f] - @mastra/schema-compat@0.10.5-alpha.0 ## 0.10.15-alpha.0 ### Patch Changes - db5cc15: Create thread if it does not exist yet in agent network stream, generate and loopStream - 5237998: Fix foreach output - 37c1acd: Format semantic recall messages grouped by dates and labeled by if they're from a different thread or not, to improve longmemeval scores - 1aa60b1: Pipe runtimeContext to vNext network agent stream and generate steps, wire up runtimeContext for vNext Networks in cliet SDK & playground - 89ec9d4: remove cohere-ai client dependency and just make a fetch call - 626b0f4: [Cloud-126] Working Memory Playground - Added working memory to playground to allow users to view/edit working memory - c22a91f: Fix nested workflow resume in loop workflow breaking - f7403ab: Only change workflow status to success after all steps are successful - 6c89d7f: Save runtimeContext in snapshot ## 0.10.14 ### Patch Changes - Update @mastra/deployer ## 0.10.12 ### Patch Changes - b4a9811: Remove async-await of stream inside llm base class - 4d5583d: [Cloud-195] added retrieved memory messages to agent traces ## 0.10.12-alpha.1 ### Patch Changes - 4d5583d: [Cloud-195] added retrieved memory messages to agent traces ## 0.10.12-alpha.0 ### Patch Changes - b4a9811: Remove async-await of stream inside llm base class ## 0.10.11 ### Patch Changes - 2873c7f: dependencies updates: - Updated dependency [`dotenv@^16.6.1` ↗︎](https://www.npmjs.com/package/dotenv/v/16.6.1) (from `^16.5.0`, in `dependencies`) - 1c1c6a1: dependencies updates: - Updated dependency [`hono@^4.8.4` ↗︎](https://www.npmjs.com/package/hono/v/4.8.4) (from `^4.8.3`, in `dependencies`) - f8ce2cc: Add stepId to workflow executeStep error log - 8c846b6: Fixed a problem where per-resource working memory wasn't being queried properly - c7bbf1e: Implement workflow retry delay - 8722d53: Fix multi modal remaining steps - 565cc0c: fix redirection when clicking on the playground breadcrumbs - b790fd1: Ability to pass a function to .sleep()/.sleepUntil() - 132027f: Check if workflow and step is suspended before resuming - 0c85311: Fix Google models ZodNull tool schema handling - d7ed04d: make workflow execute use createRunAsync - cb16baf: Fix MCP tool output schema type and return value - f36e4f1: Allow passing custom instructions to generateTitle to override default instructions. - 7f6e403: [MASTRA-3765] Save Message parts - Add ability for user to save messages on step finish for stream and agent - Updated dependencies [0c85311] - @mastra/schema-compat@0.10.4 ## 0.10.11-alpha.4 ## 0.10.11-alpha.3 ### Patch Changes - c7bbf1e: Implement workflow retry delay - 8722d53: Fix multi modal remaining steps - 132027f: Check if workflow and step is suspended before resuming - 0c85311: Fix Google models ZodNull tool schema handling - cb16baf: Fix MCP tool output schema type and return value - Updated dependencies [0c85311] - @mastra/schema-compat@0.10.4-alpha.0 ## 0.10.11-alpha.2 ### Patch Changes - 2873c7f: dependencies updates: - Updated dependency [`dotenv@^16.6.1` ↗︎](https://www.npmjs.com/package/dotenv/v/16.6.1) (from `^16.5.0`, in `dependencies`) - 1c1c6a1: dependencies updates: - Updated dependency [`hono@^4.8.4` ↗︎](https://www.npmjs.com/package/hono/v/4.8.4) (from `^4.8.3`, in `dependencies`) - 565cc0c: fix redirection when clicking on the playground breadcrumbs ## 0.10.11-alpha.1 ### Patch Changes - 7f6e403: [MASTRA-3765] Save Message parts - Add ability for user to save messages on step finish for stream and agent ## 0.10.11-alpha.0 ### Patch Changes - f8ce2cc: Add stepId to workflow executeStep error log - 8c846b6: Fixed a problem where per-resource working memory wasn't being queried properly - b790fd1: Ability to pass a function to .sleep()/.sleepUntil() - d7ed04d: make workflow execute use createRunAsync - f36e4f1: Allow passing custom instructions to generateTitle to override default instructions. ## 0.10.10 ### Patch Changes - 4d3fbdf: Return tool error message rather than throw when a tool error happens for agent and tool playground page. ## 0.10.10-alpha.1 ## 0.10.10-alpha.0 ### Patch Changes - 4d3fbdf: Return tool error message rather than throw when a tool error happens for agent and tool playground page. ## 0.10.9 ### Patch Changes - 9dda1ac: dependencies updates: - Updated dependency [`hono@^4.8.3` ↗︎](https://www.npmjs.com/package/hono/v/4.8.3) (from `^4.7.11`, in `dependencies`) - c984582: Improve error messages for invalid message content in MessageList - 7e801dd: [MASTRA-4118] fixes issue with agent network loopStream where subsequent messages aren't present in playground on refresh - a606c75: show right suspend schema for nested workflow on playground - 7aa70a4: Use the right step id for nested workflow steps in watch-v2 - 764f86a: Introduces the runCount property in the execution parameters for the steps execute function - 1760a1c: Use workflow stream in playground instead of watch - 038e5ae: Add cancel workflow run - 7dda16a: Agent Network: Prompting improvements for better decisions - 5ebfcdd: Fix MessageList toUIMessage to filter out tool invocations with state="call" or "partial-call" - b2d0c91: Made title generation a blocking operation to prevent issues where the process might close before the title is generated - 4e809ad: Visualizations for .sleep()/.sleepUntil()/.waitForEvent() - 57929df: [MASTRA-4143[ change message-list and agent network display - b7852ed: [MASTRA-4139] make private functions protected in memory - 6320a61: Allow passing model to generateTitle to override default model selection. ## 0.10.9-alpha.0 ### Patch Changes - 9dda1ac: dependencies updates: - Updated dependency [`hono@^4.8.3` ↗︎](https://www.npmjs.com/package/hono/v/4.8.3) (from `^4.7.11`, in `dependencies`) - c984582: Improve error messages for invalid message content in MessageList - 7e801dd: [MASTRA-4118] fixes issue with agent network loopStream where subsequent messages aren't present in playground on refresh - a606c75: show right suspend schema for nested workflow on playground - 7aa70a4: Use the right step id for nested workflow steps in watch-v2 - 764f86a: Introduces the runCount property in the execution parameters for the steps execute function - 1760a1c: Use workflow stream in playground instead of watch - 038e5ae: Add cancel workflow run - 7dda16a: Agent Network: Prompting improvements for better decisions - 5ebfcdd: Fix MessageList toUIMessage to filter out tool invocations with state="call" or "partial-call" - b2d0c91: Made title generation a blocking operation to prevent issues where the process might close before the title is generated - 4e809ad: Visualizations for .sleep()/.sleepUntil()/.waitForEvent() - 57929df: [MASTRA-4143[ change message-list and agent network display - b7852ed: [MASTRA-4139] make private functions protected in memory - 6320a61: Allow passing model to generateTitle to override default model selection. ## 0.10.8 ### Patch Changes - b8f16b2: Fixes generateTitle overwriting working memory when both get used in the same LLM response cycle. - 3e04487: Fix provider tools to check for output schema before attaching to tool - a344ac7: Fix tool streaming in agent network - dc4ca0a: Fixed a regression where intentionally serialized JSON message content was being parsed back into an object by MessageList ## 0.10.8-alpha.1 ### Patch Changes - b8f16b2: Fixes generateTitle overwriting working memory when both get used in the same LLM response cycle. - 3e04487: Fix provider tools to check for output schema before attaching to tool - dc4ca0a: Fixed a regression where intentionally serialized JSON message content was being parsed back into an object by MessageList ## 0.10.8-alpha.0 ### Patch Changes - a344ac7: Fix tool streaming in agent network ## 0.10.7 ### Patch Changes - 15e9d26: Added per-resource working memory for LibSQL, Upstash, and PG - d1baedb: fix bad merge with mastra error - d8f2d19: Add updateMessages API to storage classes (only support for PG and LibSQL for now) and to memory class. Additionally allow for metadata to be saved in the content field of a message. - 4d21bf2: throw mastra errors for MCP - 07d6d88: Bump MCP SDK version and add tool output schema support to MCPServer and MCPClient - 9d52b17: Fix inngest workflows streaming and add step metadata - 2097952: [MASTRA-4021] Fix PG getMessages and update messageLimit for all storage adapters - 792c4c0: feat: pass runId to onFinish - 5d74aab: Return isComplete of true in routing step when no resource is selected - a8b194f: Fix double tool call for working memory - 4fb0cc2: Type safe variable mapping - d2a7a31: Fix memory message context for when LLM providers throw an error if the first message is a tool call. - 502fe05: createRun() -> createRunAsync() - 144eb0b: [MASTRA-3669] Metadata Filter Types - 8ba1b51: Add custom routes by default to jsonapi - 4efcfa0: Added bail() method and more ergonomic suspend function return value - 0e17048: Throw mastra errors in storage packages - Updated dependencies [98bbe5a] - Updated dependencies [a853c43] - @mastra/schema-compat@0.10.3 ## 0.10.7-alpha.5 ### Patch Changes - Updated dependencies [a853c43] - @mastra/schema-compat@0.10.3-alpha.1 ## 0.10.7-alpha.4 ### Patch Changes - a8b194f: Fix double tool call for working memory ## 0.10.7-alpha.3 ### Patch Changes - 792c4c0: feat: pass runId to onFinish - 502fe05: createRun() -> createRunAsync() - 4efcfa0: Added bail() method and more ergonomic suspend function return value ## 0.10.7-alpha.2 ### Patch Changes - 15e9d26: Added per-resource working memory for LibSQL, Upstash, and PG - 07d6d88: Bump MCP SDK version and add tool output schema support to MCPServer and MCPClient - 5d74aab: Return isComplete of true in routing step when no resource is selected - 144eb0b: [MASTRA-3669] Metadata Filter Types - Updated dependencies [98bbe5a] - @mastra/schema-compat@0.10.3-alpha.0 ## 0.10.7-alpha.1 ### Patch Changes - d1baedb: fix bad merge with mastra error - 4d21bf2: throw mastra errors for MCP - 2097952: [MASTRA-4021] Fix PG getMessages and update messageLimit for all storage adapters - 4fb0cc2: Type safe variable mapping - d2a7a31: Fix memory message context for when LLM providers throw an error if the first message is a tool call. - 0e17048: Throw mastra errors in storage packages ## 0.10.7-alpha.0 ### Patch Changes - d8f2d19: Add updateMessages API to storage classes (only support for PG and LibSQL for now) and to memory class. Additionally allow for metadata to be saved in the content field of a message. - 9d52b17: Fix inngest workflows streaming and add step metadata - 8ba1b51: Add custom routes by default to jsonapi ## 0.10.6 ### Patch Changes - 63f6b7d: dependencies updates: - Updated dependency [`@opentelemetry/exporter-trace-otlp-grpc@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-grpc/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/exporter-trace-otlp-http@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-http/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-exporter-base@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-exporter-base/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-transformer@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-transformer/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/sdk-node@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/sdk-node/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/semantic-conventions@^1.34.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/semantic-conventions/v/1.34.0) (from `^1.33.0`, in `dependencies`) - Updated dependency [`ai@^4.3.16` ↗︎](https://www.npmjs.com/package/ai/v/4.3.16) (from `^4.2.2`, in `dependencies`) - Updated dependency [`cohere-ai@^7.17.1` ↗︎](https://www.npmjs.com/package/cohere-ai/v/7.17.1) (from `^7.16.0`, in `dependencies`) - Updated dependency [`hono@^4.7.11` ↗︎](https://www.npmjs.com/package/hono/v/4.7.11) (from `^4.5.1`, in `dependencies`) - Updated dependency [`hono-openapi@^0.4.8` ↗︎](https://www.npmjs.com/package/hono-openapi/v/0.4.8) (from `^0.4.6`, in `dependencies`) - Updated dependency [`json-schema-to-zod@^2.6.1` ↗︎](https://www.npmjs.com/package/json-schema-to-zod/v/2.6.1) (from `^2.6.0`, in `dependencies`) - Updated dependency [`pino@^9.7.0` ↗︎](https://www.npmjs.com/package/pino/v/9.7.0) (from `^9.6.0`, in `dependencies`) - Updated dependency [`xstate@^5.19.4` ↗︎](https://www.npmjs.com/package/xstate/v/5.19.4) (from `^5.19.2`, in `dependencies`) - 12a95fc: Allow passing thread metadata to agent.generate and agent.stream. This will update or create the thread with the metadata passed in. Also simplifies the arguments for those two functions into a new memory property. - 4b0f8a6: Allow passing a string, ui message, core message, or mastra message to agent.genTitle and agent.generateTitleFromUserMessage to restore previously changed public behaviour - 51264a5: Fix fetchMemory return type and value - 8e6f677: Dynamic default llm options - d70c420: fix(core, memory): fix fetchMemory regression - ee9af57: Add api for polling run execution result and get run by id - 36f1c36: MCP Client and Server streamable http fixes - 2a16996: Working Memory Schema and Template - 10d352e: fix: bug in `workflow.parallel` return types causing type errors on c… - 9589624: Throw Mastra Errors when building and bundling mastra application - 53d3c37: Get workflows from an agent if not found from Mastra instance #5083 - 751c894: pass resourceId - 577ce3a: deno support - use globalThis - 9260b3a: changeset ## 0.10.6-alpha.5 ### Patch Changes - 12a95fc: Allow passing thread metadata to agent.generate and agent.stream. This will update or create the thread with the metadata passed in. Also simplifies the arguments for those two functions into a new memory property. - 51264a5: Fix fetchMemory return type and value - 8e6f677: Dynamic default llm options ## 0.10.6-alpha.4 ### Patch Changes - 9589624: Throw Mastra Errors when building and bundling mastra application ## 0.10.6-alpha.3 ### Patch Changes - d70c420: fix(core, memory): fix fetchMemory regression - 2a16996: Working Memory Schema and Template ## 0.10.6-alpha.2 ### Patch Changes - 4b0f8a6: Allow passing a string, ui message, core message, or mastra message to agent.genTitle and agent.generateTitleFromUserMessage to restore previously changed public behaviour ## 0.10.6-alpha.1 ### Patch Changes - ee9af57: Add api for polling run execution result and get run by id - 751c894: pass resourceId - 577ce3a: deno support - use globalThis - 9260b3a: changeset ## 0.10.6-alpha.0 ### Patch Changes - 63f6b7d: dependencies updates: - Updated dependency [`@opentelemetry/exporter-trace-otlp-grpc@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-grpc/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/exporter-trace-otlp-http@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/exporter-trace-otlp-http/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-exporter-base@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-exporter-base/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/otlp-transformer@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/otlp-transformer/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/sdk-node@^0.201.1` ↗︎](https://www.npmjs.com/package/@opentelemetry/sdk-node/v/0.201.1) (from `^0.201.0`, in `dependencies`) - Updated dependency [`@opentelemetry/semantic-conventions@^1.34.0` ↗︎](https://www.npmjs.com/package/@opentelemetry/semantic-conventions/v/1.34.0) (from `^1.33.0`, in `dependencies`) - Updated dependency [`ai@^4.3.16` ↗︎](https://www.npmjs.com/package/ai/v/4.3.16) (from `^4.2.2`, in `dependencies`) - Updated dependency [`cohere-ai@^7.17.1` ↗︎](https://www.npmjs.com/package/cohere-ai/v/7.17.1) (from `^7.16.0`, in `dependencies`) - Updated dependency [`hono@^4.7.11` ↗︎](https://www.npmjs.com/package/hono/v/4.7.11) (from `^4.5.1`, in `dependencies`) - Updated dependency [`hono-openapi@^0.4.8` ↗︎](https://www.npmjs.com/package/hono-openapi/v/0.4.8) (from `^0.4.6`, in `dependencies`) - Updated dependency [`json-schema-to-zod@^2.6.1` ↗︎](https://www.npmjs.com/package/json-schema-to-zod/v/2.6.1) (from `^2.6.0`, in `dependencies`) - Updated dependency [`pino@^9.7.0` ↗︎](https://www.npmjs.com/package/pino/v/9.7.0) (from `^9.6.0`, in `dependencies`) - Updated dependency [`xstate@^5.19.4` ↗︎](https://www.npmjs.com/package/xstate/v/5.19.4) (from `^5.19.2`, in `dependencies`) - 36f1c36: MCP Client and Server streamable http fixes - 10d352e: fix: bug in `workflow.parallel` return types causing type errors on c… - 53d3c37: Get workflows from an agent if not found from Mastra instance #5083 ## 0.10.5 ### Patch Changes - 13c97f9: Save run status, result and error in storage snapshot ## 0.10.4 ### Patch Changes - d1ed912: dependencies updates: - Updated dependency [`dotenv@^16.5.0` ↗︎](https://www.npmjs.com/package/dotenv/v/16.5.0) (from `^16.4.7`, in `dependencies`) - f6fd25f: Updates @mastra/schema-compat to allow all zod schemas. Uses @mastra/schema-compat to apply schema transformations to agent output schema. - dffb67b: updated stores to add alter table and change tests - f1f1f1b: Add basic filtering capabilities to logs - 925ab94: added paginated functions to base class and added boilerplate and updated imports - f9816ae: Create @mastra/schema-compat package to extract the schema compatibility layer to be used outside of mastra - 82090c1: Add pagination to logs - 1b443fd: - add trackException to loggers to allow mastra cloud to track exceptions at runtime - Added generic MastraBaseError in packages/core/src/error/index.ts to improve type safety and flexibility of error handling - ce97900: Add paginated APIs to cloudflare-d1 storage class - f1309d3: Now that UIMessages are stored, we added a check to make sure large text files or source urls are not sent to the LLM for thread title generation. - 14a2566: Add pagination to libsql storage APIs - f7f8293: Added LanceDB implementations for MastraVector and MastraStorage - 48eddb9: update filter logic in Memory class to support semantic recall search scope - Updated dependencies [f6fd25f] - Updated dependencies [f9816ae] - @mastra/schema-compat@0.10.2 ## 0.10.4-alpha.3 ### Patch Changes - 925ab94: added paginated functions to base class and added boilerplate and updated imports ## 0.10.4-alpha.2 ### Patch Changes - 48eddb9: update filter logic in Memory class to support semantic recall search scope ## 0.10.4-alpha.1 ### Patch Changes - f6fd25f: Updates @mastra/schema-compat to allow all zod schemas. Uses @mastra/schema-compat to apply schema transformations to agent output schema. - dffb67b: updated stores to add alter table and change tests - f1309d3: Now that UIMessages are stored, we added a check to make sure large text files or source urls are not sent to the LLM for thread title generation. - f7f8293: Added LanceDB implementations for MastraVector and MastraStorage - Updated dependencies [f6fd25f] - @mastra/schema-compat@0.10.2-alpha.3 ## 0.10.4-alpha.0 ### Patch Changes - d1ed912: dependencies updates: - Updated dependency [`dotenv@^16.5.0` ↗︎](https://www.npmjs.com/package/dotenv/v/16.5.0) (from `^16.4.7`, in `dependencies`) - f1f1f1b: Add basic filtering capabilities to logs - f9816ae: Create @mastra/schema-compat package to extract the schema compatibility layer to be used outside of mastra - 82090c1: Add pagination to logs - 1b443fd: - add trackException to loggers to allow mastra cloud to track exceptions at runtime - Added generic MastraBaseError in packages/core/src/error/index.ts to improve type safety and flexibility of error handling - ce97900: Add paginated APIs to cloudflare-d1 storage class - 14a2566: Add pagination to libsql storage APIs - Updated dependencies [f9816ae] - @mastra/schema-compat@0.10.2-alpha.2 ## 0.10.3 ### Patch Changes - 2b0fc7e: Ensure context messages aren't saved to the storage db ## 0.10.3-alpha.0 ### Patch Changes - 2b0fc7e: Ensure context messages aren't saved to the storage db ## 0.10.2 ### Patch Changes - ee77e78: Type fixes for dynamodb and MessageList - 592a2db: Added different icons for agents and workflows in mcp tools list - e5dc18d: Added a backwards compatible layer to begin storing/retrieving UIMessages in storage instead of CoreMessages - ab5adbe: Add support for runtimeContext to generateTitle - 1e8bb40: Add runtimeContext to tools and agents in a workflow step. Also updated start/resume docs for runtime context. - 1b5fc55: Fixed an issue where the playground wouldn't display images saved in memory. Fixed memory to always store images as strings. Removed duplicate storage of reasoning and file/image parts in storage dbs - 195c428: Add runId to step execute fn - f73e11b: fix telemetry disabled not working on playground - 37643b8: Fix tool access - 99fd6cf: Fix workflow stream chunk type - c5bf1ce: Add backwards compat code for new MessageList in storage - add596e: Mastra protected auth - 8dc94d8: Enhance workflow DI runtimeContext get method type safety - ecebbeb: Mastra core auth abstract definition - 79d5145: Fixes passing telemetry configuration when Agent.stream is used with experimental_output - 12b7002: Add serializedStepGraph to workflow run snapshot in storage - 2901125: feat: set mastra server middleware after Mastra has been initialized ## 0.10.2-alpha.8 ### Patch Changes - 37643b8: Fix tool access - 79d5145: Fixes passing telemetry configuration when Agent.stream is used with experimental_output ## 0.10.2-alpha.7 ## 0.10.2-alpha.6 ### Patch Changes - 99fd6cf: Fix workflow stream chunk type - 8dc94d8: Enhance workflow DI runtimeContext get method type safety ## 0.10.2-alpha.5 ### Patch Changes - 1b5fc55: Fixed an issue where the playground wouldn't display images saved in memory. Fixed memory to always store images as strings. Removed duplicate storage of reasoning and file/image parts in storage dbs - add596e: Mastra protected auth - ecebbeb: Mastra core auth abstract definition ## 0.10.2-alpha.4 ### Patch Changes - c5bf1ce: Add backwards compat code for new MessageList in storage - 12b7002: Add serializedStepGraph to workflow run snapshot in storage ## 0.10.2-alpha.3 ### Patch Changes - ab5adbe: Add support for runtimeContext to generateTitle - 195c428: Add runId to step execute fn - f73e11b: fix telemetry disabled not working on playground ## 0.10.2-alpha.2 ### Patch Changes - 1e8bb40: Add runtimeContext to tools and agents in a workflow step. Also updated start/resume docs for runtime context. ## 0.10.2-alpha.1 ### Patch Changes - ee77e78: Type fixes for dynamodb and MessageList - 2901125: feat: set mastra server middleware after Mastra has been initialized ## 0.10.2-alpha.0 ### Patch Changes - 592a2db: Added different icons for agents and workflows in mcp tools list - e5dc18d: Added a backwards compatible layer to begin storing/retrieving UIMessages in storage instead of CoreMessages ## 0.10.1 ### Patch Changes - d70b807: Improve storage.init - 6d16390: Support custom bundle externals on mastra Instance - 1e4a421: Fix duplication of items in array results in workflow - 200d0da: Return payload data, start time, end time, resume time and suspend time for each step in workflow state Return error stack for failed workflow runs - bf5f17b: Adds ability to pass workflows into MCPServer to generate tools from the workflows. Each workflow will become a tool that can start the workflow run. - 5343f93: Move emitter to symbol to make private - 38aee50: Adds ability to pass an agents into an MCPServer instance to automatically generate tools from them. - 5c41100: Added binding support for cloudflare deployers, added cloudflare kv namespace changes, and removed randomUUID from buildExecutionGraph - d6a759b: Update workflows code in core readme' - 6015bdf: Leverage defaultAgentStreamOption, defaultAgentGenerateOption in playground ## 0.10.1-alpha.3 ### Patch Changes - d70b807: Improve storage.init ## 0.10.1-alpha.2 ### Patch Changes - 6015bdf: Leverage defaultAgentStreamOption, defaultAgentGenerateOption in playground ## 0.10.1-alpha.1 ### Patch Changes - 200d0da: Return payload data, start time, end time, resume time and suspend time for each step in workflow state Return error stack for failed workflow runs - bf5f17b: Adds ability to pass workflows into MCPServer to generate tools from the workflows. Each workflow will become a tool that can start the workflow run. - 5343f93: Move emitter to symbol to make private - 38aee50: Adds ability to pass an agents into an MCPServer instance to automatically generate tools from them. - 5c41100: Added binding support for cloudflare deployers, added cloudflare kv namespace changes, and removed randomUUID from buildExecutionGraph - d6a759b: Update workflows code in core readme' ## 0.10.1-alpha.0 ### Patch Changes - 6d16390: Support custom bundle externals on mastra Instance - 1e4a421: Fix duplication of items in array results in workflow ## 0.10.0 ### Minor Changes - 5eb5a99: Remove pino from @mastra/core into @mastra/loggers - 7e632c5: Removed default LibSQLStore and LibSQLVector from @mastra/core. These now live in a separate package @mastra/libsql - b2ae5aa: Added support for experimental authentication and authorization - 0dcb9f0: Memory breaking changes: storage, vector, and embedder are now required. Working memory text streaming has been removed, only tool calling is supported for working memory updates now. Default settings have changed (lastMessages: 40->10, semanticRecall: true->false, threads.generateTitle: true->false) ### Patch Changes - b3a3d63: BREAKING: Make vnext workflow the default worklow, and old workflow legacy_workflow - 344f453: Await onFinish & onStepFinish to ensure the stream doesn't close early - 0a3ae6d: Fixed a bug where tool input schema properties that were optional became required - 95911be: Fixed an issue where if @mastra/core was not released at the same time as create-mastra, create-mastra would match the alpha tag instead of latest tag when running npm create mastra@latest - f53a6ac: Add VNextWorkflowRuns type - 1e9fbfa: Upgrade to OpenTelemetry JS SDK 2.x - eabdcd9: [MASTRA-3451] SQL Injection Protection - 90be034: Pass zod schema directly to getInitData - 99f050a: Bumped a workspace package zod version to attempt to prevent duplicate dep installs of @mastra/core - d0ee3c6: Change all public functions and constructors in vector stores to use named args and prepare to phase out positional args - 23f258c: Add new list and get routes for mcp servers. Changed route make-up for more consistency with existing API routes. Lastly, added in a lot of extra detail that can be optionally passed to the mcp server per the mcp spec. - a7292b0: BREAKING(@mastra/core, all vector stores): Vector store breaking changes (remove deprecated functions and positional arguments) - 2672a05: Add MCP servers and tool call execution to playground ## 0.10.0-alpha.1 ### Minor Changes - 5eb5a99: Remove pino from @mastra/core into @mastra/loggers - 7e632c5: Removed default LibSQLStore and LibSQLVector from @mastra/core. These now live in a separate package @mastra/libsql - b2ae5aa: Added support for experimental authentication and authorization - 0dcb9f0: Memory breaking changes: storage, vector, and embedder are now required. Working memory text streaming has been removed, only tool calling is supported for working memory updates now. Default settings have changed (lastMessages: 40->10, semanticRecall: true->false, threads.generateTitle: true->false) ### Patch Changes - b3a3d63: BREAKING: Make vnext workflow the default worklow, and old workflow legacy_workflow - 344f453: Await onFinish & onStepFinish to ensure the stream doesn't close early - 0a3ae6d: Fixed a bug where tool input schema properties that were optional became required - 95911be: Fixed an issue where if @mastra/core was not released at the same time as create-mastra, create-mastra would match the alpha tag instead of latest tag when running npm create mastra@latest - 1e9fbfa: Upgrade to OpenTelemetry JS SDK 2.x - a7292b0: BREAKING(@mastra/core, all vector stores): Vector store breaking changes (remove deprecated functions and positional arguments) ## 0.9.5-alpha.0 ### Patch Changes - f53a6ac: Add VNextWorkflowRuns type - eabdcd9: [MASTRA-3451] SQL Injection Protection - 90be034: Pass zod schema directly to getInitData - 99f050a: Bumped a workspace package zod version to attempt to prevent duplicate dep installs of @mastra/core - d0ee3c6: Change all public functions and constructors in vector stores to use named args and prepare to phase out positional args - 23f258c: Add new list and get routes for mcp servers. Changed route make-up for more consistency with existing API routes. Lastly, added in a lot of extra detail that can be optionally passed to the mcp server per the mcp spec. - 2672a05: Add MCP servers and tool call execution to playground ## 0.9.4 ### Patch Changes - 396be50: updated mcp server routes for MCP SSE for use with hono server - ab80e7e: Fix resume workflow throwing workflow run not found error - c3bd795: [MASTRA-3358] Deprecate updateIndexById and deleteIndexById - da082f8: Switch from serializing json schema string as a function to a library that creates a zod object in memory from the json schema. This reduces the errors we were seeing from zod schema code that could not be serialized. - a5810ce: Add support for experimental_generateMessageId and remove it from client-js types since it's not serializable - 3e9c131: Typo resoolve. - 3171b5b: Fix jsonSchema on vercel tools - 973e5ac: Add workflows to agents properly - daf942f: [MASTRA-3367] updated createthread when using generatetitle to perserve thread metadata - 0b8b868: Added A2A support + streaming - 9e1eff5: Fix tool compatibility schema handling by ensuring zodSchema.shape is safely accessed, preventing potential runtime errors. - 6fa1ad1: Fixes and issue when a tool provides no inputSchema and when a tool uses a non-zod schema. - c28d7a0: Fix watch workflow not streaming response back in legacy workflow - edf1e88: allows ability to pass McpServer into the mastra class and creates an endpoint /api/servers/:serverId/mcp to POST messages to an MCP server ## 0.9.4-alpha.4 ### Patch Changes - 3e9c131: Typo resoolve. ## 0.9.4-alpha.3 ### Patch Changes - 396be50: updated mcp server routes for MCP SSE for use with hono server - c3bd795: [MASTRA-3358] Deprecate updateIndexById and deleteIndexById - da082f8: Switch from serializing json schema string as a function to a library that creates a zod object in memory from the json schema. This reduces the errors we were seeing from zod schema code that could not be serialized. - a5810ce: Add support for experimental_generateMessageId and remove it from client-js types since it's not serializable ## 0.9.4-alpha.2 ### Patch Changes - 3171b5b: Fix jsonSchema on vercel tools - 973e5ac: Add workflows to agents properly - 9e1eff5: Fix tool compatibility schema handling by ensuring zodSchema.shape is safely accessed, preventing potential runtime errors. ## 0.9.4-alpha.1 ### Patch Changes - ab80e7e: Fix resume workflow throwing workflow run not found error - 6fa1ad1: Fixes and issue when a tool provides no inputSchema and when a tool uses a non-zod schema. - c28d7a0: Fix watch workflow not streaming response back in legacy workflow - edf1e88: allows ability to pass McpServer into the mastra class and creates an endpoint /api/servers/:serverId/mcp to POST messages to an MCP server ## 0.9.4-alpha.0 ### Patch Changes - daf942f: [MASTRA-3367] updated createthread when using generatetitle to perserve thread metadata - 0b8b868: Added A2A support + streaming ## 0.9.3 ### Patch Changes - e450778: vnext: Inngest playground fixes - 8902157: added an optional `bodySizeLimit` to server config so that users can pass custom bodylimit size in mb. If not, it defaults to 4.5 mb - ca0dc88: fix: filter out excessive logs when getting LLM for agents - 526c570: expose agent runtimeContext from clientSDK - d7a6a33: Allow more user messages to be saved to memory, and fix message saving when using output flag - 9cd1a46: [MASTRA-3338] update naming scheme for embedding index based on vector store rules and added duplicate index checks - b5d2de0: In vNext workflow serializedStepGraph, return only serializedStepFlow for steps created from a workflow allow viewing inner nested workflows in a multi-layered nested vnext workflow on the playground - 644f8ad: Adds a tool compatibility layer to ensure models from various providers work the same way. Models may not be able to support all json schema properties (such as some openai reasoning models), as well as other models support the property but seem to ignore it. The feature allows for a compatibility class for a provider that can be customized to fit the models and make sure they're using the tool schemas properly. - 70dbf51: [MASTRA-2452] updated setBaggage for tracing ## 0.9.3-alpha.1 ### Patch Changes - e450778: vnext: Inngest playground fixes - 8902157: added an optional `bodySizeLimit` to server config so that users can pass custom bodylimit size in mb. If not, it defaults to 4.5 mb - ca0dc88: fix: filter out excessive logs when getting LLM for agents - 9cd1a46: [MASTRA-3338] update naming scheme for embedding index based on vector store rules and added duplicate index checks - 70dbf51: [MASTRA-2452] updated setBaggage for tracing ## 0.9.3-alpha.0 ### Patch Changes - 526c570: expose agent runtimeContext from clientSDK - b5d2de0: In vNext workflow serializedStepGraph, return only serializedStepFlow for steps created from a workflow allow viewing inner nested workflows in a multi-layered nested vnext workflow on the playground - 644f8ad: Adds a tool compatibility layer to ensure models from various providers work the same way. Models may not be able to support all json schema properties (such as some openai reasoning models), as well as other models support the property but seem to ignore it. The feature allows for a compatibility class for a provider that can be customized to fit the models and make sure they're using the tool schemas properly. ## 0.9.2 ### Patch Changes - 6052aa6: Add getWorkflowRunById to vNext workflow core and server handler - 967b41c: fix: removes new agent getter methods from telemetry - 3d2fb5c: Fix commonjs import for vnext workflows - 26738f4: Switched from a custom MCP tools schema deserializer to json-schema-to-zod - fixes an issue where MCP tool schemas didn't deserialize properly in Mastra playground. Also added support for testing tools with no input arguments in playground - 4155f47: Add parameters to filter workflow runs Add fromDate and toDate to telemetry parameters - 7eeb2bc: Add Memory default storage breaking change warning - b804723: Fix #3831: keep conversations in tact by keeping empty assistant messages - 8607972: Introduce Mastra lint cli command - ccef9f9: Fixed a type error when converting tools - 0097d50: Add serializedStepGraph to vNext workflow Return serializedStepGraph from vNext workflow Use serializedStepGraph in vNext workflow graph - 7eeb2bc: Added explicit storage to memory in create-mastra so new projects don't see breaking change warnings - 17826a9: Added a breaking change warning about deprecated working memory "use: 'text-stream'" which is being fully replaced by "use: 'tool-call'" - 7d8b7c7: In vnext getworkflowRunById, pick run from this.#runs if it does not exist in storage - fba031f: Show traces for vNext workflow - 3a5f1e1: Created a new @mastra/fastembed package based on the default embedder in @mastra/core as the default embedder will be removed in a breaking change (May 20th) Added a warning to use the new @mastra/fastembed package instead of the default embedder - 51e6923: fix ts errors on default proxy storage - 8398d89: vNext: dynamic input mappings ## 0.9.2-alpha.6 ### Patch Changes - 6052aa6: Add getWorkflowRunById to vNext workflow core and server handler - 7d8b7c7: In vnext getworkflowRunById, pick run from this.#runs if it does not exist in storage - 3a5f1e1: Created a new @mastra/fastembed package based on the default embedder in @mastra/core as the default embedder will be removed in a breaking change (May 20th) Added a warning to use the new @mastra/fastembed package instead of the default embedder - 8398d89: vNext: dynamic input mappings ## 0.9.2-alpha.5 ### Patch Changes - 3d2fb5c: Fix commonjs import for vnext workflows - 7eeb2bc: Add Memory default storage breaking change warning - 8607972: Introduce Mastra lint cli command - 7eeb2bc: Added explicit storage to memory in create-mastra so new projects don't see breaking change warnings - fba031f: Show traces for vNext workflow ## 0.9.2-alpha.4 ### Patch Changes - ccef9f9: Fixed a type error when converting tools - 51e6923: fix ts errors on default proxy storage ## 0.9.2-alpha.3 ### Patch Changes - 967b41c: fix: removes new agent getter methods from telemetry - 4155f47: Add parameters to filter workflow runs Add fromDate and toDate to telemetry parameters - 17826a9: Added a breaking change warning about deprecated working memory "use: 'text-stream'" which is being fully replaced by "use: 'tool-call'" ## 0.9.2-alpha.2 ### Patch Changes - 26738f4: Switched from a custom MCP tools schema deserializer to json-schema-to-zod - fixes an issue where MCP tool schemas didn't deserialize properly in Mastra playground. Also added support for testing tools with no input arguments in playground ## 0.9.2-alpha.1 ### Patch Changes - b804723: Fix #3831: keep conversations in tact by keeping empty assistant messages ## 0.9.2-alpha.0 ### Patch Changes - 0097d50: Add serializedStepGraph to vNext workflow Return serializedStepGraph from vNext workflow Use serializedStepGraph in vNext workflow graph ## 0.9.1 ### Patch Changes - 405b63d: add ability to clone workflows with different id - 81fb7f6: Workflows v2 - 20275d4: Adding warnings for current implicit Memory default options as they will be changing soon in a breaking change. Also added memory to create-mastra w/ new defaults so new projects don't see these warnings - 7d1892c: Return error object directly in vNext workflows - a90a082: Rename container to runtimeContext in vNext workflow Add steps accessor for stepFlow in vnext workflow Add getWorkflowRun to vnext workflow Add vnext_getWorkflows() to mastra core - 2d17c73: Fix checking for presence of constant value mappings - 61e92f5: vNext fix workflow watch cleanup - 35955b0: Rename import to runtime-contxt - 6262bd5: Mastra server custom host config - c1409ef: Add vNextWorkflow handlers and APIs Add stepGraph and steps to vNextWorkflow - 3e7b69d: Dynamic agent props - e4943b8: Default arrays to string type when transformation JSON schema to zod as per the JSON schema spec. - 11d4485: Show VNext workflows on the playground Show running status for step in vNext workflowState - 479f490: [MASTRA-3131] Add getWorkflowRunByID and add resourceId as filter for getWorkflowRuns - c23a81c: added deprecation warnings for pg and individual args - 2d4001d: Add new @msstra/libsql package and use it in create-mastra - c71013a: vNeuxt: unset currentStep for workflow status change event - 1d3b1cd: Rebump ## 0.9.1-alpha.8 ### Patch Changes - 2d17c73: Fix checking for presence of constant value mappings ## 0.9.1-alpha.7 ### Patch Changes - 1d3b1cd: Rebump ## 0.9.1-alpha.6 ### Patch Changes - c23a81c: added deprecation warnings for pg and individual args ## 0.9.1-alpha.5 ### Patch Changes - 3e7b69d: Dynamic agent props ## 0.9.1-alpha.4 ### Patch Changes - e4943b8: Default arrays to string type when transformation JSON schema to zod as per the JSON schema spec. - 479f490: [MASTRA-3131] Add getWorkflowRunByID and add resourceId as filter for getWorkflowRuns ## 0.9.1-alpha.3 ### Patch Changes - 6262bd5: Mastra server custom host config ## 0.9.1-alpha.2 ### Patch Changes - 405b63d: add ability to clone workflows with different id - 61e92f5: vNext fix workflow watch cleanup - c71013a: vNeuxt: unset currentStep for workflow status change event ## 0.9.1-alpha.1 ### Patch Changes - 20275d4: Adding warnings for current implicit Memory default options as they will be changing soon in a breaking change. Also added memory to create-mastra w/ new defaults so new projects don't see these warnings - 7d1892c: Return error object directly in vNext workflows - a90a082: Rename container to runtimeContext in vNext workflow Add steps accessor for stepFlow in vnext workflow Add getWorkflowRun to vnext workflow Add vnext_getWorkflows() to mastra core - 35955b0: Rename import to runtime-contxt - c1409ef: Add vNextWorkflow handlers and APIs Add stepGraph and steps to vNextWorkflow - 11d4485: Show VNext workflows on the playground Show running status for step in vNext workflowState - 2d4001d: Add new @msstra/libsql package and use it in create-mastra ## 0.9.1-alpha.0 ### Patch Changes - 81fb7f6: Workflows v2 ## 0.9.0 ### Minor Changes - fe3ae4d: Remove \_\_ functions in storage and move to storage proxy to make sure init is called ### Patch Changes - 000a6d4: Fixed an issue where the TokenLimiter message processor was adding new messages into the remembered messages array - 08bb78e: Added an extra safety for Memory message ordering - ed2f549: Fix exlude methods for batchTraceInsert - 7e92011: Include tools with deployment builds - 9ee4293: Improve commonjs support Add types files in the root directory to make sure typescript can resolve it without an exportsmap - 03f3cd0: Propagate context to passed in tools - c0f22b4: [MASTRA-3130] Metadata Filter Update for PG and Libsql - 71d9444: updated savemessage to not use mutation when hiding working memory - 157c741: Fix message dupes using processors - 8a8a73b: fix container to network sub agent - 0a033fa: Adds MCPServer component - 9c26508: Fixed an issue where "mastra dev" wouldn't always print out localhost:4111 logs due to new NODE_ENV fixes - 0f4eae3: Rename Container into RuntimeContext - 16a8648: Disable swaggerUI, playground for production builds, mastra instance server build config to enable swaggerUI, apiReqLogs, openAPI documentation for prod builds - 6f92295: Fixed an issue where some user messages and llm messages would have the exact same createdAt date, leading to incorrect message ordering. Added a fix for new messages as well as any that were saved before the fix in the wrong order ## 0.9.0-alpha.8 ### Patch Changes - 000a6d4: Fixed an issue where the TokenLimiter message processor was adding new messages into the remembered messages array - ed2f549: Fix exlude methods for batchTraceInsert - c0f22b4: [MASTRA-3130] Metadata Filter Update for PG and Libsql - 0a033fa: Adds MCPServer component - 9c26508: Fixed an issue where "mastra dev" wouldn't always print out localhost:4111 logs due to new NODE_ENV fixes - 0f4eae3: Rename Container into RuntimeContext - 16a8648: Disable swaggerUI, playground for production builds, mastra instance server build config to enable swaggerUI, apiReqLogs, openAPI documentation for prod builds ## 0.9.0-alpha.7 ### Patch Changes - 71d9444: updated savemessage to not use mutation when hiding working memory ## 0.9.0-alpha.6 ### Patch Changes - 157c741: Fix message dupes using processors ## 0.9.0-alpha.5 ### Patch Changes - 08bb78e: Added an extra safety for Memory message ordering ## 0.9.0-alpha.4 ### Patch Changes - 7e92011: Include tools with deployment builds ## 0.9.0-alpha.3 ### Minor Changes - fe3ae4d: Remove \_\_ functions in storage and move to storage proxy to make sure init is called ## 0.8.4-alpha.2 ### Patch Changes - 9ee4293: Improve commonjs support Add types files in the root directory to make sure typescript can resolve it without an exportsmap ## 0.8.4-alpha.1 ### Patch Changes - 8a8a73b: fix container to network sub agent - 6f92295: Fixed an issue where some user messages and llm messages would have the exact same createdAt date, leading to incorrect message ordering. Added a fix for new messages as well as any that were saved before the fix in the wrong order ## 0.8.4-alpha.0 ### Patch Changes - 03f3cd0: Propagate context to passed in tools ## 0.8.3 ### Patch Changes - d72318f: Refactored the evals table to use the DS tables - 0bcc862: Fixed an issue where we were sanitizing response message content and filter on a value that may not always be an array - 10a8caf: Removed an extra console log that made it into core - 359b089: Allowed explicitly disabling vector/embedder in Memory by passing vector: false or options.semanticRecall: false - 32e7b71: Add support for dependency injection - 37bb612: Add Elastic-2.0 licensing for packages - 7f1b291: Client Side tool call passing ## 0.8.3-alpha.5 ### Patch Changes - d72318f: Refactored the evals table to use the DS tables ## 0.8.3-alpha.4 ### Patch Changes - 7f1b291: Client Side tool call passing ## 0.8.3-alpha.3 ### Patch Changes - 10a8caf: Removed an extra console log that made it into core ## 0.8.3-alpha.2 ### Patch Changes - 0bcc862: Fixed an issue where we were sanitizing response message content and filter on a value that may not always be an array ## 0.8.3-alpha.1 ### Patch Changes - 32e7b71: Add support for dependency injection - 37bb612: Add Elastic-2.0 licensing for packages ## 0.8.3-alpha.0 ### Patch Changes - 359b089: Allowed explicitly disabling vector/embedder in Memory by passing vector: false or options.semanticRecall: false ## 0.8.2 ### Patch Changes - a06aadc: Upgrade fastembed to fix bug where fastembe cannot be imported ## 0.8.2-alpha.0 ### Patch Changes - a06aadc: Upgrade fastembed to fix bug where fastembe cannot be imported ## 0.8.1 ### Patch Changes - 99e2998: Set default max steps to 5 - 8fdb414: Custom mastra server cors config ## 0.8.1-alpha.0 ### Patch Changes - 99e2998: Set default max steps to 5 - 8fdb414: Custom mastra server cors config ## 0.8.0 ### Minor Changes - 619c39d: Added support for agents as steps ### Patch Changes - 56c31b7: Batch insert messages for libsql adapter - 5ae0180: Removed prefixed doc references - fe56be0: exclude \_\_primitive, getMemory, hasOwnMemory from traces since they create noisy traces - 93875ed: Improved the performance of Memory semantic recall by 2 to 3 times when using pg by making tweaks to @mastra/memory @mastra/core and @mastra/pg - 107bcfe: Fixed JSON parsing in memory component to prevent crashes when encountering strings that start with '[' or '{' but are not valid JSON - 9bfa12b: Accept ID on step config - 515ebfb: Fix compound subscriber bug - 5b4e19f: fix hanging and excessive workflow execution - dbbbf80: Added clickhouse storage - a0967a0: Added new "Memory Processor" feature to @mastra/core and @mastra/memory, allowing devs to modify Mastra Memory before it's sent to the LLM - fca3b21: fix server in mastra not to be mandatory - 88fa727: Added getWorkflowRuns for libsql, pg, clickhouse and upstash as well as added route getWorkflowRunsHandler - f37f535: Added variables to while and until loops - a3f0e90: Update storage initialization to ensure tables are present - 4d67826: Fix eval writes, remove id column - 6330967: Enable route timeout using server options - 8393832: Handle nested workflow view on workflow graph - 6330967: Add support for configuration of server port using Mastra instance - 99d43b9: Updated evaluate to include agent output - d7e08e8: createdAt needs to be nullable - febc8a6: Added dual tracing and fixed local tracing recursion - 7599d77: fix(deps): update ai sdk to ^4.2.2 - 0118361: Add resourceId to memory metadata - 619c39d: AgentStep -> Agent as a workflow step (WIP) - cafae83: Changed error messages for vector mismatch with index - 8076ecf: Unify workflow watch/start response - 8df4a77: Fix if-else execution order - 304397c: Add support for custom api routes in mastra ## 0.8.0-alpha.8 ### Patch Changes - 8df4a77: Fix if-else execution order ## 0.8.0-alpha.7 ### Patch Changes - febc8a6: Added dual tracing and fixed local tracing recursion ## 0.8.0-alpha.6 ### Patch Changes - a3f0e90: Update storage initialization to ensure tables are present ## 0.8.0-alpha.5 ### Patch Changes - 93875ed: Improved the performance of Memory semantic recall by 2 to 3 times when using pg by making tweaks to @mastra/memory @mastra/core and @mastra/pg ## 0.8.0-alpha.4 ### Patch Changes - d7e08e8: createdAt needs to be nullable ## 0.8.0-alpha.3 ### Patch Changes - 5ae0180: Removed prefixed doc references - 9bfa12b: Accept ID on step config - 515ebfb: Fix compound subscriber bug - 88fa727: Added getWorkflowRuns for libsql, pg, clickhouse and upstash as well as added route getWorkflowRunsHandler - f37f535: Added variables to while and until loops - 4d67826: Fix eval writes, remove id column - 6330967: Enable route timeout using server options - 8393832: Handle nested workflow view on workflow graph - 6330967: Add support for configuration of server port using Mastra instance ## 0.8.0-alpha.2 ### Patch Changes - 56c31b7: Batch insert messages for libsql adapter - dbbbf80: Added clickhouse storage - 99d43b9: Updated evaluate to include agent output ## 0.8.0-alpha.1 ### Minor Changes - 619c39d: Added support for agents as steps ### Patch Changes - fe56be0: exclude \_\_primitive, getMemory, hasOwnMemory from traces since they create noisy traces - a0967a0: Added new "Memory Processor" feature to @mastra/core and @mastra/memory, allowing devs to modify Mastra Memory before it's sent to the LLM - fca3b21: fix server in mastra not to be mandatory - 0118361: Add resourceId to memory metadata - 619c39d: AgentStep -> Agent as a workflow step (WIP) ## 0.7.1-alpha.0 ### Patch Changes - 107bcfe: Fixed JSON parsing in memory component to prevent crashes when encountering strings that start with '[' or '{' but are not valid JSON - 5b4e19f: fix hanging and excessive workflow execution - 7599d77: fix(deps): update ai sdk to ^4.2.2 - cafae83: Changed error messages for vector mismatch with index - 8076ecf: Unify workflow watch/start response - 304397c: Add support for custom api routes in mastra ## 0.7.0 ### Minor Changes - 1af25d5: Added nested workflows API ### Patch Changes - b4fbc59: Fixed an issue where sending CoreMessages to AI SDK would result in "Unsupported role: tool" errors - a838fde: Update memory.ts - a8bd4cf: Fixed JSON Schema generation for null types to prevent duplicate null entries in type arrays - 7a3eeb0: Fixed a memory issue when using useChat where new messages were formatted as ui messages, were mixed with stored core messages in memory, and a mixed list was sent to AI SDK, causing it to error - 0b54522: AgentNetwork logs - b3b34f5: Fix agent generate,stream returnType with experimental_output - a4686e8: Realtime event queue - 6530ad1: Correct agent onFinish interface - 27439ad: Updated the jsonSchemaPropertiesToTSTypes function to properly handle JSON Schema definitions where type can be an array of strings. Previously, the function only handled single string types, but according to the JSON Schema specification, type can be an array of possible types. ## 0.7.0-alpha.3 ### Patch Changes - b3b34f5: Fix agent generate,stream returnType with experimental_output - a4686e8: Realtime event queue ## 0.7.0-alpha.2 ### Patch Changes - a838fde: Update memory.ts - a8bd4cf: Fixed JSON Schema generation for null types to prevent duplicate null entries in type arrays - 7a3eeb0: Fixed a memory issue when using useChat where new messages were formatted as ui messages, were mixed with stored core messages in memory, and a mixed list was sent to AI SDK, causing it to error - 6530ad1: Correct agent onFinish interface ## 0.7.0-alpha.1 ### Minor Changes - 1af25d5: Added nested workflows API ### Patch Changes - 0b54522: AgentNetwork logs - 27439ad: Updated the jsonSchemaPropertiesToTSTypes function to properly handle JSON Schema definitions where type can be an array of strings. Previously, the function only handled single string types, but according to the JSON Schema specification, type can be an array of possible types. ## 0.6.5-alpha.0 ### Patch Changes - b4fbc59: Fixed an issue where sending CoreMessages to AI SDK would result in "Unsupported role: tool" errors ## 0.6.4 ### Patch Changes - 6794797: Check for eval values before inserting into storage - fb68a80: Inject mastra instance into llm class - b56a681: Update README and some tests for vector stores - 248cb07: Allow ai-sdk Message type for messages in agent generate and stream Fix sidebar horizontal overflow in playground ## 0.6.4-alpha.1 ### Patch Changes - 6794797: Check for eval values before inserting into storage ## 0.6.4-alpha.0 ### Patch Changes - fb68a80: Inject mastra instance into llm class - b56a681: Update README and some tests for vector stores - 248cb07: Allow ai-sdk Message type for messages in agent generate and stream Fix sidebar horizontal overflow in playground ## 0.6.3 ### Patch Changes - 404640e: AgentNetwork changeset - 3bce733: fix: agent.generate only get thread if there is threadID ## 0.6.3-alpha.1 ### Patch Changes - 3bce733: fix: agent.generate only get thread if there is threadID ## 0.6.3-alpha.0 ### Patch Changes - 404640e: AgentNetwork changeset ## 0.6.2 ### Patch Changes - beaf1c2: createTool type fixes - 3084e13: More parallel memory operations ## 0.6.2-alpha.0 ### Patch Changes - beaf1c2: createTool type fixes - 3084e13: More parallel memory operations ## 0.6.1 ### Patch Changes - fc2f89c: Insert static payload into inputData - dfbb131: Fix after method on multiple passes - f4854ee: Fix else branch execution when if-branch has loops - afaf73f: Add fix for vercel tools and optional instructions - 0850b4c: Watch and resume per run - 7bcfaee: Remove node_modules-path dir which calls \_\_dirname at the top level and breaks some esm runtimes - 44631b1: Fix after usage with skipped conditions on the awaited steps - 9116d70: Handle the different workflow methods in workflow graph - 6e559a0: Update Voice for realtime providers - 5f43505: feat: OpenAI realtime voice provider for speech to speech communication Update voice speaking event type ## 0.6.1-alpha.2 ### Patch Changes - fc2f89c: Insert static payload into inputData - dfbb131: Fix after method on multiple passes - 0850b4c: Watch and resume per run - 9116d70: Handle the different workflow methods in workflow graph ## 0.6.1-alpha.1 ### Patch Changes - f4854ee: Fix else branch execution when if-branch has loops - afaf73f: Add fix for vercel tools and optional instructions - 44631b1: Fix after usage with skipped conditions on the awaited steps - 6e559a0: Update Voice for realtime providers - 5f43505: feat: OpenAI realtime voice provider for speech to speech communication Update voice speaking event type ## 0.6.1-alpha.0 ### Patch Changes - 7bcfaee: Remove node_modules-path dir which calls \_\_dirname at the top level and breaks some esm runtimes ## 0.6.0 ### Minor Changes - 1c8cda4: Experimental .afterEvent() support. Fixed suspend/resume in first workflow or .after() branch step. Changed suspend metadata to be in context.resumeData instead of resumed step output. - 95b4144: Added server middleware to apply custom functionality in API endpoints like auth ### Patch Changes - 16b98d9: Reduce default step retry attempts - 3729dbd: Fixed a bug where useChat with client side tool calling and Memory would not work. Added docs for using Memory with useChat() - c2144f4: Enable dynamic import of default-storage to reduce runtime/bundle size when not using default storage ## 0.6.0-alpha.1 ### Minor Changes - 1c8cda4: Experimental .afterEvent() support. Fixed suspend/resume in first workflow or .after() branch step. Changed suspend metadata to be in context.resumeData instead of resumed step output. - 95b4144: Added server middleware to apply custom functionality in API endpoints like auth ### Patch Changes - 16b98d9: Reduce default step retry attempts - c2144f4: Enable dynamic import of default-storage to reduce runtime/bundle size when not using default storage ## 0.5.1-alpha.0 ### Patch Changes - 3729dbd: Fixed a bug where useChat with client side tool calling and Memory would not work. Added docs for using Memory with useChat() ## 0.5.0 ### Minor Changes - 59df7b6: Added a new option to use tool-calls for saving working memory: new Memory({ workingMemory: { enabled: true, use: "tool-call" } }). This is to support response methods like toDataStream where masking working memory chunks would be more resource intensive and complex. To support this `memory` is now passed into tool execute args. - dfbe4e9: Added new looping constructs with while/until and optional enum-based cyclical condition execution - 3764e71: Workflow trigger data should only accept object types - 02ffb7b: Added updateIndexById and deleteIndexById methods in the MastraVector inteface - 358f069: Experimental if-else branching in between steps ### Patch Changes - a910463: Improve typinges for getStepResult and workflow results - 22643eb: Replace MastraPrimitives with direct Mastra instance - 6feb23f: Fix for else condition with ref/query syntax - f2d6727: Support for compound `.after` syntax - 7a7a547: Fix telemetry getter in hono server - 29f3a82: Improve agent generate,stream returnTypes - 3d0e290: Fixed an issue where messages that were numbers weren't being stored as strings. Fixed incorrect array access when retrieving memory messages - e9fbac5: Update Vercel tools to have id and update deployer - 301e4ee: Fix log level showing number in core logger - ee667a2: Fixed a serialization bug for thread IDs and dates in memory - dab255b: Fixed bug where using an in memory libsql db (config.url = ":memory:) for memory would throw errors about missing tables - 1e8bcbc: Fix suspend types - f6678e4: Fixed an issue where we were using a non-windows-friendly absolute path check for libsql file urls - 9e81f35: Fix query filter for vector search and rerank - c93798b: Added MastraLanguageModel which extends LanguageModelV1 - a85ab24: make execute optional for create tool - dbd9f2d: Handle different condition types on workflow graph - 59df7b6: Keep default memory db in .mastra/mastra.db, not .mastra/output/memory.db for consistency - caefaa2: Added optional chaining to a memory function call that may not exist - c151ae6: Fixed an issue where models that don't support structured output would error when generating a thread title. Added an option to disable thread title llm generation `new Memory({ threads: { generateTitle: false }})` - 52e0418: Split up action types between tools and workflows - d79aedf: Fix import/require paths in these package.json - 03236ec: Added GRPC Exporter for Laminar and updated dodcs for Observability Providers - df982db: Updated Agent tool input to accept vercel tool format - a171b37: Better retry mechanisms - 506f1d5: Properly serialize any date object when inserting into libsql - 0461849: Fixed a bug where mastra.db file location was inconsistently created when running mastra dev vs running a file directly (tsx src/index.ts for ex) - 2259379: Add documentation for workflow looping APIs - aeb5e36: Adds default schema for tool when not provided - f2301de: Added the ability to ensure the accessed thread in memory.query() is for the right resource id. ex memory.query({ threadId, resourceId }). If the resourceId doesn't own the thread it will throw an error. - fd4a1d7: Update cjs bundling to make sure files are split - c139344: When converting JSON schemas to Zod schemas, we were sometimes marking optional fields as nullable instead, making them required with a null value, even if the schema didn't mark them as required ## 0.5.0-alpha.12 ### Patch Changes - a85ab24: make execute optional for create tool ## 0.5.0-alpha.11 ### Patch Changes - 7a7a547: Fix telemetry getter in hono server - c93798b: Added MastraLanguageModel which extends LanguageModelV1 - dbd9f2d: Handle different condition types on workflow graph - a171b37: Better retry mechanisms - fd4a1d7: Update cjs bundling to make sure files are split ## 0.5.0-alpha.10 ### Patch Changes - a910463: Improve typinges for getStepResult and workflow results ## 0.5.0-alpha.9 ### Patch Changes - e9fbac5: Update Vercel tools to have id and update deployer - 1e8bcbc: Fix suspend types - aeb5e36: Adds default schema for tool when not provided - f2301de: Added the ability to ensure the accessed thread in memory.query() is for the right resource id. ex memory.query({ threadId, resourceId }). If the resourceId doesn't own the thread it will throw an error. ## 0.5.0-alpha.8 ### Patch Changes - 506f1d5: Properly serialize any date object when inserting into libsql ## 0.5.0-alpha.7 ### Patch Changes - ee667a2: Fixed a serialization bug for thread IDs and dates in memory ## 0.5.0-alpha.6 ### Patch Changes - f6678e4: Fixed an issue where we were using a non-windows-friendly absolute path check for libsql file urls ## 0.5.0-alpha.5 ### Minor Changes - dfbe4e9: Added new looping constructs with while/until and optional enum-based cyclical condition execution - 3764e71: Workflow trigger data should only accept object types - 358f069: Experimental if-else branching in between steps ### Patch Changes - 22643eb: Replace MastraPrimitives with direct Mastra instance - 6feb23f: Fix for else condition with ref/query syntax - f2d6727: Support for compound `.after` syntax - 301e4ee: Fix log level showing number in core logger - 9e81f35: Fix query filter for vector search and rerank - caefaa2: Added optional chaining to a memory function call that may not exist - c151ae6: Fixed an issue where models that don't support structured output would error when generating a thread title. Added an option to disable thread title llm generation `new Memory({ threads: { generateTitle: false }})` - 52e0418: Split up action types between tools and workflows - 03236ec: Added GRPC Exporter for Laminar and updated dodcs for Observability Providers - df982db: Updated Agent tool input to accept vercel tool format - 0461849: Fixed a bug where mastra.db file location was inconsistently created when running mastra dev vs running a file directly (tsx src/index.ts for ex) - 2259379: Add documentation for workflow looping APIs ## 0.5.0-alpha.4 ### Patch Changes - d79aedf: Fix import/require paths in these package.json ## 0.5.0-alpha.3 ### Patch Changes - 3d0e290: Fixed an issue where messages that were numbers weren't being stored as strings. Fixed incorrect array access when retrieving memory messages ## 0.5.0-alpha.2 ### Minor Changes - 02ffb7b: Added updateIndexById and deleteIndexById methods in the MastraVector inteface ## 0.5.0-alpha.1 ### Patch Changes - dab255b: Fixed bug where using an in memory libsql db (config.url = ":memory:) for memory would throw errors about missing tables ## 0.5.0-alpha.0 ### Minor Changes - 59df7b6: Added a new option to use tool-calls for saving working memory: new Memory({ workingMemory: { enabled: true, use: "tool-call" } }). This is to support response methods like toDataStream where masking working memory chunks would be more resource intensive and complex. To support this `memory` is now passed into tool execute args. ### Patch Changes - 29f3a82: Improve agent generate,stream returnTypes - 59df7b6: Keep default memory db in .mastra/mastra.db, not .mastra/output/memory.db for consistency - c139344: When converting JSON schemas to Zod schemas, we were sometimes marking optional fields as nullable instead, making them required with a null value, even if the schema didn't mark them as required ## 0.4.4 ### Patch Changes - 1da20e7: Update typechecks for positional args ## 0.4.4-alpha.0 ### Patch Changes - 1da20e7: Update typechecks for positional args ## 0.4.3 ### Patch Changes - 0d185b1: Ensure proper message sort order for tool calls and results when using Memory semanticRecall feature - ed55f1d: Fixes to watch payload in workloads with nested branching - 06aa827: add option for specifying telemetry settings at generation time - 0fd78ac: Update vector store functions to use object params - 2512a93: Support all aisdk options for agent stream,generate - e62de74: Fix optional tool llm execute - 0d25b75: Add all agent stream,generate option to cliend-js sdk - fd14a3f: Updating filter location from @mastra/core/filter to @mastra/core/vector/filter - 8d13b14: Fixes early exits in workflows with branching - 3f369a2: A better async/await based interface for suspend/resume tracking - 3ee4831: Fixed agent.generate() so it properly infers the return type based on output: schema | string and experimental_output: schema - 4d4e1e1: Updated vector tests and pinecone - bb4f447: Add support for commonjs - 108793c: Throw error when resourceId is not provided but Memory is configured and a threadId was passed - 5f28f44: Updated Chroma Vector to allow for document storage - dabecf4: Pass threadId and resourceId into tool execute functions so that tools are able to query memory ## 0.4.3-alpha.4 ### Patch Changes - dabecf4: Pass threadId and resourceId into tool execute functions so that tools are able to query memory ## 0.4.3-alpha.3 ### Patch Changes - 0fd78ac: Update vector store functions to use object params - 0d25b75: Add all agent stream,generate option to cliend-js sdk - fd14a3f: Updating filter location from @mastra/core/filter to @mastra/core/vector/filter - 3f369a2: A better async/await based interface for suspend/resume tracking - 4d4e1e1: Updated vector tests and pinecone - bb4f447: Add support for commonjs ## 0.4.3-alpha.2 ### Patch Changes - 2512a93: Support all aisdk options for agent stream,generate - e62de74: Fix optional tool llm execute ## 0.4.3-alpha.1 ### Patch Changes - 0d185b1: Ensure proper message sort order for tool calls and results when using Memory semanticRecall feature - ed55f1d: Fixes to watch payload in workloads with nested branching - 8d13b14: Fixes early exits in workflows with branching - 3ee4831: Fixed agent.generate() so it properly infers the return type based on output: schema | string and experimental_output: schema - 108793c: Throw error when resourceId is not provided but Memory is configured and a threadId was passed - 5f28f44: Updated Chroma Vector to allow for document storage ## 0.4.3-alpha.0 ### Patch Changes - 06aa827: add option for specifying telemetry settings at generation time ## 0.4.2 ### Patch Changes - 7fceae1: Removed system prompt with todays date since it can interfere with input token caching. Also removed a memory system prompt that refered to date ranges - we no longer use date ranges for memory so this was removed - 8d94c3e: Optional tool execute - 99dcdb5: Inject primitives into condition function, and renames getStepPayload to getStepResult. - 6cb63e0: Experimental output support - f626fbb: add stt and tts capabilities on agent - e752340: Move storage/vector libSQL to own files so they do not get imported when not using bundlers. - eb91535: Correct typo in LanguageModel-related ## 0.4.2-alpha.2 ### Patch Changes - 8d94c3e: Optional tool execute - 99dcdb5: Inject primitives into condition function, and renames getStepPayload to getStepResult. - e752340: Move storage/vector libSQL to own files so they do not get imported when not using bundlers. - eb91535: Correct typo in LanguageModel-related ## 0.4.2-alpha.1 ### Patch Changes - 6cb63e0: Experimental output support ## 0.4.2-alpha.0 ### Patch Changes - 7fceae1: Removed system prompt with todays date since it can interfere with input token caching. Also removed a memory system prompt that refered to date ranges - we no longer use date ranges for memory so this was removed - f626fbb: add stt and tts capabilities on agent ## 0.4.1 ### Patch Changes - ce44b9b: Fixed a bug where embeddings were being created for memory even when semanticRecall was turned off - 967da43: Logger, transport fixes - b405f08: add stt and tts capabilities on agent ## 0.4.0 ### Minor Changes - 2fc618f: Add MastraVoice class ### Patch Changes - fe0fd01: Fixed a bug where masked tags don't work when a chunk includes other text (ex "o w") in the maskStreamTags() util ## 0.4.0-alpha.1 ### Patch Changes - fe0fd01: Fixed a bug where masked tags don't work when a chunk includes other text (ex "o w") in the maskStreamTags() util ## 0.4.0-alpha.0 ### Minor Changes - 2fc618f: Add MastraVoice class ## 0.3.0 ### Minor Changes - f205ede: Memory can no longer be added to new Mastra(), only to new Agent() - this is for simplicity as each agent will typically need its own memory settings ## 0.2.1 ### Patch Changes - d59f1a8: Added example docs for evals and export metricJudge - 91ef439: Add eslint and ran autofix - 4a25be4: Fixed race condition when multiple storage methods attempt to initialize the db at the same time - bf2e88f: Fix treeshake bug - 2f0d707: Fix wrong usage of peerdep of AI pkg - aac1667: Improve treeshaking of core and output ## 0.2.1-alpha.0 ### Patch Changes - d59f1a8: Added example docs for evals and export metricJudge - 91ef439: Add eslint and ran autofix - 4a25be4: Fixed race condition when multiple storage methods attempt to initialize the db at the same time - bf2e88f: Fix treeshake bug - 2f0d707: Fix wrong usage of peerdep of AI pkg - aac1667: Improve treeshaking of core and output ## 0.2.0 ### Minor Changes - 4d4f6b6: Update deployer - 30322ce: Added new Memory API for managed agent memory via MastraStorage and MastraVector classes - d7d465a: Breaking change for Memory: embeddings: {} has been replaced with embedder: new OpenAIEmbedder() (or whichever embedder you want - check the docs) - 5285356: Renamed MastraLibSQLStorage and MastraLibSQLVector to DefaultStorage and DefaultVectorDB. I left the old export names so that it wont break anyones projects but all docs now show the new names - 74b3078: Reduce verbosity in workflows API - 8b416d9: Breaking changes - 16e5b04: Moved @mastra/vector-libsql into @mastra/core/vector/libsql - 8769a62: Split core into separate entry files ### Patch Changes - f537e33: feat: add default logger - 6f2c0f5: Prevent telemetry proxy from converting sync methods to async - e4d4ede: Better setLogger() - 0be7181: Fix forward version - dd6d87f: Update Agent and LLM config to accept temperature setting - 9029796: add more logs to agent for debugging - 6fa4bd2: New LLM primitive, OpenAI, AmazonBedrock - f031a1f: expose embed from rag, and refactor embed - 8151f44: Added \_\_registerPrimitives to model.ts - d7d465a: Embedding api - 73d112c: Core and deployer fixes - 592e3cf: Add custom rag tools, add vector retrieval, and update docs - 9d1796d: Fix storage and eval serialization on api - e897f1c: Eval change - 4a54c82: Fix dane labelling functionality - 3967e69: Added GraphRAG implementation and updated docs - 8ae2bbc: Dane publishing - e9d1b47: Rename Memory options historySearch to semanticRecall, rename embeddingOptions to embedding - 016493a: Deprecate metrics in favor of evals - bc40916: Pass mastra instance directly into actions allowing access to all registered primitives - 93a3719: Mastra prompt template engine - 7d83b92: Create default storage and move evals towards it - 9fb3039: Storage - d5e12de: optional mastra config object - e1dd94a: update the api for embeddings - 07c069d: Add dotenv as dependency - 5cdfb88: add getWorkflows method to core, add runId to workflow logs, update workflow starter file, add workflows page with table and workflow page with info, endpoints and logs - 837a288: MAJOR Revamp of tools, workflows, syncs. - 685108a: Remove syncs and excess rag - c8ff2f5: Fixed passing CoreMessages to stream/generate where the role is not user. Previously all messages would be rewritten to have role: "user" - 5fdc87c: Update evals storage in attachListeners - ae7bf94: Fix loggers messing up deploys - 8e7814f: Add payload getter on machine context - 66a03ec: Removed an extra llm call that was needed for the old Memory API but is no longer needed - 7d87a15: generate command in agent, and support array of message strings - b97ca96: Tracing into default storage - 23dcb23: Redeploy core - 033eda6: More fixes for refactor - 8105fae: Split embed into embed and embedMany to handle different return types - e097800: TTS in core - 1944807: Unified logger and major step in better logs - 1874f40: Added re ranking tool to RAG - 685108a: Removing mastra syncs - f7d1131: Improved types when missing inputSchema - 79acad0: Better type safety on trigger step - 7a19083: Updates to the LLM class - 382f4dc: move telemetry init to instrumentation.mjs file in build directory - 1ebd071: Add more embedding models - 0b74006: Workflow updates - 2f17a5f: Added filter translator and tests for Qdrant - f368477: Added evals package and added evals in core - 7892533: Updated test evals to use Mastra Storage - 9c10484: update all packages - b726bf5: Fix agent memory int. - 70dabd9: Fix broken publish - 21fe536: add keyword tags for packages and update readmes - 176bc42: Added runId and proper parent spans to workflow tracing - 401a4d9: Add simple conditions test - 2e099d2: Allow trigger passed in to `then` step - 0b826f6: Allow agents to use ZodSchemas in structuredOutput - d68b532: Updated debug logs - 75bf3f0: remove context bug in agent tool execution, update style for mastra dev rendered pages - e6d8055: Added Mastra Storage to add and query live evals - e2e76de: Anthropic model added to new primitive structure - ccbc581: Updated operator validation and handling for all vector stores - 5950de5: Added update instructions API - fe3dcb0: Add fastembed import error handling - 78eec7c: Started implementation on Unified Filter API for several vector stores. - a8a459a: Updated Evals table UI - 0be7181: Add perplexity models - 7b87567: Propagate setLogger calls to more places - b524c22: Package upgrades - df843d3: Fixed libsql db relative file paths so they're always outside the .mastra directory. If they're inside .mastra they will be deleted when code is re-bundled - 4534e77: Fix fastembed imports in mastra cloud for default embedder - d6d8159: Workflow graph diagram - 0bd142c: Fixes learned from docs - 9625602: Use mastra core splitted bundles in other packages - 72d1990: Updated evals table schema - f6ba259: simplify generate api - 2712098: add getAgents method to core and route to cli dev, add homepage interface to cli - eedb829: Better types, and correct payload resolution - cb290ee: Reworked the Memory public API to have more intuitive and simple property names - b4d7416: Added the ability to pass a configured Memory class instance directly to new Agent instances instead of passing memory to Mastra - e608d8c: Export CoreMessage Types from ai sdk - 06b2c0a: Update summarization prompt and fix eval input - 002d6d8: add memory to playground agent - e448a26: Correctly pass down runId to called tools - fd494a3: TTS module - dc90663: Fix issues in packages - c872875: update createMultiLogger to combineLogger - 3c4488b: Fix context not passed in agent tool execution - a7b016d: Added export for MockMastraEngine from @mastra/core - fd75f3c: Added storage, vector, embedder setters to the base MastraMemory class - 7f24c29: Add Chroma Filter translator and updated vector store tests - 2017553: Added fallback title when calling createThread() with no title - this is needed as storage db schemas mark title as non-null - a10b7a3: Implemented new filtering for vectorQueryTool and updated docs - cf6d825: Fixed a bug where 0 values in memory configs were falling back to default val. Removed a noisy log. Removed a deprecated option - 963c15a: Add new toolset primitive and implementation for composio - 7365b6c: More models - 5ee67d3: make trace name configurable for telemetry exporter - d38f7a6: clean up old methods in agent - 38b7f66: Update deployer logic - 2fa7f53: add more logs to workflow, only log failed workflow if all steps fail, animate workflow diagram edges - 1420ae2: Fix storage logger - f6da688: update agents/:agentId page in dev to show agent details and endpoints, add getTools to agent - 3700be1: Added helpful error when using vector with Memory class - error now contains embedding option example - 9ade36e: Changed measure for evals, added endpoints, attached metrics to agent, added ui for evals in playground, and updated docs - 10870bc: Added a default vector db (libsql) and embedder (fastembed) so that new Memory() can be initialized with zero config - 2b01511: Update CONSOLE logger to store logs and return logs, add logs to dev agent page - a870123: Added local embedder class that uses fastembed-js, a Typescript/NodeJS implementation of @Qdrant/fastembed - ccf115c: Fixed incomplete tool call errors when including memory message history in context - 04434b6: Create separate logger file - 5811de6: Updates spec-writer example to use new workflows constructs. Small improvements to workflow internals. Switch transformer tokenizer for js compatible one. - 9f3ab05: pass custom telemetry exporter - 66a5392: batchInsert needs init. Use private version for internal calls - 4b1ce2c: Update Google model support in documentation and type definitions to include new Gemini versions - 14064f2: Deployer abstract class - f5dfa20: only add logger if there is a logger - 327ece7: Updates for ts versions - da2e8d3: Export EmbedManyResult and EmbedResult from ai sdk and update docs - 95a4697: Fixed trace method for telemetry - d5fccfb: expose model function - 3427b95: Updated docs to include intermediate rag examples (metadata filtering, query filters, etc) - 538a136: Added Simple Condition for workflows, updated /api/workflows/{workflowId}/execute endpoint and docs - e66643a: Add o1 models - b5393f1: New example: Dane and many fixes to make it work - d2cd535: configure dotenv in core - c2dd6b5: This set of changes introduces a new .step API for subscribing to step executions for running other step chains. It also improves step types, and enables the ability to create a cyclic step chain. - 67637ba: Fixed storage bugs related to the new Memory API - 836f4e3: Fixed some issues with memory, added Upstash as a memory provider. Silenced dev logs in core - 5ee2e78: Update core for Alpha3 release - cd02c56: Implement a new and improved API for workflows. - 01502b0: fix thread title containing unnecessary text and removed unnecessary logs in memory - d9c8dd0: Logger changes for default transports - 9fb59d6: changeset - a9345f9: Fixed tsc build for core types - 99f1847: Clean up logs - 04f3171: More providers - d5ec619: Remove promptTemplate from core - 27275c9: Added new short term "working" memory for agents. Also added a "maskStreamTags" helper to assist in hiding working memory xml blocks in streamed responses - ae7bf94: Changeset - 4f1d1a1: Enforce types ann cleanup package.json - ee4de15: Dane fixes - 202d404: Added instructions when generating evals - a221426: Simplify workflows watch API ## 0.2.0-alpha.110 ### Patch Changes - 016493a: Deprecate metrics in favor of evals - 382f4dc: move telemetry init to instrumentation.mjs file in build directory - 176bc42: Added runId and proper parent spans to workflow tracing - d68b532: Updated debug logs - fe3dcb0: Add fastembed import error handling - e448a26: Correctly pass down runId to called tools - fd75f3c: Added storage, vector, embedder setters to the base MastraMemory class - ccf115c: Fixed incomplete tool call errors when including memory message history in context - a221426: Simplify workflows watch API ## 0.2.0-alpha.109 ### Patch Changes - d5fccfb: expose model function ## 0.2.0-alpha.108 ### Patch Changes - 5ee67d3: make trace name configurable for telemetry exporter - 95a4697: Fixed trace method for telemetry ## 0.2.0-alpha.107 ### Patch Changes - 66a5392: batchInsert needs init. Use private version for internal calls ## 0.2.0-alpha.106 ### Patch Changes - 6f2c0f5: Prevent telemetry proxy from converting sync methods to async - a8a459a: Updated Evals table UI ## 0.2.0-alpha.105 ### Patch Changes - 1420ae2: Fix storage logger - 99f1847: Clean up logs ## 0.2.0-alpha.104 ### Patch Changes - 5fdc87c: Update evals storage in attachListeners - b97ca96: Tracing into default storage - 72d1990: Updated evals table schema - cf6d825: Fixed a bug where 0 values in memory configs were falling back to default val. Removed a noisy log. Removed a deprecated option - 10870bc: Added a default vector db (libsql) and embedder (fastembed) so that new Memory() can be initialized with zero config ## 0.2.0-alpha.103 ### Patch Changes - 4534e77: Fix fastembed imports in mastra cloud for default embedder ## 0.2.0-alpha.102 ### Patch Changes - a9345f9: Fixed tsc build for core types ## 0.2.0-alpha.101 ### Patch Changes - 66a03ec: Removed an extra llm call that was needed for the old Memory API but is no longer needed - 4f1d1a1: Enforce types ann cleanup package.json ## 0.2.0-alpha.100 ### Patch Changes - 9d1796d: Fix storage and eval serialization on api ## 0.2.0-alpha.99 ### Patch Changes - 7d83b92: Create default storage and move evals towards it ## 0.2.0-alpha.98 ### Patch Changes - 70dabd9: Fix broken publish - 202d404: Added instructions when generating evals ## 0.2.0-alpha.97 ### Patch Changes - 07c069d: Add dotenv as dependency - 7892533: Updated test evals to use Mastra Storage - e6d8055: Added Mastra Storage to add and query live evals - 5950de5: Added update instructions API - df843d3: Fixed libsql db relative file paths so they're always outside the .mastra directory. If they're inside .mastra they will be deleted when code is re-bundled - a870123: Added local embedder class that uses fastembed-js, a Typescript/NodeJS implementation of @Qdrant/fastembed ## 0.2.0-alpha.96 ### Minor Changes - 74b3078: Reduce verbosity in workflows API ## 0.2.0-alpha.95 ### Patch Changes - 9fb59d6: changeset ## 0.2.0-alpha.94 ### Minor Changes - 8b416d9: Breaking changes ### Patch Changes - 9c10484: update all packages ## 0.2.0-alpha.93 ### Minor Changes - 5285356: Renamed MastraLibSQLStorage and MastraLibSQLVector to DefaultStorage and DefaultVectorDB. I left the old export names so that it wont break anyones projects but all docs now show the new names ## 0.2.0-alpha.92 ### Minor Changes - 4d4f6b6: Update deployer ## 0.2.0-alpha.91 ### Minor Changes - d7d465a: Breaking change for Memory: embeddings: {} has been replaced with embedder: new OpenAIEmbedder() (or whichever embedder you want - check the docs) - 16e5b04: Moved @mastra/vector-libsql into @mastra/core/vector/libsql ### Patch Changes - d7d465a: Embedding api - 2017553: Added fallback title when calling createThread() with no title - this is needed as storage db schemas mark title as non-null - a10b7a3: Implemented new filtering for vectorQueryTool and updated docs ## 0.2.0-alpha.90 ### Patch Changes - 8151f44: Added \_\_registerPrimitives to model.ts - e897f1c: Eval change - 3700be1: Added helpful error when using vector with Memory class - error now contains embedding option example ## 0.2.0-alpha.89 ### Patch Changes - 27275c9: Added new short term "working" memory for agents. Also added a "maskStreamTags" helper to assist in hiding working memory xml blocks in streamed responses ## 0.2.0-alpha.88 ### Patch Changes - ccbc581: Updated operator validation and handling for all vector stores ## 0.2.0-alpha.87 ### Patch Changes - 7365b6c: More models ## 0.2.0-alpha.86 ### Patch Changes - 6fa4bd2: New LLM primitive, OpenAI, AmazonBedrock - e2e76de: Anthropic model added to new primitive structure - 7f24c29: Add Chroma Filter translator and updated vector store tests - 67637ba: Fixed storage bugs related to the new Memory API - 04f3171: More providers ## 0.2.0-alpha.85 ### Patch Changes - e9d1b47: Rename Memory options historySearch to semanticRecall, rename embeddingOptions to embedding ## 0.2.0-alpha.84 ### Patch Changes - 2f17a5f: Added filter translator and tests for Qdrant - cb290ee: Reworked the Memory public API to have more intuitive and simple property names - b4d7416: Added the ability to pass a configured Memory class instance directly to new Agent instances instead of passing memory to Mastra - 38b7f66: Update deployer logic ## 0.2.0-alpha.83 ### Minor Changes - 30322ce: Added new Memory API for managed agent memory via MastraStorage and MastraVector classes - 8769a62: Split core into separate entry files ### Patch Changes - 78eec7c: Started implementation on Unified Filter API for several vector stores. - 9625602: Use mastra core splitted bundles in other packages ## 0.1.27-alpha.82 ### Patch Changes - 73d112c: Core and deployer fixes ## 0.1.27-alpha.81 ### Patch Changes - 9fb3039: Storage ## 0.1.27-alpha.80 ### Patch Changes - 327ece7: Updates for ts versions ## 0.1.27-alpha.79 ### Patch Changes - 21fe536: add keyword tags for packages and update readmes ## 0.1.27-alpha.78 ### Patch Changes - 685108a: Remove syncs and excess rag - 685108a: Removing mastra syncs ## 0.1.27-alpha.77 ### Patch Changes - 8105fae: Split embed into embed and embedMany to handle different return types ## 0.1.27-alpha.76 ### Patch Changes - ae7bf94: Fix loggers messing up deploys - ae7bf94: Changeset ## 0.1.27-alpha.75 ### Patch Changes - 23dcb23: Redeploy core ## 0.1.27-alpha.74 ### Patch Changes - 7b87567: Propagate setLogger calls to more places ## 0.1.27-alpha.73 ### Patch Changes - 3427b95: Updated docs to include intermediate rag examples (metadata filtering, query filters, etc) ## 0.1.27-alpha.72 ### Patch Changes - e4d4ede: Better setLogger() - 06b2c0a: Update summarization prompt and fix eval input ## 0.1.27-alpha.71 ### Patch Changes - d9c8dd0: Logger changes for default transports ## 0.1.27-alpha.70 ### Patch Changes - dd6d87f: Update Agent and LLM config to accept temperature setting - 04434b6: Create separate logger file ## 0.1.27-alpha.69 ### Patch Changes - 1944807: Unified logger and major step in better logs - 9ade36e: Changed measure for evals, added endpoints, attached metrics to agent, added ui for evals in playground, and updated docs ## 0.1.27-alpha.68 ### Patch Changes - 0be7181: Fix forward version - 0be7181: Add perplexity models ## 0.1.27-alpha.67 ### Patch Changes - c8ff2f5: Fixed passing CoreMessages to stream/generate where the role is not user. Previously all messages would be rewritten to have role: "user" ## 0.1.27-alpha.66 ### Patch Changes - 14064f2: Deployer abstract class ## 0.1.27-alpha.65 ### Patch Changes - e66643a: Add o1 models ## 0.1.27-alpha.64 ### Patch Changes - f368477: Added evals package and added evals in core - d5ec619: Remove promptTemplate from core ## 0.1.27-alpha.63 ### Patch Changes - e097800: TTS in core ## 0.1.27-alpha.62 ### Patch Changes - 93a3719: Mastra prompt template engine ## 0.1.27-alpha.61 ### Patch Changes - dc90663: Fix issues in packages ## 0.1.27-alpha.60 ### Patch Changes - 3967e69: Added GraphRAG implementation and updated docs ## 0.1.27-alpha.59 ### Patch Changes - b524c22: Package upgrades ## 0.1.27-alpha.58 ### Patch Changes - 1874f40: Added re ranking tool to RAG - 4b1ce2c: Update Google model support in documentation and type definitions to include new Gemini versions ## 0.1.27-alpha.57 ### Patch Changes - fd494a3: TTS module ## 0.1.27-alpha.56 ### Patch Changes - 9f3ab05: pass custom telemetry exporter ## 0.1.27-alpha.55 ### Patch Changes - 592e3cf: Add custom rag tools, add vector retrieval, and update docs - 837a288: MAJOR Revamp of tools, workflows, syncs. - 0b74006: Workflow updates ## 0.1.27-alpha.54 ### Patch Changes - d2cd535: configure dotenv in core ## 0.1.27-alpha.53 ### Patch Changes - 8e7814f: Add payload getter on machine context ## 0.1.27-alpha.52 ### Patch Changes - eedb829: Better types, and correct payload resolution ## 0.1.27-alpha.51 ### Patch Changes - a7b016d: Added export for MockMastraEngine from @mastra/core - da2e8d3: Export EmbedManyResult and EmbedResult from ai sdk and update docs - 538a136: Added Simple Condition for workflows, updated /api/workflows/{workflowId}/execute endpoint and docs ## 0.1.27-alpha.50 ### Patch Changes - 401a4d9: Add simple conditions test ## 0.1.27-alpha.49 ### Patch Changes - 79acad0: Better type safety on trigger step - f5dfa20: only add logger if there is a logger ## 0.1.27-alpha.48 ### Patch Changes - b726bf5: Fix agent memory int. ## 0.1.27-alpha.47 ### Patch Changes - f6ba259: simplify generate api ## 0.1.27-alpha.46 ### Patch Changes - 8ae2bbc: Dane publishing - 0bd142c: Fixes learned from docs - ee4de15: Dane fixes ## 0.1.27-alpha.45 ### Patch Changes - e608d8c: Export CoreMessage Types from ai sdk - 002d6d8: add memory to playground agent ## 0.1.27-alpha.44 ### Patch Changes - 2fa7f53: add more logs to workflow, only log failed workflow if all steps fail, animate workflow diagram edges ## 0.1.27-alpha.43 ### Patch Changes - 2e099d2: Allow trigger passed in to `then` step - d6d8159: Workflow graph diagram ## 0.1.27-alpha.42 ### Patch Changes - 4a54c82: Fix dane labelling functionality ## 0.1.27-alpha.41 ### Patch Changes - 5cdfb88: add getWorkflows method to core, add runId to workflow logs, update workflow starter file, add workflows page with table and workflow page with info, endpoints and logs ## 0.1.27-alpha.40 ### Patch Changes - 9029796: add more logs to agent for debugging ## 0.1.27-alpha.39 ### Patch Changes - 2b01511: Update CONSOLE logger to store logs and return logs, add logs to dev agent page ## 0.1.27-alpha.38 ### Patch Changes - f031a1f: expose embed from rag, and refactor embed ## 0.1.27-alpha.37 ### Patch Changes - c872875: update createMultiLogger to combineLogger - f6da688: update agents/:agentId page in dev to show agent details and endpoints, add getTools to agent - b5393f1: New example: Dane and many fixes to make it work ## 0.1.27-alpha.36 ### Patch Changes - f537e33: feat: add default logger - bc40916: Pass mastra instance directly into actions allowing access to all registered primitives - f7d1131: Improved types when missing inputSchema - 75bf3f0: remove context bug in agent tool execution, update style for mastra dev rendered pages - 3c4488b: Fix context not passed in agent tool execution - d38f7a6: clean up old methods in agent ## 0.1.27-alpha.35 ### Patch Changes - 033eda6: More fixes for refactor ## 0.1.27-alpha.34 ### Patch Changes - 837a288: MAJOR Revamp of tools, workflows, syncs. - 5811de6: Updates spec-writer example to use new workflows constructs. Small improvements to workflow internals. Switch transformer tokenizer for js compatible one. ## 0.1.27-alpha.33 ### Patch Changes - e1dd94a: update the api for embeddings ## 0.1.27-alpha.32 ### Patch Changes - 2712098: add getAgents method to core and route to cli dev, add homepage interface to cli ## 0.1.27-alpha.31 ### Patch Changes - c2dd6b5: This set of changes introduces a new .step API for subscribing to step executions for running other step chains. It also improves step types, and enables the ability to create a cyclic step chain. ## 0.1.27-alpha.30 ### Patch Changes - 963c15a: Add new toolset primitive and implementation for composio ## 0.1.27-alpha.29 ### Patch Changes - 7d87a15: generate command in agent, and support array of message strings ## 0.1.27-alpha.28 ### Patch Changes - 1ebd071: Add more embedding models ## 0.1.27-alpha.27 ### Patch Changes - cd02c56: Implement a new and improved API for workflows. ## 0.1.27-alpha.26 ### Patch Changes - d5e12de: optional mastra config object ## 0.1.27-alpha.25 ### Patch Changes - 01502b0: fix thread title containing unnecessary text and removed unnecessary logs in memory ## 0.1.27-alpha.24 ### Patch Changes - 836f4e3: Fixed some issues with memory, added Upstash as a memory provider. Silenced dev logs in core ## 0.1.27-alpha.23 ### Patch Changes - 0b826f6: Allow agents to use ZodSchemas in structuredOutput ## 0.1.27-alpha.22 ### Patch Changes - 7a19083: Updates to the LLM class ## 0.1.27-alpha.21 ### Patch Changes - 5ee2e78: Update core for Alpha3 release