AM
AgenticMedia
SponsoredSponsored Banner
Domain.com - Your Global Address Book. Domains & Sites Starts at $X.XX/domain
agentic-ai
•7 min read

Multi-Agent Workflows Explained: Assigning Roles to AI Teams

How to architect multi-agent systems with specialized roles — Researcher, Writer, Code Reviewer, Tester — comparing hierarchical vs. peer-to-peer patterns with LangGraph and CrewAI code.

AT

AgenticMedia Team

Content Creator

Written in Markdown
Org-chart-style diagram showing specialized AI agents in hierarchical and peer-to-peer communication patterns

TL;DR / Key Takeaways

Multi-agent systems fail most often not because the LLM is weak, but because the communication topology doesn’t match the task. Before writing a single agent prompt, decide how agents talk to each other:

  • Hierarchical (manager-delegate) patterns fit tasks with a clear decomposition — a manager agent breaks work into subtasks and routes them to specialists, then synthesizes results. Best when subtasks are largely independent.
  • Peer-to-peer (round-robin/group chat) patterns fit tasks requiring iterative refinement — a Writer and a Critic going back and forth until quality converges. Best when the “right” output depends on multiple rounds of feedback, not a single pass.
  • Pipeline patterns (a strict Researcher → Writer → Reviewer → Tester sequence) are the simplest and most debuggable topology, and are the right default unless you have a specific reason to need dynamic routing or iterative back-and-forth.
  • More agents is not automatically better. Every additional agent in the loop is an additional LLM round-trip, additional failure surface, and additional context to keep synchronized — a well-scoped 3-agent pipeline reliably outperforms a poorly-scoped 6-agent system in both cost and output quality.
  • Role specificity beats role quantity. A single agent with a well-written, narrow role prompt (“You are a Python code reviewer who checks only for security vulnerabilities”) outperforms a vague “helpful assistant” role split across multiple agents.

Core Concept: Three Communication Topologies

1. Pipeline (Sequential, Fixed Order)

Each agent’s output becomes the next agent’s input, in a fixed, predetermined sequence. This is the simplest topology to build, debug, and reason about — there’s no dynamic routing decision to get wrong.

Researcher → Writer → Code Reviewer → Tester → Final Output

Best for: tasks with a natural linear decomposition — content pipelines, code generation-then-review workflows, data processing chains.

2. Hierarchical (Manager-Delegate)

A manager agent receives the overall task, decides which specialist agent(s) should handle which subtask, and can route work dynamically based on the task’s actual shape rather than a fixed sequence — including skipping agents that aren’t needed for a given input.

                 ┌─→ Researcher ─┐
Manager Agent ───┼─→ Code Reviewer ─┼──→ Manager synthesizes → Output
                 └─→ Tester ──────┘

Best for: tasks where the right subset of specialists varies by input — e.g., a customer support triage system where only some tickets need a technical specialist agent.

3. Peer-to-Peer (Group Chat / Iterative)

Agents participate in a shared conversation, critiquing and revising each other’s output across multiple turns, with a termination condition (approval, iteration cap, or convergence check) ending the loop.

Writer ⟷ Critic ⟷ Writer ⟷ Critic → (Critic approves) → Output

Best for: tasks that benefit from genuine back-and-forth refinement — code review cycles, editorial revision, negotiation-style tasks where a single pass rarely produces the final-quality output.


Practical Tutorial: Building a Researcher → Writer → Code Reviewer → Tester Pipeline

Pattern A: Hierarchical, via LangGraph Conditional Routing

from typing import TypedDict, Literal
from langgraph.graph import StateGraph, END
from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(model="claude-sonnet-4-6")

class TeamState(TypedDict):
    task: str
    research_notes: str
    draft_code: str
    review_feedback: str
    test_results: str
    needs_revision: bool

def manager_route(state: TeamState) -> Literal["researcher", "code_reviewer", "end"]:
    """Manager decides the next specialist based on current state."""
    if not state.get("research_notes"):
        return "researcher"
    if state.get("needs_revision"):
        return "code_reviewer"  # send back for another review pass
    return "end"

def researcher_node(state: TeamState) -> TeamState:
    response = llm.invoke(f"Research technical context needed for: {state['task']}")
    return {"research_notes": response.content}

def writer_node(state: TeamState) -> TeamState:
    response = llm.invoke(
        f"Write Python code for: {state['task']}\nContext: {state['research_notes']}"
    )
    return {"draft_code": response.content}

def code_reviewer_node(state: TeamState) -> TeamState:
    response = llm.invoke(
        f"Review this code for correctness and security issues:\n{state['draft_code']}\n"
        f"Respond with 'APPROVED' or specific revision feedback."
    )
    approved = "APPROVED" in response.content
    return {"review_feedback": response.content, "needs_revision": not approved}

def tester_node(state: TeamState) -> TeamState:
    response = llm.invoke(f"Write and describe unit tests for:\n{state['draft_code']}")
    return {"test_results": response.content}

graph = StateGraph(TeamState)
graph.add_node("researcher", researcher_node)
graph.add_node("writer", writer_node)
graph.add_node("code_reviewer", code_reviewer_node)
graph.add_node("tester", tester_node)

graph.set_entry_point("researcher")
graph.add_edge("researcher", "writer")
graph.add_edge("writer", "code_reviewer")
graph.add_conditional_edges(
    "code_reviewer",
    lambda state: "writer" if state["needs_revision"] else "tester",
    {"writer": "writer", "tester": "tester"},
)
graph.add_edge("tester", END)

app = graph.compile()
result = app.invoke({"task": "a rate limiter using the token bucket algorithm"})

