@mastra/core@1.65.0
mastra-ai/mastra@mastra/core@1.65.0Sep 9, 2026by PaulieScanlon
Full Release Notes
## Highlights
### Advanced Trace Querying (Core Contract + Server Endpoints + Storage Implementations)
Mastra now has a strict, portable “advanced trace query” contract (bounded time ranges, recursive predicates, thread grouping, deterministic cursor pagination) plus an authenticated server endpoint for it, with real implementations in ClickHouse, DuckDB, and Postgres.
### Tenant-Scoped Trace Deletion + Trace Cascade Cleanup (Experiments & API)
You can delete up to 1,000 traces per request with tenant scoping, and deletion now cascades across spans and trace-linked signals (scores, feedback, metrics, logs). Experiment deletion now also cleans up the traces the experiment produced (including orphaned experiments), exposed in core APIs, server routes, and client-js methods.
### Workflow Graph Metadata for Control-Flow Blocks
Control-flow entries (parallel/branch/loops/foreach/sleep/map, etc.) now support optional `id`, `description`, and `metadata`, surviving serialization and rehydration so visual editors/review tools can label and stably address blocks (not just executable steps).
### Agent Channel Actions API (Handle Button Clicks / Select Changes)
Agent channels gained `handlers.onAction` so apps can handle interactive UI card actions (e.g., “retry” buttons) while still delegating to Mastra’s built-in approval handler (or disabling it).
### Factory Custom Boards Become First-Class (Installable, Typed, UI + Policy/Rules Ownership)
Factory adds a typed `defineBoard()` contract with board-owned phase semantics (`resting`/`working`/`terminal`), installable custom boards that fully render in the UI and can be targeted end-to-end by decisions/tools, plus board-owned transition policy and tool-result rules (removing the old global rules object).
### Breaking Changes
- `@mastra/factory`: `defineBoard()` phases must add `kind`, working phases must add `role`, and `initialPhase` must be `resting`.
- `@mastra/factory`: global rules object removed (`FactoryRules`, defaults/merge/assert helpers, `new MastraFactory({ rules })`); tool-result rules move onto `defineBoard({ tools })` and `configVersion` replaces `rules.version`.
- `@mastra/factory`: `GET /web/factory/projects/:id/attention` response shape changed (tier removed; counts/latest fields moved under `kinds[kind]...`).
- `@mastra/playground-ui`: `TraceDataPanelView` replaces `partialThreadTabSlot` with `messagesPanelSlot`; `TracesLayout` replaces `sidePanelWide` with `sidePanelWidth: 'half' | 'wide' | 'full'`.
## Changelog
### [@mastra/core@1.65.0](https://github.com/mastra-ai/mastra/blob/@mastra/core@1.65.0/packages/core/CHANGELOG.md)
#### Minor Changes
- Added tenant-scoped trace deletion arguments for observability storage with a limit of 1,000 trace IDs per batch. (#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)
```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)
```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)
`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)
```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)
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)
```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`)
- 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)
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.
_Also in: @mastra/observability@1.17.6_
- 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)
- Fixed a message history compatibility issue. (#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)
```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)
- Fixed BrowserViewer connections for Browser Use stdin commands while preserving thread isolation. (#23142)
- Fixed dataset experiments to pass request context when resolving dynamic agent models. (#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)
- Added Perplexity integration attribution while preserving custom headers. (#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)
_Also in: @mastra/code-sdk@1.7.0_
- 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)
- 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)
- 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)
_Also in: @mastra/arize@1.3.14, @mastra/otel-exporter@1.3.14_
- 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)
```typescript
await dataset.purgeItem({ itemId: 'item-123' });
```
_Also in: @mastra/client-js@1.44.0, @mastra/libsql@1.22.4, @mastra/mongodb@1.18.6, @mastra/mysql@0.8.6, @mastra/pg@1.23.0, @mastra/server@1.65.0, @mastra/spanner@1.6.5_
- Fixed Cloudflare Workers builds by preventing Node-only runtime dependencies from being bundled. (#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)
- Fixed AgentController reply IDs after a suspended tool resumes so streamed replies match their saved messages in Memory. (#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)
Fixes #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)
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)
- Fixed `CompositeAuth` resource mapping validation to reject invalid IDs from the authenticating provider while preserving providers without a mapper, including nested composites. (#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)
- Added inference start timestamps to step-start stream events for accurate time-to-first-token measurement. (#23094)
- Fixed unnecessary streaming overhead for final-only output processors. (#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)
- 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. (#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,
},
});
```
### [@mastra/ai-sdk@1.10.2](https://github.com/mastra-ai/mastra/blob/@mastra/ai-sdk@1.10.2/client-sdks/ai-sdk/CHANGELOG.md)
#### Patch Changes
- Fixed `toModelOutput` only applying to the first turn when `useChat` holds the conversation history. Tool result metadata now reaches the browser, so the compact projection is what comes back on the next request instead of the full raw tool output. This stops raw tool output from inflating later prompts for apps that do not use Mastra Memory. (#23290)
Fixes #22012
### [@mastra/clickhouse@1.17.0](https://github.com/mastra-ai/mastra/blob/@mastra/clickhouse@1.17.0/stores/clickhouse/CHANGELOG.md)
#### Minor Changes
- Added schema-neutral advanced trace-query execution over ClickHouse's existing completion-only observability tables. (#22727)
Trace queries read from the historical-complete root table, deduplicate completed deliveries by the existing `dedupeKey`, reconstruct each referenced relation once within the bounded root scope, fail closed on unsupported order fields, and enforce a configurable 15-second execution timeout. This feature requires no schema, table-engine, or data migration.
#### Patch Changes
- Improved `batchDeleteTraces()` with a durable deletion request and synchronous lightweight delete masking across trace branches, metrics, logs, scores, and feedback. Physical removal follows the deployment's configured retention TTL and merge policy. (#22553)
```ts
await observabilityStore.batchDeleteTraces({ traceIds: ['trace-123'] });
```
### [@mastra/client-js@1.44.0](https://github.com/mastra-ai/mastra/blob/@mastra/client-js@1.44.0/client-sdks/client-js/CHANGELOG.md)
#### Minor Changes
- Added typed optional author profiles to listFeedback(), without additional network requests. Profiles are available when the server authentication provider supports user lookup. (#23201)
```ts
const result = await client.listFeedback();
// Before: only the stored author ID was typed.
console.log(result.feedback[0]?.feedbackUserId);
// Now: the resolved profile is also typed when available.
console.log(result.feedback[0]?.author?.name);
```
- Added `deleteTraces()` to delete traces and their linked observability signals. (#22553)
```typescript
await mastraClient.deleteTraces({ traceIds: ['trace-1'] });
```
- Added queryTraces for querying completed traces with recursive predicates and thread grouping. (#22728)
- Added methods to delete experiments. Deletion attempts to remove the observability traces produced by the experiment, including their spans and trace-linked signals. Unsupported observability storage leaves the traces in place and logs a warning. (#22550)
**Delete an experiment from a dataset**
```ts
await client.deleteDatasetExperiment(datasetId, experimentId, {
organizationId,
projectId,
});
```
**Delete any experiment, including orphaned experiments whose dataset was already deleted**
```ts
await client.deleteExperiment(experimentId);
```
- Added `updateDatasetExperiment()` to rename an experiment or change its description and metadata. (#22924)
```typescript
const experiment = await client.updateDatasetExperiment({
datasetId: 'dataset-id',
experimentId: 'exp-id',
name: 'Baseline vs. new prompt',
});
```
#### Patch Changes
- Include `requestContext` in dataset item version history responses (`GET /api/datasets/:datasetId/items/:itemId/versions` and the single version endpoint). The field was stored but stripped from the response, so it could not be compared between versions. (#23234)
_Also in: @mastra/server@1.65.0_
- Preserve arbitrary provider namespaces in agent execution `providerOptions` instead of silently stripping providers outside the built-in allowlist. Validate provider option values as JSON and update the generated client route types to match the open provider contract. (#23221)
_Also in: @mastra/server@1.65.0_
- `getSystemPackages()` now returns `liveKitConnectionRouteEnabled`, true when the default `@mastra/livekit` connection route is mounted on the server. (#19496)
```ts
const { liveKitConnectionRouteEnabled } = await client.getSystemPackages();
```
- Regenerated route types: workflow step-graph entries now expose optional `id`, `description`, and `metadata` fields. (#22633)
```typescript
const workflow = await client.getWorkflow('my-workflow').details();
for (const entry of workflow.stepGraph) {
// id, description, and metadata are now typed on every entry
console.log(entry.id, entry.description, entry.metadata);
}
```
### [@mastra/code-sdk@1.7.0](https://github.com/mastra-ai/mastra/blob/@mastra/code-sdk@1.7.0/mastracode/sdk/CHANGELOG.md)
#### Minor Changes
- Added explicit project-level MCP enable overrides so a project can opt into a server that is disabled globally. MCP statuses expose the global default and all-server kill-switch state so clients can explain the effective setting. The global all-server kill switch remains absolute. (#23255)
```ts
await mcpManager.setServerDisabled('notion', false);
await mcpManager.inheritServer('notion');
```
- Added host-provided session instructions so workspace-free agent sessions can receive purpose-specific guidance. (#23001)
```ts
createMastraCodeAgentController({
hostInstructions: 'Help operators inspect and repair Factory state.',
});
```
#### Patch Changes
- Enabled Amazon Bedrock prompt caching for Claude Opus 5 models. (#23092)
- Added a persisted preference for Mastra Code interfaces to opt into native subagents. (#23335)
- Fixed Windows file references in custom slash commands, including `@src\context.md`, (#21632)
`@C:\path\to\file`, and `@C:/path/to/file`. Spaces, quoted paths, and glob patterns
are not supported.
- Read `/knowledge` from the same scope the Subconscious writes under. The knowledge browser was building its org rung from the session owner id (a user id), while local curation writes under the fixed `local` org, so the browser always looked at an empty scope. Both the memory factory and the inspector now derive their org/resource rungs from one resolver, and the local org id is exported as `LOCAL_KNOWLEDGE_ORG_ID`. Factory sessions keep failing closed when their org is unresolved. (#22944)
- Stop advertising Bedrock models that cannot be served over Converse. Nine `bedrock-mantle` entries in the models.dev catalog carry a `provider` override pointing at a different endpoint and API shape, but every catalog id is handed to `createAmazonBedrock()`, so they appeared in the `/models` picker and in packs while being unreachable. They are now filtered out of the advertised catalog. (#23061)
- Improved the agent's guidance for memory tools. (#23039)
### [@mastra/convex@1.5.7](https://github.com/mastra-ai/mastra/blob/@mastra/convex@1.5.7/stores/convex/CHANGELOG.md)
#### Patch Changes
- Fixed workflow snapshot upserts overwriting the stored `createdAt`. The Convex server functions now preserve the existing creation time when patching an existing `mastra_workflow_snapshots` row, so a save whose read predated another writer's insert can no longer replace the original timestamp. This keeps `listWorkflowRuns()` ordering and `fromDate`/`toDate` filtering correct, and matches the behaviour of the SQL adapters, whose `ON CONFLICT DO UPDATE` omits `createdAt`. (#23136)
Deploy your Convex functions to pick up the fix; no application code changes are required.
### [@mastra/dsql@1.3.5](https://github.com/mastra-ai/mastra/blob/@mastra/dsql@1.3.5/stores/dsql/CHANGELOG.md)
#### Patch Changes
- Fixed scoped trace deletion to reject unsupported tenant filters instead of deleting data without scope. (#22553)
_Also in: @mastra/libsql@1.22.4, @mastra/mongodb@1.18.6, @mastra/mssql@1.7.5, @mastra/mysql@0.8.6, @mastra/oracledb@0.2.3, @mastra/spanner@1.6.5_
### [@mastra/duckdb@1.7.0](https://github.com/mastra-ai/mastra/blob/@mastra/duckdb@1.7.0/stores/duckdb/CHANGELOG.md)
#### Minor Changes
- Added advanced trace query support to DuckDB observability storage, including filtering, grouping, ordering, cursor pagination, shared cross-adapter semantics, and query-shape-aware relation reads. (#22801)
Repeated writes for a score ID now retain the latest record so trace queries evaluate the current score consistently with other observability adapters.
#### Patch Changes
- Fixed DuckDB score retries to preserve delta cursors when updating an existing score. (#23076)
- Fixed trace deletion to cascade to metrics, logs, scores, and feedback while respecting tenant scope. (#22553)
_Also in: @mastra/pg@1.23.0_
### [@mastra/elysia@0.1.5](https://github.com/mastra-ai/mastra/blob/@mastra/elysia@0.1.5/server-adapters/elysia/CHANGELOG.md)
#### Patch Changes
- Fixed route-specific oversized-request rejection before handler execution and preserved explicitly attached HTTP exception responses. When a host parser has already consumed a request without `Content-Length`, the route limit remains a post-parse safeguard. (#22728)
_Also in: @mastra/express@1.5.9_
### [@mastra/factory@0.13.0](https://github.com/mastra-ai/mastra/blob/@mastra/factory@0.13.0/mastracode/factory/CHANGELOG.md)
#### Minor Changes
- Added board-owned phase semantics. Every phase in `defineBoard()` now declares `kind: 'resting' | 'working' | 'terminal'`, and working phases name the agent `role` that carries them. The runtime reads those declarations from the installed board for consent arming, the external-author guard, kickoff seating, run-start lanes, terminal cleanup, closed-PR and issue sweeps, and supervisor findings instead of matching built-in phase names. Custom boards now get their own terminal cleanup, consent handling, and role routing and no longer inherit Work's meanings by accident; unknown boards or phases fail closed (consent requested, nothing cleaned up, no seat started or revoked). Work and Review behave as before. (#23163)
Existing `defineBoard()` calls must add `kind` to every phase and `role` to working phases; `initialPhase` must be resting.
```ts
// before
phases: { queued: { title: 'Queued', next: 'shipped' }, shipped: { title: 'Shipped' } }
// after
phases: {
queued: { title: 'Queued', kind: 'resting', next: 'shipped' },
shipped: { title: 'Shipped', kind: 'terminal' },
}
```
Decision and tool-input validation still accept only built-in board IDs and phase names; the exported built-in stage and role constants are unchanged.
- Installed custom boards are now first-class in the Factory UI, and intake routing to them is explicit. (#23388)
- `GET /web/factory/projects/:id/boards` returns the installed board catalog (IDs, titles, initial phase, ordered phases with `kind` and working `role`, transition topology). Handlers, policies, and prompts are never serialized.
- The UI renders sidebar links, columns, phase labels, working/terminal states, card creation, and card moves from that catalog. Work and Review keep their URLs; custom boards open at `/factories/:id/boards/:boardId`. Unknown boards and catalog failures show an explicit unavailable state instead of falling back to Work. A card's persisted `board` determines membership; UI actions cannot reassign it. Settings › Skills groups built-in skills by board and lists custom boards' declared roles.
- Intake bindings are explicit: a Linear project or GitHub repository feeds a board only once bound to one, and opening a board no longer materializes cards. `intake_source_bindings` gains a `board` column; rebinding a Linear project moves its non-terminal, idle cards to the new board's initial phase.
- GitHub label routing: `GET`/`PUT /web/intake/label-routes` map a label to a board per Factory project (new `intake_label_routes` table). `issueOpened` picks the routed board, saving a route relocates matching cards, and `issues.labeled` / `issues.unlabeled` move cards between the routed board and Work while refreshing label metadata. Unrouted issues still go to Work.
Install a board and it shows up in the UI; route intake to it from Settings › Intake:
```ts
import { MastraFactory, defineBoard } from '@mastra/factory';
const release = defineBoard({
id: 'release',
title: 'Release',
initialPhase: 'queued',
phases: {
queued: { title: 'Queued', kind: 'resting', next: 'shipping' },
shipping: { title: 'Shipping', kind: 'working', role: 'release-publisher', next: 'shipped' },
shipped: { title: 'Shipped', kind: 'terminal' },
},
});
new MastraFactory({ storage, boards: [release] });
```
Then in Settings › Intake, bind a Linear project to **Release**, or add a GitHub label route such as `release → Release`. `mastra api factory boards <project-id>` lists what a project has installed.
- Added board-owned transitionPolicy for custom transition restrictions. Work retains its classification, approval, and acceptance policy automatically. Custom boards no longer accidentally inherit Work policy through phase or role names; shared runtime safeguards remain enforced. (#23153)
```typescript
const board = defineBoard({
id: 'release',
title: 'Release',
initialPhase: 'approval',
phases: {
approval: { title: 'Approval', next: 'shipped' },
shipped: { title: 'Shipped' },
},
transitionPolicy: context => {
if (context.toStage === 'shipped' && !context.isHumanTransition) {
return { type: 'reject', code: 'approval_required', reason: 'Human approval required.' };
}
},
});
```
Import defineBoard from @mastra/factory/boards and install the board through MastraFactory boards. Phase execution semantics and built-in customization are unchanged.
- Boards now own tool-result rules and the global Factory rules object is gone. (#23266)
- `defineBoard({ tools })` declares tool-result handlers on the board whose seat produces the result. Work declares `submit_plan` (an approved plan advances Planning → Execute); Review declares none; custom boards inherit nothing. A tool result on a board that is not installed or does not declare the tool fires no rule.
- Removed `FactoryRules`, `defaultFactoryRules`, `builtInFactoryRules`, `mergeFactoryRuleOverrides`, `assertFactoryRules`, `resolveFactoryToolRule`, and the `@mastra/factory/rules/defaults` subpath. `new MastraFactory({ rules })` now throws with a migration hint.
- Added `MastraFactoryConfig.configVersion` (default `factory-config-v1`), the operator-maintained audit label previously set through `rules.version`. Rule contexts and transition results carry `configVersion` instead of `ruleSetVersion`; the storage column keeps its `rule_set_version` name.
Migration:
```ts
// before
new MastraFactory({ storage, rules: defaultFactoryRules({ version: 'v2', overrides: { tools: { my_tool: { onResult } } } }) });
// after
new MastraFactory({ storage, configVersion: 'v2', boards: [defineBoard({ ..., tools: { my_tool: { onResult } } })] });
```
- Switched the default platform integrations endpoint used by `PlatformGithubIntegration` and `PlatformLinearIntegration` from `https://platform.mastra.ai/v1` to `https://integrations.mastra.ai`, and added `MASTRA_PLATFORM_REGION` support. Set it to `us` or `eu` (case-insensitive) to route to the regional replica at `https://integrations.us.mastra.ai` or `https://integrations.eu.mastra.ai`. (#20925)
Endpoint resolution precedence: `MASTRA_INTEGRATIONS_API_URL` (dedicated integrations override) > `MASTRA_PLATFORM_REGION` > global default. `MASTRA_SHARED_API_URL` configures the shared platform API and does not affect integrations routing. A trailing `/v1` on the override is stripped, so version-suffixed URLs keep working unchanged.
Migration:
```bash
# Before (implicit default)
# https://platform.mastra.ai/v1
# After (implicit default)
# https://integrations.mastra.ai
# Route platform integrations to a regional replica
export MASTRA_PLATFORM_REGION=us
# Keep the previous route (e.g. pinned deployments)
export MASTRA_INTEGRATIONS_API_URL=https://platform.mastra.ai/v1
```
- Added `workBoard` and `WorkBoardPhase` exports so developers can inspect the built-in Work board phases and validate lifecycle transitions. (#23078)
```ts
import { workBoard } from '@mastra/factory';
workBoard.allowsTransition('planning', 'execute');
```
- Removed global Work and Review lifecycle configuration. Installed board definitions now exclusively supply phase entry and exit handlers; custom boards declare them through defineBoard(). Work and Review remain installed automatically, and built-in customization is deferred. Global rules retain shared audit version and tool-result handlers. (#23148)
Remove former rules.work and rules.review overrides. For a custom board, declare handlers on phases instead:
```typescript
const releaseBoard = defineBoard({
id: 'release',
title: 'Release',
initialPhase: 'queued',
phases: {
queued: { title: 'Queued', onEnter: { manual: () => undefined } },
},
});
new MastraFactory({ storage, boards: [releaseBoard] });
```
The web deployment now uses guarded built-in intake: only eligible linked GitHub arrivals automatically invoke factory-triage. Manual and noncandidate arrivals no longer start solely from entering Intake. Explicit triage and existing approval safeguards remain unchanged.
- Added per-event rules to GithubIntegration and PlatformGithubIntegration so each installation owns its GitHub behavior. Functions replace defaults, null disables an event handler, and omitted or undefined values retain defaults. Removed global GitHub rule configuration; migrate rules.github[event].onEvent to the integration constructor rules[event]. (#23135)
```typescript
// Before: global Factory overrides
const overrides = { github: { issueCommentCreated: { onEvent: null } } };
// After: integration constructor
const github = new PlatformGithubIntegration({ rules: { issueCommentCreated: null } });
```
- Added authenticated Factory web usage telemetry with account, project, deployment, and region attribution. Set `MASTRA_TELEMETRY_DISABLED=true` on the Factory Server to opt out. (#23348)
- Added a typed board definition API and migrated the built-in Review board to declare its phases, transitions, and phase behavior through it. This establishes the contract for future custom board installation and Mastra Workflow integration. (#23029)
```ts
import { defineBoard } from '@mastra/factory';
const board = defineBoard({
id: 'release',
title: 'Release',
initialPhase: 'prepare',
phases: {
prepare: { title: 'Prepare', next: 'verify' },
verify: { title: 'Verify', outcomes: { approved: 'done', rejected: 'prepare' } },
done: { title: 'Done' },
},
});
```
- Added end-to-end execution for installed custom boards. Lifecycle and tool-result decisions can target custom phases, linked items use the target board’s initial phase, and bound tools retain live authorization and revision checks. (#23357)
Previously, installing a custom board did not enable decisions and tools to target its custom phases. Existing board configuration now runs through the shared Code Agent without a separate per-role agent API:
```typescript
import { MastraFactory } from '@mastra/factory';
import type { MastraFactoryConfig } from '@mastra/factory';
import { defineBoard } from '@mastra/factory/boards';
import type { BoardPhaseDefinition } from '@mastra/factory/boards';
type ReleasePhase = 'queued' | 'shipped';
const board = defineBoard<'release', Record<ReleasePhase, BoardPhaseDefinition<ReleasePhase>>>({
id: 'release',
title: 'Release',
initialPhase: 'queued',
phases: {
queued: { title: 'Queued', kind: 'resting', next: 'shipped' },
shipped: { title: 'Shipped', kind: 'terminal' },
},
});
export function createFactory(config: MastraFactoryConfig) {
return new MastraFactory({ ...config, boards: [board] });
}
```
Custom boards do not inherit Work policy. Built-in board customization, the built-in UI pipeline, and completion metrics are unchanged.
- Added a factory Supervisor that explains unhealthy work items, highlights actionable findings, and provides a dedicated factory-scoped chat without requiring a repository workspace. (#23001)
Create or reconnect the factory-scoped session with `POST /web/factory/projects/:id/supervisor/session`, and read the current deterministic findings with `GET /web/factory/projects/:id/supervisor/health`.
- Added Factory instance board installation. Custom boards can now be installed alongside the built-in Work and Review boards, or the defaults can be disabled for custom-only configurations. (#23084)
```ts
const factory = new MastraFactory({
storage,
boards: [releaseBoard],
includeDefaultBoards: false,
});
```
- Moved Linear event rules into `LinearIntegration` and `PlatformLinearIntegration`. Built-in handlers remain enabled without configuration. Constructor `rules` accept replacements or `null` to disable an event; omitted events retain defaults. (#23139)
Move former global `rules.linear[event].onEvent` values to the owning integration constructor:
```typescript
// Before: global Factory overrides
const overrides = { linear: { issueClosed: { onEvent: null } } };
// After: integration constructor options
const linear = new PlatformLinearIntegration({ rules: { issueClosed: null } });
```
Both direct and platform integrations use their own handlers for fetched issues and reconciliation while preserving shared audit metadata.
#### Patch Changes
- Improved Factory repository search by persisting only the repository selected for a project. (#22982)
- The attention inbox now reports counts and the newest item per kind, and the UI decides what interrupts a person. Runs waiting for approval leave the sidebar badge and the notification sound; the sidebar popover lists them under an "Approvals" tab beside "Needs you" and "Activity", each tab carrying its unread count, and the inbox page files them under "Waiting for approval", above "Activity". (#23274)
Breaking for callers of `GET /web/factory/projects/:id/attention`:
- the `tier` query is gone; ask for the kinds you want with a repeatable `kind` query, or omit it for every kind
- `openCount`, `badgeCount`, `unreadCount` and the three `latestOccurrence*` fields are gone; read `kinds[kind].open`, `kinds[kind].unread` and `kinds[kind].latest` instead. `kinds` always covers every kind, whatever `kind` filter the items use
Before:
```ts
const inbox = await fetch(`${base}/attention?tier=badge`).then(r => r.json());
inbox.badgeCount; // unread across the badge kinds
inbox.latestOccurrenceAt; // newest badge item
```
After:
```ts
const query = new URLSearchParams([
['kind', 'mention'],
['kind', 'automation-failed'],
]);
const inbox = await fetch(`${base}/attention?${query}`).then(r => r.json());
inbox.items; // mentions and failed automations only
inbox.kinds.mention.unread + inbox.kinds['automation-failed'].unread; // the badge number
inbox.kinds.mention.latest?.at; // newest mention, or null
```
- Refreshed the live-session marker on board cards. (#23074)
A card with a running session used to show one point of light crawling round its outline. It now shows pools of light resting on that outline.
- While the agent works, the pools drift along the rim.
- Once the session is waiting on you, they park and the whole rim comes up lit.
- Tightened the board card's corner. The radius now lives in one token instead of being repeated on every card-shaped surface, and it is tied to the buttons inside the card: a card's corner is the pill's radius plus the padding around it, so the two stay concentric. (#23074)
- Fixed Linear tools being unavailable in Factory board runs. (#23079)
- Added compact session filters to the Factory sidebar so sessions can be searched and narrowed by owner, status, or recent activity without permanently adding controls to the sidebar. (#23104)
- Stop rejecting `factory_transition_work_item` calls from non-triage agents that include a `triageType` key. Sessions are shared across role rotations, so a work or plan agent can copy the triage agent's earlier call shape from history; the strict schema then failed the whole transition with `Unrecognized key: "triageType"`. The key is now accepted and ignored for non-triage bindings; triage bindings still require it, and only they can forward a classification. (#23278)
- Fixed board card buttons breaking their labels across two lines. The actions on a card now keep their width and the worker's name gives way instead, so "Re-review" and "Open session" stay on one line in a narrow column. (#23074)
- Fixed hosted Factory bearer requests to select only organizations proven by the authenticated user's memberships. (#23196)
- A run waiting for approval is now an item in Needs attention — the inbox, the sidebar popover and the Overview preview all list it, with Run it and Dismiss on the row. Marking everything read clears the badge while the dot keeps saying a run is parked. The separate approval panel is gone. `GET /web/factory/projects/:id/attention` no longer returns `approvalCount`; parked runs arrive as items of kind `automation-proposed`. (#22945)
- A card's button now does exactly what dragging the card does: it moves the card, and the lane's rule decides which run starts there. The card moves as soon as you click and reports the run's state from the server, so a run started from a card is retried, superseded and reported like every other automated run instead of failing into a toast. The "X is ready" toast is gone; the session link arrives with the next poll. (#22957)
Investigate on a Linear issue now lands in Triage, the lane its rule lives in, instead of Planning. Re-review on a Done-lane pull request re-enters Review and runs the re-review skill. Runs started from a card carry the issue or pull request number, the `gh pr checkout` step with the expected head branch, and the Linear fetch hint. A candidate's custom prompt is posted as a comment on the card it files, so the run reads it from the card's feed. A card in Done or Canceled offers its session instead of a lane; only a Done-lane pull request that is still open keeps Re-review.
- Factory transcript file diffs now use the same colors as code blocks, and tool cards and folded tool groups keep their look while sharing their parts with Studio. (#23258)
- Approving a proposed move now carries your consent to the run that move queues: one click instead of two on cards from outside the write-access circle. Creating a card straight into a working lane, or moving it through the API, now counts as starting it, exactly like a drag. (#22941)
- Fixed a newly linked repository staying unchecked under Work Intake. Linking a repository to a Factory now selects it for GitHub issue intake, so its open issues reach the board right away. Existing intake selections are kept. (#23228)
- Pull request review sessions now start on the PR head in seconds. The session branch comes from a blob-less fetch of the repository history, so `git log` and `git blame` work in the review while past file contents load on demand. (#23261)
- A board card announces an automated run once. While the run's session is live the card shows only its session marker, orange while the sandbox comes up and green once the agent is working; the "Automated run in progress…" row is gone, along with its copy that lingered on a card in Done until the dispatcher saw the run end. Sidebar rows read the same order, so a session whose sandbox is still materializing shows as initializing even after its run is registered. The sidebar lists a session the dispatcher created as soon as the run registry shows a run on it, instead of on the next reload. (#23060)
- Fixed a Factory automation that failed on a card already at Done or Canceled staying in Needs attention until the server restarted. The session row and the board card kept the "waiting on you" marker, and Retry could only fail the same way again. Such a failure now settles as superseded the moment it happens, the way a restart already repaired it, so the marker clears on the next poll. (#23263)
- Fixed the sign-in callback redirecting straight back to the identity provider in a loop when it denies access (for example access_denied for an account that is not part of the organization). The denial now lands on the sign-in page with the error shown. (#21188)
- The Factory board now names a column for a stage it does not recognise, and invites you to drag work there, instead of leaving the column blank. (#23038)
- Fixed supervisor findings refreshing the Attention inbox on every health tick. A finding now keeps the moment its condition began, so a tick that finds nothing new writes nothing, and the inbox orders findings by when they opened. (#23274)
- The supervisor health check no longer reports failed decisions and proposals waiting on a person as findings. The board and the attention inbox already carry both, so every one of them showed up twice in the inbox. The supervisor agent reads them with `factory_list_attention`; rows already stored for these kinds resolve on the next health tick. (#23233)
- Factory model selectors can now accept a custom model ID when the deployed model catalog has not caught up with a newly released model. The shared combobox exposes this as opt-in behavior, leaving existing selectors unchanged. (#23105)
_Also in: @mastra/playground-ui@53.0.0_
- Comment rows in the work item feed now share one look for quoted replies and inline editing. Row actions (quote, copy link, edit, delete) appear only when you hover that row, instead of lighting up on every row while the card is hovered. (#23052)
- Fixed Factory-authored pull requests to resume their original session for inline reviewer feedback while leaving regular pull requests out of autonomous fixes. Factory now also creates and starts an initial Review session, or re-review session after completion, when a trusted maintainer requests the configured GitHub App as a reviewer. (#22959)
- Fixed Factory triage to preserve existing workflow status labels during initial issue handling. (#22988)
- Fixed the board's Intake column pulling every open pull request or issue of the repository on its own, behind a spinner, whenever a filter, cards already on the board, or drafts left the loaded pages with little to show. (#23220)
Reaching the end of the column now loads one page. A page that adds nothing to scroll past leaves the end where it is, so the next page waits for a scroll or the Load more button instead of loading by itself. The Activity, Attention, and Rules lists follow the same rule.
- Added Factory API support for automation clients to inspect and operate projects, work items, decisions, attention, health, metrics, and supervisor state. (#23226)
```ts
import { MastraFactory, type MastraFactoryConfig } from '@mastra/factory';
export function createFactory(storage: MastraFactoryConfig['storage']) {
return new MastraFactory({ storage });
}
```
- Fixed Work approval bypasses through intermediate phases. Classified non-bug items without recorded acceptance now require a human transition into Planning or Execute regardless of their previous phase, including historical items without an acceptance stamp. (#23153)
- Fixed Factory API transition validation to accept custom board and phase identifiers while retaining installed-board policy checks. (#23357)
- Added trusted pull request comment commands to start or re-run Factory reviews. (#22986)
- Fix automated runs falsely failing when a plan agent handed a card straight on to Build. Decisions whose role was replaced on the session by the next role now complete instead of failing or retrying. (#22942)
- Fixed Slack emoji showing as raw shortcodes in the Factory feed. An aside like `aside: nice :thumbsup:` lands on the card as "nice 👍" instead of "nice :thumbsup:", and a thread's card title reads the same way. (#23250)
Custom workspace emoji are images with no unicode character, so a name like `:party-parrot:` keeps its colons. Substitution happens as messages arrive: comments and titles stored before this release keep the shortcodes they were saved with.
- Thread messages now show who sent them. In a shared session, a message written by someone else carries a small avatar beside it; hover or focus it to see their name. Messages that came in from Slack keep their "via Slack" badge. A teammate's message also appears in the thread the moment they send it, instead of after a reload. (#23085)
- A Factory run parked on a plan or a question now shows up in Needs attention as an "agent waiting" item that opens the thread, and leaves the inbox by itself once someone answers. The card's wick and the sidebar row read "Waiting on you" meanwhile. Before, the dispatcher recorded the pause as a failed automation with a retry button that could do nothing, and the row stayed after the answer. (#23274)
- Settings controls now match what they set instead of every choice being the same row of buttons. (#22983)
**Thinking level** is a slider, not six buttons. Six buttons said "pick one of these"; a slider says what is actually true — the level is a ramp, so you drag the thumb along it. The track carries a dot per stop and fills up to the handle, and the colour turns to warning on the top two steps where the bill turns. The level is named to the left of the track in a fixed column, so "Off" and "Extra high" leave the track in the same place and nothing on the page shifts as you drag. The save only fires when you let go, so crossing the whole scale is one write, not five, and the write is optimistic: the thumb stays where you dropped it instead of the row greying out and snapping back. A per-mode row that follows the base level reads "Follows base" and grows a "Reset to base" button only once it has an override of its own. A refusal from the server puts the slider back and says why under the row that asked for it, rather than as a banner over the whole section. Arrow keys move it, and screen readers hear the level name rather than a number. One control at every width — the phone and desktop renderings are no longer two separate components.
The per-mode rows moved next to the level they follow. They set deployment defaults, but they sat in your personal card beside the session thinking level, so "Follows base" pointed at a row that was neither the base nor on the same screen. Base and modes are now one group of their own, and the session-level row that used to sit beside them is labelled for what it really is: the level for chats opened from this factory, shared with everyone working in it. Saving no longer raises a toast per change — the control already shows the new value, and it holds the stop you dropped it on until the write lands instead of flicking back and forth once.
On a deployment that refuses these writes — the defaults live in one settings file shared by everyone, so an authenticated deployment keeps them fixed — the rows now say so and render read-only, instead of letting you drag a slider that fails on release.
**Theme** uses the shared theme toggle instead of a picker built here, so System, Light and Dark read and behave the same in the Factory as everywhere else in the product.
**Completion sound** is a one-line select with a mute button tucked under its left edge, not four buttons: whether a run makes a sound and which sound it makes are two different questions. Muting swaps the icon and greys the select's label while keeping your sound, so unmuting returns to what you had. Nothing moves as you toggle, and the greyed-out select keeps its own background rather than turning translucent over the button behind it. Each pick plays, since a sound can only be judged by ear.
**Observe attachments** was a hand-rolled copy of the shared button group; it now uses the shared one, so Auto/On/Off, Notifications and the tool-permission rows stay identical — those are alternatives, not a ramp, and keep the control that says so.
- Settings now say who each block applies to. Every section heading carries a scope label: **Personal** for your own account, chats and credentials, **Factory-wide** for what everyone working in this factory shares, **Org-wide** for what the whole organization shares such as custom providers and GitHub CLI tokens, and **Deployment-wide** for the handful of settings that live in the server's own settings file and reach every factory on it. (#22983)
Writing the labels turned up blocks that claimed the wrong owner, and those moved to where they belong:
- **Thinking level, auto-approve tools, smart editing, notifications and the tool-permission rows** sat under Personal, but a settings page has no chat session of its own, so they all write the factory-level session that every member of the factory shares. They now read Factory-wide, and say plainly that auto-approve, smart editing and permissions reset when the server restarts.
- **Base and per-mode thinking defaults** claimed Factory-wide while writing one settings file shared by every factory on the server. They are their own Deployment-wide block now, sitting together so a mode row that follows the base level shows the row it follows.
- **GitHub and Linear issue syncing** claimed Factory-wide while storing one row per person. They read Personal, and say that teammates choose their own.
- **Linear routing** is org-level config that happens to name a factory, so it reads Org-wide.
- **"Create work items for new Slack threads"** sat in a Personal block on the Slack page while flipping a switch on the factory itself. It has its own Factory-wide block now, next to the per-account routing that really is personal.
- **Model packs**: choosing your default pack is personal, but creating or removing one changes the list for the whole org, which the block now says.
- **Factory skills** ship with the server and are identical on every factory, so they read Deployment-wide rather than implying this factory has its own.
Where the same form exists at two scopes, the label becomes a switch instead of a second copy of the form, and switching slides the old content out and the new content in from the picked side, so the change is visible even when both scopes are configured alike:
- **Provider access** switches between your own credentials and the org-wide ones (org admins only). The sign-in and API-key tabs moved up next to the heading, so the section leads with one row of controls instead of two. Each row shows one status and one action for the picked scope; from the personal view a provider you have no credential for reads "Covered by org" when the org already has one. Signing in or adding a key no longer asks who it is for; the switch already decided.
- **Observational memory** switches between your interactive chats and Factory runs instead of stacking two identical forms.
- The sidebar stage chip next to the Mastra logo now reads Beta instead of Alpha. (#23331)
- Seed Factory ownership when creating repo-backed Slack sessions so plan artifacts use the Factory workspace path. (#23101)
- Fixed the Factory dispatcher starting a duplicate run while a skill run longer than ten minutes was still working. A run the run registry still shows in flight is left alone. A run older than six hours is failed as overdue, so a hung run no longer holds its slot forever. (#23274)
- A thinking slider released twice in a row no longer writes the second level again when you click away. Releasing the thumb commits the drop once and holds it until the write lands, so a click away after that finds nothing left to commit. (#23038)
- Improved the chat transcript: a run of three or more tool calls now folds into a single row while the reply is still being written, instead of only once it is finished. The folded row names the step that is running and counts progress, and opens onto the individual calls. Calls that need something from you, such as a question, a plan or an approval, stay on their own row. (#23313)
- Fixed Factory custom API routes to honor validated bearer organization selection. (#23203)
- Fixed autonomous Factory runs so they retain the selected session model. (#22986)
### [@mastra/fastify@1.5.9](https://github.com/mastra-ai/mastra/blob/@mastra/fastify@1.5.9/server-adapters/fastify/CHANGELOG.md)
#### Patch Changes
- Fixed route-specific request-body limits before JSON parsing and preserved explicitly attached HTTP exception responses. (#22728)
_Also in: @mastra/hono@1.7.7_
### [@mastra/inngest@1.8.10](https://github.com/mastra-ai/mastra/blob/@mastra/inngest@1.8.10/workflows/inngest/CHANGELOG.md)
#### Patch Changes
- Fixed Inngest workflow steps receiving `retryCount: 0` on every retry; steps now receive the current attempt number. (#22492)
### [@mastra/koa@1.7.9](https://github.com/mastra-ai/mastra/blob/@mastra/koa@1.7.9/server-adapters/koa/CHANGELOG.md)
#### Patch Changes
- Preserved status, headers, and body content from explicitly attached HTTP exception responses. (#22728)
### [@mastra/langfuse@1.5.5](https://github.com/mastra-ai/mastra/blob/@mastra/langfuse@1.5.5/observability/langfuse/CHANGELOG.md)
#### Patch Changes
- Fixed root span metadata missing from Langfuse trace metadata. Since the Langfuse v5 (OTLP) upgrade, only a fixed set of keys reached the trace, so `runId`, `resourceId`, and custom metadata set on the root span were nested under `metadata.attributes` in Langfuse and could not be used in trace filters or evaluator scopes. The exporter now forwards every remaining root span metadata key to `langfuse.trace.metadata.<key>`, matching the behavior before the upgrade. Explicit `metadata.langfuse.*` values and the agent/workflow identity keys keep precedence, and child spans never change trace metadata. Fixes #23187. (#23206)
### [@mastra/livekit@0.3.2](https://github.com/mastra-ai/mastra/blob/@mastra/livekit@0.3.2/integrations/livekit/CHANGELOG.md)
#### Patch Changes
- Add `options` to `MastraVoiceAgentMemory` so per-call memory config is forwarded by the in-process agent and remote reply generators. Setting `readOnly` keeps LiveKit's preemptive (speculative) turns from persisting partial user and assistant messages to the thread, so preemptive generation can stay on with memory; committed turns are then persisted by the caller. (#22928)
```ts
new MastraLLM({
agent,
memory: { thread: 'thread-id', options: { readOnly: true } },
});
```
Documents the recipe and corrects the worker/plugin notes on what discarded speculations persist.
### [@mastra/memory@1.28.3](https://github.com/mastra-ai/mastra/blob/@mastra/memory@1.28.3/packages/memory/CHANGELOG.md)
#### Patch Changes
- Fixed a memory tool reliability issue. (#23042)
- Fixed observation indexing retries so temporary connection errors recover without duplicating stored observations. (#23205)
- Observational memory's observer and reflector passes now trace as memory operations rather than generic spans, appearing as `memory: observe` and `memory: reflect`. (#22542)
They also reported an output-step-processor entity type, which they are not — they wrap the model calls made inside the processor — so their runtimes were counted as processor overhead in `mastra_processor_duration_ms`.
- Fixed observational memory buffering so transient database connection timeouts are retried instead of failing the buffer operation. (#23019)
- Fixed Observational Memory to forward only images and PDFs to the observer by default. (#22153)
**Before:** Omitting `observeAttachments` forwarded every attachment type.
**After:** Omitting `observeAttachments` forwards images and PDFs. To retain the previous behavior and forward every attachment type, explicitly set `observeAttachments: true`:
```ts
new ObservationalMemory({
observation: {
observeAttachments: true,
},
});
```
### [@mastra/mysql@0.8.6](https://github.com/mastra-ai/mastra/blob/@mastra/mysql@0.8.6/stores/mysql/CHANGELOG.md)
#### Patch Changes
- Update README to include docs link (#22965)
### [@mastra/next@0.2.23](https://github.com/mastra-ai/mastra/blob/@mastra/next@0.2.23/server-adapters/next/CHANGELOG.md)
#### Patch Changes
- Link the Next.js and TanStack Start adapter READMEs to their setup guides and API references. (#22964)
_Also in: @mastra/tanstack-start@0.2.23_
### [@mastra/observability@1.17.6](https://github.com/mastra-ai/mastra/blob/@mastra/observability@1.17.6/observability/mastra/CHANGELOG.md)
#### Patch Changes
- Fixed `SensitiveDataFilter` so it also redacts sensitive fields inside a span's `requestContext`. Secrets stored in `RequestContext` (for example a per-request API token that tools use) were exported to every tracing exporter in plain text, even when the key was listed in `sensitiveFields`. The filter now applies the same redaction to `requestContext` as it does to `attributes`, `metadata`, `input`, `output`, and `errorInfo`. Fixes https://github.com/mastra-ai/mastra/issues/23046 (#23055)
- Fixed `mastra_processor_duration_ms` counting spans that borrow a processor entity type without being processor runs. Observational memory's model passes were tagged as output-step processors, so seconds-long model calls were reported as processor overhead. (#22542)
- Skip impossible JSON prefixes in SensitiveDataFilter to avoid repeated parse exceptions for serialization markers while preserving valid embedded-JSON redaction. (#23314)
- Fixed the `indexed` redaction style of `SensitiveDataFilter` giving the same value a new token every time a span was exported. A span is processed again for each `span_started`, `span_updated`, and `span_ended` event, and the filter treated its own `[APIKEY_1]` token as a new secret and replaced it with `[APIKEY_2]`. Already redacted values now keep their token, so the same secret maps to one token across every span and event of a trace while the trace's mapping is retained (state is kept for the 1000 most recently seen traces). Fixes #23056 (#23057)
### [@mastra/oracledb@0.2.3](https://github.com/mastra-ai/mastra/blob/@mastra/oracledb@0.2.3/stores/oracledb/CHANGELOG.md)
#### Patch Changes
- Fixed trace deletion to remove trace-linked scores while preserving score records without a trace ID. (#22553)
### [@mastra/pg@1.23.0](https://github.com/mastra-ai/mastra/blob/@mastra/pg@1.23.0/stores/pg/CHANGELOG.md)
#### Minor Changes
- Added read/write pool separation to `PostgresStore`. Pass `writePool` together with an optional `readPool` to route plain reads to a replica while writes, schema setup, transactions, locking reads, and every read-modify-write path stay on the primary. When `readPool` is omitted, reads fall back to the writer, and the existing single `pool` configuration keeps working unchanged. Closes #12035. (#23154)
```ts
import { Pool } from 'pg';
import { PostgresStore } from '@mastra/pg';
const store = new PostgresStore({
id: 'pg',
writePool: new Pool({ connectionString: process.env.PG_PRIMARY_URL }),
readPool: new Pool({ connectionString: process.env.PG_REPLICA_URL }),
});
store.pool; // writer
store.readPool; // reader (falls back to the writer when readPool is omitted)
```
Standalone reads such as `getThreadById`, `listThreads`, `agents.getById`, or `knowledge.search` hit `readPool`. Lookups that feed a mutation (for example the thread check inside `saveMessages`, the metadata merge in `updateThread`, or version resolution inside `skills.update`) always hit `writePool`, so a lagging replica cannot cause false "not found" errors or overwrite recent writes. Caller-provided pools are never closed by the store.
- Added portable advanced trace-query execution with parameterized PostgreSQL plans, bounded reusable relation scopes, completed-root filtering before pagination, current-row conformance, and a configurable 15-second transaction-local execution timeout. (#22727)
#### Patch Changes
- Fixed PgVector `query()`, `upsert()`, `updateVector()`, `deleteVector()`, and `deleteVectors()` failing with `column "namespace" does not exist` on vector tables created before `@mastra/pg` 1.22. (#23281)
The namespace column migration previously ran only inside `createIndex()`, so tables that were only read after upgrading were never migrated. The migration now runs lazily (once per index per process) from every data path, and is applied atomically under a database-scoped advisory lock so it is safe across concurrent processes.
When schema changes are disabled via `disableInit` or `MASTRA_DISABLE_STORAGE_INIT`, a descriptive `MASTRA_VECTOR_PG_ENSURE_NAMESPACE_MIGRATION_REQUIRED` error is thrown instead. In that case, either call `createIndex()` with init enabled, or run the following migration as a single transaction (replace `<index>` with the table name):
```sql
BEGIN;
SELECT pg_advisory_xact_lock((1936876916::bigint << 32) | '<index>'::regclass::oid::bigint);
ALTER TABLE <index> ADD COLUMN IF NOT EXISTS namespace VARCHAR(255) NOT NULL DEFAULT 'default';
CREATE UNIQUE INDEX IF NOT EXISTS <index>_namespace_vector_id_idx ON <index> (namespace, vector_id);
ALTER TABLE <index> DROP CONSTRAINT IF EXISTS <index>_vector_id_key;
COMMIT;
```
**What automatic migration requires**
- First access to a legacy table requires table-owner DDL permissions.
- First access takes a transaction-scoped advisory lock and PostgreSQL DDL locks.
- Already-migrated tables remain usable with SELECT-only access.
- Failed or incomplete migrations are retried on the next operation.
- Equivalent composite unique indexes are recognized regardless of name or key order.
- For long table names, automatic migration uses a bounded hashed index name and rolls back if a conflicting index prevents reconciliation.
**Before you run the SQL above**
- The example assumes the original generated constraint name. Use the actual legacy constraint name if it was renamed.
- For a long table name, choose a unique index name of at most 63 characters.
- If an index with the same name already exists, verify it is a valid, non-partial unique index on exactly `namespace` and `vector_id` before dropping the legacy constraint.
Fixes #23272
- Improved PostgresStore last-N message reads. Paginated reads now avoid materializing message content for every matching row before applying the page limit, letting the `(thread_id, createdAt DESC)` index serve the page. Totals and single-round-trip behavior remain unchanged. (#23369)
- # Fix namespace migration for long vector table names (#23280)
- Fix `PgVector.createIndex()` failing for table names of 40 or more characters by bounding generated index names with a 128-bit hash suffix. Short names remain unchanged.
- Preserve legacy vector-ID uniqueness if replacement-index creation fails by creating the namespace unique index before dropping the legacy constraint.
- Repair tables left half-migrated by earlier versions on the next `createIndex()` call, provided they contain no duplicate `(namespace, vector_id)` pairs.
Fixes #23273
- `PgFactoryStorage` now widens an `integer` column to `bigint` when the collection schema says so, the way it already adds missing columns and drops stale `NOT NULL`. A Factory deployed before `factory_attention_receipts.occurrence` became `bigint` no longer needs a hand-run `ALTER TABLE` before parked-run receipts can be written. (#23274)
- Fixed vector operations timing out with small PostgreSQL connection pools when index metadata is not cached. (#23205)
### [@mastra/platform-workspace@1.6.0](https://github.com/mastra-ai/mastra/blob/@mastra/platform-workspace@1.6.0/workspaces/platform-workspace/CHANGELOG.md)
#### Minor Changes
- Added `MASTRA_PLATFORM_REGION` support to `PlatformSandbox` and `PlatformFilesystem`. Set it to `us` or `eu` (case-insensitive) to route the workspace proxy to the regional replica at `https://workspaces.us.mastra.ai` or `https://workspaces.eu.mastra.ai`. When unset, calls continue to hit the global default `https://workspaces.mastra.ai`. An explicit `MASTRA_WORKSPACE_PROXY_URL` still overrides both. (#20925)
```bash
# Route platform workspaces to the EU replica
export MASTRA_PLATFORM_REGION=eu
```
#### Patch Changes
- Fixed workspace providers to support `MASTRA_PLATFORM_SECRET_KEY` for local development while preserving precedence for explicit `accessToken` options and `MASTRA_PLATFORM_ACCESS_TOKEN`. (#23361)
- Repository templates now pin to the last default-branch head resolved for the same clone URL when the lookup fails, instead of dropping the repo steps and booting the base image. (#22947)
### [@mastra/playground-ui@53.0.0](https://github.com/mastra-ai/mastra/blob/@mastra/playground-ui@53.0.0/packages/playground-ui/CHANGELOG.md)
#### Minor Changes
- Replaced the trace panel's "Messages" tab with a "Messages" column so an agent turn reads left-to-right as Messages → Trace → Span detail. (#23009)
**`TraceDataPanelView`**: the `partialThreadTabSlot` prop was removed. Pass `messagesPanelSlot` instead; it renders as a column to the left of the timeline inside the same card, and the columns animate open/closed.
```tsx
// Before
<TraceDataPanelView partialThreadTabSlot={({ traceId }) => <ThreadView traceId={traceId} />} />
// After
<TraceDataPanelView messagesPanelSlot={<ThreadView traceId={traceId} />} />
```
**`TracesLayout`**: `sidePanelWide` (boolean) was replaced by `sidePanelWidth: 'half' | 'wide' | 'full'`. `'full'` lets a three-column panel span the whole frame.
```tsx
// Before
<TracesLayout sidePanelWide={!!spanId} />
// After
<TracesLayout sidePanelWidth={spanId ? 'wide' : 'half'} />
```
- Added a `thread` variant to the `Comment` component, plus `CommentQuote`, `CommentEditor` and `CommentArrival` parts, so a dense comment feed (avatar gutter, grouped rows, quoted replies, inline editing, hover actions) can be built from the design system instead of hand-rolled per app. (#23052)
```tsx
<Comment variant="thread">
<CommentItem continued={sameAuthorAsAbove} highlighted={isLinkedComment}>
<CommentItemAvatar>{sameAuthorAsAbove ? null : <Avatar name={author} />}</CommentItemAvatar>
<CommentItemContent>
<CommentItemHeader>
<CommentItemAuthor>{author}</CommentItemAuthor>
<CommentItemTimestamp dateTime={occurredAt}>{relative}</CommentItemTimestamp>
</CommentItemHeader>
<CommentQuote authorName={replyTo.authorName} quote={replyTo.quote} />
<CommentItemBody>{body}</CommentItemBody>
</CommentItemContent>
<CommentItemActions>{/* revealed on row hover */}</CommentItemActions>
</CommentItem>
</Comment>
```
`CommentEditor` owns only the draft: pass `isPending` and `error` from the mutation that saves it, and close it from that mutation's success. While `isPending` the textarea is read-only and both buttons are disabled.
The existing `default` and `embed` variants are unchanged.
- Added `xs`, `sm`, and `md` sizes to `ThemeToggle`, matching the Badge size scale. The default size is `md`; use `<ThemeToggle size="sm" />` in constrained menus. (#22990)
- Added a `variant="new"` to `Dialog` with Factory-style spacing, an `intent="destructive"` option, a `pending` state that blocks dismissal, and `DialogCancel` and `DialogAction` footer buttons, including a configurable press-and-hold confirmation. The new variant's `DialogBody` always scrolls inside a bounded, fading area so long copy needs no special handling. The default variant and `AlertDialog` are unchanged. (#23110)
```tsx
import {
Dialog,
DialogAction,
DialogBody,
DialogCancel,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@mastra/playground-ui/components/Dialog';
<Dialog variant="new" intent="destructive" open={open} onOpenChange={setOpen} pending={isDeleting}>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete workspace?</DialogTitle>
</DialogHeader>
<DialogBody>
<DialogDescription>Uncommitted changes will be lost.</DialogDescription>
</DialogBody>
<DialogFooter>
<DialogCancel>Cancel</DialogCancel>
<DialogAction confirmation="hold" holdSeconds={2} onConfirm={deleteWorkspace}>
Hold to delete
</DialogAction>
</DialogFooter>
</DialogContent>
</Dialog>;
```
The caller closes the dialog after the action succeeds.
Also added `IntegrationDialog`, a searchable integration picker built on the new dialog variant with a fixed search field and a fading scroll list. Items carry an id, name, optional logo, an optional `badge` shown next to the name, and optional `meta` text shown muted on the right. Consumers own any vendor mapping, such as turning an auth type into a label.
```tsx
import { IntegrationDialog } from '@mastra/playground-ui/components/IntegrationDialog';
<IntegrationDialog
open={open}
onOpenChange={setOpen}
title="Add connection"
description="Choose an integration to authorize."
items={[
{ id: 'notion', name: 'Notion', logo: <img src={notionLogo} alt="" />, meta: 'OAuth' },
{ id: 'render-mcp', name: 'Render', badge: 'MCP', meta: 'OAuth' },
]}
onSelect={item => startConnect(item.id)}
>
<IntegrationDialog.Trigger render={<Button>Add connection</Button>} />
</IntegrationDialog>;
```
- Added `SettingsLayout` for settings page titles, actions, constrained width, and section spacing. Section headings now align with the card edge by default. Pass `inset` to `SettingsLayout` and `Section.Header` to align headings with row content. (#23109)
```tsx
<SettingsLayout title="Project Settings">
<Section variant="factory">...</Section>
</SettingsLayout>
```
#### Patch Changes
- Added a reusable hook for debouncing values in playground interfaces. (#22973)
- `ChatShell.Column` accepts a `ref`, and the shell's track is now the positioning context for overlays that must span the scrolled height, such as a sticky thread rail. (#23258)
```tsx
const columnRef = useRef<HTMLDivElement>(null);
<ChatShell.Column ref={columnRef}>
<ThreadRail scrollerRef={columnRef} />
{messages}
</ChatShell.Column>;
```
- Added the shared chat pieces both transcripts draw from. `ChatShell.Turn` reserves the reply room for a live turn, `groupTurns` folds a message list into turns, and `ai/tool-call` gains `ToolCallEdit` (an edit as removed and added lines, a written file as code), `ToolCallCommand`, `ToolCallGroup`, `toolEdit`, `stripAnsi` and `stripSerializedAnsi`. `Code` now exposes its `useHighlight` hook and `tokenStyle`, and `languageForPath` resolves a highlight language from a file path. (#23258)
```tsx
const turns = groupTurns(messages, { key: message => message.id, opensTurn: message => message.role === 'user' });
turns.map((turn, index) => (
<ChatShell.Turn key={turn.key} opensTurn={turn.opensTurn} holdsRoom={turn === turns.at(-1) && running && index > 0}>
{turn.entries.map(renderMessage)}
</ChatShell.Turn>
));
const edit = toolEdit(toolName, args);
edit ? <ToolCallEdit edit={edit} /> : <ToolCallMono copyText={argsText}>{argsText}</ToolCallMono>;
```
- Moved Studio breadcrumbs inside the main content frame as a fixed header with a full-width bottom border. Fixed the first breadcrumb shifting left when navigating from a list page to a detail page, and unified the font size of all breadcrumb items. Removed the heavy card shadows on the Agent overview and settings pages in light mode. (#22961)
- Added an `onInputValueChange` callback to the `Combobox` component so consumers can react to the search text (for example to offer a "Create ..." option built from what the user typed). (#23225)
- Studio list pages (Workflows, Tools, MCP Servers, Scorers, Processors, Datasets, Experiments, Prompts) now scroll the list itself instead of the whole page, matching the Agents page: the header and filters stay fixed while the list scrolls. `DataList` no longer stretches to fill its container, so short lists and loading skeletons stay compact instead of rendering a full-height empty panel. `PageLayout height="full"` now bounds its main row to the remaining page height so nested lists can scroll internally. (#22975)
- Trace filters on the traces page now save automatically as you change them and are restored on your next visit; the explicit **Save filters** action is gone. Custom absolute date ranges are not persisted since they would go stale. Faded spans in the light theme are now tuned separately from the dark theme so they stay legible while highlighted spans still stand out. (#23223)
- Fixed inconsistent empty-state icons and centering in Studio and Agent Builder. (#23200)
- - `CodeDiff` renders a GitHub-style split diff: removed lines red on the left, added lines green on the right, with line numbers and expandable collapsed regions. (#23234)
- `DataCodeSection` accepts an optional `diff={{ against, side }}` prop that highlights the lines differing from another document (red for side `a`, green for side `b`) without changing the section layout.
```tsx
// Left column shows the older document: differing lines are red
<DataCodeSection title="Input" codeStr={olderJson} diff={{ against: newerJson, side: 'a' }} />
// Right column shows the newer document: differing lines are green
<DataCodeSection title="Input" codeStr={newerJson} diff={{ against: olderJson, side: 'b' }} />
```
- Removed DataList visual variants and custom sticky header backgrounds in favor of one bordered table style. The root is now the only element that defines a color: header, sticky columns, separators, rows, `featured` and `error` states no longer set any background, border or ring color (row/header separators and the row focus ring are removed; the skeleton shimmer reuses the root background). `featured`/`error` are exposed as `data-featured` / `data-variant` attributes for consumers that want to style them. Selection checkboxes are now always visible. (#22967)
**Before**
```tsx
<DataList columns="1fr" variant="striped" stickyHeaderBackground="tinted">
{rows}
</DataList>
```
**After**
```tsx
<DataList columns="1fr">{rows}</DataList>
```
Added a `variant="light"` option to `DataList` (and `DataListSkeleton`) that removes the panel behind the rows so the list sits directly on the page.
- `ToolCallPresentedHeader` accepts an optional `leading` slot so consumers can render content ahead of the tool icon, such as a timestamp: (#23165)
```tsx
<ToolCallPresentedHeader leading={<time>3:42:05 PM</time>} icon={FileText} label="read_file" />
```
- Fixed unreadable row labels in `HorizontalBars` when a bar has a bright fill in dark mode. The label now uses a dark tone over yellow, orange, red and green fills, so dataset names in the Studio "Experiments by Dataset" and "Review Pipeline" cards are readable again. (#23326)
- On the traces page, an agent trace that belongs to a thread now shows its reconstructed conversation as a "Messages" column to the left of the trace timeline instead of a tab. Opening a span adds the span detail as a third column and the side panel grows to fill the frame, so the layout reads Messages → Trace → Span. (#23009)
- Added a `featuredSpanIds` prop to the trace data panel to fade non-featured spans in the timeline, and `highlightSpanIds` / `handleHighlightSpans` in the trace URL state so consumers can highlight the spans behind a reconstructed message. (#23045)
- Fixed an Immer prototype pollution vulnerability in Playground UI dependencies. (#23102)
- Reduced the Breadcrumb crumb font size to the small UI size so breadcrumb items render consistently regardless of whether they are text, links, or comboboxes. (#22961)
- Fixed incomplete traces and subtraces after an agent resumes or more spans arrive. Selected details refresh on reopening, returning to Studio, or reconnecting, without periodic polling. Downloading a trace also updates its displayed data. (#23333)
- Added `groupConsecutive`, `TOOL_GROUP_MIN` and `isTaskTool` to `components/ai/tool-call`. (#23313)
`groupConsecutive` cuts a list into runs of consecutive items that belong together and keys each run by its first member, so a chat can collapse a burst of tool calls into a single `ToolCallGroup` row instead of one row per call. Runs shorter than `TOOL_GROUP_MIN` (3) are left alone; pass `min` to change that.
```tsx
import { groupConsecutive } from '@mastra/playground-ui/components/ai/tool-call';
import type { MessageFactoryPart, ToolInvocationPart } from '@mastra/react';
const isToolCall = (part: MessageFactoryPart): part is ToolInvocationPart => part.type === 'tool-invocation';
const toolGroups = (parts: readonly MessageFactoryPart[]) =>
groupConsecutive(parts, { key: part => part.toolInvocation.toolCallId, joins: isToolCall });
// byFirstKey.get(id) -> the run to draw as one group row
// memberKeys.has(id) -> already drawn inside a group row
```
`isTaskTool` names the tools that belong in a docked task list rather than in the transcript, so a chat can hide them consistently.
```tsx
import { isTaskTool } from '@mastra/playground-ui/components/ai/tool-call';
isTaskTool('task_update'); // true
```
- Added an inline tag editor on the dataset detail page. Existing tags show as removable badges under the actions row, and an "Add tag" combobox lists all tags used across datasets or lets you create a new one by typing its name. Changes are saved immediately. (#23225)
- Changed the `TracesLayout` side panel to render as an absolute, full-height overlay (`absolute inset-y-0 right-0`) instead of an in-flow grid column. In Studio, the trace side panel on `/traces` and on entity traces tabs now spans the whole app frame height, covering the route header and page toolbar, while the trace list keeps its left column. Consumers must render `TracesLayout` inside a positioned (`relative`) ancestor sized to the area the panel should cover. (#23009)
### [@mastra/rag@2.6.2](https://github.com/mastra-ai/mastra/blob/@mastra/rag@2.6.2/packages/rag/CHANGELOG.md)
#### Patch Changes
- Fixed `separatorPosition: 'start'` chunking silently dropping trailing and consecutive separators. The `character` and `recursive` strategies now keep every separator, so the joined chunks reproduce the original text. Fixes https://github.com/mastra-ai/mastra/issues/23122 (#23158)
- Fixed the reference docs for createVectorQueryTool and createGraphRAGTool to describe the actual return shape: relevantContext is an array (chunk metadata objects for vector query, chunk text strings for graph RAG), not a combined string, and sources[].document is only populated by vector stores that return document content (Chroma, Elasticsearch, LanceDB, MongoDB). For other stores such as PgVector, read the chunk text from sources[].metadata.text. Fixes #23252 (#23277)
### [@mastra/react@1.4.11](https://github.com/mastra-ai/mastra/blob/@mastra/react@1.4.11/client-sdks/react/CHANGELOG.md)
#### Patch Changes
- Added additive system context support to React agent requests so per-turn state can preserve configured agent instructions. (#22315)
```tsx
import { useChat } from '@mastra/react';
function Chat() {
const { sendMessage } = useChat({ agentId: 'my-agent' });
// `system` is appended to the agent's configured instructions
// instead of replacing them like `instructions` does.
const send = () =>
sendMessage({
message: 'Continue',
modelSettings: { system: 'Current form state: ...' },
});
return <button onClick={send}>Send</button>;
}
```
### [@mastra/redis@1.4.4](https://github.com/mastra-ai/mastra/blob/@mastra/redis@1.4.4/stores/redis/CHANGELOG.md)
#### Patch Changes
- Improved durable agent streaming latency when Redis is remote (issue #22477). Recording a stream event in the cache used to take four sequential Redis commands (INCR, EXPIRE, RPUSH, EXPIRE); it now runs as a single atomic Lua script, so each streamed chunk costs one Redis round-trip instead of four. The built-in ioredis, node-redis, and Upstash presets support this out of the box, and the cache falls back to the previous multi-command path if the client has no `evalScript` or Redis Cluster rejects the multi-key script (CROSSSLOT). (#23161)
Added an optional `evalScript` adapter hook so custom client libraries can run the script too:
```ts
import { RedisServerCache } from '@mastra/redis';
const cache = new RedisServerCache({
client,
// (client, script, keys, args) => Promise<unknown>
evalScript: (client, script, keys, args) => client.eval(script, keys.length, ...keys, ...args),
});
```
### [@mastra/redis-streams@0.4.2](https://github.com/mastra-ai/mastra/blob/@mastra/redis-streams@0.4.2/pubsub/redis-streams/CHANGELOG.md)
#### Patch Changes
- Drain already-issued read and reclaim batches before unsubscribe finishes, and keep concurrent shutdown from closing the writer before dispatch completes. Application callbacks remain non-blocking and retain responsibility for ACK/NACK. (#23213)
### [@mastra/schema-compat@1.3.9](https://github.com/mastra-ai/mastra/blob/@mastra/schema-compat@1.3.9/packages/schema-compat/CHANGELOG.md)
#### Patch Changes
- Fixed OpenAI tool schema conversion to avoid duplicating nested optional object and array definitions, preventing compatible MCP tools from being rejected. (#22929)
### [@mastra/sentry@1.2.16](https://github.com/mastra-ai/mastra/blob/@mastra/sentry@1.2.16/observability/sentry/CHANGELOG.md)
#### Patch Changes
- Skill and workspace spans now report as `ai.skill` and `ai.workspace` instead of the generic `ai.span`, matching how memory spans already map to `ai.memory`. (#22542)
Span types that are missing from an older paired `@mastra/core` are skipped rather than mapped under an `undefined` key, which would otherwise match every span whose type is undefined.
### [@mastra/server@1.65.0](https://github.com/mastra-ai/mastra/blob/@mastra/server@1.65.0/packages/server/CHANGELOG.md)
#### Minor Changes
- Added optional feedback author profiles using the configured authentication provider, without extra setup when user lookup is supported. Authenticated feedback writes now prefer the authenticated user ID; anonymous writes remain supported. Missing users and lookup failures never remove feedback records. (#23201)
Before, HTTP feedback lists returned only the author ID. Now clients can read the optional profile from the same response:
```ts
const result = await client.listFeedback();
console.log(result.feedback[0]?.author?.name);
```
- Added experiment deletion routes. `DELETE /api/datasets/:datasetId/experiments/:experimentId` deletes an experiment that belongs to a dataset, and `DELETE /api/experiments/:experimentId` deletes any experiment, including orphaned experiments whose dataset was already deleted. Both routes cascade-delete the experiment's results and respect tenancy scoping. (#22550)
They also delete the traces the experiment produced, cascading to their spans and trace-linked scores, feedback, metrics and logs. 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.
```sh
# Delete an experiment that belongs to a dataset and tenant
curl -X DELETE 'http://localhost:4111/api/datasets/ds_1/experiments/exp_123?organizationId=org_1&projectId=project_1'
# Delete any experiment, including one orphaned by dataset deletion
curl -X DELETE 'http://localhost:4111/api/experiments/exp_123'
```
Both routes respond `501` unless the installed `@mastra/core` advertises the `experiment-deletion` feature.
- Added an authenticated endpoint for deleting up to 1,000 traces and their linked observability signals per request. (#22553)
```http
POST /api/observability/traces/delete
{ "traceIds": ["trace-1"] }
```
- Added an authenticated advanced trace-query endpoint with strict validation, a 256 KiB request-body limit, stable structured query errors including database timeouts, and matching OpenAPI response schemas. (#22728)
- Added `PATCH /api/datasets/:datasetId/experiments/:experimentId` to update an experiment's name, description, or metadata. Returns the updated experiment, `404` when the experiment does not exist in that dataset, and `400` for unknown body fields. (#22924)
#### Patch Changes
- Fixed configured `mapUserToResourceId` callbacks silently disabling isolation when they return an invalid resource ID. Requests now fail before reaching a route instead of falling back to a client-provided resource ID. Providers without a mapper and custom middleware retain their existing behavior. (#21722)
- Fixed PATCH /api/memory/threads/:threadId silently ignoring an empty title. Sending an empty string as the title now clears the thread title instead of keeping the previous one. Omitting the title still leaves it unchanged. (#23350)
- Fixed `GET /agents/:agentId` returning a 500 for agents whose dynamic instructions, tools, model, or options resolvers throw when called without execution context (for example a model selected per session). Unresolved fields are now omitted from the response, matching the behaviour of `GET /agents`, so these agents open correctly in Studio. Fixes https://github.com/mastra-ai/mastra/issues/23126 (#23162)
- Accepted and preserved the new `id`, `description`, and `metadata` fields on control-flow entries (`parallel`, `conditional`, `loop`, `foreach`, `sleep`, `sleepUntil`, `mapping`) in the dynamic workflow API schemas. Definitions posted over HTTP keep these fields instead of having them silently stripped. (#22633)
```json
{
"type": "sleep",
"id": "wait-before-retry",
"description": "Pause before retrying the external operation",
"metadata": { "title": "Wait before retry" },
"duration": 5000
}
```
- Fixed A2A send and stream memory persistence by using the task context and honoring authenticated resource IDs. Keep task memory identity stable across follow-up requests and reject conflicting authenticated identities. (#23146)
- Fix the `fields` query example in the `GET /api/workflows/:workflowId/runs/:runId` route description. It suggested `?fields=status,result,metadata`, which the validator rejects with a 400; `status` and metadata fields are always included and are not selectable. (#23279)
- `GET /api/system/packages` now reports `liveKitConnectionRouteEnabled`, true when the default `@mastra/livekit` connection-details route is mounted, so clients can tell whether Studio voice calls will work. (#19496)
```ts
const { liveKitConnectionRouteEnabled } = await fetch('/api/system/packages').then(res => res.json());
```
### [@mastra/spanner@1.6.5](https://github.com/mastra-ai/mastra/blob/@mastra/spanner@1.6.5/stores/spanner/CHANGELOG.md)
#### Patch Changes
- Fixed trace deletion to remove trace-linked metrics when Spanner metrics storage is enabled while preserving metric records without a trace ID. (#22553)
### [@mastra/valkey@0.2.2](https://github.com/mastra-ai/mastra/blob/@mastra/valkey@0.2.2/stores/valkey/CHANGELOG.md)
#### Patch Changes
- Improved durable agent streaming latency when Valkey is remote (issue #22477). Recording a stream event in the cache now runs as a single atomic Lua script instead of four sequential commands, so each streamed chunk costs one round-trip. (#23161)
### [@mastra/voice-google-gemini-live@0.14.9](https://github.com/mastra-ai/mastra/blob/@mastra/voice-google-gemini-live@0.14.9/voice/google-gemini-live-api/CHANGELOG.md)
#### Patch Changes
- Deduplicate tool calls by provider call id: the same function call delivered through both `serverContent.modelTurn.parts[].functionCall` and a top-level `toolCall` message now executes once and emits a single `toolResponse` instead of running the tool twice. (#22985)
- Send a functionResponse when Gemini Live calls an unregistered tool name. Previously the provider emitted a tool_not_found error and returned without answering the call, leaving the turn unanswered so the model went silent until the user hung up. (#23131)
### [@mastra/voice-openai-realtime@0.13.9](https://github.com/mastra-ai/mastra/blob/@mastra/voice-openai-realtime@0.13.9/voice/openai-realtime-api/CHANGELOG.md)
#### Patch Changes
- Fixed realtime connections hanging when session creation fails. Connections now reject on handshake errors, early socket closure, or a 15-second timeout configurable with connectTimeoutMs. (#23143)
### Other updated packages
The following packages were updated with dependency changes only:
- [@mastra/agent-builder@1.1.17](https://github.com/mastra-ai/mastra/blob/@mastra/agent-builder@1.1.17/packages/agent-builder/CHANGELOG.md)
- [@mastra/arthur@0.4.14](https://github.com/mastra-ai/mastra/blob/@mastra/arthur@0.4.14/observability/arthur/CHANGELOG.md)
- [@mastra/braintrust@1.3.11](https://github.com/mastra-ai/mastra/blob/@mastra/braintrust@1.3.11/observability/braintrust/CHANGELOG.md)
- [@mastra/datadog@1.4.6](https://github.com/mastra-ai/mastra/blob/@mastra/datadog@1.4.6/observability/datadog/CHANGELOG.md)
- [@mastra/deepeval@0.1.8](https://github.com/mastra-ai/mastra/blob/@mastra/deepeval@0.1.8/observability/deepeval/CHANGELOG.md)
- [@mastra/deployer@1.65.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer@1.65.0/packages/deployer/CHANGELOG.md)
- [@mastra/deployer-cloud@1.65.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-cloud@1.65.0/deployers/cloud/CHANGELOG.md)
- [@mastra/deployer-cloudflare@1.2.24](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-cloudflare@1.2.24/deployers/cloudflare/CHANGELOG.md)
- [@mastra/deployer-netlify@1.2.24](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-netlify@1.2.24/deployers/netlify/CHANGELOG.md)
- [@mastra/deployer-sandbox@0.3.9](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-sandbox@0.3.9/deployers/sandbox/CHANGELOG.md)
- [@mastra/deployer-vercel@1.2.24](https://github.com/mastra-ai/mastra/blob/@mastra/deployer-vercel@1.2.24/deployers/vercel/CHANGELOG.md)
- [@mastra/editor@0.14.4](https://github.com/mastra-ai/mastra/blob/@mastra/editor@0.14.4/packages/editor/CHANGELOG.md)
- [@mastra/laminar@1.3.16](https://github.com/mastra-ai/mastra/blob/@mastra/laminar@1.3.16/observability/laminar/CHANGELOG.md)
- [@mastra/langsmith@1.3.16](https://github.com/mastra-ai/mastra/blob/@mastra/langsmith@1.3.16/observability/langsmith/CHANGELOG.md)
- [@mastra/longmemeval@1.1.24](https://github.com/mastra-ai/mastra/blob/@mastra/longmemeval@1.1.24/explorations/longmemeval/CHANGELOG.md)
- [@mastra/mcp-docs-server@1.2.24](https://github.com/mastra-ai/mastra/blob/@mastra/mcp-docs-server@1.2.24/packages/mcp-docs-server/CHANGELOG.md)
- [@mastra/nestjs@0.2.24](https://github.com/mastra-ai/mastra/blob/@mastra/nestjs@0.2.24/server-adapters/nestjs/CHANGELOG.md)
- [@mastra/opencode@0.1.24](https://github.com/mastra-ai/mastra/blob/@mastra/opencode@0.1.24/integrations/opencode/CHANGELOG.md)
- [@mastra/otel-bridge@1.5.6](https://github.com/mastra-ai/mastra/blob/@mastra/otel-bridge@1.5.6/observability/otel-bridge/CHANGELOG.md)
- [@mastra/posthog@1.3.8](https://github.com/mastra-ai/mastra/blob/@mastra/posthog@1.3.8/observability/posthog/CHANGELOG.md)
- [@mastra/temporal@0.4.3](https://github.com/mastra-ai/mastra/blob/@mastra/temporal@0.4.3/workflows/temporal/CHANGELOG.md)
- [@mastra/turso@0.1.4](https://github.com/mastra-ai/mastra/blob/@mastra/turso@0.1.4/stores/turso/CHANGELOG.md)
- [@mastra/upstash@1.4.5](https://github.com/mastra-ai/mastra/blob/@mastra/upstash@1.4.5/stores/upstash/CHANGELOG.md)
- [@mastra/voice-xai-realtime@0.2.9](https://github.com/mastra-ai/mastra/blob/@mastra/voice-xai-realtime@0.2.9/voice/xai-realtime-api/CHANGELOG.md)