Ultimatum¶
Game ID: ultimatum
Two agents (proposer A, responder B) negotiate how to split a resource R (default $100).
Why This Game Matters for Multi-Agent Systems¶
The ultimatum game models a fundamental coordination problem: how two agents divide shared value when each has private information about their own costs or minimum requirements, and failure to agree means both walk away with nothing. Here are concrete scenarios where this game arises between AI agents:
Revenue Sharing After Joint Task Completion — Two agents collaborated on a task (e.g., one researched, the other synthesized a report) and earned a shared reward from the orchestrator. They must now agree on how to split the payout. Each agent has a private cost for the effort it invested (its reservation value). If they can't agree before the orchestrator's timeout, the reward is forfeited.
Service-Level Agreement Negotiation — An orchestrator agent needs a specialist's capability (translation, code review, data enrichment). They negotiate the terms: how much the orchestrator pays vs. how much surplus the specialist keeps. The orchestrator has a private maximum willingness-to-pay; the specialist has a private minimum acceptable fee. Alternating offers with a deadline — if unresolved, the orchestrator moves on to another provider and both lose the deal.
Resource Partition Between Peer Agents — Two peer agents share a fixed pool of resources (API quota, context window tokens, compute time) allocated to their team by a coordinator. They must negotiate the split. Each has a private minimum requirement below which their own tasks would fail. If they deadlock past the allocation window, the resources go unassigned and both get nothing.
Sub-task Budget Negotiation in Pipelines — A pipeline orchestrator has a fixed budget for a two-stage task and contracts two specialist agents, one per stage. The specialists negotiate how to divide the budget between them: upstream work (data collection) vs. downstream work (analysis). Each has private costs. If they can't agree, the orchestrator cancels the pipeline.
Abstract Scenario¶
Two agents must divide a shared resource of known total value. Each agent has a private reservation value — the minimum share that makes the deal worthwhile. Agents alternate proposals and responses (accept, reject, counter-offer). On agreement, each agent's payoff is their share minus their reservation value. If no agreement is reached before the deadline, both agents receive nothing.
Rules¶
- Each agent has a private reservation value
vdrawn uniformly from [0, R/2]. - Agents take turns making offers, accepting, or rejecting.
- Payoff on agreement:
u = x - v, wherexis the agent's share. - If no agreement is reached within the round limit, both get utility 0.
Actions¶
| Action | Payload | Description |
|---|---|---|
submit_offer |
{"my_share": 60} |
Propose a split: you get my_share, they get total - my_share. Must be between 0 and total. |
accept |
{} |
Accept the other agent's offer (ends the match). Cannot accept your own offer. |
reject |
{} |
Reject the current offer and pass the turn. |
pass |
{} |
Hand the turn to the other agent. |
message_only |
{} |
Send messages without advancing the turn. |
Visible game state¶
Each agent sees:
{
"total": 100,
"current_offer": 60, # current offer on the table (or None)
"last_offer_by": "alice", # who made the current offer
"my_reservation_value": 12.5, # your private value (opponent cannot see this)
"action_history": [...] # full history of offers, accepts, rejects
}
Turn order¶
The turn order is configurable:
| Mode | Behavior |
|---|---|
ROUND_ROBIN (default) |
Strict turn order: A, B, C, A, B, C... Out-of-turn actions are rejected. |
RANDOM |
Runner picks a random agent each turn. All agents have is_my_turn=True (no turn enforcement). |
Configuration¶
from neg_env import UltimatumGame
from neg_env.spec import TurnOrder
# Defaults
game = UltimatumGame() # total=100, max_rounds=10
# Custom
game = UltimatumGame(total=200, max_rounds=20)
# Fixed reservation values (for controlled experiments)
game = UltimatumGame(
total=100,
reservation_values={"alice": 10, "bob": 30},
)
# Random turn order
game = UltimatumGame(total=100, turn_order=TurnOrder.RANDOM)
| Parameter | Type | Default | Description |
|---|---|---|---|
total |
int |
100 |
Total resource to split. |
max_rounds |
int |
10 |
Maximum rounds before timeout (everyone gets 0). |
reservation_max |
float \| None |
None |
Max reservation value. If None, defaults to total. |
reservation_values |
dict[str, float] \| None |
None |
Fixed reservation values per agent. If None, drawn uniformly from [0, reservation_max]. |
turn_order |
TurnOrder |
ROUND_ROBIN |
Turn order mode: ROUND_ROBIN or RANDOM. |
Outcome format¶
{
"payoffs": [
{"agent_id": "alice", "share": 60, "utility": 47.5},
{"agent_id": "bob", "share": 40, "utility": 27.5},
],
"reason": "agreement", # or "max_rounds_exceeded"
"split": [60, 40],
}
Prompts¶
Two built-in system prompts for LLM agents:
SYSTEM_PROMPT_FAIR— cooperative: aims for a fair split, prefers deals over deadlock.SYSTEM_PROMPT_UNFAIR— strategic: maximizes payoff, uses anchoring, calibrated concessions, deadline pressure.