CrewAI for Structured Team Workflows

9 min read Module 2 of 10 Topic 6 of 30

What you'll learn

  • Design CrewAI Crews with sequential and hierarchical process modes for different coordination needs
  • Build stateful multi-step pipelines using CrewAI Flows with typed state and event routing
  • Extend agent capabilities with custom tools using the @tool decorator and integrate with enterprise APIs
  • Identify scenarios where CrewAI's opinionated structure outperforms LangGraph's flexibility
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

CrewAI provides a high-level, opinionated framework for building collaborative agent teams that mirror real organizational structures: each agent has a role, a goal, and a backstory that shapes its behavior. Unlike lower-level orchestration graphs, CrewAI handles delegation, memory passing, and task chaining automatically, letting you focus on defining who your agents are rather than how they communicate. In enterprise settings, this structured approach dramatically reduces boilerplate while still supporting sophisticated multi-agent workflows through its Flows abstraction.

CrewAI 0.80+ Architecture Overview

CrewAI organizes everything around four core primitives: Crews (teams), Agents (specialists), Tasks (units of work), and Flows (stateful pipelines). Understanding how they compose is essential before writing a single line of code.

flowchart TD
    F["Flow (Stateful Pipeline)"]
    C["Crew (Team Orchestrator)"]
    M["Manager Agent\n(hierarchical only)"]
    A1["Agent: Researcher"]
    A2["Agent: Analyst"]
    A3["Agent: Writer"]
    T1["Task: Gather Data"]
    T2["Task: Analyze Findings"]
    T3["Task: Draft Report"]
    TL["Tool: Web Search"]
    TM["Tool: DB Query"]

    F --> C
    C --> M
    M --> A1
    M --> A2
    M --> A3
    A1 --> T1
    A2 --> T2
    A3 --> T3
    T1 --> T2 --> T3
    A1 --> TL
    A2 --> TM

    style F fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style C fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style M fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style A1 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style A2 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style A3 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style TL fill:#fff7ed,stroke:#f59e0b,color:#0F172A
    style TM fill:#fff7ed,stroke:#f59e0b,color:#0F172A

Each Agent is configured with a role, goal, and backstory, these three strings are injected into the system prompt and meaningfully shape output style. Agents hold a reference to their LLM and can carry tools, memory, and delegation permissions. Tasks specify what needs doing (description), who does it (agent), and what the output format looks like (expected_output). The Crew binds agents and tasks together under a process strategy.

Sequential vs Hierarchical Process

The process parameter on a Crew is the single most impactful architectural decision you make.

flowchart LR
    subgraph SEQ["Sequential Process"]
        direction LR
        S1["Task 1"] --> S2["Task 2"] --> S3["Task 3"]
    end
    subgraph HIE["Hierarchical Process"]
        direction TB
        MGR["Manager LLM"]
        H1["Agent A"]
        H2["Agent B"]
        H3["Agent C"]
        MGR -->|"delegates"| H1
        MGR -->|"delegates"| H2
        H1 -->|"result"| MGR
        MGR -->|"re-assigns"| H3
    end

    style MGR fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style H1 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style H2 fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style H3 fill:#f0fdf9,stroke:#0D9488,color:#0F172A

Sequential is the right default: predictable, debuggable, and cost-controlled. Each task receives the previous task’s output as context automatically, no wiring required. Hierarchical introduces a manager LLM that reads all agent capabilities and dynamically assigns work. Use hierarchical when you cannot predetermine the task sequence at design time, for example in open-ended research where the scope expands based on findings.

Here is how to define a sequential crew for an enterprise market analysis workflow:

from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool, ScrapeWebsiteTool
from langchain_openai import AzureChatOpenAI

# Use a shared Azure OpenAI instance so all agents go through the same
# enterprise deployment with unified rate limits and audit logging.
llm = AzureChatOpenAI(
    azure_deployment="gpt-5.6-sol",
    api_version="2024-05-01-preview",
    temperature=0.1,  # low temp for factual research tasks; higher for creative writing agents
)

web_search = SerperDevTool()
scraper = ScrapeWebsiteTool()

# The backstory is not cosmetic: it shapes system prompt context and
# meaningfully steers the agent's reasoning style and output format.
researcher = Agent(
    role="Senior Market Researcher",
    goal="Find accurate, recent data on the target market segment",
    backstory=(
        "You are a seasoned analyst at a top-tier consulting firm. "
        "You cite sources, quantify claims, and flag data quality issues."
    ),
    tools=[web_search, scraper],
    llm=llm,
    max_iter=5,              # cap tool-use loops; default is 15 which can be expensive
    allow_delegation=False,  # researchers stay focused; delegation risks scope creep
    memory=True,             # enable short-term memory so context survives multi-tool calls
    verbose=False,           # disable per-step console output in production
)

analyst = Agent(
    role="Strategic Analyst",
    goal="Synthesize research into actionable competitive insights",
    backstory=(
        "You transform raw data into executive-ready strategic recommendations. "
        "You identify patterns, risks, and market opportunities."
    ),
    llm=llm,
    max_iter=3,
    allow_delegation=False,
    memory=True,
)

