Defining CrewAI agent roles cleanly is the difference between a multi-agent system that decomposes work and one that spins in circles. In this guide we’ll walk through how to specify CrewAI agent roles with concrete code, from role/goal/backstory to tool allocation and verification.
Step 1: Install dependencies and scaffold
Create a virtual environment and install the framework plus the toolset:
pip install crewai crewai-tools langchain-openai
Import the core classes you’ll use for every agent and crew:
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool, FileReadTool
from langchain_openai import ChatOpenAI
import os
Keep your API keys in environment variables. CrewAI reads OPENAI_API_KEY by default, but you can override the base URL per agent if you use a gateway.
Step 2: Define the role, goal, and backstory
The role, goal, and backstory strings are not decorative. CrewAI injects them into the system prompt for the agent. Vague roles produce vague behavior. Write them like job descriptions.
research_agent = Agent(
role="Senior Market Research Analyst",
goal="Identify the top three competitor pricing models for API gateways",
backstory=(
"You have 10 years of experience analyzing SaaS infrastructure "
"pricing. You cite sources and never guess numbers."
),
verbose=True,
allow_delegation=False,
)
When you define CrewAI agent roles this way, the role sets the persona, the goal scopes the objective, and the backstory loads domain context. Avoid overlapping goals across agents; if two roles can accomplish the same task, the crew will waste tokens arbitrating.
Why backstory is not optional
The backstory is free context injection that persists across every task the agent runs. A role without a backstory defaults to a generic assistant. A backstory that states “you never use unverified stats” actively changes tool-use behavior. Treat it as the constraints section of a spec.
Step 3: Scope tools to the role
Tools should match the agent’s mandate. A research agent needs search and file access; a writer agent may need neither.
search_tool = SerperDevTool()
file_tool = FileReadTool()
research_agent.tools = [search_tool, file_tool]
You can also pass tools in the constructor. Only attach what the role needs. An agent with a web search tool and a “do not use external data” goal will conflict with itself.
For a second role, define a writer:
writer_agent = Agent(
role="Technical Writer",
goal="Draft a concise internal memo from the research agent's findings",
backstory="You turn raw analysis into clear prose for engineering leads.",
verbose=True,
)
# intentionally no tools; relies on delegated context
Parameterizing tools
Most CrewAI tools accept args. FileReadTool can be locked to a directory; SerperDevTool can take a custom API key. Scope them at construction so a role cannot read outside its lane.
Step 4: Configure the LLM backend
Each agent accepts an llm argument. Use a LangChain chat model. If you want a single OpenAI-compatible endpoint that fronts 240+ models with automatic fallback when a provider is degraded, point base_url at n4n.ai and use model strings like openai/gpt-4o.
llm = ChatOpenAI(
model="openai/gpt-4o",
temperature=0.2,
base_url="https://api.n4n.ai/v1",
api_key=os.getenv("N4N_API_KEY"),
)
research_agent.llm = llm
writer_agent.llm = llm
Lower temperature for research, higher for creative writing. Set max_iter if you see runaway loops. The gateway forwards provider cache-control hints, so repeated role prompts with stable prefixes will hit cache automatically.
Step 5: Write tasks that exercise the roles
Tasks bind an agent to a concrete deliverable. The expected_output field is critical: it tells the agent what “done” looks like.
research_task = Task(
description=(
"Search for pricing pages of three API gateway providers. "
"Extract monthly cost, per-token metering, and fallback policies."
),
expected_output="A bullet list with source URLs and extracted metrics.",
agent=research_agent,
)
write_task = Task(
description="Summarize the research into a 200-word memo for the CTO.",
expected_output="A markdown memo with headings and a recommendation.",
agent=writer_agent,
)
When you define CrewAI agent roles, pair each with at least one task that only that role can logically own. If a task could be done by either agent, split it.
Step 6: Assemble the crew and pick a process
A sequential process runs tasks in order. A hierarchical process uses a manager agent to delegate.
crew = Crew(
agents=[research_agent, writer_agent],
tasks=[research_task, write_task],
process=Process.sequential,
verbose=True,
)
result = crew.kickoff()
print(result)
If you need dynamic delegation, set process=Process.hierarchical and give the manager an LLM. For most pipelines, sequential is easier to debug because the data flow is linear.
Step 7: Run and verify success
Execute the script. Verification is not just “no exception”. Check:
- The research task printed sourced bullets (verbose logs show tool calls).
- The writer task received the research output (inspect
crew.tasks[1].output). - The final
resultmatches the expected memo format.
assert "memo" in result.lower()
assert research_agent.tools[0].name == "serper_dev_tool"
print("CrewAI agent roles executed and verified.")
If the writer produces hallucinated numbers, the research task’s expected_output was too loose. Tighten it.
Best practices for designing CrewAI agent roles
Single responsibility. One role per function. A “full-stack developer who also does sales” will dilute prompts.
Explicit backstory. The backstory is free context injection. Use it to set constraints (“You never use unverified stats”).
Tool minimalism. Extra tools increase prompt size and mistake surface. Add only what the goal requires.
Delegation control. Set allow_delegation=False unless you run a hierarchical crew. Autonomous delegation in sequential crews causes loops.
Model routing. Cheap models for triage, strong models for synthesis. Because the gateway honors client routing directives, you can pin model="anthropic/claude-3.5-sonnet" for the writer and a smaller model for research.
Common pitfalls when defining roles
- Role collision: Two agents with “analyze data” goals will both grab the task. Differentiate by scope.
- Missing expected_output: Without it, the agent decides done-ness. Always specify.
- Overlapping tools: If both agents have write access to the same file, you’ll get race conditions in long runs.
- Ignoring verbosity:
verbose=Trueis non-negotiable during development. You need to see the prompts.
Verify in production
Once local runs pass, log token usage. CrewAI exposes crew.usage_metrics after kickoff. If a role consistently burns 3x the tokens of another, its goal is too broad. Refactor the CrewAI agent roles before scaling.
That’s the loop: define, bind, run, measure, tighten.