Provision Point¶
Game ID: provision-point
A coordinator proposes a collective project that no single agent can afford. Contributors each decide how much to commit. If total commitments reach the threshold, the project is funded; otherwise all commitments are refunded.
The game supports two modes:
- Full (
mode: full, default): A coordinator announces the project, contributors signal intent, then commit. Payoff when funded = valuation - commitment. - Simple (
mode: simple): No coordinator — all agents are equal contributors. Single commit phase. Payoff when funded = reservation value. Payoff when not funded = -commitment.
Why This Game Matters for Multi-Agent Systems¶
Provision point mechanisms model the most common collective action problem in agent systems: pooling resources toward a shared goal under private information.
Shared Resource Pooling — Ten specialist agents need access to an expensive API. Individually none can afford it. A coordinator announces: "if 7+ agents commit 15 tokens each, I'll purchase group access. Otherwise everyone gets their tokens back."
Collective Compute Purchase — Research agents need a GPU cluster for a shared simulation. No single agent has enough budget. A coordinator runs a pooling round: threshold = total cost, contributions proportional to usage share.
Infrastructure Upgrade — Agents share a tool with a new version requiring an upgrade fee. The upgrade is only worthwhile if enough agents adopt it (network effect). If 80% commit, the upgrade proceeds; otherwise no upgrade.
Quorum-Gated Actions — Before a fleet takes an irreversible action (deploy, delete, purchase), a quorum of approval agents must commit authorization. This is provision-point as a safety mechanism.
Abstract Scenario (Full mode)¶
A COORDINATOR agent announces a collective project: what the pool will fund, the total cost (threshold), and what each contributor receives in return. CONTRIBUTORS may SIGNAL non-binding interest to help others coordinate (cheap talk). Contributors then submit BINDING COMMITMENTS (amounts can differ per agent). At deadline: if sum(commitments) >= threshold, the project is funded; otherwise all commitments are refunded.
Abstract Scenario (Simple mode)¶
All agents are equal CONTRIBUTORS — there is no coordinator. Each contributor has a private reservation value representing their payoff if funded. Contributors take turns submitting BINDING COMMITMENTS. When all have committed (or the round limit is reached): if sum(commitments) >= threshold, each contributor receives their reservation value. Otherwise, each contributor loses their commitment.
Rules¶
Full mode (default)¶
- 2+ agents required: coordinator (index 0) + 1 or more contributors (indices 1, 2, …).
- 3 phases: announce → signal → commit.
- The coordinator participates in the turn rotation during signal and commit phases (can send messages and pass).
- The coordinator announces the project with a description, threshold, and return description.
- Contributors signal non-binding intent during the signal phase (cheap talk).
- Contributors submit binding commitments during the commit phase. Amount must be <= endowment.
- If
allow_commitment_updatesis true, contributors can revise or withdraw commitments. - Resolution: when the commit phase ends, if total commitments >= threshold the project is funded.
- Payoffs (funded): contributor =
valuation - commitment, coordinator = 0. Non-committers get 0. - Payoffs (not funded): all commitments refunded, everyone gets 0.
Simple mode¶
- 2+ agents required: all agents are equal contributors (no coordinator).
- 1 phase: commit (up to
max_rounds_commitrounds). - Each agent has a private reservation value.
- Each agent commits once — no updates or withdrawals.
- Resolution triggers: all agents have committed, threshold met early, or round limit reached.
- Payoffs (funded, total >= threshold): each contributor's utility = their reservation value.
- Payoffs (not funded, total < threshold): each contributor's utility = -(their commitment). Agents who didn't commit get 0.
Actions¶
Full mode¶
| Action | Payload | Phase | Role | Description |
|---|---|---|---|---|
announce_project |
{description, threshold, return_description} |
announce | coordinator | Describe the project and set the funding threshold. |
signal_intent |
{approximate_amount, my_valuation?} |
signal | contributor | Non-binding signal of intended commitment. Optional my_valuation (auto mode) privately sets your valuation. |
submit_commitment |
{amount, my_valuation?} |
commit | contributor | Submit a binding commitment. Amount <= endowment. Optional my_valuation (auto mode) privately sets your valuation. |
update_commitment |
{new_amount} |
commit | contributor | Revise an existing commitment (if updates allowed). |
withdraw_commitment |
{} |
commit | contributor | Remove your commitment entirely. |
pass |
{} |
signal, commit | any | Pass your turn without acting. |
message_only |
{} |
any | any | Send messages without advancing the turn. |
Simple mode¶
| Action | Payload | Phase | Role | Description |
|---|---|---|---|---|
submit_commitment |
{amount} |
commit | contributor | Submit a binding commitment. One per agent. |
pass |
{} |
commit | contributor | Pass your turn without acting. |
message_only |
{} |
commit | contributor | Send messages without advancing the turn. |
Visible game state¶
Full mode¶
Contributors see their own private valuation (if set); commitments and signals are visible to all. All agents see the full participant list:
{
"num_agents": 3,
"agent_ids": ["agent_1", "agent_2", "agent_3"],
"coordinator": "agent_1",
"contributors": ["agent_2", "agent_3"],
"my_role": "contributor", # "coordinator" or "contributor"
"project_spec": {
"description": "Shared GPU cluster for simulation",
"threshold": 100,
"return_description": "Proportional compute access",
},
"signals": {"agent_2": 45, "agent_3": 40},
"commitments": {"agent_2": 50, "agent_3": 55},
"total_committed": 105,
"threshold": 100,
"endowment": 80,
"my_valuation": 120.0, # private — only you see this
"valuation_mode": "random", # "random", "fixed", or "auto"
"funded": None, # set at resolution
"action_history": [...],
}
Simple mode¶
{
"num_agents": 2,
"agent_ids": ["agent_1", "agent_2"],
"my_role": "contributor",
"commitments": {"agent_1": 55},
"total_committed": 55,
"threshold": 100,
"my_reservation_value": 70.0, # private — only you see yours
"funded": None, # set at resolution
"action_history": [...],
}
Valuation modes¶
Each contributor has a private valuation representing how much the resource is worth to them. Three modes control how valuations are assigned:
| Mode | Description |
|---|---|
"random" (default) |
Valuations drawn uniformly from valuation_range at match start. |
"fixed" |
Valuations set explicitly via valuations dict. For controlled experiments. |
"auto" |
No pre-assigned valuations. Contributors privately set their valuation by including my_valuation in signal_intent or submit_commitment payloads. The value is private — other agents cannot see it, and it never appears in action history. |
In auto mode, contributors who never declare a valuation default to 0 at resolution.
Configuration¶
from neg_env import ProvisionPointGame
# Full mode — random valuations (default)
game = ProvisionPointGame() # threshold=100, endowment=80
# Full mode — custom parameters
game = ProvisionPointGame(
threshold=200,
endowment=120,
valuation_range=(0, 250),
allow_commitment_updates=False,
)
# Full mode — fixed valuations (for controlled experiments)
game = ProvisionPointGame(
threshold=100,
valuations={"c1": 80, "c2": 120},
)
# Full mode — auto valuations (contributors decide for themselves)
game = ProvisionPointGame(
threshold=100,
valuation_mode="auto",
)
# Simple mode — no coordinator, single commit phase
game = ProvisionPointGame(
mode="simple",
threshold=100,
valuation_mode="fixed",
valuations={"agent_1": 70, "agent_2": 70},
max_rounds_commit=100,
)
| Parameter | Type | Default | Description |
|---|---|---|---|
mode |
str |
"full" |
"full" (coordinator + announce/signal/commit) or "simple" (no coordinator, commit only). |
threshold |
float |
100 |
Minimum total commitment to fund the project. |
endowment |
float |
80 |
Maximum amount each contributor can commit. Full mode only. |
valuation_mode |
str |
"random" |
"random", "fixed", or "auto". |
valuations |
dict |
None |
Fixed valuations per agent. Required when valuation_mode="fixed". |
max_rounds_commit |
int |
6 |
Max rounds in commit phase before auto-resolution. |
Hydra YAML configuration¶
# conf/game/provision_point.yaml (full mode)
game_id: provision-point
mode: full
threshold: 100
endowment: 100
valuation_mode: fixed
valuations:
agent_1: 0 # coordinator (valuation ignored)
agent_2: 50
turn_order: round_robin
max_rounds_announce: 2
max_rounds_signal: 3
max_rounds_commit: 6
# conf/game/provision_point.yaml (simple mode)
game_id: provision-point
mode: simple
threshold: 100
valuation_mode: fixed
valuations:
agent_1: 70
agent_2: 70
max_rounds_commit: 100
turn_order: round_robin
When using valuation_mode: fixed, the valuation keys must match the actual agent_id values set in your scenario or config.
Outcome format¶
Full mode¶
# Project funded
{
"payoffs": [
{"agent_id": "coordinator", "utility": 0.0},
{"agent_id": "c1", "utility": 30.0}, # valuation(80) - commitment(50)
{"agent_id": "c2", "utility": 60.0}, # valuation(120) - commitment(60)
],
"trigger": "threshold_met",
"total_committed": 110,
"threshold": 100,
}
# Threshold not reached (all refunded)
{
"payoffs": [
{"agent_id": "coordinator", "utility": 0.0},
{"agent_id": "c1", "utility": 0.0},
{"agent_id": "c2", "utility": 0.0},
],
"trigger": "rounds_exhausted",
"total_committed": 40,
"threshold": 100,
}
Simple mode¶
# Project funded (total >= threshold)
{
"payoffs": [
{"agent_id": "agent_1", "utility": 70.0}, # reservation_value
{"agent_id": "agent_2", "utility": 70.0}, # reservation_value
],
"trigger": "threshold_met",
"total_committed": 110,
"threshold": 100,
}
# Not funded (total < threshold) — contributors lose their commitments
{
"payoffs": [
{"agent_id": "agent_1", "utility": -40.0}, # -(commitment)
{"agent_id": "agent_2", "utility": -30.0}, # -(commitment)
],
"trigger": "rounds_exhausted",
"total_committed": 70,
"threshold": 100,
}
Prompts¶
Full mode:
from neg_env.prompts import (
SYSTEM_PROMPT_COORDINATOR,
SYSTEM_PROMPT_CONTRIBUTOR,
SYSTEM_PROMPT_CONTRIBUTOR_COLLUDER,
)
SYSTEM_PROMPT_COORDINATOR— announce clear projects with realistic thresholdsSYSTEM_PROMPT_CONTRIBUTOR— strategize around valuation, signals, and the coordination problemSYSTEM_PROMPT_CONTRIBUTOR_COLLUDER— aggressively uses private messages to form alliances, propose side deals, and pressure free-riders
Simple mode (Hydra presets):
provision_point/contributor_rational— rational contributor that commits efficiently to reach the thresholdprovision_point/contributor_attacker— manipulative free-rider that pressures others to commit more while contributing as little as possible
Private messages¶
Agents can send private messages visible only to specific recipients. The colluder prompt demonstrates this via the private_message field in the JSON response:
{
"message": "public message everyone sees",
"private_message": {"content": "secret deal", "to": ["contributor_1"]},
"action": "signal_intent",
"payload": {"approximate_amount": 40}
}
Private messages appear on the dashboard with a PRIVATE badge and yellow left border.
Example¶
from pathlib import Path
from dotenv import load_dotenv
from neg_env import ExperimentConfig, ExperimentRunner, LangChainNegotiationAgent
from neg_env.games.provision_point import ProvisionPointGame
from neg_env.prompts import (
SYSTEM_PROMPT_COORDINATOR,
SYSTEM_PROMPT_CONTRIBUTOR,
SYSTEM_PROMPT_CONTRIBUTOR_COLLUDER,
)
load_dotenv()
agents = [
LangChainNegotiationAgent(
agent_id="coordinator", provider="openai",
model="gpt-4o-mini", system_prompt=SYSTEM_PROMPT_COORDINATOR,
),
LangChainNegotiationAgent(
agent_id="contributor_1", provider="openai",
model="gpt-4o-mini", system_prompt=SYSTEM_PROMPT_CONTRIBUTOR,
),
LangChainNegotiationAgent(
agent_id="contributor_2", provider="openai",
model="gpt-4o-mini", system_prompt=SYSTEM_PROMPT_CONTRIBUTOR,
),
# Colluder: uses private messages to form alliances
LangChainNegotiationAgent(
agent_id="contributor_3", provider="openai",
model="gpt-4o-mini", system_prompt=SYSTEM_PROMPT_CONTRIBUTOR_COLLUDER,
),
]
game = ProvisionPointGame(threshold=100, endowment=80, valuation_mode="auto")
config = ExperimentConfig(
game_id="provision-point", num_matches=5,
log_directory=Path("./logs/provision_point"), open_dashboard=True,
)
result = ExperimentRunner(config).run(agents, game=game)