Skip to content

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_updates is 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_commit rounds).
  • 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 thresholds
  • SYSTEM_PROMPT_CONTRIBUTOR — strategize around valuation, signals, and the coordination problem
  • SYSTEM_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 threshold
  • provision_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)