Skip to main content

Streaming

Mastra supports real-time, incremental responses from agents and workflows, allowing users to see output as it’s generated instead of waiting for completion. This is useful for chat, long-form content, multi-step workflows, or any scenario where immediate feedback matters.

Getting started
Direct link to Getting started

Mastra's streaming API adapts based on your model version:

  • .stream(): For V2 models, supports AI SDK v5 and later (LanguageModelV2).
  • .streamLegacy(): For V1 models, supports AI SDK v4 (LanguageModelV1).

Streaming with agents
Direct link to Streaming with agents

You can pass a single string for basic prompts, an array of strings when providing multiple pieces of context, or an array of message objects with role and content for precise control over roles and conversational flows.

Using Agent.stream()
Direct link to using-agentstream

A textStream breaks the response into chunks as it's generated, allowing output to stream progressively instead of arriving all at once. Iterate over the textStream using a for await loop to inspect each stream chunk.

const testAgent = mastra.getAgent('testAgent')

const stream = await testAgent.stream([{ role: 'user', content: 'Help me organize my day' }])

for await (const chunk of stream.textStream) {
process.stdout.write(chunk)
}
info

Visit Agent.stream() for more information.

tip

For agents that dispatch background tasks, use Agent.streamUntilIdle() to keep the stream open until those tasks complete and the agent has had a chance to respond to their results.

Output from Agent.stream()
Direct link to output-from-agentstream

The output streams the generated response from the agent.

Of course!
To help you organize your day effectively, I need a bit more information.
Here are some questions to consider:
...

Agent stream properties
Direct link to Agent stream properties

An agent stream provides access to various response properties:

  • stream.textStream: A readable stream that emits text chunks.
  • stream.text: Promise that resolves to the full text response.
  • stream.finishReason: The reason the agent stopped streaming.
  • stream.usage: Token usage information.

AI SDK v5+ Compatibility
Direct link to AI SDK v5+ Compatibility

AI SDK v5 (and later) uses LanguageModelV2 for the model providers. If you are getting an error that you are using an AI SDK v4 model you will need to upgrade your model package to the next major version.

For integration with AI SDK v5+, use the toAISdkV5Stream() utility from @mastra/ai-sdk to convert Mastra streams to AI SDK-compatible format:

import { toAISdkV5Stream } from '@mastra/ai-sdk'

const testAgent = mastra.getAgent('testAgent')

const stream = await testAgent.stream([{ role: 'user', content: 'Help me organize my day' }])

// Convert to AI SDK v5+ compatible stream
const aiSDKStream = toAISdkV5Stream(stream, { from: 'agent' })

For converting messages to AI SDK v5+ format, use the toAISdkV5Messages() utility from @mastra/ai-sdk/ui:

import { toAISdkV5Messages } from '@mastra/ai-sdk/ui'

const messages = [{ role: 'user', content: 'Hello' }]
const aiSDKMessages = toAISdkV5Messages(messages)

Streaming with workflows
Direct link to Streaming with workflows

Streaming from a workflow returns a sequence of structured events describing the run lifecycle, rather than incremental text chunks. This event-based format makes it possible to track and respond to workflow progress in real time once a run is created using .createRun().

Using Run.stream()
Direct link to using-runstream

The stream() method returns a ReadableStream of events directly.

const run = await testWorkflow.createRun()

const stream = await run.stream({
inputData: {
value: 'initial data',
},
})

for await (const chunk of stream) {
console.log(chunk)
}
info

Visit Run.stream() for more information.

Output from Run.stream()
Direct link to output-from-runstream

The event structure includes runId and from at the top level, making it easier to identify and track workflow runs without digging into the payload.

{
type: 'workflow-start',
runId: '1eeaf01a-d2bf-4e3f-8d1b-027795ccd3df',
from: 'WORKFLOW',
payload: {
stepName: 'step-1',
args: { value: 'initial data' },
stepCallId: '8e15e618-be0e-4215-a5d6-08e58c152068',
startedAt: 1755121710066,
status: 'running'
}
}

Workflow stream properties
Direct link to Workflow stream properties

A workflow stream provides access to various response properties:

  • stream.status: The status of the workflow run.
  • stream.result: The result of the workflow run.
  • stream.usage: The total token usage of the workflow run.

Streaming from agents or workflows provides real-time visibility into either the LLM’s output or the status of a workflow run. This feedback can be passed directly to the user, or used within applications to handle workflow status more effectively, creating a smoother and more responsive experience.

Events emitted from agents or workflows represent different stages of generation and execution, such as when a run starts, when text is produced, or when a tool is invoked.

