"""
Pattern 099: Negotiation Protocol
Description:
The Negotiation Protocol pattern enables multiple agents to reach agreements through
structured negotiation processes. This pattern is essential for multi-agent systems
where agents have different goals, preferences, or constraints and need to find
mutually acceptable solutions through iterative proposal and counter-proposal exchanges.
Negotiation is a fundamental aspect of multi-agent coordination, allowing agents to
resolve conflicts, allocate resources, coordinate actions, and make collective decisions.
This pattern provides mechanisms for proposal generation, evaluation, counter-offers,
concession strategies, and agreement finalization.
The pattern supports various negotiation strategies including competitive (zero-sum),
cooperative (win-win), and mixed approaches. It handles single-issue and multi-issue
negotiations, bilateral and multilateral negotiations, and includes mechanisms for
handling deadlocks and timeouts.
Key Components:
1. Proposal: Structured offer from one agent to others
2. Counter-Proposal: Modified proposal in response to original
3. Utility Function: Evaluates value of proposals for agent
4. Concession Strategy: Determines how much to compromise
5. Negotiation Protocol: Rules governing negotiation process
6. Agreement: Final mutually accepted terms
7. Mediation: Optional third-party facilitation
Negotiation Types:
1. Distributive: Fixed resources, competitive
2. Integrative: Value creation, cooperative
3. Multi-issue: Multiple attributes to negotiate
4. Sequential: Issues addressed one at a time
5. Package: All issues negotiated together
Strategies:
1. Conceding: Gradually reduce demands
2. Hardball: Maintain firm position
3. Tit-for-tat: Mirror opponent's behavior
4. Time-based: Adjust based on deadline
5. Information-based: Learn and adapt
Use Cases:
- Resource allocation in multi-agent systems
- Task assignment and coordination
- Conflict resolution between agents
- Service level agreements
- Coalition formation
- Price negotiation in marketplaces
- Scheduling and planning coordination
- Policy consensus building
Advantages:
- Flexible conflict resolution
- Fair resource distribution
- Autonomous decision-making
- Adaptable to agent preferences
- Handles diverse objectives
- Enables cooperation
- Supports complex agreements
Challenges:
- Computational complexity
- Strategic manipulation
- Information asymmetry
- Deadlock situations
- Time constraints
- Preference elicitation
- Trust issues
LangChain Implementation:
This implementation uses LangChain for:
- LLM-based proposal generation and evaluation
- Utility calculation with reasoning
- Counter-proposal generation
- Agreement evaluation
- Negotiation strategy implementation
Production Considerations:
- Set negotiation timeouts and round limits
- Implement deadlock breaking mechanisms
- Log all negotiation steps for transparency
- Validate proposals against constraints
- Handle malformed proposals gracefully
- Monitor for strategic manipulation
- Implement fair mediation when needed
- Consider privacy of agent preferences
"""
import os
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass, field
from enum import Enum
from datetime import datetime
from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
load_dotenv()
class ProposalStatus(Enum):
"""Status of a negotiation proposal."""
PENDING = "pending"
ACCEPTED = "accepted"
REJECTED = "rejected"
COUNTER = "counter"
WITHDRAWN = "withdrawn"
class NegotiationOutcome(Enum):
"""Outcome of a negotiation."""
AGREEMENT = "agreement"
DEADLOCK = "deadlock"
TIMEOUT = "timeout"
WITHDRAWN = "withdrawn"
@dataclass
class Proposal:
"""
Represents a proposal in a negotiation.
Attributes:
proposal_id: Unique identifier
proposer: Agent making the proposal
recipient: Agent(s) receiving proposal
round_number: Negotiation round
terms: Dictionary of proposed terms
utility: Utility value for proposer
status: Current status of proposal
timestamp: When proposal was made
justification: Reasoning for proposal
metadata: Additional information
"""
proposal_id: str
proposer: str
recipient: str
round_number: int
terms: Dict[str, Any]
utility: float
status: ProposalStatus
timestamp: datetime
justification: str = ""
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class NegotiationState:
"""
Tracks the state of an ongoing negotiation.
Attributes:
negotiation_id: Unique identifier
participants: Agents involved
issue: What is being negotiated
proposals: History of proposals
current_round: Current round number
max_rounds: Maximum allowed rounds
start_time: When negotiation started
end_time: When negotiation ended
outcome: Final outcome
agreement: Final agreed terms
metadata: Additional information
"""
negotiation_id: str
participants: List[str]
issue: str
proposals: List[Proposal] = field(default_factory=list)
current_round: int = 1
max_rounds: int = 10
start_time: datetime = field(default_factory=datetime.now)
end_time: Optional[datetime] = None
outcome: Optional[NegotiationOutcome] = None
agreement: Optional[Dict[str, Any]] = None
metadata: Dict[str, Any] = field(default_factory=dict)
class NegotiationAgent:
"""
Agent that participates in negotiations.
This agent can generate proposals, evaluate offers, make counter-proposals,
and decide when to accept or reject proposals based on its utility function
and negotiation strategy.
"""
def __init__(
self,
name: str,
preferences: Dict[str, Any],
reservation_utility: float = 0.3,
concession_rate: float = 0.1,
temperature: float = 0.5
):
"""
Initialize negotiation agent.
Args:
name: Agent's name
preferences: Agent's preferences for negotiation issues
reservation_utility: Minimum acceptable utility (walk-away point)
concession_rate: How quickly agent makes concessions
temperature: LLM temperature for generation
"""
self.name = name
self.preferences = preferences
self.reservation_utility = reservation_utility
self.concession_rate = concession_rate
self.llm = ChatOpenAI(temperature=temperature, model="gpt-3.5-turbo")
self.negotiation_history: List[Proposal] = []
def calculate_utility(self, terms: Dict[str, Any]) -> float:
"""
Calculate utility of proposed terms.
Args:
terms: Proposed terms
Returns:
Utility value between 0 and 1
"""
# Simple weighted sum utility calculation
total_utility = 0.0
total_weight = 0.0
for key, value in terms.items():
if key in self.preferences:
pref = self.preferences[key]
weight = pref.get("weight", 1.0)
ideal = pref.get("ideal", value)
minimum = pref.get("minimum", 0)
# Normalize value
if ideal != minimum:
normalized = (value - minimum) / (ideal - minimum)
normalized = max(0.0, min(1.0, normalized))
else:
normalized = 1.0 if value == ideal else 0.0
total_utility += weight * normalized
total_weight += weight
return total_utility / total_weight if total_weight > 0 else 0.0
def generate_initial_proposal(
self,
issue: str,
recipient: str,
round_number: int = 1
) -> Proposal:
"""
Generate initial proposal.
Args:
issue: Issue being negotiated
recipient: Recipient agent
round_number: Round number
Returns:
Initial proposal
"""
# Start with terms close to ideal preferences
terms = {}
for key, pref in self.preferences.items():
# Start at 90% of ideal for initial proposal
ideal = pref.get("ideal", 100)
minimum = pref.get("minimum", 0)
initial_value = ideal * 0.9 + minimum * 0.1
terms[key] = initial_value
utility = self.calculate_utility(terms)
# Generate justification using LLM
prompt = ChatPromptTemplate.from_template(
"You are {agent_name} negotiating about {issue}. "
"You propose the following terms: {terms}. "
"Write a brief 2-3 sentence justification for this proposal, "
"emphasizing why it's fair and reasonable.\n\n"
"Justification:"
)
chain = prompt | self.llm | StrOutputParser()
justification = chain.invoke({
"agent_name": self.name,
"issue": issue,
"terms": terms
})
proposal = Proposal(
proposal_id=f"{self.name}_r{round_number}",
proposer=self.name,
recipient=recipient,
round_number=round_number,
terms=terms,
utility=utility,
status=ProposalStatus.PENDING,
timestamp=datetime.now(),
justification=justification.strip()
)
self.negotiation_history.append(proposal)
return proposal
def evaluate_proposal(
self,
proposal: Proposal,
current_round: int
) -> Tuple[bool, str]:
"""
Evaluate whether to accept a proposal.
Args:
proposal: Proposal to evaluate
current_round: Current negotiation round
Returns:
Tuple of (accept: bool, reasoning: str)
"""
utility = self.calculate_utility(proposal.terms)
# Accept if utility meets threshold
# Threshold decreases over rounds (concession)
round_factor = 1.0 - (current_round * self.concession_rate)
threshold = max(
self.reservation_utility,
self.reservation_utility + (1.0 - self.reservation_utility) * round_factor
)
accept = utility >= threshold
# Generate reasoning using LLM
prompt = ChatPromptTemplate.from_template(
"You are {agent_name} evaluating a proposal about {issue}. "
"The proposed terms are: {terms}. "
"The utility you would gain is {utility:.2f} "
"(your minimum acceptable is {threshold:.2f}). "
"Decision: {'ACCEPT' if accept else 'REJECT'}. "
"Write a brief 2-3 sentence explanation for this decision.\n\n"
"Explanation:"
)
chain = prompt | self.llm | StrOutputParser()
reasoning = chain.invoke({
"agent_name": self.name,
"issue": proposal.metadata.get("issue", "the issue"),
"terms": proposal.terms,
"utility": utility,
"threshold": threshold,
"accept": accept
})
return accept, reasoning.strip()
def generate_counter_proposal(
self,
previous_proposal: Proposal,
issue: str,
round_number: int
) -> Proposal:
"""
Generate counter-proposal.
Args:
previous_proposal: Proposal to counter
issue: Issue being negotiated
round_number: Current round number
Returns:
Counter-proposal
"""
# Make concessions based on previous proposal and round number
terms = {}
for key, pref in self.preferences.items():
ideal = pref.get("ideal", 100)
minimum = pref.get("minimum", 0)
previous_value = previous_proposal.terms.get(key, minimum)
# Move toward middle ground with concession
concession = self.concession_rate * round_number
my_target = ideal * (1 - concession) + minimum * concession
counter_value = (my_target + previous_value) / 2
# Ensure within bounds
counter_value = max(minimum, min(ideal, counter_value))
terms[key] = counter_value
utility = self.calculate_utility(terms)
# Generate justification using LLM
prompt = ChatPromptTemplate.from_template(
"You are {agent_name} making a counter-proposal about {issue}. "
"The previous proposal was: {previous_terms}. "
"Your counter-proposal is: {terms}. "
"Write a brief 2-3 sentence justification explaining "
"how this is a reasonable compromise.\n\n"
"Justification:"
)
chain = prompt | self.llm | StrOutputParser()
justification = chain.invoke({
"agent_name": self.name,
"issue": issue,
"previous_terms": previous_proposal.terms,
"terms": terms
})
proposal = Proposal(
proposal_id=f"{self.name}_r{round_number}_counter",
proposer=self.name,
recipient=previous_proposal.proposer,
round_number=round_number,
terms=terms,
utility=utility,
status=ProposalStatus.PENDING,
timestamp=datetime.now(),
justification=justification.strip(),
metadata={"counter_to": previous_proposal.proposal_id}
)
self.negotiation_history.append(proposal)
return proposal
class NegotiationMediator:
"""
Mediator that facilitates negotiation between agents.
The mediator manages the negotiation protocol, enforces rules,
tracks state, and can suggest compromises when negotiations stall.
"""
def __init__(self, temperature: float = 0.3):
"""
Initialize negotiation mediator.
Args:
temperature: LLM temperature for generation
"""
self.llm = ChatOpenAI(temperature=temperature, model="gpt-3.5-turbo")
self.negotiations: Dict[str, NegotiationState] = {}
def start_negotiation(
self,
negotiation_id: str,
participants: List[str],
issue: str,
max_rounds: int = 10
) -> NegotiationState:
"""
Start a new negotiation.
Args:
negotiation_id: Unique identifier
participants: List of participating agents
issue: Issue being negotiated
max_rounds: Maximum rounds allowed
Returns:
Initial negotiation state
"""
state = NegotiationState(
negotiation_id=negotiation_id,
participants=participants,
issue=issue,
max_rounds=max_rounds
)
self.negotiations[negotiation_id] = state
return state
def process_round(
self,
negotiation_id: str,
proposal: Proposal,
response_accept: bool,
response_reasoning: str,
counter_proposal: Optional[Proposal] = None
) -> NegotiationState:
"""
Process a negotiation round.
Args:
negotiation_id: Negotiation identifier
proposal: Current proposal
response_accept: Whether proposal was accepted
response_reasoning: Reasoning for response
counter_proposal: Counter-proposal if not accepted
Returns:
Updated negotiation state
"""
state = self.negotiations[negotiation_id]
# Add proposal to history
proposal.metadata["response_reasoning"] = response_reasoning
state.proposals.append(proposal)
if response_accept:
# Negotiation successful
proposal.status = ProposalStatus.ACCEPTED
state.outcome = NegotiationOutcome.AGREEMENT
state.agreement = proposal.terms
state.end_time = datetime.now()
elif counter_proposal:
# Counter-proposal made
proposal.status = ProposalStatus.COUNTER
state.proposals.append(counter_proposal)
state.current_round += 1
else:
# Proposal rejected without counter
proposal.status = ProposalStatus.REJECTED
state.current_round += 1
# Check for timeout
if state.current_round > state.max_rounds and not state.outcome:
state.outcome = NegotiationOutcome.TIMEOUT
state.end_time = datetime.now()
return state
def suggest_compromise(
self,
negotiation_id: str
) -> Optional[Dict[str, Any]]:
"""
Suggest compromise when negotiation stalls.
Args:
negotiation_id: Negotiation identifier
Returns:
Suggested compromise terms or None
"""
state = self.negotiations.get(negotiation_id)
if not state or len(state.proposals) str:
"""
Generate summary of negotiation.
Args:
negotiation_id: Negotiation identifier
Returns:
Summary text
"""
state = self.negotiations.get(negotiation_id)
if not state:
return "Negotiation not found."
summary = f"Negotiation: {state.issue}\n"
summary += f"Participants: {', '.join(state.participants)}\n"
summary += f"Rounds: {state.current_round}/{state.max_rounds}\n"
summary += f"Outcome: {state.outcome.value if state.outcome else 'ongoing'}\n"
if state.agreement:
summary += f"\nAgreed Terms:\n"
for key, value in state.agreement.items():
summary += f" - {key}: {value}\n"
summary += f"\nProposal History ({len(state.proposals)} proposals):\n"
for i, prop in enumerate(state.proposals[-5:], 1): # Last 5
summary += f" {i}. Round {prop.round_number}: "
summary += f"{prop.proposer} -> {prop.recipient} "
summary += f"[{prop.status.value}]\n"
return summary
def demonstrate_negotiation_protocol():
"""Demonstrate negotiation protocol pattern."""
print("=" * 80)
print("NEGOTIATION PROTOCOL PATTERN DEMONSTRATION")
print("=" * 80)
# Example 1: Simple bilateral negotiation
print("\n" + "=" * 80)
print("Example 1: Bilateral Price Negotiation")
print("=" * 80)
# Create buyer and seller agents
buyer = NegotiationAgent(
name="Buyer",
preferences={
"price": {"ideal": 100, "minimum": 100, "weight": 1.0},
"delivery_time": {"ideal": 1, "minimum": 1, "weight": 0.5}
},
reservation_utility=0.4,
concession_rate=0.08
)
seller = NegotiationAgent(
name="Seller",
preferences={
"price": {"ideal": 150, "minimum": 120, "weight": 1.0},
"delivery_time": {"ideal": 7, "minimum": 1, "weight": 0.3}
},
reservation_utility=0.3,
concession_rate=0.1
)
mediator = NegotiationMediator()
# Start negotiation
issue = "Product Sale Agreement"
state = mediator.start_negotiation(
negotiation_id="negotiation_001",
participants=["Buyer", "Seller"],
issue=issue,
max_rounds=8
)
print(f"\nStarting negotiation: {issue}")
print(f"Participants: {', '.join(state.participants)}")
print(f"Maximum rounds: {state.max_rounds}")
# Negotiation rounds
current_proposer = buyer
current_recipient = seller
current_proposal = buyer.generate_initial_proposal(issue, "Seller")
print(f"\n{'Round':<8} {'Proposer':<12} {'Action':<15} {'Price':<10} {'Delivery':<10} {'Utility':<10}")
print("-" * 80)
while state.current_round <= state.max_rounds and not state.outcome:
# Display proposal
print(f"{state.current_round:<8} {current_proposal.proposer:<12} "
f"{'Proposes':<15} "
f"{current_proposal.terms.get('price', 0):<10.1f} "
f"{current_proposal.terms.get('delivery_time', 0):<10.1f} "
f"{current_proposal.utility:<10.2f}")
# Recipient evaluates proposal
accept, reasoning = current_recipient.evaluate_proposal(
current_proposal,
state.current_round
)
if accept:
# Agreement reached
state = mediator.process_round(
"negotiation_001",
current_proposal,
True,
reasoning
)
print(f"{'':<8} {current_recipient.name:<12} {'✓ ACCEPTS':<15}")
break
else:
# Generate counter-proposal
counter = current_recipient.generate_counter_proposal(
current_proposal,
issue,
state.current_round
)
print(f"{'':<8} {current_recipient.name:<12} {'✗ Rejects':<15}")
state = mediator.process_round(
"negotiation_001",
current_proposal,
False,
reasoning,
counter
)
# Switch roles
current_proposal = counter
current_proposer, current_recipient = current_recipient, current_proposer
# Print outcome
print("\n" + "-" * 80)
print(f"Outcome: {state.outcome.value.upper()}")
if state.agreement:
print("\nFinal Agreement:")
for key, value in state.agreement.items():
print(f" - {key}: {value:.2f}")
print(f"\nUtilities:")
print(f" Buyer utility: {buyer.calculate_utility(state.agreement):.2f}")
print(f" Seller utility: {seller.calculate_utility(state.agreement):.2f}")
# Example 2: Multi-issue negotiation
print("\n" + "=" * 80)
print("Example 2: Multi-Issue Resource Allocation")
print("=" * 80)
agent_a = NegotiationAgent(
name="Agent_A",
preferences={
"cpu_cores": {"ideal": 16, "minimum": 4, "weight": 1.0},
"memory_gb": {"ideal": 64, "minimum": 16, "weight": 0.8},
"storage_tb": {"ideal": 10, "minimum": 2, "weight": 0.6}
},
reservation_utility=0.35,
concession_rate=0.12
)
agent_b = NegotiationAgent(
name="Agent_B",
preferences={
"cpu_cores": {"ideal": 12, "minimum": 4, "weight": 0.9},
"memory_gb": {"ideal": 48, "minimum": 16, "weight": 1.0},
"storage_tb": {"ideal": 8, "minimum": 2, "weight": 0.5}
},
reservation_utility=0.3,
concession_rate=0.1
)
mediator2 = NegotiationMediator()
issue2 = "Server Resource Allocation"
state2 = mediator2.start_negotiation(
negotiation_id="negotiation_002",
participants=["Agent_A", "Agent_B"],
issue=issue2,
max_rounds=6
)
print(f"\nNegotiating: {issue2}")
print(f"Available resources to allocate between agents")
# Run negotiation
proposer = agent_a
recipient = agent_b
proposal = agent_a.generate_initial_proposal(issue2, "Agent_B")
print(f"\n{'Round':<8} {'Proposer':<12} {'CPU':<10} {'Memory':<12} {'Storage':<12} {'Decision':<15}")
print("-" * 80)
for round_num in range(1, state2.max_rounds + 1):
print(f"{round_num:<8} {proposal.proposer:<12} "
f"{proposal.terms.get('cpu_cores', 0):<10.1f} "
f"{proposal.terms.get('memory_gb', 0):<12.1f} "
f"{proposal.terms.get('storage_tb', 0):<12.1f}", end=" ")
accept, reasoning = recipient.evaluate_proposal(proposal, round_num)
if accept:
state2 = mediator2.process_round(
"negotiation_002",
proposal,
True,
reasoning
)
print("✓ ACCEPTED")
break
else:
print("✗ Counter")
counter = recipient.generate_counter_proposal(proposal, issue2, round_num)
state2 = mediator2.process_round(
"negotiation_002",
proposal,
False,
reasoning,
counter
)
proposal = counter
proposer, recipient = recipient, proposer
print("\n" + "-" * 80)
if state2.agreement:
print("Agreement Reached!")
print(f"\nFinal Resource Allocation:")
for key, value in state2.agreement.items():
print(f" {key}: {value:.1f}")
# Example 3: Negotiation with mediation
print("\n" + "=" * 80)
print("Example 3: Mediated Negotiation with Compromise")
print("=" * 80)
# Stubborn agents with conflicting preferences
stubborn_a = NegotiationAgent(
name="Stubborn_A",
preferences={
"budget": {"ideal": 50000, "minimum": 40000, "weight": 1.0}
},
reservation_utility=0.7, # Very high reservation
concession_rate=0.02 # Very slow concessions
)
stubborn_b = NegotiationAgent(
name="Stubborn_B",
preferences={
"budget": {"ideal": 80000, "minimum": 70000, "weight": 1.0}
},
reservation_utility=0.7,
concession_rate=0.02
)
mediator3 = NegotiationMediator()
state3 = mediator3.start_negotiation(
negotiation_id="negotiation_003",
participants=["Stubborn_A", "Stubborn_B"],
issue="Project Budget Allocation",
max_rounds=5
)
print("\nNegotiation between stubborn agents (likely to deadlock)")
# Run negotiation
proposer = stubborn_a
recipient = stubborn_b
proposal = stubborn_a.generate_initial_proposal(
"Project Budget Allocation",
"Stubborn_B"
)
for round_num in range(1, 6):
print(f"\nRound {round_num}:")
print(f" Proposal from {proposal.proposer}: Budget = ${proposal.terms['budget']:,.0f}")
accept, reasoning = recipient.evaluate_proposal(proposal, round_num)
if accept:
state3 = mediator3.process_round(
"negotiation_003",
proposal,
True,
reasoning
)
print(f" {recipient.name} accepts!")
break
else:
print(f" {recipient.name} rejects")
counter = recipient.generate_counter_proposal(
proposal,
"Project Budget Allocation",
round_num
)
state3 = mediator3.process_round(
"negotiation_003",
proposal,
False,
reasoning,
counter
)
proposal = counter
proposer, recipient = recipient, proposer
if not state3.outcome:
print("\nDeadlock detected! Mediator suggests compromise...")
compromise = mediator3.suggest_compromise("negotiation_003")
if compromise:
print(f"Suggested compromise: Budget = ${compromise['budget']:,.0f}")
print(f" Utility for Stubborn_A: {stubborn_a.calculate_utility(compromise):.2f}")
print(f" Utility for Stubborn_B: {stubborn_b.calculate_utility(compromise):.2f}")
# Summary
print("\n" + "=" * 80)
print("SUMMARY: Negotiation Protocol Pattern")
print("=" * 80)
summary = """
The Negotiation Protocol pattern demonstrated:
1. BILATERAL NEGOTIATION (Example 1):
- Two agents negotiating price and delivery
- Iterative proposal and counter-proposal
- Utility-based evaluation
- Gradual concessions leading to agreement
- Both agents achieve acceptable utility
2. MULTI-ISSUE NEGOTIATION (Example 2):
- Resource allocation across multiple dimensions
- Weighted preferences for different attributes
- Trade-offs between competing interests
- Efficient agreement through compromise
3. MEDIATED NEGOTIATION (Example 3):
- Stubborn agents with conflicting goals
- Deadlock detection
- Mediator-suggested compromise
- Breaking negotiation impasses
KEY BENEFITS:
✓ Autonomous conflict resolution
✓ Fair resource distribution
✓ Adaptable to agent preferences
✓ Handles complex multi-issue negotiations
✓ Supports cooperative and competitive scenarios
✓ Transparent reasoning through justifications
✓ Mediation for difficult negotiations
USE CASES:
• Resource allocation in distributed systems
• Task assignment and scheduling
• Service level agreement negotiation
• Price negotiation in marketplaces
• Coalition formation
• Conflict resolution between agents
• Policy consensus building
BEST PRACTICES:
1. Set clear reservation utilities (walk-away points)
2. Implement gradual concession strategies
3. Use utility functions to evaluate proposals
4. Track negotiation history for transparency
5. Set round limits to prevent infinite loops
6. Provide justifications for proposals
7. Use mediation for deadlock situations
8. Consider fairness and efficiency
TRADE-OFFS:
• Computational cost vs. negotiation quality
• Speed of agreement vs. optimal outcome
• Information sharing vs. strategic advantage
• Autonomy vs. mediation intervention
PRODUCTION CONSIDERATIONS:
→ Monitor negotiation metrics (rounds, success rate)
→ Log all proposals for audit trails
→ Implement timeout and deadlock detection
→ Validate proposals against constraints
→ Handle edge cases (withdrawal, malformed proposals)
→ Consider privacy of agent preferences
→ Test with diverse agent strategies
→ Implement fair mediation mechanisms
This pattern enables sophisticated multi-agent coordination through
structured negotiation, allowing agents to autonomously reach agreements
while respecting individual preferences and constraints.
"""
print(summary)
if __name__ == "__main__":
demonstrate_negotiation_protocol()
Agentic Design Pattern: Negotiation Protocol
Pattern 099: Negotiation Protocol – Line-by-Line Explanation
Overview
This document provides a detailed line-by-line explanation of the Negotiation Protocol pattern implementation.
Pattern Description
Pattern 099: Negotiation Protocol
Description: The Negotiation Protocol pattern enables multiple agents to reach agreements through structured negotiation processes. This pattern is essential for multi-agent systems where agents have different goals, preferences, or constraints and need to find mutually acceptable solutions through iterative proposal and counter-proposal exchanges.
Negotiation is a fundamental aspect of multi-agent coordination, allowing agents to
resolve conflicts, allocate resources, coordinate actions, and make collective decisions.
This pattern provides mechanisms for proposal generation, evaluation, counter-offers,
concession strategies, and agreement finalization.
The pattern supports various negotiation strategies including competitive (zero-sum),
cooperative (win-win), and mixed approaches. It handles single-issue and multi-issue
negotiations, bilateral and multilateral negotiations, and includes mechanisms for
handling deadlocks and timeouts.
Key Components: 1. Proposal: Structured offer from one agent to others 2. Counter-Proposal: Modified proposal in response to original 3. Utility Function: Evaluates value of proposals for agent 4. Concession Strategy: Determines how much to compromise 5. Negotiation Protocol: Rules governing negotiation process 6. Agreement: Final mutually accepted terms 7. Mediation: Optional third-party facilitation
Negotiation Types: 1. Distributive: Fixed resources, competitive 2. Integrative: Value creation, cooperative 3. Multi-issue: Multiple attributes to negotiate 4. Sequential: Issues addressed one at a time 5. Package: All issues negotiated together
Strategies: 1. Conceding: Gradually reduce demands 2. Hardball: Maintain firm position 3. Tit-for-tat: Mirror opponent’s behavior 4. Time-based: Adjust based on deadline 5. Information-based: Learn and adapt
Use Cases: – Resource allocation in multi-agent systems – Task assignment and coordination – Conflict resolution between agents – Service level agreements – Coalition formation – Price negotiation in marketplaces – Scheduling and planning coordination – Policy consensus building
Advantages: – Flexible conflict resolution – Fair resource distribution – Autonomous decision-making – Adaptable to agent preferences – Handles diverse objectives – Enables cooperation – Supports complex agreements
Challenges: – Computational complexity – Strategic manipulation – Information asymmetry – Deadlock situations – Time constraints – Preference elicitation – Trust issues
LangChain Implementation: This implementation uses LangChain for: – LLM-based proposal generation and evaluation – Utility calculation with reasoning – Counter-proposal generation – Agreement evaluation – Negotiation strategy implementation
Production Considerations: – Set negotiation timeouts and round limits – Implement deadlock breaking mechanisms – Log all negotiation steps for transparency – Validate proposals against constraints – Handle malformed proposals gracefully – Monitor for strategic manipulation – Implement fair mediation when needed – Consider privacy of agent preferences
File Statistics
- Total Lines: 950
- Classes: 6
- Functions: 1
- Constants: 0
Module Documentation
"""
Pattern 099: Negotiation Protocol
Description:
The Negotiation Protocol pattern enables multiple agents to reach agreements through
structured negotiation processes. This pattern is essential for multi-agent systems
where agents have different goals, preferences, or constraints and need to find
mutually acceptable solutions through iterative proposal and counter-proposal exchanges.
Negotiation is a fundamental aspect of multi-agent coordination, allowing agents to
resolve conflicts, allocate resources, coordinate actions, and make collective decisions.
This pattern provides mechanisms for proposal generation, evaluation, counter-offers,
concession strategies, and agreement finalization.
The pattern supports various negotiation strategies including competitive (zero-sum),
cooperative (win-win), and mixed approaches. It handles single-issue and multi-issue
negotiations, bilateral and multilateral negotiations, and includes mechanisms for
handling deadlocks and timeouts.
Key Components:
1. Proposal: Structured offer from one agent to others
2. Counter-Proposal: Modified proposal in response to original
3. Utility Function: Evaluates value of proposals for agent
4. Concession Strategy: Determines how much to compromise
5. Negotiation Protocol: Rules governing negotiation process
6. Agreement: Final mutually accepted terms
7. Mediation: Optional third-party facilitation
Negotiation Types:
1. Distributive: Fixed resources, competitive
2. Integrative: Value creation, cooperative
3. Multi-issue: Multiple attributes to negotiate
4. Sequential: Issues addressed one at a time
5. Package: All issues negotiated together
Strategies:
1. Conceding: Gradually reduce demands
2. Hardball: Maintain firm position
3. Tit-for-tat: Mirror opponent's behavior
4. Time-based: Adjust based on deadline
5. Information-based: Learn and adapt
Use Cases:
- Resource allocation in multi-agent systems
- Task assignment and coordination
- Conflict resolution between agents
- Service level agreements
- Coalition formation
- Price negotiation in marketplaces
- Scheduling and planning coordination
- Policy consensus building
Advantages:
- Flexible conflict resolution
- Fair resource distribution
- Autonomous decision-making
- Adaptable to agent preferences
- Handles diverse objectives
- Enables cooperation
- Supports complex agreements
Challenges:
- Computational complexity
- Strategic manipulation
- Information asymmetry
- Deadlock situations
- Time constraints
- Preference elicitation
- Trust issues
LangChain Implementation:
This implementation uses LangChain for:
- LLM-based proposal generation and evaluation
- Utility calculation with reasoning
- Counter-proposal generation
- Agreement evaluation
- Negotiation strategy implementation
Production Considerations:
- Set negotiation timeouts and round limits
- Implement deadlock breaking mechanisms
- Log all negotiation steps for transparency
- Validate proposals against constraints
- Handle malformed proposals gracefully
- Monitor for strategic manipulation
- Implement fair mediation when needed
- Consider privacy of agent preferences
"""
Purpose: This module implements the Negotiation Protocol pattern, providing a structured approach to agent design and implementation.
Import Statements
Standard Library Imports
import dataclasses.dataclass
import dataclasses.field
import datetime.datetime
import enum.Enum
import langchain_core.output_parsers.StrOutputParser
import langchain_core.prompts.ChatPromptTemplate
import os
import typing.Any
import typing.Dict
import typing.List
import typing.Optional
import typing.Tuple
Purpose: Core Python functionality:
– dataclasses.dataclass: Simplified class definitions with @dataclass
– dataclasses.field: Simplified class definitions with @dataclass
– datetime.datetime: Date and time handling
– enum.Enum: Enumeration support
– langchain_core.output_parsers.StrOutputParser: Regular expression operations
– langchain_core.prompts.ChatPromptTemplate: Regular expression operations
– os: Environment variable access and system operations
– typing.Any: Type hints for better code documentation (List, Dict, Any, Optional)
– typing.Dict: Type hints for better code documentation (List, Dict, Any, Optional)
– typing.List: Type hints for better code documentation (List, Dict, Any, Optional)
– typing.Optional: Type hints for better code documentation (List, Dict, Any, Optional)
– typing.Tuple: Type hints for better code documentation (List, Dict, Any, Optional)
LangChain Imports
from langchain_openai.ChatOpenAI
Purpose: LangChain framework components: – LLM Interface: Wrapper for OpenAI models – Chains: Composable building blocks for complex workflows
Other Imports
from dotenv.load_dotenv
Purpose: Additional dependencies for specialized functionality.
Classes
Class: ProposalStatus
Lines 104-110
Inherits from: Enum
Purpose:
Status of a negotiation proposal.
Class: NegotiationOutcome
Lines 113-118
Inherits from: Enum
Purpose:
Outcome of a negotiation.
Class: Proposal
Lines 122-147
Purpose:
Represents a proposal in a negotiation.
Attributes:
proposal_id: Unique identifier
proposer: Agent making the proposal
recipient: Agent(s) receiving proposal
round_number: Negotiation round
terms: Dictionary of proposed terms
utility: Utility value for proposer
status: Current status of proposal
timestamp: When proposal was made
justification: Reasoning for proposal
metadata: Additional information
Class: NegotiationState
Lines 151-178
Purpose:
Tracks the state of an ongoing negotiation.
Attributes:
negotiation_id: Unique identifier
participants: Agents involved
issue: What is being negotiated
proposals: History of proposals
current_round: Current round number
max_rounds: Maximum allowed rounds
start_time: When negotiation started
end_time: When negotiation ended
outcome: Final outcome
agreement: Final agreed terms
metadata: Additional information
Class: NegotiationAgent
Lines 181-424
Purpose:
Agent that participates in negotiations.
This agent can generate proposals, evaluate offers, make counter-proposals,
and decide when to accept or reject proposals based on its utility function
and negotiation strategy.
Methods:
__init__(self, name, preferences, reservation_utility, concession_rate, temperature)
Lines 190-213
Purpose:
Initialize negotiation agent.
Args:
name: Agent's name
preferences: Agent's preferences for negotiation issues
reservation_utility: Minimum acceptable utility (walk-away point)
concession_rate: How quickly agent makes concessions
temperature: LLM temperature for generation
Parameters:
– name: Name
– preferences: Preferences
– reservation_utility: Reservation utility
– concession_rate: Concession rate
– temperature: Sampling temperature (0-1)
calculate_utility(self, terms)
Lines 215-246
Purpose:
Calculate utility of proposed terms.
Args:
terms: Proposed terms
Returns:
Utility value between 0 and 1
Parameters:
– terms: Terms
Returns: float
generate_initial_proposal(self, issue, recipient, round_number)
Lines 248-305
Purpose:
Generate initial proposal.
Args:
issue: Issue being negotiated
recipient: Recipient agent
round_number: Round number
Returns:
Initial proposal
Parameters:
– issue: Issue
– recipient: Recipient
– round_number: Round number
Returns: Proposal
evaluate_proposal(self, proposal, current_round)
Lines 307-355
Purpose:
Evaluate whether to accept a proposal.
Args:
proposal: Proposal to evaluate
current_round: Current negotiation round
Returns:
Tuple of (accept: bool, reasoning: str)
Parameters:
– proposal: Proposal
– current_round: Current round
Returns: <ast.Subscript object at 0x000001A06D658650>
generate_counter_proposal(self, previous_proposal, issue, round_number)
Lines 357-424
Purpose:
Generate counter-proposal.
Args:
previous_proposal: Proposal to counter
issue: Issue being negotiated
round_number: Current round number
Returns:
Counter-proposal
Parameters:
– previous_proposal: Previous proposal
– issue: Issue
– round_number: Round number
Returns: Proposal
Class: NegotiationMediator
Lines 427-584
Purpose:
Mediator that facilitates negotiation between agents.
The mediator manages the negotiation protocol, enforces rules,
tracks state, and can suggest compromises when negotiations stall.
Methods:
__init__(self, temperature)
Lines 435-443
Purpose:
Initialize negotiation mediator.
Args:
temperature: LLM temperature for generation
Parameters:
– temperature: Sampling temperature (0-1)
start_negotiation(self, negotiation_id, participants, issue, max_rounds)
Lines 445-471
Purpose:
Start a new negotiation.
Args:
negotiation_id: Unique identifier
participants: List of participating agents
issue: Issue being negotiated
max_rounds: Maximum rounds allowed
Returns:
Initial negotiation state
Parameters:
– negotiation_id: Negotiation id
– participants: Participants
– issue: Issue
– max_rounds: Max rounds
Returns: NegotiationState
process_round(self, negotiation_id, proposal, response_accept, response_reasoning, counter_proposal)
Lines 473-521
Purpose:
Process a negotiation round.
Args:
negotiation_id: Negotiation identifier
proposal: Current proposal
response_accept: Whether proposal was accepted
response_reasoning: Reasoning for response
counter_proposal: Counter-proposal if not accepted
Returns:
Updated negotiation state
Parameters:
– negotiation_id: Negotiation id
– proposal: Proposal
– response_accept: Response accept
– response_reasoning: Response reasoning
– counter_proposal: Counter proposal
Returns: NegotiationState
suggest_compromise(self, negotiation_id)
Lines 523-549
Purpose:
Suggest compromise when negotiation stalls.
Args:
negotiation_id: Negotiation identifier
Returns:
Suggested compromise terms or None
Parameters:
– negotiation_id: Negotiation id
Returns: <ast.Subscript object at 0x000001A06D7FF2D0>
get_negotiation_summary(self, negotiation_id)
Lines 551-584
Purpose:
Generate summary of negotiation.
Args:
negotiation_id: Negotiation identifier
Returns:
Summary text
Parameters:
– negotiation_id: Negotiation id
Returns: str
Functions
demonstrate_negotiation_protocol()
Lines 587-945
Purpose:
Demonstrate negotiation protocol pattern.
Key Concepts Summary
Pattern Overview
The Negotiation Protocol pattern provides a structured approach to building AI agents with specific capabilities and behaviors. This implementation leverages LangChain’s composable components to create a robust and maintainable solution.
Core Components
- Agent Class: Main implementation of the pattern logic
- Prompt Engineering: Carefully crafted prompts to guide LLM behavior
- Tool Integration: Optional tools for extended functionality
- Memory Management: State handling across interactions
- Output Parsing: Structured response processing
When to Use
✅ Good for: – Scenarios requiring this specific pattern’s strengths – Applications needing structured agent behavior – Systems where explainability is important – Production environments requiring reliability
❌ Not ideal for: – Simple single-prompt tasks – Scenarios requiring different pattern characteristics – Resource-constrained environments (if computationally intensive)
Best Practices Demonstrated
1. Environment Configuration
- ✅ Use
.envfiles for API keys - ✅ Never hardcode credentials
- ✅ Validate environment variables on startup
2. Error Handling
- ✅ Graceful degradation on failures
- ✅ Informative error messages
- ✅ Logging for debugging
3. Prompt Engineering
- ✅ Clear, specific instructions
- ✅ Structured output formats
- ✅ Few-shot examples when beneficial
4. Code Organization
- ✅ Separation of concerns (class-based design)
- ✅ Type hints for clarity
- ✅ Docstrings for documentation
5. Performance Optimization
- ✅ Appropriate temperature settings
- ✅ Token budget management
- ✅ Caching where applicable
LangChain Components Used
- ChatOpenAI: OpenAI model interface
- Chains: Sequential component composition
Why LangChain? – Composable abstractions for LLM applications – Rich ecosystem of integrations – Production-ready patterns – Active community and development
Execution Flow
1. Initialize agent with configuration
↓
2. Load environment and dependencies
↓
3. Prepare prompt with input data
↓
4. Invoke LLM through LangChain
↓
5. Parse and validate response
↓
6. Return formatted result
Key Decision Points: – Model selection (temperature, model type) – Prompt structure (few-shot vs zero-shot) – Error handling strategy – Output format validation
Performance Considerations
Computational Cost
- LLM Calls: Primary cost factor (based on tokens)
- Latency: Depends on model and input size
- Memory: Minimal for most patterns
Optimization Strategies
- Prompt Optimization: Minimize tokens while maintaining clarity
- Caching: Cache repeated queries/responses
- Model Selection: Use smaller models when appropriate
- Batch Processing: Group similar requests
- Streaming: For long responses, stream tokens
Cost Estimates (Approximate)
- GPT-3.5-turbo: ~$0.002 per request
- GPT-4: ~$0.03 per request
- GPT-4-turbo: ~$0.01 per request
Actual costs vary based on input/output token counts
Common Pitfalls to Avoid
1. Prompt Design Issues
❌ Vague or ambiguous instructions
✅ Clear, specific, testable prompts
2. Error Handling
❌ Ignoring API errors or rate limits
✅ Implement retry logic and graceful degradation
3. Token Management
❌ Exceeding model token limits
✅ Truncate inputs, chunk processing
4. Security
❌ Exposing API keys in code
✅ Use environment variables
5. Testing
❌ Only testing happy paths
✅ Test edge cases, failures, malformed inputs
6. Cost Management
❌ Unmonitored API usage
✅ Implement budgets, alerts, and monitoring
Further Improvements
Production Enhancements
- Monitoring: Add logging, metrics, tracing
- Validation: Input/output schema validation
- Rate Limiting: Implement request throttling
- Caching: Redis or in-memory cache for responses
- Async Support: Non-blocking operations for scalability
Feature Extensions
- Multi-Model Support: Switch between providers
- Fallback Models: Graceful degradation strategy
- Custom Tools: Domain-specific function calling
- Advanced Memory: Vector stores for long-term memory
- Evaluation: Automated testing and benchmarking
Code Quality
- Testing: Unit tests, integration tests
- Documentation: API docs, usage examples
- Type Safety: Stricter type hints
- Configuration: Externalized config files
Related Patterns
- Pattern 098
- Pattern 100
Pattern Families: – Reasoning Patterns (001-011) – Multi-Agent Patterns (012-019) – Tool Use Patterns (020-025) – Memory Patterns (026-031) – And many more…
Summary
The Negotiation Protocol pattern provides a powerful approach to building AI agents with specific capabilities. This implementation demonstrates:
✅ Key Strengths: – Clean, maintainable code structure – LangChain integration for composability – Type hints and documentation – Production-ready patterns
🎯 Best Use Cases: – Applications requiring this pattern’s characteristics – Systems needing structured agent behavior – Production environments requiring reliability
📚 Learning Resources: – LangChain Documentation: https://python.langchain.com/ – Pattern Documentation: See related patterns – OpenAI API Reference: https://platform.openai.com/docs/
File: 099_negotiation_protocol.py
Total Lines: 950
Pattern Category: AI Agent Design Pattern #099
Generated from actual implementation code
For educational and reference purposes