← All posts
·11 min read

Claude Code with CrewAI: Build Multi-Agent Crews

Claude CodeCrewAIMulti-AgentPython
Claude Code building a CrewAI multi-agent crew with agents, tasks, and tools

Why CrewAI needs explicit CLAUDE.md constraints

CrewAI is a Python framework for orchestrating role-playing autonomous agents. You define agents with a role, a goal, and a backstory, give them tools, assign them tasks, and assemble them into a crew that runs either sequentially or hierarchically. It is one of the cleanest mental models in the multi-agent space: agents are coworkers, tasks are tickets, the crew is the team.

The problem when you ask Claude Code to "build a CrewAI system" is that Claude's training data is a soup of overlapping agent frameworks and several generations of CrewAI's own API. Without explicit instructions, Claude will mix in bare LangChain primitives, reach for deprecated CrewAI imports, forget that every Agent needs role, goal, and backstory, wire tasks without an expected_output, default to the wrong Process, and define tools using class patterns that the current @tool decorator replaced. The code often looks plausible and imports cleanly, then fails at run time or produces a crew that does not actually delegate.

These are not rare edge cases. They are the default output of a competent Claude Code session that has no guardrails for which CrewAI it should be writing. The CLAUDE.md file is what pins it to the modern, runnable surface.

This guide covers the CLAUDE.md configuration, correct agent and task patterns, the sequential-versus-hierarchical decision, tool definitions, a runnable crew, and the six most common mistakes Claude makes by default. CrewAI sits in the same family as other agent frameworks, so if you are also evaluating that layer, Claude Code with LangChain covers the chain-and-graph alternative, and Claude Code subagents guide covers running multiple agents inside Claude Code itself.

The CrewAI CLAUDE.md template

Place this at your project root. Every Claude Code session reads it before generating any code.

# CrewAI rules

## Stack
- crewai (current major) + crewai-tools, Python 3.11+
- Pydantic v2 for structured outputs
- One LLM provider configured via environment (Anthropic / OpenAI), never hardcoded keys

## Imports (use these exact symbols)
- from crewai import Agent, Task, Crew, Process
- from crewai.tools import tool        # the @tool decorator
- from crewai_tools import ...          # prebuilt tools only
- NEVER import from langchain.agents for the orchestration layer
- NEVER use deprecated crewai tool base classes; use the @tool decorator

## Agent contract (MANDATORY)
- Every Agent MUST have: role, goal, backstory
- role = a job title string; goal = one sentence outcome; backstory = short persona
- Set verbose=True during development, allow_delegation explicitly (default False)
- Attach tools as a list: tools=[my_tool, ...]
- NEVER create an Agent missing any of role/goal/backstory

## Task contract (MANDATORY)
- Every Task MUST have: description, expected_output, agent
- expected_output describes the SHAPE of a good result (one or two sentences)
- For structured output, set output_pydantic=MyModel on the Task
- Chain tasks with context=[earlier_task] to pass results forward
- NEVER create a Task without an explicit expected_output

## Crew assembly
- Crew(agents=[...], tasks=[...], process=Process.sequential, verbose=True)
- Process.sequential  -> tasks run in listed order, output feeds forward
- Process.hierarchical -> a manager agent delegates; REQUIRES manager_llm set
- Run with result = crew.kickoff(inputs={...}) and read result.raw
- NEVER use Process.hierarchical without manager_llm or a manager_agent

## Tools
- Define custom tools with the @tool("Name") decorator over a typed function
- The docstring IS the tool description the agent reads; write it for the agent
- Return a string or a serialisable value, never a raw object the LLM cannot read
- NEVER define a tool without a clear docstring; the agent uses it to decide when to call

## Hard rules
- ALWAYS load secrets from os.environ / .env, NEVER hardcode API keys
- ALWAYS give Tasks an expected_output; it drives quality more than the description
- NEVER mix bare LangChain agent classes with CrewAI Agents in one orchestration
- ALWAYS pass runtime values via crew.kickoff(inputs={...}) and {placeholders} in text
- Keep one responsibility per agent; split rather than overload a single agent

Three rules prevent the majority of broken crews.

The agent contract rule matters because Claude will happily emit Agent(role="Researcher") and move on. CrewAI agents behave dramatically better with a real goal and backstory, the framework feeds both into the system prompt that steers the agent. An agent missing them is technically valid and practically useless, drifting off task because nothing anchors its behaviour. Declaring the three-field contract with examples makes Claude write complete agents every time.

The expected_output rule is the single highest-leverage line in the file. The description tells the agent what to do; the expected_output tells it what "done" looks like. Without it, agents return rambling prose when you wanted a list, or one line when you wanted a structured report. Claude omits it by default because the field is optional in the type signature. Making it mandatory in CLAUDE.md is what turns vague crews into reliable ones.

