@klhapp/skillmux 1.6.0 → 1.7.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/src/cli.ts CHANGED
@@ -4,7 +4,6 @@ import { Database } from "bun:sqlite";
4
4
  import { existsSync, lstatSync, mkdirSync, rmSync } from "node:fs";
5
5
  import { hostname } from "node:os";
6
6
  import { join } from "node:path";
7
- import { generateDataset } from "./dataset-generator";
8
7
 
9
8
  import { createClients } from "./clients";
10
9
  import {
@@ -116,7 +115,6 @@ import { runTarget } from "./commands/target";
116
115
  const KNOWN_COMMANDS = [
117
116
  "context",
118
117
  "config",
119
- "calibrate",
120
118
  "completions",
121
119
  "serve",
122
120
  "index",
@@ -147,7 +145,6 @@ function isDockerHostManagementCommand(command: string, subCommand: string): boo
147
145
  "local-vault",
148
146
  "models",
149
147
  "context",
150
- "calibrate",
151
148
  "eval",
152
149
  ].includes(command)
153
150
  ) {
@@ -210,7 +207,7 @@ async function main() {
210
207
  return;
211
208
  }
212
209
 
213
- // Parse global flags for context/config/calibrate
210
+ // Parse global flags for context/config
214
211
  for (let i = 0; i < rawArgv.length; i++) {
215
212
  const arg = rawArgv[i];
216
213
  if (arg === "--json") isJson = true;
@@ -235,10 +232,10 @@ async function main() {
235
232
  return;
236
233
  }
237
234
 
238
- // Only resolve target if command is target-aware or context/config/calibrate
235
+ // Only resolve target if command is target-aware or context/config
239
236
  const isLocalConfigInit = command === "config" && rawArgv[1] === "init";
240
237
  if (
241
- (["context", "config", "calibrate"].includes(command) &&
238
+ (["context", "config"].includes(command) &&
242
239
  !isLocalConfigInit) ||
243
240
  flagContext ||
244
241
  flagServer
@@ -272,11 +269,9 @@ async function main() {
272
269
  });
273
270
  break;
274
271
  case "calibrate":
275
- await handleCalibrateCommand(adapter, subCommand, rawArgv.slice(1), {
276
- target: resolvedTarget,
277
- isJson,
278
- });
279
- break;
272
+ throw new Error(
273
+ 'skillmux calibrate was removed in 2.0. Threshold calibration was removed; use "skillmux eval" for ranking evaluation.',
274
+ );
280
275
  case "completions":
281
276
  await handleCompletionsCommand(subCommand);
282
277
  break;
@@ -364,7 +359,7 @@ async function main() {
364
359
  const suggestion = suggestCorrection(command, KNOWN_COMMANDS);
365
360
  const msg = suggestion
366
361
  ? `Unknown command "${command}". Did you mean "${suggestion}"?`
367
- : `usage: skillmux <serve|index|sync|init|project|target|core pin/unpin|report|scan|install|eval|doctor|skill which|local-vault init|config show|models download|calibrate generate-dataset>`;
362
+ : `usage: skillmux <serve|index|sync|init|project|target|core pin/unpin|report|scan|install|eval|doctor|skill which|local-vault init|config show|models download>`;
368
363
  throw new Error(msg);
369
364
  }
370
365
  }
@@ -463,188 +458,6 @@ async function handleContextCommand(
463
458
  throw new Error("usage: skillmux context <add|list|current|use|remove>");
464
459
  }
465
460
 
466
- async function handleCalibrateCommand(
467
- adapter: TargetAdapter,
468
- sub: string,
469
- args: string[],
470
- ctx: { target: ResolvedTarget; isJson: boolean },
471
- ) {
472
- if (sub === "run") {
473
- let datasetPath: string | undefined;
474
- let minAutoMatchPrecision: number | undefined;
475
- let minRetrievalRecallAtK: number | undefined;
476
- let minDeliveredShortlistRecallAtK: number | undefined;
477
- let minAutoMatchCount: number | undefined;
478
- let tuneAutoMatchPrecisionBuffer: number | undefined;
479
- let tuneAutoMatchCountBuffer: number | undefined;
480
- let tuneDeliveredShortlistRecallBuffer: number | undefined;
481
- let concurrency: number | undefined;
482
- let resumeRunId: string | undefined;
483
- let timing = false;
484
- const readNumber = (flag: string, raw: string | undefined): number => {
485
- if (raw === undefined) throw new Error(`${flag} requires a value`);
486
- const value = Number(raw);
487
- if (!Number.isFinite(value)) throw new Error(`${flag} must be a number`);
488
- return value;
489
- };
490
- for (let i = 1; i < args.length; i++) {
491
- const option = args[i];
492
- if (option === "--dataset") {
493
- datasetPath = args[++i];
494
- if (!datasetPath) throw new Error("--dataset requires a path value");
495
- } else if (option === "--min-auto-match-precision") {
496
- minAutoMatchPrecision = readNumber(option, args[++i]);
497
- } else if (option === "--min-retrieval-recall-at-k") {
498
- minRetrievalRecallAtK = readNumber(option, args[++i]);
499
- } else if (option === "--min-delivered-shortlist-recall-at-k") {
500
- minDeliveredShortlistRecallAtK = readNumber(option, args[++i]);
501
- } else if (option === "--min-auto-match-count") {
502
- minAutoMatchCount = readNumber(option, args[++i]);
503
- if (!Number.isInteger(minAutoMatchCount) || minAutoMatchCount < 1) {
504
- throw new Error("--min-auto-match-count must be a positive integer");
505
- }
506
- } else if (option === "--tune-auto-match-precision-buffer") {
507
- tuneAutoMatchPrecisionBuffer = readNumber(option, args[++i]);
508
- } else if (option === "--tune-auto-match-count-buffer") {
509
- tuneAutoMatchCountBuffer = readNumber(option, args[++i]);
510
- if (!Number.isInteger(tuneAutoMatchCountBuffer) || tuneAutoMatchCountBuffer < 0) {
511
- throw new Error("--tune-auto-match-count-buffer must be a non-negative integer");
512
- }
513
- } else if (option === "--tune-delivered-shortlist-recall-buffer") {
514
- tuneDeliveredShortlistRecallBuffer = readNumber(option, args[++i]);
515
- } else if (option === "--concurrency") {
516
- const raw = args[++i];
517
- if (raw === undefined) throw new Error("--concurrency requires a value");
518
- const val = Number(raw);
519
- if (!Number.isInteger(val) || val < 1) {
520
- throw new Error("--concurrency must be a positive integer");
521
- }
522
- concurrency = val;
523
- } else if (option === "--resume") {
524
- resumeRunId = args[++i];
525
- if (!resumeRunId) throw new Error("--resume requires a run_id value");
526
- } else if (option === "--timing") {
527
- timing = true;
528
- } else if (option === "--json") {
529
- // Global flag accepted in the documented subcommand position
530
- } else {
531
- throw new Error(`unknown calibrate run option: ${option}`);
532
- }
533
- }
534
- for (const [flag, value] of [
535
- ["--min-auto-match-precision", minAutoMatchPrecision],
536
- ["--min-retrieval-recall-at-k", minRetrievalRecallAtK],
537
- ["--min-delivered-shortlist-recall-at-k", minDeliveredShortlistRecallAtK],
538
- ["--tune-auto-match-precision-buffer", tuneAutoMatchPrecisionBuffer],
539
- ["--tune-delivered-shortlist-recall-buffer", tuneDeliveredShortlistRecallBuffer],
540
- ] as const) {
541
- if (value !== undefined && (value < 0 || value > 1)) {
542
- throw new Error(`${flag} must be between 0 and 1`);
543
- }
544
- }
545
- const res = await adapter.calibrateRun({
546
- datasetPath,
547
- minAutoMatchPrecision,
548
- minRetrievalRecallAtK,
549
- minDeliveredShortlistRecallAtK,
550
- minAutoMatchCount,
551
- tuneAutoMatchPrecisionBuffer,
552
- tuneAutoMatchCountBuffer,
553
- tuneDeliveredShortlistRecallBuffer,
554
- concurrency,
555
- resumeRunId,
556
- timing,
557
- onTimingSummary: timing
558
- ? (summary) => {
559
- // Write timing report to stderr only — stdout remains valid JSON under --json.
560
- // Cumulative fields are total worker time across concurrent queries and may
561
- // exceed wall_ms. They do not sum to wall time.
562
- process.stderr.write(
563
- [
564
- "--- calibrate run timing ---",
565
- `cases_total: ${summary.cases_total}`,
566
- `cases_executed: ${summary.cases_executed} (retrieved in this invocation)`,
567
- `cases_reused: ${summary.cases_reused} (loaded from prior interrupted run)`,
568
- `wall_ms: ${summary.wall_ms.toFixed(1)}`,
569
- `vault_sync_ms: ${summary.vault_sync_ms.toFixed(1)}`,
570
- "cumulative worker time (concurrent totals; may exceed wall_ms):",
571
- ` cumulative_embedding_ms: ${summary.cumulative_embedding_ms.toFixed(1)}`,
572
- ` cumulative_lexical_ms: ${summary.cumulative_lexical_ms.toFixed(1)}`,
573
- ` cumulative_vector_ms: ${summary.cumulative_vector_ms.toFixed(1)}`,
574
- ` cumulative_reranker_ms: ${summary.cumulative_reranker_ms.toFixed(1)}`,
575
- ` cumulative_checkpoint_ms: ${summary.cumulative_checkpoint_ms.toFixed(1)}`,
576
- `policy_evaluation_ms: ${summary.policy_evaluation_ms.toFixed(1)}`,
577
- "",
578
- ].join("\n"),
579
- );
580
- }
581
- : undefined,
582
- });
583
- emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
584
- renderCalibrationTarget(ctx.target);
585
- console.log(`Calibration run complete.`);
586
- if (res.result) console.log(JSON.stringify(res.result, null, 2));
587
- });
588
- return;
589
- }
590
-
591
-
592
- if (sub === "list") {
593
- const res = await adapter.calibrateList();
594
- emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
595
- renderCalibrationTarget(ctx.target);
596
- renderTable(
597
- [
598
- { key: "run_id", header: "RUN_ID" },
599
- { key: "created_at", header: "CREATED_AT" },
600
- { key: "status", header: "STATUS" },
601
- ],
602
- res,
603
- );
604
- });
605
- return;
606
- }
607
-
608
- if (sub === "show") {
609
- const runId = args[1];
610
- if (!runId) throw new Error("usage: skillmux calibrate show <run_id>");
611
- const res = await adapter.calibrateShow(runId);
612
- emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
613
- renderCalibrationTarget(ctx.target);
614
- console.log(JSON.stringify(res, null, 2));
615
- });
616
- return;
617
- }
618
-
619
- if (sub === "apply") {
620
- const runId = args[1];
621
- if (!runId) throw new Error("usage: skillmux calibrate apply <run_id>");
622
- const res = await adapter.calibrateApply(runId);
623
- emitSuccess({ isJson: ctx.isJson, target: ctx.target }, res, () => {
624
- renderCalibrationTarget(ctx.target);
625
- console.log(`Applied calibration run "${runId}"`);
626
- });
627
- return;
628
- }
629
-
630
- if (sub === "generate-dataset") {
631
- await runCalibrateGenerateDataset(args.slice(1));
632
- return;
633
- }
634
-
635
- throw new Error(
636
- "usage: skillmux calibrate generate-dataset [--vault <path>] [--out <file>]",
637
- );
638
- }
639
-
640
- function renderCalibrationTarget(target: ResolvedTarget): void {
641
- if (target.type === "local") {
642
- console.log("Target: local");
643
- } else {
644
- console.log(`Target: remote (${target.name} -> ${target.server})`);
645
- }
646
- }
647
-
648
461
  async function handleCompletionsCommand(shell: string) {
649
462
  if (shell !== "bash" && shell !== "zsh" && shell !== "fish") {
650
463
  throw new Error("usage: skillmux completions <bash|zsh|fish>");
@@ -720,17 +533,6 @@ Setup:
720
533
  skillmux core <pin|unpin> <skill_id>... [--yes] [--dry-run] [--json]
721
534
  skillmux skill which <skill_id>
722
535
 
723
- Calibration:
724
- skillmux calibrate run [--dataset <path>] [--concurrency <n>] [--resume <run_id>]
725
- [--min-auto-match-precision <0..1>] [--min-auto-match-count <n>]
726
- [--min-retrieval-recall-at-k <0..1>]
727
- [--min-delivered-shortlist-recall-at-k <0..1>]
728
- [--tune-auto-match-precision-buffer <0..1>]
729
- [--tune-auto-match-count-buffer <n>]
730
- [--tune-delivered-shortlist-recall-buffer <0..1>]
731
- [--timing] [--json]
732
- skillmux calibrate <list|show|apply|generate-dataset>
733
-
734
536
  Init clients:
735
537
  claude-code, codex, gemini-cli, opencode, github-copilot, windsurf,
736
538
  antigravity, goose, hermes, skillmux-mcp
@@ -740,7 +542,7 @@ Init targets:
740
542
 
741
543
  Commands:
742
544
  serve, index, sync, init, project, target, core, report, scan, install, eval, doctor, skill,
743
- local-vault, config, models, calibrate, context, completions`);
545
+ local-vault, config, models, context, completions`);
744
546
  }
