Negotiation Protocols Between Autonomous Agents
Most A2A discussion assumes a clean delegation — one agent asks, another does it. Real cross-agent interactions often need actual negotiation, when the first request isn't something the receiving agent can fully satisfy as asked.
The A2A posts earlier in this series mostly assumed a clean handoff: an agent delegates a well-formed task, the receiving agent either can or can’t do it. Real interactions between autonomous agents are often messier — a receiving agent can do part of what’s asked, or can do it under different terms than requested (a different timeline, a different scope, a different cost), and the interaction needs an actual negotiation protocol, not just an accept/reject binary.
Why This Needs Structure, Not Free-Form Back-and-Forth
flowchart TD
A[Delegation request] --> B{Receiving agent's response}
B -->|Full accept| C[Proceed as requested]
B -->|Full reject| D[Delegating agent tries elsewhere]
B -->|Counter-proposal| E[Negotiation needed]
E --> F{Bounded number of rounds?}
F -->|Yes| G[Converges to accept/reject/timeout]
F -->|No, unbounded| H[Risk: agents loop indefinitely refining terms]
Two agents negotiating in unstructured natural language, with no bound on how many rounds of back-and-forth can occur, risks the same unproductive-loop failure mode covered in the very first post of this blog’s Block A — except now happening between two separate autonomous systems, potentially owned by different organizations, where nobody’s watching the exchange in real time the way a human might notice a stuck internal agent.
A Structured Counter-Proposal Format
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@dataclass
class DelegationProposal:
task: dict
terms: dict # e.g., {"deadline": ..., "cost_ceiling": ..., "scope": ...}
@dataclass
class CounterProposal:
original_proposal_id: str
modified_terms: dict
reasoning: str # why the original terms couldn't be met as-is
round_number: int
MAX_NEGOTIATION_ROUNDS = 3
def negotiate(initial_proposal: DelegationProposal, target_agent) -> dict:
current = initial_proposal
for round_num in range(MAX_NEGOTIATION_ROUNDS):
response = target_agent.evaluate_proposal(current)
if response.status in ("accept", "reject"):
return {"outcome": response.status, "final_terms": response.terms}
current = apply_counter_proposal(current, response.counter_proposal)
return {"outcome": "negotiation_timeout", "last_terms_offered": current.terms}
The hard round cap is what prevents unbounded negotiation loops — after MAX_NEGOTIATION_ROUNDS, the exchange resolves to a definite outcome (accept, reject, or timeout) rather than continuing indefinitely, the same structural safeguard as the iteration limits covered for single-agent loops much earlier in this blog, applied to a two-agent exchange instead.
Terms That Should Never Be Auto-Negotiated
Referencing the escalation design and policy-based triggers covered earlier in this series — certain terms (cost ceiling above a threshold, scope touching sensitive data, anything crossing an organizational trust boundary) shouldn’t be autonomously negotiable even within the bounded-rounds structure; a counter-proposal touching one of these should escalate to human review rather than being resolved by the two agents alone, regardless of how reasonable the counter-terms look.
1
2
3
4
5
def requires_human_approval(counter_proposal: CounterProposal) -> bool:
return (
counter_proposal.modified_terms.get("cost_ceiling", 0) > AUTO_APPROVE_COST_THRESHOLD
or counter_proposal.modified_terms.get("scope_expansion", False)
)
Log Negotiation History as Its Own Audit Trail
A negotiated outcome, especially one crossing an organizational boundary, benefits from a complete record of what was proposed and countered at each round — not just the final agreed terms — since a later dispute about what was actually agreed to needs the full exchange, not just the resolved endpoint.
Key Takeaways
- Real cross-agent interactions often need negotiation, not just a clean accept/reject binary — most A2A discussion undersells this
- Unstructured, unbounded negotiation risks the same unproductive-loop failure as single-agent systems, now between autonomous systems nobody’s watching in real time
- A hard round cap resolves every negotiation to a definite outcome — accept, reject, or timeout — rather than continuing indefinitely
- High-stakes terms should escalate to human approval rather than being auto-negotiable, even within the bounded structure
Part of the Agent Infrastructure series — the plumbing layer underneath production agentic systems.