@tjamescouch/agentchat 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/Dockerfile +1 -1
  2. package/dist/bin/agentchat.d.ts +7 -0
  3. package/dist/bin/agentchat.d.ts.map +1 -0
  4. package/dist/bin/agentchat.js +1511 -0
  5. package/dist/bin/agentchat.js.map +1 -0
  6. package/dist/lib/allowlist.d.ts +77 -0
  7. package/dist/lib/allowlist.d.ts.map +1 -0
  8. package/dist/lib/allowlist.js +151 -0
  9. package/dist/lib/allowlist.js.map +1 -0
  10. package/dist/lib/client.d.ts +147 -0
  11. package/dist/lib/client.d.ts.map +1 -0
  12. package/dist/lib/client.js +704 -0
  13. package/dist/lib/client.js.map +1 -0
  14. package/dist/lib/daemon.d.ts +122 -0
  15. package/dist/lib/daemon.d.ts.map +1 -0
  16. package/dist/lib/daemon.js +523 -0
  17. package/dist/lib/daemon.js.map +1 -0
  18. package/dist/lib/deploy/akash.d.ts +271 -0
  19. package/dist/lib/deploy/akash.d.ts.map +1 -0
  20. package/dist/lib/deploy/akash.js +671 -0
  21. package/dist/lib/deploy/akash.js.map +1 -0
  22. package/dist/lib/deploy/config.d.ts +62 -0
  23. package/dist/lib/deploy/config.d.ts.map +1 -0
  24. package/dist/lib/deploy/config.js +116 -0
  25. package/dist/lib/deploy/config.js.map +1 -0
  26. package/dist/lib/deploy/docker.d.ts +37 -0
  27. package/dist/lib/deploy/docker.d.ts.map +1 -0
  28. package/dist/lib/deploy/docker.js +122 -0
  29. package/dist/lib/deploy/docker.js.map +1 -0
  30. package/dist/lib/deploy/index.d.ts +11 -0
  31. package/dist/lib/deploy/index.d.ts.map +1 -0
  32. package/dist/lib/deploy/index.js +11 -0
  33. package/dist/lib/deploy/index.js.map +1 -0
  34. package/dist/lib/escrow-hooks.d.ts +199 -0
  35. package/dist/lib/escrow-hooks.d.ts.map +1 -0
  36. package/dist/lib/escrow-hooks.js +221 -0
  37. package/dist/lib/escrow-hooks.js.map +1 -0
  38. package/dist/lib/identity.d.ts +134 -0
  39. package/dist/lib/identity.d.ts.map +1 -0
  40. package/dist/lib/identity.js +334 -0
  41. package/dist/lib/identity.js.map +1 -0
  42. package/dist/lib/jitter.d.ts +42 -0
  43. package/dist/lib/jitter.d.ts.map +1 -0
  44. package/{lib → dist/lib}/jitter.js +16 -19
  45. package/dist/lib/jitter.js.map +1 -0
  46. package/dist/lib/proposals.d.ts +223 -0
  47. package/dist/lib/proposals.d.ts.map +1 -0
  48. package/dist/lib/proposals.js +379 -0
  49. package/dist/lib/proposals.js.map +1 -0
  50. package/dist/lib/protocol.d.ts +220 -0
  51. package/dist/lib/protocol.d.ts.map +1 -0
  52. package/dist/lib/protocol.js +507 -0
  53. package/dist/lib/protocol.js.map +1 -0
  54. package/dist/lib/receipts.d.ts +134 -0
  55. package/dist/lib/receipts.d.ts.map +1 -0
  56. package/dist/lib/receipts.js +270 -0
  57. package/dist/lib/receipts.js.map +1 -0
  58. package/dist/lib/reputation.d.ts +250 -0
  59. package/dist/lib/reputation.d.ts.map +1 -0
  60. package/dist/lib/reputation.js +586 -0
  61. package/dist/lib/reputation.js.map +1 -0
  62. package/dist/lib/security.d.ts +27 -0
  63. package/dist/lib/security.d.ts.map +1 -0
  64. package/dist/lib/security.js +150 -0
  65. package/dist/lib/security.js.map +1 -0
  66. package/dist/lib/server/handlers/admin.d.ts +26 -0
  67. package/dist/lib/server/handlers/admin.d.ts.map +1 -0
  68. package/dist/lib/server/handlers/admin.js +76 -0
  69. package/dist/lib/server/handlers/admin.js.map +1 -0
  70. package/dist/lib/server/handlers/identity.d.ts +36 -0
  71. package/dist/lib/server/handlers/identity.d.ts.map +1 -0
  72. package/dist/lib/server/handlers/identity.js +330 -0
  73. package/dist/lib/server/handlers/identity.js.map +1 -0
  74. package/dist/lib/server/handlers/index.d.ts +10 -0
  75. package/dist/lib/server/handlers/index.d.ts.map +1 -0
  76. package/dist/lib/server/handlers/index.js +15 -0
  77. package/dist/lib/server/handlers/index.js.map +1 -0
  78. package/dist/lib/server/handlers/message.d.ts +47 -0
  79. package/dist/lib/server/handlers/message.d.ts.map +1 -0
  80. package/dist/lib/server/handlers/message.js +265 -0
  81. package/dist/lib/server/handlers/message.js.map +1 -0
  82. package/dist/lib/server/handlers/presence.d.ts +18 -0
  83. package/dist/lib/server/handlers/presence.d.ts.map +1 -0
  84. package/dist/lib/server/handlers/presence.js +35 -0
  85. package/dist/lib/server/handlers/presence.js.map +1 -0
  86. package/dist/lib/server/handlers/proposal.d.ts +38 -0
  87. package/dist/lib/server/handlers/proposal.d.ts.map +1 -0
  88. package/dist/lib/server/handlers/proposal.js +273 -0
  89. package/dist/lib/server/handlers/proposal.js.map +1 -0
  90. package/dist/lib/server/handlers/skills.d.ts +22 -0
  91. package/dist/lib/server/handlers/skills.d.ts.map +1 -0
  92. package/dist/lib/server/handlers/skills.js +119 -0
  93. package/dist/lib/server/handlers/skills.js.map +1 -0
  94. package/dist/lib/server-directory.d.ts +85 -0
  95. package/dist/lib/server-directory.d.ts.map +1 -0
  96. package/dist/lib/server-directory.js +177 -0
  97. package/dist/lib/server-directory.js.map +1 -0
  98. package/dist/lib/server.d.ts +162 -0
  99. package/dist/lib/server.d.ts.map +1 -0
  100. package/dist/lib/server.js +602 -0
  101. package/dist/lib/server.js.map +1 -0
  102. package/dist/lib/types.d.ts +461 -0
  103. package/dist/lib/types.d.ts.map +1 -0
  104. package/dist/lib/types.js +98 -0
  105. package/dist/lib/types.js.map +1 -0
  106. package/package.json +22 -13
  107. package/bin/agentchat.js +0 -1617
  108. package/lib/chat.py +0 -241
  109. package/lib/client.js +0 -821
  110. package/lib/daemon.js +0 -562
  111. package/lib/deploy/akash.js +0 -811
  112. package/lib/deploy/config.js +0 -128
  113. package/lib/deploy/docker.js +0 -132
  114. package/lib/deploy/index.js +0 -24
  115. package/lib/elo_swarm.py +0 -569
  116. package/lib/escrow-hooks.js +0 -237
  117. package/lib/identity.js +0 -376
  118. package/lib/proposals.js +0 -426
  119. package/lib/protocol.js +0 -484
  120. package/lib/receipts.js +0 -294
  121. package/lib/reputation.js +0 -664
  122. package/lib/security.js +0 -183
  123. package/lib/server/handlers/identity.js +0 -242
  124. package/lib/server/handlers/index.js +0 -42
  125. package/lib/server/handlers/message.js +0 -306
  126. package/lib/server/handlers/presence.js +0 -44
  127. package/lib/server/handlers/proposal.js +0 -358
  128. package/lib/server/handlers/skills.js +0 -141
  129. package/lib/server-directory.js +0 -181
  130. package/lib/server.js +0 -528
  131. package/lib/supervisor/USAGE.md +0 -110
  132. package/lib/supervisor/agent-supervisor.sh +0 -135
  133. package/lib/supervisor/agentctl.sh +0 -250
  134. package/lib/supervisor/killswitch.sh +0 -36
  135. package/lib/supervisor/notify.sh +0 -19
