@mastra/core@1.69.0

livestorejs/livestore@mastra/core@1.69.0Sep 24, 2026by PaulieScanlon

Upgrading?

AI Summary

Elevates classifiers to first-class primitives and introduces native background tool execution patterns for cleaner async operations.

Key Highlights

  • Classifiers are now first-class primitives with full management APIs for workflows.
  • Added ClassifierProcessor for enforcing policies on agent input, output, and streaming.
  • Introduced context.background.adopt() for native background tool execution.
  • Added @mastra/connect with ten generated SaaS tool providers.
  • Enabled configurable retries for Inngest workflows.

Breaking Changes

  • Removed @mastra/playground-ui/lib/springs and altered FluidHoverHighlight API.
  • Reworked PageLayout and shell APIs.
  • Deprecated trace grouping in queryTraces (migrate to thread queries).

New Features

  • Classifier primitives
  • ClassifierProcessor
  • Background tool execution
  • Generated SaaS tool providers
  • Configurable Inngest retries

Full Release Notes

## Highlights

### Classifiers as a First-Class Primitive (Core + Workflows)
Mastra now supports registering classifiers on `new Mastra({ classifiers })`, with a full management API (`getClassifier*`, `listClassifiers`, `addClassifier`, `removeClassifier`) and automatic root tracing for classifier evaluations. Those configured classifiers can also be used as typed workflow steps (including fluent + dynamic graphs) to power typed branching/conditional control flow with token usage.

### Policy Enforcement with `ClassifierProcessor` (Input/Output/Streaming)
New `ClassifierProcessor` lets you apply typed classifier policies to agent input, output, and streaming content, enabling safety/routing gates with explicit abort behavior. It now **fails closed by default** (abort on classifier failure), with an opt-in `errorStrategy: 'warn'` to preserve prior fail-open behavior.

### Native Background Tool Execution with `context.background.adopt()`
Tools can now acknowledge immediately while handing a long-running native background operation to Mastra via `context.background.adopt({ completion, cancel })`, so execution can track completion/cancellation without keeping `execute()` pending. This enables cleaner async/background tool patterns (with the caveat that adopted handles are in-memory and don’t resume after restart).

### Connected SaaS Tools at Scale via `@mastra/connect` (New Generated Providers)
`@mastra/connect@0.3.0` adds ten generated tool providers (Slack, GitHub, Google Mail/Calendar, Fireflies, PostHog, Stripe, Discord, Twitter/X, HubSpot) built from Nango templates, including new agent-focused actions. With Mastra Platform connections, `tools: connect()` resolves the right tools per request with no extra configuration.

### More Reliable Durable Runs on Inngest (Configurable Retries + Better Resume)
Inngest workflows and durable agents now accept a `retries` option, allowing runs to survive process restarts/redeploys by retrying the function and continuing past completed steps. Durable agent resume methods (e.g., `resumeStream()`, tool approval routes) now properly resume suspended Inngest runs instead of failing with missing snapshots in key scenarios (like editor overrides).

### Breaking Changes
- `@mastra/playground-ui`: removed `@mastra/playground-ui/lib/springs`; `FluidHoverHighlight` API changed to accept only `hover` and `className`.
- `@mastra/playground-ui`: `PageLayout`/shell APIs reworked (removed `PageLayoutRoot`, `MainContentLayout`, `MainContentContent`, and multiple `AppShell` header-related props/contexts); migrate to the new `PageLayout` with `breadcrumbs`, `headerActions`, and `actionRow`.
- Trace grouping deprecations (Core/Server/Client): grouped results in `queryTraces()` / trace-query `group` are deprecated; migrate to thread query (`queryTraceThreads()` / `queryThreads` contract).

## Changelog

### [@mastra/core@1.69.0](https://github.com/mastra-ai/mastra/blob/@mastra/core@1.69.0/packages/core/CHANGELOG.md)
#### Minor Changes

