@mastra/client-js 1.47.0-alpha.1 → 1.47.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/_types/@ai-sdk_provider/package.json +1 -0
  2. package/dist/_types/@ai-sdk_provider-utils/dist/index.d.ts +1 -1
  3. package/dist/_types/@ai-sdk_provider-utils/package.json +1 -0
  4. package/dist/_types/@ai-sdk_ui-utils/dist/index.d.ts +3 -3
  5. package/dist/_types/@ai-sdk_ui-utils/package.json +1 -0
  6. package/dist/client.d.ts +64 -208
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/docs/SKILL.md +2 -2
  9. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  10. package/dist/docs/references/docs-connections-a2a.md +4 -3
  11. package/dist/docs/references/docs-harness-agent-controller.md +4 -2
  12. package/dist/docs/references/reference-client-js-agent-controller.md +77 -16
  13. package/dist/docs/references/reference-client-js-agents.md +25 -0
  14. package/dist/docs/references/reference-client-js-mastra-client.md +1 -1
  15. package/dist/docs/references/reference-client-js-observability.md +76 -1
  16. package/dist/docs/references/reference-observability-tracing-trace-query.md +188 -17
  17. package/dist/index.cjs +357 -172
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +3 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +358 -173
  22. package/dist/index.js.map +1 -1
  23. package/dist/resources/a2a.d.ts +5 -1
  24. package/dist/resources/a2a.d.ts.map +1 -1
  25. package/dist/resources/agent-builder.d.ts +25 -40
  26. package/dist/resources/agent-builder.d.ts.map +1 -1
  27. package/dist/resources/agent-controller.d.ts +5 -4
  28. package/dist/resources/agent-controller.d.ts.map +1 -1
  29. package/dist/resources/agent.d.ts +46 -74
  30. package/dist/resources/agent.d.ts.map +1 -1
  31. package/dist/resources/base.d.ts.map +1 -1
  32. package/dist/resources/channels.d.ts +13 -31
  33. package/dist/resources/channels.d.ts.map +1 -1
  34. package/dist/resources/conversations.d.ts +6 -3
  35. package/dist/resources/conversations.d.ts.map +1 -1
  36. package/dist/resources/mcp-tool.d.ts +9 -6
  37. package/dist/resources/mcp-tool.d.ts.map +1 -1
  38. package/dist/resources/memory-thread.d.ts +11 -11
  39. package/dist/resources/memory-thread.d.ts.map +1 -1
  40. package/dist/resources/observability-route-types.d.ts +101 -0
  41. package/dist/resources/observability-route-types.d.ts.map +1 -0
  42. package/dist/resources/observability.d.ts +47 -9
  43. package/dist/resources/observability.d.ts.map +1 -1
  44. package/dist/resources/responses.d.ts +5 -2
  45. package/dist/resources/responses.d.ts.map +1 -1
  46. package/dist/resources/run.d.ts +21 -57
  47. package/dist/resources/run.d.ts.map +1 -1
  48. package/dist/resources/tool.d.ts +6 -4
  49. package/dist/resources/tool.d.ts.map +1 -1
  50. package/dist/resources/vector.d.ts +5 -10
  51. package/dist/resources/vector.d.ts.map +1 -1
  52. package/dist/resources/workflow.d.ts +8 -10
  53. package/dist/resources/workflow.d.ts.map +1 -1
  54. package/dist/route-types.generated.d.ts +3029 -2246
  55. package/dist/route-types.generated.d.ts.map +1 -1
  56. package/dist/types.d.ts +252 -1593
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/utils/index.d.ts +7 -0
  59. package/dist/utils/index.d.ts.map +1 -1
  60. package/package.json +6 -6
@@ -3,7 +3,7 @@ name: mastra-client-js
3
3
  description: Documentation for @mastra/client-js. Use when working with @mastra/client-js APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/client-js"
6
- version: "1.47.0-alpha.1"
6
+ version: "1.47.0-alpha.11"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -55,7 +55,7 @@ Read the individual reference documents for detailed explanations and code examp
55
55
  - [Reference: Versioning](references/reference-editor-versioning.md) - Editor versions stored agents and prompt blocks. Database-backed resources use draft and publish operations.
56
56
  - [Client SDK](references/reference-migrations-upgrade-to-v1-client.md) - Client SDK changes align with server-side API updates, including utility renames, updated pagination, and type naming conventions.
57
57
  - [Reference: Metric queries](references/reference-observability-metrics-queries.md) - Metric queries read raw and aggregated metric data from the observability storage domain. For exporter and storage setup, see the Metrics overview.
58
- - [Advanced trace queries](references/reference-observability-tracing-trace-query.md) - Query completed traces and thread identities with recursive predicates, related records, ordering, and cursor pagination.
58
+ - [Advanced trace queries](references/reference-observability-tracing-trace-query.md) - Query completed traces and thread identities with recursive predicates, numbered pages, keyset cursors, and delta polling.
59
59
 
60
60
 
61
61
  Read [assets/SOURCE_MAP.json](assets/SOURCE_MAP.json) for source code references.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.47.0-alpha.1",
2
+ "version": "1.47.0-alpha.11",
3
3
  "package": "@mastra/client-js",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -67,7 +67,7 @@ Mastra supports A2A Protocol v0.3 and v1.0 on the same agent card and execution
