@mastra/mcp-docs-server 1.1.42-alpha.4 → 1.1.42-alpha.6
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/agent-builder/memory.md +1 -1
- package/.docs/docs/agents/adding-voice.md +31 -0
- package/.docs/docs/agents/agent-approval.md +14 -0
- package/.docs/docs/agents/code-mode.md +163 -0
- package/.docs/docs/agents/signals.md +132 -71
- package/.docs/docs/getting-started/manual-install.md +1 -1
- package/.docs/docs/memory/semantic-recall.md +1 -1
- package/.docs/docs/observability/metrics/overview.md +1 -0
- package/.docs/docs/observability/metrics/querying.md +292 -0
- package/.docs/docs/server/auth/fga.md +2 -0
- package/.docs/docs/voice/overview.md +62 -0
- package/.docs/docs/voice/speech-to-speech.md +52 -1
- package/.docs/docs/workspace/sandbox.md +4 -2
- package/.docs/guides/guide/firecrawl.md +5 -5
- package/.docs/models/embeddings.md +2 -2
- package/.docs/reference/agents/agent.md +46 -17
- package/.docs/reference/index.md +3 -0
- package/.docs/reference/observability/metrics/automatic-metrics.md +7 -1
- package/.docs/reference/processors/tool-search-processor.md +31 -0
- package/.docs/reference/server/routes.md +81 -9
- package/.docs/reference/templates/overview.md +2 -2
- package/.docs/reference/tools/mcp-client.md +51 -0
- package/.docs/reference/vectors/pg.md +2 -0
- package/.docs/reference/voice/inworld-realtime.md +353 -0
- package/.docs/reference/voice/inworld.md +2 -0
- package/.docs/reference/workspace/agentcore-runtime-sandbox.md +202 -0
- package/.docs/reference/workspace/blaxel-sandbox.md +3 -0
- package/.docs/reference/workspace/docker-sandbox.md +3 -1
- package/.docs/reference/workspace/vercel-microvm-sandbox.md +199 -0
- package/.docs/reference/workspace/vercel.md +2 -0
- package/CHANGELOG.md +15 -0
- package/package.json +8 -6
|
@@ -25,7 +25,7 @@ Observational memory lets the agent learn long-lived facts from past conversatio
|
|
|
25
25
|
|
|
26
26
|
## Observational memory model
|
|
27
27
|
|
|
28
|
-
Observational memory runs an Observer and Reflector model on top of every conversation. The default model is `google/gemini-2.5-flash`, which requires a `
|
|
28
|
+
Observational memory runs an Observer and Reflector model on top of every conversation. The default model is `google/gemini-2.5-flash`, which requires a `GOOGLE_API_KEY` environment variable in any environment where the Builder agent will run. Mastra also falls back to `GOOGLE_GENERATIVE_AI_API_KEY`.
|
|
29
29
|
|
|
30
30
|
To use a different model, set `observationalMemory.model` to any model ID supported by the Mastra model router (and provide the matching provider credentials):
|
|
31
31
|
|
|
@@ -132,6 +132,37 @@ agent.voice.send(microphoneStream)
|
|
|
132
132
|
agent.voice.close()
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
### Per-session voice for concurrent sessions
|
|
136
|
+
|
|
137
|
+
A static `voice` instance is shared across every request. For one-shot text-to-speech this is fine, but realtime and speech-to-speech providers store one WebSocket, one set of tools, and one request context per instance. If you deploy a single agent that handles several live sessions at once, a shared instance lets one session overwrite another session's tools, instructions, and request context.
|
|
138
|
+
|
|
139
|
+
To give each session its own voice, provide `voice` as a resolver. Mastra runs the resolver on every `getVoice()` call and returns a fresh, session-owned instance:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { Agent } from '@mastra/core/agent'
|
|
143
|
+
import { OpenAIRealtimeVoice } from '@mastra/voice-openai-realtime'
|
|
144
|
+
|
|
145
|
+
export const agent = new Agent({
|
|
146
|
+
id: 'support-line',
|
|
147
|
+
name: 'Support Line',
|
|
148
|
+
instructions: ({ requestContext }) => `Help user ${requestContext.get('user')}.`,
|
|
149
|
+
model: 'openai/gpt-5.4',
|
|
150
|
+
voice: ({ requestContext }) => new OpenAIRealtimeVoice({ apiKey: requestContext.get('apiKey') }),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Each concurrent session resolves its own voice instance
|
|
154
|
+
const voice = await agent.getVoice({ requestContext })
|
|
155
|
+
await voice.connect()
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
When you use a resolver:
|
|
159
|
+
|
|
160
|
+
- Each call to `getVoice()` returns a new instance, so concurrent sessions never share state.
|
|
161
|
+
- Mastra does not add tools or instructions to a resolver instance. Configure those inside the resolver or on the provider.
|
|
162
|
+
- You own the lifecycle of the returned instance, so call `disconnect()` or `close()` when the session ends.
|
|
163
|
+
|
|
164
|
+
The `agent.voice` getter has no request context, so it throws when `voice` is a resolver. Use `agent.getVoice({ requestContext })` instead.
|
|
165
|
+
|
|
135
166
|
### Event System
|
|
136
167
|
|
|
137
168
|
The realtime voice provider emits several events you can listen for:
|
|
@@ -86,6 +86,20 @@ for await (const chunk of stream.fullStream) {
|
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
#### Conditional approval with a function
|
|
90
|
+
|
|
91
|
+
Instead of a boolean, `requireToolApproval` accepts a function that decides per tool call. It receives the `toolName`, the `args` the model passed, the `requestContext`, and the `workspace`. Return `true` to require approval for that call, or `false` to allow it. This lets you gate approval dynamically — for example, only for tools whose name matches a pattern:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const stream = await agent.stream('Clean up old records', {
|
|
95
|
+
requireToolApproval: ({ toolName }) => /^delete_/.test(toolName),
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
A tool's own `requireApproval` setting still takes precedence: if a tool defines its own approval rule, that rule decides for that tool and the function above does not override it. If the function throws, the call requires approval (fail-safe).
|
|
100
|
+
|
|
101
|
+
> **Note:** Function-based `requireToolApproval` is only available on regular `stream()` / `generate()` calls. Durable agents and stored agents persist their options, and a function can't be serialized, so they accept only a boolean. If you pass a function in those contexts it falls back to requiring approval for every tool call.
|
|
102
|
+
|
|
89
103
|
### Runtime suspension with `suspend()`
|
|
90
104
|
|
|
91
105
|
A tool can also pause _during_ its `execute` function by calling `suspend()`. This is useful when the tool starts running and then discovers it needs additional user input or confirmation before it can finish.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Code mode
|
|
2
|
+
|
|
3
|
+
> **Experimental:** This feature is experimental. Breaking changes may occur without a major version bump until the API is stable.
|
|
4
|
+
|
|
5
|
+
Code mode gives an agent a single tool that runs a short TypeScript program in a sandbox. Instead of calling tools one at a time, the model writes a program that orchestrates your existing tools as `external_*` functions, batching calls with `Promise.all`, aggregating with `reduce`, branching, and doing math in a real runtime, then returns a single result.
|
|
6
|
+
|
|
7
|
+
`createCodeMode` returns this tool with the default id `execute_typescript`. The id is configurable, so an agent can have several code mode tools at once, each scoped to a different set of tools (see [Scoping tools across multiple code tools](#scoping-tools-across-multiple-code-tools)).
|
|
8
|
+
|
|
9
|
+
## When to use code mode
|
|
10
|
+
|
|
11
|
+
Use code mode when a task touches several tools at once or needs real computation between calls:
|
|
12
|
+
|
|
13
|
+
- Fewer round-trips: a task that touches several tools runs in one tool call instead of one model turn per tool.
|
|
14
|
+
- Correct math: sums, averages, and other arithmetic run as JavaScript, not as token prediction.
|
|
15
|
+
- Planning up front: filtering, aggregation, and branching happen inside the program rather than across separate turns.
|
|
16
|
+
|
|
17
|
+
## How it works
|
|
18
|
+
|
|
19
|
+
Your tools keep running on the host with full validation, request context, and tracing. Only the model's orchestration code runs in the sandbox. Each `external_*` call is bridged back to the real tool on the host, so dangerous tools, approvals, and validation behave exactly as they do for normal tool calls.
|
|
20
|
+
|
|
21
|
+
The program runs in a [Workspace sandbox](https://mastra.ai/docs/workspace/overview). A sandbox is required, because code mode runs model-authored code and the execution boundary must be chosen deliberately. Pass one via `sandbox`, or run the agent in a workspace that provides one. To execute on the host machine, pass `new LocalSandbox()` explicitly. This runs the program as a host `node` process with host privileges, so only use it for trusted or local development.
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
`createCodeMode` returns the tool plus generated instructions. With no `id`, the tool is named `execute_typescript`. Add both to your agent:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { Agent } from '@mastra/core/agent'
|
|
29
|
+
import { createCodeMode, createTool } from '@mastra/core/tools'
|
|
30
|
+
import { LocalSandbox } from '@mastra/core/workspace'
|
|
31
|
+
import { z } from 'zod'
|
|
32
|
+
|
|
33
|
+
const getTopProducts = createTool({
|
|
34
|
+
id: 'getTopProducts',
|
|
35
|
+
description: 'Get top selling products',
|
|
36
|
+
inputSchema: z.object({ limit: z.number() }),
|
|
37
|
+
outputSchema: z.object({
|
|
38
|
+
products: z.array(z.object({ id: z.string(), name: z.string(), totalSales: z.number() })),
|
|
39
|
+
}),
|
|
40
|
+
execute: async ({ limit }) => fetchTopProducts(limit),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const getProductRatings = createTool({
|
|
44
|
+
id: 'getProductRatings',
|
|
45
|
+
description: 'Get ratings for a product',
|
|
46
|
+
inputSchema: z.object({ productId: z.string() }),
|
|
47
|
+
outputSchema: z.object({ ratings: z.array(z.object({ score: z.number() })) }),
|
|
48
|
+
execute: async ({ productId }) => fetchRatings(productId),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const { tool, instructions } = createCodeMode({
|
|
52
|
+
tools: { getTopProducts, getProductRatings },
|
|
53
|
+
sandbox: new LocalSandbox(), // required; runs on the host — see "How it works"
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const agent = new Agent({
|
|
57
|
+
name: 'shop-assistant',
|
|
58
|
+
instructions: ['You are a helpful shopping assistant.', instructions],
|
|
59
|
+
model: 'openai/gpt-5.4',
|
|
60
|
+
tools: { execute_typescript: tool },
|
|
61
|
+
})
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Asked "What are the top 5 products and the average rating for each?", the model emits one `execute_typescript` call instead of many separate tool calls:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
const top = await external_getTopProducts({ limit: 5 })
|
|
68
|
+
const ratings = await Promise.all(
|
|
69
|
+
top.products.map(p => external_getProductRatings({ productId: p.id })),
|
|
70
|
+
)
|
|
71
|
+
return top.products.map((product, i) => {
|
|
72
|
+
const scores = ratings[i].ratings.map(r => r.score)
|
|
73
|
+
const avg = scores.reduce((sum, s) => sum + s, 0) / scores.length
|
|
74
|
+
return {
|
|
75
|
+
name: product.name,
|
|
76
|
+
sales: product.totalSales,
|
|
77
|
+
averageRating: Math.round(avg * 100) / 100,
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
All five rating lookups run in parallel, the averages are computed in JavaScript, and the agent receives one structured result.
|
|
83
|
+
|
|
84
|
+
## Configuration
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
const { tool, instructions } = createCodeMode({
|
|
88
|
+
tools: { getTopProducts, getProductRatings }, // exposed as external_*; only these can be called
|
|
89
|
+
sandbox, // required WorkspaceSandbox, unless the agent runs in a workspace that provides one
|
|
90
|
+
timeout: 30_000, // optional execution timeout in ms (default 30000)
|
|
91
|
+
id: 'execute_typescript', // optional tool id (default "execute_typescript")
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Option | Type | Description |
|
|
96
|
+
| --------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `tools` | `ToolsInput` | Tools exposed to the program as `external_<id>`. Only these may be called. |
|
|
98
|
+
| `sandbox` | `WorkspaceSandbox` | Sandbox to run the program in. Required unless the agent runs in a workspace that provides one. Pass `new LocalSandbox()` to run on the host. |
|
|
99
|
+
| `timeout` | `number` | Execution timeout in milliseconds. Default `30000`. |
|
|
100
|
+
| `id` | `string` | The generated tool's id. Default `execute_typescript`. |
|
|
101
|
+
|
|
102
|
+
## Result
|
|
103
|
+
|
|
104
|
+
The tool returns a `CodeModeToolResult`:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
type CodeModeToolResult = {
|
|
108
|
+
success: boolean
|
|
109
|
+
result?: unknown // value returned by the program
|
|
110
|
+
logs?: string[] // captured console output
|
|
111
|
+
error?: { message: string; name?: string; line?: number }
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Inspecting the instructions
|
|
116
|
+
|
|
117
|
+
The generated `instructions` contain the usage contract and a typed `external_*` declaration for each tool, derived from your tool schemas. Print them to see exactly what the model receives:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { createCodeModeInstructions } from '@mastra/core/tools'
|
|
121
|
+
|
|
122
|
+
console.log(createCodeModeInstructions({ tools: { getTopProducts, getProductRatings } }))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Scoping tools across multiple code tools
|
|
126
|
+
|
|
127
|
+
`createCodeMode` captures its own allow-list. Call it more than once to give an agent several code tools, each scoped to a different subset of tools. A program can only call the `external_*` functions for the tools passed to its own `createCodeMode` call, so the subsets stay isolated.
|
|
128
|
+
|
|
129
|
+
Give each tool a distinct `id` so their ids do not collide, and add each tool's instructions to the agent:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const sales = createCodeMode({
|
|
133
|
+
id: 'sales_code',
|
|
134
|
+
tools: { listRecentOrders, getCustomer },
|
|
135
|
+
sandbox,
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
const inventory = createCodeMode({
|
|
139
|
+
id: 'inventory_code',
|
|
140
|
+
tools: { listProducts, getSupplier },
|
|
141
|
+
sandbox,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const agent = new Agent({
|
|
145
|
+
name: 'ops-assistant',
|
|
146
|
+
instructions: ['You are an ops assistant.', sales.instructions, inventory.instructions],
|
|
147
|
+
model: 'openai/gpt-5.4',
|
|
148
|
+
tools: { sales_code: sales.tool, inventory_code: inventory.tool },
|
|
149
|
+
})
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
A program run by `sales_code` cannot call an inventory tool, and the reverse holds too. Use this for least-privilege scoping and to keep each tool's prompt surface small.
|
|
153
|
+
|
|
154
|
+
## Tips
|
|
155
|
+
|
|
156
|
+
- Keep tools focused, so each does one thing well and the model composes them in code.
|
|
157
|
+
- Code mode helps most when calls can be parallelized with `Promise.all`.
|
|
158
|
+
- Use `console.log` for debugging. Logs are captured in the result.
|
|
159
|
+
|
|
160
|
+
## Related
|
|
161
|
+
|
|
162
|
+
- [Tools](https://mastra.ai/docs/agents/using-tools)
|
|
163
|
+
- [Workspace overview](https://mastra.ai/docs/workspace/overview)
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
> **Experimental:** This feature is in alpha. Breaking changes may occur without a major version bump until the API is stable.
|
|
4
4
|
|
|
5
|
-
Signals are a way to interact with an agent through a thread. Instead of starting every interaction with `agent.stream()`, subscribe to a thread and send signals. Mastra either wakes the agent when the thread is idle
|
|
5
|
+
Signals are a way to interact with an agent through a thread. Instead of starting every interaction with `agent.stream()`, subscribe to a thread and send messages or signals. Mastra either wakes the agent when the thread is idle, drops input into the running agent loop, or queues input for the next turn.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Use message APIs for user-authored input. Use `sendSignal()` for lower-level system context, such as background task notifications, policy reminders, or processor-generated context.
|
|
8
8
|
|
|
9
9
|
## Quickstart
|
|
10
10
|
|
|
11
|
-
Subscribe to the thread before sending
|
|
11
|
+
Subscribe to the thread before sending messages. The subscription receives the active stream when the message wakes the agent or enters a running loop.
|
|
12
12
|
|
|
13
13
|
```typescript
|
|
14
14
|
const subscription = await agent.subscribeToThread({
|
|
@@ -16,33 +16,65 @@ const subscription = await agent.subscribeToThread({
|
|
|
16
16
|
threadId: 'thread_456',
|
|
17
17
|
})
|
|
18
18
|
|
|
19
|
-
agent.
|
|
19
|
+
agent.sendMessage('Compare that with the previous option.', {
|
|
20
|
+
resourceId: 'user_123',
|
|
21
|
+
threadId: 'thread_456',
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
for await (const chunk of subscription.stream) {
|
|
25
|
+
console.log(chunk)
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
When the thread has a running agent stream, `sendMessage()` becomes new input inside that agent loop. When the thread is idle, Mastra starts a stream with the message as the first input.
|
|
30
|
+
|
|
31
|
+
## Send a message now
|
|
32
|
+
|
|
33
|
+
Use `sendMessage()` when the user expects the active agent to see the message immediately.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
agent.sendMessage(
|
|
20
37
|
{
|
|
21
|
-
|
|
22
|
-
|
|
38
|
+
contents: 'Use the latest customer note too.',
|
|
39
|
+
attributes: { name: 'Jane', sentFrom: 'slack' },
|
|
23
40
|
},
|
|
24
41
|
{
|
|
25
42
|
resourceId: 'user_123',
|
|
26
43
|
threadId: 'thread_456',
|
|
27
44
|
},
|
|
28
45
|
)
|
|
46
|
+
```
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
48
|
+
The model receives attributed messages as XML-wrapped user input:
|
|
49
|
+
|
|
50
|
+
```xml
|
|
51
|
+
<user name="Jane" sentFrom="slack">Use the latest customer note too.</user>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Messages without attributes are sent as plain user input.
|
|
55
|
+
|
|
56
|
+
## Queue a message for the next turn
|
|
57
|
+
|
|
58
|
+
Use `queueMessage()` when a user sends a follow-up but the active model call should finish first. Mastra waits for the active run to complete, then starts a new run on the same thread.
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
agent.queueMessage('Also check whether the tests need updates.', {
|
|
62
|
+
resourceId: 'user_123',
|
|
63
|
+
threadId: 'thread_456',
|
|
64
|
+
})
|
|
33
65
|
```
|
|
34
66
|
|
|
35
|
-
When the thread
|
|
67
|
+
When the thread is idle, `queueMessage()` starts a run immediately. When the thread is active, it preserves turn order by starting a new run after the active run completes.
|
|
36
68
|
|
|
37
|
-
## Control signal behavior
|
|
69
|
+
## Control low-level signal behavior
|
|
38
70
|
|
|
39
|
-
By default, Mastra delivers signals to active runs and wakes idle threads. Use `ifActive.behavior` and `ifIdle.behavior` to change that behavior.
|
|
71
|
+
Use `sendSignal()` when you need to send system-generated context instead of user-authored input. For external events, use `type: 'notification'`. By default, Mastra delivers signals to active runs and wakes idle threads. Use `ifActive.behavior` and `ifIdle.behavior` to change that behavior.
|
|
40
72
|
|
|
41
73
|
```typescript
|
|
42
74
|
const result = agent.sendSignal(
|
|
43
75
|
{
|
|
44
|
-
type: '
|
|
45
|
-
contents: '
|
|
76
|
+
type: 'notification',
|
|
77
|
+
contents: 'GitHub CI failed on PR #123: 3 tests failed.',
|
|
46
78
|
},
|
|
47
79
|
{
|
|
48
80
|
resourceId: 'user_123',
|
|
@@ -58,44 +90,43 @@ await result.persisted
|
|
|
58
90
|
|
|
59
91
|
The behavior options are:
|
|
60
92
|
|
|
61
|
-
- `ifActive.behavior: 'deliver'`: Add the signal to the running agent loop. This is the default.
|
|
62
|
-
- `ifActive.behavior: 'persist'`: Save the signal to memory without adding it to the running loop.
|
|
63
|
-
- `ifActive.behavior: 'discard'`: Ignore the signal while the thread is active.
|
|
64
|
-
- `ifIdle.behavior: 'wake'`: Start a stream with the signal as the first input. This is the default.
|
|
65
|
-
- `ifIdle.behavior: 'persist'`: Save the signal to memory without starting a stream.
|
|
66
|
-
- `ifIdle.behavior: 'discard'`: Ignore the signal while the thread is idle.
|
|
93
|
+
- `ifActive.behavior: 'deliver'`: Add the signal or message to the running agent loop. This is the default.
|
|
94
|
+
- `ifActive.behavior: 'persist'`: Save the signal or message to memory without adding it to the running loop.
|
|
95
|
+
- `ifActive.behavior: 'discard'`: Ignore the signal or message while the thread is active.
|
|
96
|
+
- `ifIdle.behavior: 'wake'`: Start a stream with the signal or message as the first input. This is the default.
|
|
97
|
+
- `ifIdle.behavior: 'persist'`: Save the signal or message to memory without starting a stream.
|
|
98
|
+
- `ifIdle.behavior: 'discard'`: Ignore the signal or message while the thread is idle.
|
|
67
99
|
|
|
68
100
|
Pass `ifIdle.streamOptions` when the idle wake-up stream needs options such as model settings, tools, or runtime context. You do not need to repeat `memory.resource` or `memory.thread`; Mastra uses the top-level `resourceId` and `threadId` for the thread.
|
|
69
101
|
|
|
70
102
|
```typescript
|
|
71
|
-
agent.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
threadId: 'thread_456',
|
|
79
|
-
ifIdle: {
|
|
80
|
-
behavior: 'wake',
|
|
81
|
-
streamOptions: {
|
|
82
|
-
maxSteps: 3,
|
|
83
|
-
},
|
|
103
|
+
agent.sendMessage('Continue with the next step.', {
|
|
104
|
+
resourceId: 'user_123',
|
|
105
|
+
threadId: 'thread_456',
|
|
106
|
+
ifIdle: {
|
|
107
|
+
behavior: 'wake',
|
|
108
|
+
streamOptions: {
|
|
109
|
+
maxSteps: 3,
|
|
84
110
|
},
|
|
85
111
|
},
|
|
86
|
-
)
|
|
112
|
+
})
|
|
87
113
|
```
|
|
88
114
|
|
|
89
|
-
##
|
|
115
|
+
## Send notification context
|
|
116
|
+
|
|
117
|
+
Signals have a semantic `type` and an LLM-facing `tagName`. Use `type` to describe the signal category. Use `tagName` to control the XML tag the model sees.
|
|
90
118
|
|
|
91
|
-
|
|
119
|
+
For external events, use `type: 'notification'`. Reactive signals are reserved for processor- or runtime-generated context, such as policy guidance, background task results, and auto-loaded instructions.
|
|
92
120
|
|
|
93
121
|
```typescript
|
|
94
122
|
agent.sendSignal(
|
|
95
123
|
{
|
|
96
|
-
type: '
|
|
97
|
-
contents: '
|
|
98
|
-
attributes: {
|
|
124
|
+
type: 'notification',
|
|
125
|
+
contents: 'PR #123 has a new review comment from User X about the API surface.',
|
|
126
|
+
attributes: {
|
|
127
|
+
source: 'github',
|
|
128
|
+
pr: '123',
|
|
129
|
+
},
|
|
99
130
|
},
|
|
100
131
|
{
|
|
101
132
|
resourceId: 'user_123',
|
|
@@ -104,51 +135,67 @@ agent.sendSignal(
|
|
|
104
135
|
)
|
|
105
136
|
```
|
|
106
137
|
|
|
107
|
-
The model receives:
|
|
138
|
+
The model receives the signal as context like this:
|
|
108
139
|
|
|
109
140
|
```xml
|
|
110
|
-
<
|
|
141
|
+
<notification source="github" pr="123">PR #123 has a new review comment from User X about the API surface.</notification>
|
|
111
142
|
```
|
|
112
143
|
|
|
113
|
-
|
|
144
|
+
Use XML-safe `tagName` and attribute names. They can contain letters, numbers, underscores, periods, and hyphens. They must start with a letter or underscore.
|
|
145
|
+
|
|
146
|
+
## Send processor context
|
|
114
147
|
|
|
115
|
-
|
|
148
|
+
Processors can send reactive signals during a run. A processor should inspect the chat history, react to a specific trigger, and avoid sending the same context more than once.
|
|
116
149
|
|
|
117
|
-
|
|
150
|
+
The following example demonstrates a processor that injects `AGENTS.md` instructions after a tool call reads an `AGENTS.md` file.
|
|
118
151
|
|
|
119
152
|
```typescript
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
import type { Processor, ProcessInputStepArgs } from '@mastra/core/processors'
|
|
154
|
+
|
|
155
|
+
export const agentsMdReminderProcessor: Processor = {
|
|
156
|
+
id: 'agents-md-reminder',
|
|
157
|
+
async processInputStep({ messageList, sendSignal }: ProcessInputStepArgs) {
|
|
158
|
+
const messages = messageList.get.all.db()
|
|
159
|
+
const agentsMdPath = findAgentsMdPathFromToolCalls(messages)
|
|
160
|
+
|
|
161
|
+
if (!agentsMdPath || hasAlreadySentAgentsMdReminder(messages, agentsMdPath)) {
|
|
162
|
+
return messageList
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await sendSignal?.({
|
|
166
|
+
type: 'reactive',
|
|
167
|
+
contents: readAgentsMdInstructions(agentsMdPath),
|
|
168
|
+
attributes: {
|
|
169
|
+
type: 'dynamic-agents-md',
|
|
170
|
+
path: agentsMdPath,
|
|
171
|
+
},
|
|
172
|
+
metadata: {
|
|
173
|
+
path: agentsMdPath,
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
return messageList
|
|
132
178
|
},
|
|
133
|
-
|
|
179
|
+
}
|
|
134
180
|
```
|
|
135
181
|
|
|
136
|
-
|
|
182
|
+
Reactive signals default to `tagName: 'system-reminder'`, so the model receives this context as
|
|
137
183
|
|
|
138
184
|
```xml
|
|
139
|
-
<system-reminder
|
|
185
|
+
<system-reminder type="dynamic-agents-md" path="packages/ui/AGENTS.md">
|
|
186
|
+
$agentsMdFileContents
|
|
187
|
+
</system-reminder>
|
|
140
188
|
```
|
|
141
189
|
|
|
142
|
-
|
|
190
|
+
Awaiting `sendSignal()` preserves stream echo ordering when a subscribed thread is active.
|
|
143
191
|
|
|
144
|
-
##
|
|
192
|
+
## Conditional attributes
|
|
145
193
|
|
|
146
|
-
Use `ifActive.attributes` and `ifIdle.attributes` to tag
|
|
194
|
+
Use `ifActive.attributes` and `ifIdle.attributes` to tag input with context that depends on whether the agent is active or idle at delivery time. Mastra resolves the correct branch when the input is accepted.
|
|
147
195
|
|
|
148
196
|
```typescript
|
|
149
|
-
agent.
|
|
197
|
+
agent.sendMessage(
|
|
150
198
|
{
|
|
151
|
-
type: 'user-message',
|
|
152
199
|
contents: 'Also cover the edge cases.',
|
|
153
200
|
attributes: { source: 'chat' },
|
|
154
201
|
},
|
|
@@ -156,7 +203,7 @@ agent.sendSignal(
|
|
|
156
203
|
resourceId: 'user_123',
|
|
157
204
|
threadId: 'thread_456',
|
|
158
205
|
ifActive: { attributes: { delivery: 'while-active' } },
|
|
159
|
-
ifIdle: { attributes: { delivery: 'message' } },
|
|
206
|
+
ifIdle: { attributes: { delivery: 'new-message' } },
|
|
160
207
|
},
|
|
161
208
|
)
|
|
162
209
|
```
|
|
@@ -164,24 +211,35 @@ agent.sendSignal(
|
|
|
164
211
|
When the agent is working, the model sees:
|
|
165
212
|
|
|
166
213
|
```xml
|
|
167
|
-
<user
|
|
214
|
+
<user source="chat" delivery="while-active">Also cover the edge cases.</user>
|
|
168
215
|
```
|
|
169
216
|
|
|
170
217
|
When the agent is idle:
|
|
171
218
|
|
|
172
219
|
```xml
|
|
173
|
-
<user
|
|
220
|
+
<user source="chat" delivery="new-message">Also cover the edge cases.</user>
|
|
174
221
|
```
|
|
175
222
|
|
|
176
|
-
Top-level `attributes` always apply. The selected branch's `attributes` are merged into them at delivery time. The `
|
|
223
|
+
Top-level `attributes` always apply. The selected branch's `attributes` are merged into them at delivery time. The `delivery` name shown above is not a special Mastra API field. It is a custom attribute name used for this example, you can add any attribute names that suit your use case.
|
|
224
|
+
|
|
225
|
+
## Compatibility
|
|
226
|
+
|
|
227
|
+
Mastra still accepts legacy signal payloads such as `type: 'user-message'` and `type: 'system-reminder'`. It normalizes them internally to the new category and tag shape:
|
|
228
|
+
|
|
229
|
+
- `type: 'user-message'`: Normalizes to `type: 'user'` and `tagName: 'user'`
|
|
230
|
+
- `type: 'system-reminder'`: Normalizes to `type: 'reactive'` and `tagName: 'system-reminder'`
|
|
231
|
+
|
|
232
|
+
Existing stored signal rows and older clients continue to load through the compatibility layer.
|
|
233
|
+
|
|
234
|
+
> **Note:** Visit [Agent signals reference](https://mastra.ai/reference/agents/agent) for the full message, signal, and subscription types.
|
|
177
235
|
|
|
178
|
-
|
|
236
|
+
## Use HTTP routes
|
|
179
237
|
|
|
180
|
-
|
|
238
|
+
If you call Mastra over HTTP directly, use `POST /api/agents/:agentId/send-message` for immediate messages and `POST /api/agents/:agentId/queue-message` for next-turn messages. See [Server routes reference](https://mastra.ai/reference/server/routes) for request and response schemas.
|
|
181
239
|
|
|
182
240
|
## Use the client SDK
|
|
183
241
|
|
|
184
|
-
The JavaScript client exposes
|
|
242
|
+
The JavaScript client exposes thread signal APIs. Use `subscribeToThread()` before sending thread input so the client can render the stream that wakes from, or receives, the input.
|
|
185
243
|
|
|
186
244
|
```typescript
|
|
187
245
|
const agent = client.getAgent('supportAgent')
|
|
@@ -230,7 +288,10 @@ Use heartbeats together with client-side reconnect logic. Heartbeats reduce idle
|
|
|
230
288
|
|
|
231
289
|
## Related
|
|
232
290
|
|
|
291
|
+
- [`Agent.sendMessage()`](https://mastra.ai/reference/agents/agent)
|
|
292
|
+
- [`Agent.queueMessage()`](https://mastra.ai/reference/agents/agent)
|
|
233
293
|
- [`Agent.sendSignal()`](https://mastra.ai/reference/agents/agent)
|
|
234
294
|
- [`Agent.subscribeToThread()`](https://mastra.ai/reference/agents/agent)
|
|
295
|
+
- [Server agent routes](https://mastra.ai/reference/server/routes)
|
|
235
296
|
- [`client.getAgent().sendSignal()`](https://mastra.ai/reference/client-js/agents)
|
|
236
297
|
- [`client.getAgent().subscribeToThread()`](https://mastra.ai/reference/client-js/agents)
|
|
@@ -113,7 +113,7 @@ If you prefer not to use our automatic CLI tool, you can set up your project you
|
|
|
113
113
|
Add your API key:
|
|
114
114
|
|
|
115
115
|
```bash
|
|
116
|
-
|
|
116
|
+
GOOGLE_API_KEY=<your-api-key>
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
> **Note:** This guide uses Google Gemini, but you can use any supported [model provider](https://mastra.ai/models), including OpenAI, Anthropic, and more.
|
|
@@ -247,7 +247,7 @@ const agent = new Agent({
|
|
|
247
247
|
})
|
|
248
248
|
```
|
|
249
249
|
|
|
250
|
-
The model router automatically handles API key detection from environment variables (`OPENAI_API_KEY`, `
|
|
250
|
+
The model router automatically handles API key detection from environment variables (`OPENAI_API_KEY`, `GOOGLE_API_KEY`, `OPENROUTER_API_KEY`). Google models also fall back to `GOOGLE_GENERATIVE_AI_API_KEY`.
|
|
251
251
|
|
|
252
252
|
### Using AI SDK Packages
|
|
253
253
|
|
|
@@ -110,6 +110,7 @@ Before storage, all metric labels pass through a cardinality filter that blocks
|
|
|
110
110
|
## Next steps
|
|
111
111
|
|
|
112
112
|
- [Automatic metrics reference](https://mastra.ai/reference/observability/metrics/automatic-metrics)
|
|
113
|
+
- [Querying metrics](https://mastra.ai/docs/observability/metrics/querying)
|
|
113
114
|
- [Tracing overview](https://mastra.ai/docs/observability/tracing/overview)
|
|
114
115
|
- [Studio observability](https://mastra.ai/docs/studio/observability)
|
|
115
116
|
- [Observability overview](https://mastra.ai/docs/observability/overview)
|