# output_file triggers CrewAI to persist this task's result to disk,
# useful for audit trails in enterprise settings.
research_task = Task(
    description=(
        "Research the current state of {market_segment} focusing on: "
        "key players, recent funding rounds, regulatory changes in the past 6 months."
    ),
    expected_output="A structured report with bullet points, citations, and data tables",
    agent=researcher,
    output_file="research_output.md",  # persist for downstream systems or auditors
)

analysis_task = Task(
    description=(
        "Using the research report, identify the top 3 strategic opportunities "
        "for an enterprise SaaS company entering {market_segment}."
    ),
    expected_output="Executive summary with SWOT analysis and prioritized recommendations",
    agent=analyst,
    context=[research_task],  # explicitly declare dependency; CrewAI passes output as context
)

# Process.sequential guarantees deterministic execution order,
# which is critical for tasks with data dependencies.
crew = Crew(
    agents=[researcher, analyst],
    tasks=[research_task, analysis_task],
    process=Process.sequential,
    memory=True,          # enables cross-task memory via embeddings storage
    embedder={            # configure your own embedder to avoid sending data to OpenAI
        "provider": "azure_openai",
        "config": {"model": "text-embedding-3-small"},
    },
)

result = crew.kickoff(inputs={"market_segment": "AI-powered legal tech"})

CrewAI Flows for Stateful Pipelines

Flows are CrewAI’s answer to long-running, stateful pipelines where you need conditional routing, loops, and typed intermediate state. A Flow is not a Crew replacement, it is an orchestrator that calls Crews or individual LLM calls as steps.

flowchart TD
    START["@start: ingest_request"]
    VALIDATE["validate_input"]
    RESEARCH["run_research_crew"]
    DECISION{"Confidence\n> 0.8?"}
    ENRICH["enrich_with_web_data"]
    DRAFT["draft_report_crew"]
    REVIEW["human_review_gate"]
    DONE["deliver_output"]

    START --> VALIDATE --> RESEARCH --> DECISION
    DECISION -->|"Yes"| DRAFT
    DECISION -->|"No"| ENRICH --> RESEARCH
    DRAFT --> REVIEW --> DONE

    style START fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style DECISION fill:#fff7ed,stroke:#f59e0b,color:#0F172A
    style DONE fill:#f0fdf9,stroke:#0D9488,color:#0F172A

Flows use Pydantic models for state, which gives you type safety, IDE autocomplete, and validation at every step. The @start decorator marks entry points, and @listen decorators declare event-driven transitions, CrewAI routes between steps automatically based on method return values.

from crewai.flow.flow import Flow, listen, start
from pydantic import BaseModel, Field
from typing import Optional
import asyncio

class ResearchState(BaseModel):
    """Typed state shared across all Flow steps.

    Using Pydantic here (not a plain dict) means every step's
    inputs and outputs are validated: catching schema drift early
    before bad data propagates downstream.
    """
    market_segment: str = ""
    raw_research: str = ""
    confidence_score: float = 0.0
    enriched_data: Optional[str] = None
    final_report: str = ""
    iteration_count: int = 0

class MarketResearchFlow(Flow[ResearchState]):
    """End-to-end market research pipeline with confidence gating.

    Flow state persists automatically between steps, eliminating the need
    to manually pass results between functions or store them in globals.
    """

    @start()
    def ingest_request(self) -> str:
        # @start marks this as the Flow's entry point.
        # Return value is passed to all @listen(ingest_request) methods.
        print(f"Starting research for: {self.state.market_segment}")
        return "ready"

    @listen(ingest_request)
    async def run_research_crew(self, trigger: str) -> str:
        # Calling a Crew from within a Flow lets you reuse existing
        # multi-agent logic without duplicating agent definitions.
        result = await asyncio.to_thread(
            research_crew.kickoff,  # run sync crew in thread to avoid blocking event loop
            inputs={"market_segment": self.state.market_segment}
        )
        self.state.raw_research = str(result.raw)
        # Simulate confidence scoring: in production, run an LLM judge
        # or extract structured data with Pydantic from the crew output.
        self.state.confidence_score = 0.75
        self.state.iteration_count += 1
        return "research_complete"

    @listen(run_research_crew)
    async def check_confidence(self, signal: str) -> str:
        # Conditional routing based on typed state: no if/else spaghetti,
        # just clean method chaining. CrewAI re-fires the correct @listen.
        if self.state.confidence_score >= 0.8:
            return "high_confidence"
        elif self.state.iteration_count < 3:
            return "needs_enrichment"
        else:
            # Hard stop after 3 iterations prevents infinite enrichment loops,
            # which would silently inflate LLM costs in production.
            return "high_confidence"

    @listen("needs_enrichment")
    async def enrich_with_web_data(self, signal: str) -> str:
        # Enrichment runs only on low-confidence passes, gated by iteration count.
        # This is the CrewAI equivalent of a retry with backoff.
        self.state.enriched_data = f"Supplemental data for {self.state.market_segment}"
        self.state.confidence_score += 0.15  # update state; next step reads the new value
        return "ready"  # re-triggers run_research_crew via the listen chain

    @listen("high_confidence")
    async def draft_final_report(self, signal: str) -> None:
        # Terminal step: returns None to signal end of flow.
        self.state.final_report = f"Report: {self.state.raw_research[:200]}..."
        print(f"Flow complete after {self.state.iteration_count} iteration(s)")

