@mastra/mcp-docs-server 1.2.1-alpha.2 → 1.2.1-alpha.5
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/overview.md +2 -0
- package/.docs/docs/agents/background-tasks.md +89 -14
- package/.docs/docs/agents/durable-agents.md +231 -0
- package/.docs/docs/agents/skills.md +186 -0
- package/.docs/docs/agents/using-tools.md +52 -0
- package/.docs/docs/evals/datasets/running-experiments.md +76 -0
- package/.docs/docs/getting-started/build-with-ai.md +273 -8
- package/.docs/docs/server/pubsub.md +2 -2
- package/.docs/docs/workflows/overview.md +71 -0
- package/.docs/docs/workspace/skills.md +9 -1
- package/.docs/guides/build-your-ui/ai-sdk-ui.md +3 -3
- package/.docs/guides/concepts/streaming.md +317 -0
- package/.docs/guides/getting-started/quickstart.md +1 -1
- package/.docs/models/gateways/openrouter.md +2 -2
- package/.docs/models/gateways/vercel.md +10 -1
- package/.docs/models/index.md +1 -1
- package/.docs/models/providers/baseten.md +1 -1
- package/.docs/models/providers/friendli.md +3 -2
- package/.docs/models/providers/lilac.md +7 -7
- package/.docs/models/providers/opencode.md +2 -1
- package/.docs/models/providers/siliconflow-cn.md +2 -1
- package/.docs/models/providers/wafer.ai.md +2 -1
- package/.docs/reference/agents/createSkill.md +78 -0
- package/.docs/reference/agents/durable-agent.md +30 -1
- package/.docs/reference/agents/getDefaultOptions.md +1 -1
- package/.docs/reference/agents/getDefaultStreamOptions.md +1 -1
- package/.docs/reference/agents/getSkill.md +58 -0
- package/.docs/reference/agents/inngest-agent.md +339 -0
- package/.docs/reference/agents/listSkills.md +53 -0
- package/.docs/reference/ai-sdk/handle-workflow-stream.md +1 -1
- package/.docs/reference/ai-sdk/workflow-route.md +1 -1
- package/.docs/reference/index.md +4 -0
- package/.docs/reference/processors/stream-error-retry-processor.md +32 -0
- package/.docs/reference/streaming/workflows/timeTravelStream.md +1 -1
- package/.docs/reference/tools/create-tool.md +1 -1
- package/.docs/reference/workflows/run.md +1 -1
- package/CHANGELOG.md +14 -0
- package/package.json +4 -4
- package/.docs/docs/build-with-ai/mcp-docs-server.md +0 -238
- package/.docs/docs/build-with-ai/skills.md +0 -63
- package/.docs/docs/streaming/background-task-streaming.md +0 -80
- package/.docs/docs/streaming/events.md +0 -148
- package/.docs/docs/streaming/overview.md +0 -136
- package/.docs/docs/streaming/tool-streaming.md +0 -189
- package/.docs/docs/streaming/workflow-streaming.md +0 -109
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> **Note:** The Agent Builder is part of the Mastra Enterprise Edition. Production deployments require a valid EE license. [Contact sales](https://mastra.ai/contact) for more information.
|
|
4
4
|
|
|
5
|
+
[YouTube video player](https://www.youtube-nocookie.com/embed/AbdgIu4Z07I)
|
|
6
|
+
|
|
5
7
|
The Agent Builder lets you build, configure, and operate Mastra agents all within the UI. It runs inside your Mastra server, persists everything to `Mastra.storage`, and supports multi-tenant agent workflows with RBAC and channel integrations.
|
|
6
8
|
|
|
7
9
|
- [**Configuration**](https://mastra.ai/docs/agent-builder/configuration): Toggle UI sections and pin admin-controlled defaults for every new agent.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Added in:** `@mastra/core@1.29.0`
|
|
4
4
|
|
|
5
|
-
Background tasks let an agent dispatch a long-running tool call without blocking the agentic loop. The tool returns an immediate acknowledgement, the LLM continues responding, and the task runs to completion in the background. When it finishes, its result is written to memory and if you use
|
|
5
|
+
Background tasks let an agent dispatch a long-running tool call without blocking the agentic loop. The tool returns an immediate acknowledgement, the LLM continues responding, and the task runs to completion in the background. When it finishes, its result is written to memory and if you use `stream()` with the [`untilIdle`](https://mastra.ai/reference/agents/agent) option the agent is re-invoked automatically so the result is processed in the same call.
|
|
6
6
|
|
|
7
7
|
## When to use background tasks
|
|
8
8
|
|
|
@@ -119,7 +119,7 @@ If the agent has `backgroundTasks.disabled: true`, every tool call runs synchron
|
|
|
119
119
|
|
|
120
120
|
## Background tasks related stream chunks
|
|
121
121
|
|
|
122
|
-
When a tool call dispatches as a background task, two streams may surface lifecycle events for it: the agent's own stream and the [`backgroundTaskManager.stream()`](https://mastra.ai/docs/
|
|
122
|
+
When a tool call dispatches as a background task, two streams may surface lifecycle events for it: the agent's own stream and the [`backgroundTaskManager.stream()`](https://mastra.ai/docs/agents/background-tasks) SSE stream. Each stream covers a different set of chunk types:
|
|
123
123
|
|
|
124
124
|
| Chunk type | When it fires | Emitted by |
|
|
125
125
|
| --------------------------- | -------------------------------------------------------------------------------------- | -------------- |
|
|
@@ -133,20 +133,20 @@ When a tool call dispatches as a background task, two streams may surface lifecy
|
|
|
133
133
|
| `background-task-suspended` | The tool called `suspend()` from inside its execute. | Manager stream |
|
|
134
134
|
| `background-task-resumed` | A suspended task was resumed via `manager.resume(taskId, resumeData)`. | Manager stream |
|
|
135
135
|
|
|
136
|
-
`agent.stream().fullStream` only emits the agent-loop chunks (`background-task-started`, `background-task-progress`) on its own. `agent.
|
|
136
|
+
`agent.stream().fullStream` only emits the agent-loop chunks (`background-task-started`, `background-task-progress`) on its own. `agent.stream()` with `untilIdle: true` emits the same two chunks and additionally subscribes to the manager pubsub for the run's memory scope and pipes the seven manager chunks (`background-task-running`, `background-task-output`, `background-task-completed`, `background-task-failed`, `background-task-cancelled`, `background-task-suspended`, `background-task-resumed`) into the same `fullStream`.
|
|
137
137
|
|
|
138
138
|
`backgroundTaskManager.stream()` only emits the seven manager chunks.
|
|
139
139
|
|
|
140
140
|
The full payload shapes are documented in the [background task chunks reference](https://mastra.ai/reference/streaming/ChunkType).
|
|
141
141
|
|
|
142
|
-
## Keep the agent stream open with `
|
|
142
|
+
## Keep the agent stream open with `untilIdle`
|
|
143
143
|
|
|
144
|
-
`agent.stream()` returns once the LLM emits a final response even if a background task is still running.
|
|
144
|
+
`agent.stream()` returns once the LLM emits a final response even if a background task is still running. Pass `untilIdle: true` when you want the stream to stay open until every dispatched background task has completed and the LLM has had a chance to respond to the result:
|
|
145
145
|
|
|
146
146
|
```typescript
|
|
147
|
-
const stream = await agent.
|
|
147
|
+
const stream = await agent.stream('Research solana for me', {
|
|
148
148
|
memory: { thread: 't1', resource: 'u1' },
|
|
149
|
-
|
|
149
|
+
untilIdle: true,
|
|
150
150
|
})
|
|
151
151
|
|
|
152
152
|
for await (const chunk of stream.fullStream) {
|
|
@@ -155,15 +155,22 @@ for await (const chunk of stream.fullStream) {
|
|
|
155
155
|
}
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
When a background task completes, the result is injected into the agent memory, `
|
|
158
|
+
When a background task completes, the result is injected into the agent memory, `stream()` re-enters the agentic loop so the LLM can react to it. The stream closes when no tasks are running and no completions are queued.
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
Customize the idle timeout by passing an object instead of `true`. The timer only runs while the wrapper is between turns, so a slow first token won't close the stream. The default is 5 minutes:
|
|
161
161
|
|
|
162
|
-
|
|
162
|
+
```typescript
|
|
163
|
+
const stream = await agent.stream('Research solana for me', {
|
|
164
|
+
memory: { thread: 't1', resource: 'u1' },
|
|
165
|
+
untilIdle: { maxIdleMs: 30_000 },
|
|
166
|
+
})
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> **Note:** Visit [`Agent.stream()`](https://mastra.ai/reference/agents/agent) for the full API.
|
|
163
170
|
|
|
164
171
|
### Aggregate properties
|
|
165
172
|
|
|
166
|
-
`
|
|
173
|
+
`stream()` with `untilIdle` returns a `MastraModelOutput` that looks like the one from a regular `stream()` call, but only `fullStream` spans the initial turn **and** any auto-continuations. Aggregate properties (`text`, `toolCalls`, `toolResults`, `finishReason`, `messageList`, `getFullOutput()`) still resolve against the **first turn's** internal buffer. If you need an aggregate view across continuations, consume `fullStream` yourself and accumulate.
|
|
167
174
|
|
|
168
175
|
## Subagents in the background
|
|
169
176
|
|
|
@@ -185,8 +192,9 @@ const supervisor = new Agent({
|
|
|
185
192
|
},
|
|
186
193
|
})
|
|
187
194
|
|
|
188
|
-
const stream = await supervisor.
|
|
195
|
+
const stream = await supervisor.stream('Research AI in education and write an article', {
|
|
189
196
|
memory: { thread: 't1', resource: 'u1' },
|
|
197
|
+
untilIdle: true,
|
|
190
198
|
})
|
|
191
199
|
```
|
|
192
200
|
|
|
@@ -263,7 +271,7 @@ await mastra.backgroundTaskManager?.resume(taskId, {
|
|
|
263
271
|
|
|
264
272
|
### What happens to the agent loop
|
|
265
273
|
|
|
266
|
-
When a task suspends mid-`
|
|
274
|
+
When a task suspends mid-`stream()` with `untilIdle`, the wrapper treats it as terminal for the current iteration and closes. To continue the agent immediately when the resume payload is in hand, call `agent.resumeStream(resumeData, { runId, toolCallId, memory, untilIdle: true })`: the resumed bg task runs to completion, its result lands in the message list, and the agent runs a follow-up turn — all on the same SSE connection. If you'd rather drive the resume out-of-band, call `mastra.backgroundTaskManager.resume(taskId, resumeData)` directly and the result still writes into the thread for the next user turn to pick up.
|
|
267
275
|
|
|
268
276
|
### Re-registering the executor on resume
|
|
269
277
|
|
|
@@ -296,10 +304,77 @@ export const mastra = new Mastra({
|
|
|
296
304
|
})
|
|
297
305
|
```
|
|
298
306
|
|
|
307
|
+
## Streaming
|
|
308
|
+
|
|
309
|
+
### Subscribe to all task events
|
|
310
|
+
|
|
311
|
+
Calling `stream()` with no filter returns a stream of every task event in the system. On connection, the stream emits a snapshot of all currently running tasks, then forwards live events as they happen.
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const bgManager = mastra.backgroundTaskManager
|
|
315
|
+
if (!bgManager) throw new Error('Background tasks are not enabled')
|
|
316
|
+
|
|
317
|
+
const controller = new AbortController()
|
|
318
|
+
const stream = bgManager.stream({ abortSignal: controller.signal })
|
|
319
|
+
|
|
320
|
+
for await (const chunk of stream) {
|
|
321
|
+
switch (chunk.type) {
|
|
322
|
+
case 'background-task-running':
|
|
323
|
+
console.log('started', chunk.payload.taskId, chunk.payload.toolName)
|
|
324
|
+
break
|
|
325
|
+
case 'background-task-completed':
|
|
326
|
+
console.log('done', chunk.payload.taskId, chunk.payload.result)
|
|
327
|
+
break
|
|
328
|
+
case 'background-task-failed':
|
|
329
|
+
console.error('failed', chunk.payload.taskId, chunk.payload.error)
|
|
330
|
+
break
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
The stream stays open until the caller's `AbortSignal` fires. Always pass an `abortSignal` so you can disconnect cleanly.
|
|
336
|
+
|
|
337
|
+
### Filter the stream
|
|
338
|
+
|
|
339
|
+
Pass any combination of filter options to narrow the events you receive. Filters apply to both the initial snapshot and the live event subscription.
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const stream = bgManager.stream({
|
|
343
|
+
agentId: 'researcher',
|
|
344
|
+
threadId: 't1',
|
|
345
|
+
resourceId: 'u1',
|
|
346
|
+
abortSignal: controller.signal,
|
|
347
|
+
})
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
| Filter | Description |
|
|
351
|
+
| ------------- | --------------------------------------------------- |
|
|
352
|
+
| `agentId` | Only events from tasks dispatched by this agent |
|
|
353
|
+
| `runId` | Only events from this specific agent run |
|
|
354
|
+
| `threadId` | Only events from tasks scoped to this memory thread |
|
|
355
|
+
| `resourceId` | Only events from tasks scoped to this resource |
|
|
356
|
+
| `taskId` | Only events for a single task |
|
|
357
|
+
| `abortSignal` | Closes the stream when the signal aborts |
|
|
358
|
+
|
|
359
|
+
### Look up task state directly
|
|
360
|
+
|
|
361
|
+
For one-off lookups instead of a live stream, use `getTask` and `listTasks`:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
const task = await mastra.backgroundTaskManager?.getTask(taskId)
|
|
365
|
+
const { tasks, total } = await mastra.backgroundTaskManager?.listTasks({
|
|
366
|
+
status: 'running',
|
|
367
|
+
agentId: 'researcher',
|
|
368
|
+
})
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
These read from storage rather than the pubsub stream, so they're suitable for paginated lists and detail views.
|
|
372
|
+
|
|
299
373
|
## Related
|
|
300
374
|
|
|
301
|
-
- [`Agent.
|
|
375
|
+
- [`Agent.stream()` reference](https://mastra.ai/reference/agents/agent)
|
|
302
376
|
- [backgroundTasks configuration reference](https://mastra.ai/reference/configuration)
|
|
377
|
+
- [Durable agents](https://mastra.ai/docs/agents/durable-agents)
|
|
303
378
|
- [Supervisor agents](https://mastra.ai/docs/agents/supervisor-agents)
|
|
304
379
|
- [Stream chunk types](https://mastra.ai/reference/streaming/ChunkType)
|
|
305
380
|
- [Storage](https://mastra.ai/docs/memory/storage)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Durable agents
|
|
2
|
+
|
|
3
|
+
**Added in:** `@mastra/core@1.45.0`
|
|
4
|
+
|
|
5
|
+
> **Warning:** Durable agents are currently in **beta**. APIs may change in future releases.
|
|
6
|
+
|
|
7
|
+
A durable agent wraps a regular [`Agent`](https://mastra.ai/docs/agents/overview) so the agentic loop runs inside a workflow. Events flow through [PubSub](https://mastra.ai/docs/server/pubsub), which means a client can disconnect and reconnect without missing chunks. The run state is persisted, so it survives process restarts.
|
|
8
|
+
|
|
9
|
+
## When to use durable agents
|
|
10
|
+
|
|
11
|
+
Use a durable agent when any of the following apply:
|
|
12
|
+
|
|
13
|
+
- The client may drop and reconnect mid-stream (mobile, spotty networks, long-running calls).
|
|
14
|
+
- The agentic loop may outlive a single HTTP request (background research, multi-step tool use).
|
|
15
|
+
- You need an observe/reconnect API where a second client picks up a stream that a first client started.
|
|
16
|
+
- You want [Inngest-powered execution](https://mastra.ai/guides/deployment/inngest) with step memoization, retries, and monitoring.
|
|
17
|
+
|
|
18
|
+
For short-lived, request-scoped calls where the client stays connected, a regular `Agent` with `stream()` or `generate()` is simpler.
|
|
19
|
+
|
|
20
|
+
## Quickstart
|
|
21
|
+
|
|
22
|
+
Wrap an existing agent with `createDurableAgent()` from `@mastra/core/agent/durable`:
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { Agent } from '@mastra/core/agent'
|
|
26
|
+
import { createDurableAgent } from '@mastra/core/agent/durable'
|
|
27
|
+
|
|
28
|
+
const agent = new Agent({
|
|
29
|
+
id: 'researcher',
|
|
30
|
+
name: 'Researcher',
|
|
31
|
+
instructions: 'You research topics thoroughly.',
|
|
32
|
+
model: 'openai/gpt-5.5',
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export const durableResearcher = createDurableAgent({ agent })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Register the durable agent with Mastra and call `stream()`:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { Mastra } from '@mastra/core'
|
|
42
|
+
import { durableResearcher } from './agents/researcher'
|
|
43
|
+
|
|
44
|
+
const mastra = new Mastra({
|
|
45
|
+
agents: { durableResearcher },
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const { output, runId, cleanup } = await durableResearcher.stream(
|
|
49
|
+
'Research quantum computing advances in 2025',
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
for await (const chunk of output.fullStream) {
|
|
53
|
+
// Process each chunk as it arrives
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Release PubSub subscriptions and clear the run from the registry.
|
|
57
|
+
// If you skip this, an automatic cleanup timer fires after the stream ends.
|
|
58
|
+
cleanup()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The returned `runId` identifies the execution. Pass it to `observe()` to reconnect from a different client.
|
|
62
|
+
|
|
63
|
+
> **Note:** Visit the [DurableAgent reference](https://mastra.ai/reference/agents/durable-agent) for the full configuration and method API.
|
|
64
|
+
|
|
65
|
+
## How it works
|
|
66
|
+
|
|
67
|
+
A durable agent adds three layers on top of a regular agent:
|
|
68
|
+
|
|
69
|
+
1. **Workflow execution**: `stream()` serializes the messages and options into a workflow input, then triggers the agentic loop inside a durable workflow. The workflow runs the same loop as `Agent.stream()` but each step can be memoized and replayed.
|
|
70
|
+
|
|
71
|
+
2. **PubSub streaming**: As the loop runs, chunks are published to a PubSub topic keyed by the run ID. The caller subscribes to this topic and pipes chunks into a `ReadableStream`. If the caller disconnects and reconnects, missed chunks are replayed from the cache.
|
|
72
|
+
|
|
73
|
+
3. **Cache layer**: An optional cache (in-memory by default, Redis or another backend in production) stores published events so that a late subscriber can catch up.
|
|
74
|
+
|
|
75
|
+
## Execution variants
|
|
76
|
+
|
|
77
|
+
Mastra provides three factory functions that produce durable agents. They differ in how the workflow is executed:
|
|
78
|
+
|
|
79
|
+
| Factory | Package | Best for |
|
|
80
|
+
| ---------------------- | ----------------- | -------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| `createDurableAgent()` | `@mastra/core` | Local development and single-process servers. You get a stream you can await directly. |
|
|
82
|
+
| `createEventedAgent()` | `@mastra/core` | Background execution. The workflow starts without blocking, and you consume chunks through PubSub. |
|
|
83
|
+
| `createInngestAgent()` | `@mastra/inngest` | Production deployments. Inngest adds step memoization, retries, and a monitoring dashboard. |
|
|
84
|
+
|
|
85
|
+
All three return an object you register with `Mastra` the same way as a regular agent. `createDurableAgent()` and `createEventedAgent()` return class instances that extend `Agent`. `createInngestAgent()` returns a Proxy-backed object that forwards `Agent` methods to the underlying agent.
|
|
86
|
+
|
|
87
|
+
### In-process with `createDurableAgent()`
|
|
88
|
+
|
|
89
|
+
Wrap your agent and call `stream()`. You get a `DurableAgentStreamResult` back in the same process. No external infrastructure is required, so this is the fastest way to get started:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Agent } from '@mastra/core/agent'
|
|
93
|
+
import { createDurableAgent } from '@mastra/core/agent/durable'
|
|
94
|
+
|
|
95
|
+
const agent = new Agent({
|
|
96
|
+
id: 'helper',
|
|
97
|
+
instructions: 'You are a helpful assistant.',
|
|
98
|
+
model: 'openai/gpt-5.5',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
export const durableHelper = createDurableAgent({ agent })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Fire-and-forget with `createEventedAgent()`
|
|
105
|
+
|
|
106
|
+
The workflow starts in the background without blocking the caller. You still receive chunks through PubSub, so `stream()` returns a result you can consume. The HTTP handler that triggered the run does not need to wait for the workflow to finish:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { Agent } from '@mastra/core/agent'
|
|
110
|
+
import { createEventedAgent } from '@mastra/core/agent/durable'
|
|
111
|
+
|
|
112
|
+
const agent = new Agent({
|
|
113
|
+
id: 'writer',
|
|
114
|
+
instructions: 'You write articles.',
|
|
115
|
+
model: 'openai/gpt-5.5',
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
export const eventedWriter = createEventedAgent({ agent })
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Inngest-powered with `createInngestAgent()`
|
|
122
|
+
|
|
123
|
+
Run the workflow on the [Inngest](https://www.inngest.com/docs) platform. Each tool call becomes a memoized step that Inngest can retry independently, and you get a dashboard for monitoring runs:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { Agent } from '@mastra/core/agent'
|
|
127
|
+
import { createInngestAgent } from '@mastra/inngest'
|
|
128
|
+
import { Inngest } from 'inngest'
|
|
129
|
+
|
|
130
|
+
const inngest = new Inngest({ id: 'my-app' })
|
|
131
|
+
|
|
132
|
+
const agent = new Agent({
|
|
133
|
+
id: 'analyst',
|
|
134
|
+
instructions: 'You analyze data.',
|
|
135
|
+
model: 'openai/gpt-5.5',
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
export const inngestAnalyst = createInngestAgent({ agent, inngest })
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
> **Note:** Visit the [`createInngestAgent()` reference](https://mastra.ai/reference/agents/inngest-agent) for the full API, including Inngest-specific options like PubSub and cache configuration.
|
|
142
|
+
|
|
143
|
+
## Resumable streams
|
|
144
|
+
|
|
145
|
+
Durable agents support resumable streams through PubSub and an event cache. When a client disconnects mid-stream, the cache continues storing events. The same client can reconnect by calling `observe()` with the `runId`:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const { output, cleanup } = await durableResearcher.observe(runId)
|
|
149
|
+
|
|
150
|
+
for await (const chunk of output.fullStream) {
|
|
151
|
+
// Chunks from the run, including any missed while disconnected
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
cleanup()
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`createDurableAgent()` and `createEventedAgent()` use an in-memory cache by default, which means resumable streams work within a single process. For production, provide a persistent cache backend (e.g., Redis) so cached events survive process restarts:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { createDurableAgent } from '@mastra/core/agent/durable'
|
|
161
|
+
import { RedisServerCache } from '@mastra/redis'
|
|
162
|
+
|
|
163
|
+
const cache = new RedisServerCache({ url: 'redis://localhost:6379' })
|
|
164
|
+
|
|
165
|
+
export const durableAgent = createDurableAgent({
|
|
166
|
+
agent,
|
|
167
|
+
cache,
|
|
168
|
+
})
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`createInngestAgent()` does not enable caching by default. Pass a `cache` option or register the agent with a `Mastra` instance that has a `serverCache` configured to enable resumable streams.
|
|
172
|
+
|
|
173
|
+
## Streaming with background tasks
|
|
174
|
+
|
|
175
|
+
Durable agents support the same [`untilIdle`](https://mastra.ai/reference/agents/durable-agent) option as regular agents. When `untilIdle` is set, `stream()` keeps the connection open across background-task continuations until the agent is idle:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const { output, cleanup } = await durableAgent.stream('Research and summarize the topic', {
|
|
179
|
+
untilIdle: true,
|
|
180
|
+
memory: { thread: 'thread-1', resource: 'user-1' },
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
for await (const chunk of output.fullStream) {
|
|
184
|
+
// Chunks from the initial turn AND any follow-up turns triggered by
|
|
185
|
+
// background task completions
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
cleanup()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Pass `{ maxIdleMs }` to customize the idle timeout (defaults to 5 minutes):
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
await durableAgent.stream('Research topic', {
|
|
195
|
+
untilIdle: { maxIdleMs: 30_000 },
|
|
196
|
+
memory: { thread: 'thread-1', resource: 'user-1' },
|
|
197
|
+
})
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
> **Note:** Visit [Background tasks](https://mastra.ai/docs/agents/background-tasks) for the full background task guide, including configuration, subagents, and suspend/resume.
|
|
201
|
+
|
|
202
|
+
## Cleanup
|
|
203
|
+
|
|
204
|
+
Every `stream()` and `observe()` call returns a `cleanup` function. Calling it unsubscribes from PubSub and removes the run from the internal registry. If you forget to call it, an automatic timer fires after the stream ends, but calling `cleanup()` yourself frees resources immediately.
|
|
205
|
+
|
|
206
|
+
## Tool approval
|
|
207
|
+
|
|
208
|
+
Durable agents support tool approval (human-in-the-loop). When a tool call requires approval, the workflow suspends, emits an `onSuspended` callback, and waits for the caller to resume with `resume()`:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const { output, runId, cleanup } = await durableAgent.stream('Delete the old records', {
|
|
212
|
+
requireToolApproval: true,
|
|
213
|
+
onSuspended: ({ toolCallId, toolName, args }) => {
|
|
214
|
+
// Notify the user and ask for approval
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Resume the suspended run after approval:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
await durableAgent.resume(runId, { approved: true })
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Related
|
|
226
|
+
|
|
227
|
+
- [DurableAgent reference](https://mastra.ai/reference/agents/durable-agent)
|
|
228
|
+
- [`createInngestAgent()` reference](https://mastra.ai/reference/agents/inngest-agent)
|
|
229
|
+
- [Background tasks](https://mastra.ai/docs/agents/background-tasks)
|
|
230
|
+
- [Inngest deployment guide](https://mastra.ai/guides/deployment/inngest)
|
|
231
|
+
- [Agent overview](https://mastra.ai/docs/agents/overview)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Agent skills
|
|
2
|
+
|
|
3
|
+
Skills are reusable instructions that teach agents how to perform specific tasks. They follow the [Agent Skills specification](https://agentskills.io).
|
|
4
|
+
|
|
5
|
+
You can attach skills directly to an agent without setting up a workspace, filesystem, or sandbox. This is useful when you want portable, code-defined capabilities that travel with your agent definition.
|
|
6
|
+
|
|
7
|
+
## When to use agent-level skills
|
|
8
|
+
|
|
9
|
+
Use agent-level skills when:
|
|
10
|
+
|
|
11
|
+
- You want self-contained agents that don't depend on a workspace
|
|
12
|
+
- Skills are defined in code and don't need filesystem discovery
|
|
13
|
+
- You're building packages or libraries that ship agent capabilities
|
|
14
|
+
- You need per-request skill resolution based on context
|
|
15
|
+
|
|
16
|
+
For filesystem-based skill discovery across a project, use [workspace skills](https://mastra.ai/docs/workspace/skills) instead.
|
|
17
|
+
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
Define a skill inline and attach it to an agent:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Agent } from '@mastra/core/agent'
|
|
24
|
+
import { createSkill } from '@mastra/core/skills'
|
|
25
|
+
|
|
26
|
+
const codeReview = createSkill({
|
|
27
|
+
name: 'code-review',
|
|
28
|
+
description: 'Use when reviewing code changes.',
|
|
29
|
+
instructions: `
|
|
30
|
+
When reviewing code:
|
|
31
|
+
1. Check for correctness and edge cases
|
|
32
|
+
2. Verify style consistency
|
|
33
|
+
3. Look for potential bugs
|
|
34
|
+
`,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const reviewer = new Agent({
|
|
38
|
+
id: 'reviewer',
|
|
39
|
+
model: 'openai/gpt-5.5',
|
|
40
|
+
instructions: 'You are a code review assistant.',
|
|
41
|
+
skills: [codeReview],
|
|
42
|
+
})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The agent automatically gets `skill`, `skill_read`, and `skill_search` tools so it can discover and load skills during conversations.
|
|
46
|
+
|
|
47
|
+
## Defining inline skills
|
|
48
|
+
|
|
49
|
+
Use `createSkill()` to create skills entirely in code:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { createSkill } from '@mastra/core/skills'
|
|
53
|
+
|
|
54
|
+
export const releaseChecklist = createSkill({
|
|
55
|
+
name: 'release-checklist',
|
|
56
|
+
description: 'Use when preparing a release.',
|
|
57
|
+
instructions: `
|
|
58
|
+
## Release Checklist
|
|
59
|
+
1. Run the full test suite
|
|
60
|
+
2. Update CHANGELOG.md
|
|
61
|
+
3. Bump version numbers
|
|
62
|
+
4. Create a git tag
|
|
63
|
+
`,
|
|
64
|
+
references: {
|
|
65
|
+
'changelog-format.md': '# Changelog Format\nUse Keep a Changelog...',
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The `references` field bundles supporting documents that the agent can read with the `skill_read` tool, just like `references/` files in a filesystem skill.
|
|
71
|
+
|
|
72
|
+
> **Note:** Visit [`createSkill()` reference](https://mastra.ai/reference/agents/createSkill) for the full API.
|
|
73
|
+
|
|
74
|
+
## Filesystem path skills
|
|
75
|
+
|
|
76
|
+
Point to skill directories on disk without a workspace:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Agent } from '@mastra/core/agent'
|
|
80
|
+
import { createSkill } from '@mastra/core/skills'
|
|
81
|
+
|
|
82
|
+
export const agent = new Agent({
|
|
83
|
+
id: 'my-agent',
|
|
84
|
+
model: 'openai/gpt-5.5',
|
|
85
|
+
skills: [
|
|
86
|
+
'./skills/code-review', // path to a SKILL.md directory
|
|
87
|
+
'./skills/testing', // another filesystem skill
|
|
88
|
+
createSkill({
|
|
89
|
+
/* ... */
|
|
90
|
+
}), // inline skill
|
|
91
|
+
],
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Filesystem paths use `LocalSkillSource` under the hood, which reads `SKILL.md` files following the same format as [workspace skills](https://mastra.ai/docs/workspace/skills).
|
|
96
|
+
|
|
97
|
+
## Dynamic skills
|
|
98
|
+
|
|
99
|
+
For per-request skill resolution, pass a function:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { Agent } from '@mastra/core/agent'
|
|
103
|
+
import { createSkill } from '@mastra/core/skills'
|
|
104
|
+
|
|
105
|
+
const devSkill = createSkill({
|
|
106
|
+
name: 'dev-tools',
|
|
107
|
+
description: 'Developer productivity tools.',
|
|
108
|
+
instructions: '...',
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const supportSkill = createSkill({
|
|
112
|
+
name: 'support-guide',
|
|
113
|
+
description: 'Customer support guidelines.',
|
|
114
|
+
instructions: '...',
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
export const agent = new Agent({
|
|
118
|
+
id: 'dynamic-agent',
|
|
119
|
+
model: 'openai/gpt-5.5',
|
|
120
|
+
skills: ({ requestContext }) => {
|
|
121
|
+
const role = requestContext.get('userRole')
|
|
122
|
+
if (role === 'developer') return [devSkill]
|
|
123
|
+
return [supportSkill]
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The resolver function receives `{ requestContext }` and returns a `SkillInput[]` array or a `Promise<SkillInput[]>`.
|
|
129
|
+
|
|
130
|
+
## Merging with workspace skills
|
|
131
|
+
|
|
132
|
+
When an agent has both `skills` and a workspace with skills configured, they merge. Agent-level skills take precedence on name conflicts:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { Agent } from '@mastra/core/agent'
|
|
136
|
+
import { Workspace, LocalFilesystem } from '@mastra/core/workspace'
|
|
137
|
+
import { createSkill } from '@mastra/core/skills'
|
|
138
|
+
|
|
139
|
+
const workspace = new Workspace({
|
|
140
|
+
filesystem: new LocalFilesystem({ basePath: './workspace' }),
|
|
141
|
+
skills: ['skills'], // provides "code-review" skill
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
const customReview = createSkill({
|
|
145
|
+
name: 'code-review', // same name as workspace skill
|
|
146
|
+
description: 'Custom review process.',
|
|
147
|
+
instructions: '...',
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
export const agent = new Agent({
|
|
151
|
+
id: 'reviewer',
|
|
152
|
+
model: 'openai/gpt-5.5',
|
|
153
|
+
workspace,
|
|
154
|
+
skills: [customReview], // agent-level "code-review" wins
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Programmatic skill access
|
|
159
|
+
|
|
160
|
+
Use `agent.getSkill()` and `agent.listSkills()` to access skills from application code (e.g., in workflows or API routes):
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { reviewer } from '../mastra/agents'
|
|
164
|
+
|
|
165
|
+
// Get a specific skill by name
|
|
166
|
+
const skill = await reviewer.getSkill('code-review')
|
|
167
|
+
if (skill) {
|
|
168
|
+
console.log(skill.instructions)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// List all available skills
|
|
172
|
+
const allSkills = await reviewer.listSkills()
|
|
173
|
+
for (const meta of allSkills) {
|
|
174
|
+
console.log(`${meta.name}: ${meta.description}`)
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
> **Note:** Visit [`.getSkill()` reference](https://mastra.ai/reference/agents/getSkill) and [`.listSkills()` reference](https://mastra.ai/reference/agents/listSkills) for the full API.
|
|
179
|
+
|
|
180
|
+
## Related
|
|
181
|
+
|
|
182
|
+
- [Workspace skills](https://mastra.ai/docs/workspace/skills)
|
|
183
|
+
- [`createSkill()` reference](https://mastra.ai/reference/agents/createSkill)
|
|
184
|
+
- [`.getSkill()` reference](https://mastra.ai/reference/agents/getSkill)
|
|
185
|
+
- [`.listSkills()` reference](https://mastra.ai/reference/agents/listSkills)
|
|
186
|
+
- [Agent skills specification](https://agentskills.io)
|
|
@@ -292,6 +292,58 @@ await supportAgent.generate('Look up the order status', {
|
|
|
292
292
|
|
|
293
293
|
Agent-level and per-execution hooks merge per key: passing only `beforeToolCall` at execution time keeps the agent-level `afterToolCall`.
|
|
294
294
|
|
|
295
|
+
## Streaming
|
|
296
|
+
|
|
297
|
+
Tools support lifecycle hooks that allow you to monitor different stages of tool execution during streaming. These hooks are particularly useful for logging or analytics.
|
|
298
|
+
|
|
299
|
+
For generic `writer` API usage, see [Streaming](https://mastra.ai/guides/concepts/streaming).
|
|
300
|
+
|
|
301
|
+
### Available Hooks
|
|
302
|
+
|
|
303
|
+
- **onInputStart**: Called when tool call input streaming begins
|
|
304
|
+
- **onInputDelta**: Called for each chunk of input as it streams in
|
|
305
|
+
- **onInputAvailable**: Called when complete input is parsed and validated
|
|
306
|
+
- **onOutput**: Called after the tool successfully executes with the output
|
|
307
|
+
|
|
308
|
+
For detailed documentation on all lifecycle hooks, see the [createTool() reference](https://mastra.ai/reference/tools/create-tool).
|
|
309
|
+
|
|
310
|
+
### Example: Using `onInputAvailable` and `onOutput`
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { createTool } from '@mastra/core/tools'
|
|
314
|
+
import { z } from 'zod'
|
|
315
|
+
|
|
316
|
+
export const weatherTool = createTool({
|
|
317
|
+
id: 'weather-tool',
|
|
318
|
+
description: 'Get weather information',
|
|
319
|
+
inputSchema: z.object({
|
|
320
|
+
city: z.string(),
|
|
321
|
+
}),
|
|
322
|
+
outputSchema: z.object({
|
|
323
|
+
temperature: z.number(),
|
|
324
|
+
conditions: z.string(),
|
|
325
|
+
}),
|
|
326
|
+
// Called when the complete input is available
|
|
327
|
+
onInputAvailable: ({ input, toolCallId }) => {
|
|
328
|
+
console.log(`Weather requested for: ${input.city}`)
|
|
329
|
+
},
|
|
330
|
+
execute: async input => {
|
|
331
|
+
const weather = await fetchWeather(input.city)
|
|
332
|
+
return weather
|
|
333
|
+
},
|
|
334
|
+
// Called after successful execution
|
|
335
|
+
onOutput: ({ output, toolName }) => {
|
|
336
|
+
console.log(`${toolName} result: ${output.temperature}°F, ${output.conditions}`)
|
|
337
|
+
},
|
|
338
|
+
})
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Streaming tool input in UIs
|
|
342
|
+
|
|
343
|
+
When a model generates a tool call, the arguments arrive incrementally as `tool-call-delta` stream chunks before the final `tool-call` chunk. UIs can listen for the corresponding `tool_input_start`, `tool_input_delta`, and `tool_input_end` events to render tool arguments as they stream in — for example, showing a file path or command immediately rather than waiting for the complete tool call.
|
|
344
|
+
|
|
345
|
+
Using a partial JSON parser on the accumulated `argsTextDelta` fragments lets you extract usable argument values before the JSON is complete. This enables features like live diff previews for edit tools, streaming file content for write tools, and instant display of search patterns or file paths.
|
|
346
|
+
|
|
295
347
|
## Control tool selection
|
|
296
348
|
|
|
297
349
|
Pass `toolChoice` or `activeTools` to `.generate()` or `.stream()` to control which tools the agent uses at runtime.
|