Agent networks
Agent networks are deprecated and will be removed in a future major release. Supervisor agents using agent.stream() or agent.generate() are now the recommended approach. It provides the same multi-agent coordination with better control, a simpler API, and easier debugging.
See the migration guide to upgrade.
A routing agent uses an LLM to interpret a request and decide which primitives (subagents, workflows, or tools) to call, in what order, and with what data.
Create an agent networkDirect link to Create an agent network
Configure a routing agent with agents, workflows, and tools. Memory is required as .network() uses it to store task history and determine when a task is complete.
Each primitive needs a clear description so the routing agent can decide which to use. For workflows and tools, inputSchema and outputSchema also help the router determine the right inputs.
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
import { researchAgent } from './research-agent'
import { writingAgent } from './writing-agent'
import { cityWorkflow } from '../workflows/city-workflow'
import { weatherTool } from '../tools/weather-tool'
export const routingAgent = new Agent({
id: 'routing-agent',
name: 'Routing Agent',
instructions: `
You are a network of writers and researchers. The user will ask you to research a topic. Always respond with a complete report—no bullet points. Write in full paragraphs, like a blog post. Do not answer with incomplete or uncertain information.`,
model: 'openai/gpt-5.4',
agents: {
researchAgent,
writingAgent,
},
workflows: {
cityWorkflow,
},
tools: {
weatherTool,
},
memory: new Memory({
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:../mastra.db',
}),
}),
})
Subagents need a description on the Agent instance. Workflows and tools need a description plus inputSchema and outputSchema on createWorkflow() or createTool().
Call the networkDirect link to Call the network
Call .network() with a user message. The method returns a stream of events you can iterate over.
const result = await routingAgent.network('Tell me three cool ways to use Mastra')
for await (const chunk of result) {
console.log(chunk.type)
if (chunk.type === 'network-execution-event-step-finish') {
console.log(chunk.payload.result)
}
}
Structured outputDirect link to Structured output
Pass structuredOutput to get typed, validated results. Use objectStream for partial objects as they generate.
import { z } from 'zod'
const resultSchema = z.object({
summary: z.string().describe('A brief summary of the findings'),
recommendations: z.array(z.string()).describe('List of recommendations'),
confidence: z.number().min(0).max(1).describe('Confidence score'),
})
const stream = await routingAgent.network('Research AI trends', {
structuredOutput: { schema: resultSchema },
})
for await (const partial of stream.objectStream) {
console.log('Building result:', partial)
}
const final = await stream.object
console.log(final?.summary)
Approve and decline tool callsDirect link to Approve and decline tool calls
When a primitive requires approval, the stream emits an agent-execution-approval or tool-execution-approval chunk. Use approveNetworkToolCall() or declineNetworkToolCall() to respond.
Network approval uses snapshots to capture execution state. Ensure a storage provider is enabled in your Mastra instance.
const stream = await routingAgent.network('Perform some sensitive action', {
memory: {
thread: 'user-123',
resource: 'my-app',
},
})
for await (const chunk of stream) {
if (chunk.type === 'agent-execution-approval' || chunk.type === 'tool-execution-approval') {
// Approve
const approvedStream = await routingAgent.approveNetworkToolCall(chunk.payload.toolCallId, {
runId: stream.runId,
memory: { thread: 'user-123', resource: 'my-app' },
})
for await (const c of approvedStream) {
if (c.type === 'network-execution-event-step-finish') {
console.log(c.payload.result)
}
}
}
}
To decline instead, call declineNetworkToolCall() with the same arguments.
Suspend and resumeDirect link to Suspend and resume
When a primitive calls suspend(), the stream emits a suspension chunk (e.g., tool-execution-suspended). Use resumeNetwork() to provide the requested data and continue execution.
const stream = await routingAgent.network('Delete the old records', {
memory: { thread: 'user-123', resource: 'my-app' },
})
for await (const chunk of stream) {
if (chunk.type === 'workflow-execution-suspended') {
console.log(chunk.payload.suspendPayload)
}
}
// Resume with user confirmation
const resumedStream = await routingAgent.resumeNetwork(
{ confirmed: true },
{
runId: stream.runId,
memory: { thread: 'user-123', resource: 'my-app' },
},
)
for await (const chunk of resumedStream) {
if (chunk.type === 'network-execution-event-step-finish') {
console.log(chunk.payload.result)
}
}
Automatic resumptionDirect link to Automatic resumption
Set autoResumeSuspendedTools to true so the network resumes suspended primitives based on the user's next message. This creates a conversational flow where users provide the required information naturally.
const stream = await routingAgent.network('Delete the old records', {
autoResumeSuspendedTools: true,
memory: { thread: 'user-123', resource: 'my-app' },
})
Requirements for automatic resumption:
- Memory configured: The agent needs memory to track suspended tools across messages.
- Same thread: The follow-up message must use the same
threadandresourceidentifiers. resumeSchemadefined: The tool must define aresumeSchemaso the network can extract data from the user's message.
Manual (resumeNetwork) | Automatic (autoResumeSuspendedTools) | |
|---|---|---|
| Best for | Custom UIs with approval buttons | Chat-style interfaces |
| Control | Full control over resume timing and data | Network extracts data from user's message |
| Setup | Handle suspension chunks, call resumeNetwork | Set flag, define resumeSchema on tools |