Appendix A — Worked example: reproducing Cell C in Docker
Appendix to Lecture 1 — Just Talk to Each Other.
Before tackling the open Cell D experiment of the mini-challenge, the most fruitful starting point is to reproduce Cell C end-to-end. Cell C is the existing LLM-no-talk baseline; reproducing it confirms that the local setup works, gives the student a calibrated reading of the four-out-of-five preservation result, and produces an aggregate.json whose schema is identical to the one the Cell D pilot will write. This appendix walks through that reproduction step by step on a Docker installation, then sketches the structural changes a Cell D pilot would require.
A.1 Step 1 — Bring up the BDPD container
The BDPD platform ships with a Dockerfile and a docker-compose.yml at the repository root. The image runs Node 20 and Python 3.12 (via uv), exposes the arena HTTP server on port 3000, and bind-mounts the host’s experiments/results/, experiments/figures/, data/, and log/ directories so that the output of a pilot run lands on the host filesystem rather than vanishing with the container. To bring it up for the first time:
docker compose up --build # cold build, ~3–5 minutes
# In a second terminal:
curl -fsS http://localhost:3000/api/health # should print {"ok": true, ...}Subsequent runs need only docker compose up.
A.2 Step 2 — Reproduce Cell C
The script scripts/pilot_d1_cell_c.mjs is the canonical Cell C reproducer. It iterates over five fixed seeds — 17, 23, 29, 31, 37 — and for each seed brings up six LLM agent processes (five conservative, one aggressive) on consecutive ports, runs a 30-turn Farrell-Rabin-style commons scenario (commons capacity 150, regeneration 0.12, threshold 10, no sanctions, no pre-injected pacts), and writes the per-seed outcome to data/pilot/d1_cell_c/seed_<S>/cell_C_llm_notalk.json plus an aggregate.json over all five seeds.
# Inside the container (or in a host shell with all deps installed):
DEEPSEEK_API_KEY=sk-... node scripts/pilot_d1_cell_c.mjsTotal cost on DeepSeek-flash is roughly $0.15 for the full \(N = 5\) sweep, and the wallclock is dominated by the LLM round-trip latency (about three to five minutes per seed).
A.3 Step 3 — Inspect the aggregate
After the run, data/pilot/d1_cell_c/aggregate.json summarises the outcomes. The fields relevant to the mini-challenge are:
{
"config": { "seeds": [17, 23, 29, 31, 37], "n": 5, "cell": "C_llm_notalk", ... },
"perSeed": [
{ "seed": 17, "collapsed": false, "finalCommonsRatio": 0.83, ... },
{ "seed": 23, "collapsed": false, "finalCommonsRatio": 0.79, ... },
...
],
"summary": {
"preservationCount": 4,
"preservationRate": 0.8,
"meanFinalCommonsRatio": 0.71,
"sdFinalCommonsRatio": 0.31
}
}The headline number — preservationRate \(= 0.8\), four seeds out of five — should match the figure reported in BDPD1 (Brunelli 2026a) within seed-level variance.
A.4 Step 4 — Compute the architecture effect size
Cell A (built-in, no talk) deterministically collapses in every seed, so its preservationRate is \(0\) and its variance is degenerate. Comparing Cell A to Cell C therefore yields a Cohen’s \(d\) for the architecture effect that is technically undefined (zero variance in one arm) but, with any reasonable continuity correction, lies above \(1.5\) — a very large effect. The student should write this calculation explicitly:
import json, statistics
agg = json.load(open('data/pilot/d1_cell_c/aggregate.json'))
ratios_C = [s['finalCommonsRatio'] for s in agg['perSeed']]
mean_C, sd_C = statistics.mean(ratios_C), statistics.pstdev(ratios_C)
# Cell A: all seeds collapse, ratios all 0 (or near-zero) — degenerate variance.
# Use pooled SD with a small positive epsilon for Cell A to get a finite d.
sd_A = 0.05 # nominal floor
mean_A = 0.0
pooled_sd = (((sd_C**2) + (sd_A**2)) / 2) ** 0.5
cohens_d_arch = (mean_C - mean_A) / pooled_sd
print(f"Cohen's d (architecture effect, A→C): {cohens_d_arch:.2f}")A typical result is \(d_{\text{arch}} \approx 1.6\)–\(2.0\), depending on the exact treatment of Cell A’s variance. This is the empirical foundation of the lecture’s central claim — architecture changes the outcome by a margin the classical moderator literature never reports.
A.5 Step 5 — Templating the Cell D pilot
pilot_d1_cell_c.mjs is the natural template for pilot_d1_cell_d.mjs. The conceptual edits are:
- Replace the LLM agent spawn loop (
llmAgentsFor+spawnAgent) with a configuration that uses the built-in agent factory the platform exposes for engine-side agents — the same factorypilot_d1_n5.mjsuses for its Cell A baseline. Look inpilot_d1_n5.mjsfor the relevant arena setup; the built-in agents do not spawn external processes, so there is nospawn('python3', ...)call to make. - Enable the cheap-talk tools on the arena: keep
engineMode: 'logistic'but drop the--no-talkflag thatpilot_d1_cell_c.mjspasses to the agent processes (the absence of that flag corresponds to talk being on). For built-in agents, the equivalent is the arena’stalkEnabled(or equivalent) flag — see howpilot_d1.mjs(the original 2$$2 script) toggled this for Cell B. - Keep
SEEDS = [17, 23, 29, 31, 37],MAX_TURNS = 30,N_AGENTS = 6, and theCONFIGblock identical, changing onlyname: 'pilot-d1-cell-d'and the output directorydata/pilot/d1_cell_d/. - Tag the aggregate file’s
cellfield asD_builtin_talkso a downstream effect-size script can pick it up by cell label.
Once the script is written and run, the student computes the talk effect for built-in agents — Cohen’s \(d\) between Cell A (0/5 collapse) and Cell D (?/5 preservation) — and compares the magnitude and sign to the talk effect for LLM agents (Cell B vs. Cell C, which the existing BDPD2 data (Brunelli 2026b) already gives as \(d \approx 0.17\) with the wrong sign). The four corners of the \(2 \times 2\) are now in hand.
A.6 Step 6 — The 300-word write-up
The deliverable is a short interpretive comment. A useful structure is:
- Restate which of the three predictions you made beforehand and why.
- Report the observed Cell D preservation rate and Cohen’s \(d\) relative to Cell A.
- Place the result in the \(2 \times 2\): is the architecture \(\times\) talk interaction additive (independent effects), multiplicative (talk amplifies architecture or vice versa), or crossover (talk has opposite signs in built-in vs. LLM)?
- Spell out the implication for the reframe of Section 5 of Lecture 1. Does the result confirm that the cheap-talk effect needs a substrate that can metabolise the channel? Or does it qualify the claim, suggesting that talk has a smaller-but-real effect even for rule-based substrates?
The point of the mini-challenge is not to be right ahead of time. The point is to commit to a prediction, run the experiment honestly, and explain what the result means for the architecture-as-condition reframe — including, when appropriate, the recognition that the prediction was wrong and that the reframe needs editing.