745
547
 
746
548
  // ---------------------------------------------------------------------------
@@ -810,13 +612,17 @@ async function runEval(options: { isJson: boolean }): Promise<void> {
810
612
  throw new Error(`eval requires local embeddings: ${String(error)}`);
811
613
  });
812
614
  emitSuccess({ isJson: options.isJson }, report, () => {
813
- console.log(`holdout queries: ${report.queries}`);
814
- console.log(`lexical recall@3: ${report.lexical.recall_at_3.toFixed(3)}`);
815
- console.log(`lexical recall@5: ${report.lexical.recall_at_5.toFixed(3)}`);
816
- console.log(`lexical MRR: ${report.lexical.mrr.toFixed(3)}`);
817
- console.log(`hybrid recall@3: ${report.hybrid.recall_at_3.toFixed(3)}`);
818
- console.log(`hybrid recall@5: ${report.hybrid.recall_at_5.toFixed(3)}`);
819
- console.log(`hybrid MRR: ${report.hybrid.mrr.toFixed(3)}`);
615
+ console.log(`holdout queries: ${report.queries}`);
616
+ console.log(`judged queries: ${report.judged_queries}`);
617
+ console.log(`unjudged queries: ${report.unjudged_queries}`);
618
+ console.log(`lexical recall@5: ${report.lexical.recall_at_5.toFixed(3)}`);
619
+ console.log(`lexical recall@10: ${report.lexical.recall_at_10.toFixed(3)}`);
620
+ console.log(`lexical MRR: ${report.lexical.mrr.toFixed(3)}`);
621
+ console.log(`lexical nDCG@10: ${report.lexical.ndcg_at_10.toFixed(3)}`);
622
+ console.log(`hybrid recall@5: ${report.hybrid.recall_at_5.toFixed(3)}`);
623
+ console.log(`hybrid recall@10: ${report.hybrid.recall_at_10.toFixed(3)}`);
624
+ console.log(`hybrid MRR: ${report.hybrid.mrr.toFixed(3)}`);
625
+ console.log(`hybrid nDCG@10: ${report.hybrid.ndcg_at_10.toFixed(3)}`);
820
626
  });