The key architectural decision here is add_conditional_edges() on the reviewer node — if the reviewer doesn’t approve, the graph routes back to the writer for revision rather than proceeding, and this loop is explicit and bounded by the graph structure itself, not by hoping an LLM “remembers” to loop back.

Pattern B: Hierarchical, via CrewAI’s Manager Process

from crewai import Agent, Task, Crew, Process
from langchain_anthropic import ChatAnthropic

llm = ChatAnthropic(model="claude-sonnet-4-6")

researcher = Agent(
    role="Technical Researcher",
    goal="Gather accurate technical context for implementation tasks",
    backstory="You research best practices and prior art before any code is written.",
    llm=llm,
)

writer = Agent(
    role="Python Developer",
    goal="Write clean, well-documented Python code",
    backstory="You implement based on research context, following PEP 8 strictly.",
    llm=llm,
)

reviewer = Agent(
    role="Code Reviewer",
    goal="Catch correctness and security issues before code ships",
    backstory="You are a strict reviewer who never approves code with unhandled edge cases.",
    llm=llm,
)

tester = Agent(
    role="QA Engineer",
    goal="Write comprehensive unit tests",
    backstory="You write tests that cover edge cases the developer likely missed.",
    llm=llm,
)

research_task = Task(
    description="Research implementation approaches for a token bucket rate limiter.",
    expected_output="A summary of the algorithm and key implementation considerations.",
    agent=researcher,
)
write_task = Task(
    description="Implement a token bucket rate limiter in Python.",
    expected_output="Complete, documented Python code.",
    agent=writer,
    context=[research_task],
)
review_task = Task(
    description="Review the implementation for correctness and security issues.",
    expected_output="Either 'APPROVED' or specific revision feedback.",
    agent=reviewer,
    context=[write_task],
)
test_task = Task(
    description="Write unit tests covering normal operation and edge cases.",
    expected_output="A complete test suite.",
    agent=tester,
    context=[write_task, review_task],
)

crew = Crew(
    agents=[researcher, writer, reviewer, tester],
    tasks=[research_task, write_task, review_task, test_task],
    process=Process.hierarchical,
    manager_llm=llm,
)

result = crew.kickoff()

CrewAI’s Process.hierarchical introduces an implicit manager LLM that oversees task delegation — this is more convenient to set up than LangGraph’s explicit conditional routing, but the revision loop (reviewer sending work back to the writer) is not native to this structure the way it is in the LangGraph version above; CrewAI’s hierarchical mode is better suited to delegation of independent subtasks than to iterative revision loops.

Pattern C: Peer-to-Peer, via AutoGen Group Chat (Writer/Critic Loop)

from autogen import AssistantAgent, GroupChat, GroupChatManager

llm_config = {"model": "claude-sonnet-4-6", "api_key": "your-key"}

writer = AssistantAgent(
    name="Writer",
    system_message="You write Python code based on the task. Revise based on Critic feedback.",
    llm_config=llm_config,
)

critic = AssistantAgent(
    name="Critic",
    system_message=(
        "You review the Writer's code strictly. If it has issues, explain them clearly. "
        "If it meets all requirements, respond with exactly 'APPROVED'."
    ),
    llm_config=llm_config,
)

group_chat = GroupChat(
    agents=[writer, critic],
    messages=[],
    max_round=6,  # hard cap to prevent runaway conversations
    speaker_selection_method="round_robin",
)

manager = GroupChatManager(groupchat=group_chat, llm_config=llm_config)

writer.initiate_chat(
    manager,
    message="Write a token bucket rate limiter in Python.",
)

max_round=6 is the critical safety valve here — peer-to-peer conversational patterns can, without a hard cap, continue exchanging feedback indefinitely if the termination condition (APPROVED) is ambiguous or the critic is overly strict; always bound conversational loops explicitly rather than trusting the conversation to self-terminate.


Tool / Solution Comparison Table

Topology Best Fit Framework Best Suited Key Risk
Pipeline (sequential) Linear task decomposition, easiest to debug LangGraph (linear edges) or CrewAI (Process.sequential) Rigid — can’t adapt if a step needs to be skipped or repeated dynamically
Hierarchical (manager-delegate) Tasks where the right specialist subset varies by input CrewAI (Process.hierarchical) or LangGraph with conditional edges Manager LLM’s delegation reasoning can misroute on ambiguous tasks
Peer-to-peer (group chat) Iterative refinement, critique/revise cycles AutoGen (GroupChat) Unbounded conversations without an explicit round cap or clear termination signal

Actionable Checklist / Next Steps

  • Identify whether your task is linear, input-dependent, or iterative-refinement in shape before picking a topology — this decision matters more than which framework you use.
  • Default to a pipeline unless you have a specific reason to need dynamic routing or back-and-forth conversation.
  • Write narrow, specific role prompts for each agent — avoid vague “helpful assistant” framing even when using a dedicated role-based framework.
  • Always bound peer-to-peer/conversational loops with an explicit max-round cap, not just a hoped-for termination phrase.
  • Keep your agent count as small as the task genuinely requires — each additional agent adds cost, latency, and a new failure surface.
  • Test your revision/feedback loop with intentionally bad input to confirm it actually loops back and terminates correctly, not just on the happy path.

Next in this series: Pixar-Style Visual Animation Workflows: From Image Prompt to Render — Day 12 shifts to AI media production pipelines.

SponsoredSponsored Feature
Domain.com - Your Global Address Book. Domains & Sites Starts at $X.XX/domain
#Multi-Agent Systems#LangGraph#CrewAI#Agent Orchestration#Python
AT

AgenticMedia Team

Content Creator • @agenticmedia

Writer and technology enthusiast sharing engineering playbooks and digital optimization guides.