@agent-e/core 1.5.1 → 1.5.3

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.
package/dist/index.mjs CHANGED
@@ -32,7 +32,7 @@ var DEFAULT_THRESHOLDS = {
32
32
  cooldownTicks: 15,
33
33
  // Currency (P13)
34
34
  poolWinRate: 0.65,
35
- poolHouseCut: 0.1,
35
+ poolOperatorShare: 0.1,
36
36
  // Population balance (P9)
37
37
  roleSwitchFrictionMax: 0.05,
38
38
  // >5% of population switching in one period = herd
@@ -93,6 +93,12 @@ var Observer = class {
93
93
  this.customMetricFns[name] = fn;
94
94
  }
95
95
  compute(state, recentEvents) {
96
+ if (!state.currencies || state.currencies.length === 0) {
97
+ console.warn("[AgentE] Warning: state.currencies is empty. Metrics will be zeroed.");
98
+ }
99
+ if (!state.agentBalances || Object.keys(state.agentBalances).length === 0) {
100
+ console.warn("[AgentE] Warning: state.agentBalances is empty.");
101
+ }
96
102
  const tick = state.tick;
97
103
  const roles = Object.values(state.agentRoles);
98
104
  const totalAgents = Object.keys(state.agentBalances).length;
@@ -187,7 +193,7 @@ var Observer = class {
187
193
  const faucet = faucetVolumeByCurrency[curr] ?? 0;
188
194
  const sink = sinkVolumeByCurrency[curr] ?? 0;
189
195
  netFlowByCurrency[curr] = faucet - sink;
190
- tapSinkRatioByCurrency[curr] = sink > 0 ? faucet / sink : faucet > 0 ? Infinity : 1;
196
+ tapSinkRatioByCurrency[curr] = sink > 0 ? Math.min(faucet / sink, 100) : faucet > 0 ? 100 : 1;
191
197
  const prevSupply = this.previousMetrics?.totalSupplyByCurrency?.[curr] ?? totalSupplyByCurrency[curr] ?? 0;
192
198
  const currSupply = totalSupplyByCurrency[curr] ?? 0;
193
199
  inflationRateByCurrency[curr] = prevSupply > 0 ? (currSupply - prevSupply) / prevSupply : 0;
@@ -222,7 +228,7 @@ var Observer = class {
222
228
  const faucetVolume = Object.values(faucetVolumeByCurrency).reduce((s, v) => s + v, 0);
223
229
  const sinkVolume = Object.values(sinkVolumeByCurrency).reduce((s, v) => s + v, 0);
224
230
  const netFlow = faucetVolume - sinkVolume;
225
- const tapSinkRatio = sinkVolume > 0 ? faucetVolume / sinkVolume : faucetVolume > 0 ? Infinity : 1;
231
+ const tapSinkRatio = sinkVolume > 0 ? Math.min(faucetVolume / sinkVolume, 100) : faucetVolume > 0 ? 100 : 1;
226
232
  const velocity = totalSupply > 0 ? tradeEvents.length / totalSupply : 0;
227
233
  const prevTotalSupply = this.previousMetrics?.totalSupply ?? totalSupply;
228
234
  const inflationRate = prevTotalSupply > 0 ? (totalSupply - prevTotalSupply) / prevTotalSupply : 0;
@@ -260,7 +266,9 @@ var Observer = class {
260
266
  const pVals = Object.values(resourcePrices);
261
267
  priceIndexByCurrency[curr] = pVals.length > 0 ? pVals.reduce((s, p) => s + p, 0) / pVals.length : 0;
262
268
  }
263
- this.previousPricesByCurrency = JSON.parse(JSON.stringify(pricesByCurrency));
269
+ this.previousPricesByCurrency = Object.fromEntries(
270
+ Object.entries(pricesByCurrency).map(([c, p]) => [c, { ...p }])
271
+ );
264
272
  const prices = pricesByCurrency[defaultCurrency] ?? {};
265
273
  const priceVolatility = priceVolatilityByCurrency[defaultCurrency] ?? {};
266
274
  const priceIndex = priceIndexByCurrency[defaultCurrency] ?? 0;
@@ -280,9 +288,9 @@ var Observer = class {
280
288
  for (const resource of /* @__PURE__ */ new Set([...Object.keys(supplyByResource), ...Object.keys(demandSignals)])) {
281
289
  const s = supplyByResource[resource] ?? 0;
282
290
  const d = demandSignals[resource] ?? 0;
283
- if (d > 2 && s / d < 0.5) {
291
+ if (d > 0 && d > 2 && s / d < 0.5) {
284
292
  pinchPoints[resource] = "scarce";
285
- } else if (s > 3 && d > 0 && s / d > 3) {
293
+ } else if (d > 0 && s > 3 && s / d > 3) {
286
294
  pinchPoints[resource] = "oversupplied";
287
295
  } else {
288
296
  pinchPoints[resource] = "optimal";
@@ -328,21 +336,11 @@ var Observer = class {
328
336
  const arbitrageIndexByCurrency = {};
329
337
  for (const curr of currencies) {
330
338
  const cPrices = pricesByCurrency[curr] ?? {};
331
- const priceKeys = Object.keys(cPrices).filter((k) => cPrices[k] > 0);
332
- if (priceKeys.length >= 2) {
333
- let pairCount = 0;
334
- let totalDivergence = 0;
335
- for (let i = 0; i < priceKeys.length; i++) {
336
- for (let j = i + 1; j < priceKeys.length; j++) {
337
- const pA = cPrices[priceKeys[i]];
338
- const pB = cPrices[priceKeys[j]];
339
- let ratio = pA / pB;
340
- ratio = Math.max(1e-3, Math.min(1e3, ratio));
341
- totalDivergence += Math.abs(Math.log(ratio));
342
- pairCount++;
343
- }
344
- }
345
- arbitrageIndexByCurrency[curr] = pairCount > 0 ? Math.min(1, totalDivergence / pairCount) : 0;
339
+ const logPrices = Object.values(cPrices).filter((p) => p > 0).map((p) => Math.log(p));
340
+ if (logPrices.length >= 2) {
341
+ const mean = logPrices.reduce((s, v) => s + v, 0) / logPrices.length;
342
+ const variance = logPrices.reduce((s, v) => s + (v - mean) ** 2, 0) / logPrices.length;
343
+ arbitrageIndexByCurrency[curr] = Math.min(1, Math.sqrt(variance));
346
344
  } else {
347
345
  arbitrageIndexByCurrency[curr] = 0;
348
346
  }
@@ -429,10 +427,10 @@ var Observer = class {
429
427
  prices,
430
428
  priceVolatility,
431
429
  poolSizes: poolSizesAggregate,
432
- extractionRatio: NaN,
433
- newUserDependency: NaN,
434
- smokeTestRatio: NaN,
435
- currencyInsulation: NaN,
430
+ extractionRatio: 0,
431
+ newUserDependency: 0,
432
+ smokeTestRatio: 0,
433
+ currencyInsulation: 0,
436
434
  arbitrageIndex,
437
435
  giftTradeRatio,
438
436
  disposalTradeRatio,
@@ -454,7 +452,7 @@ var Observer = class {
454
452
  timeToValue,
455
453
  sharkToothPeaks: this.previousMetrics?.sharkToothPeaks ?? [],
456
454
  sharkToothValleys: this.previousMetrics?.sharkToothValleys ?? [],
457
- eventCompletionRate: NaN,
455
+ eventCompletionRate: 0,
458
456
  contentDropAge,
459
457
  systems: state.systems ?? [],
460
458
  sources: state.sources ?? [],
@@ -486,7 +484,7 @@ function computeGini(sorted) {
486
484
  for (let i = 0; i < n; i++) {
487
485
  numerator += (2 * (i + 1) - n - 1) * (sorted[i] ?? 0);
488
486
  }
489
- return Math.abs(numerator) / (n * sum);
487
+ return Math.min(1, Math.abs(numerator) / (n * sum));
490
488
  }
491
489
 
492
490
  // src/Diagnoser.ts
@@ -582,10 +580,10 @@ function emptyMetrics(tick = 0) {
582
580
  prices: {},
583
581
  priceVolatility: {},
584
582
  poolSizes: {},
585
- extractionRatio: NaN,
586
- newUserDependency: NaN,
587
- smokeTestRatio: NaN,
588
- currencyInsulation: NaN,
583
+ extractionRatio: 0,
584
+ newUserDependency: 0,
585
+ smokeTestRatio: 0,
586
+ currencyInsulation: 0,
589
587
  arbitrageIndex: 0,
590
588
  giftTradeRatio: 0,
591
589
  disposalTradeRatio: 0,
@@ -606,7 +604,7 @@ function emptyMetrics(tick = 0) {
606
604
  timeToValue: 0,
607
605
  sharkToothPeaks: [],
608
606
  sharkToothValleys: [],
609
- eventCompletionRate: NaN,
607
+ eventCompletionRate: 0,
610
608
  contentDropAge: 0,
611
609
  systems: [],
612
610
  sources: [],
@@ -811,9 +809,9 @@ var SUPPLY_CHAIN_PRINCIPLES = [
811
809
  ];
812
810
 
813
811
  // src/principles/incentives.ts
814
- var P5_ProfitabilityIsCompetitive = {
812
+ var P5_ProfitabilityIsRelative = {
815
813
  id: "P5",
816
- name: "Profitability Is Competitive, Not Absolute",
814
+ name: "Profitability Is Relative, Not Absolute",
817
815
  category: "incentive",
818
816
  description: "Any profitability formula that returns the same number regardless of how many agents are already in that role will cause stampedes. 97 intermediaries happened because profit = transactions \xD7 10 with no competition denominator.",
819
817
  check(metrics, thresholds) {
@@ -877,7 +875,7 @@ var P7_NonSpecialistsSubsidiseSpecialists = {
877
875
  id: "P7",
878
876
  name: "Non-Specialists Subsidise Specialists in Zero-Sum Games",
879
877
  category: "incentive",
880
- description: "In zero-sum pools (competitive pool, staking), the math only works if non-specialists overpay relative to specialists. If the pool is >70% specialists, there is no one left to subsidise and the pot drains.",
878
+ description: "In zero-sum pools (staking, prize pools, etc.), the math only works if non-specialists overpay relative to specialists. If the pool is >70% specialists, there is no one left to subsidise and the pot drains.",
881
879
  check(metrics, _thresholds) {
882
880
  const { poolSizes } = metrics;
883
881
  for (const [poolName, poolSize] of Object.entries(poolSizes)) {
@@ -913,7 +911,7 @@ var P8_RegulatorCannotFightDesign = {
913
911
  id: "P8",
914
912
  name: "Regulator Cannot Fight the Design",
915
913
  category: "incentive",
916
- description: "If the economy is designed to have a majority role (e.g. dominant role exceeds 55%), the regulator must know this and exempt that role from population suppression. AgentE at tick 1 seeing dominant role exceeds 55% and slashing competitive pool rewards is overreach.",
914
+ description: "If the economy is designed to have a majority role (e.g. dominant role exceeds 55%), the regulator must know this and exempt that role from population suppression. AgentE at tick 1 seeing dominant role exceeds 55% and slashing pool rewards is overreach.",
917
915
  check(metrics, _thresholds) {
918
916
  const { roleShares, avgSatisfaction } = metrics;
919
917
  if (avgSatisfaction < 45) {
@@ -938,7 +936,7 @@ var P8_RegulatorCannotFightDesign = {
938
936
  }
939
937
  };
940
938
  var INCENTIVE_PRINCIPLES = [
941
- P5_ProfitabilityIsCompetitive,
939
+ P5_ProfitabilityIsRelative,
942
940
  P6_CrowdingMultiplierOnAllRoles,
943
941
  P7_NonSpecialistsSubsidiseSpecialists,
944
942
  P8_RegulatorCannotFightDesign
@@ -1134,7 +1132,7 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
1134
1132
  id: "P13",
1135
1133
  name: "Pots Self-Regulate with Correct Multiplier",
1136
1134
  category: "currency",
1137
- description: "Competitive pot math: winRate \xD7 multiplier > (1 - houseCut) drains the pot. At 65% win rate, multiplier must be \u2264 1.38. We use 1.5 for slight surplus buffer.",
1135
+ description: "Pool math: winRate \xD7 multiplier > (1 - operatorShare) drains the pot. At 65% win rate, multiplier must be \u2264 1.38. We use 1.5 for slight surplus buffer.",
1138
1136
  check(metrics, thresholds) {
1139
1137
  const { populationByRole } = metrics;
1140
1138
  const roleEntries = Object.entries(populationByRole).sort((a, b) => b[1] - a[1]);
@@ -1143,8 +1141,8 @@ var P13_PotsAreZeroSumAndSelfRegulate = {
1143
1141
  for (const curr of metrics.currencies) {
1144
1142
  const poolSize = currencyAmounts[curr] ?? 0;
1145
1143
  if (dominantCount > 5 && poolSize < 50) {
1146
- const { poolWinRate, poolHouseCut } = thresholds;
1147
- const maxSustainableMultiplier = (1 - poolHouseCut) / poolWinRate;
1144
+ const { poolWinRate, poolOperatorShare } = thresholds;
1145
+ const maxSustainableMultiplier = (1 - poolOperatorShare) / poolWinRate;
1148
1146
  return {
1149
1147
  violated: true,
1150
1148
  severity: 7,
@@ -1427,7 +1425,7 @@ var P19_StartingSupplyExceedsDemand = {
1427
1425
  parameterType: "reward",
1428
1426
  direction: "increase",
1429
1427
  magnitude: 0.2,
1430
- reasoning: `${mostPopulatedRole} (${population} agents) has insufficient resources (${resourcesPerAgent.toFixed(2)} per agent). Cold-start scarcity. Boost competitive pool reward to attract participation despite scarcity.`
1428
+ reasoning: `${mostPopulatedRole} (${population} agents) has insufficient resources (${resourcesPerAgent.toFixed(2)} per agent). Cold-start scarcity. Boost pool reward to attract participation despite scarcity.`
1431
1429
  },
1432
1430
  confidence: 0.75,
1433
1431
  estimatedLag: 5
@@ -2172,7 +2170,7 @@ var P35_DestructionCreatesValue = {
2172
2170
  scope: { tags: ["entry"] },
2173
2171
  direction: "decrease",
2174
2172
  magnitude: 0.1,
2175
- reasoning: `${resource} supply at ${supply} units with low destruction (sink ${sinkVolume}/t). Resources not being consumed. Lower competitive pool entry to increase resource usage.`
2173
+ reasoning: `${resource} supply at ${supply} units with low destruction (sink ${sinkVolume}/t). Resources not being consumed. Lower pool entry to increase resource usage.`
2176
2174
  },
2177
2175
  confidence: 0.7,
2178
2176
  estimatedLag: 5
@@ -2779,15 +2777,24 @@ var ALL_PRINCIPLES = [
2779
2777
  ];
2780
2778
 
2781
2779
  // src/Simulator.ts
2780
+ var DEFAULT_SIM_CONFIG = {
2781
+ sinkMultiplier: 0.2,
2782
+ faucetMultiplier: 0.15,
2783
+ frictionMultiplier: 0.1,
2784
+ frictionVelocityScale: 10,
2785
+ redistributionMultiplier: 0.3,
2786
+ neutralMultiplier: 0.05,
2787
+ minIterations: 100,
2788
+ maxProjectionTicks: 20
2789
+ };
2782
2790
  var Simulator = class {
2783
- constructor(registry) {
2791
+ constructor(registry, simConfig) {
2784
2792
  this.diagnoser = new Diagnoser(ALL_PRINCIPLES);
2785
- // Cache beforeViolations for the *current* tick only (one entry max).
2786
- // Using a Map here is intentional but the cache must be bounded — we only
2787
- // care about the tick that is currently being evaluated, so we evict any
2788
- // entries whose key differs from the incoming tick.
2789
- this.beforeViolationsCache = /* @__PURE__ */ new Map();
2793
+ // Cache beforeViolations for the *current* tick only.
2794
+ this.cachedViolationsTick = -1;
2795
+ this.cachedViolations = /* @__PURE__ */ new Set();
2790
2796
  this.registry = registry;
2797
+ this.simConfig = { ...DEFAULT_SIM_CONFIG, ...simConfig };
2791
2798
  }
2792
2799
  /**
2793
2800
  * Simulate the effect of applying `action` to the current economy forward `forwardTicks`.
@@ -2811,16 +2818,13 @@ var Simulator = class {
2811
2818
  const mean = this.averageMetrics(outcomes);
2812
2819
  const netImprovement = this.checkImprovement(currentMetrics, p50, action);
2813
2820
  const tick = currentMetrics.tick;
2814
- if (this.beforeViolationsCache.size > 0 && !this.beforeViolationsCache.has(tick)) {
2815
- this.beforeViolationsCache.clear();
2816
- }
2817
- let beforeViolations = this.beforeViolationsCache.get(tick);
2818
- if (!beforeViolations) {
2819
- beforeViolations = new Set(
2821
+ if (this.cachedViolationsTick !== tick) {
2822
+ this.cachedViolations = new Set(
2820
2823
  this.diagnoser.diagnose(currentMetrics, thresholds).map((d) => d.principle.id)
2821
2824
  );
2822
- this.beforeViolationsCache.set(tick, beforeViolations);
2825
+ this.cachedViolationsTick = tick;
2823
2826
  }
2827
+ const beforeViolations = this.cachedViolations;
2824
2828
  const afterViolations = new Set(
2825
2829
  this.diagnoser.diagnose(p50, thresholds).map((d) => d.principle.id)
2826
2830
  );
@@ -2910,21 +2914,22 @@ var Simulator = class {
2910
2914
  if (!impact) {
2911
2915
  impact = this.inferFlowImpact(action.parameterType);
2912
2916
  }
2917
+ const cfg = this.simConfig;
2913
2918
  switch (impact) {
2914
2919
  case "sink":
2915
- return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.2;
2920
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * cfg.sinkMultiplier;
2916
2921
  case "faucet":
2917
- return -sign * dominantRoleCount * 0.3;
2922
+ return -sign * dominantRoleCount * cfg.redistributionMultiplier;
2918
2923
  case "neutral":
2919
- return sign * dominantRoleCount * 0.5;
2924
+ return sign * dominantRoleCount * cfg.neutralMultiplier;
2920
2925
  case "mixed":
2921
- return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * 0.15;
2926
+ return sign * (metrics.faucetVolumeByCurrency[currency] ?? 0) * cfg.faucetMultiplier;
2922
2927
  case "friction":
2923
- return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.1;
2928
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * cfg.frictionMultiplier;
2924
2929
  case "redistribution":
2925
- return sign * dominantRoleCount * 0.05;
2930
+ return sign * dominantRoleCount * cfg.neutralMultiplier;
2926
2931
  default:
2927
- return sign * (metrics.netFlowByCurrency[currency] ?? 0) * 0.1;
2932
+ return sign * (metrics.netFlowByCurrency[currency] ?? 0) * cfg.frictionMultiplier;
2928
2933
  }
2929
2934
  }
2930
2935
  /** Infer flow impact from parameter type when registry is unavailable */
@@ -2945,7 +2950,7 @@ var Simulator = class {
2945
2950
  return "mixed";
2946
2951
  }
2947
2952
  }
2948
- checkImprovement(before, after, action) {
2953
+ checkImprovement(before, after, _action) {
2949
2954
  const satisfactionImproved = after.avgSatisfaction >= before.avgSatisfaction - 2;
2950
2955
  const flowMoreBalanced = before.currencies.every((curr) => {
2951
2956
  const afterFlow = Math.abs(after.netFlowByCurrency[curr] ?? 0);
@@ -2957,7 +2962,6 @@ var Simulator = class {
2957
2962
  const beforeGini = before.giniCoefficientByCurrency[curr] ?? 0;
2958
2963
  return afterGini <= beforeGini + 0.05;
2959
2964
  });
2960
- void action;
2961
2965
  return satisfactionImproved && flowMoreBalanced && notWorseGini;
2962
2966
  }
2963
2967
  averageMetrics(outcomes) {
@@ -3109,6 +3113,14 @@ var Planner = class {
3109
3113
  this.cooldowns.clear();
3110
3114
  this.typeCooldowns.clear();
3111
3115
  }
3116
+ /** V1.5.2: Reset active plan count (e.g., on system restart) */
3117
+ resetActivePlans() {
3118
+ this.activePlanCount = 0;
3119
+ }
3120
+ /** V1.5.2: Current active plan count (for diagnostics) */
3121
+ getActivePlanCount() {
3122
+ return this.activePlanCount;
3123
+ }
3112
3124
  typeCooldownKey(type, scope) {
3113
3125
  const parts = [type];
3114
3126
  if (scope?.system) parts.push(`sys:${scope.system}`);
@@ -3125,8 +3137,9 @@ var Planner = class {
3125
3137
 
3126
3138
  // src/Executor.ts
3127
3139
  var Executor = class {
3128
- constructor() {
3140
+ constructor(settlementWindowTicks = 200) {
3129
3141
  this.activePlans = [];
3142
+ this.maxActiveTicks = settlementWindowTicks;
3130
3143
  }
3131
3144
  async apply(plan, adapter, currentParams) {
3132
3145
  const originalValue = currentParams[plan.parameter] ?? plan.currentValue;
@@ -3145,8 +3158,7 @@ var Executor = class {
3145
3158
  for (const active of this.activePlans) {
3146
3159
  const { plan, originalValue } = active;
3147
3160
  const rc = plan.rollbackCondition;
3148
- const maxActiveTicks = 200;
3149
- if (plan.appliedAt !== void 0 && metrics.tick - plan.appliedAt > maxActiveTicks) {
3161
+ if (plan.appliedAt !== void 0 && metrics.tick - plan.appliedAt > this.maxActiveTicks) {
3150
3162
  settled.push(plan);
3151
3163
  continue;
3152
3164
  }
@@ -3213,9 +3225,9 @@ var DecisionLog = class {
3213
3225
  reasoning: this.buildReasoning(diagnosis, plan, result),
3214
3226
  metricsSnapshot: metrics
3215
3227
  };
3216
- this.entries.unshift(entry);
3217
- if (this.entries.length > this.maxEntries) {
3218
- this.entries.pop();
3228
+ this.entries.push(entry);
3229
+ if (this.entries.length > this.maxEntries * 1.5) {
3230
+ this.entries = this.entries.slice(-this.maxEntries);
3219
3231
  }
3220
3232
  return entry;
3221
3233
  }
@@ -3230,8 +3242,10 @@ var DecisionLog = class {
3230
3242
  reasoning: reason,
3231
3243
  metricsSnapshot: metrics
3232
3244
  };
3233
- this.entries.unshift(entry);
3234
- if (this.entries.length > this.maxEntries) this.entries.pop();
3245
+ this.entries.push(entry);
3246
+ if (this.entries.length > this.maxEntries * 1.5) {
3247
+ this.entries = this.entries.slice(-this.maxEntries);
3248
+ }
3235
3249
  }
3236
3250
  query(filter) {
3237
3251
  return this.entries.filter((e) => {
@@ -3244,7 +3258,7 @@ var DecisionLog = class {
3244
3258
  });
3245
3259
  }
3246
3260
  latest(n = 30) {
3247
- return this.entries.slice(0, n);
3261
+ return this.entries.slice(-n).reverse();
3248
3262
  }
3249
3263
  export(format = "json") {
3250
3264
  if (format === "text") {
@@ -3473,10 +3487,13 @@ var MetricStore = class {
3473
3487
  var PersonaTracker = class {
3474
3488
  constructor() {
3475
3489
  this.agentHistory = /* @__PURE__ */ new Map();
3490
+ this.lastSeen = /* @__PURE__ */ new Map();
3476
3491
  }
3477
3492
  /** Ingest a state snapshot and update agent signal history */
3478
3493
  update(state) {
3494
+ const tick = state.tick;
3479
3495
  for (const agentId of Object.keys(state.agentBalances)) {
3496
+ this.lastSeen.set(agentId, tick);
3480
3497
  const history = this.agentHistory.get(agentId) ?? [];
3481
3498
  const inv = state.agentInventories[agentId] ?? {};
3482
3499
  const uniqueItems = Object.values(inv).filter((q) => q > 0).length;
@@ -3494,6 +3511,14 @@ var PersonaTracker = class {
3494
3511
  if (history.length > 50) history.shift();
3495
3512
  this.agentHistory.set(agentId, history);
3496
3513
  }
3514
+ if (tick % 50 === 0) {
3515
+ for (const [id, lastTick] of this.lastSeen) {
3516
+ if (tick - lastTick > 100) {
3517
+ this.agentHistory.delete(id);
3518
+ this.lastSeen.delete(id);
3519
+ }
3520
+ }
3521
+ }
3497
3522
  }
3498
3523
  /** Classify all agents and return persona distribution */
3499
3524
  getDistribution() {
@@ -3685,7 +3710,6 @@ var ParameterRegistry = class {
3685
3710
  var AgentE = class {
3686
3711
  constructor(config) {
3687
3712
  this.planner = new Planner();
3688
- this.executor = new Executor();
3689
3713
  this.registry = new ParameterRegistry();
3690
3714
  // ── State ──
3691
3715
  this.log = new DecisionLog();
@@ -3708,6 +3732,8 @@ var AgentE = class {
3708
3732
  dominantRoles: config.dominantRoles ?? [],
3709
3733
  idealDistribution: config.idealDistribution ?? {},
3710
3734
  validateRegistry: config.validateRegistry ?? true,
3735
+ simulation: config.simulation ?? {},
3736
+ settlementWindowTicks: config.settlementWindowTicks ?? 200,
3711
3737
  tickConfig: config.tickConfig ?? { duration: 1, unit: "tick" },
3712
3738
  gracePeriod: config.gracePeriod ?? 50,
3713
3739
  checkInterval: config.checkInterval ?? 5,
@@ -3733,7 +3759,8 @@ var AgentE = class {
3733
3759
  for (const w of validation.warnings) console.warn(`[AgentE] Registry warning: ${w}`);
3734
3760
  for (const e of validation.errors) console.error(`[AgentE] Registry error: ${e}`);
3735
3761
  }
3736
- this.simulator = new Simulator(this.registry);
3762
+ this.executor = new Executor(config.settlementWindowTicks);
3763
+ this.simulator = new Simulator(this.registry, config.simulation);
3737
3764
  if (config.onDecision) this.on("decision", config.onDecision);
3738
3765
  if (config.onAlert) this.on("alert", config.onAlert);
3739
3766
  if (config.onRollback) this.on("rollback", config.onRollback);
@@ -3769,7 +3796,7 @@ var AgentE = class {
3769
3796
  if (!this.isRunning || this.isPaused) return;
3770
3797
  const currentState = state ?? await Promise.resolve(this.adapter.getState());
3771
3798
  this.currentTick = currentState.tick;
3772
- const events = [...this.eventBuffer];
3799
+ const events = this.eventBuffer;
3773
3800
  this.eventBuffer = [];
3774
3801
  let metrics;
3775
3802
  try {
@@ -3887,7 +3914,9 @@ var AgentE = class {
3887
3914
  // ── Events ──────────────────────────────────────────────────────────────────
3888
3915
  on(event, handler) {
3889
3916
  const list = this.handlers.get(event) ?? [];
3890
- list.push(handler);
3917
+ if (!list.includes(handler)) {
3918
+ list.push(handler);
3919
+ }
3891
3920
  this.handlers.set(event, list);
3892
3921
  return this;
3893
3922
  }
@@ -3900,8 +3929,12 @@ var AgentE = class {
3900
3929
  const list = this.handlers.get(event) ?? [];
3901
3930
  let result;
3902
3931
  for (const handler of list) {
3903
- result = handler(...args);
3904
- if (result === false) return false;
3932
+ try {
3933
+ result = handler(...args);
3934
+ if (result === false) return false;
3935
+ } catch (err) {
3936
+ console.error(`[AgentE] Handler error on '${event}':`, err);
3937
+ }
3905
3938
  }
3906
3939
  return result;
3907
3940
  }
@@ -4358,7 +4391,7 @@ export {
4358
4391
  P57_CombinatorialPriceSpace,
4359
4392
  P58_NoNaturalNumeraire,
4360
4393
  P59_GiftEconomyNoise,
4361
- P5_ProfitabilityIsCompetitive,
4394
+ P5_ProfitabilityIsRelative,
4362
4395
  P60_SurplusDisposalAsymmetry,
4363
4396
  P6_CrowdingMultiplierOnAllRoles,
4364
4397
  P7_NonSpecialistsSubsidiseSpecialists,