DATA 420 - Modeling and Simulation - Spring 2026

Homework #8 — Competitive Agents (Part II: LLM agents)

Possible experience: +40XP

Due: Thursday, April 30, midnight

Iterated Prisoner's Dilemma Tournament

Phase 2: LLM agents

And now, for part of this project that involves by far the greatest number of unknowns: LLM-based agents. In this phase, you'll be designing an agent which calls out to Ollama to make its Iterated Prisoner's Dilemma decisions, competing against your fellow students and (ideally) destroying their very will to live.

Instructions

Preparation

(If you've already done, or even started, homework #7, you can simply "git pull" to get the latest version of the code from my IPD repo, and then skip down to the homework-#8-specific preparation.)

  1. Clone my IPD repo somewhere on your system. (If you don't know how to clone a repo, google it.)
  2. Make a virtual environment for the project, and install all the required packages for the project, which I've put in requirements.txt. (The workflow for this, if you've forgotten or never learned it, is:
    $ python -m venv IPD_venv
    $ source IPD_venv/bin/activate
    $ pip install -r requirements.txt
    

    These are Linux/Mac commands; your mileage may vary on Windows, but I think if you use "PowerShell" you should be basically okay.)

Installing Ollama

  1. Make sure you have several GB of free disk space and a stable internet connection, since the llama3.1 8B model download is multiple gigabytes. If you don't, cry, and then ask Stephen what to do.
  2. Open a web browser and go to https://ollama.com, then click the download button for your operating system (Mac, Windows, or Linux). Follow the instructions.
  3. Open a terminal (macOS/Linux), Command Prompt or PowerShell (Windows), and type ollama. If you see a help screen listing commands such as run, pull, list, and rm, then Ollama is installed correctly. Otherwise, Google and/or ChatGPT and/or ask Stephen for help.
  4. In the same terminal or command window, run:
    ollama serve
  5. Now open a second terminal or command window, and run:
    ollama ps
    This should produce a somewhat weird looking message like:
    NAME    ID    SIZE    PROCESSOR    CONTEXT    UNTIL
    which is actually correct. If it instead prints an error like "Error: could not connect to ollama server", Google and/or ChatGPT and/or ask Stephen for help.
  6. Now in that second terminal or command window, run:
    ollama run llama3.1:8b-instruct-q4_K_M
  7. On this first run, Ollama will automatically download the model files; this may take several minutes depending on your connection and hardware.
  8. Wait until the download and loading process finishes and the prompt changes to something like ">" or "Send a message".
  9. When the prompt appears, type a question (for example, "Explain Newton's laws in simple terms" or "Why is Kim Kardashian so famous when she has no significant talent?") and press Enter. Make sure you get some sensible answer.
  10. Stop the model and exit. Re-run the "ollama ps" command and make sure it does not give you an error, just like it didn't give you an error before. Ollama needs to be running in order for any of your homework #8 code to work. The way to start it is always the same: "ollama serve". (Btw, if you ever run "ollama serve" and get an error like "Error: bind address already in use" that just means it was already running, and already happy.)

Getting a pulse

Okay. With ollama running happily in the background, use your second terminal window to go into the players directory of your repo, and copy student_llm_template.py to another Python file name, which does not have the word "template" anywhere in it. (Some good choices would be yourFirstName.py, yourUMWNetID.py, or yourFavoriteWheelOfTimeCharacter.py.)

You'll be putting all your code into this file. The way the simulator works is this: in addition to instantiating quantities of agents as controlled by the --agent-fracs parameter, it also searches the directory called players for student players that it will treat as additional "one-offs." (i.e., each student player that exists in that directory will be instantiated once and added to the environment.) See the command-line arguments (by typing "cli.py -h") for how to control the number and types of other kinds of agents.

Open your new Python file in your editor, and change the places where it says StudentLLMTemplate to some other name that does not have the word "template" in it anywhere. A good choice would be YourFirstNameLLM (unless your name is Josh or Michael, in which case YourFirstAndLastNameLLM is a better choice).

For the moment, change nothing else. Run this from the command line:

$ python cli.py 10 --agent-fracs Mean 1.0 \
    --p-same 0.3 --p-diff 0.3 --seed 124 \
    --num-iter 10 \
    --plot --plot-interactive \
    --players players

Then wait patiently.

Real patiently.

Try to be as patient as Job, Penelope, Cinderella, Gandhi, or even Jesus.

Then make sure you can eventually see your named agent (and its node number in the graph) on the screen with a bunch of Mean folks (hated to do that to you, sry).

Celebrate and grab a Coke.

Stuff you gotta know from homework #7

(If you've already done, or even started, homework #7, you can skip down to the homework-#8-specific stuff.)

I'm hoping that the comments I spent a lot of time building into the template files will be self-explanatory in most cases. Here are the highlights.

Spend a little time looking through the command-line parameters of the simulator, because they are numerous and a few of them are very important to grok. Run this command:

$ python cli.py -h

and spend a few moments just reading what it says and trying to figure out what the various options do. This is time well spent.

Things you inherit from IPDAgent superclass

By virtue of the fact that your class inherits from IPDAgent, and therefore is an IPDAgent, it has all the rights and privileges therein, including the ability to access these variables/methods:

self.node

This is the node number representing your agent, under self.node. Keep in mind that the rewiring (and other) operations are all keyed on an agent's node number, not its Mesa unique_id.

self.history

The self.history instance variable contains your entire move history against every opponent. Its value is a dictionary that looks something like this:

 { 3: [
{'step':1,'self_move':'C','other_move':'D'},
{'step':2,'self_move':'D','other_move':'D'} ], 8: [
{'step':2,'self_move':'D','other_move':'C'}, ] } 

In case it's not obvious, that structure means:

  1. You're currently adjacent (directly connected) to two other nodes who you're playing IPD against: node #3 and node #8.
  2. On step 1, you played C and jerk face (agent #3) played D.
  3. On step 2, you decided to pay #3 back in kind (ignoring the advice in Matt. 5:38-44) and defect against them.
  4. On step 2, you also got a new neighbor (welcome, player #8! nice to meet you!) This player was nice to you, but you weren't nice back, defecting on the first round. We'll have to see how things continue to play out.

Some of you are thinking "waitaminit, how could I suddenly have a new opponent in round 2?" Answer: because either you added #8 or #8 added you, after the first round. Get used to this -- it's gonna happen all the time.

self.neighbors()

At any point in time, you can call self.neighbors() which gives a list of (the node numbers of) your current graph neighbors. Unlike making a FOAF request, this is free to perform at any time, and will always give truthful information.

The Model object

There are several things available to you in the Model object, which is accessible from inside your class's methods via self.model. It contains:

  1. Perhaps most importantly, the method request_foaf_info_from()
  2. An RNG (random number generator) under the name "self.model.random". If you do any random generation in your player, you must use "self.model.random.whatever...()" to do it, not "someRandomNumberGeneratorYouMadeInSomeOtherWay.whatever...()". This is to make simulation runs (mostly) reproducible.
  3. The values self.model.steps that tell you what move number you're about to make, and self.model.num_iter which contains how many rounds the overall game will last. Those two help you understand how close to the end you are, which can (and probably should) change your strategy.

This is whatcha got to work with.

Stuff you need to know for homework #8

Things you inherit from LLMAgent superclass

For homework #8, notice that your agent inherits from a new LLMAgent class, instead of directly from IPDAgent. (If you're curious, you can see this class's code in the llm_agent.py file in the agents repo directory.)

LLMAgent has at least two convenience methods I predict you'll want to use: .serialize_payoffs() and .serialize_history(). These can be called by your agent (via self.serialize_payoffs() and self.serialize_history(someNeighboringNode)) to produce common strings that you'll likely want in your prompts. (If you don't like the wording they provide, feel free to copy and modify (i.e., override) these methods in your agent file.)

Implementing a strategy

Now you need to begin to develop your strategy in earnest. You have two methods that you must change, and two others that are optional:

Method
.system_prompt()required
.build_decision_prompt()required
.build_rewiring_prompt()optional
.inform_foaf()optional

The comments give gory details about all of these. Send email if you're confused about what any of them may do.

Making it more better

I strongly recommend an iterative approach to this entire project, in which you gradually make changes to your code, each time making it smarter and more sophisticated, and seeing how it responds to various mixes of the other types of agents (specified via --agent-fracs on the command line).

Sharing agents with a buddy is an excellent and praiseworthy way of assessing your strategy, since the two (or three, or four...) of you will expose each others' weaknesses and give you ideas for improvement.

Turning it in

Send me an email with subject line "DATA 420 homework #8 turnin". It should have attached one .py file: your modified student LLM-based player that I can insert into my players directory and have it play in a simulated world with all the other kinds of agents. (This is what you'll want to do as well, with different random number seeds, to understand what kinds of agents, and in how many quantities, and with what connection probabilities, cause your agent to do well versus to suck.) Your program should have some comments indicating what the various aspects of your strategy do, so that I can understand and judge your intent.

Getting help

Come to office hours, or send me email with subject line "DATA 420 Homework #8 help!!"