The process selection rule stops a specific runtime crash. Process.hierarchical spins up a manager agent that delegates to the others, and that manager needs its own LLM via manager_llm. Claude often writes process=Process.hierarchical because it sounds more sophisticated, then omits manager_llm, and the crew fails on kickoff(). Pinning the rule, sequential by default, hierarchical only with a manager, removes the trap.

Install and project setup

pip install crewai crewai-tools

Set your provider credentials in the environment rather than in code:

# .env
ANTHROPIC_API_KEY=sk-ant-...
# or
OPENAI_API_KEY=sk-...

A clean project layout that Claude Code can navigate:

my_crew/
  .env
  CLAUDE.md
  src/
    agents.py      # agent definitions
    tasks.py       # task definitions
    tools.py       # custom @tool functions
    crew.py        # crew assembly + kickoff
  main.py          # entrypoint

Keeping agents, tasks, and tools in separate modules is not just tidiness. It gives Claude Code stable, single-responsibility files to edit, so when you ask it to "add a fact-checking agent" it edits agents.py and tasks.py rather than threading changes through one monolithic script. For why module structure helps the agent, Claude Code with Python covers the broader project conventions that keep generated code maintainable.

Defining agents

Each agent is a coworker with a job. The role, goal, and backstory together form the persona CrewAI feeds to the model.

# src/agents.py
from crewai import Agent

def researcher_agent() -> Agent:
    return Agent(
        role="Senior Research Analyst",
        goal="Find accurate, current, well-sourced information on the given topic",
        backstory=(
            "You are a meticulous analyst who never states a claim without a "
            "source and flags anything you cannot verify."
        ),
        verbose=True,
        allow_delegation=False,
    )

def writer_agent() -> Agent:
    return Agent(
        role="Technical Writer",
        goal="Turn research notes into a clear, structured brief for a developer audience",
        backstory=(
            "You write plainly, lead with the conclusion, and cut every sentence "
            "that does not earn its place."
        ),
        verbose=True,
        allow_delegation=False,
    )

Notice the goals are outcomes, not activities, and the backstories shape how the agent behaves, not just what it is. That is the difference between an agent that performs and one that wanders.

Defining tasks

Tasks are the tickets. The expected_output is what makes the result usable.

# src/tasks.py
from crewai import Task

def research_task(agent, topic_placeholder: str = "{topic}") -> Task:
    return Task(
        description=(
            f"Research {topic_placeholder}. Gather the key facts, current "
            f"developments, and the most credible sources. Flag any contested claims."
        ),
        expected_output=(
            "A bulleted list of 6-10 verified findings, each with a one-line "
            "explanation and a source reference."
        ),
        agent=agent,
    )

def writing_task(agent, context_tasks: list) -> Task:
    return Task(
        description=(
            "Using the research findings, write a 400-word brief for developers. "
            "Lead with the single most important takeaway."
        ),
        expected_output=(
            "A 400-word markdown brief with a one-sentence summary at the top "
            "followed by 3-4 short sections."
        ),
        agent=agent,
        context=context_tasks,   # receives the research task's output
    )

The context=context_tasks argument is how output flows between agents. The writing task receives the research task's result automatically, no manual plumbing. This is CrewAI's core value, and it only works when each task declares what it produces via expected_output.

Sequential vs hierarchical: choosing a process

CrewAI runs a crew with one of two processes, and choosing wrong is a common source of confusion.

Sequential runs tasks in the order you list them, feeding each output forward. It is predictable, debuggable, and the right default for pipelines: research then write then review. You always know what runs next.

# src/crew.py
from crewai import Crew, Process
from src.agents import researcher_agent, writer_agent
from src.tasks import research_task, writing_task

def build_crew() -> Crew:
    researcher = researcher_agent()
    writer = writer_agent()

    r_task = research_task(researcher)
    w_task = writing_task(writer, context_tasks=[r_task])

    return Crew(
        agents=[researcher, writer],
        tasks=[r_task, w_task],
        process=Process.sequential,
        verbose=True,
    )

Hierarchical introduces a manager agent that decides which agent handles what and can re-delegate. It is more flexible for open-ended work but less predictable, and it requires a manager_llm so the manager has a model to reason with.

from crewai import Crew, Process

crew = Crew(
    agents=[researcher, writer, editor],
    tasks=[r_task, w_task, e_task],
    process=Process.hierarchical,
    manager_llm="anthropic/claude-sonnet-4-6",   # REQUIRED for hierarchical
    verbose=True,
)

The rule of thumb: use sequential until you have a concrete reason not to. Most crews are pipelines, and a pipeline that runs in a known order is far easier to debug than a manager improvising delegation. Reach for hierarchical when the routing genuinely depends on intermediate results in a way a fixed order cannot express.