67
67
  - `1.0`: Uses the v1.0 API.
68
68
  - Any other value: Returns a `VersionNotSupported` protocol error.
69
69
 
70
- Existing `A2AAgent` and `MastraClient.getA2A()` integrations continue to use v0.3. Use `MastraClient.getA2AV1()` for v1.0 requests. The v1 client sends `A2A-Version: 1.0` automatically and adds the `tasks/list` operation.
70
+ `A2AAgent` and `MastraClient.getA2A()` use v0.3 by default. Set `protocolVersion: '1.0'` on `A2AAgent` for v1.0 subagent delegation, or use `MastraClient.getA2AV1()` for direct v1.0 requests. Both v1.0 clients send `A2A-Version: 1.0` automatically. The direct v1.0 client also adds the `tasks/list` operation.
71
71
 
72
72
  Import v1.0 protocol types and codecs from `@mastra/core/a2a/v1`. The existing `@mastra/core/a2a/client` export remains on v0.3.
73
73
 
@@ -82,7 +82,7 @@ Use `A2AAgent` when another Mastra agent should delegate work to a remote agent.
82
82
 
83
83
  ## Consume A2A agents as subagents
84
84
 
85
- Use `A2AAgent` to wrap a remote A2A agent, then add it to a parent agent with the [supervisor agents](https://mastra.ai/docs/subagents) pattern. Pass an explicit agent card URL when the remote server hosts multiple agents or uses a custom well-known path.
85
+ Use `A2AAgent` to wrap a remote A2A agent, then add it to a parent agent with the [supervisor agents](https://mastra.ai/docs/subagents) pattern. Pass an explicit agent card URL when the remote server hosts multiple agents or uses a custom well-known path. Set `protocolVersion: '1.0'` when the remote agent requires A2A v1.0. Omit it to use v0.3.
86
86
 
87
87
  ```typescript
88
88
  import { Agent } from '@mastra/core/agent'
@@ -90,6 +90,7 @@ import { A2AAgent } from '@mastra/core/a2a'
90
90
 
91
91
  const remoteWeatherAgent = new A2AAgent({
92
92
  url: 'https://weather.example.com/api/.well-known/weather-agent/agent-card.json',
93
+ protocolVersion: '1.0',
93
94
  headers: {
94
95
  Authorization: `Bearer ${process.env.WEATHER_AGENT_TOKEN}`,
95
96
  },
@@ -112,7 +113,7 @@ During execution, `A2AAgent`:
112
113
 
113
114
  - Fetches and caches the remote agent card.
114
115
  - Reads the execution URL and capabilities from the card.
115
- - Calls `message/send` for non-streaming runs or `message/stream` when streaming is supported.
116
+ - Calls `message/send` or `message/stream` with v0.3, and `SendMessage` or `SendStreamingMessage` with v1.0.
116
117
  - Converts remote messages, tasks, artifacts, and status updates into Mastra subagent results.
117
118
  - Supports `resumeGenerate()` and `resumeStream()` when the remote task requires follow-up input or resubscription.
118
119
 
@@ -74,8 +74,8 @@ const session = await controller.createSession({
74
74
  })
75
75
 
76
76
  const unsubscribe = session.subscribe(event => {
77
- if (event.type === 'message_update') {
78
- console.log(event.message)
77
+ if (event.type === 'message_update' && event.event.type === 'text-delta') {
78
+ process.stdout.write(event.event.delta)
79
79
  }
80
80
  })
81
81
 
@@ -83,6 +83,8 @@ await session.sendMessage({ content: 'Plan a small TypeScript CLI.' })
83
83
  unsubscribe()
84
84
  ```
85
85
 
86
+ Each message emits a `message_start` event with the initial message, zero or more `message_update` events with compact deltas, and a `message_end` event containing the message ID. Apply updates by ID when you need to reconstruct the complete message.
87
+
86
88
  Use the same controller for many Sessions. Don't store a current Session on the controller or route work through controller-level message methods.
87
89
 
88
90
  ## Understand the runtime model
@@ -219,31 +219,92 @@ Returns: `Promise<SendNotificationResult>`
219
219
 
220
220
  `onEvent` receives every event the session emits, discriminated by `event.type`:
221
221
 
222
- | Group | Events |
223
- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
- | Run | `agent_start`, `agent_end`, `usage_update`, `goal_evaluation`, `follow_up_queued` |
225
- | Messages | `message_start`, `message_update`, `message_end` |
226
- | Tools | `tool_input_start`, `tool_input_delta`, `tool_input_end`, `tool_start`, `tool_update`, `tool_end`, `shell_output`, `command_exit`, `tool_approval_required`, `tool_suspended`, `tool_suspension_cancelled`, `task_updated` |
227
- | Session | `state_changed`, `display_state_changed`, `mode_changed`, `model_changed`, `thread_changed`, `thread_created`, `thread_deleted`, `thread_title_updated` |
228
- | Subagents | `subagent_start`, `subagent_text_delta`, `subagent_tool_start`, `subagent_tool_end`, `subagent_end`, `subagent_model_changed` |
229
- | Memory | `om_observation_start`, `om_observation_end`, `om_observation_failed`, `om_reflection_start`, `om_reflection_end`, `om_reflection_failed`, `om_buffering_start`, `om_buffering_end`, `om_buffering_failed`, `om_model_changed`, `om_activation`, `om_status`, `om_thread_title_updated` |
230
- | Workspace | `workspace_ready`, `workspace_error`, `workspace_status_changed` |
231
- | Notification | `notification`, `notification_summary`, `info`, `error` |
222
+ | Group | Events |
223
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
+ | Run | `agent_start`, `agent_end`, `usage_update`, `goal_evaluation`, `follow_up_queued` |
225
+ | Messages | `message_start`, `message_update`, `message_end` |
226
+ | Tools | `tool_input_start`, `tool_input_delta`, `tool_input_end`, `tool_start`, `tool_update`, `tool_end`, `shell_output`, `command_exit`, `tool_approval_required`, `tool_suspended`, `tool_suspension_cancelled`, `task_updated` |
227
+ | Session | `state_changed`, `display_state_changed`, `mode_changed`, `model_changed`, `thread_changed`, `thread_created`, `thread_deleted`, `thread_title_updated` |
228
+ | Subagents | `subagent_start`, `subagent_text_delta`, `subagent_tool_start`, `subagent_tool_end`, `subagent_end`, `subagent_model_changed` |
229
+ | Memory | `om_observation_start`, `om_observation_end`, `om_observation_failed`, `om_reflection_start`, `om_reflection_end`, `om_reflection_failed`, `om_buffering_start`, `om_buffering_end`, `om_buffering_failed`, `om_model_changed`, `om_activation`, `om_status`, `om_thread_title_updated` |
230
+ | Workspace | `workspace_ready`, `workspace_error`, `workspace_status_changed` |
231
+ | Diagnostics | `info`, `error` |
232
232
 
233
- `message_*` events carry a `MastraDBMessage` and `thread_created` carries a thread, with timestamps hydrated to `Date`.
233
+ Notifications are delivered as agent signals carried on messages rather than as `notification` or `notification_summary` controller events.
234
234
 
235
- A controller can also emit events the SDK doesn't type. `AgentControllerEvent` is the union of `KnownAgentControllerEvent` and `OtherAgentControllerEvent`. Because `OtherAgentControllerEvent.type` is `string`, comparing `event.type` to a literal doesn't narrow the union. Narrow with `isKnownAgentControllerEvent(event)` first:
235
+ A message lifecycle uses three event shapes:
236
+
237
+ - `message_start` carries the initial `MastraDBMessage`, with `createdAt` hydrated to `Date`.
238
+ - `message_update` carries the message `id` and a compact text, reasoning, or part update.
239
+ - `message_end` carries the `id` of the completed message.
240
+
241
+ `thread_created` also carries a thread with timestamps hydrated to `Date`.
242
+
243
+ A controller can emit events the SDK doesn't type. `AgentControllerEvent` is the union of `KnownAgentControllerEvent` and `OtherAgentControllerEvent`. Because `OtherAgentControllerEvent.type` is `string`, comparing `event.type` to a literal doesn't narrow the union. Narrow with `isKnownAgentControllerEvent(event)` first, then reconstruct messages by ID:
236
244
 
237
245
  ```typescript
238
- import { isKnownAgentControllerEvent } from '@mastra/client-js'
246
+ import {
247
+ isKnownAgentControllerEvent,
248
+ type AgentControllerEvent,
249
+ type KnownAgentControllerEvent,
250
+ type MastraDBMessage,
251
+ } from '@mastra/client-js'
252
+
253
+ type MessageUpdate = Extract<KnownAgentControllerEvent, { type: 'message_update' }>['event']
254
+
255
+ const activeMessages = new Map<string, MastraDBMessage>()
256
+
257
+ function applyUpdate(message: MastraDBMessage, update: MessageUpdate): MastraDBMessage {
258
+ const parts = [...message.content.parts]
259
+
260
+ if (update.type === 'text-delta') {
261
+ const index = parts.findLastIndex(part => part.type === 'text')
262
+ const part = parts[index]
263
+
264
+ if (part?.type === 'text') {
265
+ parts[index] = { ...part, text: part.text + update.delta }
266
+ } else {
267
+ parts.push({ type: 'text', text: update.delta })
268
+ }
269
+ } else if (update.type === 'reasoning-delta') {
270
+ const part = parts[update.index]
271
+ const reasoning = part?.type === 'reasoning' ? part.reasoning + update.delta : update.delta
272
+ parts[update.index] = {
273
+ ...(part?.type === 'reasoning' ? part : { type: 'reasoning' as const }),
274
+ reasoning,
275
+ details: [{ type: 'text', text: reasoning }],
276
+ }
277
+ } else {
278
+ parts[update.index] = update.part
279
+ }
280
+
281
+ return { ...message, content: { ...message.content, parts } }
282
+ }
239
283
 
240
284
  function handleEvent(event: AgentControllerEvent) {
241
285
  if (!isKnownAgentControllerEvent(event)) return
242
286
 
243
287
  switch (event.type) {
244
- case 'message_update':
245
- render(event.message)
288
+ case 'message_start':
289
+ activeMessages.set(event.message.id, structuredClone(event.message))
290
+ break
291
+ case 'message_update': {
292
+ const message = activeMessages.get(event.id)
293
+ if (!message) break
294
+
295
+ const updated = applyUpdate(message, event.event)
296
+ activeMessages.set(event.id, updated)
297
+ render(updated)
298
+ break
299
+ }
300
+ case 'message_end': {
301
+ const message = activeMessages.get(event.id)
302
+ if (!message) break
303
+
304
+ renderComplete(message)
305
+ activeMessages.delete(event.id)
246
306
  break
307
+ }
247
308
  case 'tool_approval_required':
248
309
  showApproval(event.toolCallId)
249
310
  break
@@ -251,7 +312,7 @@ function handleEvent(event: AgentControllerEvent) {
251
312
  }
252
313
  ```
253
314
 
254
- Use `agentControllerMessageText(message)` to pull the plain text out of a message's nested content parts.
315
+ Use `agentControllerMessageText(message)` to pull the plain text out of a reconstructed message's nested content parts.
255
316
 
256
317
  ## Related
257
318
 
@@ -119,6 +119,31 @@ while (true) {
119
119
  }
120
120
  ```
121
121
 
122
+ #### Cancelling a stream
123
+
124
+ Cancelling the response body aborts the underlying HTTP request and stops any pending client tool executions and follow-up requests:
125
+
126
+ ```typescript
127
+ const response = await agent.stream('Tell me a story')
128
+ const reader = response.body.getReader()
129
+
130
+ const { value } = await reader.read()
131
+ await reader.cancel('user navigated away')
132
+ ```
133
+
134
+ You can also pass an `abortSignal` to cancel from outside the stream. The same option is available on `streamUntilIdle()`, `resumeStream()`, `resumeStreamUntilIdle()`, `approveToolCall()`, `declineToolCall()`, `generate()`, and `generateLegacy()`:
135
+
136
+ ```typescript
137
+ const controller = new AbortController()
138
+
139
+ const response = await agent.stream('Tell me a story', {
140
+ abortSignal: controller.signal,
141
+ })
142
+
143
+ // Later
144
+ controller.abort()
145
+ ```
146
+
122
147
  #### AI SDK compatible format
123
148
 
124
149
  To stream AI SDK-formatted parts on the client from an `agent.stream(...)` response, wrap `response.processDataStream` into a `ReadableStream<ChunkType>` and use `toAISdkStream`:
@@ -81,7 +81,7 @@ You can also pass `requestContext` as a `Record<string, any>`.
81
81
 
82
82
  **getWorkflow(workflowId)** (`Workflow`): Retrieves a specific workflow instance by ID.
83
83
 
84
- **getAgentBuilderActions()** (`Promise<Record<string, WorkflowInfo>>`): Returns all available Agent Builder actions. See Agent Builder API.
84
+ **getAgentBuilderActions()** (`Promise<GetAgentBuilderActionsResponse>`): Returns all available Agent Builder actions. See Agent Builder API.
85
85
 
86
86
  **getAgentBuilderAction(actionId)** (`AgentBuilder`): Retrieves an Agent Builder action by ID. See Agent Builder API.
87
87
 
@@ -89,6 +89,79 @@ const result = await mastraClient.queryTraces({
89
89
  })
90
90
  ```
91
91
 
92
+ ### Load a page and poll for traces
93
+
94
+ `queryTraces()` supports keyset traversal with `page`, numbered pages with `pagination`, and delta polling with `mode: 'delta'`. Use one mode per request. To migrate from `listTracesLight()`, read `traces` instead of `spans` and retain the numbered page's `deltaCursor`:
95
+
96
+ ```typescript
97
+ const timeRange = {
98
+ from: '2026-08-01T00:00:00.000Z',
99
+ to: '2026-08-08T00:00:00.000Z',
100
+ }
101
+ const initial = await mastraClient.queryTraces({
102
+ timeRange,
103
+ pagination: { page: 0, perPage: 100 },
104
+ })
105
+ if (!initial.deltaCursor) throw new Error('Delta polling is unavailable')
106
+
107
+ const changes = await mastraClient.queryTraces({
108
+ timeRange,
109
+ mode: 'delta',
110
+ after: initial.deltaCursor,
111
+ limit: 100,
112
+ })
113
+ ```
114
+
115
+ Merge `changes.traces` by `traceId`, retain `changes.deltaCursor`, and continue while `changes.delta.hasMore` is `true`. Keep the same time range and predicate across polls. Only completed traces are returned; polling doesn't guarantee notifications for related-record updates, deletions, or traces that stop matching.
116
+
117
+ See [Delta polling](https://mastra.ai/reference/observability/tracing/trace-query) for bootstrap behavior, cursor lifetime, retention, and a complete polling loop. `queryTraceThreads()` remains keyset-only.
118
+
119
+ ### Discover trace-query fields and values
120
+
121
+ `getTraceQueryFields()` returns canonical query fields and observed top-level string metadata fields for one predicate scope. The response includes each field's value kind, supported operators, and whether value suggestions are available.
122
+
123
+ ```typescript
124
+ const timeRange = {
125
+ from: '2026-08-01T00:00:00.000Z',
126
+ to: '2026-08-08T00:00:00.000Z',
127
+ }
128
+
129
+ const fields = await mastraClient.getTraceQueryFields({
130
+ timeRange,
131
+ predicateScope: 'spans',
132
+ search: 'model',
133
+ limit: 25,
134
+ })
135
+ ```
136
+
137
+ Use the exact local path from the response with the same scope. For example, `model` belongs inside a `spans.some` or `spans.none` predicate. A global picker can fetch `trace`, `spans`, `scores`, and `feedback` in parallel.
138
+
139
+ Call `getTraceQueryValues()` only after selecting a field with `valueSuggestions: true`:
140
+
141
+ ```typescript
142
+ const controller = new AbortController()
143
+
144
+ const values = await mastraClient.getTraceQueryValues(
145
+ {
146
+ timeRange,
147
+ predicateScope: 'spans',
148
+ path: 'model',
149
+ search: 'claude',
150
+ limit: 25,
151
+ },
152
+ { signal: controller.signal },
153
+ )
154
+
155
+ // Cancel this autocomplete request when the search text changes.
156
+ controller.abort()
157
+ ```
158
+
159
+ Both methods use case-insensitive literal substring search and accept an empty search. Limits default to 25 and can't exceed 100. Results are ordered by occurrence count, then deterministically by path or value. `observedFieldsTruncated` and `valuesTruncated` indicate that the caller should refine `search`. Discovery has no cursor.
160
+
161
+ Suggestions are bounded and advisory. Manual values remain valid when suggestions are unavailable, empty, or truncated. Discovery doesn't accept a draft query predicate. The client doesn't retry either request, and a per-call `AbortSignal` takes precedence over the signal configured on `MastraClient`.
162
+
163
+ A discovery timeout rejects with `504 TRACE_QUERY_EXECUTION_TIMEOUT`. Backend memory or resource exhaustion rejects with `503 TRACE_QUERY_RESOURCE_LIMIT`. Neither error contains partial suggestions; truncation flags are only present on successful, completely ranked responses.
164
+
92
165
  `queryTraceThreads()` returns thread identities derived from observability traces after applying eligibility and cross-trace conditions. It doesn't read or return memory thread records or messages. In this example, one production trace can have the low factuality score while another production trace in the same thread has the clinician correction:
93
166
 
94
167
  ```typescript
@@ -187,7 +260,7 @@ const scores = await mastraClient.listScoresBySpan({
187
260
 
188
261
  ## Feedback
189
262
 
190
- Feedback methods create, list, and query human-in-the-loop signals such as ratings, thumbs, comments, and corrections through the target Mastra runtime and its configured observability storage. They don't call the hosted Mastra Platform feedback query API. See the [feedback guide](https://mastra.ai/docs/observability/feedback) for examples and the [feedback reference](https://mastra.ai/reference/observability/feedback) for full schemas.
263
+ Feedback methods create, list, and query human-in-the-loop signals such as ratings, thumbs, comments, and corrections through the target Mastra runtime and its configured observability storage. They don't call the hosted Mastra Platform Feedback API. See the [feedback guide](https://mastra.ai/docs/observability/feedback) for examples and the [feedback reference](https://mastra.ai/reference/observability/feedback) for full schemas.
191
264
 
192
265
  ### Creating feedback
193
266
 
@@ -239,6 +312,8 @@ const feedback = await mastraClient.listFeedback({
239
312
  })
240
313
  ```
241
314
 
315
+ `filters` accepts every [`FeedbackFilter`](https://mastra.ai/reference/observability/feedback) field. The client sends them as query parameters on `GET /api/observability/feedback`. See [list query parameters](https://mastra.ai/reference/observability/feedback).
316
+
242
317
  ### Aggregating feedback
243
318
 
244
319
  Aggregate numeric feedback values, such as ratings or thumbs encoded as `1` and `-1`:
@@ -132,16 +132,120 @@ curl --request POST \
132
132
  }'
133
133
  ```
134
134
 
135
+ ## Discover fields and values
136
+
137
+ Use trace-query discovery to build field and value autocomplete without scanning trace payloads or copying the query grammar into your client. The configured observability store must support `trace-query-discovery`.
138
+
139
+ Call `getTraceQueryFields()` for one predicate scope:
140
+
141
+ ```typescript
142
+ const timeRange = {
143
+ from: '2026-08-01T00:00:00.000Z',
144
+ to: '2026-08-08T00:00:00.000Z',
145
+ }
146
+
147
+ const fields = await mastraClient.getTraceQueryFields({
148
+ timeRange,
149
+ predicateScope: 'trace',
150
+ search: 'region',
151
+ limit: 25,
152
+ })
153
+ ```
154
+
155
+ The response separates canonical fields from observed metadata fields:
156
+
157
+ ```json
158
+ {
159
+ "canonicalFields": [],
160
+ "observedFields": [
161
+ {
162
+ "path": "metadata.region",
163
+ "valueKind": "string",
164
+ "operators": ["eq", "ne", "in", "notIn", "exists", "notExists"],
165
+ "valueSuggestions": true,
166
+ "occurrences": 125
167
+ }
168
+ ],
169
+ "observedFieldsTruncated": false
170
+ }
171
+ ```
172
+
173
+ Canonical fields come from the same registry used to validate trace queries. They don't consume `limit`. The canonical order follows that registry. `observedFields` contains top-level string `metadata.<key>` paths found on qualifying current root spans. These fields are ordered by `occurrences` descending, then by path. Nested metadata, keys containing `.`, empty keys, non-string values, and oversized paths are omitted.
174
+
175
+ `predicateScope` selects the local predicate grammar. It isn't an authorization scope:
176
+
177
+ | `predicateScope` | Example returned path | Query usage |
178
+ | ---------------- | ---------------------------------- | ----------------------------------------- |
179
+ | `trace` | `environment` or `metadata.region` | Root `where` predicate |
180
+ | `spans` | `model` | Inside `spans.some` or `spans.none` |
181
+ | `scores` | `scorerId` | Inside `scores.some` or `scores.none` |
182
+ | `feedback` | `feedbackType` | Inside `feedback.some` or `feedback.none` |
183
+
184
+ A global field picker can request all four scopes in parallel and qualify display labels locally. Every returned path is directly accepted by `queryTraces()` in its matching scope. `metadata` by itself isn't a field. A user interface can offer manual metadata-key entry separately.
185
+
186
+ After a user selects one field with `valueSuggestions: true`, call `getTraceQueryValues()`:
187
+
188
+ ```typescript
189
+ const abortController = new AbortController()
190
+ const suggestions = await mastraClient.getTraceQueryValues(
191
+ {
192
+ timeRange,
193
+ predicateScope: 'trace',
194
+ path: 'metadata.region',
195
+ search: 'west',
196
+ limit: 25,
197
+ },
198
+ { signal: abortController.signal },
199
+ )
200
+ ```
201
+
202
+ ```json
203
+ {
204
+ "values": [
205
+ { "value": "eu-west-1", "count": 82 },
206
+ { "value": "us-west-2", "count": 43 }
207
+ ],
208
+ "valuesTruncated": false
209
+ }
210
+ ```
211
+
212
+ Discovery returns string values only. Missing values, empty strings, and strings larger than 4,096 UTF-8 bytes are omitted. Values are ordered by `count` descending, then by value. Suggestions are advisory: `queryTraces()` continues to accept valid manual literals that discovery doesn't return.
213
+
214
+ Value suggestions are available for these canonical fields:
215
+
216
+ | Scope | Fields |
217
+ | -------- | ----------------------------------------------------------------------------- |
218
+ | Trace | `entityName`, `entityType`, `environment`, `status` |
219
+ | Spans | `name`, `spanType`, `model`, `provider`, `status`, `entityType`, `entityName` |
220
+ | Scores | `scorerId`, `scorerVersion`, `scoreSource` |
221
+ | Feedback | `feedbackType`, `feedbackSource` |
222
+
223
+ Each executable top-level string `metadata.<key>` field also supports value suggestions in the `trace` scope. Identifiers, timestamps, durations, numeric score or feedback values, comments, errors, and version IDs don't. The fields response still includes canonical fields with `valueSuggestions: false`. The values endpoint rejects those paths with `422 TRACE_QUERY_INVALID`.
224
+
225
+ Both discovery requests require a half-open time range of at most 31 days. They use the same current-record semantics as `queryTraces()`: qualifying completed roots satisfy `from <= startedAt < to`, and related values come only from current records joined to those roots by `traceId`. Discovery doesn't accept or apply a draft `where` predicate.
226
+
227
+ `search` defaults to the empty string, is trimmed, and matches case-insensitive literal substrings. Characters such as `%` and `_` have no wildcard meaning. An empty search returns the most frequent results. `limit` defaults to 25 and has a maximum of 100. Both endpoints fetch one extra result to set their truncation flag, but don't provide a cursor. When a flag is `true`, refine `search`. It doesn't imply that a next page is available. No matches return an empty array and `false` truncation.
228
+
229
+ If discovery exceeds its execution timeout, the route returns `504 TRACE_QUERY_EXECUTION_TIMEOUT`. If the backend exceeds a memory or resource budget, it returns `503 TRACE_QUERY_RESOURCE_LIMIT`. Neither failure returns partial suggestions or sets a truncation flag. Truncation only means a complete ranking was cut to the requested response limit.
230
+
231
+ ClickHouse discovery requests default to a 5-second timeout and a 256 MiB per-query memory limit. Configure these with `observability.traceQuery.discovery.timeoutMs` and `observability.traceQuery.discovery.memoryLimitBytes` on `ClickhouseStoreVNext`. Discovery falls back to `observability.traceQuery.timeoutMs` when its timeout isn't configured, without changing ordinary `queryTraces()` execution limits.
232
+
233
+ Both routes require `observability:read` and use the configured observability storage boundary. Don't send organization or project authorization fields in the request. Treat discovered paths, values, and counts as observability data.
234
+
135
235
  ## Request fields
136
236
 
137
237
  ### Trace queries
138
238
 
139
- | Field | Required | Description |
140
- | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
141
- | `timeRange` | Yes | Trace start-time boundary. `from` is inclusive and `to` is exclusive. Both values must be ISO timestamps, `from` must be earlier than `to`, and the range can't exceed 31 days. |
142
- | `where` | No | Recursive trace predicate. Supports scalar conditions and `spans`, `scores`, and `feedback` `some` or `none` clauses. |
143
- | `orderBy` | No | One item ordering results by `startedAt` or `endedAt`, in `asc` or `desc` order. Defaults to `startedAt desc`. |
144
- | `page` | No | `{ limit, after }`. `limit` defaults to 100 and has a maximum of 1000. Pass the opaque `page.next` value as `after`. |
239
+ | Field | Required | Description |
240
+ | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
241
+ | `timeRange` | Yes | Trace start-time boundary. `from` is inclusive and `to` is exclusive. Both values must be ISO timestamps, `from` must be earlier than `to`, and the range can't exceed 31 days. |
242
+ | `where` | No | Recursive trace predicate. Supports scalar conditions and `spans`, `scores`, and `feedback` `some` or `none` clauses. |
243
+ | `orderBy` | No | One item ordering results by `startedAt` or `endedAt`, in `asc` or `desc` order. Defaults to `startedAt desc`. Not accepted in delta mode. |
244
+ | `page` | No | `{ limit, after }`. `limit` defaults to 100 and has a maximum of 1000. Pass the opaque `page.next` value as `after`. |
245
+ | `pagination` | No | Numbered pages: `{ page, perPage }`. Defaults to page 0 and 10 results. `perPage` has a maximum of 100. |
246
+ | `mode` | No | Set to `'delta'` to poll using a delta cursor instead of `page` or `pagination`. |
247
+ | `after` | No | Delta mode only. Opaque `deltaCursor` from a numbered page or previous poll. Omit to establish the current watermark. |
248
+ | `limit` | No | Delta mode only. Maximum results per batch. Defaults to 10 and has a maximum of 100. |
145
249
 
146
250
  ### Thread queries
147
251
 
@@ -347,7 +451,7 @@ const messageTrace = {
347
451
 
348
452
  The key must name one top-level property. Empty keys and nested paths are rejected. Metadata keys aren't trimmed, so leading and trailing whitespace remains part of the exact key identity. When metadata duplicates a canonical trace field, such as `resourceId`, `threadId`, or `environment`, prefer the canonical field because it uses the dedicated storage column. The `metadata.<key>` form remains available when you specifically need the value from the metadata object. Metadata and canonical values may differ.
349
453
 
350
- Metadata fields aren't available for grouping or field discovery.
454
+ Metadata fields aren't available for grouping. Trace-query discovery returns executable top-level string metadata fields observed in the selected time range.
351
455
 
352
456
  ### Filter by feedback
353
457
 
@@ -425,26 +529,93 @@ Related evidence isn't embedded in either response. Use the trace-detail and bra
425
529
 
426
530
  ## Pagination and errors
427
531
 
532
+ ### Choose a pagination mode
533
+
534
+ Trace queries support three mutually exclusive modes:
535
+
536
+ | Mode | Request fields | Response metadata |
537
+ | ------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
538
+ | Keyset, the default | `page: { limit, after }` | `page: { next }` |
539
+ | Numbered pages | `pagination: { page, perPage }` | `pagination: { total, page, perPage, hasMore }` and, when delta polling is supported, `deltaCursor` |
540
+ | Delta polling | `mode: 'delta'`, optional top-level `after` and `limit` | `delta: { limit, hasMore }` and `deltaCursor` |
541
+
542
+ Numbered pages are zero-based. Their total and rows are read consistently within each request. Don't combine `page`, `pagination`, or delta mode. Top-level `after` and `limit` are accepted only in delta mode. Thread queries and grouped compatibility queries remain keyset-only.
543
+
544
+ ### Poll after loading a numbered page
545
+
546
+ Use the numbered response's `deltaCursor` to replace the numbered-page-to-delta-polling workflow of `listTracesLight()`. Results use `traces` instead of `spans` and contain completed traces only. The configured store must support delta polling.
547
+
548
+ ```typescript
549
+ const timeRange = {
550
+ from: '2026-08-01T00:00:00.000Z',
551
+ to: '2026-08-08T00:00:00.000Z',
552
+ }
553
+ const initial = await mastraClient.queryTraces({
554
+ timeRange,
555
+ pagination: { page: 0, perPage: 100 },
556
+ })
557
+ if (!initial.deltaCursor) throw new Error('Delta polling is unavailable')
558
+
559
+ const traces = new Map(initial.traces.map(trace => [trace.traceId, trace]))
560
+ let after = initial.deltaCursor
561
+
562
+ async function poll() {
563
+ let hasMore: boolean
564
+ do {
565
+ const result = await mastraClient.queryTraces({
566
+ timeRange,
567
+ mode: 'delta',
568
+ after,
569
+ limit: 100,
570
+ })
571
+ for (const trace of result.traces) traces.set(trace.traceId, trace)
572
+ after = result.deltaCursor
573
+ hasMore = result.delta.hasMore
574
+ } while (hasMore)
575
+ }
576
+
577
+ await poll()
578
+ ```
579
+
580
+ Retain `after` and call `poll()` again at your application's polling interval. A trace can appear in the initial page and a later batch, or in multiple batches, so merge results by `traceId`. To load historical traces omitted from the initial page, request the remaining numbered pages.
581
+
582
+ A delta request without `after` returns an empty array and establishes the current cursor. It doesn't return historical matches. Always retain the returned cursor, including for empty batches. `hasMore` means another matching trace exists beyond the batch limit. Continue immediately while it's `true`.
583
+
584
+ Delta ordering uses the storage ingestion watermark and adapter-specific tie-breakers, so matching timestamps don't make the cursor ambiguous. Don't pass `orderBy` in delta mode. Keyset and delta cursors aren't interchangeable, and delta cursors can't move between storage adapters.
585
+
586
+ Keep the same normalized `where` predicate and exact `timeRange` bounds for every poll. The range continues to select root `startedAt`, even when a trace completes later. Changing `timeRange.to` to the current time invalidates the cursor. Reload a numbered page and use its cursor whenever the selection or authorization scope changes. You can change the batch limit without restarting.
587
+
588
+ New completed roots and roots completing after the cursor can be returned. Related spans, scores, and feedback are evaluated when a root is selected, but later related-record writes don't guarantee that the trace is emitted again. Deleted traces and traces that stop matching aren't returned as removals or tombstones. Refresh numbered pages when you need to reconcile those changes.
589
+
590
+ ClickHouse polling is best effort. Its delta index and trace records use separate materialized-view tables. Concurrent queries can observe an insert in one table before another, as described in [ClickHouse's materialized-view visibility rules](https://github.com/ClickHouse/clickhouse-docs/blob/main/knowledgebase/are_materialized_views_inserted_asynchronously.mdx). A poll can advance past an index entry before the matching trace becomes visible and miss that trace in later polls. Reload numbered pages periodically to reconcile these gaps.
591
+
592
+ ClickHouse's delta index retains two days of events and doesn't backfill historical rows. Reload numbered pages after a polling interruption longer than this retention window. Delta polling isn't a durable change feed and doesn't guarantee delivery of every matching trace.
593
+
594
+ ### Keyset ordering and errors
595
+
428
596
  Ordering is deterministic. Trace ordering appends `traceId` ascending as a tie-breaker. Thread queries always use raw ordinal `threadId` ascending order. Callers can't override it.
429
597
 
430
- Cursors are bound to the operation, accepted normalized query, authorization state, and ordering. Reusing a cursor after changing the trace selection, predicates, or ordering returns `409`. Trace and thread cursors aren't interchangeable. A malformed cursor returns `400`.
598
+ Keyset cursors are bound to the operation, accepted normalized query, and ordering. Reusing a keyset cursor after changing the trace selection, predicates, or ordering returns `409`. Trace and thread cursors aren't interchangeable.
599
+
600
+ Numbered-page handoff cursors and delta cursors also bind the authorization state. Changes to the caller's roles or permissions invalidate these cursors and return `409`. If a delta poll returns `409`, reload the numbered pages and resume polling with the new `deltaCursor`. A malformed cursor returns `400`.
431
601
 
432
602
  Cursor pagination is deterministic, but it isn't a database snapshot. Traces or replacement signals written between page requests can change later pages.
433
603
 
434
604
  PostgreSQL and ClickHouse stop advanced trace and thread queries after 15 seconds by default and return `504` when the database timeout is exceeded. Set `traceQueryTimeoutMs` in the store's vNext observability configuration to an integer from 1 through 300,000 milliseconds to change the timeout. DuckDB doesn't currently provide query-scoped timeout or cancellation through its driver wrapper, so this `504` guarantee doesn't apply to DuckDB.
435
605
 
436
- | Status | Meaning |
437
- | ------ | ------------------------------------------------------------------------------------------------------------------------------------- |
438
- | `400` | Malformed JSON or malformed cursor. |
439
- | `409` | The cursor doesn't match the query. |
440
- | `413` | The request body exceeds 256 KiB. |
441
- | `422` | The JSON is well formed, but the request is structurally or semantically invalid. The response includes stable issue codes and paths. |
442
- | `501` | The configured observability store doesn't support the requested trace or thread query. |
443
- | `504` | A PostgreSQL or ClickHouse query exceeded its configured database execution timeout. |
606
+ | Status | Meaning |
607
+ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
608
+ | `400` | Malformed JSON or malformed cursor. |
609
+ | `409` | The cursor doesn't match the query. |
610
+ | `413` | The request body exceeds 256 KiB. |
611
+ | `422` | The JSON is well formed, but the request is structurally or semantically invalid. The response includes stable issue codes and paths. Discovery validation uses `TRACE_QUERY_INVALID`. |
612
+ | `501` | The installed Core or configured observability store doesn't support the requested operation. Discovery returns `TRACE_QUERY_DISCOVERY_UNSUPPORTED` unless Core provides the discovery contract and the store implements both field and value discovery. |
613
+ | `503` | Discovery exceeded a backend memory or resource budget and returned `TRACE_QUERY_RESOURCE_LIMIT`. |
614
+ | `504` | A PostgreSQL or ClickHouse query exceeded its configured database execution timeout. Discovery returns `TRACE_QUERY_EXECUTION_TIMEOUT`. |
444
615
 
445
616
  ## Limitations
446
617
 
447
- Both operations consider completed traces only. They don't support running traces, custom projections, embedded evidence, summaries, aggregations, counts, measures, or custom grouping.
618
+ Both operations consider completed traces only. They don't support running traces, custom projections, embedded evidence, summaries, aggregations, measures, or custom grouping. Numbered trace pages include a total count.
448
619
 
449
620
  ## Related
450
621