Intermediate

Applying Multi-Agent Systems to Daily Tasks

Understand and design multi-agent systems with side-by-side framework comparisons and implementation patterns.

Table of Contents


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:

  1. Search for flight options (247 found → select the 4 best)
  2. Search for hotels (89 found)
  3. Create a group and request a vote
  4. 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:

ComponentRole
LLMReasoning, interpretation, decision-making
MemoryRetaining past interactions, preferences, intermediate results, important context
ToolsReal-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:

  1. Understand the request — The agent analyzes the user’s intent.
  2. Planning — The agent develops a sequence of actions to reach the objective.
  3. Execution — The agent uses tools: calls APIs, processes information.
  4. 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

TypeCharacteristics
Universal agentDesigned to handle a wide range of tasks (research, code, data, travel…). Flexible, but heavily dependent on the instructions provided.
Specialized agentDesigned 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 architectureAgent world
MonolithSingle agent
MicroservicesMulti-agent system
APIs between servicesCommunication between agents
Horizontal scalabilityDistributed context management
Service meshAgent 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
ApproachDescriptionExample
By stepsIf the task has clear stages, each stage is assigned to a dedicated agentCollection → Analysis → Output
By rolesMirrors how humans workResearcher, Analyst, Designer, Reviewer
By skillsWhen a role is too broadSummarization, validation, extraction specialist
By data contextAgents assigned to specific domainsSales agent, Engineering agent, Marketing agent
By responsibilitySeparation of generation / evaluation / approvalAdds 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 --> [*]
StateDescription
CreationThe agent is initialized and receives its system prompt (identity, role, objective)
PerceivingThe task arrives. The agent receives the prompt and begins collecting context
ReasoningThe agent analyzes the task and available context, decides on a plan
ActingPlan execution: API calls, code generation, requesting help from other agents
Memory UpdateIf all goes well, the agent updates its memory for future tasks
TerminatedEnd of task (success, failure, or timeout)

2. Communication between agents: exchanging information, delegating work, sharing results reliably and in a structured way.

Framework categories

CategoryDescriptionExamples
Code-driven frameworksOrchestration is done through code. Developers define agents and their interactions programmatically.Microsoft AutoGen, LangGraph, CrewAI
UI-driven frameworksWorkflows 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

ConceptDescription
Client agentThe agent that receives the initial user request and initiates the collaboration
Server agentThe remote agent (in another application/service) that receives the delegation
Agent cardJSON document exposed at a known URL describing the agent’s capabilities
TaskStateful unit of work initiated by an agent, with a unique identifier
MessageNatural language instructions, structured data, or file references
ArtifactTangible 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)

ProtocolAuthorPurposeCommunication
A2AGoogleAgent ↔ agent communicationPeer-to-peer between autonomous agents
MCPAnthropicLLM ↔ tools/resources connectionAgent ↔ 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

PatternControlOwnershipUsage
Agent-as-a-toolCentralized in the initiating agentStays with the callerSpecialized subtasks
HandoffDistributed across agentsMoves with each transferSequential workflows with distinct responsibilities

Execution patterns

PatternDescription
Sequential executionOne agent completes its part, then passes to the next. Linear chain.
Parallel executionThe 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-offDescription
LatencyEach additional layer introduces a step in the chain. Results must travel back up the entire hierarchy.
CostEach agent consumes model calls, memory, and tools. Multiplied across layers, the bill can grow quickly.
Bottleneck riskIf 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:

  1. The orchestrator publishes tasks to the shared ledger (central dashboard)
  2. Specialized agents continuously check the ledger
  3. An agent picks up a task it is capable of completing
  4. 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:

  1. Ingestion — Fetching multiple Google Docs
  2. Supervisor architecture — With specialized agents in parallel
  3. Parallel execution — Pricing, SLA, and Liability agents work simultaneously
  4. Aggregation — The supervisor compiles the analyses
  5. Handoff — Transfer to the recommendation agent
  6. 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

AttributeTypeDescription
rolestrDefines the agent’s function and expertise
goalstrThe individual objective that guides the agent’s decisions
backstorystrProvides context and personality for the agent
llmstr / LLMLanguage model powering the agent (default: gpt-4)
toolsList[BaseTool]Available capabilities for the agent
allow_delegationboolAllow the agent to delegate to other agents
max_iterintMaximum iterations before the agent gives its best response (default: 20)
memoryboolMaintain context between interactions
respect_context_windowboolAutomatic summarization if context exceeds limit (default: True)
reasoningboolAgent thinks and creates a plan before executing (default: False)
verboseboolEnable detailed logging for debugging
# 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

