A comprehensive guide to designing and implementing multi-agent systems with LangGraph
Table of Contents
- Introduction to Multi-Agent Systems
- Architecture and Components of AI Agents
- Architecture Patterns and Topologies
- LangGraph — Core Concepts
- Dependency Relationships Between Agents
- Agent Interaction Strategies
- Negotiation in Multi-Agent Systems
- Demo: Elevator Controller with the CNP
- Organizational Structures in Multi-Agent Systems
- Advanced Scenarios — Network Architecture
- Advanced Scenarios — Hierarchical Systems
- Game Theory Applied to Multi-Agent Systems
- Demo: Agentic Systems Based on Game Theory
- 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):
| Attribute | Description |
|---|---|
| Autonomy | Operates without constant intervention, makes decisions independently |
| Social ability | Communicates and interacts with other agents |
| Goal-oriented behavior | Acts 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
| Pattern | Description | Example |
|---|---|---|
| Sequential | Agents work one after another | Data processing pipeline |
| Hierarchical | A supervisor delegates to worker agents | Project manager + team |
| Collaborative | Agents share resources and information | Research team |
| Competitive | Agents compete for the best result | Auctions |
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
| Component | Description |
|---|---|
| State | Shared data structure representing the current snapshot |
| Nodes | Python functions encoding agent logic |
| Edges | Python functions determining which node to execute next |
| Command | Combines 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
| Type | Description | Example |
|---|---|---|
| Independent | No dependency | Two cleaning robots in separate rooms |
| Unilateral | A depends on B, but not the reverse | A weather agent depends on a sensor |
| Mutual | A and B depend on each other for the same goal | Two robotic arms lifting an object |
| Reciprocal | A and B depend on each other for different goals | A 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
| Strategy | Description | Example |
|---|---|---|
| Cooperation | Coordination for mutual benefit | Information sharing, task division |
| Collaboration | Integrated cooperation toward a common goal | Joint planning, teamwork |
| Negotiation | Communication to find a compromise | Resource allocation, conflict resolution |
| Competition | Agents pursue conflicting objectives | Zero-sum scenarios |
| Adversarial | Deliberate opposition — one agent tries to hinder the other | Attacker 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:
- The initiator sends a Call for Proposal (CFP) to potential participants
- Participants respond with a proposal or offer
- The initiator selects the best one based on criteria (cost, deadline, etc.)
- 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
| Concept | Description |
|---|---|
| Players/Agents | Two or more participants who share knowledge of rules, strategies, and payoffs |
| Strategies | The available actions for each player |
| Payoffs | The 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]
| Type | Information | Example |
|---|---|---|
| Complete Information | All players know the structure and payoffs | Chess, Prisoner’s Dilemma |
| Incomplete Information | Players have private information about their payoffs | Auctions, 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 year | A: -3 years, B: free |
| A confesses (D) | A: free, B: -3 years | A: -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
| Resource | Link |
|---|---|
| LangGraph Docs | langchain-ai.github.io/langgraph |
| LangGraph Graph API | Concepts Low Level |
| LangGraph Why? | Why LangGraph |
| LangGraph Structured Output | Agentic Concepts |
| LangGraph Community Agents | Prebuilt Agents |
| LangGraph Supervisor | langgraph-supervisor-py |
| LangGraph Swarm | langgraph-swarm-py |
| LangSmith | docs.smith.langchain.com |
| Human-in-the-loop | LangGraph HIL |
Models and APIs
| Resource | Link |
|---|---|
| GPT-4.1 (OpenAI) | openai.com/index/gpt-4-1 |
| GPT-4.1 Prompting Guide | cookbook.openai.com |
| DALL-E Image Generator | langchain docs |
Protocols and Standards
| Resource | Link |
|---|---|
| Model Context Protocol (MCP) | modelcontextprotocol.io |
| Agent2Agent Protocol (A2A) | Google Blog |
| A2A GitHub | github.com/google/A2A |
| Contract Net Protocol | Wikipedia |
Research Papers and Further Reading
| Resource | Link |
|---|---|
| Model Swarms: Swarm Intelligence for LLMs | arxiv.org/abs/2410.11163 |
| LLMs as Rational Players in Game Theory | arxiv.org/abs/2312.05488 |
| Game Theory Meets LLMs: Systematic Survey | arxiv.org/abs/2502.09053 |
| LangGraph Multi-Agent Workflows (Blog) | blog.langchain.dev |
| Hierarchical Agent Teams | LangGraph Tutorial |
Training Source Code
| Module | Link |
|---|---|
| Module 1 — Core Concepts | github.com/msajid/lg-mas-m1 |
| Module 2 — Advanced Scenarios | github.com/msajid/lg-mas-m2 |
Available Notebooks (Module 1):
architectures.ipynb— Different architecture patternscnp.ipynb— Contract Net Protocol (Lift Controller)dressing_planner.ipynb— Outfit plannerdressing_planner_supervisor.ipynb— With supervisordressing_planner_supervisor_image.ipynb— With image generationswarm.ipynb— Swarm architecture
Available Notebooks (Module 2):
dressing_planner_team.ipynb— Planning teamdressing_planner_weather_reporter.ipynb— With weather reporterflight_support_swarm_architecture.ipynb— Flight support (Swarm)hierarchical_architecture.ipynb— Hierarchical architectureprisoners_dilemma.ipynb— Prisoner’s Dilemmatrain_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