@shapeshift-labs/frontier-swarm 0.5.5 → 0.5.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.js CHANGED
@@ -90,6 +90,8 @@ export const FRONTIER_SWARM_PATCH_STACK_PLAN_KIND = 'frontier.swarm.patch-stack-
90
90
  export const FRONTIER_SWARM_PATCH_STACK_PLAN_VERSION = 1;
91
91
  export const FRONTIER_SWARM_COORDINATOR_DASHBOARD_KIND = 'frontier.swarm.coordinator-dashboard';
92
92
  export const FRONTIER_SWARM_COORDINATOR_DASHBOARD_VERSION = 1;
93
+ export const FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_KIND = 'frontier.swarm.adaptive-load-plan';
94
+ export const FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_VERSION = 1;
93
95
  export const FRONTIER_SWARM_DEFAULT_CODEX_COMPUTE_ID = 'codex.gpt-5.5.xhigh';
94
96
  export const FRONTIER_SWARM_DEFAULT_MODEL = 'gpt-5.5';
95
97
  export const FRONTIER_SWARM_DEFAULT_REASONING_EFFORT = 'xhigh';
@@ -1756,6 +1758,87 @@ export function querySwarmCoordinatorDashboard(dashboard, query = {}) {
1756
1758
  }
1757
1759
  };
1758
1760
  }
