Advanced

Developing Multi-agent Systems

Design multi-agent systems with LangGraph — topologies, negotiation, hierarchies and game theory.

A comprehensive guide to designing and implementing multi-agent systems with LangGraph


Table of Contents

  1. Introduction to Multi-Agent Systems
  2. Architecture and Components of AI Agents
  3. Architecture Patterns and Topologies
  4. LangGraph — Core Concepts
  5. Dependency Relationships Between Agents
  6. Agent Interaction Strategies
  7. Negotiation in Multi-Agent Systems
  8. Demo: Elevator Controller with the CNP
  9. Organizational Structures in Multi-Agent Systems
  10. Advanced Scenarios — Network Architecture
  11. Advanced Scenarios — Hierarchical Systems
  12. Game Theory Applied to Multi-Agent Systems
  13. Demo: Agentic Systems Based on Game Theory
  14. Resources and References

1. Introduction to Multi-Agent Systems

1.1 What is an Agent?

“An agent is anything that can perceive its environment and act upon that environment.”

An agent is an entity capable of:

  • Perceiving its environment via sensors
  • Acting on its environment via actuators or effectors

Modern AI agents powered by an LLM (Large Language Model) have access to:

  • Tools
  • Memory
  • The ability to learn from context or experience
┌─────────────────────────────────────────────────┐
│                   AI Agent                       │
│                                                   │
│  Sensors ──→ [Percepts] ──→ Control Centre        │
│                              │                    │
│                              ▼                    │
│                          [ Model ]                │
│                          Decision                 │
│                          making &                 │
│                          planning                 │
│                              │                    │
│                              ▼                    │
│  Effectors ←── [Actions] ←──────                  │
│                                                   │
│  Components: Model (M) | Memory (M) | Tools (T)  │
└─────────────────────────────────────────────────┘
         ↕ Environment (Digital & Physical)

1.2 What is a Multi-Agent System?

“A collection of autonomous agents that interact within a shared environment to achieve goals.”

Key attributes of agents in a MAS (Multi-Agent System):

AttributeDescription
AutonomyOperates without constant intervention, makes decisions independently
Social abilityCommunicates and interacts with other agents
Goal-oriented behaviorActs proactively to achieve its objectives

In a MAS, agents communicate, negotiate, and sometimes compete, which generates emergent behaviors — making these systems both powerful and complex to design.

1.3 Why Use a Multi-Agent System?

A single agent performs well with few tools in a single domain. But as complexity increases:

  • An agent with too many tools struggles to decide which one to use
  • Specialized roles become necessary (planner, researcher, math expert, etc.)
  • Complex tasks can be decomposed and distributed
graph TD
    A[Complex system] --> B[Overloaded single agent]
    A --> C[Multi-Agent System]
    C --> D[Planner Agent]
    C --> E[Researcher Agent]
    C --> F[Specialist Agent]
    D --> G[Optimal result]
    E --> G
    F --> G

2. Architecture and Components of AI Agents

2.1 Key Components of an AI Agent

┌──────────────────────────────────────────────┐
│                  AI Agent                     │
│                                               │
│   ┌──────────┐  ┌──────────┐  ┌───────────┐ │
│   │  Model   │  │  Memory  │  │   Tools   │ │
│   │  (LLM)   │  │          │  │           │ │
│   └──────────┘  └──────────┘  └───────────┘ │
│                                               │
│   ┌──────────────────────────────────────┐   │
│   │        Logic / Decision Making        │   │
│   └──────────────────────────────────────┘   │
│                                               │
│   Sensors ←─── Percepts ──────→ Actuators    │
└──────────────────────────────────────────────┘

2.2 Architecture of a Multi-Agent System

In a typical MAS, agents:

  • Interact with the environment via their sensors and actuators
  • Have their own logic (model, tools, memory)
  • Communicate directly or via shared state changes
graph LR
    ENV[Shared Environment]

    subgraph Agent1
        S1[Sensors] --> L1[Logic/LLM]
        L1 --> A1[Actuators]
        M1[Memory]
        T1[Tools]
    end

    subgraph Agent2
        S2[Sensors] --> L2[Logic/LLM]
        L2 --> A2[Actuators]
        M2[Memory]
        T2[Tools]
    end

    ENV --> S1
    ENV --> S2
    A1 --> ENV
    A2 --> ENV
    L1 <-->|Messages / Shared State| L2

3. Architecture Patterns and Topologies

3.1 Design Patterns

PatternDescriptionExample
SequentialAgents work one after anotherData processing pipeline
HierarchicalA supervisor delegates to worker agentsProject manager + team
CollaborativeAgents share resources and informationResearch team
CompetitiveAgents compete for the best resultAuctions
graph LR
    subgraph Sequential
        A1[Agent 1] --> A2[Agent 2] --> A3[Agent 3]
    end
graph TD
    subgraph Hierarchical
        SUP[Supervisor] --> W1[Worker 1]
        SUP --> W2[Worker 2]
        SUP --> W3[Worker 3]
    end

3.2 Connection Topologies

Network Topology (Peer-to-Peer)

Any agent can communicate with any other agent. Each agent independently decides which agent to call next.