- Added classifier registration on `Mastra`. (#24738)

  Classifiers can now be passed to `new Mastra({ classifiers })` and accessed with `getClassifier`, `getClassifierById`, `listClassifiers`, `addClassifier`, and `removeClassifier`. Registered classifiers evaluated without an active trace start a root `CLASSIFIER_EVALUATION` span through the `Mastra` instance's configured observability provider.

  ```ts
  const mastra = new Mastra({ classifiers: { router } });
  const classifier = mastra.getClassifier('router');
  ```

- Added configured classifiers as typed workflow steps with fluent and dynamic graph support. Classifier steps expose complete typed answers and token usage for existing branch and conditional control flow. (#24747)

  ```ts
  workflow
    .map({ message: { initData: true, path: 'message' } })
    .classifier(router)
    .branch([
      [async ({ inputData }) => inputData.answers.route.choice === 'billing', billingStep],
      [async ({ inputData }) => inputData.answers.route.choice === 'support', supportStep],
    ]);
  ```

- Deprecated the `group` option on the trace-query request contract. Grouping remains functional until the next major release; use the `queryThreads` thread-query contract for new code (exposed as `queryTraceThreads()` in `@mastra/client-js`). (#23790)

  **Before:**

  ```ts
  import type { TraceQueryRequest } from '@mastra/core/storage';

  const request: TraceQueryRequest = { timeRange, group: { by: ['threadId'] } };
  ```

  **After:**

  ```ts
  import type { QueryThreadsInput } from '@mastra/core/storage';

  const input: QueryThreadsInput = { traces: { timeRange } };
  ```

- Added `context.background.adopt()` so tools can return an acknowledgement while native background tasks track their existing operation through completion and cancellation. (#24418)

  Previously, background tools had to keep `execute()` pending:

  ```ts
  execute: async (input, context) => {
    const operation = startResearch(input, context.abortSignal);
    return await operation.finished;
  };
  ```

  Tools can now adopt their operation during native background execution:

  ```ts
  execute: async (input, context) => {
    const operation = startResearch(input, context.abortSignal);
    if (context.background) {
      context.background.adopt({
        completion: operation.finished,
        cancel: reason => operation.cancel(reason),
      });
      return { answer: 'Research started' };
    }
    return await operation.finished;
  };
  ```

  `startResearch` represents the tool's operation API. Its `finished` promise must resolve with the final tool result after cleanup, or reject on failure. Adopt once, before `execute()` returns. The handle stays in memory and cannot resume after a process restart.

- Added `rootSpanName` to `tracingOptions` so each agent or workflow run can set its own root span name. Runs of the same workflow no longer all show up as `workflow run: 'my-workflow'` in trace lists. (#24564)

  ```ts
  await run.start({
    inputData: { skillId: 'typescript' },
    tracingOptions: { rootSpanName: 'skill-analyze: typescript' },
  });
  ```

  The name applies to the root span only. Child spans keep their default names, and entity filters still match on the workflow or agent id. Related: https://github.com/mastra-ai/mastra/issues/24518

  _Also in: @mastra/observability@1.18.0, @mastra/server@1.69.0_

- Added `ClassifierProcessor` for applying typed classifier policies to agent input, output, and streaming content. (#24768)

  ```typescript
  import { Agent } from '@mastra/core/agent';
  import { Classifier } from '@mastra/core/classifier';
  import { ClassifierProcessor } from '@mastra/core/processors';

  const safety = new Classifier({
    id: 'safety',
    model,
    questions: {
      unsafe: { type: 'boolean', criteria: { true: 'Unsafe', false: 'Safe' } },
    },
  });

  const agent = new Agent({
    id: 'support-agent',
    name: 'Support agent',
    instructions: 'Answer support questions.',
    model: 'openai/gpt-5-mini',
    inputProcessors: [
      new ClassifierProcessor({
        classifier: safety,
        onResult: (answers, { abort }) => {
          if (answers.unsafe.probability > 0.8) abort('Rejected by safety policy');
        },
      }),
    ],
  });
  ```

- Added a typed Classifier primitive for fixed-option evaluation with AI SDK evaluation models. (#24458)

  ```ts
  import { Classifier } from '@mastra/core/classifier';

  const classifier = new Classifier({ id: 'router', model });
  const result = await classifier.evaluate({
    state: request,
    questions: {
      route: {
        type: 'choice',
        criteria: { support: 'Support request', sales: 'Sales request' },
      },
    },
  });
  ```

- Added `validateSkillContent()` and exported `validateSkillMetadata()` from `@mastra/core/skills`, so apps can validate a `SKILL.md` before saving it using the same rules applied at load time. (#24766)

  ```typescript
  import { validateSkillContent } from '@mastra/core/skills';

  const result = validateSkillContent({ content: skillMarkdown, directoryName: 'my-skill' });
  if (!result.valid) console.error(result.errors);
  ```

#### Patch Changes

- Update provider registry and model documentation with latest models and providers (`7fefefd`)

- Fixed deferred aborts for paused tool calls and tool approvals. They no longer stop a new run started after switching threads or running `/new`. (#24743)

- `ClassifierProcessor` now fails closed by default. When the classifier call fails, the request is aborted instead of letting unchecked content through. Pass `errorStrategy: 'warn'` to keep the previous fail-open behavior: (#24793)

  ```ts
  new ClassifierProcessor({ classifier, onResult, errorStrategy: 'warn' });
  ```

- Honor `tracingOptions.rootSpanName` when creating a root span. The caller-supplied name replaces the default `agent run: '<id>'` or `workflow run: '<id>'` name on the root span only. Works for the default and Inngest workflow engines because the name is applied when the span starts, before any durable snapshot is taken. (#24564)

  ```ts
  const span = observability.startSpan({
    type: SpanType.WORKFLOW_RUN,
    name: "workflow run: 'skill-analyze'",
    tracingOptions: { rootSpanName: 'skill-analyze: typescript' },
  });

  span.name; // "skill-analyze: typescript"
  ```

  Related: https://github.com/mastra-ai/mastra/issues/24518

  _Also in: @mastra/observability@1.18.0, @mastra/server@1.69.0_

- Tool calls whose approval policy is a function now run in parallel when that policy returns `false` for the actual call. This applies to the default `toolCallConcurrency` strategy and to `strategy: 'called'`. Previously, any function policy forced sequential execution. This included the policy that `MCPClient` attaches when `requireToolApproval` is a function. Each call's policy is evaluated once, and that verdict is reused when the tool runs. (#24763)

- Fixed a crash where a background process started with the `execute_command` workspace tool could terminate the host process. If the exit callback threw or the process could not be observed after the PID was returned, the failure escaped as an unhandled promise rejection. These failures are now caught and logged instead. (#24638)

- Fixed internal spans being exported after they are rebuilt with `rebuildSpan()` when `includeInternalSpans` is disabled. Spans created with `tracingPolicy.internal` now keep their internal status through `exportSpan()` and `rebuildSpan()`, so they stay out of exporters by default and remain included when `includeInternalSpans` is enabled. (#24742)

  _Also in: @mastra/observability@1.18.0_

- Fixed `JSON.stringify(requestContext)` throwing `TypeError: Do not know how to serialize a BigInt` when a BigInt was stored directly in `RequestContext`. Such values are now left out of `toJSON()` output, matching how nested BigInts were already handled. BigInts are still included when `BigInt.prototype.toJSON` returns a JSON-serializable value, and `requestContext.get()` still returns the original value. (#24751)

- Fixed `runEvals` saving `mastra__authToken` and values that cannot be stored in score rows. Saved scores keep string, number and boolean request context values, and nested object values use dotted keys. (#24753)

- Added `rootSpanName` to the generated `tracingOptions` request types so per-run root span names can be sent from the client. (#24564)

  ```ts
  const run = await client.getWorkflow('skillAnalyze').createRun();

  await run.startAsync({
    inputData: { skillId: 'typescript' },
    tracingOptions: { rootSpanName: 'skill-analyze: typescript' },
  });
  ```

  Related: https://github.com/mastra-ai/mastra/issues/24518

  _Also in: @mastra/client-js@1.48.0, @mastra/observability@1.18.0, @mastra/server@1.69.0_

- Fixed chat channel messages (Slack, Discord, etc.) silently disappearing when the agent failed during setup. If workspace, instructions, tools, or model resolution throws before the run starts, the error is now posted back to the chat thread instead of being dropped with no reply. (#24300)

- Fix an event-loop hang when a run starts immediately after a persisted idle signal. The synthetic run that rebroadcasts the signal resolved its completion promise before its deferred cleanup ran, so a same-thread run waiting on it re-awaited an already-resolved promise in an unbounded microtask loop — pinning a CPU core and stopping timers and HTTP process-wide. The waiter now yields to the timer queue when the same run is still active after its completion promise settles. (#24613)

- Accept `rootSpanName` in `tracingOptions` on agent and workflow HTTP routes so clients can set a per-run root span name. (#24564)

  ```http
  POST /api/workflows/skillAnalyze/start-async
  {
    "inputData": { "skillId": "typescript" },
    "tracingOptions": { "rootSpanName": "skill-analyze: typescript" }
  }
  ```

  Related: https://github.com/mastra-ai/mastra/issues/24518

  _Also in: @mastra/observability@1.18.0, @mastra/server@1.69.0_

### [@mastra/auth-cloud@1.2.6](https://github.com/mastra-ai/mastra/blob/@mastra/auth-cloud@1.2.6/auth/cloud/CHANGELOG.md)
#### Patch Changes

- Improved Cloud authentication portability by generating PKCE verifier and state values with Web Crypto while preserving synchronous login APIs. (#24583)

### [@mastra/auth-google@0.1.4](https://github.com/mastra-ai/mastra/blob/@mastra/auth-google@0.1.4/auth/google/CHANGELOG.md)
#### Patch Changes

- Improved Google authentication portability by signing service account tokens with Web Crypto. (#24583)

### [@mastra/auth-studio@1.3.7](https://github.com/mastra-ai/mastra/blob/@mastra/auth-studio@1.3.7/auth/studio/CHANGELOG.md)
#### Patch Changes

- Improved Studio authentication portability by hashing credential cache keys with Web Crypto. (#24583)

### [@mastra/clickhouse@1.20.1](https://github.com/mastra-ai/mastra/blob/@mastra/clickhouse@1.20.1/stores/clickhouse/CHANGELOG.md)
#### Patch Changes

- Fixed ClickHouse delta polling for scores returning the same score again after a retried or duplicate insert. Delta reads now return each score once, in the first poll after it was written, and always with its latest values. (#24607)

### [@mastra/client-js@1.48.0](https://github.com/mastra-ai/mastra/blob/@mastra/client-js@1.48.0/client-sdks/client-js/CHANGELOG.md)
#### Minor Changes

- Added `transports` to MCP server info responses so consumers can tell which endpoints a server serves. MCP v2 servers report `['streamable-http']` only; MCP 1.x servers also report `'sse'`. Servers that predate the field omit it, so treat absence as 1.x. (#24388)

  ```ts
  import { MastraClient } from '@mastra/client-js';

  const client = new MastraClient({ baseUrl: 'http://localhost:4111' });
  const { servers } = await client.getMcpServers();

  for (const server of servers) {
    const hasSse = server.transports?.includes('sse') ?? true;
    const path = hasSse ? 'sse' : 'mcp';
    console.log(`${server.name}: http://localhost:4111/api/mcp/${server.id}/${path}`);
  }
  ```

- Deprecated grouped results from `queryTraces()`. Existing grouped queries continue to work until the next major release; use `queryTraceThreads()` for new code. (#23790)

  **Before:**

  ```ts
  await mastraClient.queryTraces({ timeRange, group: { by: ['threadId'] } });
  ```

  **After:**

  ```ts
  await mastraClient.queryTraceThreads({ traces: { timeRange } });
  ```

#### Patch Changes

- Added client support for classifier workflow steps and their rendered workflow graph entries. (#24747)

  _Also in: @mastra/playground-ui@57.0.0, @mastra/react@1.6.1_

### [@mastra/code-sdk@1.8.1](https://github.com/mastra-ai/mastra/blob/@mastra/code-sdk@1.8.1/mastracode/sdk/CHANGELOG.md)
#### Patch Changes

- Added configured classifier discovery and typed routing paths to workflow authoring tools. (#24747)

- Missing provider credentials for signed-in Factory accounts now throw `ProviderAuthRequiredError` instead of a plain `Error`, so hosts can classify the failure as an authentication error without matching on the message text. (#24737)

- Fixed hooks running twice for sessions started in your home directory. There, the project hooks file (`~/.mastracode/hooks.json`) is the same file as the global one, and it was loaded as both. It is now loaded once, including when the project directory is a symlink to your home directory. (#24744)

- Fixed plugin tools so background execution is enabled only when the tool declares support; `mastra_expert` is no longer opted in by name. Removed global plugin-call serialization, allowing independently declared tools to run concurrently. (#24418)

### [@mastra/codemod@1.1.4](https://github.com/mastra-ai/mastra/blob/@mastra/codemod@1.1.4/packages/codemod/CHANGELOG.md)
#### Patch Changes

- Fixed the agent property codemod to migrate tools access to `listTools()`. (#24785)

- Fixed codemod runs reporting success when a transform crashed. Every jscodeshift transformation error (not just syntax errors) is now reported against the correct file, and both individual codemod runs and `v1` exit with a non-zero code when any file fails to transform. (#24783)

### [@mastra/connect@0.3.0](https://github.com/mastra-ai/mastra/blob/@mastra/connect@0.3.0/packages/connect/CHANGELOG.md)
#### Minor Changes

- Added ten generated tool providers to @mastra/connect: Slack, GitHub, Google Mail, Google Calendar, Fireflies, PostHog, Stripe, Discord, Twitter/X, and HubSpot. Each ships checked-in tools generated from Nango integration templates, including new agent-focused actions (PostHog HogQL queries, Stripe balance/dispute/coupon/account reads, GitHub tags and trees, Slack Connect shared-channel invites, Twitter search and following lookups, HubSpot form submission). Attach a provider connection in Mastra Platform and the tools resolve through connect() with no extra configuration: (#24606)

  ```ts
  import { Agent } from '@mastra/core/agent';
  import { connect } from '@mastra/connect';

  const agent = new Agent({
    id: 'ops-agent',
    model: 'anthropic/claude-sonnet-4-6',
    tools: connect(),
  });
  ```

#### Patch Changes

- Generated `@mastra/connect` providers now cover more of the upstream template catalog. Actions that authenticate with the raw connection credential — token-introspection endpoints, for example — are generated instead of skipped, and actions that validate their input with the template validation helper are generated too. The credential is fetched from the platform only for the specific actions that read it. Under the hood this extends the platform proxy runtime and the provider generator; agents consume the resulting tools through the normal provider workflow with no API changes: (#24605)

  ```typescript
  import { Agent } from '@mastra/core/agent';
  import { connect } from '@mastra/connect';

  const assistant = new Agent({
    id: 'assistant',
    name: 'Assistant',
    instructions: 'Help with connected services.',
    model: 'anthropic/claude-sonnet-4-6',
    tools: connect(), // tools for every connected provider, resolved per request
  });
  ```

### [@mastra/deployer@1.69.0](https://github.com/mastra-ai/mastra/blob/@mastra/deployer@1.69.0/packages/deployer/CHANGELOG.md)
#### Minor Changes

- Extended pnpm patch preservation to yarn (Berry) and bun, so `mastra build` keeps patches applied in the bundled output regardless of package manager. (#21716)

  Yarn needs no configuration: `.yarn/patches/` is copied into the output and Yarn applies the patches its lockfile already references.

  Bun declares patches in `package.json`, and `mastra build` rewrites them to output-relative paths in the bundled `package.json`:

  ```json
  {
    "patchedDependencies": {
      "lodash@4.17.21": "patches/lodash.patch"
    }
  }
  ```

#### Patch Changes

- Fixed `mastra build` discarding pnpm patches. Patched dependencies declared in your workspace are now carried into the built output, so deployed apps run the same patched code as your source project. (#21716)

- Fixed compatibility by requiring @mastra/core 1.58.0 or newer. These packages all build on @mastra/server, which needs core 1.58.0, but they still advertised support for core versions as old as 1.50.0. Installing one of those older pairings produced a broken setup instead of a clear version conflict. (#24715)

  If your package manager reports a peer conflict after this release, upgrade @mastra/core to 1.58.0 or newer.

  _Also in: @mastra/deployer-cloud@1.69.0, @mastra/deployer-cloudflare@1.2.28, @mastra/deployer-netlify@1.2.28, @mastra/deployer-sandbox@0.3.13, @mastra/deployer-vercel@1.2.28, @mastra/elysia@0.1.9, @mastra/express@1.5.13, @mastra/fastify@1.5.13, @mastra/hono@1.7.11, @mastra/koa@1.7.13, @mastra/nestjs@0.2.28, @mastra/next@0.2.27, @mastra/tanstack-start@0.2.27, @mastra/temporal@0.4.7_

### [@mastra/factory@0.17.0](https://github.com/mastra-ai/mastra/blob/@mastra/factory@0.17.0/mastracode/factory/CHANGELOG.md)
#### Minor Changes

- Added `skipRules` to `upsertLinkedWorkItem` decisions so a rule can file a card directly on a stage it names, with none of the board's phase rules run for it. (#24649)

  External records that arrive already past a board's first step no longer have to pass through it. Set `skipRules: true` on the decision to place the card on `stage` as its first entry: no arrival rule, no destination-entry rule, no transition row, and nothing started for the card.

  ```ts
  issueOpened: context => {
    const decision = defaultGithubRules.issueOpened(context);
    if (!decision) return decision;
    // Triage already happened upstream: file it on Planning and leave it there.
    return { ...decision, stage: 'planning', skipRules: true };
  };
  ```

  The same decision on an existing card relocates it — used by the GitHub issue reconciliation sweep, which now replays an open issue through the rules ingress when its labels changed, so label-derived placement is re-applied even when the `labeled` webhook never arrived. `moveCardToBoard` now takes an optional `targetStage`, so a placement can name a phase on the card's own board (Work › Intake to Work › Planning), not just a board's landing phase. Relocation guards still apply: terminal cards, cards with a session on their current phase's role, and cards that changed under the dispatcher stay put.

  A pull request opening is now evaluated once per card it concerns, like a merge: its own Review card is filed by the arrival (the evaluation flagged `pullRequestIntake`) and the Work item that authored the pull request is answered in a second evaluation, so a rule can place the item that is now out for review. The built-in `pullRequestOpened` files the card only on the arrival.

- **Added a `platform.github` config key to `MastraFactory`** for overriding the GitHub integration the factory installs itself. (#24665)

  When Platform credentials are present, `MastraFactory` installs a `PlatformGithubIntegration` automatically. You can now change its event handlers and app slug from the factory config, instead of constructing the integration and re-declaring the auto-install guard yourself:

  ```ts
  // Before
  import { PlatformGithubIntegration } from '@mastra/factory/integrations/platform/github/integration';

  new MastraFactory({
    storage,
    integrations: [new PlatformGithubIntegration({ rules: { issueOpened }, slug: 'factory-app' })],
  });

  // After
  new MastraFactory({
    storage,
    platform: { github: { rules: { issueOpened }, slug: 'factory-app' } },
  });
  ```

  For example, replacing `issueOpened` lets a newly created GitHub-issue work item land on the board its existing labels select instead of defaulting to Work. `slug` falls back to `platform.githubAppSlug` when omitted.

  An explicit integration with id `github` in `integrations` still takes precedence, which makes `platform.github` a no-op; the factory logs a warning rather than ignoring it silently, and warns the same way when no Platform credentials and no explicit GitHub integration are present.

#### Patch Changes

- Fixed Slack typing statuses to avoid repeated status updates during streamed responses. (#24711)

- Fixed automated skill invocations failing when consecutive agent runs end before accepting queued work. (#24750)

- Fixed GitHub sessions not being subscribed to pull requests they opened through the shared `source_control_create_change_request` tool. Comments and closes on those pull requests now reach the session, and the transcript shows the pull request link. (#24571)

- Fixed the transcript of a GitHub user session never showing its pull request link. Subscriptions a user session created are now found when the session asks for them by its own id. (#24572)

- Platform GitLab discovery now considers only connections on the Platform's `gitlab` (OAuth) integration. The `gitlab-group`, `gitlab-group-token` and `gitlab-pat` integration ids are no longer queried or accepted, so connections created through those flows are not discovered. (#24686)

- Fixed review sessions that resumed after a Factory restart reading repository instruction files from the untrusted pull request checkout before their security state was restored. (#24576)

- Fixed the session workspace Changes panel and file diffs going empty once a build session committed its work. Changes are now compared against the branch the session started from, so committed work stays visible. (#24574)

- Fixed diff-comment tool calls failing on OpenAI models. Creating or replying to a line-anchored review comment with `source_control_create_diff_comment` no longer produces an invalid function schema that the model API rejects; both modes are validated from one object input. (#24661)

### [@mastra/inngest@1.9.1](https://github.com/mastra-ai/mastra/blob/@mastra/inngest@1.9.1/workflows/inngest/CHANGELOG.md)
#### Patch Changes

- Fixed InngestAgent losing durable execution in two cases (#24736). (#24758)

  - **Editor overrides**: `__fork()` now returns an Inngest-backed agent, so agents with published editor overrides keep running on Inngest instead of silently running in-process.
  - **Resuming suspended runs**: `resumeStream()`, `approveToolCall()`, `declineToolCall()`, `approveToolCallGenerate()` and `declineToolCallGenerate()` now resume the suspended Inngest run. Previously they threw `AGENT_RESUME_NO_SNAPSHOT_FOUND`, which broke `chatRoute` tool approval.

- Inngest workflows and durable agents now accept a `retries` option, so a run can survive a process restart or redeploy. Before this change, every Inngest function was created with `retries: 0` and there was no way to change it. If a call to the application failed (for example, the process restarted mid-run), the whole run failed straight away. (#24741)

  `retries` is passed to Inngest as its function-level retry count. When set, Inngest calls the function again after a failed request, skips the steps that already finished, and continues the run. Errors thrown by your own step code are still retried per step through `retryConfig` or `step.retries`, and are never retried again at the function level. The default is still `0`.

  ```ts
  const workflow = createWorkflow({
    id: 'my-workflow',
    inputSchema,
    outputSchema,
    retries: 3,
  });

  const durableAgent = createInngestAgent({ agent, inngest, retries: 3 });
  ```

### [@mastra/koa@1.7.13](https://github.com/mastra-ai/mastra/blob/@mastra/koa@1.7.13/server-adapters/koa/CHANGELOG.md)
#### Patch Changes

- Decode route parameters once before passing them to handlers. Percent-encoded skill names and reference paths now resolve consistently with other server adapters. (#24637)

### [@mastra/playground-ui@57.0.0](https://github.com/mastra-ai/mastra/blob/@mastra/playground-ui@57.0.0/packages/playground-ui/CHANGELOG.md)
#### Minor Changes

- Aligned the Ask User card with the other AI surfaces in the chat stream. It now sits on a raised card with the same radius and a labelled header, uses design-system typography, and — most visibly — renders option pickers with the design-system `Checkbox` and `RadioGroup` instead of native browser controls, so selecting an option animates and matches every other control in Studio. (#24677)

  Fixed along the way: option descriptions used the 10px `meta` role (reserved for badges and micro labels) and now use `caption`; the card was hand-built from the frame fill plus a manual border instead of the card primitive, so it read as a recessed panel rather than a card.

  **Removed exports**

  `AskUserOptionControl` and `AskUserOptionDescription` are gone — they wrapped the native `<input>` that no longer exists. Compose a row with `AskUserOptionRow` instead, passing the control you want:

  ```tsx
  // Before
  <AskUserOptionControl type="radio" label="Staging" description="Validate first." />

  // After
  <AskUserOptionRow
    label="Staging"
    description="Validate first."
    control={<RadioGroupItem value="Staging" />}
  />
  ```

  `AskUserQuestion` is now typed against `Txt` rather than `<legend>`, so it takes `variant`, `tone`, and `as`.

- Added a Linear-style **Advanced filter** to `FilterBar`, so filters can be combined with `and` / `or` instead of only being ANDed together. (#24654)

  Pass a `FilterBarExpression` as `value` to opt in. Top-level chips stay flat and implicitly ANDed; each top-level group renders as a single **Advanced filter · N** chip that opens a popover with a recursive rule builder: one editable chip per condition laid out as `where` / `and` / `or` rows, nested groups as cards with their own `and` | `or` switch, and `+ Condition` / `+ Group` / `Clear all` footers. From the bar input, pick **Advanced filter…** at the end of the field list to create a group and start adding conditions into it. Empty groups are pruned when the popover closes. Nesting depth is bounded by the new `maxDepth` prop (default `3`).

  A flat `FilterBarItem[]` value keeps working unchanged and never shows the option.

  ```tsx
  import { FilterBar, type FilterBarExpression } from '@mastra/playground-ui';

  const [value, setValue] = useState<FilterBarExpression>({
    logic: 'and',
    nodes: [
      { id: '1', fieldId: 'status', operatorId: 'is', value: 'error' },
      {
        id: 'g1',
        kind: 'group',
        logic: 'or',
        nodes: [
          { id: '2', fieldId: 'env', operatorId: 'is', value: 'prod' },
          { id: '3', fieldId: 'env', operatorId: 'is', value: 'staging' },
        ],
      },
    ],
  });

  <FilterBar fields={fields} operators={operators} value={value} onValueChange={setValue}>
    <FilterBar.Chips />
    <FilterBar.Input />
  </FilterBar>;
  ```

- Redesigned metrics KPI cards: the value now sits next to a colored change badge ("+15.3% vs prior period"), and cost changes treat a decrease as good. Added `MetricsCardGroup`, a frame that lays out KPI or chart cards in rows that flex and wrap to fill the width, with an `inset` variant that sets the cards into a thick raised border like `DataList`. (#24716)

- Fixed menus painting two hover backgrounds at once. DropdownMenu, ContextMenu, Select and Combobox rows no longer paint their own hover background under the moving highlight, destructive items tint that highlight instead of stacking a second one, and an open submenu keeps its parent row lit on the same surface. Moving the pointer or clicking inside a submenu no longer moves the parent menu's highlight or activates the parent row under it. PropertyFilter lists now use the same moving highlight, which also follows keyboard focus, and virtualized DataList rows no longer make the highlight blink while scrolling. (#24712)

  The moving highlight now runs on CSS transitions instead of `framer-motion`, which is no longer a dependency of `@mastra/playground-ui`. It fades in and travels as before, and appears instantly without the fade-out when the pointer leaves.

  **Breaking:** the `@mastra/playground-ui/lib/springs` entry point is removed, and `FluidHoverHighlight` now takes only `hover` and `className`. Pass the `useFluidHover` return value as `hover`:

  ```tsx
  const hover = useFluidHover(containerRef);
  <FluidHoverHighlight hover={hover} className="rounded-lg" />;
  ```

- Added a `size` prop to `ButtonsGroup`, and the group now owns the control rung of every segment it holds. (#24696)

  **Why**

  A `ButtonsGroup` imposed no height of its own. Each segment brought its own off the control ladder (`sm` 28px, `md` 30px, `lg` 32px), so two segments on different rungs rendered a step in the joined pill — and nothing stopped that from happening. It was easy to hit by accident, because `CopyButton` defaults to `sm` while `Button`, `Input`, `SelectTrigger`, `Combobox` and `InputGroup` all default to `md`.

  **What changed**

  The rung is declared once, on the group, and a child's own `size` can no longer lift a segment off it. Height, the width of an icon-mode circle, and the glyph size all follow the group.

  ```tsx
  // before — the rung repeated on every segment, and nothing checked they agreed
  <ButtonsGroup>
    <CopyButton content={value} size="sm" />
    <Button size="sm" aria-label="Expand">
      <ExpandIcon />
    </Button>
  </ButtonsGroup>

  // after — one declaration, and a step in the pill is no longer expressible
  <ButtonsGroup size="sm">
    <CopyButton content={value} />
    <Button aria-label="Expand">
      <ExpandIcon />
    </Button>
  </ButtonsGroup>
  ```

  `size` defaults to `md`. A group whose segments were all `sm` needs `size="sm"` on the group — without it those segments now render at `md`. `size="icon-sm" | "icon-md" | "icon-lg"` stays on a `Button`: on `Button` the `icon-*` sizes also select the square shape, and only their rung is overridden.

  **`InputGroup` inside a group**

  A field keys its type scale off its own `data-size` (`text-caption` / `text-body-sm` / `text-body`), which the group's stylesheet cannot reach — forcing only the box left a `sm` group reading at `md`. `ButtonsGroup` now publishes its rung on the new `ControlSizeContext` (exported from `ds/primitives/control-size`) and `InputGroup` takes it **over** its own `size`, the same rule as every other segment: inside a group, an explicit `size` on the field is inert. Outside one, `size` behaves exactly as before.

  A field's addon glyph is still a flat `size-4` at every rung. That is `InputGroup`'s own behaviour, in or out of a group, and changing it moves every field in the app — left as a follow-up.

  **Removed**

  `ButtonsGroupText` no longer takes a `size` prop. A text segment only exists inside a group, and the group sets its height.

  `ButtonsGroupSeparator` is gone. A group joins its segments with a seam — one shared border, halved between neighbours — so an extra rule between them drew a second line on top of that seam. It had no call site outside its own story. A group that genuinely needs to separate two clusters should render its own divider, or be two groups.

  **One material for a neutral filled control**

  A group put the mismatch in plain sight: with every segment finally the same height, four of them still had four different fills. A field is the raised card material (`bg-card` + `shadow-raised`, white in light), a `Button` `default` was the translucent 6% `--fill` rung with a 1px border, and `ButtonsGroupText` was `--surface-panel`, the opaque twin of that rung.

  `Button`'s `default` variant now wears the same raised material as a field, and so does the text segment. In light a default button is white on the off-white canvas, like the input and the select trigger beside it; in dark the two were already within a few levels of each other, so little moves. The material has no border of its own — its edge is the rim `shadow-raised` draws, and focus repaints that rim.

  States follow the material rather than the fill: hover and press wash through `--surface-tint` (`fill-subtle`, then `fill`) instead of swapping the background, because a pinned card fill cannot be swapped without going translucent.

  Removed with it: `fieldTriggerSurfaceStyle`, which existed only to undo the Button's fill on a Select/Combobox trigger, and the `field` key of `controlTriggerOpenState` — `default` now _is_ the field's open state. `raisedControlSurfaceStyle` (exported from `ds/primitives/form-element`) is the one definition.

  **Hover and focus inside a group**

  A segment's leading edge belongs to its neighbour, so hovering an `outline` segment lit only three of its sides, and keyboard focus showed no edge at all — the group's seam colour overrode the segment's focus border. The focused segment now takes the focus colour on every side, and the neighbour that owns the shared seam takes the hover or focus colour with it.

- Pages now own their header. `PageLayout` accepts `breadcrumbs` and `actions` props and renders the header row (breadcrumbs left, actions right) above a plain scrollable `<main>`. (#24643)

  Breaking:

  - `PageLayout` no longer takes `width`, `height` or `heading`; apply `max-w-*` / grid classes via `className` instead. `PageLayoutRoot` is gone — import `PageLayout` directly.
  - `MainContentLayout` and `MainContentContent` are removed; use `PageLayout`.
  - `AppShell` drops `routeHeader`, `renderFrame`, `mainLabel` and `AppShellFrameProps`, along with `PageHeadingContext` / `usePageHeading`. `AppShell` only lays out `sidebar`, `mobileHeader` and `children`; the framed card styling moved to the consumer.

- **FilterBar: a field that takes plain typed text** (#24659)

  A field marked `search` stays pinned at the top of the field list whatever is typed, so text that names no field still commits as a filter instead of forcing a field pick first. Options also take a `start` node rendered before their label, for an avatar or an icon.

  ```tsx
  <FilterBar
    fields={[
      { id: 'text', label: 'Text', search: true, operators: ['contains'] },
      {
        id: 'teammate',
        label: 'Teammate',
        operators: ['is'],
        suggestions: [{ value: 'github:alice', label: 'Alice', start: <Avatar name="Alice" /> }],
      },
    ]}
    operators={DEFAULT_FILTER_OPERATORS}
    value={items}
    onValueChange={setItems}
  >
    <FilterBar.Chips />
    <FilterBar.Input />
  </FilterBar>
  ```

  Typing `flaky login` and pressing Enter now commits `Text contains flaky login`; `Teammate` is one arrow below.

  Fixed: the option list opens on its first row again after a field is picked or a chip is committed with the keyboard. It used to keep the highlight index of the list it replaced, so a field reached with ArrowDown opened the value list on its second option.

#### Patch Changes

- Add `ActionRow` (`ActionRow.Start`, `ActionRow.End`) — a toolbar line that pushes a start group and an end group apart and wraps on narrow viewports. `PageLayout`'s `actionRow` slot now stacks multiple rows with a consistent gap. (#24643)

- `AppShell` now insets its body (`p-1.5 lg:p-2`, dropping the left inset at `lg` when a `sidebar` is passed) so the frame rendered inside it no longer needs its own margins. Consumers that put `m-*` classes on their own frame should remove them. `PageShell` body padding is now `p-4` (was `p-4 px-6`) to match the rest of the page layouts. (#24643)

- `AppShell` accepts a `sidebar` slot and owns the sidebar/content grid, so consumers no longer wrap the sidebar and shell in a hand-rolled grid. (#24643)

- Add `DisabledFeatureButton`, an icon-only disabled control with a keyboard-reachable tooltip and docs link for features that are not yet available. The focusable trigger carries the accessible name and disabled state. (#24777)

- `EmptyState` now renders a default `CircleSlashIcon` when `iconSlot` is omitted; `iconSlot` is optional (pass `null` for no icon). All playground callsites drop their explicit `iconSlot`. (#24643)

- Add a `variant="fill"` option to `EmptyState` that centers the block in the full height of its parent, replacing the `flex h-full items-center justify-center` wrapper every empty-state call site used to hand-roll. (#24643)

- Increased SidebarNew section header spacing to make navigation sections easier to distinguish. Hide the navigation scrollbar when idle and show it on hover, focus, or scrolling. (#24713)

- `PageLayout` is now a minimal shell: `breadcrumbs`, `headerActions` (renamed from `actions`), a new `actionRow` slot pinned above the scrolling body, and `children`. The `<main>` body carries `p-4` by default and no longer accepts `className`. The `PageLayout.TopArea` / `MainArea` / `Row` / `Column` slots, `NoDataPageLayout`, and `PageShell` are removed — pass toolbars via `actionRow` and compose the body with plain elements. (#24643)

- Improved DataList status examples and removed the standalone Badge indicator story. (#24705)

- Page status states now own their centering. `SessionExpired`, `PermissionDenied` and `ErrorState` accept `variant="fill"`, and `Spinner` accepts `fill` plus a new `size="lg"`, so call sites no longer hand-roll a `flex h-full items-center justify-center` wrapper around them. `ErrorState` is rebuilt on `EmptyState` and drops its fixed `h-[30vh]` height, which used to push the block above center inside a full-height parent. (#24643)

- Fixed the `SideDialog` code section header, where the copy button and the multiline toggle rendered at two different heights (28px next to 30px) inside their joined `ButtonsGroup`, so one segment poked out of the pill. `CopyButton` defaults to `sm` while `Button` defaults to `md`, and this header passed neither — both are now `sm`, matching the same header in `DataDetailsPanel` and `DataCodeSection`. The multiline toggle is icon-only and now carries an accessible name. (#24691)

  A `ButtonsGroup` imposes no height of its own: every segment must sit on the same rung of the control size ladder (`sm` 28px, `md` 30px, `lg` 32px), or it will poke out.

- Traces filter bar (on `/traces` and agent traces) now supports advanced AND/OR filter groups. Groups are persisted in the URL as `filterGroup` params, restored from saved filters, and sent to `queryTraces` as nested `or` / `and` predicates. `FilterBar`'s `createItemId` now only applies to root-level items so a group can hold several conditions on the same field. (#24675)

### [@mastra/quickjs@0.1.2](https://github.com/mastra-ai/mastra/blob/@mastra/quickjs@0.1.2/code-mode/quickjs/CHANGELOG.md)
#### Patch Changes

- Fix the CommonJS build of `@mastra/quickjs` so programs run. Previously every `run()` failed with `(0, ts_blank_space.default) is not a function` because the CJS bundle externalised the ESM-only `ts-blank-space`. The build now splits per format: ESM keeps `ts-blank-space` and `typescript` external, while CJS bundles them in. (#24722)

### [@mastra/s3vectors@1.1.3](https://github.com/mastra-ai/mastra/blob/@mastra/s3vectors@1.1.3/stores/s3vectors/CHANGELOG.md)
#### Patch Changes

- Fixed `deleteVectors()` throwing "not yet implemented" when called with `ids`. You can now bulk delete vectors by id; large lists are split into batches automatically. Deleting by `filter` is still not supported and throws a clear error suggesting to delete by `ids` instead. (#24752)

  ```ts
  await vectorStore.deleteVectors({ indexName: 'docs', ids: ['doc-1', 'doc-2'] });
  ```

### [@mastra/server@1.69.0](https://github.com/mastra-ai/mastra/blob/@mastra/server@1.69.0/packages/server/CHANGELOG.md)
#### Minor Changes

- Deprecated thread grouping on the advanced trace-query endpoint. Grouped requests remain supported until the next major release; use the thread-query endpoint for new integrations. (#23790)

  **Before:**

  ```ts
  await mastraClient.queryTraces({ timeRange, group: { by: ['threadId'] } });
  ```

  **After:**

  ```ts
  await mastraClient.queryTraceThreads({ traces: { timeRange } });
  ```

#### Patch Changes

- Added server schema support for serialized classifier workflow steps. (#24747)

- Preserve literal percent-encoded sequences in workspace filesystem paths. Read, write, create, and delete operations now target the exact requested file instead of decoding query and body values a second time. Closes #24620. (#24637)

- MCP server listings and details now report which transports a server offers (`streamable-http`, plus `sse` for MCP 1.x servers), so clients can tell MCP v2 servers apart without probing routes. The `transports` field is optional on the response types so clients keep working against older servers that do not send it. The MCP tool info response type also declares the optional `id` that MCP v2 servers include. (#24388)

  ```bash
  curl http://localhost:4111/api/mcp/v0/servers
  # { "servers": [{ "id": "notes", "name": "notes", "version_detail": { ... }, "transports": ["streamable-http"] }], ... }
  ```

- Fixed @mastra/server compatibility by requiring @mastra/core 1.58.0 or newer. Older versions of @mastra/core are missing functionality that @mastra/server depends on, so installing them together resulted in a broken setup rather than a clear version conflict. (#24692)

### [@mastra/weaviate@0.1.1](https://github.com/mastra-ai/mastra/blob/@mastra/weaviate@0.1.1/stores/weaviate/CHANGELOG.md)
#### Patch Changes

- Raise the `@mastra/core` peer dependency floor to `1.68.0`. Every published version of this store has shipped alongside core 1.68, but the previous `>=1.0.0-0` range advertised compatibility with 68 earlier core releases that were never tested, so a package manager installed those pairings without a peer warning. (#24676)

  If your package manager reports a `@mastra/weaviate` peer conflict, upgrade `@mastra/core` to `1.68.0` or newer.

### Other updated packages

The following packages were updated with dependency changes only:

- [@mastra/arize@1.3.18](https://github.com/mastra-ai/mastra/blob/@mastra/arize@1.3.18/observability/arize/CHANGELOG.md)
- [@mastra/arthur@0.4.18](https://github.com/mastra-ai/mastra/blob/@mastra/arthur@0.4.18/observability/arthur/CHANGELOG.md)
- [@mastra/braintrust@1.3.15](https://github.com/mastra-ai/mastra/blob/@mastra/braintrust@1.3.15/observability/braintrust/CHANGELOG.md)
- [@mastra/datadog@1.4.10](https://github.com/mastra-ai/mastra/blob/@mastra/datadog@1.4.10/observability/datadog/CHANGELOG.md)
- [@mastra/deepeval@0.1.12](https://github.com/mastra-ai/mastra/blob/@mastra/deepeval@0.1.12/observability/deepeval/CHANGELOG.md)
- [@mastra/laminar@1.3.20](https://github.com/mastra-ai/mastra/blob/@mastra/laminar@1.3.20/observability/laminar/CHANGELOG.md)
- [@mastra/langfuse@1.5.9](https://github.com/mastra-ai/mastra/blob/@mastra/langfuse@1.5.9/observability/langfuse/CHANGELOG.md)
- [@mastra/langsmith@1.3.20](https://github.com/mastra-ai/mastra/blob/@mastra/langsmith@1.3.20/observability/langsmith/CHANGELOG.md)
- [@mastra/longmemeval@1.1.28](https://github.com/mastra-ai/mastra/blob/@mastra/longmemeval@1.1.28/explorations/longmemeval/CHANGELOG.md)
- [@mastra/mcp-docs-server@1.2.28](https://github.com/mastra-ai/mastra/blob/@mastra/mcp-docs-server@1.2.28/packages/mcp-docs-server/CHANGELOG.md)
- [@mastra/opencode@0.1.28](https://github.com/mastra-ai/mastra/blob/@mastra/opencode@0.1.28/integrations/opencode/CHANGELOG.md)
- [@mastra/otel-bridge@1.5.10](https://github.com/mastra-ai/mastra/blob/@mastra/otel-bridge@1.5.10/observability/otel-bridge/CHANGELOG.md)
- [@mastra/otel-exporter@1.4.1](https://github.com/mastra-ai/mastra/blob/@mastra/otel-exporter@1.4.1/observability/otel-exporter/CHANGELOG.md)
- [@mastra/posthog@1.3.12](https://github.com/mastra-ai/mastra/blob/@mastra/posthog@1.3.12/observability/posthog/CHANGELOG.md)
- [@mastra/sentry@1.2.20](https://github.com/mastra-ai/mastra/blob/@mastra/sentry@1.2.20/observability/sentry/CHANGELOG.md)

Related Repositories