@shapeshift-labs/frontier-swarm 0.5.27 → 0.5.29
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/README.md +2 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +397 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -403,6 +403,7 @@ export function createSwarmPlan(manifestInput, taskInput, options = {}) {
|
|
|
403
403
|
id,
|
|
404
404
|
runId: options.runId ?? id.replace(/^swarm-plan:/, 'swarm-run:'),
|
|
405
405
|
manifestId: compiled.manifest.id,
|
|
406
|
+
manifest: compiled.manifest,
|
|
406
407
|
createdAt: options.now ?? Date.now(),
|
|
407
408
|
filters: {
|
|
408
409
|
lanes: options.lanes ? [...options.lanes] : undefined,
|
|
@@ -8936,6 +8937,251 @@ function normalizeRouterPolicy(input) {
|
|
|
8936
8937
|
}
|
|
8937
8938
|
return createSwarmModelRoutingPolicy(input);
|
|
8938
8939
|
}
|
|
8940
|
+
function normalizeSwarmTelemetryRecords(records) {
|
|
8941
|
+
return records.map(normalizeSwarmTelemetryRecord).filter((record) => !!(record.jobId || record.computeId || record.model));
|
|
8942
|
+
}
|
|
8943
|
+
function normalizeSwarmTelemetryRecord(input) {
|
|
8944
|
+
const metadata = toJsonObject(input.metadata);
|
|
8945
|
+
const computeId = normalizeOptionalString(input.computeId) ?? normalizeOptionalString(input.compute);
|
|
8946
|
+
const model = normalizeOptionalString(input.model);
|
|
8947
|
+
const workKind = normalizeOptionalString(input.workKind);
|
|
8948
|
+
const taskKind = normalizeOptionalString(input.taskKind);
|
|
8949
|
+
const lane = normalizeOptionalString(input.lane);
|
|
8950
|
+
return {
|
|
8951
|
+
...(normalizeOptionalString(input.id) ? { id: normalizeOptionalString(input.id) } : {}),
|
|
8952
|
+
...(readNonNegativeNumber(input.generatedAt) !== undefined ? { generatedAt: readNonNegativeNumber(input.generatedAt) } : {}),
|
|
8953
|
+
...(normalizeOptionalString(input.runId) ? { runId: normalizeOptionalString(input.runId) } : {}),
|
|
8954
|
+
...(normalizeOptionalString(input.planId) ? { planId: normalizeOptionalString(input.planId) } : {}),
|
|
8955
|
+
...(normalizeOptionalString(input.jobId) ? { jobId: normalizeOptionalString(input.jobId) } : {}),
|
|
8956
|
+
...(normalizeOptionalString(input.taskId) ? { taskId: normalizeOptionalString(input.taskId) } : {}),
|
|
8957
|
+
...(lane ? { lane } : {}),
|
|
8958
|
+
...(normalizeOptionalString(input.layer) ? { layer: normalizeOptionalString(input.layer) } : {}),
|
|
8959
|
+
...(workKind ? { workKind } : {}),
|
|
8960
|
+
...(taskKind ? { taskKind } : {}),
|
|
8961
|
+
...(computeId ? { computeId } : {}),
|
|
8962
|
+
...(normalizeOptionalString(input.computeKind) ? { computeKind: normalizeOptionalString(input.computeKind) } : {}),
|
|
8963
|
+
...(model ? { model } : {}),
|
|
8964
|
+
...(normalizeOptionalString(input.modelTier) ? { modelTier: normalizeOptionalString(input.modelTier) } : {}),
|
|
8965
|
+
...(normalizeOptionalString(input.reasoningEffort) ? { reasoningEffort: normalizeOptionalString(input.reasoningEffort) } : {}),
|
|
8966
|
+
...(normalizeOptionalString(input.serviceTier) ? { serviceTier: normalizeOptionalString(input.serviceTier) } : {}),
|
|
8967
|
+
...(normalizeOptionalString(input.status) ? { status: normalizeOptionalString(input.status) } : {}),
|
|
8968
|
+
...(normalizeOptionalString(input.mergeReadiness) ? { mergeReadiness: normalizeOptionalString(input.mergeReadiness) } : {}),
|
|
8969
|
+
...(normalizeOptionalString(input.mergeDisposition) ? { mergeDisposition: normalizeOptionalString(input.mergeDisposition) } : {}),
|
|
8970
|
+
durationMs: readNonNegativeNumber(input.durationMs) ?? 0,
|
|
8971
|
+
estimatedCostUsd: readNonNegativeNumber(input.estimatedCostUsd) ?? 0,
|
|
8972
|
+
estimatedInputCostUsd: readNonNegativeNumber(input.estimatedInputCostUsd) ?? 0,
|
|
8973
|
+
estimatedOutputCostUsd: readNonNegativeNumber(input.estimatedOutputCostUsd) ?? 0,
|
|
8974
|
+
billableInputTokens: readNonNegativeNumber(input.billableInputTokens) ?? 0,
|
|
8975
|
+
outputTokens: readNonNegativeNumber(input.outputTokens) ?? 0,
|
|
8976
|
+
priceKnown: input.priceKnown === true,
|
|
8977
|
+
costEstimateInputOnly: input.costEstimateInputOnly === true,
|
|
8978
|
+
costEstimateMissingOutputTokens: input.costEstimateMissingOutputTokens === true,
|
|
8979
|
+
verificationTotal: readNonNegativeNumber(input.verificationTotal) ?? 0,
|
|
8980
|
+
verificationPassed: readNonNegativeNumber(input.verificationPassed) ?? 0,
|
|
8981
|
+
verificationFailed: readNonNegativeNumber(input.verificationFailed) ?? 0,
|
|
8982
|
+
verificationRequiredFailed: readNonNegativeNumber(input.verificationRequiredFailed) ?? 0,
|
|
8983
|
+
ownershipViolationCount: readNonNegativeNumber(input.ownershipViolationCount) ?? 0,
|
|
8984
|
+
changedPathCount: readNonNegativeNumber(input.changedPathCount) ?? 0,
|
|
8985
|
+
evidencePathCount: readNonNegativeNumber(input.evidencePathCount) ?? 0,
|
|
8986
|
+
humanActionCount: readNonNegativeNumber(input.humanActionCount) ?? 0,
|
|
8987
|
+
openHumanActionCount: readNonNegativeNumber(input.openHumanActionCount) ?? 0,
|
|
8988
|
+
semanticImportPresent: input.semanticImportPresent === true,
|
|
8989
|
+
...(metadata ? { metadata } : {})
|
|
8990
|
+
};
|
|
8991
|
+
}
|
|
8992
|
+
function telemetryRecordToRoutingFeedback(record, generatedAt) {
|
|
8993
|
+
const evidenceScore = telemetryEvidenceScore(record);
|
|
8994
|
+
return createSwarmModelRoutingFeedback({
|
|
8995
|
+
scope: record.taskId ? 'task' : record.lane ? 'lane' : 'global',
|
|
8996
|
+
runId: record.runId,
|
|
8997
|
+
planId: record.planId,
|
|
8998
|
+
jobId: record.jobId,
|
|
8999
|
+
taskId: record.taskId,
|
|
9000
|
+
lane: record.lane,
|
|
9001
|
+
layer: record.layer,
|
|
9002
|
+
workKind: record.workKind,
|
|
9003
|
+
taskKind: record.taskKind ?? record.workKind,
|
|
9004
|
+
computeId: record.computeId,
|
|
9005
|
+
computeKind: record.computeKind,
|
|
9006
|
+
model: record.model,
|
|
9007
|
+
modelTier: record.modelTier,
|
|
9008
|
+
reasoningEffort: record.reasoningEffort,
|
|
9009
|
+
serviceTier: record.serviceTier,
|
|
9010
|
+
resultStatus: record.status,
|
|
9011
|
+
mergeReadiness: record.mergeReadiness,
|
|
9012
|
+
mergeDisposition: record.mergeDisposition,
|
|
9013
|
+
evidenceQuality: {
|
|
9014
|
+
band: evidenceScore >= 0.85 ? 'strong' : evidenceScore >= 0.6 ? 'adequate' : evidenceScore > 0 ? 'weak' : 'missing',
|
|
9015
|
+
score: evidenceScore,
|
|
9016
|
+
confidence: telemetryConfidence(record)
|
|
9017
|
+
},
|
|
9018
|
+
selected: true,
|
|
9019
|
+
tags: uniqueStrings([
|
|
9020
|
+
record.status,
|
|
9021
|
+
record.mergeReadiness,
|
|
9022
|
+
record.mergeDisposition,
|
|
9023
|
+
record.priceKnown === false ? 'unknown-price' : undefined,
|
|
9024
|
+
(record.verificationRequiredFailed ?? 0) > 0 ? 'required-verification-failed' : undefined,
|
|
9025
|
+
(record.openHumanActionCount ?? 0) > 0 ? 'open-human-action' : undefined
|
|
9026
|
+
]),
|
|
9027
|
+
generatedAt: record.generatedAt ?? generatedAt,
|
|
9028
|
+
metadata: {
|
|
9029
|
+
telemetryRecordId: record.id,
|
|
9030
|
+
durationMs: record.durationMs,
|
|
9031
|
+
estimatedCostUsd: record.estimatedCostUsd,
|
|
9032
|
+
estimatedInputCostUsd: record.estimatedInputCostUsd,
|
|
9033
|
+
estimatedOutputCostUsd: record.estimatedOutputCostUsd,
|
|
9034
|
+
billableInputTokens: record.billableInputTokens,
|
|
9035
|
+
outputTokens: record.outputTokens,
|
|
9036
|
+
priceKnown: record.priceKnown,
|
|
9037
|
+
costEstimateInputOnly: record.costEstimateInputOnly,
|
|
9038
|
+
costEstimateMissingOutputTokens: record.costEstimateMissingOutputTokens,
|
|
9039
|
+
verificationTotal: record.verificationTotal,
|
|
9040
|
+
verificationPassed: record.verificationPassed,
|
|
9041
|
+
verificationFailed: record.verificationFailed,
|
|
9042
|
+
verificationRequiredFailed: record.verificationRequiredFailed,
|
|
9043
|
+
humanActionCount: record.humanActionCount,
|
|
9044
|
+
openHumanActionCount: record.openHumanActionCount,
|
|
9045
|
+
semanticImportPresent: record.semanticImportPresent
|
|
9046
|
+
}
|
|
9047
|
+
});
|
|
9048
|
+
}
|
|
9049
|
+
function createTelemetryRoutingSignals(records, input, generatedAt) {
|
|
9050
|
+
const minSamples = Math.max(1, Math.floor(input.minSamples ?? 1));
|
|
9051
|
+
const preferSuccessRate = clamp01(input.preferSuccessRate ?? 0.82);
|
|
9052
|
+
const avoidSuccessRate = clamp01(input.avoidSuccessRate ?? 0.45);
|
|
9053
|
+
const highCostUsd = readNonNegativeNumber(input.highCostUsd);
|
|
9054
|
+
const byCompute = new Map();
|
|
9055
|
+
for (const record of records) {
|
|
9056
|
+
const key = record.computeId ?? record.model;
|
|
9057
|
+
if (!key)
|
|
9058
|
+
continue;
|
|
9059
|
+
byCompute.set(key, [...(byCompute.get(key) ?? []), record]);
|
|
9060
|
+
}
|
|
9061
|
+
const signals = [];
|
|
9062
|
+
for (const [computeId, group] of byCompute) {
|
|
9063
|
+
if (group.length < minSamples)
|
|
9064
|
+
continue;
|
|
9065
|
+
const scores = group.map(telemetrySuccessScore);
|
|
9066
|
+
const successRate = scores.reduce((sum, score) => sum + score, 0) / Math.max(1, scores.length);
|
|
9067
|
+
const requiredFailed = group.reduce((sum, record) => sum + (record.verificationRequiredFailed ?? 0), 0);
|
|
9068
|
+
const openHumanActions = group.reduce((sum, record) => sum + (record.openHumanActionCount ?? 0), 0);
|
|
9069
|
+
const knownCosts = group.map((record) => record.estimatedCostUsd).filter((value) => value !== undefined && value > 0);
|
|
9070
|
+
const averageCostUsd = knownCosts.length ? knownCosts.reduce((sum, value) => sum + value, 0) / knownCosts.length : undefined;
|
|
9071
|
+
const confidence = group.length >= 5 ? 'high' : group.length >= 2 ? 'medium' : 'low';
|
|
9072
|
+
const common = {
|
|
9073
|
+
computeId,
|
|
9074
|
+
confidence: confidence,
|
|
9075
|
+
metadata: {
|
|
9076
|
+
source: 'frontier-swarm-routing-controller',
|
|
9077
|
+
sampleCount: group.length,
|
|
9078
|
+
successRate: roundScore(successRate),
|
|
9079
|
+
requiredFailed,
|
|
9080
|
+
openHumanActions,
|
|
9081
|
+
generatedAt,
|
|
9082
|
+
...(averageCostUsd !== undefined ? { averageCostUsd } : {})
|
|
9083
|
+
}
|
|
9084
|
+
};
|
|
9085
|
+
if (successRate <= avoidSuccessRate || requiredFailed > 0 || openHumanActions > 0) {
|
|
9086
|
+
signals.push({
|
|
9087
|
+
...common,
|
|
9088
|
+
mode: 'avoid',
|
|
9089
|
+
reason: requiredFailed > 0
|
|
9090
|
+
? 'telemetry-required-verification-failed'
|
|
9091
|
+
: openHumanActions > 0
|
|
9092
|
+
? 'telemetry-human-action-open'
|
|
9093
|
+
: 'telemetry-low-success-rate'
|
|
9094
|
+
});
|
|
9095
|
+
}
|
|
9096
|
+
else if (successRate >= preferSuccessRate && (highCostUsd === undefined || averageCostUsd === undefined || averageCostUsd <= highCostUsd)) {
|
|
9097
|
+
signals.push({
|
|
9098
|
+
...common,
|
|
9099
|
+
mode: 'prefer',
|
|
9100
|
+
reason: 'telemetry-high-success-rate'
|
|
9101
|
+
});
|
|
9102
|
+
}
|
|
9103
|
+
else {
|
|
9104
|
+
signals.push({
|
|
9105
|
+
...common,
|
|
9106
|
+
mode: 'observe',
|
|
9107
|
+
reason: averageCostUsd !== undefined && highCostUsd !== undefined && averageCostUsd > highCostUsd
|
|
9108
|
+
? 'telemetry-high-cost-observed'
|
|
9109
|
+
: 'telemetry-insufficient-routing-pressure'
|
|
9110
|
+
});
|
|
9111
|
+
}
|
|
9112
|
+
}
|
|
9113
|
+
return signals;
|
|
9114
|
+
}
|
|
9115
|
+
function dedupeModelRoutingPolicySignals(signals) {
|
|
9116
|
+
const byKey = new Map();
|
|
9117
|
+
for (const signal of signals) {
|
|
9118
|
+
const key = [
|
|
9119
|
+
signal.mode ?? 'observe',
|
|
9120
|
+
signal.lane ?? '',
|
|
9121
|
+
signal.layer ?? '',
|
|
9122
|
+
signal.workKind ?? signal.taskKind ?? '',
|
|
9123
|
+
signal.computeId ?? '',
|
|
9124
|
+
signal.model ?? '',
|
|
9125
|
+
signal.modelTier ?? ''
|
|
9126
|
+
].join('\u0000');
|
|
9127
|
+
byKey.set(key, { ...signal });
|
|
9128
|
+
}
|
|
9129
|
+
return Array.from(byKey.values());
|
|
9130
|
+
}
|
|
9131
|
+
function routingSignalToControllerDecision(signal, generatedAt) {
|
|
9132
|
+
const action = signal.mode === 'prefer' || signal.mode === 'avoid' ? signal.mode : 'observe';
|
|
9133
|
+
const metadata = toJsonObject(signal.metadata);
|
|
9134
|
+
return {
|
|
9135
|
+
id: `routing-controller-decision:${action}:${stableHash([signal, generatedAt])}`,
|
|
9136
|
+
action,
|
|
9137
|
+
...(signal.computeId ? { computeId: signal.computeId } : {}),
|
|
9138
|
+
...(signal.model ? { model: signal.model } : {}),
|
|
9139
|
+
...(signal.lane ? { lane: signal.lane } : {}),
|
|
9140
|
+
...(signal.workKind ? { workKind: signal.workKind } : {}),
|
|
9141
|
+
...(signal.taskKind ? { taskKind: signal.taskKind } : {}),
|
|
9142
|
+
confidence: signal.confidence ?? 'low',
|
|
9143
|
+
reason: signal.reason ?? `routing ${action} signal`,
|
|
9144
|
+
...(readNonNegativeNumber(metadata?.successRate) !== undefined ? { score: readNonNegativeNumber(metadata?.successRate) } : {}),
|
|
9145
|
+
...(metadata ? { metadata } : {})
|
|
9146
|
+
};
|
|
9147
|
+
}
|
|
9148
|
+
function telemetrySuccessScore(record) {
|
|
9149
|
+
if ((record.verificationRequiredFailed ?? 0) > 0)
|
|
9150
|
+
return 0;
|
|
9151
|
+
if ((record.openHumanActionCount ?? 0) > 0)
|
|
9152
|
+
return 0.2;
|
|
9153
|
+
const status = slug(record.status ?? '');
|
|
9154
|
+
const disposition = slug(record.mergeDisposition ?? '');
|
|
9155
|
+
const readiness = slug(record.mergeReadiness ?? '');
|
|
9156
|
+
if (['completed', 'verified', 'passed', 'success'].includes(status))
|
|
9157
|
+
return 1;
|
|
9158
|
+
if (['applied', 'committed', 'checked', 'auto-mergeable'].includes(disposition) || readiness === 'patch-candidate')
|
|
9159
|
+
return 0.9;
|
|
9160
|
+
if (['blocked', 'failed', 'failure', 'rejected', 'conflict-blocked'].includes(status) || ['rejected', 'blocked', 'stale-against-head'].includes(disposition))
|
|
9161
|
+
return 0;
|
|
9162
|
+
if (readiness === 'discovery-only' || disposition === 'discovery-only')
|
|
9163
|
+
return 0.55;
|
|
9164
|
+
if ((record.verificationTotal ?? 0) > 0) {
|
|
9165
|
+
const passed = record.verificationPassed ?? 0;
|
|
9166
|
+
return clamp01(passed / Math.max(1, record.verificationTotal ?? 1));
|
|
9167
|
+
}
|
|
9168
|
+
return 0.5;
|
|
9169
|
+
}
|
|
9170
|
+
function telemetryEvidenceScore(record) {
|
|
9171
|
+
const verificationTotal = record.verificationTotal ?? 0;
|
|
9172
|
+
const verificationPassed = record.verificationPassed ?? 0;
|
|
9173
|
+
const verificationScore = verificationTotal > 0 ? verificationPassed / Math.max(1, verificationTotal) : undefined;
|
|
9174
|
+
const evidenceScore = (record.evidencePathCount ?? 0) > 0 ? 0.2 : 0;
|
|
9175
|
+
const semanticScore = record.semanticImportPresent ? 0.1 : 0;
|
|
9176
|
+
return clamp01(Math.max(telemetrySuccessScore(record), verificationScore ?? 0) + evidenceScore + semanticScore);
|
|
9177
|
+
}
|
|
9178
|
+
function telemetryConfidence(record) {
|
|
9179
|
+
if ((record.verificationTotal ?? 0) > 0 && (record.evidencePathCount ?? 0) > 0)
|
|
9180
|
+
return 'high';
|
|
9181
|
+
if ((record.verificationTotal ?? 0) > 0 || (record.evidencePathCount ?? 0) > 0 || record.semanticImportPresent)
|
|
9182
|
+
return 'medium';
|
|
9183
|
+
return 'low';
|
|
9184
|
+
}
|
|
8939
9185
|
function routingFeedbackForTask(feedback, task) {
|
|
8940
9186
|
return feedback.filter((entry) => routingFeedbackMatchesTask(entry, task));
|
|
8941
9187
|
}
|
|
@@ -10322,6 +10568,157 @@ export function createSwarmModelRoutingPolicy(input = {}) {
|
|
|
10322
10568
|
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
10323
10569
|
};
|
|
10324
10570
|
}
|
|
10571
|
+
export function createSwarmRoutingController(input = {}) {
|
|
10572
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
10573
|
+
const records = normalizeSwarmTelemetryRecords([
|
|
10574
|
+
...(input.records ?? []),
|
|
10575
|
+
...(input.telemetry?.records ?? [])
|
|
10576
|
+
]);
|
|
10577
|
+
const summary = input.summary ?? input.telemetry?.summary;
|
|
10578
|
+
const basePolicy = normalizeRouterPolicy(input.basePolicy);
|
|
10579
|
+
const feedback = records.map((record) => telemetryRecordToRoutingFeedback(record, generatedAt));
|
|
10580
|
+
const telemetrySignals = createTelemetryRoutingSignals(records, input, generatedAt);
|
|
10581
|
+
const baseSignals = basePolicy?.signals ?? [];
|
|
10582
|
+
const policy = createSwarmModelRoutingPolicy({
|
|
10583
|
+
id: input.id ? `${input.id}:policy` : undefined,
|
|
10584
|
+
...(basePolicy ?? {}),
|
|
10585
|
+
defaultMode: input.routingMode ?? input.defaultMode ?? basePolicy?.defaultMode ?? 'fill',
|
|
10586
|
+
signals: dedupeModelRoutingPolicySignals([...baseSignals, ...telemetrySignals]),
|
|
10587
|
+
feedback: [...(basePolicy?.feedback ?? []), ...feedback],
|
|
10588
|
+
generatedAt,
|
|
10589
|
+
metadata: {
|
|
10590
|
+
...(basePolicy?.metadata ?? {}),
|
|
10591
|
+
source: 'frontier-swarm-routing-controller',
|
|
10592
|
+
telemetryRecordCount: records.length,
|
|
10593
|
+
...(summary ? { telemetrySummary: cloneJsonValue(summary) } : {}),
|
|
10594
|
+
...(toJsonObject(input.metadata) ? { input: toJsonObject(input.metadata) } : {})
|
|
10595
|
+
}
|
|
10596
|
+
});
|
|
10597
|
+
const routedPlan = input.plan
|
|
10598
|
+
? rerouteSwarmPlan({
|
|
10599
|
+
plan: input.plan,
|
|
10600
|
+
routingPolicy: policy,
|
|
10601
|
+
routingMode: input.routingMode ?? policy.defaultMode,
|
|
10602
|
+
protectedJobIds: input.protectedJobIds,
|
|
10603
|
+
runningJobIds: input.runningJobIds,
|
|
10604
|
+
completedJobIds: input.completedJobIds,
|
|
10605
|
+
generatedAt,
|
|
10606
|
+
metadata: { source: 'frontier-swarm-routing-controller' }
|
|
10607
|
+
})
|
|
10608
|
+
: undefined;
|
|
10609
|
+
const changedComputeCount = routedPlan && input.plan
|
|
10610
|
+
? input.plan.jobs.filter((job) => routedPlan.jobs.find((entry) => entry.id === job.id)?.compute.id !== job.compute.id).length
|
|
10611
|
+
: 0;
|
|
10612
|
+
const protectedJobCount = uniqueStrings([
|
|
10613
|
+
...(input.protectedJobIds ?? []),
|
|
10614
|
+
...(input.runningJobIds ?? []),
|
|
10615
|
+
...(input.completedJobIds ?? [])
|
|
10616
|
+
]).length;
|
|
10617
|
+
const decisions = [
|
|
10618
|
+
...telemetrySignals.map((signal) => routingSignalToControllerDecision(signal, generatedAt)),
|
|
10619
|
+
...(changedComputeCount > 0 ? [{
|
|
10620
|
+
id: `routing-controller-decision:reroute:${stableHash([input.plan?.id, changedComputeCount, generatedAt])}`,
|
|
10621
|
+
action: 'reroute',
|
|
10622
|
+
confidence: 'medium',
|
|
10623
|
+
reason: `rerouted ${changedComputeCount} pending jobs from telemetry policy`,
|
|
10624
|
+
metadata: { changedComputeCount }
|
|
10625
|
+
}] : [])
|
|
10626
|
+
];
|
|
10627
|
+
const signalCounts = countBy(telemetrySignals.map((signal) => signal.mode ?? 'observe'));
|
|
10628
|
+
return {
|
|
10629
|
+
kind: 'frontier.swarm.routing-controller',
|
|
10630
|
+
version: 1,
|
|
10631
|
+
id: input.id ?? 'swarm-routing-controller:' + stableHash([input.plan?.id, records.map((record) => record.id ?? record.jobId), generatedAt]),
|
|
10632
|
+
generatedAt,
|
|
10633
|
+
routingMode: input.routingMode ?? policy.defaultMode,
|
|
10634
|
+
policy,
|
|
10635
|
+
decisions,
|
|
10636
|
+
...(routedPlan ? { routedPlan } : {}),
|
|
10637
|
+
summary: {
|
|
10638
|
+
telemetryRecordCount: records.length,
|
|
10639
|
+
telemetryJobCount: new Set(records.map((record) => record.jobId).filter(Boolean)).size,
|
|
10640
|
+
feedbackCount: feedback.length,
|
|
10641
|
+
signalCount: telemetrySignals.length,
|
|
10642
|
+
preferCount: signalCounts.prefer ?? 0,
|
|
10643
|
+
avoidCount: signalCounts.avoid ?? 0,
|
|
10644
|
+
observeCount: signalCounts.observe ?? 0,
|
|
10645
|
+
decisionCount: decisions.length,
|
|
10646
|
+
reroutedJobCount: changedComputeCount,
|
|
10647
|
+
changedComputeCount,
|
|
10648
|
+
protectedJobCount,
|
|
10649
|
+
missingPlanManifest: !!input.plan && !input.plan.manifest,
|
|
10650
|
+
...(summary?.recordCount !== undefined ? { summaryRecordCount: summary.recordCount } : {}),
|
|
10651
|
+
...(summary?.estimatedCostUsd !== undefined ? { summaryEstimatedCostUsd: summary.estimatedCostUsd } : {})
|
|
10652
|
+
},
|
|
10653
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
10654
|
+
};
|
|
10655
|
+
}
|
|
10656
|
+
export function rerouteSwarmPlan(input) {
|
|
10657
|
+
const plan = input.plan;
|
|
10658
|
+
const protectedJobIds = new Set(uniqueStrings([
|
|
10659
|
+
...(input.protectedJobIds ?? []),
|
|
10660
|
+
...(input.runningJobIds ?? []),
|
|
10661
|
+
...(input.completedJobIds ?? [])
|
|
10662
|
+
]));
|
|
10663
|
+
const routingPolicy = normalizeRouterPolicy(input.routingPolicy);
|
|
10664
|
+
if (!plan.manifest || !routingPolicy) {
|
|
10665
|
+
return {
|
|
10666
|
+
...plan,
|
|
10667
|
+
metadata: mergeSwarmMetadata([plan.metadata, {
|
|
10668
|
+
routingController: {
|
|
10669
|
+
rerouteSkipped: !plan.manifest ? 'missing-plan-manifest' : 'missing-routing-policy',
|
|
10670
|
+
protectedJobCount: protectedJobIds.size,
|
|
10671
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
10672
|
+
}
|
|
10673
|
+
}])
|
|
10674
|
+
};
|
|
10675
|
+
}
|
|
10676
|
+
const routed = createSwarmPlan(plan.manifest, plan.jobs.map((job) => job.task), {
|
|
10677
|
+
...plan.filters,
|
|
10678
|
+
id: plan.id,
|
|
10679
|
+
runId: plan.runId,
|
|
10680
|
+
now: input.generatedAt ?? plan.createdAt,
|
|
10681
|
+
maxReadyJobs: plan.limits.maxReadyJobs,
|
|
10682
|
+
maxLaneConcurrency: plan.limits.maxLaneConcurrency,
|
|
10683
|
+
maxConcurrencyKeyConcurrency: plan.limits.maxConcurrencyKeyConcurrency,
|
|
10684
|
+
maxComputeConcurrency: plan.limits.maxComputeConcurrency,
|
|
10685
|
+
resourceQuotas: plan.limits.resourceQuotas,
|
|
10686
|
+
routingPolicy,
|
|
10687
|
+
routingMode: input.routingMode ?? routingPolicy.defaultMode,
|
|
10688
|
+
routingContext: plan.routingContext,
|
|
10689
|
+
metadata: mergeSwarmMetadata([plan.metadata, input.metadata])
|
|
10690
|
+
});
|
|
10691
|
+
const routedById = new Map(routed.jobs.map((job) => [job.id, job]));
|
|
10692
|
+
let changedComputeCount = 0;
|
|
10693
|
+
const jobs = plan.jobs.map((job) => {
|
|
10694
|
+
if (protectedJobIds.has(job.id))
|
|
10695
|
+
return job;
|
|
10696
|
+
const next = routedById.get(job.id);
|
|
10697
|
+
if (!next)
|
|
10698
|
+
return job;
|
|
10699
|
+
if (next.compute.id !== job.compute.id)
|
|
10700
|
+
changedComputeCount += 1;
|
|
10701
|
+
return { ...next, status: job.status };
|
|
10702
|
+
});
|
|
10703
|
+
return {
|
|
10704
|
+
...routed,
|
|
10705
|
+
id: plan.id,
|
|
10706
|
+
runId: plan.runId,
|
|
10707
|
+
createdAt: plan.createdAt,
|
|
10708
|
+
jobs,
|
|
10709
|
+
summary: summarizeJobs(jobs),
|
|
10710
|
+
metadata: mergeSwarmMetadata([routed.metadata, {
|
|
10711
|
+
routingController: {
|
|
10712
|
+
mode: input.routingMode ?? routingPolicy.defaultMode,
|
|
10713
|
+
policyId: routingPolicy.id,
|
|
10714
|
+
protectedJobCount: protectedJobIds.size,
|
|
10715
|
+
changedComputeCount,
|
|
10716
|
+
generatedAt: input.generatedAt ?? Date.now(),
|
|
10717
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
10718
|
+
}
|
|
10719
|
+
}])
|
|
10720
|
+
};
|
|
10721
|
+
}
|
|
10325
10722
|
export function createSwarmModelRoutingFeedbackFromBoard(input = {}) {
|
|
10326
10723
|
return createSwarmModelRoutingFeedback({ scope: 'lane', resultStatus: 'board-observed', generatedAt: input.generatedAt, metadata: input.metadata });
|
|
10327
10724
|
}
|