Streaming a crew's output
Streaming hands you a crew's output as it is produced: Crew(stream=True) yields text chunks, and a flow's stream_events yields frames for each step.
Last updated: 28 Sep, 2026 · CrewAI 1.15
So far a kickoff returned only after the whole run finished. Streaming lets a program show the answer as it forms, and watch each step of a flow as it happens, instead of waiting for the end.
The streaming API
A crew built with stream=True returns an object you iterate for chunks; its .result holds the final output once you have read them all. A flow's stream_events returns frames tagged by channel.
crew = Crew(agents=[...], tasks=[...], stream=True)
streaming = crew.kickoff(inputs={...})
for chunk in streaming: # StreamChunk objects
print(chunk.content, end="")
result = streaming.result # the CrewOutput, after the loop
for frame in flow.stream_events(inputs={...}): # StreamFrame objects
print(frame.channel, frame.type)A stand-in that streams
A hosted model streams its tokens as it writes them. The stand-in returns a whole string, so it emits the words as chunks itself, which is what makes this a real streamed run with no key.
class StreamingShopLLM(ShopLLM):
def call(self, messages, tools=None, **kwargs):
answer = super().call(messages, tools=tools, **kwargs)
if isinstance(answer, str):
for i, word in enumerate(answer.split(" ")):
piece = word if i == 0 else " " + word
self._emit_stream_chunk_event(piece, from_task=kwargs.get("from_task"),
from_agent=kwargs.get("from_agent"))
return answerTurning streaming on
Build the crew with stream=True and give the clerk the streaming stand-in.
clerk = Agent(role="Order clerk", ..., llm=StreamingShopLLM(model="shop"),
tools=[lookup_order])
crew = Crew(agents=[clerk], tasks=[look], stream=True, verbose=False)Streaming a crew chunk by chunk
Iterate the streaming object for chunks, then read the final result.
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
from crewai import Agent, Crew, Task
from stream_llm import StreamingShopLLM
from tools import lookup_order
clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
backstory="You can look up any order in the shop's system.",
llm=StreamingShopLLM(model="shop"), tools=[lookup_order])
look = Task(description="Find the order in this message: {question}",
expected_output="The order's status.", agent=clerk)
crew = Crew(agents=[clerk], tasks=[look], stream=True, verbose=False)
streaming = crew.kickoff(inputs={"question": "Where is my order A17?"})
for chunk in streaming:
print(f"{chunk.chunk_type.value}: {chunk.content!r}")
print("full reply:", streaming.result.raw)What the chunks carried
- Each chunk is a
StreamChunkwhosecontentis a piece of the answer and whosechunk_typeistext. - The tool call emitted no text chunks; only the final answer streamed, word by word.
- streaming.result is the full
CrewOutput, available once every chunk has been read.
Streaming a flow's frames by channel
A flow streams frames, not text. stream_events returns a session; its .flow view yields only the lifecycle frames.
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
import re
from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel
class Ticket(BaseModel):
message: str = ""
order_id: str = ""
reply: str = ""
class Desk(Flow[Ticket]):
@start()
def read_ticket(self):
found = re.findall(r"\b[A-Z]\d+\b", self.state.message)
self.state.order_id = found[0] if found else ""
@listen(read_ticket)
def answer(self):
self.state.reply = f"Looking into {self.state.order_id}."
return self.state.reply
desk = Desk(suppress_flow_events=True)
session = desk.stream_events(inputs={"message": "Where is my order A17?"})
for frame in session.flow:
print(f"{frame.channel} | {frame.type}")
print("result:", session.result)A StreamFrame carries a channel (llm, flow, tools, messages, lifecycle or custom), a type, a seq and a data dict. Filtering to the flow channel here shows the run start and finish.
Crew chunks vs flow frames
| Crew stream=True | flow.stream_events | |
|---|---|---|
| What you get | StreamChunk objects | StreamFrame objects |
| The payload | chunk.content, the text | frame.channel and frame.type |
| Final result | streaming.result | session.result |
When to stream
- A person is waiting on the answer and should see it appear rather than a blank pause.
- You want to trace a flow's steps live, for a progress display or a log.
streaming.result, or it raises because the stream has not finished.Related
- Previous: Human feedback: pausing a flow
- Next: Knowledge sources: a reference library for agents
- Reference: Streaming
- Join the chunks with
"".join(c.content for c in streaming)and compare withresult.raw. - Print
chunk.agent_rolealongside each chunk. - Iterate
sessioninstead ofsession.flowand print every frame's channel.
Every expert started right here.