A task is a unit of work assigned to an agent. Each task carries a natural-language description of what needs to be done, a specification of the expected output format, and an optional assignment to a specific agent. Tasks can depend on other tasks, run asynchronously, produce structured output via JSON or Pydantic models, and trigger callbacks on completion. Mastering the Task class is essential because tasks are where you translate business requirements into actionable instructions for your crew.
1. The Task Class
In CrewAI: Multi-Agent Orchestration, the Task class represents a discrete piece of work. At minimum,
every task requires a description (what to do) and an expected_output
(what the result should look like). You typically also assign an agent to the
task, although CrewAI: Multi-Agent Orchestration can auto-assign agents when using a hierarchical process
(see Section N.4).
The following example creates a simple task assigned to a researcher agent.
from crewai import Agent, Task
researcher = Agent(
role="Market Researcher",
goal="Gather comprehensive market data on emerging technologies",
backstory="You are an experienced market research analyst.",
)
research_task = Task(
description=(
"Research the current state of the autonomous vehicle industry. "
"Focus on the top 5 companies by market capitalization, their latest "
"product announcements, and any regulatory developments from the past "
"6 months."
),
expected_output=(
"A structured report with sections for each company, including: "
"company name, market cap, recent announcements, and regulatory status. "
"End with a summary of key industry trends."
),
agent=researcher,
)
print(research_task.description[:60])
Write expected_output as if you are describing the deliverable to a colleague. Be specific about format, length, and structure. Vague expectations (e.g., "a good report") produce inconsistent results; precise expectations (e.g., "a bullet-point list with 5 items, each containing a company name and one-sentence summary") produce reliable outputs.
2. Writing Effective Descriptions
The description field is the primary instruction the agent receives. It
functions like a prompt, so all the principles of good prompt engineering apply here.
Effective descriptions are specific, include context about why the task matters, and
clarify any constraints or boundaries.
The table below contrasts weak and strong task descriptions for common scenarios.
| Scenario | Weak Description | Strong Description |
|---|---|---|
| Summarization | "Summarize this article." | "Summarize the article in 3 bullet points, each under 25 words. Focus on quantitative claims." |
| Code review | "Review the code." | "Review the Python code for security vulnerabilities, focusing on SQL injection and path traversal. List each finding with file, line, and severity." |
| Data analysis | "Analyze the sales data." | "Analyze Q3 sales data by region. Identify the top 3 regions by revenue growth rate and any regions with declining sales." |
3. Expected Output Specification
The expected_output field tells both the agent and CrewAI: Multi-Agent Orchestration what a successful
result looks like. CrewAI: Multi-Agent Orchestration uses this field during internal quality checks: if the agent's
output does not match the expected format, the framework may prompt the agent to try again
(up to the max_iter limit). This makes expected_output a soft
guardrail that improves output consistency.
from crewai import Agent, Task
writer = Agent(
role="Blog Writer",
goal="Write engaging, SEO-optimized blog posts",
backstory="You are a content marketing specialist.",
)
blog_task = Task(
description=(
"Write a blog post about the benefits of retrieval-augmented generation "
"(RAG) for enterprise search. Target audience: CTOs evaluating AI solutions. "
"Include at least one concrete example and a call to action."
),
expected_output=(
"A blog post of 800 to 1200 words in markdown format. Must include: "
"a compelling title, an introduction paragraph, at least 3 subsections "
"with h2 headings, one real-world example, and a closing call to action."
),
agent=writer,
)
4. Task Dependencies with Context
Real workflows often require one task's output to feed into the next. CrewAI: Multi-Agent Orchestration handles
this through the context parameter, which accepts a list of other tasks.
When a task has context dependencies, CrewAI: Multi-Agent Orchestration automatically injects the outputs of those
upstream tasks into the current task's prompt. This creates a data pipeline between agents
without manual string passing.
The following example builds a two-stage pipeline where a research task feeds into a writing task.
from crewai import Agent, Task
researcher = Agent(
role="Researcher",
goal="Gather accurate, up-to-date information",
backstory="You are a thorough research specialist.",
)
writer = Agent(
role="Writer",
goal="Transform research into polished content",
backstory="You are a skilled technical writer.",
)
# Stage 1: Research
research_task = Task(
description="Research the top 5 Python web frameworks in 2025 by popularity and features.",
expected_output="A comparison table with columns: framework, GitHub stars, key features, best for.",
agent=researcher,
)
# Stage 2: Writing (depends on research)
article_task = Task(
description=(
"Using the research provided, write a developer-focused article comparing "
"Python web frameworks. Include code snippets showing a minimal 'hello world' "
"in each framework."
),
expected_output="A 1000-word article in markdown with code examples for each framework.",
agent=writer,
context=[research_task], # Injects research_task's output into this task's prompt
)
Circular dependencies (Task A depends on Task B, which depends on Task A) will cause a runtime error. Design your task graph as a directed acyclic graph (DAG). If you need iterative refinement, use multiple sequential tasks instead.
5. Asynchronous Task Execution
Some tasks are independent and can run in parallel. Setting async_execution=True
tells CrewAI: Multi-Agent Orchestration that this task does not need to wait for the previous task to finish. This
is useful when multiple agents can work simultaneously on unrelated subtasks, reducing
total execution time.
from crewai import Agent, Task
market_researcher = Agent(
role="Market Researcher",
goal="Research market size and growth trends",
backstory="You specialize in market sizing.",
)
competitor_analyst = Agent(
role="Competitor Analyst",
goal="Analyze competitor strengths and weaknesses",
backstory="You specialize in competitive intelligence.",
)
# These two tasks can run in parallel
market_task = Task(
description="Estimate the global market size for AI developer tools in 2025.",
expected_output="Market size estimate with sources and growth rate.",
agent=market_researcher,
async_execution=True, # Run in parallel
)
competitor_task = Task(
description="Identify and analyze the top 5 competitors in AI developer tools.",
expected_output="A competitor matrix with features, pricing, and market share.",
agent=competitor_analyst,
async_execution=True, # Run in parallel
)
6. Structured Output with JSON and Pydantic
When you need machine-readable output rather than free-form text, CrewAI: Multi-Agent Orchestration supports two
structured output modes. The output_json parameter accepts a Pydantic model
class and instructs the agent to return valid JSON conforming to that schema. The
output_pydantic parameter goes further, returning a validated Pydantic
object that you can use directly in Python code.
from crewai import Agent, Task
from pydantic import BaseModel
from typing import List
# Define the output schema
class CompanyProfile(BaseModel):
name: str
industry: str
revenue_millions: float
key_products: List[str]
risk_factors: List[str]
analyst = Agent(
role="Company Analyst",
goal="Produce structured company profiles",
backstory="You are a financial analyst who produces data-driven profiles.",
)
# Option 1: Get raw JSON output
json_task = Task(
description="Create a profile for Tesla, Inc. based on their latest annual report.",
expected_output="A JSON object matching the CompanyProfile schema.",
agent=analyst,
output_json=CompanyProfile, # Agent returns JSON string
)
# Option 2: Get a validated Pydantic object
pydantic_task = Task(
description="Create a profile for Apple, Inc. based on their latest annual report.",
expected_output="A structured company profile.",
agent=analyst,
output_pydantic=CompanyProfile, # Agent returns Pydantic object
)
# After crew execution, access structured data:
# result = pydantic_task.output.pydantic
# print(result.name) # "Apple, Inc."
# print(result.revenue_millions) # 394328.0
Prefer output_pydantic over output_json when you plan to use the output in downstream Python code. Pydantic validation catches schema mismatches early, and you get type-checked attribute access instead of raw dictionary lookups.
7. Task Callbacks
Callbacks let you execute custom Python code when a task completes. This is useful for
logging, metrics collection, sending notifications, or writing intermediate results to
a database. The callback parameter accepts any callable that takes a single
TaskOutput argument.
from crewai import Agent, Task
def on_task_complete(output):
"""Called when the task finishes."""
print(f"Task completed. Output length: {len(output.raw)} characters")
# You could also: write to a database, send a Slack message, update a dashboard
with open("task_output.txt", "w") as f:
f.write(output.raw)
researcher = Agent(
role="Researcher",
goal="Research a topic thoroughly",
backstory="You are a careful researcher.",
)
task_with_callback = Task(
description="Summarize recent advances in protein folding prediction.",
expected_output="A 500-word summary covering AlphaFold3 and competing approaches.",
agent=researcher,
callback=on_task_complete, # Called when this task finishes
)
8. Complete Task Configuration Example
The following example demonstrates a realistic three-task pipeline that combines dependencies, structured output, and callbacks into a cohesive workflow.
from crewai import Agent, Task, Crew
from pydantic import BaseModel
from typing import List
class MarketReport(BaseModel):
title: str
summary: str
trends: List[str]
recommendation: str
def log_completion(output):
print(f"[LOG] Task done: {len(output.raw)} chars")
researcher = Agent(
role="Market Researcher", goal="Gather market data", backstory="Expert analyst."
)
analyst = Agent(
role="Data Analyst", goal="Analyze trends", backstory="Statistical expert."
)
writer = Agent(
role="Report Writer", goal="Write executive reports", backstory="Business writer."
)
gather_task = Task(
description="Collect data on the AI chip market: key players, revenue, and growth.",
expected_output="Raw data with sources for the top 5 AI chip companies.",
agent=researcher,
callback=log_completion,
)
analyze_task = Task(
description="Analyze the collected market data. Identify the top 3 trends.",
expected_output="A list of 3 trends with supporting data points.",
agent=analyst,
context=[gather_task],
callback=log_completion,
)
report_task = Task(
description="Write an executive summary report based on the analysis.",
expected_output="A structured market report with title, summary, trends, and recommendation.",
agent=writer,
context=[analyze_task],
output_pydantic=MarketReport,
callback=log_completion,
)
crew = Crew(agents=[researcher, analyst, writer], tasks=[gather_task, analyze_task, report_task])
# result = crew.kickoff()
# print(result.pydantic.title)
Tasks are the bridge between agent capabilities and business requirements. A well-defined task has a specific description, a precise expected output, and explicit dependencies on upstream tasks. Use structured output (Pydantic models) when you need machine-readable results, and callbacks when you need side effects. In the next section, we will equip these agents with tools so they can interact with the outside world.