Learnixo

CrewAI Multi-Agents · Lesson 4 of 16

Agents, Tasks, Crews: The Three Primitives

The Four Pillars

Every CrewAI system is built from exactly four concepts:

  1. Agent — An AI persona with a role, goal, and backstory
  2. Task — A unit of work with a description and expected output
  3. Crew — The container that holds agents, tasks, and execution rules
  4. Process — The strategy for ordering and routing task execution

Understanding each in depth — not just how to use the API, but why the design choices were made — is the difference between a crew that works and one that produces inconsistent, low-quality results.


Agent

An Agent is the fundamental worker in CrewAI. It is an LLM instance augmented with identity, purpose, and capabilities.

Constructor Parameters

Python
from crewai import Agent, LLM

agent = Agent(
    # Identity
    role="Senior Pharmacovigilance Analyst",          # What the agent IS
    goal="Identify and assess drug safety signals",   # What it's trying to ACHIEVE
    backstory=(                                        # Context shaping its behavior
        "You have 12 years of experience in pharmacovigilance at a major CRO. "
        "You are systematic, citation-focused, and flag any signal that exceeds "
        "a reporting threshold, even if causality is uncertain."
    ),

    # Model
    llm=LLM(model="gpt-4o"),                          # Which LLM to use (default: gpt-4)

    # Capabilities
    tools=[],                                          # List of tools the agent can call
    allow_delegation=False,                            # Can it delegate tasks to other agents?

    # Memory
    memory=True,                                       # Enable short + long-term memory

    # Execution
    max_iter=15,                                       # Max reasoning iterations before stopping
    max_rpm=None,                                      # Max requests per minute (rate limiting)
    max_execution_time=None,                           # Max seconds per task

    # Debugging
    verbose=True,                                      # Print reasoning steps
)

Role vs Goal vs Backstory

These three parameters work together to construct the agent's system prompt:

| Parameter | Function | Analogy | |-----------|----------|---------| | role | Defines the agent's identity | Job title | | goal | States what the agent is optimizing for | KPI / objective | | backstory | Provides context, expertise, and behavioral priors | Work history + personality |

The backstory is the most powerful of the three. It shapes:

  • How cautious or bold the agent is
  • How formal or casual its output is
  • What the agent prioritizes when trade-offs arise
  • How it handles uncertainty

Weak backstory:

Python
backstory="You are a helpful assistant."

Strong backstory:

Python
backstory=(
    "You spent 8 years as a clinical research coordinator at an oncology center "
    "before moving into regulatory affairs. You have reviewed over 200 IND applications "
    "and are known for catching protocol deviations others miss. "
    "You write in precise regulatory language and always reference specific CFR sections."
)

The strong backstory produces measurably different output: more structured, more specific, less hedged.


Task

A Task defines a unit of work. It is assigned to an agent and has a precise definition of what "done" looks like.

Constructor Parameters

Python
from crewai import Task

task = Task(
    # What to do
    description=(
        "Analyze the adverse event reports for Drug X from Q1 2026. "
        "Focus on cardiovascular events. Identify any signal exceeding 2x "
        "the expected rate based on the drug label."
    ),

    # What done looks like
    expected_output=(
        "A structured report with three sections: "
        "1) Executive Summary (2-3 sentences), "
        "2) Signal Analysis (table with signal name, observed rate, expected rate, ROR), "
        "3) Recommendations (numbered list of next steps)."
    ),

    # Who does it
    agent=pharmacovigilance_analyst,

    # What previous task outputs to include as context
    context=[data_extraction_task],                    # Optional

    # Output options
    output_file="safety_report.md",                    # Optional: save to file
    output_pydantic=SafetyReport,                      # Optional: parse into Pydantic model
    output_json=True,                                  # Optional: force JSON output

    # Execution options
    async_execution=False,                             # Run concurrently with other tasks?

    # Callback (called when task completes)
    callback=on_task_complete,                         # Optional
)

The expected_output Field Is a Contract

The expected_output field is not documentation — it is a directive to the LLM. CrewAI injects it into the prompt as the definition of a successful completion.

