TechStackopoly
Multi-Agent AI · Architecture Guide · 2026

Multi-Agent AI Workflow: A Complete Guide

Single agents hit a ceiling. Complex tasks — research, analysis, code generation, content pipelines — need specialized agents working in concert. This guide covers every component of a production multi-agent system, compares CrewAI, LangGraph, and AutoGen, includes working Python code, and tells you what things will actually cost.

On this page

Multi-agent vs. single-agent: when to upgrade

A single-agent system is one LLM call — or one LLM augmented with tools — handling an entire task from start to finish. This works well for focused, bounded tasks: answering a question, summarizing a document, writing a function. The model has enough context, the toolset is small, and failure modes are simple to debug.

Problems emerge as tasks grow in scope or require competing expertise. Ask a single agent to "research three competitors, write a comparison report, fact-check every claim, format it in markdown, and check it matches our style guide" and you get one of two bad outcomes: the context window balloons to the point of degraded attention, or the model compromises across roles and does none of them well.

Multi-agent systems solve this by decomposing work into specialized units. A researcher agent uses search tools to gather facts. A critic agent reads those facts and flags weaknesses. A synthesizer agent writes the final output. Each agent has a focused prompt, a limited toolset, and a bounded scope — which means each can be independently tuned and tested.

When single-agent is still the right call

When to switch to multi-agent

Rule of thumb: If you find yourself writing a system prompt with more than three distinct "roles" or "personas," that is a signal to split into separate agents. Your outputs will improve and your prompts will become dramatically easier to maintain.

Key components of a multi-agent system

Every production multi-agent system — regardless of framework — has five core components. Understanding each one before choosing a framework prevents architecture regrets.

Component 1

Orchestrator

The orchestrator is the brain of the system. It decides which agent handles which task, routes outputs between agents, manages retry logic, and determines when the overall task is done. Orchestrators can be static (a hardcoded pipeline: A → B → C) or dynamic (an LLM reads current state and decides the next step). Static orchestrators are predictable and cheap. Dynamic orchestrators are flexible but add an extra LLM call per routing decision and can loop unexpectedly.

Component 2

Agents

Each agent is a model + system prompt + toolset + (optionally) memory. The system prompt defines the agent's role, constraints, output format, and escalation rules. The toolset is bounded to what the role actually needs — a researcher doesn't need code execution; a coder doesn't need web search. Keeping toolsets minimal reduces hallucination and speeds up the model's tool selection reasoning.

Component 3

Tools

Tools are functions the agent can call to interact with the outside world: search APIs (Tavily, Serper), code execution sandboxes (E2B, Daytona), databases, REST APIs, file systems, or browser automation. Well-designed tools have typed inputs, structured outputs, and clear error messages. An agent that receives a cryptic tool error will hallucinate a response rather than ask for help — so tool reliability is critical.

Component 4

Memory

Memory is how agents retain and share context across turns and across agent boundaries. There are four types: in-context memory (the conversation history in the current prompt), external memory (a vector store or key-value store agents can query), episodic memory (logs of past task runs retrieved for similar future tasks), and shared scratchpad (a structured dict or document all agents can read and write). Most systems combine in-context for the active turn and external for cross-agent state sharing.

Component 5

Communication protocol

Agents need a shared language for passing work. This can be as simple as a Python dict passed between function calls, or as structured as a message schema with sender, recipient, payload type, and priority fields. In frameworks like LangGraph, state is a typed dict that every node reads and writes. In CrewAI, agents communicate via task outputs. Agreeing on a communication schema early prevents the "telephone game" problem where each handoff degrades the signal.

Framework comparison: CrewAI vs LangGraph vs AutoGen vs custom

The four main options in 2026, with honest tradeoffs:

Framework Best for Orchestration style Learning curve Production readiness Pricing
CrewAI Role-based teams, sequential pipelines Declarative — define Crew, Agents, Tasks Low Good Open-source; CrewAI+ cloud is paid
LangGraph Complex stateful graphs, conditional routing, HITL Explicit graph — nodes + edges + state Medium Excellent Open-source; LangSmith for observability
AutoGen Conversational multi-agent loops, code generation Chat-driven — agents converse in rounds Medium Good Open-source (Microsoft Research)
Custom (Python) Simple pipelines (≤3 agents), maximum control Whatever you build High (architecture decisions) Depends Free — just model API costs

CrewAI — the quickest start

