@agent-e/core 1.6.6 → 1.6.7

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.d.mts CHANGED
@@ -321,6 +321,7 @@ interface Thresholds {
321
321
  priceDiscoveryWindowTicks: number;
322
322
  giftTradeFilterRatio: number;
323
323
  disposalTradeWeightDiscount: number;
324
+ dominantRoles: string[];
324
325
  }
325
326
  interface TickConfig {
326
327
  /** How many real-world units one tick represents. Default: 1 */
@@ -525,7 +526,7 @@ declare class Observer {
525
526
  private tickConfig;
526
527
  constructor(tickConfig?: Partial<TickConfig>);
527
528
  registerCustomMetric(name: string, fn: (state: EconomyState) => number): void;
528
- compute(state: EconomyState, recentEvents: EconomicEvent[]): EconomyMetrics;
529
+ compute(state: EconomyState, recentEvents: EconomicEvent[], personaDistribution?: Record<string, number>): EconomyMetrics;
529
530
  }
530
531
 
531
532
  declare class Simulator {
@@ -627,10 +628,14 @@ interface PersonaConfig {
627
628
  powerUserMinSystems: number;
628
629
  /** Rolling history window size (ticks). Default: 50 */
629
630
  historyWindow: number;
631
+ /** Ticks between full reclassification. Default: 10 */
632
+ reclassifyInterval: number;
630
633
  }
631
634
  declare class PersonaTracker {
632
635
  private agents;
633
636
  private config;
637
+ private cachedDistribution;
638
+ private lastClassifiedTick;
634
639
  constructor(config?: Partial<PersonaConfig>);
635
640
  /**
636
641
  * Ingest a state snapshot + events and update per-agent signals.
@@ -640,8 +645,10 @@ declare class PersonaTracker {
640
645
  /**
641
646
  * Classify all tracked agents and return the population distribution.
642
647
  * Returns { Whale: 0.05, ActiveTrader: 0.18, Passive: 0.42, ... }
648
+ * Caches results and only reclassifies at `reclassifyInterval` boundaries.
643
649
  */
644
- getDistribution(): Record<string, number>;
650
+ getDistribution(currentTick?: number): Record<string, number>;
651
+ private _classify;
645
652
  }
646
653
 
647
654
  /**
package/dist/index.d.ts CHANGED
@@ -321,6 +321,7 @@ interface Thresholds {
321
321
  priceDiscoveryWindowTicks: number;
322
322
  giftTradeFilterRatio: number;
323
323
  disposalTradeWeightDiscount: number;
324
+ dominantRoles: string[];
324
325
  }
325
326
  interface TickConfig {
326
327
  /** How many real-world units one tick represents. Default: 1 */
@@ -525,7 +526,7 @@ declare class Observer {
525
526
  private tickConfig;
526
527
  constructor(tickConfig?: Partial<TickConfig>);
527
528
  registerCustomMetric(name: string, fn: (state: EconomyState) => number): void;
528
- compute(state: EconomyState, recentEvents: EconomicEvent[]): EconomyMetrics;
529
+ compute(state: EconomyState, recentEvents: EconomicEvent[], personaDistribution?: Record<string, number>): EconomyMetrics;
529
530
  }
530
531
 
531
532
  declare class Simulator {
@@ -627,10 +628,14 @@ interface PersonaConfig {
627
628
  powerUserMinSystems: number;
628
629
  /** Rolling history window size (ticks). Default: 50 */
629
630
  historyWindow: number;
631
+ /** Ticks between full reclassification. Default: 10 */
632
+ reclassifyInterval: number;
630
633
  }
631
634
  declare class PersonaTracker {
632
635
  private agents;
633
636
  private config;
637
+ private cachedDistribution;
638
+ private lastClassifiedTick;
634
639
  constructor(config?: Partial<PersonaConfig>);
635
640
  /**
636
641
  * Ingest a state snapshot + events and update per-agent signals.
@@ -640,8 +645,10 @@ declare class PersonaTracker {
640
645
  /**
641
646
  * Classify all tracked agents and return the population distribution.
642
647
  * Returns { Whale: 0.05, ActiveTrader: 0.18, Passive: 0.42, ... }
648
+ * Caches results and only reclassifies at `reclassifyInterval` boundaries.
643
649
  */
644
- getDistribution(): Record<string, number>;
650
+ getDistribution(currentTick?: number): Record<string, number>;
651
+ private _classify;
645
652
  }
646
653
 
647
654
  /**
package/dist/index.js CHANGED
@@ -176,7 +176,9 @@ var DEFAULT_THRESHOLDS = {
176
176
  relativePriceConvergenceTarget: 0.85,
177
177
  priceDiscoveryWindowTicks: 20,
178
178
  giftTradeFilterRatio: 0.15,
179
- disposalTradeWeightDiscount: 0.5
179
+ disposalTradeWeightDiscount: 0.5,
180
+ // Structural dominance (P8)
181
+ dominantRoles: []
180
182
  };
181
183
  var PERSONA_HEALTHY_RANGES = {
182
184
  Active: { min: 0.2, max: 0.4 },
@@ -208,7 +210,7 @@ var Observer = class {
208
210
  registerCustomMetric(name, fn) {
209
211
  this.customMetricFns[name] = fn;
210
212
  }
211
- compute(state, recentEvents) {
213
+ compute(state, recentEvents, personaDistribution) {
212
214
  if (!state.currencies || state.currencies.length === 0) {
213
215
  console.warn("[AgentE] Warning: state.currencies is empty. Metrics will be zeroed.");
214
216
  }
@@ -361,6 +363,16 @@ var Observer = class {
361
363
  for (const [role, count] of Object.entries(populationByRole)) {
362
364
  roleShares[role] = count / Math.max(1, totalAgents);
363
365
  }
366
+ const uniqueRoles = new Set(Object.values(state.agentRoles));
367
+ const rolesEmpty = uniqueRoles.size <= 1;
368
+ if (rolesEmpty && personaDistribution && Object.keys(personaDistribution).length > 0) {
369
+ for (const [persona, fraction] of Object.entries(personaDistribution)) {
370
+ populationByRole[persona] = Math.round(fraction * totalAgents);
371
+ }
372
+ for (const [role, count] of Object.entries(populationByRole)) {
373
+ roleShares[role] = count / Math.max(1, totalAgents);
374
+ }
375
+ }
364
376
  const churnByRole = {};
365
377
  for (const e of roleChangeEvents) {
366
378
  const role = e.role ?? "unknown";
@@ -850,25 +862,45 @@ var P4_MaterialsFlowFasterThanCooldown = {
850
862
  category: "supply_chain",
851
863
  description: "Input delivery rate must exceed or match production cooldown rate. If producers produce every 5 ticks but only receive raw materials every 10 ticks, they starve regardless of supply levels.",
852
864
  check(metrics, _thresholds) {
853
- const { supplyByResource, populationByRole, velocity, totalAgents } = metrics;
865
+ const { supplyByResource, populationByRole, velocity, velocityByCurrency, totalAgents } = metrics;
854
866
  const totalSupply = Object.values(supplyByResource).reduce((s, v) => s + v, 0);
855
867
  const avgSupplyPerAgent = totalAgents > 0 ? totalSupply / totalAgents : 0;
856
868
  const roleEntries = Object.entries(populationByRole);
857
869
  const totalRoles = roleEntries.length;
858
- if (totalRoles >= 2 && velocity < 5 && avgSupplyPerAgent < 0.5) {
859
- return {
860
- violated: true,
861
- severity: 5,
862
- evidence: { avgSupplyPerAgent, velocity, totalRoles },
863
- suggestedAction: {
864
- parameterType: "yield",
865
- direction: "increase",
866
- magnitude: 0.15,
867
- reasoning: "Low supply per agent with stagnant velocity. Increase yield to compensate."
868
- },
869
- confidence: 0.65,
870
- estimatedLag: 8
871
- };
870
+ if (totalRoles >= 2 && avgSupplyPerAgent < 0.5) {
871
+ for (const [currency, currVelocity] of Object.entries(velocityByCurrency)) {
872
+ if (currVelocity < 5) {
873
+ return {
874
+ violated: true,
875
+ severity: 5,
876
+ evidence: { currency, currVelocity, avgSupplyPerAgent, totalRoles },
877
+ suggestedAction: {
878
+ parameterType: "yield",
879
+ scope: { currency },
880
+ direction: "increase",
881
+ magnitude: 0.15,
882
+ reasoning: `${currency} velocity is ${currVelocity.toFixed(1)} \u2014 materials not flowing. Increase yield to compensate.`
883
+ },
884
+ confidence: 0.7,
885
+ estimatedLag: 8
886
+ };
887
+ }
888
+ }
889
+ if (Object.keys(velocityByCurrency).length === 0 && velocity < 5) {
890
+ return {
891
+ violated: true,
892
+ severity: 5,
893
+ evidence: { avgSupplyPerAgent, velocity, totalRoles },
894
+ suggestedAction: {
895
+ parameterType: "yield",
896
+ direction: "increase",
897
+ magnitude: 0.15,
898
+ reasoning: "Low supply per agent with stagnant velocity. Increase yield to compensate."
899
+ },
900
+ confidence: 0.65,
901
+ estimatedLag: 8
902
+ };
903
+ }
872
904
  }
873
905
  if (avgSupplyPerAgent > 2) {
874
906
  return {
@@ -1027,28 +1059,42 @@ var P8_RegulatorCannotFightDesign = {
1027
1059
  id: "P8",
1028
1060
  name: "Regulator Cannot Fight the Design",
1029
1061
  category: "incentive",
1030
- 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.",
1031
- check(metrics, _thresholds) {
1062
+ description: "If a role dominates above 30%, classify as structural (in dominantRoles config) or pathological. Structural dominance gets no intervention. Pathological dominance gets crowding pressure.",
1063
+ check(metrics, thresholds) {
1032
1064
  const { roleShares, avgSatisfaction } = metrics;
1033
- if (avgSatisfaction < 45) {
1034
- const dominantRole = Object.entries(roleShares).sort((a, b) => b[1] - a[1])[0];
1035
- if (dominantRole && dominantRole[1] > 0.3) {
1036
- return {
1037
- violated: true,
1038
- severity: 4,
1039
- evidence: { dominantRole: dominantRole[0], share: dominantRole[1], avgSatisfaction },
1040
- suggestedAction: {
1041
- parameterType: "reward",
1042
- direction: "increase",
1043
- magnitude: 0.1,
1044
- reasoning: `Low satisfaction with ${dominantRole[0]} dominant. Regulator may be suppressing a structurally necessary role. Ease pressure on dominant role rewards.`
1045
- },
1046
- confidence: 0.55,
1047
- estimatedLag: 8
1048
- };
1049
- }
1065
+ const dominant = Object.entries(roleShares).sort((a, b) => b[1] - a[1])[0];
1066
+ if (!dominant) return { violated: false };
1067
+ const [dominantRole, dominantShare] = dominant;
1068
+ if (dominantShare <= 0.3) return { violated: false };
1069
+ const isStructural = thresholds.dominantRoles?.includes(dominantRole) ?? false;
1070
+ if (isStructural) {
1071
+ return {
1072
+ violated: true,
1073
+ severity: 3,
1074
+ evidence: { role: dominantRole, share: dominantShare, classification: "structural" },
1075
+ suggestedAction: {
1076
+ parameterType: "rate",
1077
+ direction: "set",
1078
+ magnitude: 0,
1079
+ reasoning: `${dominantRole} dominance (${(dominantShare * 100).toFixed(0)}%) is by design. No intervention.`
1080
+ },
1081
+ confidence: 0.85
1082
+ };
1050
1083
  }
1051
- return { violated: false };
1084
+ return {
1085
+ violated: true,
1086
+ severity: 7,
1087
+ evidence: { role: dominantRole, share: dominantShare, classification: "pathological", avgSatisfaction },
1088
+ suggestedAction: {
1089
+ parameterType: "reward",
1090
+ scope: { tags: [dominantRole] },
1091
+ direction: "decrease",
1092
+ magnitude: 0.1,
1093
+ reasoning: `${dominantRole} at ${(dominantShare * 100).toFixed(0)}% is not a designed majority \u2014 apply crowding pressure.`
1094
+ },
1095
+ confidence: 0.7,
1096
+ estimatedLag: 12
1097
+ };
1052
1098
  }
1053
1099
  };
1054
1100
  var INCENTIVE_PRINCIPLES = [
@@ -1314,7 +1360,7 @@ var P15_PoolsNeedCapAndDecay = {
1314
1360
  id: "P15",
1315
1361
  name: "Pools Need Cap + Decay",
1316
1362
  category: "currency",
1317
- description: "Any pool (bank, reward pool) without a cap accumulates infinitely. A pool at 42% of total supply means 42% of the economy is frozen. Cap at 5%, decay at 2%/tick.",
1363
+ description: "Any pool (bank, reward pool) without a cap accumulates infinitely. A pool at 42% of total supply means 42% of the economy is frozen. Cap configurable (default 10%). Violation fires at 2\xD7 cap share of total supply.",
1318
1364
  check(metrics, thresholds) {
1319
1365
  const { poolCapPercent } = thresholds;
1320
1366
  for (const [pool, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
@@ -1347,7 +1393,7 @@ var P16_WithdrawalPenaltyScales = {
1347
1393
  id: "P16",
1348
1394
  name: "Withdrawal Penalty Scales with Lock Duration",
1349
1395
  category: "currency",
1350
- description: "A 50-tick lock period with a penalty calculated as /100 means agents can exit after 1 tick and keep 99% of accrued yield. Penalty must scale linearly: (1 - ticksStaked/lockDuration) \xD7 yield.",
1396
+ description: "Depleted pool with estimated high staking signals early-withdrawal abuse. Symptom detector \u2014 penalty formula is developer responsibility.",
1351
1397
  check(metrics, _thresholds) {
1352
1398
  for (const [poolName, currencyAmounts] of Object.entries(metrics.poolSizesByCurrency)) {
1353
1399
  for (const curr of metrics.currencies) {
@@ -1463,7 +1509,7 @@ var P17_GracePeriodBeforeIntervention = {
1463
1509
  id: "P17",
1464
1510
  name: "Grace Period Before Intervention",
1465
1511
  category: "bootstrap",
1466
- description: "Any intervention before tick 50 is premature. The economy needs time to bootstrap with designed distributions. AgentE intervening at tick 1 against dominant role exceeds 55% (designed) killed the economy instantly.",
1512
+ description: "Any intervention before tick 30 is premature. The economy needs time to bootstrap with designed distributions. Early intervention against designed dominance can kill the economy instantly.",
1467
1513
  check(metrics, _thresholds) {
1468
1514
  if (metrics.tick < 30 && metrics.avgSatisfaction < 40) {
1469
1515
  return {
@@ -1784,7 +1830,7 @@ var P27_AdjustmentsNeedCooldowns = {
1784
1830
  id: "P27",
1785
1831
  name: "Adjustments Need Cooldowns",
1786
1832
  category: "regulator",
1787
- description: "Adjusting the same parameter twice in a window causes oscillation. Minimum 15 ticks between same-parameter adjustments. This is enforced in the Planner but checked here as a diagnostic.",
1833
+ description: "High churn + low satisfaction may indicate oscillation from rapid adjustments. Cooldown enforcement is structural (Planner). This is a symptom detector.",
1788
1834
  check(metrics, _thresholds) {
1789
1835
  const { churnRate, avgSatisfaction } = metrics;
1790
1836
  if (churnRate > 0.08 && avgSatisfaction < 50) {
@@ -1841,7 +1887,7 @@ var P38_CommunicationPreventsRevolt = {
1841
1887
  id: "P38",
1842
1888
  name: "Communication Prevents Revolt",
1843
1889
  category: "regulator",
1844
- description: "Every adjustment must be logged with reasoning. An adjustment made without explanation to participants causes revolt. AgentE logs every decision \u2014 this principle checks that logging is active.",
1890
+ description: "High churn may indicate unexplained changes. Logging enforcement is structural (DecisionLog). Flags high churn as signal to review recent decisions.",
1845
1891
  check(metrics, _thresholds) {
1846
1892
  const { churnRate } = metrics;
1847
1893
  if (churnRate > 0.1) {
@@ -1866,7 +1912,7 @@ var REGULATOR_PRINCIPLES = [
1866
1912
  P25_CorrectLeversForCorrectProblems,
1867
1913
  P26_ContinuousPressureBeatsThresholdCuts,
1868
1914
  P27_AdjustmentsNeedCooldowns,
1869
- P28_StructuralDominanceIsNotPathological,
1915
+ // P28 merged into P8 (v1.6.7)
1870
1916
  P38_CommunicationPreventsRevolt
1871
1917
  ];
1872
1918
 
@@ -2175,7 +2221,7 @@ var P43_SimulationMinimum = {
2175
2221
  id: "P43",
2176
2222
  name: "Simulation Minimum (100 Iterations)",
2177
2223
  category: "statistical",
2178
- description: "Fewer than 100 Monte Carlo iterations produces unreliable predictions. The variance of a 10-iteration simulation is so high that you might as well be guessing. This principle enforces the minimum in the Simulator.",
2224
+ description: "Wild inflation swings (>30%) may indicate insufficient simulation data. Minimum iteration enforcement is structural (Simulator config). Symptom detector.",
2179
2225
  check(metrics, thresholds) {
2180
2226
  const { inflationRate } = metrics;
2181
2227
  if (Math.abs(inflationRate) > 0.3) {
@@ -2342,7 +2388,7 @@ var P49_IdleAssetTax = {
2342
2388
  id: "P49",
2343
2389
  name: "Idle Asset Tax",
2344
2390
  category: "resource",
2345
- description: 'Appreciating assets without holding cost \u2192 wealth concentration. If hoarding an asset makes you richer just by holding it, everyone hoards. Decay rates, storage costs, or expiry are "idle asset taxes" that force circulation.',
2391
+ description: "Concentrated idle wealth (Gini >0.55, top 10% >60%, velocity <5). Raises transaction fees as proxy holding tax. Decay/storage/expiry requires developer implementation.",
2346
2392
  check(metrics, _thresholds) {
2347
2393
  const { giniCoefficient, top10PctShare, velocity } = metrics;
2348
2394
  if (giniCoefficient > 0.55 && top10PctShare > 0.6 && velocity < 5) {
@@ -2375,7 +2421,7 @@ var P33_FairNotEqual = {
2375
2421
  id: "P33",
2376
2422
  name: "Fair \u2260 Equal",
2377
2423
  category: "participant_experience",
2378
- description: "Gini = 0 is boring \u2014 everyone has the same and there is nothing to strive for. Healthy inequality from skill/effort is fine. Inequality from money (pay-to-win) is toxic. Target Gini 0.3-0.45: meaningful spread, not oligarchy.",
2424
+ description: "Gini = 0 is boring \u2014 everyone has the same and there is nothing to strive for. Healthy inequality from skill/effort is fine. Inequality from money (pay-to-win) is toxic. Below 0.10 Gini = too flat; above configurable thresholds = oligarchy.",
2379
2425
  check(metrics, thresholds) {
2380
2426
  for (const curr of metrics.currencies) {
2381
2427
  const giniCoefficient = metrics.giniCoefficientByCurrency[curr] ?? 0;
@@ -2485,7 +2531,7 @@ var P45_TimeBudget = {
2485
2531
  id: "P45",
2486
2532
  name: "Time Budget",
2487
2533
  category: "participant_experience",
2488
- description: "required_time \u2264 available_time \xD7 0.8. If the economy requires more engagement than participants can realistically give, it is a disguised paywall. The 0.8 buffer accounts for real life.",
2534
+ description: "timeToValue > 30 with satisfaction < 55 suggests excessive time demand. Proxy metric \u2014 does not measure individual available time.",
2489
2535
  check(metrics, thresholds) {
2490
2536
  const { timeToValue, avgSatisfaction } = metrics;
2491
2537
  const timePressure = timeToValue > 30;
@@ -2723,7 +2769,7 @@ var P52_EndowmentEffect = {
2723
2769
  id: "P52",
2724
2770
  name: "Endowment Effect",
2725
2771
  category: "operations",
2726
- description: "Participants who never owned premium assets do not value them. Free trial activities that let participants experience premium assets drive conversions because ownership creates perceived value (endowment effect).",
2772
+ description: "High completion (>90%) + low satisfaction (<60) suggests activities not creating perceived value. May indicate missing endowment effect.",
2727
2773
  check(metrics, _thresholds) {
2728
2774
  const { avgSatisfaction, churnRate } = metrics;
2729
2775
  const { eventCompletionRate } = metrics;
@@ -2796,7 +2842,7 @@ var P54_OperationalCadence = {
2796
2842
  id: "P54",
2797
2843
  name: "Operational Cadence",
2798
2844
  category: "operations",
2799
- description: ">50% of activities that are re-wrapped existing supply \u2192 stagnation. The cadence must include genuinely new supply at regular intervals. This is an advisory principle \u2014 AgentE can flag but cannot fix supply.",
2845
+ description: "Low velocity (<2) + low satisfaction after tick 100 = supply stagnation. Advisory signal for developer to audit content freshness.",
2800
2846
  check(metrics, _thresholds) {
2801
2847
  const { velocity, avgSatisfaction } = metrics;
2802
2848
  if (velocity < 2 && avgSatisfaction < 55 && metrics.tick > 100) {
@@ -3646,11 +3692,14 @@ var DEFAULT_PERSONA_CONFIG = {
3646
3692
  dormantWindow: 20,
3647
3693
  atRiskDropThreshold: 0.5,
3648
3694
  powerUserMinSystems: 3,
3649
- historyWindow: 50
3695
+ historyWindow: 50,
3696
+ reclassifyInterval: 10
3650
3697
  };
3651
3698
  var PersonaTracker = class {
3652
3699
  constructor(config) {
3653
3700
  this.agents = /* @__PURE__ */ new Map();
3701
+ this.cachedDistribution = {};
3702
+ this.lastClassifiedTick = -Infinity;
3654
3703
  this.config = { ...DEFAULT_PERSONA_CONFIG, ...config };
3655
3704
  }
3656
3705
  /**
@@ -3658,6 +3707,7 @@ var PersonaTracker = class {
3658
3707
  * Call this once per tick BEFORE getDistribution().
3659
3708
  */
3660
3709
  update(state, events) {
3710
+ if (!state.agentBalances) return;
3661
3711
  const tick = state.tick;
3662
3712
  const txByAgent = /* @__PURE__ */ new Map();
3663
3713
  if (events) {
@@ -3713,8 +3763,18 @@ var PersonaTracker = class {
3713
3763
  /**
3714
3764
  * Classify all tracked agents and return the population distribution.
3715
3765
  * Returns { Whale: 0.05, ActiveTrader: 0.18, Passive: 0.42, ... }
3766
+ * Caches results and only reclassifies at `reclassifyInterval` boundaries.
3716
3767
  */
3717
- getDistribution() {
3768
+ getDistribution(currentTick) {
3769
+ const tick = currentTick ?? 0;
3770
+ if (tick - this.lastClassifiedTick < this.config.reclassifyInterval && Object.keys(this.cachedDistribution).length > 0) {
3771
+ return this.cachedDistribution;
3772
+ }
3773
+ this.lastClassifiedTick = tick;
3774
+ this.cachedDistribution = this._classify();
3775
+ return this.cachedDistribution;
3776
+ }
3777
+ _classify() {
3718
3778
  const agentIds = [...this.agents.keys()];
3719
3779
  const total = agentIds.length;
3720
3780
  if (total === 0) return {};
@@ -4020,16 +4080,17 @@ var AgentE = class {
4020
4080
  this.currentTick = currentState.tick;
4021
4081
  const events = this.eventBuffer;
4022
4082
  this.eventBuffer = [];
4083
+ this.personaTracker.update(currentState, events);
4084
+ const personaDist = this.personaTracker.getDistribution(currentState.tick);
4023
4085
  let metrics;
4024
4086
  try {
4025
- metrics = this.observer.compute(currentState, events);
4087
+ metrics = this.observer.compute(currentState, events, personaDist);
4026
4088
  } catch (err) {
4027
4089
  console.error(`[AgentE] Observer.compute() failed at tick ${currentState.tick}:`, err);
4028
4090
  return;
4029
4091
  }
4030
4092
  this.store.record(metrics);
4031
- this.personaTracker.update(currentState, events);
4032
- metrics.personaDistribution = this.personaTracker.getDistribution();
4093
+ metrics.personaDistribution = personaDist;
4033
4094
  const { rolledBack, settled } = await this.executor.checkRollbacks(metrics, this.adapter);
4034
4095
  for (const plan2 of rolledBack) {
4035
4096
  this.planner.recordRolledBack(plan2);