Vague expected output:

Python
expected_output="A good analysis."

Result: inconsistent, often short, rarely actionable.

Specific expected output:

Python
expected_output=(
    "A numbered list of exactly 5 safety signals. "
    "Each signal must include: signal name, observed incidence rate (as a percentage), "
    "expected incidence rate from the drug label, the reporting odds ratio (ROR), "
    "and a one-sentence clinical interpretation. "
    "Format as a markdown table."
)

Result: consistent, structured, usable by downstream code.

Context Passing

The context parameter lets you explicitly declare that a task depends on the output of another task:

Python
extraction_task = Task(
    description="Extract all adverse event records from the Q1 2026 safety database.",
    expected_output="A JSON array of adverse event records with fields: id, date, event_type, severity.",
    agent=data_engineer,
)

analysis_task = Task(
    description="Analyze the extracted adverse event records for safety signals.",
    expected_output="A safety signal report in markdown format.",
    agent=pharmacovigilance_analyst,
    context=[extraction_task],  # <-- output of extraction_task is injected here
)

When analysis_task runs, CrewAI prepends the output of extraction_task to the analysis agent's context window.


Crew

The Crew is the container. It holds agents, tasks, and the rules for running them.

Constructor Parameters

Python
from crewai import Crew, Process, LLM

crew = Crew(
    # Members and work
    agents=[data_engineer, analyst, writer, reviewer],
    tasks=[extraction_task, analysis_task, writing_task, review_task],

    # Execution strategy
    process=Process.sequential,                        # or Process.hierarchical

    # For hierarchical process only
    manager_llm=LLM(model="gpt-4o"),                  # LLM used by the manager agent
    manager_agent=None,                                # Or provide a custom manager Agent

    # Memory (shared across all agents in this crew)
    memory=False,                                      # Enable crew-level memory

    # Monitoring
    verbose=True,
    step_callback=on_step,                             # Called after each agent action
    task_callback=on_task_done,                        # Called after each task completes

    # Performance
    max_rpm=10,                                        # Rate limit across the entire crew
    share_crew=False,                                  # Share crew data with CrewAI platform
    output_log_file="crew_log.txt",                    # Log all output to file
)

Running the Crew

Python
# Synchronous
result = crew.kickoff()
print(result.raw)              # Final output as string
print(result.pydantic)         # Pydantic model if output_pydantic was set
print(result.json_dict)        # Dict if output_json was set
print(result.token_usage)      # Token consumption stats

# With input variables (interpolated into task descriptions)
result = crew.kickoff(inputs={"compound": "metformin", "quarter": "Q1 2026"})
# In task descriptions, use {compound} and {quarter} as placeholders

Task descriptions with input interpolation

Python
research_task = Task(
    description=(
        "Research the safety profile of {compound} using data from {quarter}. "
        "Focus on cardiovascular adverse events."
    ),
    expected_output="A structured safety summary.",
    agent=researcher,
)

crew = Crew(agents=[researcher], tasks=[research_task], process=Process.sequential)

# Inputs are interpolated at runtime
result = crew.kickoff(inputs={"compound": "semaglutide", "quarter": "Q1 2026"})

This makes crews reusable across different inputs without redefining tasks.


Process

Process is the execution strategy that determines how tasks flow through the crew.

Process.sequential

The default. Tasks run in the order they appear in the tasks list. Each task's output is automatically available as context to subsequent tasks.

Python
crew = Crew(
    agents=[researcher, writer, reviewer],
    tasks=[research_task, writing_task, review_task],
    process=Process.sequential,
)

Execution flow:

research_task → writing_task → review_task
     ↑                ↑              ↑
   agent=researcher  agent=writer  agent=reviewer

Output flows left to right. The reviewer sees both the research and the written article.

When to use: Linear pipelines where each step builds on the previous one.

Process.hierarchical

A manager LLM reads the task list and agents, then decides the execution order and which agent handles which task. The manager can also instruct agents mid-task.

Python
from crewai import LLM