Running the crew

# main.py
from src.crew import build_crew

def main():
    crew = build_crew()
    result = crew.kickoff(inputs={"topic": "edge databases in 2026"})

    # result.raw is the final task's text output
    print(result.raw)

    # result.tasks_output is the list of per-task outputs
    for t in result.tasks_output:
        print(t.description[:60], "->", str(t.raw)[:80])

if __name__ == "__main__":
    main()

The inputs={"topic": ...} dictionary fills the {topic} placeholders in your task descriptions at run time. This is the correct way to parameterise a crew, not f-string interpolation at definition time, because it keeps the crew reusable across inputs.

Defining custom tools

Tools extend what an agent can do beyond reasoning over text. The @tool decorator turns a typed function into something an agent can call, and the docstring is the description the agent reads to decide when to use it.

# src/tools.py
from crewai.tools import tool

@tool("Fetch URL Content")
def fetch_url(url: str) -> str:
    """Fetch the readable text content of a web page given its URL.
    Use this when you need the actual contents of a specific page,
    not a search. Returns plain text, truncated to a reasonable length."""
    import httpx
    resp = httpx.get(url, timeout=20.0, follow_redirects=True)
    resp.raise_for_status()
    # In production, strip HTML to readable text before returning
    return resp.text[:8000]

Two things make a tool work well. First, the docstring is written for the agent, not for a human maintainer: it states what the tool does and when to reach for it, because the agent uses exactly that text to decide. Second, it returns a string the model can read, not a response object or a dict the LLM cannot interpret. Attach it to an agent with tools=[fetch_url]. For wiring richer external systems, the Claude Code MCP servers guide covers connecting tools through MCP as an alternative to hand-rolled functions.

Common mistakes Claude makes by default

Six patterns Claude generates without CLAUDE.md constraints, with the correct replacement for each.

1. Agent missing goal or backstory

Claude generates: Agent(role="Researcher", tools=[search]) with no goal or backstory.

Correct: Agent(role="Senior Research Analyst", goal="Find accurate, current information on the topic", backstory="A meticulous analyst who never states a claim without a source", tools=[search]). All three fields steer behaviour; omitting them produces a drifting agent.

2. Task with no expected_output

Claude generates: Task(description="Research the topic", agent=researcher).

Correct: Task(description="Research the topic", expected_output="A bulleted list of 6-10 verified findings, each with a source", agent=researcher). The expected_output drives result quality more than the description does.

3. Hierarchical process without a manager LLM

Claude generates: Crew(agents=[...], tasks=[...], process=Process.hierarchical).

Correct: add manager_llm="anthropic/claude-sonnet-4-6", or use Process.sequential. Hierarchical needs a manager model and crashes on kickoff() without one.

4. Bare LangChain agents in the orchestration layer

Claude generates: from langchain.agents import initialize_agent mixed into the crew.

Correct: use CrewAI's Agent, Task, and Crew for orchestration. LangChain has its place, but mixing its agent runtime into a CrewAI crew produces an incoherent system. Keep the orchestration in one framework.

5. Deprecated tool definition

Claude generates: a class extending an old BaseTool with a _run method.

Correct: the decorator form, @tool("Name") over a typed function with a docstring. The decorator is the current, simplest surface and the docstring becomes the agent-readable description.

6. Interpolating inputs at definition time

Claude generates: description=f"Research {topic}" where topic is a Python variable fixed when the task is built.

Correct: write description="Research {topic}" with a literal placeholder and pass crew.kickoff(inputs={"topic": topic}). This keeps the crew reusable across different inputs instead of baking one value in.

Add all six pairs to CLAUDE.md as a common-mistakes section. Claude benefits from the before-and-after contrast because it can match the pattern it would otherwise generate to the correct form.

Building crews that actually delegate

The CrewAI CLAUDE.md in this guide produces multi-agent systems where every agent has a complete role-goal-backstory contract, every task declares an expected_output, the process is sequential unless hierarchy is genuinely needed, tools use the @tool decorator with agent-facing docstrings, and runtime values flow through kickoff(inputs=...) rather than baked-in interpolation.

The underlying principle is the same for any framework integration with Claude Code: the framework has one current, correct way to do things, and Claude's training data contains several older or adjacent ways that compile but misbehave. The CLAUDE.md template collapses that ambiguity, so the crews Claude generates run on the first try and actually delegate the way CrewAI intends. For orchestrating agents inside Claude Code itself rather than in a separate Python runtime, Claude Code subagents guide is the companion read.

Get Claudify. A complete Claude Code operating system with project memory and agent patterns configured so multi-agent builds work on the first run.

More like this

Ready to upgrade your Claude Code setup?

Get Claudify
Featured on Dofollow.Tools AI Toolz Dir Claudify - Featured on Startup Fame