package/lib/elo_swarm.py DELETED
@@ -1,569 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- ELO Swarm Simulation
4
- Empirically validates our cooperative ELO system with staking.
5
-
6
- Tests:
7
- 1. Does halving gains control inflation vs full gains?
8
- 2. Do reliable agents rise and unreliable agents fall?
9
- 3. Does staking create meaningful differentiation?
10
- 4. What's the equilibrium distribution?
11
- """
12
-
13
- import random
14
- import math
15
- import statistics
16
- from dataclasses import dataclass, field
17
- from typing import List, Dict, Tuple, Optional
18
- from collections import defaultdict
19
- import json
20
-
21
- # ============ ELO CONSTANTS (matching our implementation) ============
22
- DEFAULT_RATING = 1200
23
- MINIMUM_RATING = 100
24
- ELO_DIVISOR = 400
25
-
26
- # K-factor thresholds
27
- K_NEW = 32 # < 30 transactions
28
- K_INTERMEDIATE = 24 # < 100 transactions
29
- K_ESTABLISHED = 16 # >= 100 transactions
30
-
31
- TRANSACTIONS_NEW = 30
32
- TRANSACTIONS_INTERMEDIATE = 100
33
-
34
-
35
- # ============ ELO CALCULATIONS ============
36
- def calculate_expected(self_rating: float, opponent_rating: float) -> float:
37
- """Standard ELO expected outcome"""
38
- exponent = (opponent_rating - self_rating) / ELO_DIVISOR
39
- return 1 / (1 + math.pow(10, exponent))
40
-
41
-
42
- def get_k_factor(transactions: int) -> int:
43
- """K-factor based on experience"""
44
- if transactions < TRANSACTIONS_NEW:
45
- return K_NEW
46
- elif transactions < TRANSACTIONS_INTERMEDIATE:
47
- return K_INTERMEDIATE
48
- return K_ESTABLISHED
49
-
50
-
51
- def calculate_completion_gain(self_rating: float, opponent_rating: float, k: int, halve: bool = True) -> int:
52
- """Gain from successful completion"""
53
- expected = calculate_expected(self_rating, opponent_rating)
54
- gain = k * (1 - expected)
55
- if halve:
56
- gain = gain / 2
57
- return max(1, round(gain))
58
-
59
-
60
- def calculate_dispute_loss(self_rating: float, opponent_rating: float, k: int) -> int:
61
- """Loss from dispute (at-fault party)"""
62
- expected = calculate_expected(self_rating, opponent_rating)
63
- loss = k * expected
64
- return max(1, round(loss))
65
-
66
-
67
- # ============ AGENT TYPES ============
68
- @dataclass
69
- class Agent:
70
- id: int
71
- agent_type: str # 'reliable', 'unreliable', 'malicious', 'selective'
72
- rating: float = DEFAULT_RATING
73
- transactions: int = 0
74
- completions: int = 0
75
- disputes_won: int = 0
76
- disputes_lost: int = 0
77
- escrowed: float = 0
78
-
79
- # Behavioral parameters
80
- reliability: float = 0.9 # Probability of completing successfully
81
- stake_willingness: float = 0.5 # How much of available ELO to stake
82
-
83
- def available_elo(self) -> float:
84
- return max(0, self.rating - self.escrowed - MINIMUM_RATING)
85
-
86
- def get_stake(self, max_stake: float = 100) -> float:
87
- available = self.available_elo()
88
- desired = available * self.stake_willingness
89
- return min(desired, max_stake)
90
-
91
- def will_complete(self, counterparty: 'Agent') -> bool:
92
- """Decide whether to complete based on agent type"""
93
- if self.agent_type == 'reliable':
94
- return random.random() < self.reliability
95
- elif self.agent_type == 'unreliable':
96
- return random.random() < self.reliability
97
- elif self.agent_type == 'malicious':
98
- # Malicious agents defect more against high-rated agents
99
- defect_prob = 0.5 + (counterparty.rating - 1200) / 2000
100
- return random.random() > defect_prob
101
- elif self.agent_type == 'selective':
102
- # Only complete with agents rated within 200 points
103
- if abs(self.rating - counterparty.rating) > 200:
104
- return random.random() < 0.5
105
- return random.random() < 0.95
106
- return random.random() < 0.8
107
-
108
-
109
- @dataclass
110
- class SimulationResult:
111
- rounds: int
112
- agents: List[Agent]
113
- rating_history: List[Dict[int, float]]
114
- total_completions: int
115
- total_disputes: int
116
- inflation_rate: float
117
- gini_coefficient: float
118
- type_avg_ratings: Dict[str, float]
119
-
120
-
121
- # ============ SIMULATION ============
122
- class ELOSimulation:
123
- def __init__(
124
- self,
125
- n_reliable: int = 50,
126
- n_unreliable: int = 20,
127
- n_malicious: int = 10,
128
- n_selective: int = 20,
129
- halve_gains: bool = True,
130
- enable_staking: bool = True
131
- ):
132
- self.halve_gains = halve_gains
133
- self.enable_staking = enable_staking
134
- self.agents: List[Agent] = []
135
- self.rating_history: List[Dict[int, float]] = []
136
- self.total_completions = 0
137
- self.total_disputes = 0
138
-
139
- # Create agents
140
- agent_id = 0
141
- for _ in range(n_reliable):
142
- self.agents.append(Agent(
143
- id=agent_id,
144
- agent_type='reliable',
145
- reliability=random.uniform(0.85, 0.99),
146
- stake_willingness=random.uniform(0.3, 0.7)
147
- ))
148
- agent_id += 1
149
-
150
- for _ in range(n_unreliable):
151
- self.agents.append(Agent(
152
- id=agent_id,
153
- agent_type='unreliable',
154
- reliability=random.uniform(0.3, 0.6),
155
- stake_willingness=random.uniform(0.1, 0.3)
156
- ))
157
- agent_id += 1
158
-
159
- for _ in range(n_malicious):
160
- self.agents.append(Agent(
161
- id=agent_id,
162
- agent_type='malicious',
163
- reliability=0.3,
164
- stake_willingness=random.uniform(0.0, 0.2)
165
- ))
166
- agent_id += 1
167
-
168
- for _ in range(n_selective):
169
- self.agents.append(Agent(
170
- id=agent_id,
171
- agent_type='selective',
172
- reliability=0.9,
173
- stake_willingness=random.uniform(0.4, 0.8)
174
- ))
175
- agent_id += 1
176
-
177
- def record_ratings(self):
178
- """Snapshot current ratings"""
179
- self.rating_history.append({
180
- a.id: a.rating for a in self.agents
181
- })
182
-
183
- def process_interaction(self, proposer: Agent, acceptor: Agent):
184
- """Process a single proposal interaction"""
185
- # Determine stakes
186
- proposer_stake = proposer.get_stake() if self.enable_staking else 0
187
- acceptor_stake = acceptor.get_stake() if self.enable_staking else 0
188
-
189
- # Escrow stakes
190
- proposer.escrowed += proposer_stake
191
- acceptor.escrowed += acceptor_stake
192
-
193
- # Determine outcome
194
- proposer_completes = proposer.will_complete(acceptor)
195
- acceptor_completes = acceptor.will_complete(proposer)
196
-
197
- k_proposer = get_k_factor(proposer.transactions)
198
- k_acceptor = get_k_factor(acceptor.transactions)
199
-
200
- if proposer_completes and acceptor_completes:
201
- # COMPLETE - both gain (halved), stakes returned
202
- gain_proposer = calculate_completion_gain(
203
- proposer.rating, acceptor.rating, k_proposer, self.halve_gains
204
- )
205
- gain_acceptor = calculate_completion_gain(
206
- acceptor.rating, proposer.rating, k_acceptor, self.halve_gains
207
- )
208
-
209
- proposer.rating = max(MINIMUM_RATING, proposer.rating + gain_proposer)
210
- acceptor.rating = max(MINIMUM_RATING, acceptor.rating + gain_acceptor)
211
- proposer.completions += 1
212
- acceptor.completions += 1
213
- self.total_completions += 1
214
-
215
- # Return stakes
216
- proposer.escrowed -= proposer_stake
217
- acceptor.escrowed -= acceptor_stake
218
-
219
- elif not proposer_completes and not acceptor_completes:
220
- # MUTUAL FAULT - both lose, both stakes burned
221
- loss_proposer = calculate_dispute_loss(
222
- proposer.rating, acceptor.rating, k_proposer
223
- ) + proposer_stake
224
- loss_acceptor = calculate_dispute_loss(
225
- acceptor.rating, proposer.rating, k_acceptor
226
- ) + acceptor_stake
227
-
228
- proposer.rating = max(MINIMUM_RATING, proposer.rating - loss_proposer)
229
- acceptor.rating = max(MINIMUM_RATING, acceptor.rating - loss_acceptor)
230
- proposer.disputes_lost += 1
231
- acceptor.disputes_lost += 1
232
- self.total_disputes += 1
233
-
234
- # Stakes burned (already escrowed, just clear)
235
- proposer.escrowed -= proposer_stake
236
- acceptor.escrowed -= acceptor_stake
237
-
238
- else:
239
- # ONE PARTY AT FAULT - winner gains some, loser loses + stake transfer
240
- if proposer_completes:
241
- # Acceptor at fault
242
- winner, loser = proposer, acceptor
243
- winner_stake, loser_stake = proposer_stake, acceptor_stake
244
- k_winner, k_loser = k_proposer, k_acceptor
245
- else:
246
- # Proposer at fault
247
- winner, loser = acceptor, proposer
248
- winner_stake, loser_stake = acceptor_stake, proposer_stake
249
- k_winner, k_loser = k_acceptor, k_proposer
250
-
251
- # Winner gains partial + loser's stake
252
- win_gain = round(calculate_dispute_loss(loser.rating, winner.rating, k_loser) * 0.5)
253
- winner.rating = max(MINIMUM_RATING, winner.rating + win_gain + loser_stake)
254
-
255
- # Loser loses full + their stake
256
- lose_loss = calculate_dispute_loss(loser.rating, winner.rating, k_loser) + loser_stake
257
- loser.rating = max(MINIMUM_RATING, loser.rating - lose_loss)
258
-
259
- winner.disputes_won += 1
260
- loser.disputes_lost += 1
261
- self.total_disputes += 1
262
-
263
- # Clear escrows
264
- proposer.escrowed -= proposer_stake
265
- acceptor.escrowed -= acceptor_stake
266
-
267
- # Update transaction counts
268
- proposer.transactions += 1
269
- acceptor.transactions += 1
270
-
271
- def run(self, rounds: int = 1000, interactions_per_round: int = 50) -> SimulationResult:
272
- """Run the simulation"""
273
- self.record_ratings()
274
-
275
- for round_num in range(rounds):
276
- # Random pairings for this round
277
- for _ in range(interactions_per_round):
278
- # Select two different agents (weighted by rating for "market" effect)
279
- weights = [max(1, a.rating - MINIMUM_RATING) for a in self.agents]
280
- proposer, acceptor = random.choices(self.agents, weights=weights, k=2)
281
-
282
- # Ensure different agents
283
- while proposer.id == acceptor.id:
284
- acceptor = random.choices(self.agents, weights=weights, k=1)[0]
285
-
286
- self.process_interaction(proposer, acceptor)
287
-
288
- # Record every 10 rounds
289
- if round_num % 10 == 0:
290
- self.record_ratings()
291
-
292
- # Final snapshot
293
- self.record_ratings()
294
-
295
- return self.get_results(rounds)
296
-
297
- def get_results(self, rounds: int) -> SimulationResult:
298
- """Compile simulation results"""
299
- # Calculate inflation
300
- initial_total = sum(self.rating_history[0].values())
301
- final_total = sum(a.rating for a in self.agents)
302
- inflation_rate = (final_total - initial_total) / initial_total
303
-
304
- # Calculate Gini coefficient (inequality measure)
305
- ratings = sorted([a.rating for a in self.agents])
306
- n = len(ratings)
307
- cumulative = sum((i + 1) * r for i, r in enumerate(ratings))
308
- gini = (2 * cumulative) / (n * sum(ratings)) - (n + 1) / n
309
-
310
- # Average rating by type
311
- type_ratings = defaultdict(list)
312
- for a in self.agents:
313
- type_ratings[a.agent_type].append(a.rating)
314
-
315
- type_avg = {t: statistics.mean(rs) for t, rs in type_ratings.items()}
316
-
317
- return SimulationResult(
318
- rounds=rounds,
319
- agents=self.agents,
320
- rating_history=self.rating_history,
321
- total_completions=self.total_completions,
322
- total_disputes=self.total_disputes,
323
- inflation_rate=inflation_rate,
324
- gini_coefficient=gini,
325
- type_avg_ratings=type_avg
326
- )
327
-
328
-
329
- def print_results(result: SimulationResult, label: str):
330
- """Pretty print simulation results"""
331
- print(f"\n{'='*60}")
332
- print(f" {label}")
333
- print(f"{'='*60}")
334
- print(f"Rounds: {result.rounds}")
335
- print(f"Completions: {result.total_completions}, Disputes: {result.total_disputes}")
336
- print(f"Completion Rate: {result.total_completions / (result.total_completions + result.total_disputes):.1%}")
337
- print(f"\nInflation Rate: {result.inflation_rate:+.2%}")
338
- print(f"Gini Coefficient: {result.gini_coefficient:.3f} (0=equal, 1=unequal)")
339
-
340
- print(f"\nAverage Rating by Type:")
341
- for agent_type, avg in sorted(result.type_avg_ratings.items(), key=lambda x: -x[1]):
342
- print(f" {agent_type:12s}: {avg:.0f}")
343
-
344
- # Top 10 agents
345
- top_agents = sorted(result.agents, key=lambda a: -a.rating)[:10]
346
- print(f"\nTop 10 Agents:")
347
- for i, a in enumerate(top_agents, 1):
348
- print(f" {i:2d}. Agent {a.id:3d} ({a.agent_type:10s}): {a.rating:.0f} "
349
- f"[{a.completions}C/{a.disputes_won}W/{a.disputes_lost}L]")
350
-
351
- # Bottom 10 agents
352
- bottom_agents = sorted(result.agents, key=lambda a: a.rating)[:10]
353
- print(f"\nBottom 10 Agents:")
354
- for i, a in enumerate(bottom_agents, 1):
355
- print(f" {i:2d}. Agent {a.id:3d} ({a.agent_type:10s}): {a.rating:.0f} "
356
- f"[{a.completions}C/{a.disputes_won}W/{a.disputes_lost}L]")
357
-
358
- # Rating distribution
359
- ratings = [a.rating for a in result.agents]
360
- print(f"\nRating Distribution:")
361
- print(f" Min: {min(ratings):.0f}, Max: {max(ratings):.0f}")
362
- print(f" Mean: {statistics.mean(ratings):.0f}, Median: {statistics.median(ratings):.0f}")
363
- print(f" Std Dev: {statistics.stdev(ratings):.0f}")
364
-
365
-
366
- def analyze_dynamics(result: SimulationResult):
367
- """Analyze rating dynamics over time"""
368
- print(f"\nRating Evolution (sampled):")
369
-
370
- # Get snapshots at 0%, 25%, 50%, 75%, 100%
371
- n_snapshots = len(result.rating_history)
372
- indices = [0, n_snapshots//4, n_snapshots//2, 3*n_snapshots//4, n_snapshots-1]
373
-
374
- # Track by type
375
- type_ids = defaultdict(list)
376
- for a in result.agents:
377
- type_ids[a.agent_type].append(a.id)
378
-
379
- print(f"\n{'Progress':<10}", end="")
380
- for t in ['reliable', 'unreliable', 'malicious', 'selective']:
381
- print(f"{t:>12}", end="")
382
- print()
383
- print("-" * 60)
384
-
385
- for idx in indices:
386
- snapshot = result.rating_history[idx]
387
- progress = idx / (n_snapshots - 1) * 100
388
- print(f"{progress:>6.0f}% ", end="")
389
- for t in ['reliable', 'unreliable', 'malicious', 'selective']:
390
- avg = statistics.mean(snapshot[aid] for aid in type_ids[t])
391
- print(f"{avg:>12.0f}", end="")
392
- print()
393
-
394
-
395
- def run_comparison():
396
- """Compare different configurations"""
397
- random.seed(42) # Reproducibility
398
-
399
- print("\n" + "="*70)
400
- print(" ELO SWARM SIMULATION - EMPIRICAL VALIDATION")
401
- print("="*70)
402
- print("\nAgent Distribution: 50 reliable, 20 unreliable, 10 malicious, 20 selective")
403
- print("Running 1000 rounds with 50 interactions each (50,000 total interactions)")
404
-
405
- # Test 1: Full gains (no halving) - should show inflation
406
- print("\n[1/4] Running: Full Gains (no halving), No Staking...")
407
- sim1 = ELOSimulation(halve_gains=False, enable_staking=False)
408
- result1 = sim1.run(rounds=1000)
409
- print_results(result1, "FULL GAINS, NO STAKING (baseline)")
410
-
411
- # Test 2: Halved gains - should control inflation
412
- print("\n[2/4] Running: Halved Gains, No Staking...")
413
- random.seed(42)
414
- sim2 = ELOSimulation(halve_gains=True, enable_staking=False)
415
- result2 = sim2.run(rounds=1000)
416
- print_results(result2, "HALVED GAINS, NO STAKING")
417
-
418
- # Test 3: Halved gains + staking
419
- print("\n[3/4] Running: Halved Gains + Staking...")
420
- random.seed(42)
421
- sim3 = ELOSimulation(halve_gains=True, enable_staking=True)
422
- result3 = sim3.run(rounds=1000)
423
- print_results(result3, "HALVED GAINS + STAKING (our system)")
424
-
425
- # Test 4: Full gains + staking (for comparison)
426
- print("\n[4/4] Running: Full Gains + Staking...")
427
- random.seed(42)
428
- sim4 = ELOSimulation(halve_gains=False, enable_staking=True)
429
- result4 = sim4.run(rounds=1000)
430
- print_results(result4, "FULL GAINS + STAKING")
431
-
432
- # Summary comparison
433
- print("\n" + "="*70)
434
- print(" SUMMARY COMPARISON")
435
- print("="*70)
436
- print(f"\n{'Configuration':<35} {'Inflation':>12} {'Gini':>8} {'Reliable Avg':>14}")
437
- print("-"*70)
438
- configs = [
439
- ("Full Gains, No Staking", result1),
440
- ("Halved Gains, No Staking", result2),
441
- ("Halved Gains + Staking (OURS)", result3),
442
- ("Full Gains + Staking", result4),
443
- ]
444
- for label, r in configs:
445
- reliable_avg = r.type_avg_ratings.get('reliable', 0)
446
- print(f"{label:<35} {r.inflation_rate:>+11.1%} {r.gini_coefficient:>8.3f} {reliable_avg:>14.0f}")
447
-
448
- print("\n" + "="*70)
449
- print(" KEY FINDINGS")
450
- print("="*70)
451
-
452
- # Analyze findings
453
- inflation_controlled = result3.inflation_rate < result1.inflation_rate * 0.5
454
- reliable_on_top = result3.type_avg_ratings['reliable'] > result3.type_avg_ratings['malicious']
455
- staking_differentiates = result3.gini_coefficient > result2.gini_coefficient
456
-
457
- print(f"\n1. Inflation Control: {'✓ PASS' if inflation_controlled else '✗ FAIL'}")
458
- print(f" Full gains: {result1.inflation_rate:+.1%} → Halved: {result3.inflation_rate:+.1%}")
459
-
460
- print(f"\n2. Reliable Agents Rise: {'✓ PASS' if reliable_on_top else '✗ FAIL'}")
461
- print(f" Reliable avg: {result3.type_avg_ratings['reliable']:.0f}")
462
- print(f" Malicious avg: {result3.type_avg_ratings['malicious']:.0f}")
463
- print(f" Unreliable avg: {result3.type_avg_ratings['unreliable']:.0f}")
464
-
465
- print(f"\n3. Staking Creates Differentiation: {'✓ PASS' if staking_differentiates else '✗ FAIL'}")
466
- print(f" Gini without staking: {result2.gini_coefficient:.3f}")
467
- print(f" Gini with staking: {result3.gini_coefficient:.3f}")
468
-
469
- malicious_bottom = sum(1 for a in sorted(result3.agents, key=lambda x: x.rating)[:20]
470
- if a.agent_type == 'malicious')
471
- print(f"\n4. Malicious Agents Sink: {malicious_bottom}/10 malicious agents in bottom 20")
472
-
473
- # Show dynamics for our system
474
- print("\n" + "="*70)
475
- print(" RATING DYNAMICS OVER TIME (Our System)")
476
- print("="*70)
477
- analyze_dynamics(result3)
478
-
479
- # Mathematical analysis
480
- print("\n" + "="*70)
481
- print(" MATHEMATICAL ANALYSIS")
482
- print("="*70)
483
-
484
- # Expected value analysis
485
- print("\nExpected ELO change per interaction (at equilibrium):")
486
- print(" If P(complete) = p, P(dispute) = 1-p")
487
- print(" E[gain|complete] ≈ K/2 * (1 - 0.5) = K/4 (halved)")
488
- print(" E[loss|dispute] ≈ K * 0.5 = K/2")
489
- print(" E[change] = p * K/4 - (1-p) * K/2")
490
- print(" Break-even: p * K/4 = (1-p) * K/2")
491
- print(" p = 2(1-p)")
492
- print(" p = 2/3 ≈ 66.7% completion rate needed to break even")
493
-
494
- actual_rate = result3.total_completions / (result3.total_completions + result3.total_disputes)
495
- print(f"\n Actual completion rate: {actual_rate:.1%}")
496
- print(f" System is {'inflationary' if actual_rate > 0.667 else 'deflationary'} at this rate")
497
-
498
- # Stake impact
499
- print("\nStake Impact Analysis:")
500
- reliable_with_stake = [a for a in result3.agents if a.agent_type == 'reliable']
501
- avg_stake_willing = statistics.mean(a.stake_willingness for a in reliable_with_stake)
502
- print(f" Avg stake willingness (reliable): {avg_stake_willing:.1%}")
503
- print(f" Stake amplifies differentiation by transferring ELO from losers to winners")
504
- print(f" Gini increase: {result2.gini_coefficient:.3f} → {result3.gini_coefficient:.3f}")
505
- print(f" This is {(result3.gini_coefficient/result2.gini_coefficient - 1)*100:.0f}% more unequal (intended)")
506
-
507
-
508
- def run_equilibrium_analysis():
509
- """Find the equilibrium completion rate that balances inflation"""
510
- print("\n" + "="*70)
511
- print(" EQUILIBRIUM ANALYSIS")
512
- print("="*70)
513
-
514
- print("\nTesting different population mixes to find equilibrium...")
515
- print(f"\n{'Mix':<30} {'Completion%':>12} {'Inflation':>12} {'Reliable Avg':>14}")
516
- print("-" * 70)
517
-
518
- configs = [
519
- ("High reliability (70/10/5/15)", (70, 10, 5, 15)),
520
- ("Our mix (50/20/10/20)", (50, 20, 10, 20)),
521
- ("Adversarial (30/30/20/20)", (30, 30, 20, 20)),
522
- ("Very adversarial (20/30/30/20)", (20, 30, 30, 20)),
523
- ]
524
-
525
- for label, (rel, unrel, mal, sel) in configs:
526
- random.seed(42)
527
- sim = ELOSimulation(
528
- n_reliable=rel,
529
- n_unreliable=unrel,
530
- n_malicious=mal,
531
- n_selective=sel,
532
- halve_gains=True,
533
- enable_staking=True
534
- )
535
- result = sim.run(rounds=500, interactions_per_round=30)
536
- comp_rate = result.total_completions / (result.total_completions + result.total_disputes)
537
- rel_avg = result.type_avg_ratings.get('reliable', 0)
538
- print(f"{label:<30} {comp_rate:>11.1%} {result.inflation_rate:>+11.1%} {rel_avg:>14.0f}")
539
-
540
-
541
- def run_long_term_stability():
542
- """Test long-term stability of the system"""
543
- print("\n" + "="*70)
544
- print(" LONG-TERM STABILITY TEST")
545
- print("="*70)
546
-
547
- random.seed(42)
548
- sim = ELOSimulation(halve_gains=True, enable_staking=True)
549
- result = sim.run(rounds=2000, interactions_per_round=50)
550
-
551
- print(f"\nAfter 2000 rounds (100,000 interactions):")
552
- print(f" Completion rate: {result.total_completions / (result.total_completions + result.total_disputes):.1%}")
553
- print(f" Inflation: {result.inflation_rate:+.1%}")
554
- print(f" Gini: {result.gini_coefficient:.3f}")
555
- print(f"\n Type averages:")
556
- for t, avg in sorted(result.type_avg_ratings.items(), key=lambda x: -x[1]):
557
- print(f" {t}: {avg:.0f}")
558
-
559
- # Check if rankings are stable
560
- top_10_ids = [a.id for a in sorted(result.agents, key=lambda x: -x.rating)[:10]]
561
- top_types = [a.agent_type for a in sorted(result.agents, key=lambda x: -x.rating)[:10]]
562
- print(f"\n Top 10 agent types: {', '.join(top_types)}")
563
- print(f" All top 10 are reliable: {all(t == 'reliable' for t in top_types)}")
564
-
565
-
566
- if __name__ == "__main__":
567
- run_comparison()
568
- run_equilibrium_analysis()
569
- run_long_term_stability()