CrewAICrewAI 1.15 · Python 3.10 to 3.13
Dashboard
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
35 small wins to finish your pathNext lesson →

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.

Project files used on this pageThis lesson builds on a project from earlier lessons. The code below imports this file. Click a file to see its code, or follow the link to the lesson that wrote it. To run the code yourself, keep it in the same folder.
View the code here
shop_llm.py
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

python
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

Example
crew = Crew(agents=[agent], tasks=[task], verbose=True)
crew.kickoff(inputs={"question": "Where is my order A17?"})

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.

python
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.

Example
watch = Watch()
crew.kickoff(inputs={"question": "Where is my order A17?"})
crew.kickoff(inputs={"question": "Is C40 in stock?"})

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

WayOutput goes toYour code can use it
verbose=Truethe screenno, it is for reading
event listenerwherever your function sends ityes

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.
Watch out
Creating a listener registers it for the whole process. Make one instance, not one per crew, or each step is handled twice.
Try it yourself
  • Import CrewKickoffCompletedEvent, subscribe to it and print event.output.raw.
  • Print event.task.agent.role in started.
  • Set agent.verbose = True as well as the crew's and compare the output.

You understood something today that you didn't yesterday.