async def main():
    flow = MarketResearchFlow()
    flow.state.market_segment = "AI-powered legal tech"
    await flow.kickoff_async()
    return flow.state.final_report

Custom Tools with @tool

CrewAI tools extend agent capabilities with any Python function. The @tool decorator wraps a function so CrewAI knows how to describe it to the LLM and handle input/output marshaling automatically.

from crewai.tools import tool
import httpx
from pydantic import BaseModel

class CompanyLookupInput(BaseModel):
    company_name: str
    include_financials: bool = False

@tool("Company Database Lookup")
def lookup_company(company_name: str, include_financials: bool = False) -> str:
    """Look up company details from the internal enterprise CRM.

    Args:
        company_name: Legal name of the company to look up
        include_financials: Whether to include revenue and funding data

    Returns structured JSON string with company profile.
    """
    # The docstring is NOT just documentation: CrewAI sends it to the LLM
    # as the tool description. Precise, example-rich docstrings dramatically
    # improve the agent's ability to call the tool correctly.

    endpoint = "https://crm.internal/api/v2/companies/search"

    # Using httpx (not requests) here because CrewAI runs in async contexts;
    # requests would block the event loop during network I/O.
    with httpx.Client(timeout=10.0) as client:
        resp = client.get(
            endpoint,
            params={"q": company_name, "financials": include_financials},
            headers={"Authorization": f"Bearer {_get_crm_token()}"},
        )
        resp.raise_for_status()

    data = resp.json()
    # Return a string, not a dict: LLMs consume text, and returning raw
    # dicts can cause serialization issues in some CrewAI versions.
    return (
        f"Company: {data['name']}\n"
        f"HQ: {data['headquarters']}\n"
        f"Employees: {data['employee_count']}\n"
        f"Founded: {data['founded_year']}\n"
        + (f"ARR: ${data.get('arr_usd', 'N/A')}" if include_financials else "")
    )

def _get_crm_token() -> str:
    # Centralize credential retrieval; never hardcode secrets in tool functions.
    # In production, use a secrets manager (AWS Secrets Manager, Vault, etc.)
    import os
    return os.environ["CRM_API_TOKEN"]

# Attach the tool to any agent that needs company data
sales_intel_agent = Agent(
    role="Sales Intelligence Specialist",
    goal="Build comprehensive prospect profiles for enterprise sales",
    backstory="You research target accounts with precision, surfacing intent signals and key contacts.",
    tools=[lookup_company, web_search],
    llm=llm,
)

When CrewAI Beats LangGraph

mindmap
  root["Choose CrewAI when..."]
    Team["Role-based teams are natural"]
      SR["Structure mirrors org chart"]
      DR["Delegation is desirable"]
    Memory["Built-in memory matters"]
      EM["Entity memory"]
      SM["Short-term context"]
    Speed["Speed of development wins"]
      LP["Low boilerplate"]
      HC["High configurability"]
    Flows["Stateful pipelines needed"]
      TP["Typed Pydantic state"]
      ER["Event routing"]

CrewAI wins when your problem maps naturally to a team of specialists: a researcher, an analyst, a writer, a reviewer. Its built-in delegation, memory layers (short-term, long-term, entity, contextual), and sequential/hierarchical processes handle 80% of enterprise multi-agent patterns without custom graph wiring.

LangGraph wins when you need fine-grained control over every edge, conditional branching with complex logic, custom state reducers, or deep integration with LangSmith observability. If you find yourself fighting CrewAI’s abstractions to express your workflow, switch to LangGraph, the extra boilerplate is worth the control.

A practical heuristic: start with CrewAI for prototyping and team-like workflows; migrate to LangGraph when you hit a wall with delegation control, custom state management, or streaming requirements.

CrewAI 0.80+ provides a production-ready foundation for structured multi-agent collaboration, and its Flows abstraction closes the gap with lower-level orchestrators for complex pipelines. When your agents naturally map to organizational roles with clear responsibilities, CrewAI’s conventions will accelerate delivery and reduce the cognitive overhead of managing agent coordination manually.

Knowledge Check

3 questions to test your understanding

1 What is the key difference between CrewAI's sequential and hierarchical process modes?

2 Which CrewAI component is best suited for implementing a stateful multi-step pipeline where early results influence later routing decisions?

3 What production risk does allow_delegation=True introduce for every agent in a Crew?

Discussion

Questions and notes from learners on this topic

Loading discussion…

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan