@mastra/core
Version:
436 lines (332 loc) • 17.4 kB
Markdown
> Discover all available pages from the documentation index: https://mastra.ai/llms.txt
# Subagents
**Added in:** `/core@1.8.0`
Subagents are specialized agents that another agent can delegate tasks to. Add them to the parent agent's `agents` property, then call [`Agent.stream()`](https://mastra.ai/reference/streaming/agents/stream) or [`Agent.generate()`](https://mastra.ai/reference/agents/generate). The parent agent uses its instructions and each subagent's `description` to decide when and how to delegate tasks.
## When to use subagents
Use subagents when a task requires agents with different specializations to work together. The parent agent decides when to delegate and passes context to each subagent. It then synthesizes their results.
Common use cases:
- Research and writing workflows where one agent gathers data and another produces content
- Multi-step tasks that need different expertise at each stage
- Tasks where you need fine-grained control over delegation behavior
> **Note:** A parent agent that coordinates subagents is often called a supervisor. The supervisor pattern is one approach to building multi-agent systems in Mastra. For other patterns, read the [conceptual overview](https://mastra.ai/guides/concepts/multi-agent-systems).
## Quickstart
Define subagents with clear descriptions, then add them to a parent agent:
```typescript
import { Agent } from '/core/agent'
import { Memory } from '/memory'
import { LibSQLStore } from '/libsql'
const researchAgent = new Agent({
id: 'research-agent',
description: 'Gathers factual information and returns bullet-point summaries.',
model: 'openai/gpt-5-mini',
})
const writingAgent = new Agent({
id: 'writing-agent',
description: 'Transforms research into well-structured articles.',
model: 'openai/gpt-5-mini',
})
const parentAgent = new Agent({
id: 'parent-agent',
instructions: `You coordinate research and writing using specialized agents.
Delegate to research-agent for facts, then writing-agent for content.`,
model: 'openai/gpt-5.6-sol',
agents: { researchAgent, writingAgent },
memory: new Memory({
storage: new LibSQLStore({ id: 'storage', url: 'file:mastra.db' }),
}),
})
const stream = await parentAgent.stream('Research AI in education and write an article', {
maxSteps: 10,
})
for await (const chunk of stream.textStream) {
process.stdout.write(chunk)
}
```
## Delegation hooks
Delegation hooks let you intercept, modify, or reject delegations as they happen. Configure them under the `delegation` option, either in the agent's `defaultOptions` or per-call.
### `onDelegationStart`
Called before the parent agent delegates to a subagent. Return an object to control the delegation:
- `proceed: true`: Allow the delegation (default behavior)
- `proceed: false`: Reject the delegation with a `rejectionReason`
- `modifiedPrompt`: Rewrite the prompt sent to the subagent
- `modifiedMaxSteps`: Limit the subagent's iteration count
```typescript
const stream = await parentAgent.stream('Research AI trends', {
maxSteps: 10,
delegation: {
onDelegationStart: async context => {
console.log(`Delegating to: ${context.primitiveId}`)
// Modify the prompt for a specific agent
if (context.primitiveId === 'research-agent') {
return {
proceed: true,
modifiedPrompt: `${context.prompt}\n\nFocus on 2024-2025 data.`,
modifiedMaxSteps: 5,
}
}
// Reject delegation after too many iterations
if (context.iteration > 8) {
return {
proceed: false,
rejectionReason: 'Max iterations reached. Synthesize current findings.',
}
}
return { proceed: true }
},
},
})
```
The `context` object includes:
| Property | Description |
| ------------- | ----------------------------------------- |
| `primitiveId` | The ID of the subagent being delegated to |
| `prompt` | The prompt the parent agent is sending |
| `iteration` | Current iteration number |
### `onDelegationComplete`
Called after a delegation finishes. Use it to inspect results or provide feedback, or alternatively stop execution:
- `context.bail()`: Stop the parent agent's loop immediately
- Return `{ feedback: '...' }`: Add feedback that gets saved to the parent agent's memory and is visible to subsequent iterations
```typescript
const stream = await parentAgent.stream('Research AI trends', {
maxSteps: 10,
delegation: {
onDelegationComplete: async context => {
console.log(`Completed: ${context.primitiveId}`)
// Bail on errors
if (context.error) {
context.bail()
return {
feedback: `Delegation to ${context.primitiveId} failed: ${context.error}. Try a different approach.`,
}
}
},
},
})
```
The `context` object includes:
| Property | Description |
| ------------- | ---------------------------------------- |
| `primitiveId` | The ID of the subagent that ran |
| `result` | The subagent's response |
| `error` | Error if the delegation failed |
| `bail()` | Function to stop the parent agent's loop |
## Message filtering
By default, subagents receive the full conversation context from the parent agent. Use `messageFilter` to control what messages are shared, for example, to remove sensitive data or limit context size.
```typescript
const stream = await parentAgent.stream('Research AI trends', {
maxSteps: 10,
delegation: {
messageFilter: ({ messages, primitiveId, prompt }) => {
// Remove messages containing sensitive data
return messages
.filter(msg => {
const content =
typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
return !content.includes('confidential')
})
.slice(-10) // Only pass the last 10 messages
},
},
})
```
The callback receives `messages` (the full conversation history), `primitiveId` (the subagent ID), and `prompt` (the delegation prompt). Return the filtered array of messages.
## Subagent result context
When a subagent completes, the parent agent's model receives the subagent's text response in later iterations. Nested tool calls and subagent metadata, such as thread and resource IDs, aren't added to the parent agent's model context.
Application code and UI integrations can still inspect `subAgentToolResults` and the rest of the raw delegation result in the tool result payload.
This keeps debugging and display data available without sending nested tool arguments or outputs back into the parent agent's next model call.
Set `includeSubAgentToolResultsInModelContext` to include the full subagent result, including nested tool results and subagent metadata, in the parent agent's model context.
```typescript
await parentAgent.generate('Research AI trends', {
delegation: {
includeSubAgentToolResultsInModelContext: true,
},
})
```
## Iteration monitoring
`onIterationComplete` is called after each iteration of the parent agent's loop. Use it to monitor execution or guide the next iteration. You can also stop execution early.
```typescript
const stream = await parentAgent.stream('Research AI trends', {
maxSteps: 10,
onIterationComplete: async context => {
console.log(`Iteration ${context.iteration}/${context.maxIterations}`)
console.log(`Finish reason: ${context.finishReason}`)
// Inject feedback to guide the agent
if (!context.text.includes('recommendations')) {
return {
continue: true,
feedback: 'Please include specific recommendations in your analysis.',
}
}
// Stop early when the response is sufficient
if (context.text.length > 1000 && context.finishReason === 'stop') {
return { continue: false }
}
return { continue: true }
},
})
```
Return `{ continue: true }` to keep iterating, or `{ continue: false }` to stop. Include optional `feedback` to inject guidance into the conversation. When `feedback` is combined with `continue: false`, the model may get one final turn to produce a text response incorporating the feedback, but only if the current iteration is still active (e.g., after tool calls), otherwise no extra turn is granted.
## Memory isolation
Mastra isolates subagent memory during delegation. Subagents receive the full conversation context for better decision-making, but only their specific delegation prompt and response are saved to their memory.
How it works:
1. **Full context forwarded**: When the parent agent delegates, the subagent receives all messages from the parent agent's conversation
2. **Scoped memory saves**: Only the delegation prompt and the subagent's response are saved to the subagent's memory
3. **Fresh thread per invocation**: Each delegation uses a unique thread ID, ensuring clean separation
As a result, subagents have the context they need without cluttering their memory with the parent agent's entire conversation. Visit [memory in multi-agent systems](https://mastra.ai/docs/memory/overview) for more details.
## Tool approval propagation
Tool approvals propagate through the delegation chain. When a subagent uses a tool with `requireApproval: true` or calls `suspend()`, the approval request surfaces in the parent agent's stream.
```typescript
const sensitiveDataTool = createTool({
id: 'get-user-data',
requireApproval: true,
execute: async input => {
return await database.getUserData(input.userId)
},
})
const dataAgent = new Agent({
id: 'data-agent',
tools: { sensitiveDataTool },
})
const parentAgent = new Agent({
id: 'parent-agent',
agents: { dataAgent },
memory: new Memory(),
})
const stream = await parentAgent.stream('Get data for user 123')
for await (const chunk of stream.fullStream) {
if (chunk.type === 'tool-call-approval') {
console.log('Tool requires approval:', chunk.payload.toolName)
}
}
```
## Cancellation
When you pass an `abortSignal` to the parent agent's [`stream()`](https://mastra.ai/reference/streaming/agents/stream) or [`generate()`](https://mastra.ai/reference/agents/generate) call, Mastra forwards that same signal to delegated subagents. Calling `AbortController.abort()` cancels in-flight subagent runs at their next step instead of letting them run to completion.
```typescript
const controller = new AbortController()
const stream = await parentAgent.stream('Research AI trends', {
abortSignal: controller.signal,
})
// Cancel the parent agent and any in-flight subagents
controller.abort()
```
## Task completion scoring
Agents don't always produce a complete, correct output on the first try. Task completion scorers can help by validating whether the task is complete after each iteration. If validation fails, the parent agent continues iterating. Feedback from failed scorers is included in the conversation context so subagents can see what was missing.
```typescript
import { createScorer } from '/core/evals'
const taskCompleteScorer = createScorer({
id: 'task-complete',
name: 'Task Completeness',
}).generateScore(async context => {
const text = (context.run.output || '').toString()
const hasAnalysis = text.includes('analysis')
const hasRecommendations = text.includes('recommendation')
return hasAnalysis && hasRecommendations ? 1 : 0
})
const stream = await parentAgent.stream('Research AI in education', {
maxSteps: 10,
isTaskComplete: {
scorers: [taskCompleteScorer],
strategy: 'all',
onComplete: async result => {
console.log('Task complete:', result.complete)
},
},
})
```
### Rubric scorer
The built-in rubric scorer lets you define what "correct" looks like as a checklist and have the agent self-evaluate and iterate until every criterion is satisfied or `maxSteps` is reached.
It works as an **LLM-as-judge** scorer. After each iteration, a separate grader model reviews the agent's output against the rubric. The loop ends when every required criterion passes. A failed criterion adds its feedback to the conversation so the agent can try again.
This is most effective for tasks with clear, verifiable success criteria. You can use it like so:
```typescript
import { Agent } from '/core/agent'
import { createRubricScorer } from '/evals/scorers/prebuilt'
const parentAgent = new Agent({
id: 'parent-agent',
instructions: 'You coordinate research and writing using specialized agents.',
model: 'openai/gpt-5.6-sol',
agents: { researchAgent, writingAgent },
})
const rubricScorer = createRubricScorer({
model: 'openai/gpt-5-mini',
criteria: [
{ description: 'The response includes an analysis section' },
{ description: 'The response includes concrete recommendations' },
],
})
const stream = await parentAgent.stream('Research AI in education', {
maxSteps: 10,
isTaskComplete: {
scorers: [rubricScorer],
strategy: 'all',
},
})
```
For full API details, see the [rubric scorer reference](https://mastra.ai/reference/evals/rubric).
## Writing effective instructions
Clear instructions are essential for effective delegation.
The parent agent's `instructions` should specify the available resources and when to use each one. They should also define coordination behavior and success criteria.
Each subagent should have a clear `description` that explains its purpose and return format, including when the parent agent should use it.
The parent agent uses these descriptions to make delegation decisions.
```typescript
const parentAgent = new Agent({
id: 'parent-agent',
instructions: `You coordinate research and writing tasks.
Available resources:
- researchAgent: Gathers factual data and sources (returns bullet points)
- writingAgent: Transforms research into narrative content (returns full paragraphs)
Delegation strategy:
1. For research requests: Delegate to researchAgent first
2. For writing requests: Delegate to writingAgent
3. For complex requests: Delegate to researchAgent first, then writingAgent
Success criteria:
- All user questions are fully answered
- Response is well-formatted and complete`,
agents: { researchAgent, writingAgent },
})
```
## Running subagents in the background
Subagent invocations are dispatched as tool calls, so they can run as [background tasks](https://mastra.ai/docs/long-running-agents/background-tasks). This is useful when one or more delegations are long-running and you don't want them to block the parent agent's response.
Enable the [backgroundTasks manager](https://mastra.ai/reference/configuration) on the Mastra instance, then opt subagents in on the parent agent:
```typescript
const parentAgent = new Agent({
id: 'parent-agent',
instructions: 'Coordinate research and writing using the available agents.',
model: 'openai/gpt-5.6-sol',
agents: { researchAgent, writingAgent },
backgroundTasks: {
tools: {
researchAgent: { enabled: true, timeoutMs: 900_000 },
writingAgent: { enabled: true, timeoutMs: 900_000 },
},
},
})
const stream = await parentAgent.streamUntilIdle('Research AI in education and write an article', {
memory: { thread: 't1', resource: 'u1' },
})
```
Use [`streamUntilIdle()`](https://mastra.ai/reference/streaming/agents/streamUntilIdle) instead of `stream()` so the stream stays open until the subagents complete and the parent agent has had a chance to respond to their results.
If a subagent isn't listed under the parent agent's `backgroundTasks.tools` but has its own background-eligible tools, the parent agent still dispatches the subagent as a background task and inherits its config. See [Inheriting from the subagent](https://mastra.ai/docs/long-running-agents/background-tasks) for details.
## Subagent versioning
When using the [editor](https://mastra.ai/docs/editor/overview), you can control which stored version of each subagent the parent agent uses at runtime. Set version overrides on the Mastra instance or per invocation:
```typescript
const result = await parentAgent.generate('Research and write about AI safety', {
versions: {
agents: {
'research-agent': { status: 'published' },
'writing-agent': { versionId: 'draft-456' },
},
},
})
```
Version overrides propagate automatically through delegation. See [Subagent versioning](https://mastra.ai/reference/editor/versioning) for details on resolution order and server API usage.
## Related
- [Background tasks](https://mastra.ai/docs/long-running-agents/background-tasks)
- [Subagent versioning](https://mastra.ai/reference/editor/versioning)
- [Guide: Research coordinator](https://mastra.ai/guides/guide/research-coordinator)
- [Agent.stream() reference](https://mastra.ai/reference/streaming/agents/stream)
- [Agent.streamUntilIdle() reference](https://mastra.ai/reference/streaming/agents/streamUntilIdle)
- [Agent.generate() reference](https://mastra.ai/reference/agents/generate)
- [Agent approval](https://mastra.ai/docs/agents/agent-approval)
- [Memory in multi-agent systems](https://mastra.ai/docs/memory/overview)
- [Concept: Multi-agent systems](https://mastra.ai/guides/concepts/multi-agent-systems)
- 📹 [Mastra supervisor agents workshop](https://www.youtube.com/watch?v=FNb2fL9WhQg\&t=1872s)