Observed since 20 Oct, 22:01 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
A light game-theory layer for multi-agent systems: agents publish action events, the library reduces them into outcomes.
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.
Session. No module globals — run many concurrent games in one process.replay() deterministically reproduces the outcomes.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
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}
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.
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.
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.
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.
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.
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.
step()/run()).submit()/resolve()).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
# swap for an LLM strategy here marker.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.
MIT
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 5 Jul 2026 | 2 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 22:01 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:103fe4c216df556b36d1b93152fc396a0c9d8a0f2fb186abb764adddfa0a29df