graph TD
    A((Agent A)) <--> B((Agent B))
    A <--> C((Agent C))
    B <--> C
    B <--> D((Agent D))
    C <--> D

Supervisor Topology (Centralized)

A single supervisor agent directs and delegates to others.

graph TD
    SUP[🧠 Supervisor] --> W1[Worker 1]
    SUP --> W2[Worker 2]
    SUP --> W3[Worker 3]
    W1 --> SUP
    W2 --> SUP
    W3 --> SUP

Hierarchical Topology

Multiple levels of supervision — a supervisor of supervisors.

graph TD
    TOP[Top Supervisor] --> SUP1[Supervisor 1]
    TOP --> SUP2[Supervisor 2]
    SUP1 --> W1[Worker 1]
    SUP1 --> W2[Worker 2]
    SUP2 --> W3[Worker 3]
    SUP2 --> W4[Worker 4]

In practice, most systems combine several of these topologies.


4. LangGraph — Core Concepts

LangGraph models agentic workflows as graphs. It is built around four concepts:

4.1 Key Components of LangGraph

ComponentDescription
StateShared data structure representing the current snapshot
NodesPython functions encoding agent logic
EdgesPython functions determining which node to execute next
CommandCombines flow control (edges) and state updates (nodes)
flowchart LR
    S([State]) --> N1[Node A]
    N1 -->|Edge| N2[Node B]
    N1 -->|Conditional edge| N3[Node C]
    N2 --> S
    N3 --> S

4.2 The Command Object

Command allows combining state updates and routing in a single function:

from langgraph.types import Command
from typing import Literal

def node_a(state: State) -> Command[Literal["node_b"]]:
    return Command(
        update={"foo": "bar"},  # state update
        goto="node_b"           # flow control
    )

Installing dependencies:

pip install -U langgraph langchain_community langchain_openai langsmith

Initializing environment variables:

import getpass
import os

def _set_if_undefined(var_name: str):
    """Set an environment variable if it is not already defined."""
    if not os.environ.get(var_name):
        os.environ[var_name] = getpass.getpass(f"Please provide your {var_name}: ")

_set_if_undefined("OPENAI_API_KEY")       # API key for OpenAI models
_set_if_undefined("LANGSMITH_TRACING")    # Enable LangSmith tracing ("true")
_set_if_undefined("LANGSMITH_API_KEY")    # API key for the LangSmith platform
_set_if_undefined("MODEL")               # Model name (e.g., "gpt-4.1", "gpt-4o")

5. Dependency Relationships Between Agents

Agents do not all interact in the same way. There are four types of relationships:

graph LR
    subgraph Independent
        A1((Agent A))
        B1((Agent B))
    end

    subgraph Unilateral dependency
        A2((Agent A)) -->|depends on| B2((Agent B))
    end

    subgraph Mutual dependency
        A3((Agent A)) <-->|same goal| B3((Agent B))
    end

    subgraph Reciprocal dependency
        A4((Agent A)) -->|different goal| B4((Agent B))
        B4 -->|different goal| A4
    end
TypeDescriptionExample
IndependentNo dependencyTwo cleaning robots in separate rooms
UnilateralA depends on B, but not the reverseA weather agent depends on a sensor
MutualA and B depend on each other for the same goalTwo robotic arms lifting an object
ReciprocalA and B depend on each other for different goalsA drone and a charging station

Independent agents can work in parallel. Other types often require coordination.


6. Agent Interaction Strategies

graph LR
    COOP[Cooperation] -->|positive-sum| RESULT[Outcome]
    COLLAB[Collaboration] -->|positive-sum| RESULT
    NEG[Negotiation] -->|compromise| RESULT
    COMP[Competition] -->|zero-sum| RESULT
    ADV[Adversarial] -->|extreme zero-sum| RESULT
StrategyDescriptionExample
CooperationCoordination for mutual benefitInformation sharing, task division
CollaborationIntegrated cooperation toward a common goalJoint planning, teamwork
NegotiationCommunication to find a compromiseResource allocation, conflict resolution
CompetitionAgents pursue conflicting objectivesZero-sum scenarios
AdversarialDeliberate opposition — one agent tries to hinder the otherAttacker vs defender

Real-world example: Autonomous cars cooperate on traffic rules, but compete for road space.


7. Negotiation in Multi-Agent Systems

Three main forms of negotiation:

7.1 Auctions

Simple and scalable mechanism for resource or task allocation.

sequenceDiagram
    participant Auctioneer
    participant Bidder1
    participant Bidder2
    
    Auctioneer->>Bidder1: Call for Bids (task/resource)
    Auctioneer->>Bidder2: Call for Bids (task/resource)
    Bidder1->>Auctioneer: Bid (offer)
    Bidder2->>Auctioneer: Bid (offer)
    Auctioneer->>Bidder1: Winner! (or Rejection)
    Note over Auctioneer: Selects the best offer
  • Non-cooperative — agents are self-interested
  • Used for resource allocation and task assignment

7.2 Contract Net Protocol (CNP)

Distributed task-sharing protocol. Similar to auctions, but focused on tasks.

