By Sagar Shankaran, Founder of CallSphere
Learn how to implement negotiation protocols for AI agents including offer-counteroffer patterns, compromise strategies, and deadlock resolution. Build agents that autonomously reach mutually acceptable outcomes.
Key takeaways
In multi-agent systems, agents frequently have competing objectives. A cost-optimization agent wants to minimize spending. A quality agent wants the best possible output. A deadline agent wants the fastest completion. These agents cannot all get what they want simultaneously — they need a structured negotiation protocol to find acceptable tradeoffs.
Negotiation protocols formalize how agents propose, counter, and accept solutions. Without them, you end up with brittle priority hierarchies where one agent always overrides others, losing the benefit of multi-agent reasoning.
The most common negotiation pattern mirrors human bargaining. One agent proposes an offer, the other evaluates it against its own utility function, and either accepts or responds with a counteroffer. Rounds continue until agreement or a deadline.
flowchart TD
INPUT(["Task input"])
SUPER["Supervisor agent<br/>plans plus monitors"]
W1["Worker 1<br/>research"]
W2["Worker 2<br/>code"]
W3["Worker 3<br/>writing"]
CRITIC{"Output meets<br/>rubric?"}
REWORK["Rework or<br/>retry path"]
SHARED[("Shared scratchpad<br/>and memory")]
OUT(["Final result"])
INPUT --> SUPER
SUPER --> W1 --> CRITIC
SUPER --> W2 --> CRITIC
SUPER --> W3 --> CRITIC
W1 --> SHARED
W2 --> SHARED
W3 --> SHARED
SHARED --> SUPER
CRITIC -->|Pass| OUT
CRITIC -->|Fail| REWORK --> SUPER
style SUPER fill:#4f46e5,stroke:#4338ca,color:#fff
style CRITIC fill:#f59e0b,stroke:#d97706,color:#1f2937
style OUT fill:#059669,stroke:#047857,color:#fff
style SHARED fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
from dataclasses import dataclass, field
from enum import Enum
from typing import Any
class NegotiationStatus(Enum):
PENDING = "pending"
ACCEPTED = "accepted"
REJECTED = "rejected"
DEADLOCKED = "deadlocked"
@dataclass
class Proposal:
round_num: int
proposer: str
terms: dict[str, float]
utility_score: float
@dataclass
class NegotiationAgent:
agent_id: str
preferences: dict[str, float] # ideal values
weights: dict[str, float] # importance per dimension
min_acceptable_utility: float = 0.5
concession_rate: float = 0.1
def evaluate_utility(self, terms: dict[str, float]) -> float:
total = 0.0
for key, weight in self.weights.items():
ideal = self.preferences[key]
actual = terms.get(key, 0)
distance = abs(ideal - actual) / max(abs(ideal), 1)
total += weight * (1 - distance)
return max(0.0, min(1.0, total))
def make_counteroffer(
self, received: Proposal, round_num: int
) -> Proposal:
new_terms = {}
concession = self.concession_rate * round_num
for key in self.preferences:
ideal = self.preferences[key]
their_value = received.terms.get(key, ideal)
new_terms[key] = ideal + concession * (their_value - ideal)
return Proposal(
round_num=round_num,
proposer=self.agent_id,
terms=new_terms,
utility_score=self.evaluate_utility(new_terms),
)
Each agent has a utility function that scores any proposal on a 0-to-1 scale, a minimum acceptable utility below which it will not agree, and a concession rate that controls how quickly it moves toward the other party's position.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
class NegotiationProtocol:
def __init__(
self, max_rounds: int = 10, convergence_threshold: float = 0.02
):
self.max_rounds = max_rounds
self.convergence_threshold = convergence_threshold
self.history: list[Proposal] = []
def run(
self, agent_a: NegotiationAgent, agent_b: NegotiationAgent
) -> dict[str, Any]:
# Agent A makes the opening offer based on its preferences
current = Proposal(
round_num=0,
proposer=agent_a.agent_id,
terms=dict(agent_a.preferences),
utility_score=1.0,
)
self.history.append(current)
for round_num in range(1, self.max_rounds + 1):
responder = agent_b if current.proposer == agent_a.agent_id else agent_a
utility = responder.evaluate_utility(current.terms)
if utility >= responder.min_acceptable_utility:
return {
"status": NegotiationStatus.ACCEPTED,
"final_terms": current.terms,
"rounds": round_num,
"utility_a": agent_a.evaluate_utility(current.terms),
"utility_b": agent_b.evaluate_utility(current.terms),
}
counter = responder.make_counteroffer(current, round_num)
self.history.append(counter)
if (len(self.history) >= 2 and
self._proposals_converged(self.history[-1], self.history[-2])):
return self._find_midpoint(agent_a, agent_b, round_num)
current = counter
return {"status": NegotiationStatus.DEADLOCKED, "rounds": self.max_rounds}
def _proposals_converged(self, p1: Proposal, p2: Proposal) -> bool:
diffs = [abs(p1.terms[k] - p2.terms.get(k, 0)) for k in p1.terms]
return max(diffs) < self.convergence_threshold
def _find_midpoint(self, a, b, round_num):
last_a = [p for p in self.history if p.proposer == a.agent_id][-1]
last_b = [p for p in self.history if p.proposer == b.agent_id][-1]
midpoint = {
k: (last_a.terms[k] + last_b.terms.get(k, 0)) / 2
for k in last_a.terms
}
return {
"status": NegotiationStatus.ACCEPTED,
"final_terms": midpoint,
"rounds": round_num,
"resolution": "convergence_midpoint",
}
When agents cannot reach agreement within the round limit, you need a fallback. Three common strategies work well in practice.
Mediator agent: A third agent with no stake in the outcome evaluates both positions and imposes a compromise. This works well when you have a supervisor agent in your hierarchy.
BATNA fallback: Each agent has a Best Alternative To Negotiated Agreement — a default outcome it falls back to if negotiation fails. The system picks whichever BATNA produces higher combined utility.
Progressive concession: Force both agents to increase their concession rates each round, guaranteeing eventual convergence. This sacrifices agent autonomy but prevents infinite loops.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
def resolve_deadlock(
agent_a: NegotiationAgent,
agent_b: NegotiationAgent,
batna_a: dict[str, float],
batna_b: dict[str, float],
) -> dict:
utility_batna_a = (
agent_a.evaluate_utility(batna_a) + agent_b.evaluate_utility(batna_a)
)
utility_batna_b = (
agent_a.evaluate_utility(batna_b) + agent_b.evaluate_utility(batna_b)
)
best_batna = batna_a if utility_batna_a >= utility_batna_b else batna_b
return {"resolution": "batna_fallback", "terms": best_batna}
A common real-world use case is allocating compute budget between a fast-but-cheap model and a slow-but-accurate model. The speed agent negotiates for more fast-model calls, the quality agent pushes for the expensive model, and the negotiation protocol finds the optimal split given your total budget.
Always set a max_rounds limit and a deadlock resolution strategy. In production systems, also add a wall-clock timeout. Most negotiations converge within 5-8 rounds if concession rates are set between 0.05 and 0.15.
Yes, but with care. Have each LLM agent output structured JSON with its proposal and reasoning, then validate the output against constraints before passing it to the counterparty. The risk is that LLMs may not concede rationally — they can oscillate or suddenly agree to poor terms. Hybrid approaches that use LLMs for creative proposal generation but utility functions for acceptance decisions tend to work best.
Priority systems impose a fixed hierarchy — quality always beats cost, or vice versa. Negotiation finds different tradeoffs depending on the specific situation. A 2% quality drop that saves 40% cost might be acceptable, while a 15% quality drop for the same savings is not. Negotiation captures these nonlinear tradeoffs naturally.
#AgentNegotiation #MultiAgentSystems #ProtocolDesign #AICoordination #Python #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
Five proven multi-agent architecture patterns built on A2A — orchestrator, peer mesh, hub-and-spoke, marketplace, and tiered specialist.
Langgraph multi-agent supervisor handoffs docs: the supervisor pattern in LangGraph for coordinating specialist agents, with full code, an eval pipeline that scores routing accuracy, and the failure modes to watch for.
Handoffs done right — when one agent should hand control to another, how to preserve context, and how to evaluate the handoff decision itself.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco