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.
- Multi-agent vs. single-agent: when to upgrade
- Key components of a multi-agent system
- Framework comparison: CrewAI, LangGraph, AutoGen, custom
- Step-by-step: build a research + synthesis system
- Agent roles and specialization patterns
- Cost breakdown for running multi-agent systems
- Common pitfalls and how to avoid them
- Build your agent workflow in 2 minutes
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
- The task fits in a single, well-scoped prompt
- You need the lowest possible latency (multi-agent adds round-trips)
- You are cost-sensitive and the task doesn't warrant 4–8 LLM calls
- Debugging needs to stay simple — fewer moving parts means clearer failure modes
When to switch to multi-agent
- Tasks require distinct expertise that would conflict in a single prompt (e.g., code generation + security review)
- Work is parallelizable — multiple agents can run simultaneously and merge results
- You need iterative refinement loops: generate → critique → revise → validate
- The context window would overflow if a single agent held all intermediate state
- Different steps need different tools, models, or cost profiles (use a cheap model for research, a premium model for synthesis)
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.
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.
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.
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.
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.
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.
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
- Number of agents in the chain — each hop is at least one LLM call, often two (reasoning + tool call)
- Model tier per agent — you do not need a premium model for every role; use cheaper models for grunt work
- Context size at each step — agents that receive the full history of previous agents' outputs accumulate tokens fast
- Revision loops — a critic that triggers two researcher re-runs triples the cost of that stage
- Tool call latency and retries — failed tool calls that retry burn tokens on error messages
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
- Tiered models: Use Haiku or Gemini Flash for research and critique; reserve Sonnet or GPT-4 for the final synthesis step where quality matters most.
- Trim context aggressively: Only pass each agent the output it needs — not the full conversation history. A critic does not need the original search URLs, only the researcher's summary.
- Cache tool results: If multiple agents might query the same URL or database row in the same task run, cache tool outputs in the shared state dict.
- Cap revision loops: Always set a hard maximum on how many times a critic can send work back. Two is usually enough; three is a ceiling.
- Prompt compression: Run a cheap model to compress the researcher's output before passing it to the synthesizer. Cutting 2,000 tokens of filler saves money on every task.
Scale comparison
| Scale | Tasks/month | Estimated monthly cost | Primary driver |
|---|---|---|---|
| Prototype | 100 | ~$3–$10 | Model API fees |
| Small production | 1,000 | ~$28–$80 | Model API fees |
| Medium production | 10,000 | ~$200–$600 | Model + search API |
| Large production | 100,000 | ~$1,500–$5,000 | Model (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 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:
- Swap the model at each agent node (mix Haiku and Sonnet to optimize cost)
- Add or remove tools from each agent's toolset
- Toggle the revision loop and set the max revision count
- See the real-time monthly cost update as you make changes
- Export the complete LangGraph Python project, ready to run
Other guides that pair well with this one:
- RAG Pipeline Architecture Guide — add retrieval to your agents
- Document processing pipeline
- LLM observability stack
- Code generation agent pipeline