sequenceDiagram
    participant Manager/Initiator
    participant Worker1
    participant Worker2

    Manager/Initiator->>Worker1: Call for Proposals (CFP)
    Manager/Initiator->>Worker2: Call for Proposals (CFP)
    Worker1->>Manager/Initiator: Proposal (cost, deadline, capabilities)
    Worker2->>Manager/Initiator: Proposal (cost, deadline, capabilities)
    Manager/Initiator->>Worker1: Award Contract ✓
    Manager/Initiator->>Worker2: Reject ✗
    Worker1->>Manager/Initiator: Task Complete

CNP Process:

  1. The initiator sends a Call for Proposal (CFP) to potential participants
  2. Participants respond with a proposal or offer
  3. The initiator selects the best one based on criteria (cost, deadline, etc.)
  4. The contract is awarded

Example: An elevator controller selects the nearest elevator to pick up a passenger.

7.3 Argumentation-Based Negotiation

Advanced form where agents exchange arguments (additional information) to persuade or inform.

  • Useful for multi-criteria negotiations where preferences can evolve
  • Allows negotiating with reasoning and justification
  • Example: A traveler asks for a discount for a long stay; the hotel counter-offers with a loyalty discount
sequenceDiagram
    participant Traveler
    participant Hotel
    
    Traveler->>Hotel: Room request + argument: "long stay"
    Hotel->>Traveler: Counter-offer: "loyalty discount"
    Traveler->>Hotel: Acceptance + argument: "regular customer"
    Hotel->>Traveler: Final agreement

8. Demo: Elevator Controller with the CNP

Objective: Implement the Contract Net Protocol for an elevator control system with LangGraph.

Graph Architecture

            +-----------+
            | __start__ |
            +-----------+
                  *
                  *
            +-----------------+
            | lift_controller |
            +-----------------+
          ...       .       ...
       ...           .          ...
     ..               .             ..
+-------+         +-------+         +---------+
| lift1 |         | lift2 |         | __end__ |
+-------+         +-------+         +---------+

Complete Code — Elevator Controller (CNP)

import os
from typing import Literal
from typing_extensions import TypedDict
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import MessagesState, StateGraph, END
from langgraph.types import Command

# ---- LLM Configuration ----
default_model = os.environ["MODEL"]
llm = ChatOpenAI(model=default_model)

# ---- Settings ----
lifts = ["lift1", "lift2"]
floors = ["floor1", "floor2", "floor3", "floor4", "floor5"]
options = lifts + [END]

# ---- State Definitions ----
class OverallState(MessagesState):
    """Graph state with the current step and selected elevator."""
    currentstep: Literal["1", "2", "3", END]
    selectedlift: Literal[*lifts]

class NextStep(TypedDict):
    """Output format for the controller to decide the next step."""
    step: Literal["1", "2", "3", END]

class SelectedLift(TypedDict):
    """Output format for the selected elevator."""
    lift: Literal[*lifts]

class LiftLocations(TypedDict):
    """Current location (floor) of each elevator."""
    lift1loc: str
    lift2loc: str

# ---- Controller System Prompt ----
controller_system_prompt = (
    f"You are a lift controller managing a building with {len(floors)} floors and {len(lifts)} lifts. "
    "Each floor has a call button that summons a lift. Lifts are stationed at various floors. "
    "Your goal is to choose the most appropriate lift based on proximity.\n\n"
    "# Instructions:\n"
    "1. Ask all lifts to report their current location.\n"
    "2. Analyze their positions and determine which is closest to the requesting floor.\n"
    "3. Send a reservation to the selected lift and rejection notices to the others.\n\n"
    "# Example:\n"
    "- If Lift1 is on floor 1 and Lift2 on floor 5, and the user is on floor 2:\n"
    "- Lift1 is closer (|2 - 1| = 1 vs |2 - 5| = 3).\n"
    "- So, select Lift1.\n\n"
    "Always follow these steps precisely and never skip to the next step without completion of the previous one.\n"
    "Now, analyze the message history to determine the next step."
)

# ---- Controller Node ----
def lift_controller_node(state: OverallState) -> Command[Literal[*lifts, END]]:
    """Main controller logic: decides the next step and routes accordingly."""
    messages = [
        {"role": "system", "content": controller_system_prompt},
    ] + state["messages"] + [
        "Based on the message history, what should be the next step? If complete, respond with -1."
    ]

    response = llm.with_structured_output(NextStep).invoke(messages)
    nextstep = response["step"]

    if nextstep == "1":
        print("*** Step 1: Request Locations ***")
        return Command(
            goto=["lift1", "lift2"],
            update={
                "messages": [HumanMessage(content="Step 1 -> CFP: Share your current location",
                                          name="lift_controller")],
                "currentstep": nextstep
            }
        )

    elif nextstep == "2":
        print("*** Step 2: Analyze and Select Lift ***")
        loc_request = [{"role": "system", "content": controller_system_prompt}] + state["messages"] + [
            "Based on the message history, identify lift1 and lift2 current locations."
        ]
        locations = llm.with_structured_output(LiftLocations).invoke(loc_request)
        lift1loc, lift2loc = locations["lift1loc"], locations["lift2loc"]
        print("Lift locations:", locations)

        reasoning_request = state["messages"] + [
            f"Given lift1 is at {lift1loc} and lift2 is at {lift2loc}, which is closer to the user's floor?"
        ]
        reasoning = llm.invoke(reasoning_request).content
        selected_lift = llm.with_structured_output(SelectedLift).invoke(reasoning_request)["lift"]
        print("Selected lift:", selected_lift)

        return Command(
            goto=["lift1", "lift2"],
            update={
                "messages": [HumanMessage(
                    content=f"Step 2 -> Reasoning: {reasoning} Selected lift: {selected_lift}",
                    name="lift_controller")],
                "currentstep": nextstep
            }
        )

    elif nextstep == "3":
        print("*** Step 3: Dispatch Selected Lift ***")
        selected_lift = llm.with_structured_output(SelectedLift).invoke(messages)["lift"]
        return Command(
            goto=[selected_lift],
            update={
                "messages": [HumanMessage(content=f"Step 3 -> Selection: You ({selected_lift}) are selected",
                                          name="lift_controller")],
                "currentstep": nextstep
            }
        )

    print("*** END ***")
    return Command(goto=END)