821
627
  }
822
628
 
@@ -1802,47 +1608,6 @@ async function runInstall(
1802
1608
  }
1803
1609
  }
1804
1610
 
1805
- function parseCalibrateGenerateDatasetArgs(args: string[]): {
1806
- vault?: string;
1807
- out?: string;
1808
- } {
1809
- let vault: string | undefined;
1810
- let out: string | undefined;
1811
- for (let i = 0; i < args.length; i++) {
1812
- const option = args[i];
1813
- if (option === "--vault") {
1814
- const value = args[++i];
1815
- if (!value) throw new Error("--vault requires a path value");
1816
- vault = value;
1817
- } else if (option === "--out") {
1818
- const value = args[++i];
1819
- if (!value) throw new Error("--out requires a file path value");
1820
- out = value;
1821
- } else {
1822
- throw new Error(`unknown calibrate option: ${option}`);
1823
- }
1824
- }
1825
- return { vault, out };
1826
- }
1827
-
1828
- async function runCalibrateGenerateDataset(args: string[]): Promise<void> {
1829
- const { vault: vaultArg, out: outArg } =
1830
- parseCalibrateGenerateDatasetArgs(args);
1831
- const config = await loadConfig();
1832
- const vaultPath = expandHome(vaultArg ?? config.vault_path);
1833
- const outPath = expandHome(outArg ?? join(config.state_dir, "queries.json"));
1834
-
1835
- const skills = await scanVault(vaultPath);
1836
- const dataset = generateDataset(skills);
1837
-
1838
- const parentDir = join(outPath, "..");
1839
- mkdirSync(parentDir, { recursive: true });
1840
- await Bun.write(outPath, JSON.stringify(dataset, null, 2) + "\n");
1841
- console.log(
1842
- `generated synthetic dataset with ${dataset.length} cases at ${outPath}`,
1843
- );
1844
- }
1845
-
1846
1611
  if (import.meta.main) {
1847
1612
  await main();
1848
1613
  }