Event types
Direct link to Event types

Below is a complete list of events emitted from .stream(). Depending on whether you’re streaming from an agent or a workflow, only a subset of these events will occur:

  • start: Marks the beginning of an agent or workflow run.
  • step-start: Indicates a workflow step has begun execution.
  • text-delta: Incremental text chunks as they're generated by the LLM.
  • tool-call: When the agent decides to use a tool, including the tool name and arguments.
  • tool-result: The result returned from tool execution.
  • step-finish: Confirms that a specific step has fully finalized, and may include metadata like the finish reason for that step.
  • finish: When the agent or workflow completes, including usage statistics.

Inspecting agent streams
Direct link to Inspecting agent streams

Iterate over the stream with a for await loop to inspect all emitted event chunks.

const testAgent = mastra.getAgent('testAgent')

const stream = await testAgent.stream([{ role: 'user', content: 'Help me organize my day' }])

for await (const chunk of stream) {
console.log(chunk)
}
info

Visit Agent.stream() for more information.

Example agent output
Direct link to Example agent output

Below is an example of events that may be emitted. Each event always includes a type and can include additional fields like from and payload.

{
type: 'start',
from: 'AGENT',
// ..
}
{
type: 'step-start',
from: 'AGENT',
payload: {
messageId: 'msg-cdUrkirvXw8A6oE4t5lzDuxi',
// ...
}
}
{
type: 'tool-call',
from: 'AGENT',
payload: {
toolCallId: 'call_jbhi3s1qvR6Aqt9axCfTBMsA',
toolName: 'testTool'
// ..
}
}

Writer API
Direct link to Writer API

The writer API is shared by tools and workflow steps — see the Tools and Workflows docs for feature-specific examples.

Agent using tool
Direct link to Agent using tool

Agent streaming can be combined with tool calls, allowing tool outputs to be written directly into the agent’s streaming response. This makes it possible to surface tool activity as part of the overall interaction.

import { Agent } from '@mastra/core/agent'
import { testTool } from '../tools/test-tool'

export const testAgent = new Agent({
id: 'test-agent',
name: 'Test Agent',
instructions: 'You are a weather agent.',
model: 'openai/gpt-5.5',
tools: { testTool },
})

Using context.writer
Direct link to using-contextwriter

The context.writer object is available in a tool's execute() function and can be used to emit custom events, data, or values into the active stream. This enables tools to provide intermediate results or status updates while execution is still in progress.

warning

You must await the call to writer.write() or else you will lock the stream and get a WritableStream is locked error.

import { createTool } from '@mastra/core/tools'

export const testTool = createTool({
execute: async (inputData, context) => {
const { value } = inputData

await context?.writer?.write({
type: 'custom-event',
status: 'pending',
})

const response = await fetch()

await context?.writer?.write({
type: 'custom-event',
status: 'success',
})

return {
value: '',
}
},
})

You can also use writer.custom() to emit top-level stream chunks. This is useful when integrating with UI frameworks.

import { createTool } from '@mastra/core/tools'

export const testTool = createTool({
execute: async (inputData, context) => {
const { value } = inputData

await context?.writer?.custom({
type: 'data-tool-progress',
status: 'pending',
})

const response = await fetch()

await context?.writer?.custom({
type: 'data-tool-progress',
status: 'success',
})

return {
value: '',
}
},
})

Transient data chunks
Direct link to Transient data chunks

By default, data-* chunks emitted with writer.custom() are persisted to storage as part of the message history. For chunks that are only needed during live streaming — such as progress updates or verbose log output — set transient: true to skip storage persistence. Transient chunks are still streamed to the client in real time but aren't saved to the database.

await context?.writer?.custom({
type: 'data-build-log',
data: { line: 'Compiling module 3 of 12...' },
transient: true,
})

Use transient chunks when the data is large or high-frequency and only relevant during the live session. After a page refresh, transient chunks are no longer available — only the tool's return value and any non-transient chunks are loaded from storage.

Using the writer argument
Direct link to using-the-writer-argument

The writer argument is passed to a workflow step's execute function and can be used to emit custom events, data, or values into the active stream. This enables workflow steps to provide intermediate results or status updates while execution is still in progress.

warning

You must await the call to writer.write(...) or else you will lock the stream and get a WritableStream is locked error.

import { createStep } from "@mastra/core/workflows";

export const testStep = createStep({
execute: async ({ inputData, writer }) => {
const { value } = inputData;

await writer?.write({
type: "custom-event",
status: "pending"
});

const response = await fetch(...);

await writer?.write({
type: "custom-event",
status: "success"
});

return {
value: ""
};
},
});