@mastra/mcp-docs-server 1.2.2-alpha.9 → 1.2.3-alpha.1
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.
- package/.docs/docs/{harness → agent-controller}/modes.md +19 -19
- package/.docs/docs/agent-controller/overview.md +128 -0
- package/.docs/docs/agent-controller/session.md +143 -0
- package/.docs/docs/{harness → agent-controller}/subagents.md +12 -12
- package/.docs/docs/agent-controller/threads-and-state.md +141 -0
- package/.docs/docs/{harness → agent-controller}/tool-approvals.md +23 -23
- package/.docs/docs/agents/channels.md +47 -0
- package/.docs/docs/server/auth/google.md +281 -0
- package/.docs/docs/server/auth.md +2 -1
- package/.docs/models/gateways/vercel.md +3 -1
- package/.docs/models/index.md +1 -1
- package/.docs/models/providers/baseten.md +1 -1
- package/.docs/models/providers/fireworks-ai.md +2 -1
- package/.docs/models/providers/friendli.md +3 -1
- package/.docs/models/providers/llmgateway.md +1 -2
- package/.docs/models/providers/nebius.md +3 -2
- package/.docs/models/providers/opencode-go.md +1 -1
- package/.docs/models/providers/ovhcloud.md +1 -2
- package/.docs/models/providers/scaleway.md +2 -1
- package/.docs/models/providers/tinfoil.md +77 -0
- package/.docs/models/providers/togetherai.md +3 -2
- package/.docs/models/providers/xiaomi-token-plan-ams.md +4 -6
- package/.docs/models/providers/xiaomi-token-plan-cn.md +4 -6
- package/.docs/models/providers/xiaomi-token-plan-sgp.md +4 -6
- package/.docs/models/providers/xiaomi.md +4 -7
- package/.docs/models/providers.md +1 -0
- package/.docs/reference/{harness/harness-class.md → agent-controller/agent-controller-class.md} +106 -106
- package/.docs/reference/{harness → agent-controller}/session.md +97 -91
- package/.docs/reference/agents/channels.md +3 -1
- package/.docs/reference/agents/durable-agent.md +30 -3
- package/.docs/reference/auth/google.md +355 -0
- package/.docs/reference/channels/channel-provider.md +65 -0
- package/.docs/reference/channels/slack-provider.md +226 -0
- package/.docs/reference/index.md +5 -2
- package/CHANGELOG.md +28 -0
- package/package.json +6 -6
- package/.docs/docs/harness/overview.md +0 -128
- package/.docs/docs/harness/session.md +0 -143
- package/.docs/docs/harness/threads-and-state.md +0 -141
package/.docs/reference/{harness/harness-class.md → agent-controller/agent-controller-class.md}
RENAMED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AgentController class
|
|
2
2
|
|
|
3
|
-
> **Beta:** The `
|
|
3
|
+
> **Beta:** The `AgentController` feature is in beta stage and subject to breaking changes in minor versions until it graduates from its beta status.
|
|
4
4
|
|
|
5
|
-
The `
|
|
5
|
+
The `AgentController` 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.
|
|
6
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/
|
|
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/agent-controller/session), accessed through `agentController.session`.
|
|
8
8
|
|
|
9
|
-
For a conceptual introduction, see the [
|
|
9
|
+
For a conceptual introduction, see the [AgentController overview](https://mastra.ai/docs/agent-controller/overview).
|
|
10
10
|
|
|
11
11
|
## Usage example
|
|
12
12
|
|
|
13
|
-
Import the `
|
|
13
|
+
Import the `AgentController` class and create a new instance with your agent, storage backend, and modes:
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import {
|
|
16
|
+
import { AgentController } from '@mastra/core/agent-controller'
|
|
17
17
|
import { LibSQLStore } from '@mastra/libsql'
|
|
18
18
|
import { z } from 'zod'
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const agentController = new AgentController({
|
|
21
21
|
id: 'my-coding-agent',
|
|
22
22
|
agent: myAgent,
|
|
23
23
|
storage: new LibSQLStore({ url: 'file:./data.db' }),
|
|
@@ -35,20 +35,20 @@ const harness = new Harness({
|
|
|
35
35
|
],
|
|
36
36
|
})
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
agentController.subscribe(event => {
|
|
39
39
|
if (event.type === 'message_update') {
|
|
40
40
|
renderMessage(event.message)
|
|
41
41
|
}
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
await
|
|
45
|
-
await
|
|
46
|
-
await
|
|
44
|
+
await agentController.init()
|
|
45
|
+
await agentController.selectOrCreateThread()
|
|
46
|
+
await agentController.sendMessage({ content: 'Hello!' })
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Constructor parameters
|
|
50
50
|
|
|
51
|
-
**id** (`string`): Unique identifier for this
|
|
51
|
+
**id** (`string`): Unique identifier for this agentController instance.
|
|
52
52
|
|
|
53
53
|
**resourceId** (`string`): Resource ID for grouping threads (e.g., project identifier). Threads are scoped to this resource ID. Defaults to \`id\`.
|
|
54
54
|
|
|
@@ -60,13 +60,13 @@ await harness.sendMessage({ content: 'Hello!' })
|
|
|
60
60
|
|
|
61
61
|
**memory** (`MastraMemory`): Memory configuration shared across all modes. Propagated to mode agents that don't have their own memory.
|
|
62
62
|
|
|
63
|
-
**modes** (`
|
|
63
|
+
**modes** (`AgentControllerMode[]`): 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.
|
|
64
64
|
|
|
65
65
|
**modes.id** (`string`): Unique identifier for this mode (e.g., \`"plan"\`, \`"build"\`).
|
|
66
66
|
|
|
67
67
|
**modes.name** (`string`): Human-readable name for display.
|
|
68
68
|
|
|
69
|
-
**modes.default** (`boolean`): Whether this is the default mode when the
|
|
69
|
+
**modes.default** (`boolean`): Whether this is the default mode when the agentController starts. Deprecated in favor of \`metadata.default\` or the top-level \`defaultModeId\`.
|
|
70
70
|
|
|
71
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.
|
|
72
72
|
|
|
@@ -98,7 +98,7 @@ await harness.sendMessage({ content: 'Hello!' })
|
|
|
98
98
|
|
|
99
99
|
**browser** (`MastraBrowser | ((ctx) => MastraBrowser)`): Browser automation instance or a dynamic factory function. When omitted, each session created via createSession can provide its own browser.
|
|
100
100
|
|
|
101
|
-
**subagents** (`
|
|
101
|
+
**subagents** (`AgentControllerSubagent[]`): Subagent definitions. When provided, the agentController creates a built-in \`subagent\` tool that parent agents can call to spawn focused subagents.
|
|
102
102
|
|
|
103
103
|
**subagents.id** (`string`): Unique identifier for this subagent type (e.g., \`"explore"\`, \`"execute"\`).
|
|
104
104
|
|
|
@@ -110,7 +110,7 @@ await harness.sendMessage({ content: 'Hello!' })
|
|
|
110
110
|
|
|
111
111
|
**subagents.tools** (`ToolsInput`): Tools this subagent has direct access to.
|
|
112
112
|
|
|
113
|
-
**subagents.
|
|
113
|
+
**subagents.allowedAgentControllerTools** (`string[]`): Tool IDs from the agentController's shared \`tools\` config. Merged with \`tools\` above to let subagents use a subset of agentController tools.
|
|
114
114
|
|
|
115
115
|
**subagents.allowedWorkspaceTools** (`string[]`): Workspace tool names the subagent is allowed to use. Uses the exposed names (after any renames via workspace tool config). When set, workspace tools not in this list are hidden from the model. Non-workspace tools are never affected. When omitted, all workspace tools are visible.
|
|
116
116
|
|
|
@@ -120,17 +120,17 @@ await harness.sendMessage({ content: 'Hello!' })
|
|
|
120
120
|
|
|
121
121
|
**subagents.stopWhen** (`LoopOptions['stopWhen']`): Optional stop condition for the spawned subagent.
|
|
122
122
|
|
|
123
|
-
**subagents.forked** (`boolean`): When \`true\`, calls to this subagent default to forked mode: the subagent runs on a clone of the parent thread, reusing the parent agent’s instructions, tools, and model so the prompt-cache prefix stays intact. Requires \`memory\` to be configured. The subagent definition’s own \`instructions\`, \`tools\`, \`
|
|
123
|
+
**subagents.forked** (`boolean`): When \`true\`, calls to this subagent default to forked mode: the subagent runs on a clone of the parent thread, reusing the parent agent’s instructions, tools, and model so the prompt-cache prefix stays intact. Requires \`memory\` to be configured. The subagent definition’s own \`instructions\`, \`tools\`, \`allowedAgentControllerTools\`, \`allowedWorkspaceTools\`, \`defaultModelId\`, \`maxSteps\`, and \`stopWhen\` are ignored in forked mode. Callers can still override per-invocation via \`forked: false\` in the \`subagent\` tool input. See the \[Forked subagents]\(#forked-subagents) section below for full semantics.
|
|
124
124
|
|
|
125
125
|
**resolveModel** (`(modelId: string) => MastraLanguageModel`): Converts a model ID string (e.g., \`"anthropic/claude-sonnet-4"\`) to a language model instance. Used by subagents and observational memory model resolution.
|
|
126
126
|
|
|
127
|
-
**omConfig** (`
|
|
127
|
+
**omConfig** (`AgentControllerOMConfig`): Default configuration for observational memory (observer/reflector model IDs and thresholds).
|
|
128
128
|
|
|
129
|
-
**disableBuiltinTools** (`BuiltinToolId[]`): Built-in
|
|
129
|
+
**disableBuiltinTools** (`BuiltinToolId[]`): Built-in tool IDs to remove from the \`controllerBuiltIn\` toolset. Valid values are \`ask\_user\`, \`submit\_plan\`, \`task\_write\`, \`task\_update\`, \`task\_complete\`, \`task\_check\`, and \`subagent\`.
|
|
130
130
|
|
|
131
131
|
**heartbeatHandlers** (`HeartbeatHandler[]`): Periodic background tasks started during \`init()\`. Use for gateway sync, cache refresh, and similar tasks.
|
|
132
132
|
|
|
133
|
-
**idGenerator** (`() => string`): Custom ID generator for
|
|
133
|
+
**idGenerator** (`() => string`): Custom ID generator for AgentController-managed IDs such as threads and mode-run identifiers. (Default: `timestamp + random string`)
|
|
134
134
|
|
|
135
135
|
**modelAuthChecker** (`ModelAuthChecker`): Custom auth checker for model providers. Return \`true\`/\`false\` to override the default environment variable check, or \`undefined\` to fall back to defaults.
|
|
136
136
|
|
|
@@ -152,7 +152,7 @@ await harness.sendMessage({ content: 'Hello!' })
|
|
|
152
152
|
|
|
153
153
|
## Properties
|
|
154
154
|
|
|
155
|
-
**id** (`string`):
|
|
155
|
+
**id** (`string`): AgentController identifier, set at construction.
|
|
156
156
|
|
|
157
157
|
**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.
|
|
158
158
|
|
|
@@ -162,24 +162,24 @@ await harness.sendMessage({ content: 'Hello!' })
|
|
|
162
162
|
|
|
163
163
|
#### `init()`
|
|
164
164
|
|
|
165
|
-
Initialize the
|
|
165
|
+
Initialize the agentController. Loads storage, initializes a static workspace (dynamic factory workspaces are resolved per-session during `createSession`), propagates memory and workspace to mode agents, and starts heartbeat handlers. Call this before using the agentController.
|
|
166
166
|
|
|
167
167
|
```typescript
|
|
168
|
-
await
|
|
168
|
+
await agentController.init()
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
#### `createSession({ id, ownerId, resourceId?, tags?, workspace?, browser?, requestContext? })`
|
|
172
172
|
|
|
173
|
-
Create a new, fully-wired `Session` and bring it online. The session starts in the default mode with the seeded model, connects to the
|
|
173
|
+
Create a new, fully-wired `Session` and bring it online. The session starts in the default mode with the seeded model, connects to the AgentController's shared machinery (agent, storage/lock, config catalog), and has a current thread (the most recent thread for the resource, or a freshly created one). Call `init()` once before creating sessions so shared storage is ready.
|
|
174
174
|
|
|
175
|
-
The
|
|
175
|
+
The AgentController owns no session of its own — every consumer creates its own session and drives all work through it. In a server or multiplayer setting, each request, thread, or user gets its own session, isolated from every other: independent event bus, mode, model, state, and current thread.
|
|
176
176
|
|
|
177
177
|
`id` and `ownerId` are required — they mirror `SessionRecord.id` and `SessionRecord.ownerId` and are stable for the life of the session. `resourceId` is optional and defaults to `config.resourceId` then `config.id`.
|
|
178
178
|
|
|
179
|
-
Each session owns its own `Workspace` and `Browser` instance. When `workspace` is omitted, the
|
|
179
|
+
Each session owns its own `Workspace` and `Browser` instance. When `workspace` is omitted, the AgentController resolves its configured workspace (a static instance or a dynamic factory) and passes it to the session. Pass a `workspace` override to give a specific session a different workspace than the AgentController default. The workspace is initialized during session creation; `workspace_ready` and `workspace_status_changed` events are emitted on the session bus after `init()` completes, and late subscribers receive a replay of the last workspace status.
|
|
180
180
|
|
|
181
181
|
```typescript
|
|
182
|
-
const session = await
|
|
182
|
+
const session = await agentController.createSession({
|
|
183
183
|
id: 'session-xyz',
|
|
184
184
|
ownerId: 'user-123',
|
|
185
185
|
})
|
|
@@ -188,7 +188,7 @@ const session = await harness.createSession({
|
|
|
188
188
|
You can also override the resource:
|
|
189
189
|
|
|
190
190
|
```typescript
|
|
191
|
-
const session = await
|
|
191
|
+
const session = await agentController.createSession({
|
|
192
192
|
id: 'session-xyz',
|
|
193
193
|
ownerId: 'user-123',
|
|
194
194
|
resourceId: 'project-abc',
|
|
@@ -198,7 +198,7 @@ const session = await harness.createSession({
|
|
|
198
198
|
Override the workspace and browser for a specific session:
|
|
199
199
|
|
|
200
200
|
```typescript
|
|
201
|
-
const session = await
|
|
201
|
+
const session = await agentController.createSession({
|
|
202
202
|
id: 'session-xyz',
|
|
203
203
|
ownerId: 'user-123',
|
|
204
204
|
workspace: myWorkspace,
|
|
@@ -208,14 +208,14 @@ const session = await harness.createSession({
|
|
|
208
208
|
|
|
209
209
|
`tags` scopes initial thread selection: a thread is a resume candidate only when its metadata matches every provided tag. This lets worktrees sharing a resourceId each resume their own thread (via a `projectPath` tag).
|
|
210
210
|
|
|
211
|
-
Switching the resource ID via `
|
|
211
|
+
Switching the resource ID via `agentController.setResourceId()` changes only the resource ID, not `id` or `ownerId`. Read them through `session.identity.getId()` and `session.identity.getOwnerId()`.
|
|
212
212
|
|
|
213
213
|
#### `selectOrCreateThread()`
|
|
214
214
|
|
|
215
215
|
Select the most recent thread for the current resource, or create one if none exist. Loads thread metadata and acquires a thread lock.
|
|
216
216
|
|
|
217
217
|
```typescript
|
|
218
|
-
const thread = await
|
|
218
|
+
const thread = await agentController.selectOrCreateThread()
|
|
219
219
|
```
|
|
220
220
|
|
|
221
221
|
#### `destroy()`
|
|
@@ -223,7 +223,7 @@ const thread = await harness.selectOrCreateThread()
|
|
|
223
223
|
Stop all heartbeat handlers and clean up resources.
|
|
224
224
|
|
|
225
225
|
```typescript
|
|
226
|
-
await
|
|
226
|
+
await agentController.destroy()
|
|
227
227
|
```
|
|
228
228
|
|
|
229
229
|
#### `removeHeartbeat({ id })`
|
|
@@ -231,7 +231,7 @@ await harness.destroy()
|
|
|
231
231
|
Remove a specific heartbeat handler by ID. Calls the handler's `shutdown()` callback if defined.
|
|
232
232
|
|
|
233
233
|
```typescript
|
|
234
|
-
await
|
|
234
|
+
await agentController.removeHeartbeat({ id: 'gateway-sync' })
|
|
235
235
|
```
|
|
236
236
|
|
|
237
237
|
#### `stopHeartbeats()`
|
|
@@ -239,7 +239,7 @@ await harness.removeHeartbeat({ id: 'gateway-sync' })
|
|
|
239
239
|
Stop and remove all heartbeat handlers.
|
|
240
240
|
|
|
241
241
|
```typescript
|
|
242
|
-
await
|
|
242
|
+
await agentController.stopHeartbeats()
|
|
243
243
|
```
|
|
244
244
|
|
|
245
245
|
#### `getCurrentAgent()`
|
|
@@ -247,7 +247,7 @@ await harness.stopHeartbeats()
|
|
|
247
247
|
Return the fully-configured Agent for the current mode with runtime services (storage, memory, workspace, pubsub, telemetry) propagated.
|
|
248
248
|
|
|
249
249
|
```typescript
|
|
250
|
-
const agent =
|
|
250
|
+
const agent = agentController.getCurrentAgent()
|
|
251
251
|
```
|
|
252
252
|
|
|
253
253
|
#### `getResolvedMemory()`
|
|
@@ -255,7 +255,7 @@ const agent = harness.getCurrentAgent()
|
|
|
255
255
|
Return the resolved memory instance, or `null` if no memory is configured.
|
|
256
256
|
|
|
257
257
|
```typescript
|
|
258
|
-
const memory = await
|
|
258
|
+
const memory = await agentController.getResolvedMemory()
|
|
259
259
|
```
|
|
260
260
|
|
|
261
261
|
#### `getMastra()`
|
|
@@ -263,15 +263,15 @@ const memory = await harness.getResolvedMemory()
|
|
|
263
263
|
Return the internal `Mastra` instance, or `undefined` before `init()`. Useful for scorer registration, observability access, and eval tooling.
|
|
264
264
|
|
|
265
265
|
```typescript
|
|
266
|
-
const mastra =
|
|
266
|
+
const mastra = agentController.getMastra()
|
|
267
267
|
```
|
|
268
268
|
|
|
269
269
|
#### `getWorkspace()`
|
|
270
270
|
|
|
271
|
-
Return the
|
|
271
|
+
Return the AgentController-level workspace if it is a static `Workspace` instance. Dynamic factory workspaces are not resolved here — use [`resolveWorkspace()`](#resolveworkspace-session-requestcontext-) to resolve a factory against a session's request context.
|
|
272
272
|
|
|
273
273
|
```typescript
|
|
274
|
-
const workspace =
|
|
274
|
+
const workspace = agentController.getWorkspace()
|
|
275
275
|
```
|
|
276
276
|
|
|
277
277
|
#### `resolveWorkspace({ session, requestContext? })`
|
|
@@ -279,31 +279,31 @@ const workspace = harness.getWorkspace()
|
|
|
279
279
|
Eagerly resolve and cache the workspace. For dynamic workspaces (factory function), this triggers the factory against the given session's request context and caches the result so `getWorkspace()` returns it. Returns the resolved workspace or `undefined` if none is configured.
|
|
280
280
|
|
|
281
281
|
```typescript
|
|
282
|
-
const workspace = await
|
|
282
|
+
const workspace = await agentController.resolveWorkspace({ session })
|
|
283
283
|
```
|
|
284
284
|
|
|
285
285
|
```typescript
|
|
286
286
|
// With an explicit request context
|
|
287
287
|
const requestContext = new RequestContext()
|
|
288
|
-
const workspace = await
|
|
288
|
+
const workspace = await agentController.resolveWorkspace({ session, requestContext })
|
|
289
289
|
```
|
|
290
290
|
|
|
291
291
|
#### `hasWorkspace()`
|
|
292
292
|
|
|
293
|
-
Whether a workspace is configured on this
|
|
293
|
+
Whether a workspace is configured on this AgentController (static instance or dynamic factory). Sessions without an explicit workspace override fall back to this.
|
|
294
294
|
|
|
295
295
|
```typescript
|
|
296
|
-
if (
|
|
296
|
+
if (agentController.hasWorkspace()) {
|
|
297
297
|
// ...
|
|
298
298
|
}
|
|
299
299
|
```
|
|
300
300
|
|
|
301
301
|
#### `isWorkspaceReady()`
|
|
302
302
|
|
|
303
|
-
Whether the
|
|
303
|
+
Whether the AgentController-level static workspace has been initialized. Dynamic factory workspaces are resolved and initialized per-session during `createSession`, so this returns `false` for factory configs until a session is created.
|
|
304
304
|
|
|
305
305
|
```typescript
|
|
306
|
-
if (
|
|
306
|
+
if (agentController.isWorkspaceReady()) {
|
|
307
307
|
// ...
|
|
308
308
|
}
|
|
309
309
|
```
|
|
@@ -312,24 +312,24 @@ if (harness.isWorkspaceReady()) {
|
|
|
312
312
|
|
|
313
313
|
#### `listModes()`
|
|
314
314
|
|
|
315
|
-
Return all configured `
|
|
315
|
+
Return all configured `AgentControllerMode` instances.
|
|
316
316
|
|
|
317
317
|
```typescript
|
|
318
|
-
const modes =
|
|
318
|
+
const modes = agentController.listModes()
|
|
319
319
|
```
|
|
320
320
|
|
|
321
|
-
To read the active mode, use [`session.mode.get()`](https://mastra.ai/reference/
|
|
321
|
+
To read the active mode, use [`session.mode.get()`](https://mastra.ai/reference/agent-controller/session); to resolve it to a full `AgentControllerMode`, use [`session.mode.resolve()`](https://mastra.ai/reference/agent-controller/session).
|
|
322
322
|
|
|
323
323
|
### Models
|
|
324
324
|
|
|
325
|
-
To read the active model ID, use [`session.model.get()`](https://mastra.ai/reference/
|
|
325
|
+
To read the active model ID, use [`session.model.get()`](https://mastra.ai/reference/agent-controller/session); for a short display name, use [`session.model.displayName()`](https://mastra.ai/reference/agent-controller/session); to check whether a model is selected, use [`session.model.hasSelection()`](https://mastra.ai/reference/agent-controller/session).
|
|
326
326
|
|
|
327
327
|
#### `getCurrentModelAuthStatus()`
|
|
328
328
|
|
|
329
329
|
Check if the current model's provider has authentication configured. Uses `modelAuthChecker` if provided, falling back to environment variable checks from the provider registry.
|
|
330
330
|
|
|
331
331
|
```typescript
|
|
332
|
-
const status = await
|
|
332
|
+
const status = await agentController.getCurrentModelAuthStatus()
|
|
333
333
|
// { hasAuth: true, apiKeyEnvVar: 'ANTHROPIC_API_KEY' }
|
|
334
334
|
```
|
|
335
335
|
|
|
@@ -338,20 +338,20 @@ const status = await harness.getCurrentModelAuthStatus()
|
|
|
338
338
|
Retrieve all available models from the provider registry, including their authentication status and use counts.
|
|
339
339
|
|
|
340
340
|
```typescript
|
|
341
|
-
const models = await
|
|
341
|
+
const models = await agentController.listAvailableModels()
|
|
342
342
|
// [{ id, provider, modelName, hasApiKey, apiKeyEnvVar, useCount }]
|
|
343
343
|
```
|
|
344
344
|
|
|
345
345
|
### Threads
|
|
346
346
|
|
|
347
|
-
The
|
|
347
|
+
The agentController 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/agent-controller/session).
|
|
348
348
|
|
|
349
349
|
#### `createThread({ title? })`
|
|
350
350
|
|
|
351
351
|
Create a new thread. Initializes thread metadata, saves it to storage, acquires a thread lock, and emits a `thread_created` event.
|
|
352
352
|
|
|
353
353
|
```typescript
|
|
354
|
-
const thread = await
|
|
354
|
+
const thread = await agentController.createThread({ title: 'New conversation' })
|
|
355
355
|
```
|
|
356
356
|
|
|
357
357
|
#### `switchThread({ threadId })`
|
|
@@ -359,17 +359,17 @@ const thread = await harness.createThread({ title: 'New conversation' })
|
|
|
359
359
|
Switch to a different thread. Aborts any in-progress operations, acquires a lock on the new thread, releases the lock on the previous thread, loads the thread's metadata, and emits a `thread_changed` event.
|
|
360
360
|
|
|
361
361
|
```typescript
|
|
362
|
-
await
|
|
362
|
+
await agentController.switchThread({ threadId: 'thread-abc123' })
|
|
363
363
|
```
|
|
364
364
|
|
|
365
|
-
To list threads from storage, use [`session.thread.list()`](https://mastra.ai/reference/
|
|
365
|
+
To list threads from storage, use [`session.thread.list()`](https://mastra.ai/reference/agent-controller/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.
|
|
366
366
|
|
|
367
367
|
#### `renameThread({ title })`
|
|
368
368
|
|
|
369
369
|
Update the title of the current thread.
|
|
370
370
|
|
|
371
371
|
```typescript
|
|
372
|
-
await
|
|
372
|
+
await agentController.renameThread({ title: 'Updated title' })
|
|
373
373
|
```
|
|
374
374
|
|
|
375
375
|
#### `cloneThread({ sourceThreadId?, title?, resourceId? })`
|
|
@@ -378,10 +378,10 @@ Clone an existing thread and switch to the clone. Copies all messages, acquires
|
|
|
378
378
|
|
|
379
379
|
```typescript
|
|
380
380
|
// Clone the current thread
|
|
381
|
-
const cloned = await
|
|
381
|
+
const cloned = await agentController.cloneThread()
|
|
382
382
|
|
|
383
383
|
// Clone a specific thread with a custom title
|
|
384
|
-
const cloned = await
|
|
384
|
+
const cloned = await agentController.cloneThread({
|
|
385
385
|
sourceThreadId: 'thread-abc123',
|
|
386
386
|
title: 'Alternative approach',
|
|
387
387
|
})
|
|
@@ -389,14 +389,14 @@ const cloned = await harness.cloneThread({
|
|
|
389
389
|
|
|
390
390
|
See [`Memory.cloneThread()`](https://mastra.ai/reference/memory/cloneThread) for details on what gets cloned.
|
|
391
391
|
|
|
392
|
-
To read the current resource ID, use [`session.identity.getResourceId()`](https://mastra.ai/reference/
|
|
392
|
+
To read the current resource ID, use [`session.identity.getResourceId()`](https://mastra.ai/reference/agent-controller/session).
|
|
393
393
|
|
|
394
394
|
#### `setResourceId({ resourceId })`
|
|
395
395
|
|
|
396
396
|
Set the resource ID and clear the current thread.
|
|
397
397
|
|
|
398
398
|
```typescript
|
|
399
|
-
|
|
399
|
+
agentController.setResourceId({ resourceId: 'project-xyz' })
|
|
400
400
|
```
|
|
401
401
|
|
|
402
402
|
#### `getKnownResourceIds()`
|
|
@@ -404,7 +404,7 @@ harness.setResourceId({ resourceId: 'project-xyz' })
|
|
|
404
404
|
Return the distinct resource IDs that have threads in storage.
|
|
405
405
|
|
|
406
406
|
```typescript
|
|
407
|
-
const resourceIds = await
|
|
407
|
+
const resourceIds = await agentController.getKnownResourceIds()
|
|
408
408
|
```
|
|
409
409
|
|
|
410
410
|
#### `getSession()`
|
|
@@ -412,7 +412,7 @@ const resourceIds = await harness.getKnownResourceIds()
|
|
|
412
412
|
Return current session information including thread ID, mode ID, and the list of threads.
|
|
413
413
|
|
|
414
414
|
```typescript
|
|
415
|
-
const session = await
|
|
415
|
+
const session = await agentController.getSession()
|
|
416
416
|
// { currentThreadId, currentModeId, threads }
|
|
417
417
|
```
|
|
418
418
|
|
|
@@ -420,24 +420,24 @@ const session = await harness.getSession()
|
|
|
420
420
|
|
|
421
421
|
#### `sendMessage({ content, files?, requestContext? })`
|
|
422
422
|
|
|
423
|
-
Send a message to the current agent. Creates a thread if none exists, builds a `RequestContext` and toolsets, and streams the agent's response. Handles tool calls, approvals, and errors automatically. If you provide `requestContext`, the
|
|
423
|
+
Send a message to the current agent. Creates a thread if none exists, builds a `RequestContext` and toolsets, and streams the agent's response. Handles tool calls, approvals, and errors automatically. If you provide `requestContext`, the agentController forwards it to tools and subagents during the run.
|
|
424
424
|
|
|
425
425
|
```typescript
|
|
426
|
-
await
|
|
426
|
+
await agentController.sendMessage({ content: 'Explain the authentication flow' })
|
|
427
427
|
```
|
|
428
428
|
|
|
429
|
-
Reading messages is owned by [`session.thread`](https://mastra.ai/reference/
|
|
429
|
+
Reading messages is owned by [`session.thread`](https://mastra.ai/reference/agent-controller/session): use `listActiveMessages()` for the active thread, `listMessages({ threadId })` for a specific thread, and `firstUserMessage({ threadId })` / `firstUserMessages({ threadIds })` for thread previews.
|
|
430
430
|
|
|
431
431
|
### Memory
|
|
432
432
|
|
|
433
|
-
The `memory` property bundles thread management operations into a single namespace. `memory.createThread`, `memory.switchThread`, and `memory.renameThread` delegate to the corresponding
|
|
433
|
+
The `memory` property bundles thread management operations into a single namespace. `memory.createThread`, `memory.switchThread`, and `memory.renameThread` delegate to the corresponding AgentController lifecycle methods documented above; `memory.listThreads` delegates to [`session.thread.list()`](https://mastra.ai/reference/agent-controller/session).
|
|
434
434
|
|
|
435
435
|
#### `memory.deleteThread({ threadId })`
|
|
436
436
|
|
|
437
|
-
Delete a thread and all its messages from storage. If the deleted thread is the currently active thread, the thread lock is released and the
|
|
437
|
+
Delete a thread and all its messages from storage. If the deleted thread is the currently active thread, the thread lock is released and the agentController clears its active thread. Emits a `thread_deleted` event.
|
|
438
438
|
|
|
439
439
|
```typescript
|
|
440
|
-
await
|
|
440
|
+
await agentController.memory.deleteThread({ threadId: 'thread-abc123' })
|
|
441
441
|
```
|
|
442
442
|
|
|
443
443
|
### Flow control
|
|
@@ -447,7 +447,7 @@ await harness.memory.deleteThread({ threadId: 'thread-abc123' })
|
|
|
447
447
|
Abort any in-progress generation.
|
|
448
448
|
|
|
449
449
|
```typescript
|
|
450
|
-
|
|
450
|
+
agentController.abort()
|
|
451
451
|
```
|
|
452
452
|
|
|
453
453
|
#### `steer({ content, requestContext? })`
|
|
@@ -455,7 +455,7 @@ harness.abort()
|
|
|
455
455
|
Steer the agent mid-stream by injecting an instruction into the current generation.
|
|
456
456
|
|
|
457
457
|
```typescript
|
|
458
|
-
|
|
458
|
+
agentController.steer({ content: 'Focus on security implications' })
|
|
459
459
|
```
|
|
460
460
|
|
|
461
461
|
#### `followUp({ content, requestContext? })`
|
|
@@ -463,12 +463,12 @@ harness.steer({ content: 'Focus on security implications' })
|
|
|
463
463
|
Queue a follow-up message to be sent after the current generation completes. If no operation is running, sends the message immediately.
|
|
464
464
|
|
|
465
465
|
```typescript
|
|
466
|
-
|
|
466
|
+
agentController.followUp({ content: 'Now apply those changes' })
|
|
467
467
|
```
|
|
468
468
|
|
|
469
469
|
### Tool approvals
|
|
470
470
|
|
|
471
|
-
Responding to a pending tool approval is owned by the session — see [`session.respondToToolApproval()`](https://mastra.ai/reference/
|
|
471
|
+
Responding to a pending tool approval is owned by the session — see [`session.respondToToolApproval()`](https://mastra.ai/reference/agent-controller/session). The agentController owns the permission _policy_ that decides when approval is required, documented under [Permissions](#permissions) below.
|
|
472
472
|
|
|
473
473
|
### Tool suspensions and plans
|
|
474
474
|
|
|
@@ -479,11 +479,11 @@ Respond to a pending tool suspension. Interactive built-in tools such as `ask_us
|
|
|
479
479
|
Provide `toolCallId` to select which suspension to resume. It is required when more than one tool is suspended at the same time (for example, parallel `ask_user` calls). When omitted, it resolves to the sole pending suspension.
|
|
480
480
|
|
|
481
481
|
```typescript
|
|
482
|
-
|
|
482
|
+
agentController.subscribe(event => {
|
|
483
483
|
if (event.type === 'tool_suspended' && event.toolName === 'ask_user') {
|
|
484
484
|
const { question } = event.suspendPayload as { question: string }
|
|
485
485
|
// Show `question` to the user, then resume the tool with their answer.
|
|
486
|
-
|
|
486
|
+
agentController.respondToToolSuspension({
|
|
487
487
|
toolCallId: event.toolCallId,
|
|
488
488
|
resumeData: 'Yes, proceed with the refactor',
|
|
489
489
|
})
|
|
@@ -494,7 +494,7 @@ harness.subscribe(event => {
|
|
|
494
494
|
For multi-select questions, pass the selected option labels as a string array.
|
|
495
495
|
|
|
496
496
|
```typescript
|
|
497
|
-
|
|
497
|
+
agentController.respondToToolSuspension({
|
|
498
498
|
toolCallId: event.toolCallId,
|
|
499
499
|
resumeData: ['Add tests', 'Update docs'],
|
|
500
500
|
})
|
|
@@ -504,14 +504,14 @@ harness.respondToToolSuspension({
|
|
|
504
504
|
|
|
505
505
|
The `submit_plan` built-in tool pauses via the native tool-suspension primitive, so it surfaces through the same `tool_suspended` event as other interactive tools. Resume it with [`respondToToolSuspension`](#respondtotoolsuspension-resumedata-toolcallid-requestcontext-), passing a `resumeData` object with `action` (`'approved'` or `'rejected'`) and an optional `feedback` string.
|
|
506
506
|
|
|
507
|
-
On approval, the
|
|
507
|
+
On approval, the AgentController automatically switches to its default (execution) mode. On rejection, the plan-mode run resumes so the agent can revise and submit again.
|
|
508
508
|
|
|
509
509
|
```typescript
|
|
510
|
-
|
|
510
|
+
agentController.respondToToolSuspension({
|
|
511
511
|
toolCallId: event.toolCallId,
|
|
512
512
|
resumeData: { action: 'approved' },
|
|
513
513
|
})
|
|
514
|
-
|
|
514
|
+
agentController.respondToToolSuspension({
|
|
515
515
|
toolCallId: event.toolCallId,
|
|
516
516
|
resumeData: { action: 'rejected', feedback: 'Needs more detail' },
|
|
517
517
|
})
|
|
@@ -524,7 +524,7 @@ harness.respondToToolSuspension({
|
|
|
524
524
|
Resolve a tool's category using the configured `toolCategoryResolver`.
|
|
525
525
|
|
|
526
526
|
```typescript
|
|
527
|
-
const category =
|
|
527
|
+
const category = agentController.getToolCategory({ toolName: 'mastra_workspace_write_file' })
|
|
528
528
|
// 'edit'
|
|
529
529
|
```
|
|
530
530
|
|
|
@@ -535,7 +535,7 @@ const category = harness.getToolCategory({ toolName: 'mastra_workspace_write_fil
|
|
|
535
535
|
Load observational memory records for the current thread and emit an `om_status` event with reconstructed progress.
|
|
536
536
|
|
|
537
537
|
```typescript
|
|
538
|
-
await
|
|
538
|
+
await agentController.loadOMProgress()
|
|
539
539
|
```
|
|
540
540
|
|
|
541
541
|
#### `getObservationalMemoryRecord()`
|
|
@@ -543,7 +543,7 @@ await harness.loadOMProgress()
|
|
|
543
543
|
Return the full `ObservationalMemoryRecord` for the current thread and resource, or `null` if no thread is selected or no record exists.
|
|
544
544
|
|
|
545
545
|
```typescript
|
|
546
|
-
const record = await
|
|
546
|
+
const record = await agentController.getObservationalMemoryRecord()
|
|
547
547
|
|
|
548
548
|
if (record) {
|
|
549
549
|
console.log(record.activeObservations)
|
|
@@ -552,7 +552,7 @@ if (record) {
|
|
|
552
552
|
}
|
|
553
553
|
```
|
|
554
554
|
|
|
555
|
-
The observer/reflector model selection and observation/reflection thresholds live on the Session under [`session.om`](https://mastra.ai/reference/
|
|
555
|
+
The observer/reflector model selection and observation/reflection thresholds live on the Session under [`session.om`](https://mastra.ai/reference/agent-controller/session), grouped by role. Read them with `session.om.observer.modelId()` / `session.om.reflector.modelId()` and `session.om.observer.threshold()` / `session.om.reflector.threshold()`, and change a role's model with `session.om.observer.switchModel()` / `session.om.reflector.switchModel()`.
|
|
556
556
|
|
|
557
557
|
### Forked subagents
|
|
558
558
|
|
|
@@ -560,11 +560,11 @@ By default, a subagent runs with a fresh context — it doesn't see the parent c
|
|
|
560
560
|
|
|
561
561
|
#### Enabling forked mode
|
|
562
562
|
|
|
563
|
-
Set `forked: true` either on the `
|
|
563
|
+
Set `forked: true` either on the `AgentControllerSubagent` definition (per-type default) or on each `subagent` tool call (per-invocation override):
|
|
564
564
|
|
|
565
565
|
```typescript
|
|
566
566
|
// Per-type default — every call to this subagent forks unless overridden.
|
|
567
|
-
const subagents:
|
|
567
|
+
const subagents: AgentControllerSubagent[] = [
|
|
568
568
|
{
|
|
569
569
|
id: 'collaborator',
|
|
570
570
|
name: 'Collaborator',
|
|
@@ -579,10 +579,10 @@ The model can also pass `forked: true` (or `forked: false`) per-invocation in th
|
|
|
579
579
|
|
|
580
580
|
#### Semantics and constraints
|
|
581
581
|
|
|
582
|
-
- **Memory required.** Forked mode calls `memory.cloneThread` to create the fork, so the
|
|
583
|
-
- **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`, `
|
|
584
|
-
- **Toolsets inherited, recursive forks blocked at runtime.** Forks inherit the parent's toolsets verbatim (`ask_user`, `submit_plan`, user-configured
|
|
585
|
-
- **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/
|
|
582
|
+
- **Memory required.** Forked mode calls `memory.cloneThread` to create the fork, so the agentController must have `memory` configured and an active parent thread. Calls without those return a structured error rather than throwing.
|
|
583
|
+
- **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`, `allowedAgentControllerTools`, `allowedWorkspaceTools`, `defaultModelId`, `maxSteps`, and `stopWhen` are ignored in forked mode — this is what preserves the prompt-cache prefix.
|
|
584
|
+
- **Toolsets inherited, recursive forks blocked at runtime.** Forks inherit the parent's toolsets verbatim (`ask_user`, `submit_plan`, user-configured agentController 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.
|
|
585
|
+
- **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/agent-controller/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.
|
|
586
586
|
- **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.
|
|
587
587
|
- **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.
|
|
588
588
|
|
|
@@ -596,24 +596,24 @@ Forked mode trades isolation for context inheritance. If the subagent should run
|
|
|
596
596
|
|
|
597
597
|
Register an event listener. Returns an unsubscribe function.
|
|
598
598
|
|
|
599
|
-
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/
|
|
599
|
+
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/agent-controller/session). After every event the agentController emits `display_state_changed`, so high-frequency events such as `message_update`, `tool_update`, and `tool_input_delta` are coalesced into the next snapshot.
|
|
600
600
|
|
|
601
601
|
```typescript
|
|
602
602
|
// Render from the coalesced display-state snapshot:
|
|
603
|
-
const unsubscribe =
|
|
603
|
+
const unsubscribe = agentController.subscribe(event => {
|
|
604
604
|
if (event.type === 'display_state_changed') {
|
|
605
|
-
render(
|
|
605
|
+
render(agentController.session.displayState.get())
|
|
606
606
|
}
|
|
607
607
|
})
|
|
608
608
|
|
|
609
|
-
// Render an initial frame before the next
|
|
610
|
-
render(
|
|
609
|
+
// Render an initial frame before the next agentController event:
|
|
610
|
+
render(agentController.session.displayState.get())
|
|
611
611
|
```
|
|
612
612
|
|
|
613
613
|
To handle raw events directly, switch on `event.type`:
|
|
614
614
|
|
|
615
615
|
```typescript
|
|
616
|
-
const unsubscribe =
|
|
616
|
+
const unsubscribe = agentController.subscribe(event => {
|
|
617
617
|
switch (event.type) {
|
|
618
618
|
case 'message_update':
|
|
619
619
|
renderMessage(event.message)
|
|
@@ -633,7 +633,7 @@ unsubscribe()
|
|
|
633
633
|
|
|
634
634
|
## Events
|
|
635
635
|
|
|
636
|
-
The
|
|
636
|
+
The agentController emits events through registered listeners. The following table lists the available event types:
|
|
637
637
|
|
|
638
638
|
| Event type | Description |
|
|
639
639
|
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -642,7 +642,7 @@ The harness emits events through registered listeners. The following table lists
|
|
|
642
642
|
| `thread_changed` | The active thread changed. |
|
|
643
643
|
| `thread_created` | A new thread was created. |
|
|
644
644
|
| `thread_deleted` | A thread was deleted. |
|
|
645
|
-
| `state_changed` |
|
|
645
|
+
| `state_changed` | AgentController state was updated. |
|
|
646
646
|
| `agent_start` | The agent started processing. |
|
|
647
647
|
| `agent_end` | The agent finished processing. |
|
|
648
648
|
| `message_start` | A new message started streaming. |
|
|
@@ -690,13 +690,13 @@ The harness emits events through registered listeners. The following table lists
|
|
|
690
690
|
| `task_updated` | A task list was updated. |
|
|
691
691
|
| `goal_evaluation` | A goal evaluation completed (when native agent goals are configured). |
|
|
692
692
|
| `shell_output` | A tool emitted shell output (stdout or stderr). |
|
|
693
|
-
| `display_state_changed` | The canonical `
|
|
693
|
+
| `display_state_changed` | The canonical `AgentControllerDisplayState` snapshot changed. Read it from [`session.displayState.get()`](https://mastra.ai/reference/agent-controller/session). |
|
|
694
694
|
|
|
695
|
-
The
|
|
695
|
+
The agentController 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/agent-controller/session) snapshot) rather than subscribing to them directly.
|
|
696
696
|
|
|
697
697
|
## Built-in tools
|
|
698
698
|
|
|
699
|
-
The
|
|
699
|
+
The agentController provides built-in tools to agents in every mode:
|
|
700
700
|
|
|
701
701
|
| Tool | Description |
|
|
702
702
|
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -715,11 +715,11 @@ The `ask_user` tool accepts `options` for choice prompts. Set `selectionMode` to
|
|
|
715
715
|
The following example demonstrates a multi-select response handler. The tool pauses through the `tool_suspended` event, the UI reads `selectionMode` from `event.suspendPayload`, lets the user choose multiple options, then returns a string array with `respondToToolSuspension()`.
|
|
716
716
|
|
|
717
717
|
```typescript
|
|
718
|
-
|
|
718
|
+
agentController.subscribe(event => {
|
|
719
719
|
if (event.type === 'tool_suspended' && event.toolName === 'ask_user') {
|
|
720
720
|
const { selectionMode } = event.suspendPayload as { selectionMode?: string }
|
|
721
721
|
if (selectionMode === 'multi_select') {
|
|
722
|
-
|
|
722
|
+
agentController.respondToToolSuspension({
|
|
723
723
|
toolCallId: event.toolCallId,
|
|
724
724
|
resumeData: ['Add tests', 'Update docs'],
|
|
725
725
|
})
|
|
@@ -730,7 +730,7 @@ harness.subscribe(event => {
|
|
|
730
730
|
|
|
731
731
|
## Related
|
|
732
732
|
|
|
733
|
-
- [Session class](https://mastra.ai/reference/
|
|
734
|
-
- [
|
|
735
|
-
- [Threads and state](https://mastra.ai/docs/
|
|
736
|
-
- [Tool approvals](https://mastra.ai/docs/
|
|
733
|
+
- [Session class](https://mastra.ai/reference/agent-controller/session)
|
|
734
|
+
- [AgentController overview](https://mastra.ai/docs/agent-controller/overview)
|
|
735
|
+
- [Threads and state](https://mastra.ai/docs/agent-controller/threads-and-state)
|
|
736
|
+
- [Tool approvals](https://mastra.ai/docs/agent-controller/tool-approvals)
|