crew = Crew(
    agents=[specialist_a, specialist_b, specialist_c],
    tasks=[task_1, task_2, task_3],
    process=Process.hierarchical,
    manager_llm=LLM(model="gpt-4o"),   # strong model recommended for manager
    verbose=True,
)

The manager agent is automatically created. It:

  1. Reads the task list
  2. Decides which agent is best suited for each task
  3. Delegates tasks
  4. Synthesizes results

When to use: When you have multiple specialist agents and the task routing is non-trivial, or when you want the manager to dynamically reorder or merge tasks.


How It All Fits Together: Full Example

Python
from crewai import Agent, Task, Crew, Process, LLM
from crewai_tools import SerperDevTool

# --- LLM ---
llm = LLM(model="gpt-4o")

# --- Tools ---
search = SerperDevTool()

# --- Agents ---
researcher = Agent(
    role="Clinical Research Analyst",
    goal="Find accurate, current clinical evidence for pharmaceutical questions",
    backstory=(
        "You are a clinical research analyst with expertise in systematic reviews. "
        "You evaluate evidence quality using GRADE criteria and flag low-quality evidence."
    ),
    tools=[search],
    llm=llm,
    verbose=True,
)

writer = Agent(
    role="Medical Communications Writer",
    goal="Translate clinical evidence into clear professional communications",
    backstory=(
        "You have 8 years of medical writing experience across pharma, biotech, and CROs. "
        "You write for HCP audiences and follow AMWA style guidelines."
    ),
    llm=llm,
    verbose=True,
)

reviewer = Agent(
    role="Regulatory Compliance Reviewer",
    goal="Ensure communications are accurate, balanced, and compliant",
    backstory=(
        "You are a regulatory affairs specialist with FDA review experience. "
        "You apply 21 CFR Part 202 standards and flag any promotional language "
        "that makes claims unsupported by the cited evidence."
    ),
    llm=llm,
    verbose=True,
)

# --- Tasks ---
research_task = Task(
    description=(
        "Research the clinical evidence for {drug} in treating {indication}. "
        "Include Phase 3 trial results, approved indications, and major safety findings."
    ),
    expected_output=(
        "A structured evidence summary with sections: "
        "Efficacy (key Phase 3 results with effect sizes), "
        "Safety (adverse events above 5% incidence), "
        "Regulatory Status (approved indications by region)."
    ),
    agent=researcher,
)

writing_task = Task(
    description=(
        "Write a 400-word HCP-facing medical communication on {drug} for {indication} "
        "based on the research summary."
    ),
    expected_output=(
        "A 400-word communication with: title, clinical context paragraph, "
        "efficacy section, safety section, and conclusion. "
        "No promotional language. All claims must reference the research summary."
    ),
    agent=writer,
    context=[research_task],
)

review_task = Task(
    description=(
        "Review the medical communication for accuracy, balance, and regulatory compliance. "
        "Apply 21 CFR Part 202 promotional material standards."
    ),
    expected_output=(
        "A review report with: "
        "Status (Approved / Approved with Changes / Rejected), "
        "Issues Found (numbered list, empty if none), "
        "Required Changes (numbered list, empty if approved)."
    ),
    agent=reviewer,
    context=[writing_task],
)

# --- Crew ---
crew = Crew(
    agents=[researcher, writer, reviewer],
    tasks=[research_task, writing_task, review_task],
    process=Process.sequential,
    verbose=True,
)

# --- Run ---
result = crew.kickoff(inputs={
    "drug": "tirzepatide",
    "indication": "type 2 diabetes",
})

print("=== FINAL REVIEW OUTPUT ===")
print(result.raw)

Summary

| Concept | Class | Key Parameters | |---------|-------|---------------| | Agent | Agent | role, goal, backstory, tools, llm, memory | | Task | Task | description, expected_output, agent, context | | Crew | Crew | agents, tasks, process, manager_llm | | Process | Process | sequential (default), hierarchical |

The power of CrewAI is in the combination: specific agent personas produce consistent behavior, specific expected outputs produce usable results, and the process mode determines how those results flow through the system.