1761
+ export function createSwarmAdaptiveLoadPlan(input = {}) {
1762
+ const generatedAt = input.generatedAt ?? Date.now();
1763
+ const mode = input.mode ?? 'balanced';
1764
+ const maxLimits = createAdaptiveMaxLimits(input);
1765
+ const minLimits = createAdaptiveMinLimits(input.minLimits, maxLimits);
1766
+ const currentLimits = clampAdaptiveLimits(createAdaptiveCurrentLimits(input.currentLimits, maxLimits), minLimits, maxLimits);
1767
+ const effectiveLimits = mode === 'off'
1768
+ ? cloneJsonValue(maxLimits)
1769
+ : cloneJsonValue(currentLimits);
1770
+ const schedule = input.schedule ?? (input.plan ? createSwarmSchedule({ plan: input.plan, run: input.run }) : undefined);
1771
+ const observations = normalizeAdaptiveObservations([
1772
+ ...deriveAdaptiveScheduleObservations(schedule, generatedAt),
1773
+ ...deriveAdaptiveRunObservations(input.run, generatedAt),
1774
+ ...deriveAdaptiveMergeIndexObservations(input.mergeIndex, generatedAt),
1775
+ ...deriveAdaptiveDashboardObservations(input.dashboard, generatedAt),
1776
+ ...deriveAdaptiveAdmissionObservations(input.admission, generatedAt),
1777
+ ...(input.observations ?? [])
1778
+ ], generatedAt);
1779
+ const decisions = [];
1780
+ const bottlenecks = observations.filter((observation) => adaptiveObservationIsBottleneck(observation));
1781
+ if (mode === 'observe') {
1782
+ for (const observation of bottlenecks) {
1783
+ decisions.push(createAdaptiveDecision({
1784
+ action: 'observe',
1785
+ target: adaptiveDecisionTargetForObservation(observation),
1786
+ key: adaptiveDecisionKeyForObservation(observation),
1787
+ reason: observation.reasons[0] ?? observation.kind,
1788
+ observationIds: [observation.id]
1789
+ }));
1790
+ }
1791
+ }
1792
+ else if (mode !== 'off') {
1793
+ for (const observation of bottlenecks) {
1794
+ applyAdaptiveObservation(effectiveLimits, minLimits, maxLimits, mode, observation, decisions);
1795
+ }
1796
+ const healthy = observations.filter((observation) => observation.kind === 'healthy-throughput');
1797
+ if (bottlenecks.length === 0 && healthy.length > 0) {
1798
+ for (const observation of healthy) {
1799
+ applyAdaptiveRecovery(effectiveLimits, maxLimits, observation, decisions);
1800
+ }
1801
+ }
1802
+ }
1803
+ const summary = {
1804
+ observationCount: observations.length,
1805
+ bottleneckCount: bottlenecks.length,
1806
+ decisionCount: decisions.length,
1807
+ reducedCount: decisions.filter((decision) => decision.action === 'decrease').length,
1808
+ increasedCount: decisions.filter((decision) => decision.action === 'increase').length,
1809
+ ...(effectiveLimits.maxReadyJobs !== undefined ? { effectiveMaxReadyJobs: effectiveLimits.maxReadyJobs } : {}),
1810
+ ...(maxLimits.maxReadyJobs !== undefined ? { maxReadyJobs: maxLimits.maxReadyJobs } : {})
1811
+ };
1812
+ return {
1813
+ kind: FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_KIND,
1814
+ version: FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_VERSION,
1815
+ id: input.id ?? 'swarm-adaptive-load-plan:' + stableHash([input.plan?.id, input.run?.id, mode, observations, decisions, generatedAt]),
1816
+ ...(input.plan?.id ? { planId: input.plan.id } : {}),
1817
+ ...(input.run?.id ? { runId: input.run.id } : {}),
1818
+ mode,
1819
+ generatedAt,
1820
+ maxLimits,
1821
+ currentLimits,
1822
+ minLimits,
1823
+ effectiveLimits,
1824
+ observations,
1825
+ decisions,
1826
+ summary,
1827
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
1828
+ };
1829
+ }
1830
+ export function createSwarmScheduleInputFromAdaptiveLoadPlan(plan, adaptive, input = {}) {
1831
+ return {
1832
+ plan,
1833
+ ...(input.run ? { run: input.run } : {}),
1834
+ ...(input.now !== undefined ? { now: input.now } : {}),
1835
+ ...(adaptive.effectiveLimits.maxReadyJobs !== undefined ? { maxReadyJobs: adaptive.effectiveLimits.maxReadyJobs } : {}),
1836
+ maxLaneConcurrency: adaptive.effectiveLimits.maxLaneConcurrency,
1837
+ maxConcurrencyKeyConcurrency: adaptive.effectiveLimits.maxConcurrencyKeyConcurrency,
1838
+ maxComputeConcurrency: adaptive.effectiveLimits.maxComputeConcurrency,
1839
+ resourceQuotas: adaptive.effectiveLimits.resourceQuotas
1840
+ };
1841
+ }
1759
1842
  export function resolveSwarmCompute(manifestInput, taskInput) {
1760
1843
  const compiled = compileSwarm(manifestInput);
1761
1844
  const task = isSwarmTask(taskInput) ? taskInput : normalizeTask(taskInput);
@@ -2271,6 +2354,593 @@ function schedulerPriorityForReason(reason) {
2271
2354
  return 25;
2272
2355
  return 30;
2273
2356
  }
2357
+ function createAdaptiveMaxLimits(input) {
2358
+ const plan = input.plan;
2359
+ const jobCount = Math.max(1, plan?.jobs.length ?? input.schedule?.summary.jobCount ?? input.dashboard?.summary.jobCount ?? 1);
2360
+ const raw = mergeAdaptiveScheduleLimitInputs(plan?.limits, input.maxLimits);
2361
+ const maxReadyJobs = positiveNumber(raw.maxReadyJobs) ? Math.floor(raw.maxReadyJobs) : jobCount;
2362
+ const maxLaneConcurrency = { ...raw.maxLaneConcurrency };
2363
+ const maxConcurrencyKeyConcurrency = { ...raw.maxConcurrencyKeyConcurrency };
2364
+ const maxComputeConcurrency = { ...raw.maxComputeConcurrency };
2365
+ const resourceQuotas = { ...raw.resourceQuotas };
2366
+ for (const job of plan?.jobs ?? []) {
2367
+ maxLaneConcurrency[job.lane] = adaptivePositiveLimit(maxLaneConcurrency[job.lane], job.compute.maxConcurrency ?? maxReadyJobs);
2368
+ maxConcurrencyKeyConcurrency[job.concurrencyKey] = adaptivePositiveLimit(maxConcurrencyKeyConcurrency[job.concurrencyKey], maxReadyJobs);
2369
+ maxComputeConcurrency[job.compute.id] = adaptivePositiveLimit(maxComputeConcurrency[job.compute.id], job.compute.maxConcurrency ?? maxReadyJobs);
2370
+ for (const [resource, amount] of Object.entries(job.resourceRequirements?.resources ?? {})) {
2371
+ resourceQuotas[resource] = adaptivePositiveLimit(resourceQuotas[resource], Math.max(1, Math.ceil(amount), maxReadyJobs));
2372
+ }
2373
+ if (job.resourceRequirements?.browser?.required) {
2374
+ resourceQuotas.browser = adaptivePositiveLimit(resourceQuotas.browser, maxReadyJobs);
2375
+ resourceQuotas['browser-port'] = adaptivePositiveLimit(resourceQuotas['browser-port'], maxReadyJobs);
2376
+ }
2377
+ }
2378
+ return {
2379
+ maxReadyJobs,
2380
+ maxLaneConcurrency: normalizeAdaptiveLimitRecord(maxLaneConcurrency),
2381
+ maxConcurrencyKeyConcurrency: normalizeAdaptiveLimitRecord(maxConcurrencyKeyConcurrency),
2382
+ maxComputeConcurrency: normalizeAdaptiveLimitRecord(maxComputeConcurrency),
2383
+ resourceQuotas: normalizeAdaptiveLimitRecord(resourceQuotas)
2384
+ };
2385
+ }
2386
+ function createAdaptiveCurrentLimits(input, maxLimits) {
2387
+ if (!input)
2388
+ return cloneJsonValue(maxLimits);
2389
+ const raw = mergeAdaptiveScheduleLimitInputs(maxLimits, input);
2390
+ return {
2391
+ ...(positiveNumber(raw.maxReadyJobs) ? { maxReadyJobs: Math.floor(raw.maxReadyJobs) } : {}),
2392
+ maxLaneConcurrency: normalizeAdaptiveLimitRecord(raw.maxLaneConcurrency),
2393
+ maxConcurrencyKeyConcurrency: normalizeAdaptiveLimitRecord(raw.maxConcurrencyKeyConcurrency),
2394
+ maxComputeConcurrency: normalizeAdaptiveLimitRecord(raw.maxComputeConcurrency),
2395
+ resourceQuotas: normalizeAdaptiveLimitRecord(raw.resourceQuotas)
2396
+ };
2397
+ }
2398
+ function createAdaptiveMinLimits(input, maxLimits) {
2399
+ const raw = mergeAdaptiveScheduleLimitInputs(undefined, input);
2400
+ const laneMinimums = Object.fromEntries(Object.keys(maxLimits.maxLaneConcurrency).map((key) => [key, 1]));
2401
+ const keyMinimums = Object.fromEntries(Object.keys(maxLimits.maxConcurrencyKeyConcurrency).map((key) => [key, 1]));
2402
+ const computeMinimums = Object.fromEntries(Object.keys(maxLimits.maxComputeConcurrency).map((key) => [key, 1]));
2403
+ const resourceMinimums = Object.fromEntries(Object.keys(maxLimits.resourceQuotas).map((key) => [key, 1]));
2404
+ return {
2405
+ maxReadyJobs: adaptivePositiveLimit(raw.maxReadyJobs, 1),
2406
+ maxLaneConcurrency: normalizeAdaptiveLimitRecord({ ...laneMinimums, ...raw.maxLaneConcurrency }),
2407
+ maxConcurrencyKeyConcurrency: normalizeAdaptiveLimitRecord({ ...keyMinimums, ...raw.maxConcurrencyKeyConcurrency }),
2408
+ maxComputeConcurrency: normalizeAdaptiveLimitRecord({ ...computeMinimums, ...raw.maxComputeConcurrency }),
2409
+ resourceQuotas: normalizeAdaptiveLimitRecord({ ...resourceMinimums, ...raw.resourceQuotas })
2410
+ };
2411
+ }
2412
+ function mergeAdaptiveScheduleLimitInputs(left, right) {
2413
+ return {
2414
+ ...(right?.maxReadyJobs !== undefined ? { maxReadyJobs: right.maxReadyJobs } : left?.maxReadyJobs !== undefined ? { maxReadyJobs: left.maxReadyJobs } : {}),
2415
+ maxLaneConcurrency: { ...(left?.maxLaneConcurrency ?? {}), ...(right?.maxLaneConcurrency ?? {}) },
2416
+ maxConcurrencyKeyConcurrency: { ...(left?.maxConcurrencyKeyConcurrency ?? {}), ...(right?.maxConcurrencyKeyConcurrency ?? {}) },
2417
+ maxComputeConcurrency: { ...(left?.maxComputeConcurrency ?? {}), ...(right?.maxComputeConcurrency ?? {}) },
2418
+ resourceQuotas: { ...(left?.resourceQuotas ?? {}), ...(right?.resourceQuotas ?? {}) }
2419
+ };
2420
+ }
2421
+ function clampAdaptiveLimits(value, minLimits, maxLimits) {
2422
+ return {
2423
+ ...(value.maxReadyJobs !== undefined || maxLimits.maxReadyJobs !== undefined ? {
2424
+ maxReadyJobs: clampAdaptiveLimit(value.maxReadyJobs ?? maxLimits.maxReadyJobs ?? 1, minLimits.maxReadyJobs ?? 1, maxLimits.maxReadyJobs ?? value.maxReadyJobs ?? 1)
2425
+ } : {}),
2426
+ maxLaneConcurrency: clampAdaptiveRecord(value.maxLaneConcurrency, minLimits.maxLaneConcurrency, maxLimits.maxLaneConcurrency),
2427
+ maxConcurrencyKeyConcurrency: clampAdaptiveRecord(value.maxConcurrencyKeyConcurrency, minLimits.maxConcurrencyKeyConcurrency, maxLimits.maxConcurrencyKeyConcurrency),
2428
+ maxComputeConcurrency: clampAdaptiveRecord(value.maxComputeConcurrency, minLimits.maxComputeConcurrency, maxLimits.maxComputeConcurrency),
2429
+ resourceQuotas: clampAdaptiveRecord(value.resourceQuotas, minLimits.resourceQuotas, maxLimits.resourceQuotas)
2430
+ };
2431
+ }
2432
+ function normalizeAdaptiveObservations(input, generatedAt) {
2433
+ const out = [];
2434
+ input.forEach((entry, index) => {
2435
+ const reasons = uniqueStrings([...(entry.reason ? [entry.reason] : []), ...(entry.reasons ?? [])]);
2436
+ const at = entry.at ?? generatedAt;
2437
+ const observation = {
2438
+ id: entry.id ?? 'swarm-adaptive-observation:' + stableHash([entry.kind, entry.jobId, entry.lane, entry.resource, reasons, index, at]),
2439
+ kind: entry.kind,
2440
+ severity: entry.severity ?? adaptiveDefaultSeverity(entry.kind),
2441
+ at,
2442
+ value: Number.isFinite(entry.value) ? Number(entry.value) : 1,
2443
+ ...(entry.jobId ? { jobId: entry.jobId } : {}),
2444
+ ...(entry.taskId ? { taskId: entry.taskId } : {}),
2445
+ ...(entry.lane ? { lane: entry.lane } : {}),
2446
+ ...(entry.compute ? { compute: entry.compute } : {}),
2447
+ ...(entry.concurrencyKey ? { concurrencyKey: entry.concurrencyKey } : {}),
2448
+ ...(entry.resource ? { resource: entry.resource } : {}),
2449
+ ...(entry.path ? { path: entry.path } : {}),
2450
+ ...(entry.region ? { region: entry.region } : {}),
2451
+ reasons: reasons.length ? reasons : [entry.kind],
2452
+ ...(toJsonObject(entry.metadata) ? { metadata: toJsonObject(entry.metadata) } : {})
2453
+ };
2454
+ out.push(observation);
2455
+ });
2456
+ return dedupeAdaptiveObservations(out);
2457
+ }
2458
+ function deriveAdaptiveScheduleObservations(schedule, at) {
2459
+ if (!schedule)
2460
+ return [];
2461
+ const observations = [];
2462
+ for (const blocked of schedule.blocked) {
2463
+ for (const reason of blocked.reasons) {
2464
+ if (reason === 'waiting-for-dependencies')
2465
+ continue;
2466
+ const resource = reason.startsWith('resource-capacity:') ? reason.slice('resource-capacity:'.length) : undefined;
2467
+ observations.push({
2468
+ kind: resource ? 'resource-capacity' : reason,
2469
+ severity: reason === 'ready-capacity' ? 'info' : 'warning',
2470
+ at,
2471
+ jobId: blocked.jobId,
2472
+ taskId: blocked.taskId,
2473
+ lane: blocked.lane,
2474
+ compute: blocked.compute,
2475
+ concurrencyKey: blocked.concurrencyKey,
2476
+ ...(resource ? { resource } : {}),
2477
+ reason
2478
+ });
2479
+ }
2480
+ }
2481
+ return observations;
2482
+ }
2483
+ function deriveAdaptiveRunObservations(run, at) {
2484
+ if (!run)
2485
+ return [];
2486
+ const observations = [];
2487
+ for (const result of run.results) {
2488
+ if (result.status === 'failed' || result.exitCode !== undefined && result.exitCode !== 0 || result.verification.some((entry) => entry.required !== false && entry.status !== undefined && entry.status !== 0)) {
2489
+ observations.push({
2490
+ kind: 'evidence-failure',
2491
+ severity: 'error',
2492
+ at,
2493
+ jobId: result.jobId,
2494
+ reason: 'worker failed or required evidence command failed'
2495
+ });
2496
+ }
2497
+ if (result.mergeDisposition === 'discovery-only' || result.mergeReadiness === 'discovery-only') {
2498
+ observations.push({
2499
+ kind: 'discovery-only-output',
2500
+ severity: 'info',
2501
+ at,
2502
+ jobId: result.jobId,
2503
+ reason: 'worker produced discovery output instead of a mergeable patch'
2504
+ });
2505
+ }
2506
+ if (semanticSummaryIsEmpty(result.semanticImport)) {
2507
+ observations.push({
2508
+ kind: 'semantic-empty',
2509
+ severity: 'warning',
2510
+ at,
2511
+ jobId: result.jobId,
2512
+ reason: 'semantic import emitted no selected/imported files or symbols'
2513
+ });
2514
+ }
2515
+ else if (semanticSummaryIsWeak(result.semanticImport)) {
2516
+ observations.push({
2517
+ kind: 'semantic-weak',
2518
+ severity: 'info',
2519
+ at,
2520
+ jobId: result.jobId,
2521
+ reason: 'semantic import has limited source maps, regions, or patch hints'
2522
+ });
2523
+ }
2524
+ if (result.durationMs !== undefined && result.durationMs > 900000) {
2525
+ observations.push({
2526
+ kind: 'slow-job',
2527
+ severity: 'warning',
2528
+ at,
2529
+ jobId: result.jobId,
2530
+ value: result.durationMs,
2531
+ reason: 'worker duration exceeded adaptive slow-job threshold'
2532
+ });
2533
+ }
2534
+ if ((result.status === 'completed' || result.status === 'verified') && result.exitCode === 0 && result.changedPaths.length > 0 && result.mergeDisposition !== 'discovery-only') {
2535
+ observations.push({
2536
+ kind: 'healthy-throughput',
2537
+ severity: 'info',
2538
+ at,
2539
+ jobId: result.jobId,
2540
+ reason: 'worker completed with changed paths'
2541
+ });
2542
+ }
2543
+ }
2544
+ return observations;
2545
+ }
2546
+ function deriveAdaptiveMergeIndexObservations(index, at) {
2547
+ if (!index)
2548
+ return [];
2549
+ const observations = [];
2550
+ for (const entry of index.entries) {
2551
+ if (entry.staleAgainstHead || entry.disposition === 'stale-against-head') {
2552
+ observations.push({
2553
+ kind: 'stale-patch',
2554
+ severity: 'warning',
2555
+ at,
2556
+ jobId: entry.jobId,
2557
+ lane: entry.lane,
2558
+ path: entry.changedPaths[0],
2559
+ region: entry.changedRegions[0],
2560
+ reason: 'patch is stale against coordinator head'
2561
+ });
2562
+ }
2563
+ if (entry.conflictingJobIds.length) {
2564
+ observations.push({
2565
+ kind: 'merge-conflict',
2566
+ severity: 'warning',
2567
+ at,
2568
+ jobId: entry.jobId,
2569
+ lane: entry.lane,
2570
+ path: entry.changedPaths[0],
2571
+ region: entry.changedRegions[0],
2572
+ value: entry.conflictingJobIds.length,
2573
+ reason: 'merge index found conflicting changed paths or regions'
2574
+ });
2575
+ }
2576
+ if (entry.disposition === 'discovery-only') {
2577
+ observations.push({
2578
+ kind: 'discovery-only-output',
2579
+ severity: 'info',
2580
+ at,
2581
+ jobId: entry.jobId,
2582
+ lane: entry.lane,
2583
+ reason: 'merge index classified the bundle as discovery-only'
2584
+ });
2585
+ }
2586
+ if (semanticSummaryIsEmpty(entry.semanticImport)) {
2587
+ observations.push({
2588
+ kind: 'semantic-empty',
2589
+ severity: 'warning',
2590
+ at,
2591
+ jobId: entry.jobId,
2592
+ lane: entry.lane,
2593
+ reason: 'semantic sidecar is present but empty'
2594
+ });
2595
+ }
2596
+ else if (semanticSummaryIsWeak(entry.semanticImport)) {
2597
+ observations.push({
2598
+ kind: 'semantic-weak',
2599
+ severity: 'info',
2600
+ at,
2601
+ jobId: entry.jobId,
2602
+ lane: entry.lane,
2603
+ reason: 'semantic sidecar lacks merge-useful structure'
2604
+ });
2605
+ }
2606
+ }
2607
+ return observations;
2608
+ }
2609
+ function deriveAdaptiveDashboardObservations(dashboard, at) {
2610
+ if (!dashboard)
2611
+ return [];
2612
+ const observations = [];
2613
+ for (const job of dashboard.jobs) {
2614
+ if (job.duplicateGroupId) {
2615
+ observations.push({
2616
+ kind: 'duplicate-output',
2617
+ severity: 'info',
2618
+ at,
2619
+ jobId: job.jobId,
2620
+ lane: job.lane,
2621
+ path: job.changedPaths[0],
2622
+ region: job.changedRegions[0],
2623
+ reason: 'coordinator dashboard found duplicate worker output'
2624
+ });
2625
+ }
2626
+ if (job.tests.requiredFailed > 0) {
2627
+ observations.push({
2628
+ kind: 'evidence-failure',
2629
+ severity: 'error',
2630
+ at,
2631
+ jobId: job.jobId,
2632
+ lane: job.lane,
2633
+ reason: 'dashboard shows required evidence failures'
2634
+ });
2635
+ }
2636
+ if (job.staleAgainstHead) {
2637
+ observations.push({
2638
+ kind: 'stale-patch',
2639
+ severity: 'warning',
2640
+ at,
2641
+ jobId: job.jobId,
2642
+ lane: job.lane,
2643
+ path: job.changedPaths[0],
2644
+ reason: 'dashboard marks patch stale against head'
2645
+ });
2646
+ }
2647
+ if (semanticSummaryIsEmpty(job.semanticImport)) {
2648
+ observations.push({
2649
+ kind: 'semantic-empty',
2650
+ severity: 'warning',
2651
+ at,
2652
+ jobId: job.jobId,
2653
+ lane: job.lane,
2654
+ reason: 'dashboard semantic import summary is empty'
2655
+ });
2656
+ }
2657
+ }
2658
+ return observations;
2659
+ }
2660
+ function deriveAdaptiveAdmissionObservations(admission, at) {
2661
+ if (!admission)
2662
+ return [];
2663
+ const observations = [];
2664
+ for (const deferral of admission.deferred) {
2665
+ for (const reason of deferral.reasons) {
2666
+ const kind = reason === 'stale-against-head'
2667
+ ? 'stale-patch'
2668
+ : reason === 'conflicting-changes'
2669
+ ? 'merge-conflict'
2670
+ : reason === 'not-auto-mergeable'
2671
+ ? 'discovery-only-output'
2672
+ : reason === 'max-ready'
2673
+ ? 'ready-capacity'
2674
+ : 'budget-pressure';
2675
+ observations.push({
2676
+ kind,
2677
+ severity: kind === 'ready-capacity' ? 'info' : 'warning',
2678
+ at,
2679
+ jobId: deferral.jobId,
2680
+ reason: `merge admission deferred: ${reason}`
2681
+ });
2682
+ }
2683
+ }
2684
+ return observations;
2685
+ }
2686
+ function applyAdaptiveObservation(limits, minLimits, maxLimits, mode, observation, decisions) {
2687
+ if (observation.kind === 'ready-capacity') {
2688
+ decisions.push(createAdaptiveDecision({
2689
+ action: 'hold',
2690
+ target: 'max-ready-jobs',
2691
+ previous: limits.maxReadyJobs,
2692
+ next: limits.maxReadyJobs,
2693
+ max: maxLimits.maxReadyJobs,
2694
+ min: minLimits.maxReadyJobs,
2695
+ reason: observation.reasons[0] ?? 'ready-capacity',
2696
+ observationIds: [observation.id]
2697
+ }));
2698
+ return;
2699
+ }
2700
+ const target = adaptiveDecisionTargetForObservation(observation);
2701
+ const key = adaptiveDecisionKeyForObservation(observation);
2702
+ if (adaptiveObservationIsCapacityBackpressure(observation)) {
2703
+ decisions.push(createAdaptiveDecision({
2704
+ action: 'hold',
2705
+ target,
2706
+ ...(key ? { key } : {}),
2707
+ reason: observation.reasons[0] ?? observation.kind,
2708
+ observationIds: [observation.id]
2709
+ }));
2710
+ return;
2711
+ }
2712
+ if (target === 'lane' && key) {
2713
+ decreaseAdaptiveRecordLimit(limits.maxLaneConcurrency, minLimits.maxLaneConcurrency, maxLimits.maxLaneConcurrency, key, mode, observation, decisions, target);
2714
+ }
2715
+ else if (target === 'concurrency-key' && key) {
2716
+ decreaseAdaptiveRecordLimit(limits.maxConcurrencyKeyConcurrency, minLimits.maxConcurrencyKeyConcurrency, maxLimits.maxConcurrencyKeyConcurrency, key, mode, observation, decisions, target);
2717
+ }
2718
+ else if (target === 'compute' && key) {
2719
+ decreaseAdaptiveRecordLimit(limits.maxComputeConcurrency, minLimits.maxComputeConcurrency, maxLimits.maxComputeConcurrency, key, mode, observation, decisions, target);
2720
+ }
2721
+ else if (target === 'resource' && key) {
2722
+ decreaseAdaptiveRecordLimit(limits.resourceQuotas, minLimits.resourceQuotas, maxLimits.resourceQuotas, key, mode, observation, decisions, target);
2723
+ }
2724
+ if (adaptiveObservationShouldReduceReadyWindow(observation)) {
2725
+ const previous = limits.maxReadyJobs ?? maxLimits.maxReadyJobs ?? 1;
2726
+ const min = minLimits.maxReadyJobs ?? 1;
2727
+ const max = maxLimits.maxReadyJobs ?? previous;
2728
+ const next = adaptiveReducedValue(previous, min, mode, observation);
2729
+ limits.maxReadyJobs = clampAdaptiveLimit(next, min, max);
2730
+ decisions.push(createAdaptiveDecision({
2731
+ action: limits.maxReadyJobs < previous ? 'decrease' : 'hold',
2732
+ target: 'max-ready-jobs',
2733
+ previous,
2734
+ next: limits.maxReadyJobs,
2735
+ max,
2736
+ min,
2737
+ reason: observation.reasons[0] ?? observation.kind,
2738
+ observationIds: [observation.id]
2739
+ }));
2740
+ }
2741
+ }
2742
+ function applyAdaptiveRecovery(limits, maxLimits, observation, decisions) {
2743
+ if (limits.maxReadyJobs !== undefined && maxLimits.maxReadyJobs !== undefined && limits.maxReadyJobs < maxLimits.maxReadyJobs) {
2744
+ const previous = limits.maxReadyJobs;
2745
+ limits.maxReadyJobs = Math.min(maxLimits.maxReadyJobs, previous + 1);
2746
+ decisions.push(createAdaptiveDecision({
2747
+ action: 'increase',
2748
+ target: 'max-ready-jobs',
2749
+ previous,
2750
+ next: limits.maxReadyJobs,
2751
+ max: maxLimits.maxReadyJobs,
2752
+ reason: observation.reasons[0] ?? observation.kind,
2753
+ observationIds: [observation.id]
2754
+ }));
2755
+ }
2756
+ if (observation.lane) {
2757
+ increaseAdaptiveRecordLimit(limits.maxLaneConcurrency, maxLimits.maxLaneConcurrency, observation.lane, observation, decisions, 'lane');
2758
+ }
2759
+ if (observation.compute) {
2760
+ increaseAdaptiveRecordLimit(limits.maxComputeConcurrency, maxLimits.maxComputeConcurrency, observation.compute, observation, decisions, 'compute');
2761
+ }
2762
+ }
2763
+ function decreaseAdaptiveRecordLimit(record, minRecord, maxRecord, key, mode, observation, decisions, target) {
2764
+ const previous = record[key] ?? maxRecord[key];
2765
+ if (!positiveNumber(previous))
2766
+ return;
2767
+ const min = minRecord[key] ?? 1;
2768
+ const max = maxRecord[key] ?? previous;
2769
+ const next = clampAdaptiveLimit(adaptiveReducedValue(previous, min, mode, observation), min, max);
2770
+ record[key] = next;
2771
+ decisions.push(createAdaptiveDecision({
2772
+ action: next < previous ? 'decrease' : 'hold',
2773
+ target,
2774
+ key,
2775
+ previous,
2776
+ next,
2777
+ max,
2778
+ min,
2779
+ reason: observation.reasons[0] ?? observation.kind,
2780
+ observationIds: [observation.id]
2781
+ }));
2782
+ }
2783
+ function increaseAdaptiveRecordLimit(record, maxRecord, key, observation, decisions, target) {
2784
+ const previous = record[key];
2785
+ const max = maxRecord[key];
2786
+ if (!positiveNumber(previous) || !positiveNumber(max) || previous >= max)
2787
+ return;
2788
+ record[key] = Math.min(max, previous + 1);
2789
+ decisions.push(createAdaptiveDecision({
2790
+ action: 'increase',
2791
+ target,
2792
+ key,
2793
+ previous,
2794
+ next: record[key],
2795
+ max,
2796
+ reason: observation.reasons[0] ?? observation.kind,
2797
+ observationIds: [observation.id]
2798
+ }));
2799
+ }
2800
+ function adaptiveReducedValue(previous, min, mode, observation) {
2801
+ if (observation.kind === 'merge-conflict' || observation.kind === 'duplicate-output' || observation.kind === 'concurrency-key-capacity')
2802
+ return min;
2803
+ const severityFactor = observation.severity === 'critical' ? 0.45 : observation.severity === 'error' ? 0.55 : observation.severity === 'warning' ? 0.7 : 0.85;
2804
+ const modeFactor = mode === 'conservative' ? 0.6 : mode === 'aggressive' ? 0.85 : 0.75;
2805
+ return Math.max(min, Math.floor(previous * Math.min(severityFactor, modeFactor)));
2806
+ }
2807
+ function adaptiveObservationShouldReduceReadyWindow(observation) {
2808
+ return observation.kind === 'evidence-failure'
2809
+ || observation.kind === 'stale-patch'
2810
+ || observation.kind === 'browser-contention'
2811
+ || observation.kind === 'semantic-empty'
2812
+ || observation.kind === 'log-noise'
2813
+ || observation.kind === 'discovery-only-output'
2814
+ || observation.kind === 'budget-pressure'
2815
+ || observation.kind === 'slow-job';
2816
+ }
2817
+ function adaptiveObservationIsCapacityBackpressure(observation) {
2818
+ return observation.kind === 'resource-capacity'
2819
+ || observation.kind === 'lane-capacity'
2820
+ || observation.kind === 'concurrency-key-capacity'
2821
+ || observation.kind === 'compute-capacity';
2822
+ }
2823
+ function adaptiveDecisionTargetForObservation(observation) {
2824
+ if (observation.kind === 'resource-capacity' || observation.kind === 'browser-contention')
2825
+ return 'resource';
2826
+ if (observation.kind === 'lane-capacity')
2827
+ return 'lane';
2828
+ if (observation.kind === 'concurrency-key-capacity' || observation.kind === 'merge-conflict' || observation.kind === 'duplicate-output')
2829
+ return 'concurrency-key';
2830
+ if (observation.kind === 'compute-capacity')
2831
+ return 'compute';
2832
+ if (observation.lane)
2833
+ return 'lane';
2834
+ return 'max-ready-jobs';
2835
+ }
2836
+ function adaptiveDecisionKeyForObservation(observation) {
2837
+ const target = adaptiveDecisionTargetForObservation(observation);
2838
+ if (target === 'resource')
2839
+ return observation.resource ?? (observation.kind === 'browser-contention' ? 'browser' : undefined);
2840
+ if (target === 'lane')
2841
+ return observation.lane;
2842
+ if (target === 'concurrency-key')
2843
+ return observation.concurrencyKey ?? observation.region ?? observation.path;
2844
+ if (target === 'compute')
2845
+ return observation.compute;
2846
+ return undefined;
2847
+ }
2848
+ function adaptiveObservationIsBottleneck(observation) {
2849
+ if (observation.kind === 'healthy-throughput' || observation.kind === 'ready-capacity')
2850
+ return false;
2851
+ return observation.severity !== 'info'
2852
+ || observation.kind === 'merge-conflict'
2853
+ || observation.kind === 'stale-patch'
2854
+ || observation.kind === 'semantic-empty'
2855
+ || observation.kind === 'log-noise'
2856
+ || observation.kind === 'duplicate-output';
2857
+ }
2858
+ function adaptiveDefaultSeverity(kind) {
2859
+ if (kind === 'evidence-failure' || kind === 'budget-pressure')
2860
+ return 'error';
2861
+ if (kind === 'merge-conflict' || kind === 'stale-patch' || kind === 'semantic-empty' || kind === 'browser-contention' || kind.endsWith('-capacity'))
2862
+ return 'warning';
2863
+ return 'info';
2864
+ }
2865
+ function createAdaptiveDecision(input) {
2866
+ return {
2867
+ id: 'swarm-adaptive-decision:' + stableHash([input.action, input.target, input.key, input.previous, input.next, input.reason, input.observationIds]),
2868
+ ...input
2869
+ };
2870
+ }
2871
+ function semanticSummaryIsEmpty(summary) {
2872
+ if (!summary)
2873
+ return false;
2874
+ return summary.total === 0
2875
+ || summary.selected === 0 && summary.eligible === 0 && summary.imported === 0 && summary.semanticIndex.symbols === 0 && summary.semanticSidecars.symbols === 0;
2876
+ }
2877
+ function semanticSummaryIsWeak(summary) {
2878
+ if (!summary || semanticSummaryIsEmpty(summary))
2879
+ return false;
2880
+ return summary.imported === 0
2881
+ || summary.semanticIndex.symbols === 0
2882
+ || summary.semanticSidecars.ownershipRegions === 0
2883
+ || summary.sourceMapMappingCount === 0;
2884
+ }
2885
+ function dedupeAdaptiveObservations(observations) {
2886
+ const byKey = new Map();
2887
+ for (const observation of observations) {
2888
+ const key = [
2889
+ observation.kind,
2890
+ observation.jobId ?? '',
2891
+ observation.lane ?? '',
2892
+ observation.compute ?? '',
2893
+ observation.concurrencyKey ?? '',
2894
+ observation.resource ?? '',
2895
+ observation.path ?? '',
2896
+ observation.region ?? '',
2897
+ observation.reasons.join('|')
2898
+ ].join('\0');
2899
+ const existing = byKey.get(key);
2900
+ if (!existing || adaptiveSeverityRank(observation.severity) > adaptiveSeverityRank(existing.severity))
2901
+ byKey.set(key, observation);
2902
+ }
2903
+ return Array.from(byKey.values()).sort((left, right) => adaptiveSeverityRank(right.severity) - adaptiveSeverityRank(left.severity) || left.kind.localeCompare(right.kind) || (left.jobId ?? '').localeCompare(right.jobId ?? ''));
2904
+ }
2905
+ function adaptiveSeverityRank(severity) {
2906
+ if (severity === 'critical')
2907
+ return 4;
2908
+ if (severity === 'error')
2909
+ return 3;
2910
+ if (severity === 'warning')
2911
+ return 2;
2912
+ if (severity === 'info')
2913
+ return 1;
2914
+ return 0;
2915
+ }
2916
+ function normalizeAdaptiveLimitRecord(input = {}) {
2917
+ const out = {};
2918
+ for (const [key, value] of Object.entries(input)) {
2919
+ if (positiveNumber(value))
2920
+ out[key] = Math.max(1, Math.floor(value));
2921
+ }
2922
+ return out;
2923
+ }
2924
+ function clampAdaptiveRecord(value, minRecord, maxRecord) {
2925
+ const keys = uniqueStrings([...Object.keys(value), ...Object.keys(minRecord), ...Object.keys(maxRecord)]);
2926
+ const out = {};
2927
+ for (const key of keys) {
2928
+ const max = maxRecord[key];
2929
+ if (!positiveNumber(max))
2930
+ continue;
2931
+ const min = minRecord[key] ?? 1;
2932
+ out[key] = clampAdaptiveLimit(value[key] ?? max, min, max);
2933
+ }
2934
+ return out;
2935
+ }
2936
+ function clampAdaptiveLimit(value, min, max) {
2937
+ const upper = Math.max(1, Math.floor(max));
2938
+ const lower = Math.min(upper, Math.max(1, Math.floor(min)));
2939
+ return Math.min(upper, Math.max(lower, Math.floor(value)));
2940
+ }
2941
+ function adaptivePositiveLimit(value, fallback) {
2942
+ return positiveNumber(value) ? Math.max(1, Math.floor(value)) : Math.max(1, Math.floor(fallback));
2943
+ }
2274
2944
  function normalizeBudget(input = {}) {
2275
2945
  return {
2276
2946
  ...(positiveNumber(input.maxCostUsd) ? { maxCostUsd: input.maxCostUsd } : {}),