Table of Contents
- Module 1: Understanding Multi-Agent Systems
- Module 2: Designing Multi-Agent Systems
- Appendix A: Code Examples by Framework
- Appendix B: Advanced Implementation Patterns
- Key Concepts Summary
- Architecture Comparison
- Framework Comparison
- Tools and Frameworks Mentioned
Module 1: Understanding Multi-Agent Systems
1.1 — AI agents
What is an AI agent?
We often think of AI as something that answers questions: you ask a question, the AI responds, and that’s it. But some AI systems go much further: they receive an objective, break it down, make decisions, and execute multiple steps to accomplish it. This is where AI agents come into play.
Concrete example: “I want to travel to Rome from June 15 to 22. Find me flights and hotels near the Colosseum. Share the results with my friends and book the trip.” An AI agent will then:
- Search for flight options (247 found → select the 4 best)
- Search for hotels (89 found)
- Create a group and request a vote
- Book the tickets and send confirmation emails
Architecture of an AI agent
An AI agent uses an LLM (Large Language Model) as its brain. The LLM is responsible for reasoning. Two additional components make it significantly more capable:
| Component | Role |
|---|---|
| LLM | Reasoning, interpretation, decision-making |
| Memory | Retaining past interactions, preferences, intermediate results, important context |
| Tools | Real-world actions: API calls, database queries, document creation, sending messages |
The agent processing cycle
flowchart TD
A[User request] --> B[Understand the intent]
B --> C[Planning\nDefine the action sequence]
C --> D[Execution\nTool calls, data collection]
D --> E{Objective reached?}
E -- Yes --> F[Final result]
E -- No --> G[New plan]
G --> D
D --> H[Verification\nResult quality]
H --> E
Detailed steps:
- Understand the request — The agent analyzes the user’s intent.
- Planning — The agent develops a sequence of actions to reach the objective.
- Execution — The agent uses tools: calls APIs, processes information.
- Verification — The agent checks whether the objective is reached. If yes → done. If not → new plan.
Fundamental difference: An LLM generates responses. An AI agent reasons, plans, acts, and completes multi-step tasks.
Tool calling: how an agent uses its tools
Tool calling (also called function calling) is the central mechanism that transforms an LLM into an agent. The LLM receives the list of available tools as JSON schemas, then generates structured calls that the runtime executes.
sequenceDiagram
participant U as User
participant A as Agent (LLM)
participant T as Tool (API / DB)
U->>A: "What is the weather in Paris?"
A->>A: Analysis + decision to use get_weather
A->>T: tool_call: get_weather(city="Paris")
T-->>A: {"temp": "18°C", "condition": "cloudy"}
A->>A: Integrates the result into the context
A-->>U: "It is 18°C in Paris, with a cloudy sky."
Tool schema (standard format):
{
"name": "get_weather",
"description": "Get current weather conditions for a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Name of the city"
}
},
"required": ["city"]
}
}
1.2 — Single-agent vs. multi-agent
Types of agents
| Type | Characteristics |
|---|---|
| Universal agent | Designed to handle a wide range of tasks (research, code, data, travel…). Flexible, but heavily dependent on the instructions provided. |
| Specialized agent | Designed for a specific purpose. Instructions, behavior, and assumptions already defined in the system prompt. More effective and reliable in its domain. |
The context window limit
Regardless of agent type, it operates within a technical constraint called the context window: the amount of information it can hold in memory at any given moment.
As the agent works, this context grows. When it is full, the agent performs compaction: it compresses or summarizes parts of the context to free space. But this comes at a cost — some details may be lost.
flowchart LR
A[Complex task] --> B[Single agent]
B --> C{Context window full?}
C -- No --> D[Continue]
C -- Yes --> E[Compaction\nDetail loss]
E --> F[Risk of errors]
Why multi-agent systems?
Instead of forcing a single agent to handle everything, the task is divided among multiple agents. Each agent manages a smaller, focused responsibility with its own context window.
Analogy with software architecture:
| Software architecture | Agent world |
|---|---|
| Monolith | Single agent |
| Microservices | Multi-agent system |
| APIs between services | Communication between agents |
| Horizontal scalability | Distributed context management |
| Service mesh | Agent orchestration layer |
Important note: In multi-agent systems, agents depend on the outputs and decisions of others. If one agent produces incorrect results, it can affect the entire system. Design and coordination are therefore critical.
Single-agent vs. multi-agent: when to choose?
flowchart TD
START([New task to automate]) --> Q1{Simple,\nbounded task?}
Q1 -- Yes --> SA[Single Agent\nSimple and fast solution]
Q1 -- No --> Q2{Context window\na limiting factor?}
Q2 -- Yes --> MA[Multi-Agent System]
Q2 -- No --> Q3{Multiple expertise\nrequired?}
Q3 -- Yes --> MA
Q3 -- No --> Q4{Parallelism\nneeded?}
Q4 -- Yes --> MA
Q4 -- No --> SA
style SA fill:#27ae60,color:#fff
style MA fill:#8e44ad,color:#fff
1.3 — Agent decomposition
Basic principle
The initial temptation when discovering multi-agent systems is to split everything into many agents. In practice, the most effective starting point is much simpler: start with a single agent. Only move to a multi-agent design when there is a clear reason to do so.
Analogy: Preparing a five-course meal. One person without a recipe can do it, but it requires a lot of context switching and mental effort. Five professional chefs, each responsible for one dish, work more clearly and reliably.
Decomposition approaches
mindmap
root((Agent Decomposition))
By steps
Agent 1 Data collection
Agent 2 Analysis
Agent 3 Output production
By roles
Researcher agent
Analyst agent
Designer agent
Reviewer agent
By skills
Summarization specialist
Validation agent
Data extraction agent
By data context
Sales data agent
Engineering data agent
Marketing data agent
By responsibility
Generation agent
Evaluation agent
Approval or rejection agent
| Approach | Description | Example |
|---|---|---|
| By steps | If the task has clear stages, each stage is assigned to a dedicated agent | Collection → Analysis → Output |
| By roles | Mirrors how humans work | Researcher, Analyst, Designer, Reviewer |
| By skills | When a role is too broad | Summarization, validation, extraction specialist |
| By data context | Agents assigned to specific domains | Sales agent, Engineering agent, Marketing agent |
| By responsibility | Separation of generation / evaluation / approval | Adds control and trust to the system |
There is no single correct method for designing a multi-agent system. The right approach depends on the complexity of the task, the required level of reliability, and the desired level of control.
Module 2: Designing Multi-Agent Systems
2.1 — Multi-agent frameworks
The coordination problem
When moving from a single agent to multiple agents working together, complexity increases drastically. You are no longer just prompting a model — you are coordinating a system. This raises important questions:
- How do you ensure agents know when to start, what to do, how to communicate, and when to stop?
This is where orchestration comes in. The simplest way to orchestrate agents is to use a multi-agent framework — a control layer that manages the system.
What a framework solves
A multi-agent framework solves two major problems:
1. The agent lifecycle
stateDiagram-v2
[*] --> Creation: Initialization
Creation --> Perceiving: Task received
Perceiving --> Reasoning: Context analysis
Reasoning --> Acting: Plan execution
Acting --> MemoryUpdate: Memory update
MemoryUpdate --> Terminated: Task complete
Acting --> Terminated: Timeout / Error
Terminated --> [*]
| State | Description |
|---|---|
| Creation | The agent is initialized and receives its system prompt (identity, role, objective) |
| Perceiving | The task arrives. The agent receives the prompt and begins collecting context |
| Reasoning | The agent analyzes the task and available context, decides on a plan |
| Acting | Plan execution: API calls, code generation, requesting help from other agents |
| Memory Update | If all goes well, the agent updates its memory for future tasks |
| Terminated | End of task (success, failure, or timeout) |
2. Communication between agents: exchanging information, delegating work, sharing results reliably and in a structured way.
Framework categories
| Category | Description | Examples |
|---|---|---|
| Code-driven frameworks | Orchestration is done through code. Developers define agents and their interactions programmatically. | Microsoft AutoGen, LangGraph, CrewAI |
| UI-driven frameworks | Workflows are built visually with blocks and connections (low-code/no-code). | n8n, Zapier, Flowise |
2.2 — Agent-to-agent protocol (A2A)
Context: when not using a framework
In many real-world systems, agents are integrated directly into applications via SDKs. The application itself manages the agent lifecycle. But how do these agents communicate with each other without a centralized framework?
This is where the Agent2Agent (A2A) protocol comes in: an open standard (initiated by Google) that enables transparent communication and collaboration between AI agents without tight coupling via custom integrations.
A2A Architecture
sequenceDiagram
participant U as User
participant CA as Client Agent (App 1)
participant AC as Agent Card URL
participant SA as Server Agent (App 2)
U->>CA: User request
CA->>AC: GET /.well-known/agent.json
AC-->>CA: Agent Card (JSON) - capabilities, supported tasks, contact point
CA->>SA: Message (Task initiation) - HTTP POST
Note over CA,SA: Task = stateful unit of work with unique identifier
SA->>SA: Task processing
SA-->>CA: Artifact (output) - report, data, code, analysis
CA-->>U: Final result
Key A2A protocol concepts
| Concept | Description |
|---|---|
| Client agent | The agent that receives the initial user request and initiates the collaboration |
| Server agent | The remote agent (in another application/service) that receives the delegation |
| Agent card | JSON document exposed at a known URL describing the agent’s capabilities |
| Task | Stateful unit of work initiated by an agent, with a unique identifier |
| Message | Natural language instructions, structured data, or file references |
| Artifact | Tangible output produced by the agent: report, extracted data, generated code, analysis results |
Example agent card (JSON structure):
{
"name": "ResearchAgent",
"description": "Agent specialized in research and information synthesis",
"version": "1.0",
"capabilities": ["web_search", "document_summarization", "fact_extraction"],
"supportedTasks": ["research", "summarize", "extract"],
"endpoint": "https://api.example.com/agents/research",
"authentication": {
"type": "bearer_token"
}
}
A2A vs MCP (Model Context Protocol)
| Protocol | Author | Purpose | Communication |
|---|---|---|---|
| A2A | Agent ↔ agent communication | Peer-to-peer between autonomous agents | |
| MCP | Anthropic | LLM ↔ tools/resources connection | Agent ↔ data sources/tools |
These two protocols are complementary: MCP connects an agent to its tools, A2A connects agents to each other.
2.3 — Network architectures
Overview
A network architecture (also called peer-to-peer) allows any agent to communicate with any other agent. There is no central controller — just peers connected in a graph.
Limitation: As the number of agents increases, the number of possible communication paths grows quasi-exponentially. Flexibility increases, but so does complexity.
Pattern 1: Agent-as-a-tool
Agent 1 receives a task. It realizes it needs help, so it calls Agent 2 the way it would call a tool or an API. Agent 2 performs a specific function and returns the result. Task ownership remains with Agent 1 — it is still responsible for the overall result.
flowchart LR
U[User] --> A1[Agent 1\nOrchestrator]
A1 -- "Tool call" --> A2[Agent 2\nSpecialist]
A2 -- "Returns result" --> A1
A1 -- "Final result" --> U
style A1 fill:#4a90d9,color:#fff
style A2 fill:#7cb8f7,color:#fff
Pattern 2: Handoff
Agent 1 receives a task, but instead of simply asking for help, it fully transfers the task to Agent 2. Ownership moves. Agent 2 is now responsible for completing the task.
flowchart LR
U[User] --> A1[Agent 1]
A1 -- "Handoff\n(ownership transfer)" --> A2[Agent 2]
A2 -- "Handoff" --> A3[Agent 3]
A3 -- "Final result" --> U
style A1 fill:#e67e22,color:#fff
style A2 fill:#e67e22,color:#fff
style A3 fill:#27ae60,color:#fff
Pattern comparison
| Pattern | Control | Ownership | Usage |
|---|---|---|---|
| Agent-as-a-tool | Centralized in the initiating agent | Stays with the caller | Specialized subtasks |
| Handoff | Distributed across agents | Moves with each transfer | Sequential workflows with distinct responsibilities |
Execution patterns
| Pattern | Description |
|---|---|
| Sequential execution | One agent completes its part, then passes to the next. Linear chain. |
| Parallel execution | The task is split among multiple agents simultaneously. Results are aggregated. |
flowchart TD
subgraph Sequential
S1[Agent A] --> S2[Agent B] --> S3[Agent C] --> S4[Result]
end
subgraph Parallel
P1[Orchestrator] --> P2[Agent A]
P1 --> P3[Agent B]
P1 --> P4[Agent C]
P2 --> P5[Aggregator]
P3 --> P5
P4 --> P5
P5 --> P6[Result]
end
2.4 — Demo: Network architectures (Claude Code)
Scenario: Building a weather dashboard with an agent team
The tool used is Claude Code, which has a feature called agent teams.
Prompt used (high-level, intentionally vague):
“Create a weather dashboard. Use a team of 4 agents: Layout Agent, Data Agent, Chart Agent, Widget Agent.”
flowchart TD
O[Orchestrator Agent\nautomatically created] --> LA[Layout Agent\nUI structure]
O --> DA[Data Agent\nWeather API and data]
O --> CA[Chart Agent\nVisualizations]
O --> WA[Widget Agent\nUI components]
LA -- "Design coordination" --> CA
DA -- "Structured data" --> CA
LA -- "Design consistency" --> WA
style O fill:#8e44ad,color:#fff
style LA fill:#3498db,color:#fff
style DA fill:#2ecc71,color:#fff
style CA fill:#e74c3c,color:#fff
style WA fill:#f39c12,color:#fff
Results:
- Duration: ~9 minutes
- Model usage cost: ~$5 USD
- Complete application built in parallel by the agent team
- Imperfect result (some layout and styling issues), but functional
Key lessons:
- Claude Code automatically created an orchestrator agent — even without explicitly requesting one
- Agents coordinated to maintain a consistent design language
- Agents worked in parallel, but communicated to ensure integration
- A more detailed prompt would have produced a significantly better result
2.5 — Hierarchical architectures
Supervisor architecture
The simplest form: a central agent (the supervisor) receives the user’s objective, breaks it down into tasks, and assigns them to specialized agents. These specialized agents do not communicate with each other — they communicate only with the supervisor.
flowchart TD
U[User] --> SV[Supervisor Agent\nCentral decision-maker]
SV --> A1[Specialized agent 1]
SV --> A2[Specialized agent 2]
SV --> A3[Specialized agent 3]
A1 -- result --> SV
A2 -- result --> SV
A3 -- result --> SV
SV --> R[Aggregated final output]
style SV fill:#8e44ad,color:#fff
Supervisor responsibilities:
- Receives the user’s overall objective
- Breaks the task into assignable subtasks
- Delegates each subtask to the appropriate specialized agent
- Collects and aggregates the results
- Assembles a coherent final output
Tree architecture
Extension of the supervisor: multi-level model. Each supervisor orchestrates only the agents directly beneath it.
flowchart TD
T[Top Supervisor] --> MS1[Mid Supervisor 1\nDomain A]
T --> MS2[Mid Supervisor 2\nDomain B]
MS1 --> SA1[Specialist 1A]
MS1 --> SA2[Specialist 1B]
MS2 --> SA3[Specialist 2A]
MS2 --> SA4[Specialist 2B]
style T fill:#8e44ad,color:#fff
style MS1 fill:#9b59b6,color:#fff
style MS2 fill:#9b59b6,color:#fff
Trade-offs of hierarchical architectures
| Trade-off | Description |
|---|---|
| Latency | Each additional layer introduces a step in the chain. Results must travel back up the entire hierarchy. |
| Cost | Each agent consumes model calls, memory, and tools. Multiplied across layers, the bill can grow quickly. |
| Bottleneck risk | If a supervisor slows down or becomes unresponsive, everything beneath it is blocked. |
2.6 — Demo: Hierarchical architectures (n8n)
Scenario: Processing a blog article
Tool used: n8n (visual workflow framework with blocks)
flowchart LR
GD[Google Doc\nBlog article] --> SV[Supervisor Agent\nClaude Sonnet]
SV --> SM[Social Media Agent\nCreate a social media post]
SV --> KT[Key Takeaway Agent\nExtract 3 key points]
SV --> NL[Newsletter Agent\nDraft a summary newsletter]
SM -- result --> SV
KT -- result --> SV
NL -- result --> SV
SV --> GD2[Google Doc\nAggregated output]
style SV fill:#8e44ad,color:#fff
style SM fill:#3498db,color:#fff
style KT fill:#2ecc71,color:#fff
style NL fill:#e67e22,color:#fff
Key points:
- Each specialized agent communicates only with the supervisor — not with each other
- The supervisor aggregates outputs into a coherent final result
- Each agent’s system prompt is simple and direct
- Model used: Claude Sonnet
Example system prompt for a specialized agent (Newsletter Agent):
You are a newsletter writing specialist.
Your task is to produce a concise newsletter blurb (3-5 sentences)
summarizing the most important points from the provided blog content.
Focus on what the reader needs to know. Be engaging and clear.
2.7 — Custom architecture
Reality of production systems
In real-world systems, architectures almost never fit into a single category. Most production systems are custom architectures — compositions of multiple patterns.
flowchart TD
U[User] --> NET[Agent network\nFree exploration]
NET --> SV[Supervisor architecture\nStructured organization]
SV --> PA[Agent A\nSpecialist]
SV --> PB[Agent B\nSpecialist]
SV --> PC[Agent sub-network]
PA --> AGG[Aggregator]
PB --> AGG
PC --> AGG
AGG --> SUM[Summary agent\nFinal handoff]
SUM --> R[Final result]
style SV fill:#8e44ad,color:#fff
style SUM fill:#27ae60,color:#fff
Human-in-the-loop
Sometimes decisions are too sensitive to be fully automated. The human-in-the-loop pattern introduces a human approval point into the workflow.
flowchart LR
A[AI Agents\nProcessing] --> H{Human\nvalidation}
H -- Approved --> C[Continue\nworkflow]
H -- Rejected --> D[Correction\nor abort]
H -- Modified --> A
Benefits: dramatically improves trust, safety, and reliability — particularly in business or decision-making contexts.
Typical human-in-the-loop use cases:
- Approval of contracts or legal documents
- Validation of financial recommendations
- Code review before deployment
- Sensitive HR decisions
Magnetic architecture
An architectural style that works differently from the others: instead of agents passing work directly to each other, two main elements are introduced:
flowchart TD
O[Orchestrator Agent] -- "Publishes tasks" --> L["(Shared Ledger\nTask Board)"]
L -- "Task available" --> A1[Specialized agent 1]
L -- "Task available" --> A2[Specialized agent 2]
L -- "Task available" --> A3[Specialized agent 3]
A1 -- "Task completed\nLedger update" --> L
A2 -- "Task completed\nLedger update" --> L
A3 -- "Task completed\nLedger update" --> L
style O fill:#8e44ad,color:#fff
style L fill:#f39c12,color:#000
How it works:
- The orchestrator publishes tasks to the shared ledger (central dashboard)
- Specialized agents continuously check the ledger
- An agent picks up a task it is capable of completing
- It finishes the task, updates the ledger, then moves to the next available one
Coordination happens via shared state rather than direct communication between agents.
2.8 — Demo: Custom architecture (n8n)
Scenario: Analyzing vendor proposals
Tool used: n8n (more complex workflow than the previous demo)
Objective: Analyze multiple vendor proposals and arrive at a recommendation with human validation.
flowchart TD
GD1[Google Doc 1\nVendor A Proposal] --> AGG_IN[Ingestion and\naggregation]
GD2[Google Doc 2\nVendor B Proposal] --> AGG_IN
GD3[Google Doc N\nVendor N Proposal] --> AGG_IN
AGG_IN --> SV[Supervisor Agent]
SV --> PA[Pricing Agent\nPricing analysis]
SV --> SA[SLA Agent\nSLA analysis]
SV --> LA[Liability Agent\nLiability analysis]
PA -- analysis --> SV
SA -- analysis --> SV
LA -- analysis --> SV
SV --> RA[Recommendation Agent\nHandoff\nFinal decision + justification]
RA --> H{Human-in-the-loop\nApproval}
H -- Approved --> APP[Approved branch\nWorkflow continues]
H -- Rejected --> REJ[Rejected branch]
style SV fill:#8e44ad,color:#fff
style RA fill:#27ae60,color:#fff
style H fill:#e74c3c,color:#fff
Patterns combined in this workflow:
- Ingestion — Fetching multiple Google Docs
- Supervisor architecture — With specialized agents in parallel
- Parallel execution — Pricing, SLA, and Liability agents work simultaneously
- Aggregation — The supervisor compiles the analyses
- Handoff — Transfer to the recommendation agent
- Human-in-the-loop — Human validation before continuing
Structure of each analyzed proposal:
- Pricing
- Service Level Agreements (SLAs)
- Liability clauses
Result: The recommendation agent produces the preferred vendor recommendation with clear justification. The workflow pauses awaiting human approval. If approved → approved branch. If rejected → rejected branch.
Appendix A: Code Examples by Framework
A.1 — Microsoft AutoGen
AutoGen is Microsoft’s multi-agent framework. It enables creating agents capable of using tools, collaborating, and orchestrating via Python code.
Simple agent with tool calling
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
# Define a tool (standard Python function)
async def web_search(query: str) -> str:
# Search for information on the web (mock - use SerpAPI in production)
return f"Results for: {query} — AutoGen is a multi-agent framework by Microsoft."
# Create the model client
model_client = OpenAIChatCompletionClient(
model="gpt-4o",
# api_key="YOUR_API_KEY",
)
# Create the agent with the tool
agent = AssistantAgent(
name="research_assistant",
model_client=model_client,
tools=[web_search],
system_message="You are a research assistant. Use available tools to answer questions.",
)
async def main():
result = await Console(
agent.run_stream(task="What is AutoGen?"),
output_stats=True,
)
return result
asyncio.run(main())
Supervisor pattern with AutoGen (RoundRobinGroupChat)
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
model_client = OpenAIChatCompletionClient(model="gpt-4o")
researcher = AssistantAgent(
name="researcher",
model_client=model_client,
system_message="You are a research agent. Collect and present structured information.",
)
analyst = AssistantAgent(
name="analyst",
model_client=model_client,
system_message="You are an analysis agent. Extract key insights from the provided information.",
)
writer = AssistantAgent(
name="writer",
model_client=model_client,
system_message="You are a writer. Produce a clear final report. End with 'FINAL REPORT COMPLETE'.",
)
termination = TextMentionTermination("FINAL REPORT COMPLETE")
team = RoundRobinGroupChat(
participants=[researcher, analyst, writer],
termination_condition=termination,
)
async def main():
await Console(
team.run_stream(task="Analyze trends in multi-agent systems in 2025.")
)
asyncio.run(main())
Structured output with AutoGen
from typing import Literal
from pydantic import BaseModel
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient
class VendorRecommendation(BaseModel):
recommended_vendor: str
confidence_score: float # 0.0 to 1.0
rationale: str
risks: list[str]
decision: Literal["approve", "reject", "review"]
model_client = OpenAIChatCompletionClient(model="gpt-4o")
recommendation_agent = AssistantAgent(
name="recommendation_agent",
model_client=model_client,
system_message="Analyze the provided data and produce a structured recommendation.",
output_content_type=VendorRecommendation, # Output forced to validated JSON
)
Context window management (BufferedChatCompletionContext)
from autogen_core.model_context import BufferedChatCompletionContext
from autogen_agentchat.agents import AssistantAgent
# Limit context to the last 10 messages
agent_with_buffer = AssistantAgent(
name="agent",
model_client=model_client,
system_message="You are a helpful assistant.",
model_context=BufferedChatCompletionContext(buffer_size=10),
)
A.2 — CrewAI
CrewAI is a Python framework oriented around roles and crews (teams). Each agent has a role, a goal, and a backstory that define its behavior.
Basic crew: Researcher + Analyst
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool
search_tool = SerperDevTool()
researcher = Agent(
role="Senior AI Researcher",
goal="Find the latest developments in multi-agent systems",
backstory="Senior researcher specializing in AI, expert in information synthesis.",
tools=[search_tool],
verbose=True,
)
analyst = Agent(
role="AI Technology Analyst",
goal="Analyze trends and produce an actionable report",
backstory="Technology analyst with 10 years of experience in strategic insights.",
verbose=True,
)
research_task = Task(
description="Research the top 5 multi-agent frameworks of 2025. Include: name, key features, use cases.",
expected_output="Structured list of 5 frameworks with details",
agent=researcher,
)
analysis_task = Task(
description="Produce a comparative report. Recommend the best framework based on: ease of use, scalability, community support.",
expected_output="Comparative report with justified recommendation",
agent=analyst,
context=[research_task],
)
crew = Crew(
agents=[researcher, analyst],
tasks=[research_task, analysis_task],
process=Process.sequential,
verbose=True,
)
result = crew.kickoff()
print(result.raw)
Crew with supervisor (Process.hierarchical)
from crewai import Agent, Task, Crew, Process
manager = Agent(
role="Project Manager",
goal="Coordinate the team to produce a comprehensive vendor proposal analysis",
backstory="Project manager expert in vendor evaluation.",
allow_delegation=True,
verbose=True,
)
pricing_agent = Agent(
role="Pricing Analyst",
goal="Analyze in detail the pricing structure of proposals",
backstory="Expert in pricing analysis and commercial negotiation.",
verbose=True,
)
sla_agent = Agent(
role="SLA Specialist",
goal="Evaluate Service Level Agreements and identify risks",
backstory="SLA specialist with expertise in technology contracts.",
verbose=True,
)
liability_agent = Agent(
role="Legal Risk Analyst",
goal="Analyze liability clauses and legal risks",
backstory="Legal expert specializing in IT contracts and risk management.",
verbose=True,
)
main_task = Task(
description="Analyze proposals from 3 cloud vendors. Evaluate: pricing, SLAs, liability clauses. Produce a recommendation.",
expected_output="Complete analysis report with ranked recommendation",
agent=manager,
)
crew = Crew(
agents=[manager, pricing_agent, sla_agent, liability_agent],
tasks=[main_task],
process=Process.hierarchical,
manager_agent=manager,
verbose=True,
)
result = crew.kickoff(inputs={"proposals": "..."})
Key attributes of a CrewAI agent
| Attribute | Type | Description |
|---|---|---|
role | str | Defines the agent’s function and expertise |
goal | str | The individual objective that guides the agent’s decisions |
backstory | str | Provides context and personality for the agent |
llm | str / LLM | Language model powering the agent (default: gpt-4) |
tools | List[BaseTool] | Available capabilities for the agent |
allow_delegation | bool | Allow the agent to delegate to other agents |
max_iter | int | Maximum iterations before the agent gives its best response (default: 20) |
memory | bool | Maintain context between interactions |
respect_context_window | bool | Automatic summarization if context exceeds limit (default: True) |
reasoning | bool | Agent thinks and creates a plan before executing (default: False) |
verbose | bool | Enable detailed logging for debugging |
YAML configuration (recommended approach by CrewAI)
# config/agents.yaml
researcher:
role: >
{topic} Senior Research Specialist
goal: >
Uncover comprehensive insights about {topic}
backstory: >
You are a seasoned researcher with expertise in {topic}.
You excel at finding relevant information and presenting it clearly.
analyst:
role: >
{topic} Strategic Analyst
goal: >
Create detailed analysis reports based on research findings about {topic}
backstory: >
You are a meticulous analyst who transforms complex data into
actionable insights and clear recommendations.
# crew.py
from crewai import Agent, Crew, Process
from crewai.project import CrewBase, agent, crew, task
from crewai_tools import SerperDevTool
@CrewBase
class ResearchCrew:
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
@agent
def researcher(self) -> Agent:
return Agent(
config=self.agents_config["researcher"],
tools=[SerperDevTool()],
verbose=True,
)
@agent
def analyst(self) -> Agent:
return Agent(
config=self.agents_config["analyst"],
verbose=True,
)
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
verbose=True,
)
A.3 — LangGraph
LangGraph is a LangChain framework for creating agent workflows as directed graphs. Each node is a function, each edge is a conditional transition. It excels for complex patterns with shared state.
Core concepts
| Concept | Description |
|---|---|
| State | TypedDict dictionary shared between all nodes — the graph’s working memory |
| Node | Python function receiving the state and returning a state delta |
| Edge | Connection between nodes, can be conditional |
| Conditional edge | Routing decision based on the current state |
| Checkpointing | State snapshot at each step — enables replay and human-in-the-loop |
flowchart LR
subgraph LangGraph["LangGraph State Machine"]
S["(Shared state)"] --> N1[Node 1\nAgent / Tool]
N1 --> E{Edge\nCondition}
E -- "Condition A" --> N2[Node 2]
E -- "Condition B" --> N3[Node 3]
N2 --> S
N3 --> S
end
Supervisor pattern with LangGraph
from typing import Annotated, Literal, TypedDict
from langchain_core.messages import HumanMessage, AIMessage, BaseMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import create_react_agent
import operator
# --- Shared state ---
class AgentState(TypedDict):
messages: Annotated[list[BaseMessage], operator.add]
next_agent: str
llm = ChatOpenAI(model="gpt-4o")
# --- Specialized agents ---
from langchain_community.tools.tavily_search import TavilySearchResults
search_tool = TavilySearchResults(max_results=3)
research_agent = create_react_agent(
llm, [search_tool],
state_modifier="You are a research agent. Use tools to find information.",
)
analysis_agent = create_react_agent(
llm, [],
state_modifier="You are an analyst. Analyze the provided information and produce insights.",
)
# --- Supervisor ---
MEMBERS = ["researcher", "analyst"]
SUPERVISOR_PROMPT = f"""You are a supervisor managing a team: {MEMBERS}.
Decide who should act next. Respond with the agent name or 'FINISH' if work is done."""
def supervisor_node(state: AgentState) -> AgentState:
messages = state["messages"]
response = llm.invoke([
{"role": "system", "content": SUPERVISOR_PROMPT},
*[{"role": "user" if isinstance(m, HumanMessage) else "assistant", "content": m.content}
for m in messages]
])
return {"messages": [], "next_agent": response.content.strip()}
def run_researcher(state: AgentState) -> AgentState:
result = research_agent.invoke({"messages": state["messages"]})
return {"messages": result["messages"], "next_agent": "supervisor"}
def run_analyst(state: AgentState) -> AgentState:
result = analysis_agent.invoke({"messages": state["messages"]})
return {"messages": result["messages"], "next_agent": "supervisor"}
def route(state: AgentState) -> Literal["researcher", "analyst", "__end__"]:
next_agent = state.get("next_agent", "FINISH")
return END if next_agent == "FINISH" else next_agent
# --- Build the graph ---
graph_builder = StateGraph(AgentState)
graph_builder.add_node("supervisor", supervisor_node)
graph_builder.add_node("researcher", run_researcher)
graph_builder.add_node("analyst", run_analyst)
graph_builder.set_entry_point("supervisor")
graph_builder.add_conditional_edges("supervisor", route)
graph_builder.add_edge("researcher", "supervisor")
graph_builder.add_edge("analyst", "supervisor")
graph = graph_builder.compile()
result = graph.invoke({
"messages": [HumanMessage(content="Analyze trends in multi-agent systems in 2025.")],
"next_agent": "",
})
print(result["messages"][-1].content)
Human-in-the-loop with LangGraph (checkpointing)
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import interrupt
memory = MemorySaver()
def human_approval_node(state: AgentState) -> AgentState:
# interrupt() suspends execution and returns control to the caller
human_response = interrupt({
"recommendation": state["messages"][-1].content,
"question": "Do you approve this recommendation? (yes/no)"
})
if human_response.lower() == "yes":
return {"messages": [AIMessage(content="Recommendation approved.")], "next_agent": "execute"}
return {"messages": [AIMessage(content="Recommendation rejected.")], "next_agent": "FINISH"}
# Compile with checkpointer
graph_with_hitl = graph_builder.compile(checkpointer=memory)
# Launch with a thread_id to allow resumption
config = {"configurable": {"thread_id": "vendor-analysis-001"}}
result = graph_with_hitl.invoke(initial_state, config=config)
# Resume with the human decision
graph_with_hitl.invoke(
{"messages": [HumanMessage(content="yes")]},
config=config,
)
Appendix B: Advanced Implementation Patterns
B.1 — Tool calling and the ReAct pattern
ReAct (Reasoning + Acting)
The ReAct pattern is the fundamental cycle of an autonomous agent: the agent reasons about what it knows, decides on an action, observes the result, reasons again, and so on.
flowchart TD
T[Task] --> R[Thought\nReason about current state]
R --> A[Action\nTool call]
A --> O[Observation\nTool result]
O --> R2{Objective\nreached?}
R2 -- No --> R
R2 -- Yes --> F[Final response]
style R fill:#3498db,color:#fff
style A fill:#e74c3c,color:#fff
style O fill:#2ecc71,color:#fff
Example ReAct trace:
Thought: I need to find the best flight from Paris to Rome for June 2025.
Action: search_flights(origin="CDG", destination="FCO", date="2025-06-15")
Observation: [{"flight": "AF1234", "price": 189}, {"flight": "EZY5678", "price": 142}]
Thought: EasyJet is cheaper. I also need to check hotels.
Action: search_hotels(city="Rome", area="Colosseum")
Observation: [{"hotel": "Hotel Roma", "price_per_night": 120, "rating": 4.2}]
Thought: I have all the information. I will formulate a recommendation.
Final Answer: For your Rome trip June 15-22, I recommend...
Complete tool schema (OpenAI format)
tools = [
{
"type": "function",
"function": {
"name": "search_flights",
"description": "Search for available flights between two cities",
"parameters": {
"type": "object",
"properties": {
"origin": {
"type": "string",
"description": "IATA code of the departure airport (e.g., CDG)"
},
"destination": {
"type": "string",
"description": "IATA code of the arrival airport (e.g., FCO)"
},
"date": {
"type": "string",
"description": "Departure date in YYYY-MM-DD format"
},
"passengers": {
"type": "integer",
"description": "Number of passengers",
"default": 1
}
},
"required": ["origin", "destination", "date"]
}
}
}
]
B.2 — Memory management
Agents have two main types of memory:
flowchart LR
subgraph ST["Short-term Memory"]
CTX[Context Window\nCurrent conversation\nToken limit]
end
subgraph LT["Long-term Memory"]
VDB["(Vector Database\nEmbeddings\nSemantic search)"]
SQL["(SQL / KV Store\nStructured data)"]
end
CTX -- "Summary / Archival" --> VDB
VDB -- "Retrieval RAG" --> CTX
style CTX fill:#3498db,color:#fff
style VDB fill:#8e44ad,color:#fff
| Type | Technology | Usage |
|---|---|---|
| Short-term | Context window (in-memory) | Current conversation, recent steps |
| Long-term semantic | Vector DB (Pinecone, Chroma, Weaviate) | Similarity search in history |
| Long-term structured | SQL, Redis | User preferences, stable facts |
| Ephemeral | State variables in the graph | Intermediate results of a task |
Simple RAG integrated into a CrewAI agent:
from crewai_tools import RagTool
rag_tool = RagTool()
knowledge_agent = Agent(
role="Knowledge Specialist",
goal="Answer questions using the internal knowledge base",
backstory="Expert in enterprise knowledge management.",
tools=[rag_tool],
respect_context_window=True, # Automatic summarization if context is full
verbose=True,
)
B.3 — Structured outputs between agents
Structured outputs allow agents to communicate precise data rather than free text — essential for reliable multi-agent pipelines.
from pydantic import BaseModel, Field
from typing import Literal
class FlightAnalysis(BaseModel):
# Structured output from the flight analysis agent
recommended_flight: str = Field(description="Identifier of the recommended flight")
price_eur: float = Field(description="Price in euros")
duration_minutes: int = Field(description="Duration in minutes")
confidence: float = Field(ge=0.0, le=1.0, description="Confidence score 0-1")
rationale: str = Field(description="Justification for the recommendation")
decision: Literal["book", "wait", "reject"] = Field(description="Final decision")
class VendorAnalysisReport(BaseModel):
# Complete output from the vendor analysis workflow
recommended_vendor: str
pricing_score: float = Field(ge=0, le=10)
sla_score: float = Field(ge=0, le=10)
liability_score: float = Field(ge=0, le=10)
overall_score: float = Field(ge=0, le=10)
key_risks: list[str]
key_strengths: list[str]
recommendation: Literal["approve", "conditional_approve", "reject"]
human_review_required: bool
Benefits of structured outputs:
- Automatic data validation between agents
- Reduced parsing errors
- Implicit documentation of the contract between agents
- Facilitates human-in-the-loop (structured data = faster human review)
Key Concepts Summary
| Concept | Definition |
|---|---|
| AI agent | System combining an LLM (reasoning), memory (context), and tools (actions) to accomplish multi-step tasks |
| Tool calling | Mechanism by which an LLM generates structured calls to external functions (APIs, DB, services) |
| ReAct pattern | Thought → Action → Observation cycle repeated until the objective is reached |
| Context window | Limit on the amount of information an agent can hold in memory at any given moment |
| Compaction | Process of compressing context when the context window is full — risk of detail loss |
| Agent decomposition | Dividing a complex task into distinct responsibilities assigned to dedicated agents |
| Multi-agent framework | Control layer managing the lifecycle and communication of agents |
| Agent lifecycle | Creation → Perceiving → Reasoning → Acting → (Memory update) → Terminated |
| A2A protocol | Open standard (Google) for communication between agents without a centralized framework |
| MCP protocol | Open standard (Anthropic) for connecting agent ↔ tools/resources |
| Agent card | JSON document describing an agent’s capabilities, supported tasks, and contact point |
| Task (A2A) | Stateful unit of work with a unique identifier |
| Artifact (A2A) | Tangible output produced by an agent (report, data, code…) |
| Agent-as-a-tool | Network pattern where an agent calls another like a tool — ownership stays with the caller |
| Handoff | Network pattern where task ownership is fully transferred to another agent |
| Supervisor architecture | Hierarchical architecture with a central agent orchestrating specialized agents |
| Tree architecture | Extension of the supervisor across multiple levels |
| Human-in-the-loop | Human intervention point in the workflow for validation or correction |
| Magnetic architecture | Architecture with orchestrator + shared ledger — coordination via shared state |
| Custom architecture | Composition of multiple architectural patterns in a single system |
| Structured output | Pydantic schema-validated JSON output — reliable communication between agents |
Architecture Comparison
| Architecture | Control | Complexity | Scalability | Use case |
|---|---|---|---|---|
| Network (peer-to-peer) | Decentralized | High as agent count increases | Flexible | Free exploration, creative collaboration |
| Agent-as-a-tool | Centralized (calling agent) | Low to moderate | Good | Well-defined specialized subtasks |
| Handoff | Distributed across agents | Moderate | Good | Sequential workflows with distinct stages |
| Sequential execution | Sequential | Low | Limited | Simple pipelines |
| Parallel execution | Orchestrator + aggregator | Moderate | Very good | Tasks divisible into independent parts |
| Supervisor | Centralized (supervisor) | Moderate | Good | Controlled delegation, single report |
| Tree | Multi-level hierarchical | High | Very good | Complex systems with expertise levels |
| Human-in-the-loop | Hybrid AI + human | Variable | Limited by human | Sensitive decisions, critical validation |
| Magnetic | Via shared state | Moderate | Very good | Asynchronous tasks, autonomous agents |
| Custom | Hybrid | Very high | Depends on design | Real production systems |
Framework Comparison
| Framework | Type | Paradigm | Strengths | Weaknesses | Ideal for |
|---|---|---|---|---|---|
| Microsoft AutoGen | Code-driven | Teams / Groups | Very flexible, MCP support, streaming | Initial complexity | Advanced Python applications, Microsoft enterprises |
| CrewAI | Code-driven | Roles / Crews / Tasks | Intuitive, YAML config, native RAG | Less flexible for complex graphs | Role-oriented business workflows |
| LangGraph | Code-driven | State graphs | Full control, native human-in-the-loop, checkpointing | Verbose, learning curve | Complex stateful workflows, robust production |
| n8n | UI-driven | Visual blocks | Accessible, quick to prototype, rich integrations | Less flexible for complex logic | Automation workflows, non-technical teams |
| Zapier | UI-driven | Triggers / Actions | Ease of use, 5000+ apps | Limited for complex AI logic | Simple no-code automation |
| Flowise | UI-driven | Visual LLM chains | Visual LLM apps, embeddings | Smaller community | Rapid prototyping of LLM apps |
| Claude Code | IDE tool | Agent teams | Integrated into development, automatic spawning | High model usage cost | Agent-assisted code development |
Tools and Frameworks Mentioned
| Tool / Framework | Type | Description |
|---|---|---|
| Claude Code | Code-driven / IDE | Anthropic AI IDE with agent teams feature for building applications in parallel |
| n8n | UI-driven | Visual workflow framework with blocks for building multi-agent systems |
| Microsoft AutoGen | Code-driven | Microsoft framework for orchestrating agents via code (Python) |
| LangGraph | Code-driven | LangChain framework for building agent workflows as state graphs |
| CrewAI | Code-driven | Python framework oriented around roles/teams for agent orchestration |
| Zapier | UI-driven | Low-code automation with AI agent capabilities |
| Flowise | UI-driven | No-code interface for LLM apps and agent workflows |
| Claude Sonnet | LLM | Anthropic model used as the agent brain in demos |
| A2A Protocol | Open standard | Google protocol for inter-agent communication without a centralized framework |
| MCP | Open standard | Anthropic Model Context Protocol for connecting agent ↔ tools/resources |
| Google Docs | External service | Data source and result destination in n8n demos |
Search Terms
applying · multi-agent · systems · daily · tasks · ai · agents · orchestration · artificial · intelligence · generative · agent · architecture · pattern · architectures · framework · a2a · context · supervisor · tool · autogen · calling · comparison · concepts