Watching a crew work
An event listener is a class that subscribes to a crew's event bus, so your own code reacts to each step of a run.
Last updated: 28 Sep, 2026 · CrewAI 1.15
The inputs lesson gave the crew its question as an input. A one-task run hides little, but from the tool-calls lesson on agents call tools and pass work along, and you want to see each step. CrewAI offers two ways.
View the code here
import os
import re
from crewai import BaseLLM
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
os.environ["CREWAI_TRACING_ENABLED"] = "false"
os.environ["CREWAI_DISABLE_VERSION_CHECK"] = "true"
class ShopLLM(BaseLLM):
def call(self, messages, tools=None, **kwargs):
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
text = messages[-1]["content"]
orders = re.findall(r"\b[A-Z]\d+\b", text)
if orders:
return f"I have no way to look up {orders[0]} yet."
return "Hello. Which order is this about?"
The crew as it stands
from crewai import Agent, Crew, Task
from shop_llm import ShopLLM
agent = Agent(
role="Support agent",
goal="Answer customers of a small online shop",
backstory="You have worked the shop's support desk for years.",
llm=ShopLLM(model="shop"),
)
task = Task(
description="Answer the customer: {question}",
expected_output="One short, friendly sentence.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])The same crew, now taking the question as an input rather than holding it in the task.
Watching a run with verbose
crew = Crew(agents=[agent], tasks=[task], verbose=True)
crew.kickoff(inputs={"question": "Where is my order A17?"})╭───────────────────────── 🚀 Crew Execution Started ──────────────────────────╮ │ │ │ Crew Execution Started │ │ Name: crew │ │ ID: c21c2cbc-615e-485c-8d85-dc7854f24d39 │ │ │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────── 📋 Task Started ───────────────────────────────╮ │ │ │ Task Started │ │ Name: Answer the customer: Where is my order A17? │ │ ID: 40141193-77db-41d5-b7b9-88c4a39043da │ │ │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────── 🤖 Agent Started ──────────────────────────────╮ │ │ │ Agent: Support agent │ │ │ │ Task: Answer the customer: Where is my order A17? │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────── ✅ Agent Final Answer ────────────────────────────╮ │ │ │ Agent: Support agent │ │ │ │ Final Answer: │ │ I have no way to look up A17 yet. │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────── 📋 Task Completion ─────────────────────────────╮ │ │ │ Task Completed │ │ Name: Answer the customer: Where is my order A17? │ │ Agent: Support agent │ │ │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────── Crew Completion ───────────────────────────────╮ │ │ │ Crew Execution Completed │ │ Name: crew │ │ ID: c21c2cbc-615e-485c-8d85-dc7854f24d39 │ │ Final Output: I have no way to look up A17 yet. │ │ │ │ │ ╰──────────────────────────────────────────────────────────────────────────────╯
Each panel is one step: the crew starting, the task starting, the agent at work, its final answer, the task completing, the crew completing. The ids change on every run. Verbose output is for reading; code cannot use it.
Reacting with an event listener
CrewAI emits an event at each of those steps on a shared event bus. A listener is a class that subscribes to the events it cares about.
from crewai.events import BaseEventListener, TaskCompletedEvent, TaskStartedEvent
class Watch(BaseEventListener):
def setup_listeners(self, bus):
@bus.on(TaskStartedEvent)
def started(source, event):
print("task started:", event.task.description)
@bus.on(TaskCompletedEvent)
def finished(source, event):
print("task finished:", event.output.raw)setup_listeners receives the bus, and @bus.on registers a function for one event type. Each function gets the object that emitted the event and the event itself, which carries the task, its output, and more.
watch = Watch()
crew.kickoff(inputs={"question": "Where is my order A17?"})
crew.kickoff(inputs={"question": "Is C40 in stock?"})task started: Answer the customer: Where is my order A17? task finished: I have no way to look up A17 yet. task started: Answer the customer: Is C40 in stock? task finished: I have no way to look up C40 yet.
Creating an instance is what registers it, and it stays registered for every crew in the program. Both runs are traced. There are dozens of event types, for tools, flows, memory and more, each carrying the details of its step.
Reading the two ways
- verbose prints a panel per step; the ids change each run, and code cannot read the panels.
- A listener fires on the events it subscribes to, and creating the instance is what registers it.
- Both runs are traced, because the listener stays registered for every crew in the program.
verbose vs an event listener
| Way | Output goes to | Your code can use it |
|---|---|---|
| verbose=True | the screen | no, it is for reading |
| event listener | wherever your function sends it | yes |
When you reach for a listener
- Writing a run to a log or a metrics service.
- Saving a transcript of every task for later review.
- Counting tool calls or timing steps across many runs.
Related
- Previous: Inputs: one crew, many tickets
- Next: Tools: functions an agent can use
- Import
CrewKickoffCompletedEvent, subscribe to it and printevent.output.raw. - Print
event.task.agent.roleinstarted. - Set
agent.verbose = Trueas well as the crew's and compare the output.
You understood something today that you didn't yesterday.