@claudexor/orchestrator 3.2.0 → 3.2.1

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.
@@ -326,9 +326,11 @@ export class Orchestrator {
326
326
  execRootOf(input) {
327
327
  return input.executionRoot ?? input.repoRoot;
328
328
  }
329
- async sessionSpecFields(input, harnessId, log, defaultRoute = null) {
329
+ async sessionSpecFields(input, harnessId, model, log, defaultRoute = null, quotaAdmission) {
330
330
  const cfg = this.config(input.repoRoot)?.global;
331
- const profile = await this.preflightProfile(input, harnessId, log, defaultRoute);
331
+ const profile = quotaAdmission?.model === model
332
+ ? quotaAdmission.profile
333
+ : await this.preflightProfile(input, harnessId, model, log, defaultRoute);
332
334
  return {
333
335
  // "auto" at ANY level falls through (thread turns send the thread default
334
336
  // "auto" as a per-run value; it must not shadow a configured preference).
@@ -426,13 +428,13 @@ export class Orchestrator {
426
428
  .filter(({ profile, status }) => profileStatusAdmits(profile, status))
427
429
  .map(({ profile }) => profile.profile_id));
428
430
  }
429
- async preflightProfile(input, harnessId, log, defaultRoute) {
431
+ async preflightProfile(input, harnessId, model, log, defaultRoute) {
430
432
  const profile = this.resolveCredentialProfile(input, harnessId);
431
433
  const policy = this.profilePolicy(input.repoRoot, harnessId);
432
434
  const registry = this.config(input.repoRoot)?.global.credential_profiles ?? [];
433
435
  const snapshots = this.deps.quotaSnapshots?.() ?? [];
434
436
  const emit = (type, payload) => log?.emit(type, payload);
435
- const breach = profileHeadroomBreach(snapshots, harnessId, profile?.profile_id ?? null, policy.headroom_threshold);
437
+ const breach = profileHeadroomBreach(snapshots, harnessId, profile?.profile_id ?? null, policy.headroom_threshold, model);
436
438
  const readyProfileIds = policy.limit_action === "rotate" &&
437
439
  breach !== null &&
438
440
  (profile !== null || defaultRoute === "local_session")
@@ -449,6 +451,7 @@ export class Orchestrator {
449
451
  snapshots,
450
452
  readyProfileIds,
451
453
  defaultRoute,
454
+ model,
452
455
  emit,
453
456
  });
454
457
  }
@@ -459,6 +462,7 @@ export class Orchestrator {
459
462
  registry,
460
463
  snapshots,
461
464
  readyProfileIds,
465
+ model,
462
466
  emit,
463
467
  });
464
468
  }
@@ -728,6 +732,7 @@ export class Orchestrator {
728
732
  // the default store's sources apply only to profile-less runs.
729
733
  authRouteEstimate: this.profileAuthRoute(input, id) ??
730
734
  estimateEffectiveAuthRoute(this.authPreferenceForHarness(input.repoRoot, id, input.authPreference), status.authSources),
735
+ quotaAdmission: { model: null, profile: null, route: null },
731
736
  supportsSynthesize: manifest.capabilities.synthesize,
732
737
  supportsInteractive: manifest.capabilities.interactive,
733
738
  supportsJsonSchemaOutput: manifest.capabilities.json_schema_output,
@@ -762,11 +767,29 @@ export class Orchestrator {
762
767
  if (pool.length === 0) {
763
768
  throw new HarnessUnavailableError(`no harness can perform '${intent}' for this mode${dropped.length ? ` (skipped: ${dropped.join(", ")})` : ""}`);
764
769
  }
765
- const ordered = this.orderPool(pool, input, intent, statusById, ledger, runId);
770
+ // Quota admission must use the account that will actually spawn. In
771
+ // particular, an opt-in default-subject rotation has to select its ready
772
+ // profile before the budget router filters the exhausted default away.
773
+ // The same resolved profile is reused by the first spec build below.
774
+ const quotaPreparedPool = await Promise.all(pool.map(async (routed) => {
775
+ const model = input.models?.[routed.adapter.id] ?? routed.settings?.defaultModel ?? null;
776
+ const profile = await this.preflightProfile(input, routed.adapter.id, model, log, routed.authRouteEstimate);
777
+ const route = profile
778
+ ? profile.credential_kind === "api_key"
779
+ ? "managed_api_key"
780
+ : "vendor_native"
781
+ : routed.authRouteEstimate === "api_key"
782
+ ? "managed_api_key"
783
+ : routed.authRouteEstimate === "local_session"
784
+ ? "vendor_native"
785
+ : null;
786
+ return { ...routed, quotaAdmission: { model, profile, route } };
787
+ }));
788
+ const ordered = this.orderPool(quotaPreparedPool, input, intent, statusById, ledger, runId);
766
789
  if (ordered.length === 0) {
767
790
  throw new HarnessUnavailableError(`no harness remains eligible for '${intent}' after budget and quota routing`);
768
791
  }
769
- emitPrimaryDivergence(log, input.primaryHarness, ordered, pool, dropped);
792
+ emitPrimaryDivergence(log, input.primaryHarness, ordered, quotaPreparedPool, dropped);
770
793
  const n = input.n ?? ordered.length;
771
794
  const selectionOrder = ordered;
772
795
  const out = [];
@@ -873,28 +896,25 @@ export class Orchestrator {
873
896
  : authModes.includes("api_key")
874
897
  ? "api_key"
875
898
  : "unknown";
876
- // A selected profile's credential_kind decides the route outright
877
- // (round-18 #2): an api_key profile must never inherit a
899
+ // The quota-preflight profile's credential_kind decides the route
900
+ // outright (round-18 #2). This includes an unpinned default subject
901
+ // that rotated before ranking; an api_key profile must never inherit a
878
902
  // subscription classification from the default store's metric.
879
- const authMode = this.profileAuthRoute(input, r.adapter.id) ??
903
+ const admittedProfileRoute = r.quotaAdmission.profile
904
+ ? r.quotaAdmission.profile.credential_kind === "api_key"
905
+ ? "api_key"
906
+ : "local_session"
907
+ : null;
908
+ const authMode = admittedProfileRoute ??
880
909
  (input.authPreference === "api_key"
881
910
  ? "api_key"
882
911
  : input.authPreference === "subscription"
883
912
  ? "local_session"
884
913
  : (metric?.last_auth_mode ?? guessedAuthMode));
885
- // The quota subject this candidate would actually run as (release
886
- // wave round-16 #2): the resolved profile id, or null for the engine
887
- // default so profile A's cooldown never excludes profile B or the
888
- // default. A profile that does not resolve for this harness routes
889
- // as unknown (undefined) and stays conservatively any-subject.
890
- let credentialSubjectId;
891
- try {
892
- credentialSubjectId =
893
- this.resolveCredentialProfile(input, r.adapter.id)?.profile_id ?? null;
894
- }
895
- catch {
896
- credentialSubjectId = undefined;
897
- }
914
+ // The exact quota subject selected before ranking: profile id or the
915
+ // engine default. Profile A's cooldown never excludes profile B or the
916
+ // default on the same harness and route.
917
+ const credentialSubjectId = r.quotaAdmission.profile?.profile_id ?? null;
898
918
  // QA-034: the typed auth-route evidence (doctor source verification x the
899
919
  // resolved route) is AUTHORITATIVE for billing knowledge in the router —
900
920
  // a VERIFIED native route proves subscription_entitlement, so it survives
@@ -905,20 +925,19 @@ export class Orchestrator {
905
925
  return {
906
926
  harnessId: r.adapter.id,
907
927
  available: true,
908
- model: input.models?.[r.adapter.id] ??
909
- config.harnesses[r.adapter.id]?.default_model ??
910
- undefined,
928
+ model: r.quotaAdmission.model,
911
929
  effort: input.efforts?.[r.adapter.id] ??
912
930
  input.effort ??
913
931
  config.harnesses[r.adapter.id]?.effort ??
914
932
  undefined,
915
933
  billingKnowledge: authMode === "api_key" ? "metered" : "unknown",
916
934
  incrementalCostUsd: authMode === "api_key" ? (metric?.avg_cost_usd ?? null) : null,
917
- credentialRoute: authMode === "api_key"
918
- ? "managed_api_key"
919
- : authMode === "local_session"
920
- ? "vendor_native"
921
- : undefined,
935
+ credentialRoute: r.quotaAdmission.route ??
936
+ (authMode === "api_key"
937
+ ? "managed_api_key"
938
+ : authMode === "local_session"
939
+ ? "vendor_native"
940
+ : undefined),
922
941
  ...(authRoute ? { authRoute } : {}),
923
942
  credentialSubjectId,
924
943
  };
@@ -966,6 +985,36 @@ export class Orchestrator {
966
985
  }
967
986
  return ordered;
968
987
  }
988
+ /** Harness-only convergence helpers still need the exact quota identity
989
+ * selected before ranking. This facade keeps their small interface while
990
+ * preventing a scoped limit on one model/account from cooling another. */
991
+ quotaLedgerView(ledger, routes) {
992
+ const byId = new Map(routes.map((route) => [route.adapter.id, route]));
993
+ const identity = (id) => {
994
+ const route = byId.get(id);
995
+ return route
996
+ ? {
997
+ credentialRoute: route.quotaAdmission.route ?? undefined,
998
+ credentialSubjectId: route.quotaAdmission.profile?.profile_id ?? null,
999
+ model: route.quotaAdmission.model,
1000
+ }
1001
+ : null;
1002
+ };
1003
+ return {
1004
+ bindingPaceSlack: (id) => {
1005
+ const selected = identity(id);
1006
+ return selected
1007
+ ? ledger.bindingPaceSlack(id, selected.credentialRoute, selected.credentialSubjectId, Date.now(), selected.model)
1008
+ : ledger.bindingPaceSlack(id);
1009
+ },
1010
+ cooldownActive: (id) => {
1011
+ const selected = identity(id);
1012
+ return selected
1013
+ ? ledger.cooldownActive(id, selected.credentialRoute, selected.credentialSubjectId, Date.now(), selected.model)
1014
+ : ledger.cooldownActive(id);
1015
+ },
1016
+ };
1017
+ }
969
1018
  /**
970
1019
  * Typed auth-route evidence for one candidate (QA-034): the concrete
971
1020
  * credential route the resolved auth mode maps to, plus the doctor's
@@ -1343,7 +1392,7 @@ export class Orchestrator {
1343
1392
  const inPlaceEnvelope = envelope.worktree_path === envelope.repo_root;
1344
1393
  const rawContextPacket = await rawContextForEnvelope(routed.implementationTransport, envelope);
1345
1394
  const sessionFields = runInput
1346
- ? await this.sessionSpecFields(runInput, adapter.id, log, routed.authRouteEstimate)
1395
+ ? await this.sessionSpecFields(runInput, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission)
1347
1396
  : undefined;
1348
1397
  // Continuity (INV-137): once the lane (harness + resolved profile) is known,
1349
1398
  // build the continuation packet, materialize context/THREAD.md, and point
@@ -3263,7 +3312,8 @@ export class Orchestrator {
3263
3312
  // observed quota cooldown across all harnesses, or genuine no-progress (a stall on the same
3264
3313
  // failure signature after every available harness has tried it).
3265
3314
  const stallThreshold = input.untilClean === true ? 4 : 2;
3266
- const allCooledDown = () => adapterPool.every((a) => ledger.cooldownActive(a.adapter.id));
3315
+ const convergenceQuotaLedger = this.quotaLedgerView(ledger, adapterPool);
3316
+ const allCooledDown = () => adapterPool.every((a) => convergenceQuotaLedger.cooldownActive(a.adapter.id));
3267
3317
  const attemptTelemetries = [];
3268
3318
  let lastDiffStable = true;
3269
3319
  try {
@@ -3614,7 +3664,7 @@ export class Orchestrator {
3614
3664
  if (adapterPool.length > 1 && triedSinceProgress.size < adapterPool.length) {
3615
3665
  // Quota-headroom consumer (mid-run, where quota observations EXIST);
3616
3666
  // pick + honest route event owned by runSupport.rotateOnStall.
3617
- adapterIdx = rotateOnStall(adapterPool.map((a) => a.adapter.id), adapterIdx, ledger, triedSinceProgress, log, lastRun?.harnessId ?? null);
3667
+ adapterIdx = rotateOnStall(adapterPool.map((a) => a.adapter.id), adapterIdx, convergenceQuotaLedger, triedSinceProgress, log, lastRun?.harnessId ?? null);
3618
3668
  routed = adapterPool[adapterIdx];
3619
3669
  adapter = routed.adapter;
3620
3670
  }
@@ -3903,7 +3953,7 @@ export class Orchestrator {
3903
3953
  const adapter = routed.adapter;
3904
3954
  const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
3905
3955
  const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
3906
- const sessionFields = await this.sessionSpecFields(input, adapter.id, log, routed.authRouteEstimate);
3956
+ const sessionFields = await this.sessionSpecFields(input, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission);
3907
3957
  const laneContinuity = args.laneRun
3908
3958
  ? await this.resolveContinuity(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null, sessionFields.resume_session_id !== null, sessionFields, store, paths, this.execRootOf(input), log)
3909
3959
  : null;
@@ -4281,7 +4331,7 @@ export class Orchestrator {
4281
4331
  buildSpec: async (routed, homeEnv, prompt, attemptId) => {
4282
4332
  const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
4283
4333
  const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
4284
- const sessionFields = await this.sessionSpecFields(input, routed.adapter.id, log, routed.authRouteEstimate);
4334
+ const sessionFields = await this.sessionSpecFields(input, routed.adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission);
4285
4335
  const spec = HarnessRunSpec.parse({
4286
4336
  session_id: newId("ses"),
4287
4337
  intent: "synthesize",
@@ -4519,7 +4569,7 @@ export class Orchestrator {
4519
4569
  const explorerPrompt = (opts.deepScan
4520
4570
  ? `${prompt}\n\nExplorer ${idx + 1}/${adapters.length}: focus on a distinct slice. Emit evidence-cited findings, explicit unknowns/omissions, and follow-up questions. Do not edit files.`
4521
4571
  : prompt) + contextSection;
4522
- const sessionFields = await this.sessionSpecFields(input, adapter.id, log, routed.authRouteEstimate);
4572
+ const sessionFields = await this.sessionSpecFields(input, adapter.id, knobs.model, log, routed.authRouteEstimate, routed.quotaAdmission);
4523
4573
  const grantResume = sessionFields.resume_session_id !== null && !resumeGranted.has(adapter.id);
4524
4574
  if (grantResume)
4525
4575
  resumeGranted.add(adapter.id);