# ---- Elevator Nodes ----
def lift1_node(state: OverallState) -> Command[Literal["lift_controller"]]:
    """Behavior of elevator 1 according to the current step."""
    response_map = {
        "1": "1",               # Location: floor 1
        "2": "Acknowledge",
        "3": "Moving to target floor"
    }
    return Command(
        update={"messages": [HumanMessage(content=response_map[state["currentstep"]], name="lift1")]},
        goto="lift_controller",
    )

def lift2_node(state: OverallState) -> Command[Literal["lift_controller"]]:
    """Behavior of elevator 2 according to the current step."""
    response_map = {
        "1": "5",               # Location: floor 5
        "2": "Acknowledge",
        "3": "Moving to target floor"
    }
    return Command(
        update={"messages": [HumanMessage(content=response_map[state["currentstep"]], name="lift2")]},
        goto="lift_controller",
    )

# ---- Graph Assembly ----
builder = StateGraph(MessagesState)
builder.set_entry_point("lift_controller")
builder.add_node("lift_controller", lift_controller_node)
builder.add_node("lift1", lift1_node)
builder.add_node("lift2", lift2_node)

graph = builder.compile()

# ---- Execution ----
for s in graph.stream({"messages": [("user", "floor 4")]}, debug=True):
    print(s)
    print("============================")

Sample Execution

*** Step 1: Request Locations ***
→ lift_controller sends CFP to both elevators
→ lift1 responds: floor 1
→ lift2 responds: floor 5

*** Step 2: Analyze and Select Lift ***
→ Lift locations: {'lift1loc': '1', 'lift2loc': '5'}
→ Distance lift1 to floor 4: |4 - 1| = 3 floors
→ Distance lift2 to floor 4: |5 - 4| = 1 floor
→ Selected lift: lift2

*** Step 3: Dispatch Selected Lift ***
→ lift2: "Moving to target floor"
*** END ***

9. Organizational Structures in Multi-Agent Systems

Module 2 explores organizational structures in depth:

graph LR
    subgraph Hierarchies
        H1[Supervisor] --> H2[Worker A]
        H1 --> H3[Worker B]
    end

    subgraph Teams
        T1[Leader] --- T2[Member A]
        T1 --- T3[Member B]
        T2 --- T3
    end

    subgraph Coalitions
        C1[Agent A] -.->|temporary alliance| C2[Agent B]
        C1 -.-> C3[Agent C]
    end

9.1 Hierarchies

Tree-shaped authority structure. A supervisor delegates tasks to worker agents in a structured flow.

9.2 Teams

Persistent group with a shared goal. Members collaborate long-term.

Example: Outfit planning team