CrewAI's mental model maps cleanly to how humans organize work: you define a Crew (the team), Agents (the people), and Tasks (the work items). The framework handles sequencing, context passing, and output formatting. A three-agent research crew can be standing in under 50 lines of Python. The trade-off is that complex conditional logic — "if the critic flags more than two issues, loop back to the researcher" — requires workarounds that LangGraph handles natively.

LangGraph — the production standard

LangGraph models your system as a directed graph where each node is an agent or a function, and edges define the flow. State is a typed dict that persists across nodes and across human interrupts. This makes LangGraph the right choice for: workflows that need to pause and wait for human input, systems with complex conditional routing, long-running tasks that must survive restarts, and anything you need to replay or debug step-by-step. The LangGraph Studio debugger is genuinely excellent. The cost is more boilerplate than CrewAI.

AutoGen — the conversational approach

Microsoft Research's AutoGen treats agents as participants in a group chat. Agents take turns responding to each other's messages until a termination condition is met. This is natural for iterative refinement (write code, run it, fix bugs, repeat) and code-heavy workflows. AutoGen's AssistantAgent + UserProxyAgent pattern is particularly effective for tasks that involve executing code in a sandbox. The limitation: purely conversational coordination can be harder to structure for strictly sequential pipelines.

Custom Python — when frameworks get in the way

For pipelines with 2–3 agents and a fixed sequential flow, rolling your own is often cleaner than fighting a framework's abstractions. Define each agent as a function, pass outputs as structured dicts, and call them in sequence. You get zero magic, full transparency, and no dependency on a framework's release cycle. This approach breaks down past 3–4 agents or when you need conditional routing, human-in-the-loop, or persistent state across sessions.

Step-by-step: build a research + synthesis multi-agent system

This example builds a three-agent pipeline using LangGraph and the Anthropic Claude API. The system takes a research question, has a Researcher agent gather information, a Critic agent evaluate the sources and flag gaps, and a Synthesizer agent produce a structured report.

Step 1 — Install dependencies

pip install langgraph langchain-anthropic langchain-community tavily-python

Step 2 — Define shared state

from typing import TypedDict, Annotated, List
from langgraph.graph.message import add_messages

class AgentState(TypedDict):
    # The research question
    question: str
    # Raw research findings from Researcher
    research: str
    # Critique and gaps from Critic
    critique: str
    # Final synthesized report from Synthesizer
    report: str
    # Message history (for debugging)
    messages: Annotated[list, add_messages]
    # How many revision loops we've done
    revision_count: int

Step 3 — Build the Researcher agent

import os
from langchain_anthropic import ChatAnthropic
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.messages import HumanMessage, SystemMessage

llm = ChatAnthropic(
    model="claude-sonnet-4-5",
    api_key=os.environ["ANTHROPIC_API_KEY"],
)

search_tool = TavilySearchResults(max_results=5)

RESEARCHER_PROMPT = """You are a meticulous research agent. Your job is to gather
accurate, relevant information on the given question using web search.
Search multiple angles. Return a structured summary with:
- Key facts (bulleted)
- Source URLs
- Confidence level (high/medium/low) for each fact
Do not synthesize or editorialize. Report only what sources say."""

def researcher_node(state: AgentState) -> AgentState:
    # Run searches
    search_results = search_tool.invoke(state["question"])
    raw_content = "\n\n".join(
        f"Source: {r['url']}\n{r['content']}"
        for r in search_results
    )

    response = llm.invoke([
        SystemMessage(content=RESEARCHER_PROMPT),
        HumanMessage(content=f"Question: {state['question']}\n\nSearch results:\n{raw_content}")
    ])

    return {**state, "research": response.content, "revision_count": state.get("revision_count", 0)}

Step 4 — Build the Critic agent

CRITIC_PROMPT = """You are a rigorous fact-checking and critique agent.
Review the research findings and identify:
1. Claims that need stronger sourcing
2. Logical gaps or missing perspectives
3. Potential bias or outdated information
4. Questions the synthesizer should address

Output a structured critique. If the research is solid (score 8+/10),
write "APPROVED" at the top. Otherwise write "REVISE NEEDED"."""

def critic_node(state: AgentState) -> AgentState:
    response = llm.invoke([
        SystemMessage(content=CRITIC_PROMPT),
        HumanMessage(content=f"Original question: {state['question']}\n\nResearch to review:\n{state['research']}")
    ])
    return {**state, "critique": response.content}

