github mastra-ai/mastra @mastra/core@1.65.0
September 9, 2026

5 hours ago

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

Minor Changes

  • Added tenant-scoped trace deletion arguments for observability storage with a limit of 1,000 trace IDs per batch. (#22553)

    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)

    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)

    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.

    // 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)

    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:

    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)

    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)

    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 #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)

    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.

    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:

    const workflow = createWorkflow({
      id: 'my-workflow',
      inputSchema,
      outputSchema,
      options: {
        // Exclude this workflow from Mastra.restartAllActiveWorkflowRuns()
        autoRestartActiveRuns: false,
      },
    });

@mastra/ai-sdk@1.10.2

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

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)

    await observabilityStore.batchDeleteTraces({ traceIds: ['trace-123'] });

@mastra/client-js@1.44.0

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)

    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)

    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

    await client.deleteDatasetExperiment(datasetId, experimentId, {
      organizationId,
      projectId,
    });

    Delete any experiment, including orphaned experiments whose dataset was already deleted

    await client.deleteExperiment(experimentId);
  • Added updateDatasetExperiment() to rename an experiment or change its description and metadata. (#22924)

    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)

    const { liveKitConnectionRouteEnabled } = await client.getSystemPackages();
  • Regenerated route types: workflow step-graph entries now expose optional id, description, and metadata fields. (#22633)

    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

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)

    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)

    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

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

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

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

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

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.

    // 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:

    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)

    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:

    // 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:

    # 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)

    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:

    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)

    // 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)

    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:

    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)

    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:

    // 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:

    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:

    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)

    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 👍", 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

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

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

Patch Changes

  • Preserved status, headers, and body content from explicitly attached HTTP exception responses. (#22728)

@mastra/langfuse@1.5.5

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

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)

    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

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:

    new ObservationalMemory({
      observation: {
        observeAttachments: true,
      },
    });

@mastra/mysql@0.8.6

Patch Changes

  • Update README to include docs link (#22965)

@mastra/next@0.2.23

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

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 #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

Patch Changes

  • Fixed trace deletion to remove trace-linked scores while preserving score records without a trace ID. (#22553)

@mastra/pg@1.23.0

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)

    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):

    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

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)

    # 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

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.

    // 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.

    // 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)

    <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)

    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.

    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)

    <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)

    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)

    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.
    // 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

    <DataList columns="1fr" variant="striped" stickyHeaderBackground="tinted">
      {rows}
    </DataList>

    After

    <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)

    <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.

    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.

    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

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 #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

Patch Changes

  • Added additive system context support to React agent requests so per-turn state can preserve configured agent instructions. (#22315)

    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

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:

    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

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

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

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

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:

    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.

    # 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)

    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 #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)

    {
      "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)

    const { liveKitConnectionRouteEnabled } = await fetch('/api/system/packages').then(res => res.json());

@mastra/spanner@1.6.5

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

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

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

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:

Don't miss a new mastra release

NewReleases is sending notifications on new releases.