ConceptDescription
StateTypedDict dictionary shared between all nodes — the graph’s working memory
NodePython function receiving the state and returning a state delta
EdgeConnection between nodes, can be conditional
Conditional edgeRouting decision based on the current state
CheckpointingState 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
TypeTechnologyUsage
Short-termContext window (in-memory)Current conversation, recent steps
Long-term semanticVector DB (Pinecone, Chroma, Weaviate)Similarity search in history
Long-term structuredSQL, RedisUser preferences, stable facts
EphemeralState variables in the graphIntermediate 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

ConceptDefinition
AI agentSystem combining an LLM (reasoning), memory (context), and tools (actions) to accomplish multi-step tasks
Tool callingMechanism by which an LLM generates structured calls to external functions (APIs, DB, services)
ReAct patternThought → Action → Observation cycle repeated until the objective is reached
Context windowLimit on the amount of information an agent can hold in memory at any given moment
CompactionProcess of compressing context when the context window is full — risk of detail loss
Agent decompositionDividing a complex task into distinct responsibilities assigned to dedicated agents
Multi-agent frameworkControl layer managing the lifecycle and communication of agents
Agent lifecycleCreation → Perceiving → Reasoning → Acting → (Memory update) → Terminated
A2A protocolOpen standard (Google) for communication between agents without a centralized framework
MCP protocolOpen standard (Anthropic) for connecting agent ↔ tools/resources
Agent cardJSON 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-toolNetwork pattern where an agent calls another like a tool — ownership stays with the caller
HandoffNetwork pattern where task ownership is fully transferred to another agent
Supervisor architectureHierarchical architecture with a central agent orchestrating specialized agents
Tree architectureExtension of the supervisor across multiple levels
Human-in-the-loopHuman intervention point in the workflow for validation or correction
Magnetic architectureArchitecture with orchestrator + shared ledger — coordination via shared state
Custom architectureComposition of multiple architectural patterns in a single system
Structured outputPydantic schema-validated JSON output — reliable communication between agents

Architecture Comparison

ArchitectureControlComplexityScalabilityUse case
Network (peer-to-peer)DecentralizedHigh as agent count increasesFlexibleFree exploration, creative collaboration
Agent-as-a-toolCentralized (calling agent)Low to moderateGoodWell-defined specialized subtasks
HandoffDistributed across agentsModerateGoodSequential workflows with distinct stages
Sequential executionSequentialLowLimitedSimple pipelines
Parallel executionOrchestrator + aggregatorModerateVery goodTasks divisible into independent parts
SupervisorCentralized (supervisor)ModerateGoodControlled delegation, single report
TreeMulti-level hierarchicalHighVery goodComplex systems with expertise levels
Human-in-the-loopHybrid AI + humanVariableLimited by humanSensitive decisions, critical validation
MagneticVia shared stateModerateVery goodAsynchronous tasks, autonomous agents
CustomHybridVery highDepends on designReal production systems

Framework Comparison

FrameworkTypeParadigmStrengthsWeaknessesIdeal for
Microsoft AutoGenCode-drivenTeams / GroupsVery flexible, MCP support, streamingInitial complexityAdvanced Python applications, Microsoft enterprises
CrewAICode-drivenRoles / Crews / TasksIntuitive, YAML config, native RAGLess flexible for complex graphsRole-oriented business workflows
LangGraphCode-drivenState graphsFull control, native human-in-the-loop, checkpointingVerbose, learning curveComplex stateful workflows, robust production
n8nUI-drivenVisual blocksAccessible, quick to prototype, rich integrationsLess flexible for complex logicAutomation workflows, non-technical teams
ZapierUI-drivenTriggers / ActionsEase of use, 5000+ appsLimited for complex AI logicSimple no-code automation
FlowiseUI-drivenVisual LLM chainsVisual LLM apps, embeddingsSmaller communityRapid prototyping of LLM apps
Claude CodeIDE toolAgent teamsIntegrated into development, automatic spawningHigh model usage costAgent-assisted code development

Tools and Frameworks Mentioned

Tool / FrameworkTypeDescription
Claude CodeCode-driven / IDEAnthropic AI IDE with agent teams feature for building applications in parallel
n8nUI-drivenVisual workflow framework with blocks for building multi-agent systems
Microsoft AutoGenCode-drivenMicrosoft framework for orchestrating agents via code (Python)
LangGraphCode-drivenLangChain framework for building agent workflows as state graphs
CrewAICode-drivenPython framework oriented around roles/teams for agent orchestration
ZapierUI-drivenLow-code automation with AI agent capabilities
FlowiseUI-drivenNo-code interface for LLM apps and agent workflows
Claude SonnetLLMAnthropic model used as the agent brain in demos
A2A ProtocolOpen standardGoogle protocol for inter-agent communication without a centralized framework
MCPOpen standardAnthropic Model Context Protocol for connecting agent ↔ tools/resources
Google DocsExternal serviceData 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

Interested in this course?

Contact us to book it or get a custom training plan for your team.