Metadata-Version: 2.4
Name: agent-gametheory
Version: 0.1.0
Summary: A light game-theory layer for multi-agent systems: agents publish action events, the library reduces them into outcomes.
Author-email: Ravindra Kadam <ravindrakad@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/ravikadam/gametheory
Project-URL: Repository, https://github.com/ravikadam/gametheory
Project-URL: Issues, https://github.com/ravikadam/gametheory/issues
Keywords: game-theory,multi-agent,mechanism-design,auctions,negotiation,llm-agents
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: langgraph
Requires-Dist: langgraph; extra == "langgraph"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# 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

```bash
pip install agent-gametheory
```

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

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

## Quickstart

```python
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:

```python
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:

```python
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:

```python
@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:

```python
# 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:

```python
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

```bash
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](examples/prisoners_dilemma.py)** — repeated play,
  tit-for-tat vs. always-defect, sealed simultaneous moves.
- **[negotiation.py](examples/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](examples/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