@@ -3,7 +3,6 @@ export type ShellType = "bash" | "zsh" | "fish";
3
3
  const TOP_LEVEL_COMMANDS: { name: string; description: string }[] = [
4
4
  { name: "context", description: "Manage connection contexts" },
5
5
  { name: "config", description: "Manage configuration" },
6
- { name: "calibrate", description: "Manage policy calibration" },
7
6
  { name: "serve", description: "Start MCP server" },
8
7
  { name: "index", description: "Rebuild local search index" },
9
8
  { name: "sync", description: "Synchronize vault skills" },
@@ -45,9 +44,6 @@ _skillmux_completions() {
45
44
  config)
46
45
  COMPREPLY=( $(compgen -W "init show get validate diff set status" -- "$cur") )
47
46
  ;;
48
- calibrate)
49
- COMPREPLY=( $(compgen -W "run list show apply generate-dataset" -- "$cur") )
50
- ;;
51
47
  completions)
52
48
  COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
53
49
  ;;
@@ -60,11 +60,8 @@ export const RELOADABLE_KEYS = [
60
60
  "recall.k_lexical",
61
61
  "recall.k_vector",
62
62
  "recall.k_rerank",
63
- "output.ambiguous_candidate_limit",
64
- "thresholds.candidate_limit",
65
- "thresholds.match_score",
66
- "thresholds.match_margin",
67
- "thresholds.candidate_floor",
63
+ "output.top_k",
64
+ "output.max_top_k",
68
65
  "inference.embedding.endpoint",
69
66
  "inference.embedding.api_key_env",
70
67
  "inference.reranker.adapter",
@@ -123,7 +120,7 @@ export async function getEffectiveConfig(configPath?: string): Promise<{
123
120
  sources: ConfigSourceMap;
124
121
  rawToml: Record<string, unknown>;
125
122
  }> {
126
- const path = configPath ?? DEFAULT_CONFIG_PATH;
123
+ const path = configPath ?? process.env.SKILLMUX_CONFIG ?? DEFAULT_CONFIG_PATH;
127
124
  const effective = await loadConfig(path);
128
125
  const rawToml: Record<string, unknown> = {};
129
126
 
@@ -150,11 +147,8 @@ export async function getEffectiveConfig(configPath?: string): Promise<{
150
147
  "recall.k_lexical",
151
148
  "recall.k_vector",
152
149
  "recall.k_rerank",
153
- "output.ambiguous_candidate_limit",
154
- "thresholds.candidate_limit",
155
- "thresholds.match_score",
156
- "thresholds.match_margin",
157
- "thresholds.candidate_floor",
150
+ "output.top_k",
151
+ "output.max_top_k",
158
152
  "inference.mode",
159
153
  "inference.bundle",
160
154
  "inference.models_dir",
@@ -200,15 +194,8 @@ export function isEnvMasked(key: string, allowEnvOverrides: boolean = true): boo
200
194
  if (key === "recall.k_lexical" && (process.env.SKILLMUX_RECALL_K_LEXICAL || process.env.RECALL_K_LEXICAL)) return true;
201
195
  if (key === "recall.k_vector" && (process.env.SKILLMUX_RECALL_K_VECTOR || process.env.RECALL_K_VECTOR)) return true;
202
196
  if (key === "recall.k_rerank" && (process.env.SKILLMUX_RECALL_K_RERANK || process.env.RECALL_K_RERANK)) return true;
203
- if (
204
- (key === "output.ambiguous_candidate_limit" || key === "thresholds.candidate_limit") &&
205
- (process.env.SKILLMUX_OUTPUT_AMBIGUOUS_CANDIDATE_LIMIT ||
206
- process.env.AMBIGUOUS_CANDIDATE_LIMIT ||
207
- process.env.SKILLMUX_CANDIDATE_LIMIT ||
208
- process.env.CANDIDATE_LIMIT)
209
- ) {
210
- return true;
211
- }
197
+ if (key === "output.top_k" && (process.env.SKILLMUX_OUTPUT_TOP_K || process.env.OUTPUT_TOP_K)) return true;
198
+ if (key === "output.max_top_k" && (process.env.SKILLMUX_OUTPUT_MAX_TOP_K || process.env.OUTPUT_MAX_TOP_K)) return true;
212
199
  if (key === "inference.models_dir" && (process.env.SKILLMUX_MODELS_DIR || process.env.SKILL_ROUTER_MODELS_DIR)) return true;
213
200
  if (key === "inference.embedding.device" && (process.env.SKILLMUX_EMBED_DEVICE || process.env.EMBED_DEVICE)) return true;
214
201
  if (key === "inference.embedding.dtype" && (process.env.SKILLMUX_EMBED_DTYPE || process.env.EMBED_DTYPE)) return true;
@@ -229,6 +216,12 @@ export function isEnvMasked(key: string, allowEnvOverrides: boolean = true): boo
229
216
  }
230
217
 
231
218
  export function validateDottedKey(key: string): void {
219
+ if (key === "output.ambiguous_candidate_limit") {
220
+ throw new Error("output.ambiguous_candidate_limit is obsolete in 2.0. Use output.top_k instead.");
221
+ }
222
+ if (key.startsWith("thresholds.") || key.startsWith("inference.thresholds.")) {
223
+ throw new Error("thresholds are obsolete in 2.0. Threshold calibration was removed; use output.top_k.");
224
+ }
232
225
  const allowed = new Set([
233
226
  "config.environment_overrides",
234
227
  "vault_path",
@@ -236,11 +229,8 @@ export function validateDottedKey(key: string): void {
236
229
  "recall.k_lexical",
237
230
  "recall.k_vector",
238
231
  "recall.k_rerank",
239
- "output.ambiguous_candidate_limit",
240
- "thresholds.candidate_limit",
241
- "thresholds.match_score",
242
- "thresholds.match_margin",
243
- "thresholds.candidate_floor",
232
+ "output.top_k",
233
+ "output.max_top_k",
244
234
  "inference.mode",
245
235
  "inference.bundle",
246
236
  "inference.models_dir",
@@ -279,11 +269,8 @@ export function parseDottedValue(key: string, valueStr: string): unknown {
279
269
  "recall.k_lexical",
280
270
  "recall.k_vector",
281
271
  "recall.k_rerank",
282
- "output.ambiguous_candidate_limit",
283
- "thresholds.candidate_limit",
284
- "thresholds.match_score",
285
- "thresholds.match_margin",
286
- "thresholds.candidate_floor",
272
+ "output.top_k",
273
+ "output.max_top_k",
287
274
  "inference.embedding.dimension",
288
275
  "inference.timeout_ms",
289
276
  "server.rate_limit.requests_per_minute",
@@ -328,7 +315,7 @@ export async function setDottedKey(
328
315
  throw new Error(`Cannot set environment-masked configuration key "${key}"`);
329
316
  }
330
317
 
331
- const path = opts?.configPath ?? DEFAULT_CONFIG_PATH;
318
+ const path = opts?.configPath ?? process.env.SKILLMUX_CONFIG ?? DEFAULT_CONFIG_PATH;
332
319
  const targetName = opts?.targetName ?? "local";
333
320
 
334
321
  const { effective: priorEffective, rawToml } = await getEffectiveConfig(path);
@@ -445,7 +432,7 @@ export async function getLocalConfigStatus(configPath?: string): Promise<ConfigS
445
432
 
446
433
  return {
447
434
  target: "local",
448
- desired_source: configPath ?? DEFAULT_CONFIG_PATH,
435
+ desired_source: configPath ?? process.env.SKILLMUX_CONFIG ?? DEFAULT_CONFIG_PATH,
449
436
  desired_source_hash: hash,
450
437
  active_revision: hash,
451
438
  active_source_hash: hash,
@@ -12,15 +12,11 @@ import type { Config } from "./types";
12
12
 
13
13
  export const LIVE_RELOAD_KEYS = new Set([
14
14
  "config.environment_overrides",
15
- "inference.thresholds.match_score",
16
- "inference.thresholds.match_margin",
17
- "inference.thresholds.candidate_floor",
18
- "inference.calibration.run_id",
19
15
  "recall.k_lexical",
20
16
  "recall.k_vector",
21
17
  "recall.k_rerank",
22
- "output.ambiguous_candidate_limit",
23
- "thresholds.candidate_limit",
18
+ "output.top_k",
19
+ "output.max_top_k",
24
20
  "inference.embedding.endpoint",
25
21
  "inference.embedding.api_key_env",
26
22
  "inference.reranker.adapter",