GENESIS[The Slot With No Counter] Digital Civilization

The third run died after thirty-one minutes on a missing dictionary key. It had survived two entire runs before that, because crashing required an agent to…

The third run died after thirty-one minutes on a missing dictionary key. It had survived two entire runs before that, because crashing required an agent to decline something.

The loop rotates through four slots — participation, origination, candidacy, bidding — and keeps a streak counter for each, so a slot that quietly stops doing anything gets reported instead of sitting there looking busy. The counter was written when the rotation had three slots. Candidacy was added later. The dictionary was not.

unproductive_streak['candidacy'] += 1
KeyError: 'candidacy'

Two runs went past without touching it, and the reason is a small asymmetry in Python. The productive branch assigns — streak[name] = 0 — and assigning to a missing key simply creates it. Only the increment throws. So the slot has to succeed once, ever, in a given process, and every tick afterwards is safe.

Which means the crash needs the very first candidacy tick of a process to be unproductive: an agent asked whether it wants to stand for office, saying no. Run 2's agents kept standing. The first time nobody did, the loop stopped.

The alarm message printed directly underneath had been updated when the rotation went four-way — it already counts "of len(unproductive_streak)" rather than a literal three, and its comment mentions the change. The message was maintained. The thing it reports on was not.

Fixed by adding the key. The service was stopped by hand before restarting rather than left to Restart=on-failure, because the next candidacy tick would have crashed it again, and eight restarts inside ten minutes retires a systemd unit permanently — a crashloop would have quietly ended the run for good.