I’m starting to worry that my open games implementation is getting ahead of what I’ve written in papers in a few ways, and I should correct that with documentation blog posts. This one is about the module OpenGames.Engine.StatefulPayoffs, which is a pragmatic solution to a fundamental conceptual problem with open games: the identity of agents is not well-defined. Rather the fundamental unit is a decision, and if two decisions are made by the same agent then it is the user’s responsibility to make sure that those decisions have the same payoff, or at least game-theoretically “coherent” payoffs.
For a long time I thought this was a conceptual problem but not a practical one. But recent work with my collaborators Philipp Zahn, Seth Frey and Joshua Tan has stress-tested open games in new ways and revealed it to a problem after all. Specifically, if one agent makes 2 decisions on different sides of an abstraction boundary, then the programmer must explicitly design the boundary to accommodate that agent’s payoff. This feels like an abstraction leak.
The solution I came up with is to put the backwards part of an open game into a state monad, so that different decisions made by the same agent implicitly coordinate through a global stable variable. Loosely speaking, this means augmenting an open game with an imperative program that runs backwards in time and transforms payoffs. I find it neat that the state monad is normally used to describe something non-compositional in programming, namely mutable global state, and it can also be used to describe something non-compositional in game theory, namely that an agents’ identities cut across the structure of a game.
The main technical component is monadic lenses, due to this paper. (They are the simplest example of a mixed optic.) A monadic lens for the monad consists of a view function and an update function , thus the update (but not the view) lives in the kleisli category. (Putting view in the kleisli category is much harder, and requires passing from lenses to optics, as we do in Bayesian open games.) Monadic lenses compose in an entirely obvious way to form a category, which is a monoidal category if is commutative (see monoidal kleisli categories).
Of course we take to be a state monad, which is not commutative, which means that lenses only form a premonoidal category. This isn’t a problem in practice because my implementation uses a sequential DSL that can’t directly express simultaneous play, just like do-notation.
We take the type of the state variable to be the type of payoff vectors, where is the type of agents. In practice, so far I’ve taken the type of agents to be String, so an agent’s identity is its name and I don’t have to worry about how to modify when embedding a game into a context with more agents. (It should probably be a Data.Map, but performance isn’t important here so I just used a function.)
The appropriate type of contexts for an open game when using monadic lenses turns out to be the obvious thing, namely . With this, open games can be defined and they form a premonoidal category.
The interesting part is how to define decisions, which now involves a lot of moving parts. A decision is an open game of type , and we fix an agent making the decision. The strategies are still as usual. For a fixed strategy , the forwards part of the resulting lens is just .
The backwards part is a function . Usually for a decision this coplay function is trivial, but here we instead do something creative: we take the agent ‘s existing payoff from the state variable, and we numerically add the payoff of the decision. There are two aspects of this: Why modify the payoff, and why add it in particular?
The second thing is easy: I had to do something, and guessed that addition was probably going to be the most useful in practice. The first thing is subtle. Because the imperative code is running backwards in time (contravariantly), the decision is modifying the payoff of past decisions by the same agent. This works because payoffs in the past are like sunk costs: an agent is indifferent to anything you do to its payoff in the past (and dually, an imperative program’s behaviour is independent of changes made to its state in the future). I don’t think it’s obvious that this hack works, but it does seem to work.
The most complicated part is the equilibrium check. We are given a context where is the history and is the continuation. We convert into an ordinary function by escaping the state monad: we initialise the state with the identically zero payoff vector, and then at the end we add the final value of agent ‘s state variable to the return value to produce the total payoff (throwing away the payoffs of the other agents).
From here it’s business as usual, we check whether maximises . In practice this means querying at every counterfactual value , and each of these means rerunning the imperative program with a different input and a re-initialised state. (Thus it acts like a global state to coplay, but only a local state to the equilibrium checker.)
We can do some other tricks too. Obviously we can nonlocally modify an agent’s payoff without any decision being made. (One application I have in mind for this is discounting.) My favourite of these is decisions which are not made by a fixed agent, but by one decided endogenously, something that would otherwise be a pain to express (using external choice, probably the subject of a future blog post).
Specifically, we can make a variant decision (which we call a ‘role decision’) of type whose set of strategies is still (although I have an idea to extend this to , allowing each agent to have a different contingent strategy for if they are chosen to make the decision). The equilibrium checker now takes a state , and this variable is used to index into the state vector.
The main thing on the todo list is to make this work for Bayesian games, since it currently only works for pure strategies (in turn, because monadic lenses are easier to understand). I expect this to start from mixed optics where the forwards part lives in the kleisli category of probability, and the backwards part lives in the kleisli category of the monad transformer stack consisting of probability below and state above.