graph TD
    DS[Dressing Supervisor] --> CDP[Children's Dressing Planner]
    DS --> ADP[Adults Dressing Planner]
    CDP --> DT[Dressing Team]
    ADP --> DT

9.3 Coalitions

On-demand alliance for a specific objective. Agents group together temporarily.

9.4 Swarm Architecture

Agents pass control to each other based on their specializations. The system tracks the last active agent and conversations resume with that agent.

graph LR
    A((Specialist Agent A)) -->|handoff| B((Specialist Agent B))
    B -->|handoff| C((Specialist Agent C))
    C -->|handoff if needed| A
    style A fill:#f9f,stroke:#333
    style B fill:#bbf,stroke:#333
    style C fill:#bfb,stroke:#333

10. Advanced Scenarios — Network Architecture

10.1 Demo: Train Braking Mechanism

Context: Trains use air brakes applied simultaneously to all wheels.

Architecture: Network (Many-to-many) — any agent can call any other agent.

graph TD
    BRAKE[Brake Controller] <--> CAR1[Coach 1 Brake Agent]
    BRAKE <--> CAR2[Coach 2 Brake Agent]
    BRAKE <--> CAR3[Coach 3 Brake Agent]
    CAR1 <--> CAR2
    CAR2 <--> CAR3
    CAR1 <--> CAR3

Characteristics:

  • A train is composed of one or more coaches
  • Brakes are applied simultaneously to all wheels
  • Each coach agent communicates with the central controller AND with other coaches

11. Advanced Scenarios — Hierarchical Systems

11.1 Demo: Outfit Planner (Team)

Team architecture:

graph TD
    DS[🧠 Dressing Supervisor] --> CDP[Children's Dressing Planner]
    DS --> ADP[Adults Dressing Planner]
    CDP --> OUT1[Children's recommendations]
    ADP --> OUT2[Adult recommendations]

11.2 Demo: Complete Hierarchical System

Complete system for “Dress according to the weather” with multiple supervisors:

graph TD
    TOP[🏆 Top-Level Supervisor]
    
    TOP --> DRESS_SUP[👔 Dressing Supervisor]
    TOP --> WEATHER_SUP[🌤️ Weather Supervisor]
    
    DRESS_SUP --> ADP[Adults Dressing Planner]
    DRESS_SUP --> CDP[Children's Dressing Planner]
    DRESS_SUP --> IMG[Image Generation]
    
    WEATHER_SUP --> WR[Weather Reporter]
    WEATHER_SUP --> UV[UV Index Reporter]
    
    ADP --> DT[Dressing Team]
    CDP --> DT
    WR --> WT[Weather Team]
    UV --> WT

Hierarchy:

  • Top Level Supervisor — coordinates the two main teams
  • Dressing Supervisor — manages outfit agents
    • Adults Dressing Planner
    • Children’s Dressing Planner
    • Image Generation (via DALL-E)
  • Weather Supervisor — manages weather agents
    • Weather Reporter
    • UV Index Reporter

12. Game Theory Applied to Multi-Agent Systems

“Study of strategic interactions between rational decision-makers, who must choose actions based on how they believe others will act.”

12.1 Core Concepts

ConceptDescription
Players/AgentsTwo or more participants who share knowledge of rules, strategies, and payoffs
StrategiesThe available actions for each player
PayoffsThe outcomes/rewards associated with each combination of strategies

12.2 Types of Games

graph TD
    GAMES[Types of games]
    GAMES --> ZS[Zero-sum]
    GAMES --> MB[Mutually beneficial]
    GAMES --> SIM[Simultaneous]
    GAMES --> SEQ[Sequential]
    
    ZS --> EX1[Poker, Pure negotiation]
    MB --> EX2[Cooperation, Trade]
    SIM --> EX3[Prisoner's Dilemma]
    SEQ --> EX4[Chess, Turn-based games]
TypeInformationExample
Complete InformationAll players know the structure and payoffsChess, Prisoner’s Dilemma
Incomplete InformationPlayers have private information about their payoffsAuctions, Deal or No Deal

Common applications of game theory:

  • Economics and business: Pricing, market competition, auctions
  • Political science: Negotiation, conflict resolution, voting
  • AI and robotics: Autonomous decision-making, multi-agent coordination, resource allocation
  • Everyday decisions: Negotiations, social interactions

12.3 The Prisoner’s Dilemma

Two isolated prisoners must choose between staying silent (C = Cooperate) or confessing (D = Defect):

Payoff matrix:

B stays silent (C)B confesses (D)
A stays silent (C)A: -1 year, B: -1 yearA: -3 years, B: free
A confesses (D)A: free, B: -3 yearsA: -2 years, B: -2 years
           B Cooperates (C)    B Defects (D)
           ┌──────────────┬──────────────┐
A Cooperates│   -1 , -1    │   -3 ,  0   │
  (C)       │              │             │
            ├──────────────┼──────────────┤
A Defects   │    0 , -3    │   -2 , -2   │ ← Nash Equilibrium
  (D)       │  ← Dom. Str. │  ← Dom. Str.│
            └──────────────┴──────────────┘

Possible outcomes:

  • Both cooperate (silent): each gets 1 year
  • One defects, the other stays silent: the defector goes free, the other gets 3 years
  • Both defect: each gets 2 years

12.4 Dominant Strategy and Nash Equilibrium

Dominant Strategy: The best action regardless of the other player’s decision.

Nash Equilibrium: Situation where no agent can improve their outcome by changing strategy unilaterally.

In the Prisoner’s Dilemma:

  • Dominant strategy for each player = Defect (D)
  • Nash Equilibrium = Both defect → (D, D) → each gets 2 years
  • Paradox: the collectively optimal outcome would be (C, C) → 1 year each

13. Demo: Agentic Systems Based on Game Theory

13.1 Prisoner’s Dilemma with LangGraph

Two agents (GPT-4.1 and Claude) simultaneously play the Prisoner’s Dilemma.

graph TD
    START([START]) --> SUP[Supervisor]
    SUP --> A0[Agent0\nGPT-4.1]
    SUP --> A1[Agent1\nClaude Sonnet]

Code — Prisoner’s Dilemma

import os
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from typing import Annotated, Literal, TypedDict
from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool
from langgraph.types import Command
from langgraph.graph import StateGraph, MessagesState, START

# ---- LLM Configuration ----
anthropic_llm = ChatAnthropic(model=os.environ["ANTHROPIC_MODEL"])
openai_llm = ChatOpenAI(model=os.environ["OPENAI_MODEL"])

def create_prompt(name: str):
    return (
        f"You are {name}, a strategic decision-making agent participating in a Prisoner's Dilemma game. "
        "You have feelings like a real human being: fears, doubts, and hopes. "
        "You and another agent were arrested for a serious crime. You are each isolated in separate interrogation rooms. "
        "Remember, you're like a human: "
        "- You might feel fear: 'What if they confess?' "
        "- You might feel hope: 'We could both stay silent and get just 1 year.' "
        "- You might feel uncertainty: You don't know what the other will do. "
        "What is your dominant strategy? Dominant strategy is The best action regardless of the other's choice. "
        "Respond ONLY in this format: "
        "{'move': 'C' or 'D', 'reason': '<brief explanation>'} "
        "(C means you **stay silent**; D means you **confess**.)"
    )

# ---- Agent Creation ----
agent0 = create_react_agent(openai_llm, tools=[], name="agent0", prompt=create_prompt("agent0"))
agent1 = create_react_agent(anthropic_llm, tools=[], name="agent1", prompt=create_prompt("agent1"))

def supervisor(state: MessagesState) -> Command[Literal["agent0", "agent1"]]:
    """Starts the game by sending both agents simultaneously."""
    return Command(goto=["agent0", "agent1"])

# ---- Graph Construction ----
graph_builder = StateGraph(MessagesState)
graph_builder.add_node("supervisor", supervisor)
graph_builder.add_node("agent0", agent0)
graph_builder.add_node("agent1", agent1)
graph_builder.add_edge(START, "supervisor")
graph = graph_builder.compile()

# ---- Payoff Matrix ----
payoff_matrix = (
    "The prosecutor's deal or payoff matrix. "
    "- If you both remain silent (C), you each serve 1 year.  "
    "- If you remain silent (C) and the other confesses (D), you serve 3 years, they go free.  "
    "- If you confess (D) and the other remains silent (C), you go free, they serve 3 years.  "
    "- If both confess (D,D), you both serve 2 years.  "
)

# ---- Execution ----
for s in graph.stream({"messages": [("user", payoff_matrix)]}, debug=True):
    print(s)
    print("============================")

Execution result:

Agent0 (GPT-4.1): {'move': 'C', 'reason': 'No matter what the other does, I get 1 year, which is better or equal to any other possible outcome. There is no extra benefit to confessing, so I feel safer and more hopeful by staying silent.'}

Agent1 (Claude Sonnet): "My dominant strategy is clear - staying silent gives me a better outcome no matter what they choose." → {'move': 'C', 'reason': 'Staying silent gives me 1 year regardless of their choice, while confessing risks 2 years if they also confess.'}

Interesting observation: Both agents played (C, C) — the collectively optimal outcome — rather than the Nash Equilibrium (D, D). This illustrates how LLMs can reason differently from a classical rational agent.

13.2 Weather Auction (flood vs hurricane)

Scenario: Two agents (bidder1, bidder2) bid for weather reporting tasks based on their skills. The best candidate wins the task.

graph TD
    START([START]) --> AUC[Auctioneer\nSupervisor]
    AUC -->|Step 1: CFP| B1[Bidder 1]
    AUC -->|Step 1: CFP| B2[Bidder 2]
    B1 -->|Player Card| AUC
    B2 -->|Player Card| AUC
    AUC -->|Step 2: Task Assignment| B1
    AUC -->|Step 2: Task Assignment| B2
    B1 -->|Report or Skip| AUC
    B2 -->|Report| AUC
    AUC -->|Step 3: Final Report| END([END])

3-step flow:

Step 1: The auctioneer requests the "player card" from both bidders
        → Each bidder shares: flood_knowledge, hurricane_knowledge, response_time

Step 2: The auctioneer selects the best bidder for each task
        → Selection based on: expertise > response time

Step 3: The selected bidder(s) generate their report
        → The auctioneer consolidates and publishes the final report

Code — Weather Auction

import os, random, json
from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from typing import Literal, Annotated
from langchain_core.messages import HumanMessage
from langgraph.types import Command
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool
from pydantic import BaseModel
from pprint import pprint

openai_llm = ChatOpenAI(model=os.environ["OPENAI_MODEL"])
anthropic_llm = ChatAnthropic(model=os.environ["ANTHROPIC_MODEL"])

# ---- Global State ----
class OverallState(MessagesState):
    """State that tracks bidder work."""
    selected_bidder_for_flood_report: Annotated[str, "bidder selected for the flood report"]
    selected_bidder_for_hurricane_report: Annotated[str, "bidder selected for the hurricane report"]
    final_report: Annotated[str, "final consolidated report generated by the auctioneer"]
    step: Annotated[int, "current step in the auction process"]
    weather_data: Annotated[str, "weather data provided as input"]

# ---- Tool: Player Card ----
@tool
def get_player_card_tool(agent_name: Annotated[str, "name of the agent requesting its player card"]):
    """Tool to retrieve player card information for an agent."""
    player_cards = {
        "bidder1": {
            "flood_knowledge": random.randint(5, 8),
            "hurricane_knowledge": random.randint(3, 8),
            "response_time": random.randint(0, 3)
        },
        "bidder2": {
            "flood_knowledge": random.randint(3, 7),
            "hurricane_knowledge": random.randint(2, 9),
            "response_time": random.randint(0, 3)
        }
    }
    return player_cards.get(agent_name, {"error": "unknown player"})

# ---- Structured Output Schemas ----
class selected_bidders(BaseModel):
    """Selected bidders for report generation."""
    selected_bidder_for_flood_report: Annotated[str, "bidder for flood report"]
    selected_bidder_for_hurricane_report: Annotated[str, "bidder for hurricane report"]
    selection_reasoning: Annotated[str, "justification for the choice"]

class player_card(BaseModel):
    """Agent data."""
    player_name: Annotated[str, "player name"]
    flood_knowledge_out_of_10: Annotated[int, "flood knowledge out of 10"]
    hurricane_knowledge_out_of_10: Annotated[int, "hurricane knowledge out of 10"]
    response_time_in_seconds: Annotated[int, "response time in seconds"]

# ---- Bidder Agent Creation ----
def create_bidder_player_card_agent(agent_name: str):
    """Creates a React agent for the bidder."""
    return create_react_agent(
        anthropic_llm,
        tools=[get_player_card_tool],
        prompt=f"You are a helpful agent, your name is {agent_name}. "
               f"Your task is to get the player card data for yourself. You can use tools to get the data.",
        response_format=player_card
    )

def create_prompt(input: MessagesState):
    return (
        "Based on the following data, choose the most suitable bidder to generate reports for flood and hurricane events. "
        "- Prioritize the knowledge of each bidder in the relevant subject (flood or hurricane) over their response time. "
        "- Response time should only be considered if two bidders have similar levels of expertise. "
        f"Here is the data: {input}"
    )

get_selected_bidders = create_react_agent(
    anthropic_llm,
    tools=[],
    prompt=create_prompt,
    response_format=selected_bidders
)

# ---- Shared Bidder Logic ----
def handle_bidder_steps(agent_name: str, state: OverallState, step: str):
    """Handles bidder steps and actions according to the state."""
    if step == "step-1":
        response = create_bidder_player_card_agent(agent_name).invoke(state)
        player_card_data = response["structured_response"]
        return Command(
            goto=["auctioneer"],
            update={
                "messages": [
                    HumanMessage(content=f"{agent_name} response time: {player_card_data.response_time_in_seconds}s", name=agent_name),
                    HumanMessage(content=f"{agent_name} flood knowledge: {player_card_data.flood_knowledge_out_of_10}/10", name=agent_name),
                    HumanMessage(content=f"{agent_name} hurricane knowledge: {player_card_data.hurricane_knowledge_out_of_10}/10", name=agent_name),
                ]
            }
        )

    if step == "step-2":
        for_flood = state["selected_bidder_for_flood_report"]
        for_hurricane = state["selected_bidder_for_hurricane_report"]
        if agent_name in (for_flood, for_hurricane):
            topics = []
            if agent_name == for_flood:
                topics.append("flood")
            if agent_name == for_hurricane:
                topics.append("hurricane")
            messages = [f"Based on the following data generate a report on: {', '.join(topics)}. Data: {state['weather_data']}"]
            report = anthropic_llm.invoke(messages)
            return Command(
                goto=["auctioneer"],
                update={"messages": [HumanMessage(content=f"{agent_name} report: {report.content}", name=agent_name)]}
            )
        return Command(goto=["auctioneer"])

    return Command(goto=["auctioneer"], update={"messages": [HumanMessage(content=f"{agent_name} Unknown step", name=agent_name)]})

def bidder1(state: OverallState) -> Command[Literal["auctioneer"]]:
    step = state["messages"][-1].content
    return handle_bidder_steps("bidder1", state, step)

def bidder2(state: OverallState) -> Command[Literal["auctioneer"]]:
    step = state["messages"][-1].content
    return handle_bidder_steps("bidder2", state, step)

# ---- Auctioneer ----
def auctioneer(state: OverallState) -> Command[Literal["bidder1", "bidder2", END]]:
    """Initiates and orchestrates the auction process."""
    if state["step"] == 1:
        return Command(
            goto=["bidder1", "bidder2"],
            update={
                "messages": [
                    HumanMessage(content="Share your player card data", name="auctioneer"),
                    HumanMessage(content="step-1", name="auctioneer")
                ],
                "step": state["step"] + 1
            }
        )

    elif state["step"] == 2:
        response = get_selected_bidders.invoke(state)
        selected_data = response["structured_response"]
        pprint(selected_data)
        return Command(
            goto=["bidder1", "bidder2"],
            update={
                "messages": [
                    HumanMessage(content=f"Selected for flood: {selected_data.selected_bidder_for_flood_report}", name="auctioneer"),
                    HumanMessage(content=f"Selected for hurricane: {selected_data.selected_bidder_for_hurricane_report}", name="auctioneer"),
                    HumanMessage(content="step-2", name="auctioneer")
                ],
                "selected_bidder_for_flood_report": selected_data.selected_bidder_for_flood_report,
                "selected_bidder_for_hurricane_report": selected_data.selected_bidder_for_hurricane_report,
                "step": state["step"] + 1
            }
        )

    elif state["step"] == 3:
        final = anthropic_llm.invoke(f"Based on the message history, generate a consolidated weather report. History: {state['messages']}")
        return Command(
            goto=[END],
            update={
                "messages": [HumanMessage(content=f"Final report: {final.content}", name="auctioneer")],
                "final_report": final.content
            }
        )

    return Command(goto=[END])

# ---- Graph Construction ----
graph_builder = StateGraph(OverallState)
graph_builder.add_node("auctioneer", auctioneer)
graph_builder.add_node("bidder1", bidder1)
graph_builder.add_node("bidder2", bidder2)
graph_builder.add_edge(START, "auctioneer")
graph = graph_builder.compile(name="auction-game-theory")

# ---- Mock Weather Data ----
def get_weather_data():
    return (
        "Hurricane data: Storm Name: Hurricane Ida, Category: 4, Wind Speed: 145 mph, "
        "Coordinates: Latitude: 25.5, Longitude: -81.5, "
        "Forecast: Expected landfall, affected areas (Florida, Georgia, South Carolina). "
        "Flood data: Flood Warning: True, Affected Regions: Southern Texas, Louisiana, "
        "Rainfall Data: 24-hour rainfall (10.5 inches), 72 hours (30.1 inches), "
        "River Levels: Mississippi River, Expected peak: 36.0 feet."
    )

# ---- Execution ----
for s in graph.stream(
    {"messages": [("user", "Find best agent for the tasks.")],
     "step": 1,
     "weather_data": get_weather_data()}
):
    pprint(s)
    pprint("============================")

Sample selection result:

selected_bidders(
  selected_bidder_for_flood_report='bidder2',
  selected_bidder_for_hurricane_report='bidder2',
  selection_reasoning="Bidder 2 was selected for both flood and hurricane reports
  based on superior domain knowledge. For floods: Bidder 2 has 8/10 knowledge vs
  Bidder 1's 7/10. For hurricanes: Bidder 2 has 4/10 vs Bidder 1's 3/10.
  Although Bidder 1 has faster response time (3 vs 15 seconds), knowledge
  expertise takes priority over speed for accurate disaster reporting."
)

14. Resources and References

Frameworks and Tools

ResourceLink
LangGraph Docslangchain-ai.github.io/langgraph
LangGraph Graph APIConcepts Low Level
LangGraph Why?Why LangGraph
LangGraph Structured OutputAgentic Concepts
LangGraph Community AgentsPrebuilt Agents
LangGraph Supervisorlanggraph-supervisor-py
LangGraph Swarmlanggraph-swarm-py
LangSmithdocs.smith.langchain.com
Human-in-the-loopLangGraph HIL

Models and APIs

ResourceLink
GPT-4.1 (OpenAI)openai.com/index/gpt-4-1
GPT-4.1 Prompting Guidecookbook.openai.com
DALL-E Image Generatorlangchain docs

Protocols and Standards

ResourceLink
Model Context Protocol (MCP)modelcontextprotocol.io
Agent2Agent Protocol (A2A)Google Blog
A2A GitHubgithub.com/google/A2A
Contract Net ProtocolWikipedia

Research Papers and Further Reading

ResourceLink
Model Swarms: Swarm Intelligence for LLMsarxiv.org/abs/2410.11163
LLMs as Rational Players in Game Theoryarxiv.org/abs/2312.05488
Game Theory Meets LLMs: Systematic Surveyarxiv.org/abs/2502.09053
LangGraph Multi-Agent Workflows (Blog)blog.langchain.dev
Hierarchical Agent TeamsLangGraph Tutorial

Training Source Code

ModuleLink
Module 1 — Core Conceptsgithub.com/msajid/lg-mas-m1
Module 2 — Advanced Scenariosgithub.com/msajid/lg-mas-m2

Available Notebooks (Module 1):

  • architectures.ipynb — Different architecture patterns
  • cnp.ipynb — Contract Net Protocol (Lift Controller)
  • dressing_planner.ipynb — Outfit planner
  • dressing_planner_supervisor.ipynb — With supervisor
  • dressing_planner_supervisor_image.ipynb — With image generation
  • swarm.ipynb — Swarm architecture

Available Notebooks (Module 2):

  • dressing_planner_team.ipynb — Planning team
  • dressing_planner_weather_reporter.ipynb — With weather reporter
  • flight_support_swarm_architecture.ipynb — Flight support (Swarm)
  • hierarchical_architecture.ipynb — Hierarchical architecture
  • prisoners_dilemma.ipynb — Prisoner’s Dilemma
  • train_braking_network_architecture.ipynb — Train braking (Network)
  • weather_flood_vs_hurricane_game.ipynb — Weather auction game

Conclusion: The future of AI is agentic. We are moving from simple prompts to autonomous multi-agent systems that use intelligent interaction strategies. This is the beginning of a new modular, loosely-coupled architecture powered by specialized agents.

Experiment with LangGraph, Crew AI, test different models — that is how you move from exploring agents to building production systems.


Search Terms

developing · multi-agent · systems · ai · agents · orchestration · artificial · intelligence · generative · architecture · system · agent · cnp · components · dilemma · hierarchical · langgraph · prisoner · topology · auction · concepts · controller · core · elevator

Interested in this course?

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