maxpool 1.1.0 → 1.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxpool",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Multi-account Claude Code proxy with adaptive, rate-aware load balancing across Claude accounts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -34,14 +34,20 @@ const DEFAULT_SCHEDULER = {
34
34
  weeklyCriticalThreshold: 0.95,
35
35
  weeklyExhaustedThreshold: 0.985,
36
36
  weeklyBurnDebtWeight: 0.6,
37
- // Routing-cost tuning (lower cost = preferred). Quota scarcity is the primary
38
- // signal; recent-load spread breaks ties between equally-scarce accounts so
39
- // sequential traffic rotates instead of funnelling onto one account.
40
- scarcityWeight: 6, // multiplies quota scarcity (pace overage, 0..~1)
41
- spreadShareWeight: 3, // multiplies an account's share of recent fleet load (0..1)
42
- recoveryRampWeight: 4, // decaying penalty applied to a just-recovered account
43
- recoveryRampMs: 5 * 60_000, // how long the post-recovery ramp lasts
44
- spreadWindowMs: 15 * 60_000,// rolling window used to measure recent per-account load
37
+ // Routing-cost tuning (lower cost = preferred). The goal is to AVOID
38
+ // short-term (rate/concurrency) throttling by spreading load across healthy
39
+ // accounts. So in-flight concurrency is the DOMINANT term, with a steep
40
+ // per-account soft cap; burn-pace is only a soft de-preference (never a
41
+ // bench); quota "use-it-or-lose-it" is intentionally a minor signal here.
42
+ concurrencyWeight: 2, // multiplies in-flight load (activeWeight+reqWeight) — dominant
43
+ perAccountConcurrencyTarget: 3, // D: soft per-account in-flight target; past it, capPenalty bites
44
+ capPenaltyWeight: 10, // steep penalty per unit of in-flight depth past D (throttle safety floor)
45
+ paceCostWeight: 1.5, // soft de-preference of accounts burning ahead of pace (was the ×6 term)
46
+ scarcityWeight: 6, // legacy; superseded by paceCostWeight (kept so old configs don't error)
47
+ spreadShareWeight: 3, // multiplies an account's share of recent fleet load (0..1)
48
+ recoveryRampWeight: 4, // decaying penalty applied to a just-recovered account
49
+ recoveryRampMs: 5 * 60_000, // how long the post-recovery ramp lasts
50
+ spreadWindowMs: 15 * 60_000, // rolling window used to measure recent per-account load
45
51
  };
46
52
  const LOAD_EVENT_MAX_AGE_MS = 60 * 60 * 1000;
47
53
  const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
@@ -454,7 +460,10 @@ export class AccountManager {
454
460
  if (this.getGlobalInFlight() >= this.scheduler.safetyMaxGlobalActive) return false;
455
461
  if (account.status === 'exhausted' || account.status === 'error') return false;
456
462
  if (this._isSessionQuotaUnavailable(account)) return false;
457
- const weeklyState = this._weeklyState(account);
463
+ // Gate on RAW weekly usage, not pace-adjusted: an account with real
464
+ // headroom (e.g. 69% used, resets in days) must stay in the healthy-spread
465
+ // pool even if it's burning fast. Pace is a soft SCORE cost, never a bench.
466
+ const weeklyState = this._weeklyRawState(account);
458
467
  if (weeklyState === 'exhausted') return false;
459
468
  if (weeklyState === 'critical' && !options.allowWeeklyCritical) return false;
460
469
  if (weeklyState === 'reserve' && !options.allowWeeklyReserve) return false;
@@ -1125,8 +1134,21 @@ export class AccountManager {
1125
1134
  _scoreAccount(account, requestInfo = {}, ctx = null) {
1126
1135
  const now = ctx?.now ?? Date.now();
1127
1136
  const reqWeight = Math.max(1, requestInfo.weight || 1);
1128
- const concurrency = account.activeWeight + reqWeight;
1129
- const scarcity = this._accountScarcity(account, now) * this.scheduler.scarcityWeight;
1137
+ const inflight = account.activeWeight + reqWeight;
1138
+
1139
+ // DOMINANT term: in-flight concurrency. Short-term throttling is driven by
1140
+ // how many requests pile on one account, so least-loaded-first spread is
1141
+ // the primary objective.
1142
+ const concurrency = inflight * this.scheduler.concurrencyWeight;
1143
+
1144
+ // Steep soft cap past depth D — the throttle safety floor. No single
1145
+ // account absorbs a deep concurrent burst no matter how "cheap" it looks.
1146
+ const capPenalty = this.scheduler.capPenaltyWeight
1147
+ * Math.max(0, inflight - this.scheduler.perAccountConcurrencyTarget);
1148
+
1149
+ // Burn-pace COST only (demoted from the old dominant scarcity×6 term): a
1150
+ // soft de-preference of accounts burning ahead of an even pace. Never a bench.
1151
+ const paceCost = this._accountScarcity(account, now) * this.scheduler.paceCostWeight;
1130
1152
 
1131
1153
  const fleetRecentWeight = ctx?.fleetRecentWeight ?? 0;
1132
1154
  const recentWeight = this._loadSummary(account, this.scheduler.spreadWindowMs, now).weight;
@@ -1139,7 +1161,7 @@ export class AccountManager {
1139
1161
  // probed and learned (matches the legacy unknown-quota exploration nudge).
1140
1162
  const explorationBonus = account.quota.unified7dReset == null ? -0.5 : 0;
1141
1163
 
1142
- return concurrency + scarcity + spread + ramp + failurePenalty + explorationBonus;
1164
+ return concurrency + capPenalty + paceCost + spread + ramp + failurePenalty + explorationBonus;
1143
1165
  }
1144
1166
 
1145
1167
  /**