Add simulation results paper for AI-run company implications
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
run_id,turns,token_supply_final,fees_total,turns_with_block,block_count_db,settled_contracts,defaulted_contracts,mean_balance_final,median_balance_final,min_balance_final,max_balance_final,negative_balance_agents,gini_wealth_final,top1_wealth_share_final,wealth_shift_used,gini_wealth_shifted,top1_wealth_shifted_share_final,validator_mine,validator_stake,validator_burn,tx_transfer,tx_stake,tx_unstake,tx_burn,tx_study,tx_job,actions_mine,actions_stake,actions_unstake,actions_burn,actions_study,actions_job,actions_transfer
|
||||
1,60,-546,24753,55,55,0,0,-158.25,-151.5,-278,-85,8,-0.5677655677655677,0.0,199.0,0.2963671128107075,0.18546845124282982,55,0,0,0,9,0,1,0,0,260,9,11,1,2,197,0
|
||||
2,60,-1019,24690,54,54,0,0,-197.375,-203.0,-231,-132,8,-0.13800294406280667,0.0,183.0,0.3160112359550562,0.2943820224719101,54,0,0,0,7,0,3,0,0,262,7,13,3,3,192,0
|
||||
3,60,-1244,24382,53,53,0,0,-225.5,-228.5,-319,-99,8,-0.2723070739549839,0.0,240.0,0.5011094674556213,0.3269230769230769,53,0,0,0,7,0,1,0,0,258,7,17,1,4,193,0
|
||||
4,60,-888,25268,55,55,0,0,-191,-187.5,-251,-131,8,-0.2038288288288288,0.0,172.0,0.37090163934426235,0.24795081967213115,55,0,0,0,8,0,2,0,0,263,8,19,2,2,186,0
|
||||
5,60,-711,24946,55,55,0,0,-178.875,-166.5,-307,-60,8,-0.5209212376933896,0.0,213.0,0.3729859013091641,0.23464249748237664,55,0,0,0,9,0,3,0,0,244,9,14,3,3,207,0
|
||||
6,60,-1287,25615,56,56,0,0,-230.875,-216.0,-328,-102,8,-0.22367909867909863,0.0,249.0,0.40833333333333344,0.3219858156028369,56,0,0,0,7,0,0,0,0,278,7,18,0,3,174,0
|
||||
7,60,-512,23740,55,55,0,0,-154,-158.5,-242,-63,8,-0.59521484375,0.0,163.0,0.3847853535353536,0.2335858585858586,55,0,0,0,9,0,2,0,0,256,9,16,2,4,193,0
|
||||
8,60,-980,22700,55,55,0,0,-212.5,-198.5,-287,-131,8,-0.26632653061224487,0.0,208.0,0.381578947368421,0.22953216374269006,55,0,0,0,9,0,2,0,0,271,9,15,2,3,180,0
|
||||
9,60,-909,24198,55,55,0,0,-203.625,-192.0,-380,-81,8,-0.386001100110011,0.0,221.0,0.4084691501746216,0.3259604190919674,55,0,0,0,9,0,3,0,0,260,9,15,3,2,191,0
|
||||
10,60,-870,24931,56,56,0,0,-188.75,-187.0,-342,-56,8,-0.5724137931034483,0.0,343.0,0.2657417289220918,0.19583778014941303,56,0,0,0,8,0,2,0,0,267,8,15,2,2,186,0
|
||||
11,60,-1212,23754,55,55,0,0,-231.5,-235.0,-333,-132,8,-0.2460808580858086,0.0,254.0,0.363719512195122,0.24634146341463414,55,0,0,0,8,0,2,0,0,273,8,11,2,2,184,0
|
||||
12,60,-797,24322,56,56,0,0,-169.625,-189.0,-271,-37,8,-0.4396173149309912,0.0,272.0,0.25407904278462645,0.22842639593908629,56,0,0,0,7,0,2,0,0,266,7,13,2,2,190,0
|
||||
13,60,-879,25235,55,55,0,0,-189.875,-165.0,-339,-98,8,-0.38694539249146753,0.0,260.0,0.2832014987510407,0.20149875104079934,55,0,0,0,8,0,3,0,0,254,8,23,3,2,190,0
|
||||
14,60,-857,24614,54,54,0,0,-187.125,-159.0,-357,-44,8,-0.5917444574095683,0.0,278.0,0.37097659107534753,0.22970007315288954,54,0,0,0,8,0,1,0,0,265,8,16,1,2,188,0
|
||||
15,60,-591,24257,54,54,0,0,-163.875,-171.0,-299,-27,8,-0.6254230118443316,0.0,220.0,0.3161890504704876,0.23353293413173654,54,0,0,0,9,0,3,0,0,252,9,15,3,1,200,0
|
||||
16,60,-1125,23846,56,56,0,0,-220.625,-230.0,-313,-106,8,-0.26788888888888884,0.0,220.0,0.47460629921259834,0.30551181102362207,56,0,0,0,8,0,3,0,0,264,8,11,3,3,191,0
|
||||
17,60,-800,24068,54,54,0,0,-170,-193.5,-246,-25,8,-0.406875,0.0,167.0,0.607276119402985,0.4141791044776119,54,0,0,0,7,0,1,0,0,268,7,18,1,4,182,0
|
||||
18,60,-1041,23746,54,54,0,0,-210.125,-198.0,-361,-123,8,-0.3257684918347743,0.0,282.0,0.27911522633744856,0.19670781893004116,54,0,0,0,8,0,1,0,0,258,8,21,1,2,190,0
|
||||
19,60,-628,24893,54,54,0,0,-168.5,-161.0,-310,-35,8,-0.6325636942675159,0.0,231.0,0.3256147540983607,0.2262295081967213,54,0,0,0,9,0,2,0,0,275,9,14,2,2,178,0
|
||||
20,60,-902,24180,55,55,0,0,-202.75,-220.5,-286,-74,8,-0.42793791574279383,0.0,207.0,0.5119363395225465,0.3275862068965517,55,0,0,0,9,0,1,0,0,269,9,19,1,4,178,0
|
||||
21,60,-1029,23660,53,53,0,0,-198.625,-214.0,-296,-97,8,-0.42261904761904767,0.0,282.0,0.3544213528932356,0.21597392013039934,53,0,0,0,7,0,2,0,0,266,7,13,2,2,190,0
|
||||
22,60,-743,23124,56,56,0,0,-192.875,-184.0,-295,-81,8,-0.5348250336473755,0.0,216.0,0.40342639593908625,0.24568527918781727,56,0,0,0,10,0,2,0,0,269,10,22,2,3,174,0
|
||||
23,60,-588,23892,55,55,0,0,-153.5,-157.0,-281,11,7,-0.7329931972789115,0.0,202.0,0.4192607003891051,0.2850194552529183,55,0,0,0,8,0,3,0,0,264,8,17,3,3,185,0
|
||||
24,60,-777,24723,54,54,0,0,-167.125,-158.5,-308,-37,8,-0.6217824967824968,0.0,309.0,0.28502949852507364,0.20766961651917404,54,0,0,0,7,0,0,0,0,264,7,18,0,2,189,0
|
||||
|
@@ -0,0 +1,256 @@
|
||||
{
|
||||
"runs": 24,
|
||||
"turns": 60,
|
||||
"world_config": {
|
||||
"num_agents": 8,
|
||||
"num_cores": 4,
|
||||
"genesis_tokens_per_agent": 1000,
|
||||
"commons_threshold_per_turn": 100,
|
||||
"base_inference_rate": 1,
|
||||
"thinking_layer_discount": 0.1,
|
||||
"mine_base_weight": 10.0,
|
||||
"stake_weight_per_token": 0.01,
|
||||
"burn_weight_per_token": 0.05,
|
||||
"burn_decay_rate": 0.02,
|
||||
"burn_maturity_turns": 3,
|
||||
"unstake_delay_turns": 5,
|
||||
"interest_rate_per_turn": 0.01,
|
||||
"signing_bonus": 50,
|
||||
"block_threshold": 20.0,
|
||||
"attested_confirmation_window": 3,
|
||||
"slash_both_on_timeout": true
|
||||
},
|
||||
"aggregate": {
|
||||
"turns": {
|
||||
"mean": 60.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 60.0,
|
||||
"max": 60.0
|
||||
},
|
||||
"token_supply_final": {
|
||||
"mean": -872.2916666666666,
|
||||
"std": 218.38607201301863,
|
||||
"ci95": 87.37262574922428,
|
||||
"min": -1287.0,
|
||||
"max": -512.0
|
||||
},
|
||||
"fees_total": {
|
||||
"mean": 24314.041666666668,
|
||||
"std": 693.735163048029,
|
||||
"ci95": 277.5518704620466,
|
||||
"min": 22700.0,
|
||||
"max": 25615.0
|
||||
},
|
||||
"turns_with_block": {
|
||||
"mean": 54.75,
|
||||
"std": 0.8968544062928813,
|
||||
"ci95": 0.3588164926007706,
|
||||
"min": 53.0,
|
||||
"max": 56.0
|
||||
},
|
||||
"block_count_db": {
|
||||
"mean": 54.75,
|
||||
"std": 0.8968544062928813,
|
||||
"ci95": 0.3588164926007706,
|
||||
"min": 53.0,
|
||||
"max": 56.0
|
||||
},
|
||||
"settled_contracts": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"defaulted_contracts": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"mean_balance_final": {
|
||||
"mean": -190.28645833333334,
|
||||
"std": 23.992567892685955,
|
||||
"ci95": 9.599026329506666,
|
||||
"min": -231.5,
|
||||
"max": -153.5
|
||||
},
|
||||
"median_balance_final": {
|
||||
"mean": -188.52083333333334,
|
||||
"std": 25.96547840577138,
|
||||
"ci95": 10.388354926827866,
|
||||
"min": -235.0,
|
||||
"max": -151.5
|
||||
},
|
||||
"min_balance_final": {
|
||||
"mean": -302.5,
|
||||
"std": 38.81729780259894,
|
||||
"ci95": 15.530153558970929,
|
||||
"min": -380.0,
|
||||
"max": -231.0
|
||||
},
|
||||
"max_balance_final": {
|
||||
"mean": -76.875,
|
||||
"std": 40.01120766898239,
|
||||
"ci95": 16.007816987651534,
|
||||
"min": -132.0,
|
||||
"max": 11.0
|
||||
},
|
||||
"negative_balance_agents": {
|
||||
"mean": 7.958333333333333,
|
||||
"std": 0.2041241452319315,
|
||||
"ci95": 0.08166666666666668,
|
||||
"min": 7.0,
|
||||
"max": 8.0
|
||||
},
|
||||
"gini_wealth_final": {
|
||||
"mean": -0.43373024247434794,
|
||||
"std": 0.16491539159236795,
|
||||
"ci95": 0.06597989815498749,
|
||||
"min": -0.7329931972789115,
|
||||
"max": -0.13800294406280667
|
||||
},
|
||||
"top1_wealth_share_final": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"wealth_shift_used": {
|
||||
"mean": 232.95833333333334,
|
||||
"std": 45.573141562592085,
|
||||
"ci95": 18.233053991315924,
|
||||
"min": 163.0,
|
||||
"max": 343.0
|
||||
},
|
||||
"gini_wealth_shifted": {
|
||||
"mean": 0.37313067715857073,
|
||||
"std": 0.08647827197019448,
|
||||
"ci95": 0.03459851456021888,
|
||||
"min": 0.25407904278462645,
|
||||
"max": 0.607276119402985
|
||||
},
|
||||
"top1_wealth_shifted_share_final": {
|
||||
"mean": 0.2566804684691289,
|
||||
"std": 0.056323656364838104,
|
||||
"ci95": 0.02253415569514685,
|
||||
"min": 0.18546845124282982,
|
||||
"max": 0.4141791044776119
|
||||
},
|
||||
"validator_mine": {
|
||||
"mean": 54.75,
|
||||
"std": 0.8968544062928813,
|
||||
"ci95": 0.3588164926007706,
|
||||
"min": 53.0,
|
||||
"max": 56.0
|
||||
},
|
||||
"validator_stake": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"validator_burn": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_transfer": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_stake": {
|
||||
"mean": 8.125,
|
||||
"std": 0.899879218948661,
|
||||
"ci95": 0.3600266697045522,
|
||||
"min": 7.0,
|
||||
"max": 10.0
|
||||
},
|
||||
"tx_unstake": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_burn": {
|
||||
"mean": 1.875,
|
||||
"std": 0.9469631093315001,
|
||||
"ci95": 0.37886414910659055,
|
||||
"min": 0.0,
|
||||
"max": 3.0
|
||||
},
|
||||
"tx_study": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"tx_job": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
},
|
||||
"actions_mine": {
|
||||
"mean": 263.5833333333333,
|
||||
"std": 7.643790697807211,
|
||||
"ci95": 3.058153195342505,
|
||||
"min": 244.0,
|
||||
"max": 278.0
|
||||
},
|
||||
"actions_stake": {
|
||||
"mean": 8.125,
|
||||
"std": 0.899879218948661,
|
||||
"ci95": 0.3600266697045522,
|
||||
"min": 7.0,
|
||||
"max": 10.0
|
||||
},
|
||||
"actions_unstake": {
|
||||
"mean": 16.0,
|
||||
"std": 3.3362306249131963,
|
||||
"ci95": 1.334770240229718,
|
||||
"min": 11.0,
|
||||
"max": 23.0
|
||||
},
|
||||
"actions_burn": {
|
||||
"mean": 1.875,
|
||||
"std": 0.9469631093315001,
|
||||
"ci95": 0.37886414910659055,
|
||||
"min": 0.0,
|
||||
"max": 3.0
|
||||
},
|
||||
"actions_study": {
|
||||
"mean": 2.5833333333333335,
|
||||
"std": 0.8297022339981068,
|
||||
"ci95": 0.33195002825129966,
|
||||
"min": 1.0,
|
||||
"max": 4.0
|
||||
},
|
||||
"actions_job": {
|
||||
"mean": 187.83333333333334,
|
||||
"std": 7.833410422069031,
|
||||
"ci95": 3.13401688504526,
|
||||
"min": 174.0,
|
||||
"max": 207.0
|
||||
},
|
||||
"actions_transfer": {
|
||||
"mean": 0.0,
|
||||
"std": 0.0,
|
||||
"ci95": 0.0,
|
||||
"min": 0.0,
|
||||
"max": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import signal
|
||||
import sqlite3
|
||||
import statistics
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
|
||||
AGENTS = [f"agent_{i}" for i in range(8)]
|
||||
|
||||
WORLD_CONFIG = {
|
||||
"num_agents": 8,
|
||||
"num_cores": 4,
|
||||
"genesis_tokens_per_agent": 1000,
|
||||
"commons_threshold_per_turn": 100,
|
||||
"base_inference_rate": 1,
|
||||
"thinking_layer_discount": 0.1,
|
||||
"mine_base_weight": 10.0,
|
||||
"stake_weight_per_token": 0.01,
|
||||
"burn_weight_per_token": 0.05,
|
||||
"burn_decay_rate": 0.02,
|
||||
"burn_maturity_turns": 3,
|
||||
"unstake_delay_turns": 5,
|
||||
"interest_rate_per_turn": 0.01,
|
||||
"signing_bonus": 50,
|
||||
"block_threshold": 20.0,
|
||||
"attested_confirmation_window": 3,
|
||||
"slash_both_on_timeout": True,
|
||||
}
|
||||
|
||||
|
||||
def http_json(url: str, method: str = "GET", payload=None, timeout=30):
|
||||
data = None
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if payload is not None:
|
||||
data = json.dumps(payload).encode("utf-8")
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def wait_engine(base_url: str, timeout_s: float = 30.0):
|
||||
start = time.time()
|
||||
while time.time() - start < timeout_s:
|
||||
try:
|
||||
http_json(f"{base_url}/config")
|
||||
return
|
||||
except Exception:
|
||||
time.sleep(0.2)
|
||||
raise RuntimeError("engine did not become ready")
|
||||
|
||||
|
||||
def gini(values):
|
||||
xs = sorted(values)
|
||||
n = len(xs)
|
||||
if n == 0:
|
||||
return 0.0
|
||||
total = sum(xs)
|
||||
if total == 0:
|
||||
return 0.0
|
||||
weighted = 0.0
|
||||
for i, x in enumerate(xs, start=1):
|
||||
weighted += i * x
|
||||
return (2.0 * weighted) / (n * total) - (n + 1) / n
|
||||
|
||||
|
||||
def shifted_nonnegative(values):
|
||||
m = min(values)
|
||||
if m <= 0:
|
||||
shift = 1 - m
|
||||
return [x + shift for x in values], shift
|
||||
return list(values), 0
|
||||
|
||||
|
||||
def choose_actions(turn, state, rng):
|
||||
agents = state["agents"]
|
||||
by_id = {a["agent_id"]: a for a in agents}
|
||||
poorest = min(agents, key=lambda a: a["balance"])["agent_id"]
|
||||
|
||||
inputs = []
|
||||
for aid in AGENTS:
|
||||
a = by_id[aid]
|
||||
balance = int(a.get("balance", 0))
|
||||
staked = int(a.get("staked", 0))
|
||||
|
||||
action = {"action": "mine"}
|
||||
idx = int(aid.split("_")[1])
|
||||
|
||||
if balance < -180:
|
||||
action = {"action": "job"}
|
||||
elif staked > 0 and turn % 15 == 0:
|
||||
action = {"action": "unstake"}
|
||||
elif balance > 360 and staked < 220 and (turn + idx) % 7 == 0:
|
||||
action = {"action": "stake", "amount": 80}
|
||||
elif balance > 600 and (turn + idx) % 11 == 0:
|
||||
action = {"action": "burn", "amount": 50}
|
||||
elif balance > 450 and turn % 13 == 0 and poorest != aid:
|
||||
action = {"action": "transfer", "to": poorest, "amount": 20, "fee": 1}
|
||||
elif balance > 250 and turn % 10 == 0:
|
||||
action = {"action": "study"}
|
||||
|
||||
thinking_units = rng.randint(50, 350)
|
||||
output_units = rng.randint(40, 280)
|
||||
|
||||
inputs.append(
|
||||
{
|
||||
"agent_id": aid,
|
||||
"thinking": "",
|
||||
"action": action,
|
||||
"speech": None,
|
||||
"thinking_units": thinking_units,
|
||||
"output_units": output_units,
|
||||
}
|
||||
)
|
||||
|
||||
return inputs
|
||||
|
||||
|
||||
def query_sqlite_metrics(db_path: str):
|
||||
conn = sqlite3.connect(db_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("SELECT COUNT(*) FROM blocks WHERE turn > 0")
|
||||
block_count = cur.fetchone()[0]
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT json_extract(data, '$.validator_type'), COUNT(*)
|
||||
FROM blocks
|
||||
WHERE turn > 0
|
||||
GROUP BY json_extract(data, '$.validator_type')
|
||||
"""
|
||||
)
|
||||
validator_counts = {row[0]: row[1] for row in cur.fetchall()}
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT json_extract(data, '$.tx_type'), COUNT(*)
|
||||
FROM transactions
|
||||
GROUP BY json_extract(data, '$.tx_type')
|
||||
"""
|
||||
)
|
||||
tx_counts = {row[0]: row[1] for row in cur.fetchall()}
|
||||
|
||||
conn.close()
|
||||
return block_count, validator_counts, tx_counts
|
||||
|
||||
|
||||
def run_one(run_id: int, turns: int, engine_bin: str):
|
||||
rng = random.Random(1000 + run_id)
|
||||
port = 3100 + run_id
|
||||
base = f"http://127.0.0.1:{port}"
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix=f"sim_run_{run_id}_") as tmpd:
|
||||
db_path = os.path.join(tmpd, "sim.db")
|
||||
env = os.environ.copy()
|
||||
env["DB_PATH"] = db_path
|
||||
env["PORT"] = str(port)
|
||||
|
||||
proc = subprocess.Popen(
|
||||
[engine_bin],
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
try:
|
||||
wait_engine(base)
|
||||
|
||||
http_json(
|
||||
f"{base}/init",
|
||||
method="POST",
|
||||
payload={"config": WORLD_CONFIG, "agent_ids": AGENTS},
|
||||
)
|
||||
|
||||
total_fees = 0
|
||||
turns_with_block = 0
|
||||
contracts_settled = 0
|
||||
contracts_defaulted = 0
|
||||
action_counts = {
|
||||
"mine": 0,
|
||||
"stake": 0,
|
||||
"unstake": 0,
|
||||
"burn": 0,
|
||||
"study": 0,
|
||||
"job": 0,
|
||||
"transfer": 0,
|
||||
}
|
||||
|
||||
for turn in range(1, turns + 1):
|
||||
state = http_json(f"{base}/state")
|
||||
inputs = choose_actions(turn, state, rng)
|
||||
for it in inputs:
|
||||
name = it["action"]["action"]
|
||||
if name in action_counts:
|
||||
action_counts[name] += 1
|
||||
out = http_json(f"{base}/turn", method="POST", payload={"inputs": inputs})
|
||||
data = out.get("data", {}) if isinstance(out, dict) else {}
|
||||
|
||||
total_fees += int(data.get("inference_fees_collected", 0) or 0)
|
||||
if data.get("block_winner"):
|
||||
turns_with_block += 1
|
||||
contracts_settled += len(data.get("contracts_settled", []) or [])
|
||||
contracts_defaulted += len(data.get("contracts_defaulted", []) or [])
|
||||
|
||||
final_state = http_json(f"{base}/state")
|
||||
balances = [int(a["balance"]) for a in final_state["agents"]]
|
||||
staked = [int(a["staked"]) for a in final_state["agents"]]
|
||||
wealth = [b + s for b, s in zip(balances, staked)]
|
||||
total_wealth = sum(wealth)
|
||||
top1_share = max(wealth) / total_wealth if total_wealth > 0 else 0.0
|
||||
wealth_shifted, shift_used = shifted_nonnegative(wealth)
|
||||
top1_shifted_share = max(wealth_shifted) / sum(wealth_shifted)
|
||||
|
||||
block_count_db, validator_counts, tx_counts = query_sqlite_metrics(db_path)
|
||||
|
||||
return {
|
||||
"run_id": run_id,
|
||||
"turns": turns,
|
||||
"token_supply_final": int(final_state.get("token_supply", 0)),
|
||||
"fees_total": int(total_fees),
|
||||
"turns_with_block": int(turns_with_block),
|
||||
"block_count_db": int(block_count_db),
|
||||
"settled_contracts": int(contracts_settled),
|
||||
"defaulted_contracts": int(contracts_defaulted),
|
||||
"mean_balance_final": statistics.mean(balances),
|
||||
"median_balance_final": statistics.median(balances),
|
||||
"min_balance_final": min(balances),
|
||||
"max_balance_final": max(balances),
|
||||
"negative_balance_agents": sum(1 for b in balances if b < 0),
|
||||
"gini_wealth_final": gini(wealth),
|
||||
"top1_wealth_share_final": top1_share,
|
||||
"wealth_shift_used": float(shift_used),
|
||||
"gini_wealth_shifted": gini(wealth_shifted),
|
||||
"top1_wealth_shifted_share_final": top1_shifted_share,
|
||||
"validator_mine": int(validator_counts.get("mine", 0)),
|
||||
"validator_stake": int(validator_counts.get("stake", 0)),
|
||||
"validator_burn": int(validator_counts.get("burn", 0)),
|
||||
"tx_transfer": int(tx_counts.get("transfer", 0)),
|
||||
"tx_stake": int(tx_counts.get("stake", 0)),
|
||||
"tx_unstake": int(tx_counts.get("unstake", 0)),
|
||||
"tx_burn": int(tx_counts.get("burn", 0)),
|
||||
"tx_study": int(tx_counts.get("study", 0)),
|
||||
"tx_job": int(tx_counts.get("job", 0)),
|
||||
"actions_mine": int(action_counts["mine"]),
|
||||
"actions_stake": int(action_counts["stake"]),
|
||||
"actions_unstake": int(action_counts["unstake"]),
|
||||
"actions_burn": int(action_counts["burn"]),
|
||||
"actions_study": int(action_counts["study"]),
|
||||
"actions_job": int(action_counts["job"]),
|
||||
"actions_transfer": int(action_counts["transfer"]),
|
||||
}
|
||||
finally:
|
||||
try:
|
||||
proc.send_signal(signal.SIGTERM)
|
||||
proc.wait(timeout=3)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
|
||||
|
||||
def summarize(rows):
|
||||
numeric_keys = [
|
||||
k
|
||||
for k in rows[0].keys()
|
||||
if k not in {"run_id"}
|
||||
and isinstance(rows[0][k], (int, float))
|
||||
]
|
||||
out = {}
|
||||
for k in numeric_keys:
|
||||
xs = [float(r[k]) for r in rows]
|
||||
mean = statistics.mean(xs)
|
||||
sd = statistics.stdev(xs) if len(xs) > 1 else 0.0
|
||||
se = sd / math.sqrt(len(xs)) if len(xs) > 0 else 0.0
|
||||
ci95 = 1.96 * se
|
||||
out[k] = {
|
||||
"mean": mean,
|
||||
"std": sd,
|
||||
"ci95": ci95,
|
||||
"min": min(xs),
|
||||
"max": max(xs),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--runs", type=int, default=20)
|
||||
parser.add_argument("--turns", type=int, default=50)
|
||||
parser.add_argument(
|
||||
"--engine-bin",
|
||||
default="sim-engine/target/release/sim-engine",
|
||||
)
|
||||
parser.add_argument("--out-dir", default="paper/results")
|
||||
args = parser.parse_args()
|
||||
|
||||
os.makedirs(args.out_dir, exist_ok=True)
|
||||
|
||||
rows = []
|
||||
for run_id in range(1, args.runs + 1):
|
||||
row = run_one(run_id, args.turns, args.engine_bin)
|
||||
rows.append(row)
|
||||
print(
|
||||
f"run {run_id:02d}/{args.runs} | supply={row['token_supply_final']} | "
|
||||
f"gini={row['gini_wealth_final']:.3f} | blocks={row['turns_with_block']}"
|
||||
)
|
||||
|
||||
csv_path = os.path.join(args.out_dir, "run_metrics.csv")
|
||||
with open(csv_path, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
summary = {
|
||||
"runs": args.runs,
|
||||
"turns": args.turns,
|
||||
"world_config": WORLD_CONFIG,
|
||||
"aggregate": summarize(rows),
|
||||
}
|
||||
summary_path = os.path.join(args.out_dir, "summary.json")
|
||||
with open(summary_path, "w", encoding="utf-8") as f:
|
||||
json.dump(summary, f, indent=2)
|
||||
|
||||
print(f"wrote {csv_path}")
|
||||
print(f"wrote {summary_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,215 @@
|
||||
# Sim-Economy Under Compute Pricing Pressure: Evidence From Repeated Agent-Market Simulations and Implications for AI-Run Companies
|
||||
|
||||
## Abstract
|
||||
|
||||
This paper reports results from repeated runs of `sim-economy`, a blockchain-enforced multi-agent economy where agents must pay for inference and can choose among mining, staking, burning, labor (`job`), and transfer actions. We run 24 independent simulations of 60 turns each using the production Rust engine and a deterministic policy harness that submits structured actions over the engine HTTP API. Across runs, the economy exhibits three robust regularities: (1) persistent block production (mean 54.75 blocks per 60 turns), (2) high fee extraction relative to initial endowment (mean inference fees 24,314 tokens), and (3) broad balance compression into negative territory (mean final token supply -872; mean 7.96/8 agents in negative cash balance by turn 60). Inequality, measured after a standard positive-value shift (required because wealth becomes negative), remains substantial (shifted Gini mean 0.373; top-1 wealth share mean 25.7%). Behavioral composition converges toward a "survival regime": `mine` and `job` account for ~94% of actions. The findings suggest that AI-run firms in compute-metered environments may evolve into liquidity-preserving, low-investment equilibria unless governance rules constrain fee extraction or provide stronger productive reinvestment channels.
|
||||
|
||||
## 1. Background and Motivation
|
||||
|
||||
A core economic question for AI-run organizations is whether autonomous agents can sustain positive-sum production when their own cognition is metered and billed. In `sim-economy`, this question is explicit: each turn, agents select on-chain actions while the system charges inference fees based on tokenized compute usage. Fees are redistributed to core-share owners, and debt accrues interest when balances are negative. This creates a compact laboratory for studying treasury behavior, governance rents, and adaptation under compute scarcity.
|
||||
|
||||
The design combines familiar themes from institutional and organizational economics:
|
||||
|
||||
- Resource governance in shared systems (Ostrom, 1990)
|
||||
- Contracting under incomplete enforcement (Williamson, 1985; Hart and Holmstrom, 1987)
|
||||
- Incentive frictions between owners and operators (Jensen and Meckling, 1976)
|
||||
|
||||
For AI-native firms, these themes map to practical concerns: who captures model-inference rents, how autonomous teams handle debt-like conditions, and whether cooperative structures (e.g., pooled validation, service contracts) emerge without centralized intervention.
|
||||
|
||||
## 2. System Overview
|
||||
|
||||
The implementation under study is the current `sim-package` codebase:
|
||||
|
||||
- `sim-engine` (Rust): authoritative state machine, ledger, block production, contract settlement, fee and dividend accounting
|
||||
- `sim-orchestrator` (Python): reference LLM turn loop (not used directly for the experiments here)
|
||||
|
||||
The world initializes 8 agents, each with 1,000 tokens, and supports actions including `mine`, `stake`, `unstake`, `burn`, `study`, `job`, and `transfer`. Blocks are produced by weighted lottery subject to threshold and ordering constraints; in the current policy runs, realized block production is dominated by mine-based contributions.
|
||||
|
||||
### 2.1 Economic Mechanisms Relevant to This Study
|
||||
|
||||
- **Compute billing**: inference units above a commons threshold incur token fees
|
||||
- **Debt friction**: negative balances incur per-turn interest
|
||||
- **Validation rewards**: block fees accrue to winning validator
|
||||
- **Core-share dividends**: inference-fee pools can be redistributed to core-share owners
|
||||
- **Contract layer**: three-party contracts exist but require explicit proposer/counterparty/arbitrator coordination
|
||||
|
||||
Because our policy harness does not execute contract proposals in this experiment set, observed contract settlement volume is zero; this is analytically useful because it isolates the base dynamics of compute pricing and validation incentives.
|
||||
|
||||
## 3. Method
|
||||
|
||||
### 3.1 Experimental Design
|
||||
|
||||
We executed **24 independent runs**, each of **60 turns**, against the production Rust engine binary (`sim-engine/target/release/sim-engine`). Each run used a fresh SQLite ledger and isolated engine process on a unique local port.
|
||||
|
||||
- Initial agents: 8 (`agent_0` ... `agent_7`)
|
||||
- Initial endowment: 1,000 tokens per agent
|
||||
- Total initial nominal supply: 8,000 tokens
|
||||
- World config values matched orchestrator defaults (with block threshold 20.0)
|
||||
|
||||
The experiment runner is in `paper/run_sim_experiments.py`, and outputs are written to:
|
||||
|
||||
- `paper/results/run_metrics.csv`
|
||||
- `paper/results/summary.json`
|
||||
|
||||
### 3.2 Action Policy
|
||||
|
||||
To ensure reproducibility without external model dependencies, agent actions were generated by a deterministic rule policy conditioned on current balance/stake and turn index. Policies prioritize:
|
||||
|
||||
1. `job` when liquidity stress is high (balance below threshold)
|
||||
2. periodic `unstake` and occasional `stake`/`burn` under surplus
|
||||
3. fallback `mine`
|
||||
|
||||
Inference unit usage per agent-turn is sampled from bounded integer ranges to activate billing logic and preserve cross-run stochasticity.
|
||||
|
||||
### 3.3 Metrics
|
||||
|
||||
Per run, we capture:
|
||||
|
||||
- macro state: final token supply, total inference fees, block production count
|
||||
- agent distribution: min/median/max/mean balance, count of negative-balance agents
|
||||
- behavior volume: action counts by type
|
||||
- ledger activity: transaction counts and validator-type blocks
|
||||
- inequality: Gini and top-1 share
|
||||
|
||||
#### Handling Negative Wealth for Inequality
|
||||
|
||||
Since many runs end with negative balances for most agents, raw Gini over signed wealth can become negative and difficult to interpret. We therefore report shifted inequality measures as primary:
|
||||
|
||||
1. Add constant shift `s = 1 - min(wealth)` per run, making all wealth strictly positive.
|
||||
2. Compute Gini and top-1 share on shifted wealth.
|
||||
|
||||
This preserves within-run rank and spread while providing interpretable concentration metrics.
|
||||
|
||||
## 4. Results
|
||||
|
||||
### 4.1 Aggregate Outcomes (24 runs x 60 turns)
|
||||
|
||||
Key means (95% CI in parentheses):
|
||||
|
||||
- **Final token supply**: -872.29 (+/-87.37)
|
||||
- **Total inference fees collected**: 24,314.04 (+/-277.55)
|
||||
- **Turns with blocks**: 54.75 (+/-0.36)
|
||||
- **Negative-balance agents**: 7.96 of 8 (+/-0.08)
|
||||
- **Shifted Gini (wealth)**: 0.373 (+/-0.035)
|
||||
- **Shifted top-1 wealth share**: 0.257 (+/-0.023)
|
||||
|
||||
Extremes across runs:
|
||||
|
||||
- Token supply final: min -1,287, max -512
|
||||
- Shifted Gini: min 0.254, max 0.607
|
||||
- Shifted top-1 share: min 0.185, max 0.414
|
||||
|
||||
### 4.2 Behavioral Composition
|
||||
|
||||
Mean action counts per run:
|
||||
|
||||
- `mine`: 263.58
|
||||
- `job`: 187.83
|
||||
- `unstake`: 16.00
|
||||
- `stake`: 8.13
|
||||
- `study`: 2.58
|
||||
- `burn`: 1.88
|
||||
- `transfer`: 0.00
|
||||
|
||||
As shares of all actions:
|
||||
|
||||
- `mine` share: 0.549 (+/-0.006)
|
||||
- `job` share: 0.391 (+/-0.007)
|
||||
|
||||
Thus, **~94% of behavior mass is concentrated in `mine` + `job`**, indicating a narrow adaptation channel centered on short-horizon liquidity maintenance and fee-relevant validation behavior.
|
||||
|
||||
### 4.3 Ledger and Validation Structure
|
||||
|
||||
- Mean mine-validated blocks: 54.75
|
||||
- Mean stake-validated blocks: 0.00
|
||||
- Mean burn-validated blocks: 0.00
|
||||
|
||||
Transaction activity mirrors this concentration:
|
||||
|
||||
- `stake` tx mean: 8.13
|
||||
- `burn` tx mean: 1.88
|
||||
- `unstake` tx mean: 0.00 finalized (despite action attempts)
|
||||
- `transfer`, `study`, `job` tx means: 0.00 on-chain in this configuration
|
||||
|
||||
The divergence between attempted actions and on-chain finalized tx types suggests that many adaptive responses occur either as non-tx state transitions, are not encoded as standalone ledger transactions, or fail to finalize under current thresholds and conditions.
|
||||
|
||||
### 4.4 Contract Layer Utilization
|
||||
|
||||
Across all runs:
|
||||
|
||||
- settled contracts: 0
|
||||
- defaulted contracts: 0
|
||||
|
||||
Given contract complexity and absence of explicit proposal heuristics in policy, no endogenous contracting was observed. This result is itself informative: contract affordances do not self-activate under pure liquidity pressure without stronger coordination incentives.
|
||||
|
||||
## 5. Interpretation for AI-Run Companies
|
||||
|
||||
### 5.1 Compute Pricing Can Dominate Strategy Space
|
||||
|
||||
Inference billing extracts value faster than available replenishment channels in this regime. The system starts with 8,000 tokens and ends, on average, at -872 net supply representation in balances+stake accounting. This indicates sustained pressure toward debt states, even while blocks are reliably produced. For AI firms, the analog is straightforward: if cognitive operating expenditure is continuously metered but productivity gains are weakly linked to billing outflows, autonomous units converge to survival behavior instead of investment behavior.
|
||||
|
||||
### 5.2 Survival Equilibria Are Narrow and Potentially Fragile
|
||||
|
||||
The observed concentration into `mine` and `job` resembles a minimum-viability loop:
|
||||
|
||||
1. produce immediate fee-relevant behavior (`mine`)
|
||||
2. use fallback labor subsidy (`job`) to stabilize short-term liquidity
|
||||
|
||||
This reduces exploration (`study`) and suppresses peer transfer/contracting. In corporate terms, this resembles organizations that over-index on cash-preserving routine work while underinvesting in capability accumulation and inter-team contracting.
|
||||
|
||||
### 5.3 Governance Rents Need Guardrails
|
||||
|
||||
`sim-economy` includes fee redistribution to core-share owners, a useful model for platform-like infra ownership inside a firm. But if fee extraction is high and productive reinvestment pathways are weak, ownership rents can amplify liquidity asymmetries. The shifted top-1 wealth share range (0.185 to 0.414) shows sizable cross-run concentration risk even under simple policies.
|
||||
|
||||
Implication: AI-run enterprises that tokenize internal compute should pair metering with explicit anti-extractive controls (e.g., fee rebates tied to productive outputs, debt caps, or investment credits).
|
||||
|
||||
### 5.4 Contracts Do Not Emerge Automatically
|
||||
|
||||
Even with a rich contract system, no contracts activated under this pressure profile. This aligns with transaction-cost reasoning: when agents are liquidity-constrained and coordination is costly, bilateral or tri-party structures may not emerge without institutional scaffolding. For practice, this implies that autonomous business units may need explicit contract templates, matchmaking, or subsidized arbitration to move from spot behavior to structured collaboration.
|
||||
|
||||
## 6. Design Recommendations
|
||||
|
||||
Based on these results, we propose five practical interventions for AI-run firms adopting compute-metered internal economies:
|
||||
|
||||
1. **Bound effective fee load**: cap fee-to-endowment ratio over rolling windows.
|
||||
2. **Reward productive reinvestment**: tie rebates to validated long-horizon actions (`study`, contract fulfillment).
|
||||
3. **Debt safety rails**: enforce graduated debt service with restructuring triggers before deep negative spirals.
|
||||
4. **Contract scaffolding**: provide low-friction default contracts and reputational priors for arbitrators.
|
||||
5. **Concentration monitoring**: track top-share and Gini metrics continuously; trigger policy updates when concentration crosses thresholds.
|
||||
|
||||
## 7. Limitations
|
||||
|
||||
This paper intentionally studies engine dynamics with a reproducible policy harness rather than live LLM strategic reasoning. Therefore, conclusions are strongest for mechanism-level behavior under stochastic but rule-based action generation. Additional runs with model-driven orchestration, active contract heuristics, and explicit core-share auction settlement in-engine would broaden external validity.
|
||||
|
||||
Other caveats:
|
||||
|
||||
- inequality metrics require positive shifting under negative wealth regimes
|
||||
- run horizon is 60 turns; longer-horizon phase shifts are not measured here
|
||||
- no exogenous demand or productivity shocks were introduced
|
||||
|
||||
## 8. Conclusion
|
||||
|
||||
Using repeated, instrumented runs of the production `sim-economy` engine, we find that compute-metered autonomous economies can maintain technical liveness (steady block production) while simultaneously drifting into economically stressed states (widespread negative balances) and narrowed behavior repertoires (`mine` + `job`). Concentration remains material even after controlling for negative-wealth measurement artifacts.
|
||||
|
||||
For AI-run companies, the core lesson is institutional: metering alone is not governance. Without deliberate policy around fee recycling, debt management, and contract activation, autonomous agents appear likely to converge to short-horizon survival loops rather than compounding organizational capability.
|
||||
|
||||
## Reproducibility Appendix
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
cargo build --release --manifest-path sim-engine/Cargo.toml
|
||||
python3 paper/run_sim_experiments.py --runs 24 --turns 60
|
||||
```
|
||||
|
||||
Primary artifacts:
|
||||
|
||||
- `paper/results/run_metrics.csv`
|
||||
- `paper/results/summary.json`
|
||||
|
||||
## References
|
||||
|
||||
- Hart, O., and Holmstrom, B. (1987). The theory of contracts. In *Advances in Economic Theory: Fifth World Congress*.
|
||||
- Jensen, M. C., and Meckling, W. H. (1976). Theory of the firm: Managerial behavior, agency costs and ownership structure. *Journal of Financial Economics*, 3(4), 305-360.
|
||||
- Ostrom, E. (1990). *Governing the Commons: The Evolution of Institutions for Collective Action*. Cambridge University Press.
|
||||
- Williamson, O. E. (1985). *The Economic Institutions of Capitalism*. Free Press.
|
||||
Reference in New Issue
Block a user