n4nAI

How to define agent roles in CrewAI

Step-by-step guide to defining CrewAI agent roles: set role, goal, backstory, tools, and LLM backend, then run a crew and verify outputs.

n4n Team4 min read781 words

Audio narration

Coming soon — every post will get a voice note here.

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:

  1. The research task printed sourced bullets (verbose logs show tool calls).
  2. The writer task received the research output (inspect crew.tasks[1].output).
  3. The final result matches 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=True is 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.

Tagscrewaiai-agentsagent-design

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All crewai multi-agent systems posts →