Step 5 — Build the Synthesizer agent

SYNTHESIZER_PROMPT = """You are a senior research synthesizer. Using the
provided research and critique, write a clear, well-structured report that:
- Directly answers the question
- Organizes findings into logical sections with headers
- Notes uncertainty where it exists
- Is written for a technically literate audience
- Is 400-600 words

Do not pad. Do not hedge unnecessarily. Be direct."""

def synthesizer_node(state: AgentState) -> AgentState:
    response = llm.invoke([
        SystemMessage(content=SYNTHESIZER_PROMPT),
        HumanMessage(content=(
            f"Question: {state['question']}\n\n"
            f"Research:\n{state['research']}\n\n"
            f"Critic feedback:\n{state['critique']}"
        ))
    ])
    return {**state, "report": response.content}

Step 6 — Wire the graph and add conditional routing

from langgraph.graph import StateGraph, END

def should_revise(state: AgentState) -> str:
    """Route back to researcher if critic flagged issues, up to 2 times."""
    if "REVISE NEEDED" in state["critique"] and state["revision_count"] < 2:
        return "revise"
    return "synthesize"

def increment_revision(state: AgentState) -> AgentState:
    return {**state, "revision_count": state["revision_count"] + 1}

# Build the graph
builder = StateGraph(AgentState)
builder.add_node("researcher", researcher_node)
builder.add_node("critic", critic_node)
builder.add_node("increment_revision", increment_revision)
builder.add_node("synthesizer", synthesizer_node)

# Define flow
builder.set_entry_point("researcher")
builder.add_edge("researcher", "critic")
builder.add_conditional_edges(
    "critic",
    should_revise,
    {"revise": "increment_revision", "synthesize": "synthesizer"}
)
builder.add_edge("increment_revision", "researcher")
builder.add_edge("synthesizer", END)

graph = builder.compile()

# Run it
result = graph.invoke({
    "question": "What are the biggest technical risks in deploying LLMs in healthcare?",
    "research": "",
    "critique": "",
    "report": "",
    "messages": [],
    "revision_count": 0,
})

print(result["report"])

This 120-line system handles a full research → critique → revision loop → synthesis pipeline. The conditional routing means the critic's verdict drives whether the researcher runs again — giving you automated quality control without human intervention.

Adding observability: Wrap the graph invocation with Langfuse tracing by setting LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables and passing a callbacks=[LangfuseCallbackHandler()] to each LLM call. You'll see every agent invocation, token count, and latency in the Langfuse dashboard — essential for tuning costs and debugging quality regressions.

Agent roles and specialization patterns

The most common multi-agent architectures reuse a handful of proven role patterns. Mix and match based on what your task requires.

The Research Triangle

Researcher → Critic → Synthesizer. The pattern shown in the code above. Used for: market research reports, literature reviews, competitive analysis, due diligence. The critic prevents the synthesizer from confidently stating things the research doesn't support.

The Code Pipeline

Planner → Coder → Tester → Reviewer. The Planner breaks a feature request into subtasks. The Coder implements each one. The Tester runs the code in a sandbox (E2B works well here) and reports errors. The Reviewer checks for security issues, code style, and correctness. This pattern dramatically outperforms a single "write me code" agent on tasks longer than ~50 lines.

The Content Factory

Researcher → Outliner → Writer → Editor → SEO Checker. Used for large-scale content production. Each agent has a narrow, auditable scope. The Editor only cares about clarity and tone. The SEO Checker only cares about keyword density and heading structure. Keeping roles narrow makes each agent's output easier to evaluate.

The Analyst Stack

Data Loader → SQL Analyst → Interpreter → Visualizer. The Data Loader connects to databases and pulls relevant tables. The SQL Analyst writes and executes queries. The Interpreter explains what the numbers mean in plain language. The Visualizer describes or generates the right chart type. This pattern handles business intelligence tasks that used to require a human analyst.

Parallelization pattern

Not all agent pipelines are sequential. When subproblems are independent — e.g., researching three different competitors simultaneously — run agents in parallel using asyncio.gather() or LangGraph's parallel branch support. This cuts wall-clock time dramatically at the same cost.

import asyncio

async def run_parallel_research(questions: list[str]) -> list[str]:
    tasks = [graph.ainvoke({"question": q, ...}) for q in questions]
    results = await asyncio.gather(*tasks)
    return [r["report"] for r in results]

