@mastra/mcp-docs-server 1.1.49-alpha.1 → 1.1.49-alpha.2

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.
@@ -1,15 +1,17 @@
1
1
  # Harness class
2
2
 
3
- **Added in:** `@mastra/core@1.5.0`
4
-
5
- > **Alpha:** The `Harness` class is in alpha stage and subject to change. It won't follow semantic versioning guarantees until it graduates from experimental status. Use with caution and expect breaking changes in minor versions.
6
- >
7
- > [Mastra Code](https://code.mastra.ai/) is the flagship implementation of the `Harness` class, showcasing how it can be used to build a powerful terminal-based coding agent with multi-model support, persistent conversations, and built-in tools.
3
+ > **Alpha:** The `Harness` feature is in alpha stage and subject to breaking changes in minor versions until it graduates from its alpha status.
8
4
 
9
5
  The `Harness` class orchestrates multiple agent modes, shared state, memory, and storage. It provides a control layer that a TUI or other UI can drive to manage threads, switch models and modes, send messages, handle tool approvals, and track events.
10
6
 
7
+ Per-conversation state — identity, the active thread, mode and model selection, run state, grants, and the display snapshot — lives on the [`Session`](https://mastra.ai/reference/harness/session), accessed through `harness.session`.
8
+
9
+ For a conceptual introduction, see the [Harness overview](https://mastra.ai/docs/harness/overview).
10
+
11
11
  ## Usage example
12
12
 
13
+ Import the `Harness` class and create a new instance with your agent, storage backend, and modes:
14
+
13
15
  ```typescript
14
16
  import { Harness } from '@mastra/core/harness'
15
17
  import { LibSQLStore } from '@mastra/libsql'
@@ -17,13 +19,19 @@ import { z } from 'zod'
17
19
 
18
20
  const harness = new Harness({
19
21
  id: 'my-coding-agent',
22
+ agent: myAgent,
20
23
  storage: new LibSQLStore({ url: 'file:./data.db' }),
21
24
  stateSchema: z.object({
22
25
  currentModelId: z.string().optional(),
23
26
  }),
24
27
  modes: [
25
- { id: 'plan', name: 'Plan', default: true, agent: planAgent },
26
- { id: 'build', name: 'Build', agent: buildAgent },
28
+ {
29
+ id: 'plan',
30
+ name: 'Plan',
31
+ metadata: { default: true },
32
+ instructions: 'Reason about changes before making them.',
33
+ },
34
+ { id: 'build', name: 'Build', transitionsTo: 'plan' },
27
35
  ],
28
36
  })
29
37
 
@@ -46,30 +54,48 @@ await harness.sendMessage({ content: 'Hello!' })
46
54
 
47
55
  **storage** (`MastraCompositeStore`): Storage backend for persistence (threads, messages, state).
48
56
 
49
- **stateSchema** (`StandardJSONSchemaV1`): Standard JSON Schema defining the shape of harness state. Used for validation and extracting defaults.
57
+ **stateSchema** (`StandardJSONSchemaV1`): Standard JSON Schema defining the shape of application state, accessed at runtime via session.state. Used for validation and extracting defaults.
50
58
 
51
59
  **initialState** (`Partial<z.infer<TState>>`): Initial state values. Must conform to the schema if provided.
52
60
 
53
61
  **memory** (`MastraMemory`): Memory configuration shared across all modes. Propagated to mode agents that don't have their own memory.
54
62
 
55
- **modes** (`HarnessMode[]`): Available agent modes. At least one mode is required. Each mode defines an agent and optional defaults.
63
+ **modes** (`HarnessMode[]`): Available agent modes. At least one mode is required. When a top-level \`agent\` is provided, each mode layers its own instructions and tool overrides on top of the shared agent.
56
64
 
57
65
  **modes.id** (`string`): Unique identifier for this mode (e.g., \`"plan"\`, \`"build"\`).
58
66
 
59
67
  **modes.name** (`string`): Human-readable name for display.
60
68
 
61
- **modes.default** (`boolean`): Whether this is the default mode when the harness starts.
69
+ **modes.default** (`boolean`): Whether this is the default mode when the harness starts. Deprecated in favor of \`metadata.default\` or the top-level \`defaultModeId\`.
62
70
 
63
71
  **modes.defaultModelId** (`string`): Default model ID for this mode (e.g., \`"anthropic/claude-sonnet-4-20250514"\`). Used when no per-mode model has been explicitly selected.
64
72
 
65
- **modes.color** (`string`): Hex color for the mode indicator (e.g., \`"#7c3aed"\`).
73
+ **modes.description** (`string`): Description surfaced in mode pickers and Studio UI.
74
+
75
+ **modes.instructions** (`string`): Additional instructions layered above the backing agent's own instructions for this mode.
76
+
77
+ **modes.transitionsTo** (`string`): Target mode ID to switch to on \`submit\_plan\` approval. Must reference another mode's \`id\`.
78
+
79
+ **modes.metadata** (`Record<string, unknown>`): Arbitrary metadata. \`metadata.default === true\` marks the default mode when \`defaultModeId\` is unset.
80
+
81
+ **modes.tools** (`ToolsInput`): Replaces the backing agent's tools for this mode. Mutually exclusive with \`additionalTools\`.
66
82
 
67
- **modes.agent** (`Agent | ((state) => Agent)`): The agent for this mode. It can be a static Agent instance or a function that receives harness state and returns an Agent.
83
+ **modes.additionalTools** (`ToolsInput`): Tools layered on top of the backing agent's tools. Mutually exclusive with \`tools\`.
84
+
85
+ **modes.agent** (`Agent`): The agent for this mode. Deprecated in favor of the top-level \`agent\` config with mode-level overrides.
86
+
87
+ **agent** (`Agent`): Shared backing agent that each mode forks and decorates. When provided, modes layer instructions and tool overrides on top of this agent instead of providing their own.
88
+
89
+ **defaultModeId** (`string`): Default mode to enter when a thread has no persisted mode. Takes precedence over \`metadata.default\` on individual modes.
90
+
91
+ **instructions** (`string`): Base instructions shared across all modes. Appended to the backing agent's instructions.
68
92
 
69
93
  **tools** (`ToolsInput | ((ctx) => ToolsInput)`): Tools available to all agents across all modes. It can be a static tools object or a dynamic function that receives the request context.
70
94
 
71
95
  **workspace** (`Workspace | WorkspaceConfig | ((ctx) => Workspace)`): Workspace configuration. Accepts a pre-constructed Workspace, a WorkspaceConfig for the harness to construct internally, or a dynamic factory function.
72
96
 
97
+ **browser** (`MastraBrowser | ((ctx) => MastraBrowser)`): Browser automation configuration. Propagated to mode agents that don't have their own browser configured.
98
+
73
99
  **subagents** (`HarnessSubagent[]`): Subagent definitions. When provided, the harness creates a built-in \`subagent\` tool that parent agents can call to spawn focused subagents.
74
100
 
75
101
  **subagents.id** (`string`): Unique identifier for this subagent type (e.g., \`"explore"\`, \`"execute"\`).
@@ -108,14 +134,26 @@ await harness.sendMessage({ content: 'Hello!' })
108
134
 
109
135
  **modelUseCountProvider** (`ModelUseCountProvider`): Provides per-model use counts for sorting and display in \`listAvailableModels()\`.
110
136
 
137
+ **modelUseCountTracker** (`ModelUseCountTracker`): Callback invoked when a model is selected via \`switchModel()\`. Use to track and persist model usage for ranking.
138
+
139
+ **customModelCatalogProvider** (`CustomModelCatalogProvider`): Catalog hook for additional models (e.g., user-defined custom providers). Returned entries are merged into \`listAvailableModels()\`.
140
+
141
+ **gateways** (`MastraModelGatewayInterface[]`): Model gateways registered on the internal Mastra instance. Apps that need gateway-backed model resolution should also provide \`resolveModel\`.
142
+
111
143
  **toolCategoryResolver** (`(toolName: string) => ToolCategory | null`): Maps tool names to permission categories (\`'read'\`, \`'edit'\`, \`'execute'\`, \`'mcp'\`, \`'other'\`). Used by the permission system to resolve category-level policies.
112
144
 
145
+ **pubsub** (`PubSub`): PubSub instance used by the internal Mastra instance and mode agents for event coordination.
146
+
113
147
  **threadLock** (`{ acquire, release }`): Thread locking callbacks to prevent concurrent access from multiple processes. \`acquire\` should throw if the lock is held.
114
148
 
149
+ **observability** (`ObservabilityEntrypoint`): Observability entrypoint for tracing, scoring, and feedback. When provided, the internal Mastra instance produces trace spans for agent runs.
150
+
115
151
  ## Properties
116
152
 
117
153
  **id** (`string`): Harness identifier, set at construction.
118
154
 
155
+ **session** (`Session`): The per-conversation state for the active conversation: identity, the active thread binding and reads, mode and model selection, run and abort state, the live stream, tool suspensions, follow-ups, approvals, permission grants, token usage, and the display-state snapshot. See the Session reference for the full API.
156
+
119
157
  ## Methods
120
158
 
121
159
  ### Lifecycle
@@ -144,40 +182,44 @@ Stop all heartbeat handlers and clean up resources.
144
182
  await harness.destroy()
145
183
  ```
146
184
 
147
- ### State
148
-
149
- #### `getState()`
185
+ #### `removeHeartbeat({ id })`
150
186
 
151
- Return a read-only snapshot of the current harness state.
187
+ Remove a specific heartbeat handler by ID. Calls the handler's `shutdown()` callback if defined.
152
188
 
153
189
  ```typescript
154
- const state = harness.getState()
190
+ await harness.removeHeartbeat({ id: 'gateway-sync' })
155
191
  ```
156
192
 
157
- #### `getDisplayState()`
193
+ #### `stopHeartbeats()`
158
194
 
159
- Return the current `HarnessDisplayState` snapshot for UI rendering.
195
+ Stop and remove all heartbeat handlers.
160
196
 
161
197
  ```typescript
162
- const displayState = harness.getDisplayState()
198
+ await harness.stopHeartbeats()
163
199
  ```
164
200
 
165
- #### `restoreDisplayTasks(tasks)`
201
+ #### `getCurrentAgent()`
202
+
203
+ Return the fully-configured Agent for the current mode with runtime services (storage, memory, workspace, pubsub, telemetry) propagated.
204
+
205
+ ```typescript
206
+ const agent = harness.getCurrentAgent()
207
+ ```
166
208
 
167
- Restore the task portion of `HarnessDisplayState` after a UI replays persisted task tool history. This emits `display_state_changed` without emitting a live `task_updated` event.
209
+ #### `getResolvedMemory()`
168
210
 
169
- The task list itself is held in the thread-scoped `tasks` storage domain (the task store) and projected onto the agent state-signal lane (`stateId: "tasks"`), not in Harness state, so this method only updates the display snapshot. Task tools read and write the task store directly; you no longer need to seed `setState({ tasks })`.
211
+ Return the resolved memory instance, or `null` if no memory is configured.
170
212
 
171
213
  ```typescript
172
- harness.restoreDisplayTasks(replayedTasks)
214
+ const memory = await harness.getResolvedMemory()
173
215
  ```
174
216
 
175
- #### `setState(updates)`
217
+ #### `getMastra()`
176
218
 
177
- Update the harness state. Validates against `stateSchema` if provided, and emits a `state_changed` event with the new state and changed keys.
219
+ Return the internal `Mastra` instance, or `undefined` before `init()`. Useful for scorer registration, observability access, and eval tooling.
178
220
 
179
221
  ```typescript
180
- await harness.setState({ currentModelId: 'anthropic/claude-sonnet-4-6' })
222
+ const mastra = harness.getMastra()
181
223
  ```
182
224
 
183
225
  ### Modes
@@ -190,21 +232,7 @@ Return all configured `HarnessMode` instances.
190
232
  const modes = harness.listModes()
191
233
  ```
192
234
 
193
- #### `getCurrentModeId()`
194
-
195
- Return the ID of the currently active mode.
196
-
197
- ```typescript
198
- const modeId = harness.getCurrentModeId()
199
- ```
200
-
201
- #### `getCurrentMode()`
202
-
203
- Return the `HarnessMode` object for the current mode.
204
-
205
- ```typescript
206
- const mode = harness.getCurrentMode()
207
- ```
235
+ To read the active mode, use [`session.mode.get()`](https://mastra.ai/reference/harness/session); to resolve it to a full `HarnessMode`, use [`session.mode.resolve()`](https://mastra.ai/reference/harness/session).
208
236
 
209
237
  #### `switchMode({ modeId })`
210
238
 
@@ -216,13 +244,7 @@ await harness.switchMode({ modeId: 'build' })
216
244
 
217
245
  ### Models
218
246
 
219
- #### `getCurrentModelId()`
220
-
221
- Return the ID of the currently selected model from state.
222
-
223
- ```typescript
224
- const modelId = harness.getCurrentModelId()
225
- ```
247
+ To read the active model ID, use [`session.model.get()`](https://mastra.ai/reference/harness/session); to check whether a model is selected, use [`session.model.hasSelection()`](https://mastra.ai/reference/harness/session).
226
248
 
227
249
  #### `getModelName()`
228
250
 
@@ -240,16 +262,6 @@ Return the complete model ID string.
240
262
  const fullId = harness.getFullModelId()
241
263
  ```
242
264
 
243
- #### `hasModelSelected()`
244
-
245
- Check if a model ID is currently selected.
246
-
247
- ```typescript
248
- if (harness.hasModelSelected()) {
249
- // Ready to send messages
250
- }
251
- ```
252
-
253
265
  #### `switchModel({ modelId, scope?, modeId? })`
254
266
 
255
267
  Switch the active model. When `scope` is `'thread'`, the model ID is persisted to thread metadata so it's restored when switching back. Emits a `model_changed` event.
@@ -282,13 +294,7 @@ const models = await harness.listAvailableModels()
282
294
 
283
295
  ### Threads
284
296
 
285
- #### `getCurrentThreadId()`
286
-
287
- Return the ID of the currently active thread.
288
-
289
- ```typescript
290
- const threadId = harness.getCurrentThreadId()
291
- ```
297
+ The harness owns thread lifecycle transitions — creating, switching, cloning, renaming, and deleting threads — because they coordinate the shared thread lock and emit events. The active thread binding and thread/message reads live on [`session.thread`](https://mastra.ai/reference/harness/session).
292
298
 
293
299
  #### `createThread({ title? })`
294
300
 
@@ -306,22 +312,7 @@ Switch to a different thread. Aborts any in-progress operations, acquires a lock
306
312
  await harness.switchThread({ threadId: 'thread-abc123' })
307
313
  ```
308
314
 
309
- #### `listThreads(options?)`
310
-
311
- List threads from storage. By default, only threads for the current resource are returned, and transient [forked subagent](#forked-subagents) threads are hidden so they don’t appear in user-facing thread pickers / startup flows.
312
-
313
- ```typescript
314
- // List threads for current resource (forks hidden)
315
- const threads = await harness.listThreads()
316
-
317
- // List all threads across resources (forks still hidden)
318
- const allThreads = await harness.listThreads({ allResources: true })
319
-
320
- // Include forked subagent fork threads (debug / admin tooling only)
321
- const everything = await harness.listThreads({ includeForkedSubagents: true })
322
- ```
323
-
324
- Fork threads are tagged with `metadata.forkedSubagent === true` (and `metadata.parentThreadId`) by the harness. Set `includeForkedSubagents: true` to opt back into seeing them — e.g. for a debug panel.
315
+ To list threads from storage, use [`session.thread.list()`](https://mastra.ai/reference/harness/session). By default it returns only threads for the current resource and hides transient [forked subagent](#forked-subagents) threads; pass `includeForkedSubagents: true` to opt back into seeing them — e.g. for a debug panel.
325
316
 
326
317
  #### `renameThread({ title })`
327
318
 
@@ -348,20 +339,22 @@ const cloned = await harness.cloneThread({
348
339
 
349
340
  See [`Memory.cloneThread()`](https://mastra.ai/reference/memory/cloneThread) for details on what gets cloned.
350
341
 
351
- #### `getResourceId()`
342
+ To read the current resource ID, use [`session.identity.getResourceId()`](https://mastra.ai/reference/harness/session).
352
343
 
353
- Return the current resource ID.
344
+ #### `setResourceId({ resourceId })`
345
+
346
+ Set the resource ID and clear the current thread.
354
347
 
355
348
  ```typescript
356
- const resourceId = harness.getResourceId()
349
+ harness.setResourceId({ resourceId: 'project-xyz' })
357
350
  ```
358
351
 
359
- #### `setResourceId({ resourceId })`
352
+ #### `getKnownResourceIds()`
360
353
 
361
- Set the resource ID and clear the current thread.
354
+ Return the distinct resource IDs that have threads in storage.
362
355
 
363
356
  ```typescript
364
- harness.setResourceId({ resourceId: 'project-xyz' })
357
+ const resourceIds = await harness.getKnownResourceIds()
365
358
  ```
366
359
 
367
360
  #### `getSession()`
@@ -383,52 +376,11 @@ Send a message to the current agent. Creates a thread if none exists, builds a `
383
376
  await harness.sendMessage({ content: 'Explain the authentication flow' })
384
377
  ```
385
378
 
386
- #### `listMessages(options?)`
387
-
388
- Retrieve messages for the current thread.
389
-
390
- ```typescript
391
- const messages = await harness.listMessages()
392
-
393
- // Limit to the last 50 messages
394
- const recent = await harness.listMessages({ limit: 50 })
395
- ```
396
-
397
- #### `listMessagesForThread({ threadId, limit? })`
398
-
399
- Retrieve messages for a specific thread.
400
-
401
- ```typescript
402
- const messages = await harness.listMessagesForThread({ threadId: 'thread-abc123' })
403
- ```
404
-
405
- #### `getFirstUserMessageForThread({ threadId })`
406
-
407
- Retrieve the first user message for a given thread.
408
-
409
- ```typescript
410
- const firstMsg = await harness.getFirstUserMessageForThread({ threadId: 'thread-abc123' })
411
- ```
379
+ Reading messages is owned by [`session.thread`](https://mastra.ai/reference/harness/session): use `listActiveMessages()` for the active thread, `listMessages({ threadId })` for a specific thread, and `firstUserMessage({ threadId })` / `firstUserMessages({ threadIds })` for thread previews.
412
380
 
413
381
  ### Memory
414
382
 
415
- The `memory` property exposes thread management operations. These are also available as top-level methods on the harness.
416
-
417
- #### `memory.createThread({ title? })`
418
-
419
- Create a new thread. Same as `harness.createThread()`.
420
-
421
- #### `memory.switchThread({ threadId })`
422
-
423
- Switch to a different thread. Same as `harness.switchThread()`.
424
-
425
- #### `memory.listThreads(options?)`
426
-
427
- List threads from storage. Same as `harness.listThreads()`.
428
-
429
- #### `memory.renameThread({ title })`
430
-
431
- Update the title of the current thread. Same as `harness.renameThread()`.
383
+ The `memory` property bundles thread management operations into a single namespace. `memory.createThread`, `memory.switchThread`, and `memory.renameThread` delegate to the corresponding Harness lifecycle methods documented above; `memory.listThreads` delegates to [`session.thread.list()`](https://mastra.ai/reference/harness/session).
432
384
 
433
385
  #### `memory.deleteThread({ threadId })`
434
386
 
@@ -466,14 +418,7 @@ harness.followUp({ content: 'Now apply those changes' })
466
418
 
467
419
  ### Tool approvals
468
420
 
469
- #### `respondToToolApproval({ decision, requestContext? })`
470
-
471
- Respond to a pending tool approval request. Called when a `tool_approval_required` event is received.
472
-
473
- ```typescript
474
- harness.respondToToolApproval({ decision: 'approve' })
475
- harness.respondToToolApproval({ decision: 'decline' })
476
- ```
421
+ Responding to a pending tool approval is owned by the session — see [`session.respondToToolApproval()`](https://mastra.ai/reference/harness/session). The harness owns the permission _policy_ that decides when approval is required, documented under [Permissions](#permissions) below.
477
422
 
478
423
  ### Tool suspensions and plans
479
424
 
@@ -524,30 +469,7 @@ harness.respondToToolSuspension({
524
469
 
525
470
  ### Permissions
526
471
 
527
- #### `grantSessionCategory({ category })`
528
-
529
- Grant a tool category for the current session. Tools in this category are auto-approved without prompting.
530
-
531
- ```typescript
532
- harness.grantSessionCategory({ category: 'edit' })
533
- ```
534
-
535
- #### `grantSessionTool({ toolName })`
536
-
537
- Grant a specific tool for the current session.
538
-
539
- ```typescript
540
- harness.grantSessionTool({ toolName: 'mastra_workspace_execute_command' })
541
- ```
542
-
543
- #### `getSessionGrants()`
544
-
545
- Return currently granted session categories and tools.
546
-
547
- ```typescript
548
- const grants = harness.getSessionGrants()
549
- // { categories: Set<string>, tools: Set<string> }
550
- ```
472
+ The harness owns the permission _policy_ — the rules that decide when a tool requires approval. Session-scoped _grants_ (which tools and categories are auto-approved for the current conversation) live on the session: see [`session.grantCategory()`](https://mastra.ai/reference/harness/session), [`session.grantTool()`](https://mastra.ai/reference/harness/session), and [`session.getGrants()`](https://mastra.ai/reference/harness/session).
551
473
 
552
474
  #### `setPermissionForCategory({ category, policy })`
553
475
 
@@ -754,7 +676,7 @@ The model can also pass `forked: true` (or `forked: false`) per-invocation in th
754
676
  - **Memory required.** Forked mode calls `memory.cloneThread` to create the fork, so the harness must have `memory` configured and an active parent thread. Calls without those return a structured error rather than throwing.
755
677
  - **Parent agent reused.** The fork runs through the parent agent's `stream(...)` call. The parent's instructions, tools, model, `maxSteps`, and `stopWhen` apply. The subagent definition's `instructions`, `tools`, `allowedHarnessTools`, `allowedWorkspaceTools`, `defaultModelId`, `maxSteps`, and `stopWhen` are ignored in forked mode — this is what preserves the prompt-cache prefix.
756
678
  - **Toolsets inherited, recursive forks blocked at runtime.** Forks inherit the parent's toolsets verbatim (`ask_user`, `submit_plan`, user-configured harness tools, _including the `subagent` tool itself_) so the LLM request prefix — system prompt + tool list + tool schemas + tool descriptions — stays byte-identical to the parent's. This is what preserves the prompt cache. The `subagent` entry is kept on the model side but its `execute` is replaced inside the fork with a stub that returns a non-error "tool unavailable inside a forked subagent" message: nested forks are blocked at the runtime layer without perturbing the cached prefix.
757
- - **Fork threads are tagged.** Each fork thread is created with `metadata.forkedSubagent === true` and `metadata.parentThreadId === <parent>`. By default, [`listThreads`](#listthreadsoptions) hides these so they don't show up in user-facing thread pickers / startup flows. Pass `includeForkedSubagents: true` to see them in admin / debug tooling.
679
+ - **Fork threads are tagged.** Each fork thread is created with `metadata.forkedSubagent === true` and `metadata.parentThreadId === <parent>`. By default, [`session.thread.list()`](https://mastra.ai/reference/harness/session) hides these so they don't show up in user-facing thread pickers / startup flows. Pass `includeForkedSubagents: true` to see them in admin / debug tooling.
758
680
  - **Save-queue flushed before clone.** The agent stream batches message saves through a debounced `SaveQueueManager`, so the parent's latest user / assistant turn may not be on disk yet when the subagent tool call fires. The fork tool flushes pending saves first via the `flushMessages` callback on `AgentToolExecutionContext` before cloning, so the fork actually carries the latest turn. Flush failures are non-fatal — the clone still runs.
759
681
  - **Parent thread untouched.** All subagent activity (messages, OM writes) lands on the fork. The parent thread is never appended to during a forked subagent run.
760
682
 
@@ -764,34 +686,25 @@ Forked mode trades isolation for context inheritance. If the subagent should run
764
686
 
765
687
  ### Events
766
688
 
767
- #### `subscribeDisplayState(listener, options?)`
689
+ #### `subscribe(listener)`
768
690
 
769
- Register a listener for coalesced display state snapshots. Use this method for UI, Server-Sent Events (SSE), terminal UI (TUI), and bridge rendering paths that only need the latest `HarnessDisplayState`. Use [`subscribe`](#subscribelistener) when you need the raw event log.
691
+ Register an event listener. Returns an unsubscribe function.
770
692
 
771
- ```typescript
772
- const unsubscribe = harness.subscribeDisplayState(displayState => {
773
- render(displayState)
774
- })
693
+ Use this method for all consumers — UI, Server-Sent Events (SSE), terminal UI (TUI), bridge rendering, audit logs, debugging, analytics, and deterministic replay. For display rendering, watch for the `display_state_changed` event and read the latest snapshot from [`session.displayState.get()`](https://mastra.ai/reference/harness/session). After every event the harness emits `display_state_changed`, so high-frequency events such as `message_update`, `tool_update`, and `tool_input_delta` are coalesced into the next snapshot.
775
694
 
776
- // Optional tuning:
777
- harness.subscribeDisplayState(render, {
778
- windowMs: 250,
779
- maxWaitMs: 500,
695
+ ```typescript
696
+ // Render from the coalesced display-state snapshot:
697
+ const unsubscribe = harness.subscribe(event => {
698
+ if (event.type === 'display_state_changed') {
699
+ render(harness.session.displayState.get())
700
+ }
780
701
  })
781
702
 
782
- // Later:
783
- unsubscribe()
703
+ // Render an initial frame before the next harness event:
704
+ render(harness.session.displayState.get())
784
705
  ```
785
706
 
786
- Returns: `() => void`
787
-
788
- `subscribeDisplayState()` doesn't call the listener immediately. Call [`getDisplayState`](#getdisplaystate) first if the UI needs an initial render before the next harness event.
789
-
790
- #### `subscribe(listener)`
791
-
792
- Register an event listener. Returns an unsubscribe function.
793
-
794
- Use this method for audit logs, debugging, analytics, deterministic replay, or consumers that need every raw event. For display rendering, prefer [`subscribeDisplayState`](#subscribedisplaystatelistener-options) so high-frequency events such as `message_update`, `tool_update`, and `tool_input_delta` are coalesced into the latest display state snapshot.
707
+ To handle raw events directly, switch on `event.type`:
795
708
 
796
709
  ```typescript
797
710
  const unsubscribe = harness.subscribe(event => {
@@ -840,15 +753,28 @@ The harness emits events through registered listeners. The following table lists
840
753
  | `usage_update` | Token usage was updated. |
841
754
  | `error` | An error occurred. |
842
755
  | `info` | An informational message was emitted. |
756
+ | `system_reminder` | A system reminder was injected into the conversation. |
757
+ | `state_signal` | A state signal was emitted (state-driven prompt injection). |
758
+ | `reactive_signal` | A reactive signal fired in response to a state change. |
759
+ | `notification` | A notification was delivered to the thread inbox. |
760
+ | `notification_summary` | A summary of pending notifications was emitted. |
843
761
  | `follow_up_queued` | A follow-up message was queued. |
844
762
  | `workspace_status_changed` | The workspace status changed. |
845
763
  | `workspace_ready` | The workspace finished initializing. |
846
764
  | `workspace_error` | The workspace encountered an error. |
847
765
  | `om_status` | Observational Memory status update. |
766
+ | `om_activation` | Observational Memory was activated for the thread. |
767
+ | `om_model_changed` | An Observational Memory role model changed (`observer` or `reflector`). |
848
768
  | `om_observation_start` | An observation started. |
849
769
  | `om_observation_end` | An observation completed. |
770
+ | `om_observation_failed` | An observation failed. |
850
771
  | `om_reflection_start` | A reflection started. |
851
772
  | `om_reflection_end` | A reflection completed. |
773
+ | `om_reflection_failed` | A reflection failed. |
774
+ | `om_buffering_start` | Observational Memory started buffering messages. |
775
+ | `om_buffering_end` | Observational Memory finished buffering messages. |
776
+ | `om_buffering_failed` | Observational Memory buffering failed. |
777
+ | `om_thread_title_updated` | Observational Memory updated the thread title. |
852
778
  | `subagent_start` | A subagent started processing. |
853
779
  | `subagent_text_delta` | A subagent emitted a text delta. |
854
780
  | `subagent_tool_start` | A subagent started a tool call. |
@@ -856,6 +782,11 @@ The harness emits events through registered listeners. The following table lists
856
782
  | `subagent_end` | A subagent finished processing. |
857
783
  | `subagent_model_changed` | A subagent's model changed. |
858
784
  | `task_updated` | A task list was updated. |
785
+ | `goal_evaluation` | A goal evaluation completed (when native agent goals are configured). |
786
+ | `shell_output` | A tool emitted shell output (stdout or stderr). |
787
+ | `display_state_changed` | The canonical `HarnessDisplayState` snapshot changed. Read it from [`session.displayState.get()`](https://mastra.ai/reference/harness/session). |
788
+
789
+ The harness also emits low-level streaming content chunks — `text`, `thinking`, `tool_call`, `tool_result`, `image`, and `file`. These are the raw pieces that get assembled into messages; most UIs render from `message_update` (or read the [`session.displayState`](https://mastra.ai/reference/harness/session) snapshot) rather than subscribing to them directly.
859
790
 
860
791
  ## Built-in tools
861
792
 
@@ -889,4 +820,11 @@ harness.subscribe(event => {
889
820
  }
890
821
  }
891
822
  })
892
- ```
823
+ ```
824
+
825
+ ## Related
826
+
827
+ - [Session class](https://mastra.ai/reference/harness/session)
828
+ - [Harness overview](https://mastra.ai/docs/harness/overview)
829
+ - [Threads and state](https://mastra.ai/docs/harness/threads-and-state)
830
+ - [Tool approvals](https://mastra.ai/docs/harness/tool-approvals)