Skip to main content

gametheory

A light game-theory layer for multi-agent systems. Agents publish action events; the library reduces them into outcome events using a payoff function you supply. It sits on top of LangGraph / CrewAI / AutoGen / anything else without importing them, and it never owns the event loop — you drive it.

  • Zero dependencies. The core is pure stdlib; framework adapters are optional extras.
  • You drive, it reduces. No background threads, no started loops. Synchronous.
  • Pure scoring, impure driving. Payoff and resolution take data and return data; only the transport does I/O.
  • Everything is scoped to a Session. No module globals — run many concurrent games in one process.
  • A session is a fold over its event log. Given the log, replay() deterministically reproduces the outcomes.

Install

pip install agent-gametheory

The distribution is named agent-gametheory on PyPI, but the import is simply import gametheory. For development:

git clone https://github.com/ravikadam/gametheory.git
cd gametheory
pip install -e ".[dev]"     # library + pytest
pytest                      # run the test suite

Quickstart

from gametheory import Game, Session, InMemoryTransport

pd = Game(players=["alice", "bob"], actions=["cooperate", "defect"])

@pd.payoff
def score(actions):
    table = {("cooperate", "cooperate"): (3, 3), ("cooperate", "defect"): (0, 5),
             ("defect", "cooperate"): (5, 0), ("defect", "defect"): (1, 1)}
    a, b = table[(actions["alice"], actions["bob"])]
    return {"alice": a, "bob": b}

s = Session(pd, transport=InMemoryTransport())
s.submit("alice", "cooperate", scope="sealed")   # sealed: simultaneous moves
s.submit("bob", "defect", scope="sealed")
outcome = s.resolve()
print(outcome.payoffs)          # {'alice': 0, 'bob': 5}

A five-minute game theory primer

You don't need a game theory background to use this library — just these ideas, each of which maps to one knob in the API.

A game is players, actions, and payoffs. Some agents (players) each pick an action, and a payoff function turns the combination of actions into a score for everyone. That's the whole formal object, and it's exactly what Game(players=..., actions=...) plus @game.payoff declares. The interesting part is that each player's score depends on what others chose — that interdependence is what makes it a game rather than N separate optimizations.

Simultaneous vs. sequential is an information question. In the prisoner's dilemma or a sealed-bid auction, players move "at the same time" — which really means nobody sees the others' choices before committing. In negotiation or chess, moves are sequential: you see the last move before making yours. In this library that's not two different engines, it's one knob: submit actions with scope="sealed" and other players can't observe them until the round resolves (simultaneous); submit with scope="public" and the next player to act sees them (sequential).

A dominant strategy is a choice that's best no matter what others do. In the prisoner's dilemma, defecting is dominant — and both players defecting leaves both worse off than mutual cooperation. That gap between individually-rational and collectively-good is the classic reason multi-agent systems need coordination mechanisms, not just smarter agents.

Repeated games change behavior. Play the same game over multiple rounds (rounds=5) and history starts to matter: strategies like tit-for-tat can sustain cooperation because today's defection can be punished tomorrow. The Observation handed to every strategy carries the full outcome history for exactly this reason.

Mechanism design is game theory run backwards. Instead of asking "what will players do in this game?", you ask "what game should I put my agents in so that selfish play produces the outcome I want?" The auction example is the canonical case: a second-price (Vickrey) auction awards the job to the best bid but sets the price by the runner-up's bid. Since your bid only decides whether you win — never the price you're paid — bidding your true cost is a dominant strategy. You get honest agents without asking anyone to be honest. Payoffs express such transfers with no special API: negative numbers for payers, positive for payees.

Concepts

Game is a pure spec: players, an action space (an enum list or a (player, action) -> bool validator), a resolution rule, and a payoff function. resolves_when is "all_acted", "any_acted", "timeout" (pass deadline= to resolve), or your own predicate over the standing actions. rounds > 1 enables repeated play.

Session drives one play-through:

s.submit(player, action, scope="public")  # validate + emit an ACTION event
s.resolve()          # -> Outcome when resolves_when holds (else raises NotResolved)
s.step()             # ask each pending player's Strategy, then try to resolve
s.run()              # loop step() for game.rounds rounds -> list[Outcome]
s.history            # accumulated outcomes
s.utilities          # running payoff sum per player

Scopes are the information-visibility knob on every action event:

scope other players see it revealed later?
public immediately —
sealed not until the round resolves yes, in the outcome
private never no — referee/payoff only

Strategies are plain callables registered per player; step() hands each one an Observation: {"round", "history", "visible_actions", "self"}, filtered by the scope rules above.

Replay rebuilds outcomes from an event log with no transport or strategy calls:

from gametheory import replay
assert replay(s.transport.events) == s.history

Exceptions (InvalidAction, NotResolved, GameError) live in gametheory.types.

Using it in a multi-agent system

The integration contract is deliberately tiny: your agents compute actions however they like; the game only sees session.submit(player, action, scope). The library never calls your framework, never blocks, and never spins a loop, so it composes with any orchestrator that can call a function.

Pattern 1 — LLM agents as strategies

A Strategy is just Observation -> Action. Replace a rule with a model call and the game doesn't know the difference:

@game.strategy("buyer", scope="public")
def buyer(obs):
    # obs = {"round", "history", "visible_actions", "self"} — everything the
    # agent is *allowed* to know, with sealed/private info already filtered.
    prompt = f"Round {obs['round']}. Standing offers: {obs['visible_actions']}. ..."
    return parse_action(llm.invoke(prompt))   # e.g. an Anthropic/OpenAI call

session.run()   # the library asks each strategy in turn, then scores

Because the Observation is built from scope rules, you can hand it straight into a prompt without leaking sealed bids or private signals — the information structure of the game is enforced before the model ever sees it.

Pattern 2 — external agents drive, the session referees

When agents live in LangGraph, CrewAI, or separate services, don't register strategies at all. Let your orchestrator run the agents and treat the session as a referee that validates, hides, resolves, and scores:

# inside each agent's node/task, whenever it decides:
session.submit("worker-3", bid, scope="sealed")

# in your orchestrator, when the round should close:
outcome = session.resolve()          # or session.step() on a schedule
broadcast(outcome.payoffs)           # feed results back to the agents

The langgraph extra ships a thin adapter (gametheory.adapters.langgraph) that wraps players as graph nodes and resolution as a terminal node — but it's sugar; the two calls above are the whole integration.

Pattern 3 — the event log as the system of record

Every submit/resolve is a frozen Event published to the Transport. Subscribe to stream them into your own bus, UI, or store — and audit any game after the fact:

transport.subscribe(lambda ev: log.info("%s r%d %s: %s", ev.kind, ev.round, ev.player, ev.payload))
assert replay(transport.events) == session.history   # deterministic audit

This is where post-hoc analysis belongs (win rates, efficiency, whether your mechanism actually induced honesty) — over the log, outside the library.

Which pattern when?

  • Prototyping strategies / simulations → Pattern 1 (step()/run()).
  • Real agents with their own runtimes → Pattern 2 (submit()/resolve()).
  • Either way, keep Pattern 3 on for observability and replayable audits.

Examples

python examples/prisoners_dilemma.py   # simultaneous play via sealed scope
python examples/negotiation.py         # alternating offers over a 100 surplus
python examples/compute_auction.py     # sealed-bid reverse Vickrey with transfers
  • prisoners_dilemma.py — repeated play, tit-for-tat vs. always-defect, sealed simultaneous moves.
  • negotiation.py — buyer and seller split a surplus of 100 over up to 6 alternating-offer rounds; public offers, custom resolution predicate, breakdown cost when talks fail. Each strategy is a plain function with a # swap for an LLM strategy here marker.
  • compute_auction.py — N workers bid to run a batch job; the scheduler runs a sealed-bid second-price auction. Shows transfers (winner paid, scheduler pays) and demonstrates that overbidding loses the job while lowballing wins it at a loss.

Out of scope

Equilibrium solvers, learning algorithms, persistence, async, CLIs. Those are analyses you run over the event log afterwards — the log is designed to make that easy.

License

MIT

Metadata

Release files for agent-gametheory 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agent-gametheory 0.1.0
File Size Uploaded
agent_gametheory-0.1.0.tar.gz 15.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agent-gametheory 0.1.0
File Interpreter ABI Platform
agent_gametheory-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 28.4 kB

Release files / agent_gametheory-0.1.0.tar.gz

Download URL agent_gametheory-0.1.0.tar.gz
Size 15.0 kB
Tags Source
SHA-256 checksum
How to use checksums
05ef98eed4dc663e75369a01b6dad86ad18811f3947d290217b9403f119da9b2
BLAKE2b-256 checksum
How to use checksums
4e27db338cb134fadd774b7f0b4ebae7496c452f994aea35662f8684076f958d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release files / agent_gametheory-0.1.0-py3-none-any.whl

Download URL agent_gametheory-0.1.0-py3-none-any.whl
Size 13.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2fafc539ce5abb0af1d6ed9c94bb391bfaab70e36179332d4355b6bad6cb809e
BLAKE2b-256 checksum
How to use checksums
6b2764c85cd085bdd32b0d9b015504f3af52429ba790f09caf61d6d4fabc8d68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page