Cost breakdown for running multi-agent systems

Multi-agent systems are cost multipliers. A task that costs $0.01 with one agent call may cost $0.04–$0.12 when routed through four agents. Understanding the cost levers is critical before you scale.

What drives cost

Sample cost table — 3-agent research system, 1,000 tasks/month

Agent Model Avg tokens/task Cost/task Monthly (1k tasks)
Researcher Claude Haiku 3.5 ~4,000 in / ~800 out ~$0.003 ~$3
Critic Claude Haiku 3.5 ~2,000 in / ~400 out ~$0.0015 ~$1.50
Synthesizer Claude Sonnet 4.5 ~5,000 in / ~600 out ~$0.018 ~$18
Search API (Tavily) 5 searches/task ~$0.005 ~$5
Observability (Langfuse) $0 (free tier)
Total ~$0.028/task ~$28/mo

Cost optimization strategies

Scale comparison

ScaleTasks/monthEstimated monthly costPrimary driver
Prototype100~$3–$10Model API fees
Small production1,000~$28–$80Model API fees
Medium production10,000~$200–$600Model + search API
Large production100,000~$1,500–$5,000Model (negotiate volume discounts)

Common pitfalls and how to avoid them

1. Agents that argue in circles

The problem: In conversational multi-agent systems (AutoGen-style), agents can ping-pong corrections at each other indefinitely. The critic always finds something to fix; the writer always pushes back. The task never terminates.

The fix: Always implement a hard termination condition — a max turn count, an "APPROVED" keyword check, or a time limit. Never rely on agents to self-terminate. max_rounds=5 in AutoGen is a start; a semantic check in your routing function is better.

2. Context window overflow in long pipelines

The problem: Each agent appends its output to the conversation history. By the time the synthesizer runs, the context contains 20,000 tokens of intermediate work — bloating costs and degrading output quality as the model loses focus on earlier content.

The fix: Pass only what each agent needs. Use the shared state dict to store structured summaries, not raw conversation logs. Implement a "compaction" step between major pipeline stages that summarizes prior work into a dense, structured format.

3. Tool failures that cascade silently

The problem: A search API returns an error. The agent interprets the error message as content and confidently summarizes it. The next agent builds on this hallucinated research. The final output is confident nonsense.

The fix: Wrap every tool call in structured error handling. Return a typed error object (not a string) that the agent can identify as a failure. Teach each agent's prompt how to handle tool failures explicitly: "If a search tool returns an error, note it and try an alternative query before proceeding."

4. Prompt bleed between agents

The problem: When you pass one agent's raw output directly into another agent's prompt, the second agent often picks up the first agent's persona or style. A critic that reads "I researched this thoroughly and found..." starts responding as if it is the researcher.

The fix: Strip metadata and first-person framing before passing between agents. Pass structured data (JSON or markdown sections with clear headers) rather than raw prose. Each agent's system prompt should include a clear self-definition that overrides any persona bleed in the input.

5. No observability until something breaks in production

The problem: Multi-agent systems have 4–20 LLM calls per task. Without tracing, when a task produces bad output you have no idea which agent is the culprit, what input it received, or why it made the choices it made. Debugging is guesswork.

The fix: Add Langfuse or LangSmith from day one, not week six. Instrument every agent call with a run_name that identifies the agent role and task ID. Log the full input and output of every agent turn. Set up alerts for tasks that exceed a cost threshold or latency threshold — those are your early-warning system for runaway loops.

6. Forgetting that agents can be wrong together

The problem: Multi-agent systems can give a false sense of rigor. A critic that agrees with a confident but wrong researcher produces a confidently wrong synthesis. Having multiple agents does not guarantee correctness — it can entrench errors.

The fix: Build in external ground truth checks where it matters. For factual claims: verify against a trusted corpus, not just other agents. For code: actually run it in a sandbox and check the output. For data analysis: have the analyst agent state its assumptions explicitly so humans can audit them.

Build your multi-agent system in 2 minutes
Open the free TechStackopoly workflow planner, load the multi-agent template, customize agent roles and tools, see real-time cost estimates, and export a runnable Python project. No login required.
Open the Agent Planner →

Build your agent workflow in 2 minutes

TechStackopoly's visual Workflow Planner includes a starter multi-agent template with Researcher, Critic, and Synthesizer agents pre-configured. You can:

Other guides that pair well with this one: