Your first crew: agents and tasks
A crew is a team that runs a list of tasks with a list of agents and returns one result; kickoff starts it.
Last updated: 28 Sep, 2026 · CrewAI 1.15
ShopLLM from the custom-LLM lesson answers messages. An agent wraps it with a job, a task says what the job is, and a crew runs them together.
ShopLLM answers messages. An agent wraps it with a job description, in three parts CrewAI requires: a role, a goal and a backstory.
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?"
An agent
from crewai import Agent
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"),
)
print(agent.role)Support agent
Nothing ran yet. The agent is a description of a worker; it needs a task.
A task and a crew
from crewai import Crew, Task
task = Task(
description="Answer the customer: Where is my order A17?",
expected_output="One short, friendly sentence.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
result = crew.kickoff()
print(result.raw)I have no way to look up A17 yet.
A task has a description of the work and an expected_output saying what finished looks like, and names the agent that does it. The crew takes lists of agents and tasks. kickoff runs the tasks and returns a CrewOutput, whose raw is the last task's answer as text.
Reading each task's output
result = crew.kickoff()
for output in result.tasks_output:
print(output.agent, "|", output.raw)Support agent | I have no way to look up A17 yet.
tasks_output keeps one result per task, each tagged with the role of the agent that produced it. With one task there is one entry; a crew of two agents has two.
Pick one to watch it run, step by step.
The crew holds the lists; the task says what to do and who does it; the agent turns that into messages for the model. A run this small has four moving parts, and you wrote one of them.
An agent with no model
from crewai import Agent
agent = Agent(
role="Support agent",
goal="Answer customers of a small online shop",
backstory="You have worked the shop's support desk for years.",
)
print(agent.llm.model)gpt-4.1-mini
Leaving out llm makes CrewAI choose a hosted OpenAI model. The installed 1.15 selects gpt-4.1-mini here, printed above; the choice can change between versions, and the Agents page of the documentation still names an older default, so pass llm= yourself rather than depend on it. Creating the agent works. Running it is where the key is needed:
from crewai import Agent, Crew, Task
import shop_llm
agent = Agent(role="Support agent", goal="Answer customers", backstory="You work the desk.")
task = Task(description="Where is my order A17?", expected_output="One sentence.", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()[CrewAIEventsBus] Warning: Event pairing mismatch. 'llm_call_failed' closed
'flow_started' (expected 'llm_call_started')
[CrewAIEventsBus] Warning: Event pairing mismatch. 'llm_call_failed' closed
'flow_started' (expected 'llm_call_started')
[CrewAIEventsBus] Warning: Event pairing mismatch. 'llm_call_failed' closed
'flow_started' (expected 'llm_call_started')
Traceback (most recent call last):
File "main.py", line 6, in <module>
Crew(agents=[agent], tasks=[task]).kickoff()
ValueError: OPENAI_API_KEY is requiredCrewAI tried the call three times before giving up, and printed a warning line for each failed call. import shop_llm is there only for its three settings, so the failed run sends nothing. Every agent below passes llm= explicitly.
What kickoff hands back
- kickoff returns a CrewOutput; its raw is the last task's answer as text.
- tasks_output holds one entry per task, each tagged with the role of the agent that produced it.
- Leaving out llm still builds the agent; the missing key only bites at kickoff.
raw vs tasks_output
| Attribute | Holds | Reach for it when |
|---|---|---|
| result.raw | the last task's answer as text | you want the final reply |
| result.tasks_output | one output per task | you need each step's result |
| output.agent | the role that produced it | you trace who did what |
When a crew earns its place
- The work has more than one step, and each step has a clear owner.
- You want one result out, with the steps in between recorded.
- Different steps need different tools or different instructions.
Related
- Change the task's description to ask about C40 and run the crew again.
- Delete
expected_outputfrom the task and read the error. - Print
result.tasks_output[0].description.
This is what real progress feels like.