textopt 0.0.0 → 0.1.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.
Files changed (55) hide show
  1. package/README.md +44 -22
  2. package/dist/bootstrap-search/index.cjs +153 -73
  3. package/dist/bootstrap-search/index.d.cts +32 -10
  4. package/dist/bootstrap-search/index.d.mts +32 -10
  5. package/dist/bootstrap-search/index.mjs +144 -66
  6. package/dist/{demos-B0pVQjYC.d.mts → demos-1b7JiUue.d.mts} +10 -3
  7. package/dist/{demos-BTuzFNsp.d.cts → demos-CU9dy2oT.d.cts} +10 -3
  8. package/dist/demos-D2o0qSSo.cjs +244 -0
  9. package/dist/demos-DE2oxNWX.mjs +215 -0
  10. package/dist/file-cache.cjs +11 -3
  11. package/dist/file-cache.mjs +11 -3
  12. package/dist/gepa/index.cjs +76 -71
  13. package/dist/gepa/index.d.cts +12 -6
  14. package/dist/gepa/index.d.mts +12 -6
  15. package/dist/gepa/index.mjs +49 -46
  16. package/dist/index.cjs +129 -27
  17. package/dist/index.d.cts +145 -7
  18. package/dist/index.d.mts +145 -7
  19. package/dist/index.mjs +113 -15
  20. package/dist/{math-COOofUyv.cjs → math-BhlziRPc.cjs} +60 -9
  21. package/dist/math-Dqme4rYz.mjs +123 -0
  22. package/dist/mipro/index.cjs +98 -70
  23. package/dist/mipro/index.d.cts +17 -14
  24. package/dist/mipro/index.d.mts +17 -14
  25. package/dist/mipro/index.mjs +84 -58
  26. package/dist/opro/index.cjs +130 -51
  27. package/dist/opro/index.d.cts +17 -9
  28. package/dist/opro/index.d.mts +17 -9
  29. package/dist/opro/index.mjs +115 -38
  30. package/dist/{optimizer-B7SpRwl7.d.cts → optimizer-Bh5DPRMH.d.cts} +50 -4
  31. package/dist/{optimizer-DqCoth_w.d.mts → optimizer-Ck6-e_8o.d.mts} +50 -4
  32. package/dist/random-search/index.cjs +93 -49
  33. package/dist/random-search/index.d.cts +15 -13
  34. package/dist/random-search/index.d.mts +15 -13
  35. package/dist/random-search/index.mjs +83 -41
  36. package/dist/{reflection-CQToe-5B.d.cts → reflection-Dt3QrXhM.d.cts} +7 -11
  37. package/dist/{reflection-Cr_upzU0.d.mts → reflection-LRaAZP4e.d.mts} +7 -11
  38. package/dist/{evaluation-OZOp6TB7.cjs → reporting-CNHzbJC-.cjs} +165 -5
  39. package/dist/reporting-DQbAohc9.d.cts +240 -0
  40. package/dist/reporting-DQbAohc9.d.mts +240 -0
  41. package/dist/{evaluation-BV0nSZVx.mjs → reporting-DY-DC4HG.mjs} +124 -6
  42. package/dist/simba/index.cjs +210 -83
  43. package/dist/simba/index.d.cts +32 -11
  44. package/dist/simba/index.d.mts +32 -11
  45. package/dist/simba/index.mjs +200 -75
  46. package/dist/testing.cjs +1 -0
  47. package/dist/testing.d.cts +5 -3
  48. package/dist/testing.d.mts +5 -3
  49. package/dist/testing.mjs +1 -1
  50. package/package.json +4 -3
  51. package/dist/demos-B9BJiNKz.cjs +0 -143
  52. package/dist/demos-Degx6UmP.mjs +0 -126
  53. package/dist/math-DhrDmpFS.mjs +0 -78
  54. package/dist/types-CWv4IQFF.d.cts +0 -129
  55. package/dist/types-CWv4IQFF.d.mts +0 -129
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_evaluation = require("../evaluation-OZOp6TB7.cjs");
3
- const require_demos = require("../demos-B9BJiNKz.cjs");
2
+ const require_reporting = require("../reporting-CNHzbJC-.cjs");
3
+ const require_concurrency = require("../concurrency-C-cFzWW2.cjs");
4
+ const require_demos = require("../demos-D2o0qSSo.cjs");
4
5
  const require_rng = require("../rng-DbA_rPIo.cjs");
5
6
  const require_sampling = require("../sampling-Dars7ctR.cjs");
6
7
  //#region src/simba/advice.ts
@@ -15,7 +16,7 @@ const ADVICE_BLOCK = /<advice\s+component="([^"]+)"\s*>([\s\S]*?)<\/advice>/g;
15
16
  * behaviour rather than to the instance being easier.
16
17
  */
17
18
  function buildAdvicePrompt(args) {
18
- const { components, input, better, worse } = args;
19
+ const { components, current, input, better, worse } = args;
19
20
  return [
20
21
  "Two runs of the same system on the same input are shown below, with the reward each earned.",
21
22
  "Your job is to write advice that would make the system behave like the higher scoring run the next time it sees a similar input.",
@@ -26,12 +27,18 @@ function buildAdvicePrompt(args) {
26
27
  ...worse === void 0 ? [] : trajectoryBlock("worse", worse),
27
28
  ...better === void 0 ? [] : trajectoryBlock("better", better),
28
29
  "",
29
- "Write advice for each of these components:",
30
+ "Write advice for each of these components, shown with what it says now:",
30
31
  "",
31
32
  "<components>",
32
- components.join("\n"),
33
+ components.map((component) => [
34
+ `<component name="${component}">`,
35
+ current[component] ?? "",
36
+ "</component>"
37
+ ].join("\n")).join("\n"),
33
38
  "</components>",
34
39
  "",
40
+ "Your advice is appended to what the component already says. Do not restate advice it already carries; add what is missing or correct what is wrong.",
41
+ "",
35
42
  "The component will not have access to this example, so advice that only covers this input is wasted. State the general behaviour it should adopt, and be concrete about when it applies.",
36
43
  "Address each component's own sub-task rather than the system as a whole.",
37
44
  "Base the advice on what actually differed between the two runs. If nothing useful can be said for a component, leave it out.",
@@ -229,21 +236,28 @@ const DEMO_DROP_SCALE = 3;
229
236
  var SimbaOptimizer = class {
230
237
  #config;
231
238
  constructor(config = {}) {
239
+ assertSimbaConfig(config);
232
240
  this.#config = config;
233
241
  }
234
- optimize(task) {
235
- return run({
236
- config: this.#config,
237
- task
238
- });
242
+ async optimize(task) {
243
+ try {
244
+ return await run({
245
+ config: this.#config,
246
+ task
247
+ });
248
+ } finally {
249
+ await require_reporting.flushReporters(task.reporters ?? []);
250
+ }
239
251
  }
240
252
  };
241
253
  async function run(args) {
242
254
  const { config, task } = args;
243
- const { minibatchSize = DEFAULT_MINIBATCH_SIZE, candidates: candidateCount = DEFAULT_CANDIDATES, maxSteps = DEFAULT_MAX_STEPS, maxDemos = DEFAULT_MAX_DEMOS, samplingTemperature = DEFAULT_TEMPERATURE, candidateTemperature = DEFAULT_TEMPERATURE, strategies, maxReflectionCalls, seed = 0, trackBestOutputs = false, checkpointCache = true } = config;
244
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, demoComponents = [], instructionComponents, renderDemo, buildAdvicePrompt: buildPrompt = buildAdvicePrompt, sampler = require_sampling.createEpochShuffledSampler({ minibatchSize }), maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
245
- const components = require_evaluation.componentNames(seedCandidate);
246
- const ruleComponents = instructionComponents ?? components.filter((name) => !demoComponents.includes(name));
255
+ const { minibatchSize = DEFAULT_MINIBATCH_SIZE, candidates: candidateCount = DEFAULT_CANDIDATES, concurrency = 1, maxSteps = DEFAULT_MAX_STEPS, maxDemos = DEFAULT_MAX_DEMOS, samplingTemperature = DEFAULT_TEMPERATURE, candidateTemperature = DEFAULT_TEMPERATURE, strategies, maxReflectionCalls, seed = 0, trackBestOutputs = false, checkpointCache = true } = config;
256
+ const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, demoComponents = [], instructionComponents, renderDemo, buildAdvicePrompt: buildPrompt = buildAdvicePrompt, sampler = require_sampling.createEpochShuffledSampler({ minibatchSize }), maxMetricCalls, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = require_reporting.defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
257
+ const emit = require_reporting.createEmitter(reporters);
258
+ const components = require_reporting.componentNames(seedCandidate);
259
+ const nonDemo = components.filter((name) => !demoComponents.includes(name));
260
+ const ruleComponents = instructionComponents ?? (nonDemo.length > 0 ? nonDemo : components);
247
261
  const enabled = strategies ?? (demoComponents.length > 0 ? ["appendDemo", "appendRule"] : ["appendRule"]);
248
262
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
249
263
  if (validationSet.length === 0) throw new Error("optimize requires a non-empty validationSet");
@@ -252,6 +266,10 @@ async function run(args) {
252
266
  if (enabled.includes("appendDemo") && demoComponents.length === 0) throw new Error("the appendDemo strategy requires at least one entry in demoComponents");
253
267
  if (enabled.includes("appendRule") && ruleComponents.length === 0) throw new Error("the appendRule strategy requires at least one instruction component to write into");
254
268
  if (testSet !== void 0 && testSet.length === 0) throw new Error("optimize requires a non-empty testSet when one is given; omit it to skip held-out evaluation");
269
+ const trainingIds = trainingSet.map((datum, index) => instanceId({
270
+ datum,
271
+ index
272
+ }));
255
273
  const validationIds = validationSet.map((datum, index) => instanceId({
256
274
  datum,
257
275
  index
@@ -260,28 +278,25 @@ async function run(args) {
260
278
  datum,
261
279
  index
262
280
  })) ?? [];
263
- const fingerprint = require_evaluation.runFingerprint({
281
+ const fingerprint = require_reporting.runFingerprint({
264
282
  seedCandidate,
265
- trainingIds: trainingSet.map((datum, index) => instanceId({
266
- datum,
267
- index
268
- })),
283
+ trainingIds,
269
284
  validationIds,
270
285
  seed,
271
286
  ...cacheNamespace === void 0 ? {} : { cacheNamespace }
272
287
  });
273
- require_evaluation.assertResumable({
288
+ require_reporting.assertResumable({
274
289
  fingerprint,
275
290
  ...resumeFrom === void 0 ? {} : { snapshot: resumeFrom }
276
291
  });
277
- const deadline = require_evaluation.createDeadline({ maxWallClockMs });
292
+ const deadline = require_reporting.createDeadline({ maxWallClockMs });
278
293
  const rng = require_rng.createSeededRng(seed, resumeFrom?.rngState);
279
- const budget = require_evaluation.createBudget({
294
+ const budget = require_reporting.createBudget({
280
295
  maxMetricCalls,
281
296
  spent: resumeFrom?.metricCalls ?? 0
282
297
  });
283
- const evaluationCache = cache === false ? void 0 : cache ?? require_evaluation.createMemoryCache();
284
- const evaluator = require_evaluation.createEvaluator({
298
+ const evaluationCache = cache === false ? void 0 : cache ?? require_reporting.createMemoryCache();
299
+ const evaluator = require_reporting.createEvaluator({
285
300
  adapter,
286
301
  budget,
287
302
  ...retry === void 0 ? {} : { retry },
@@ -289,8 +304,9 @@ async function run(args) {
289
304
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
290
305
  trackOutputs: true,
291
306
  cacheHits: resumeFrom?.cacheHits ?? 0,
307
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
292
308
  ...signal === void 0 ? {} : { signal },
293
- onEvaluation: (event) => onEvent?.({
309
+ onEvaluation: (event) => emit({
294
310
  type: "evaluation",
295
311
  ...event
296
312
  })
@@ -306,7 +322,7 @@ async function run(args) {
306
322
  let step = resumeFrom?.step ?? 0;
307
323
  let reflectionCalls = resumeFrom?.reflectionCalls ?? 0;
308
324
  let stopReason = "maxSteps";
309
- onEvent?.({
325
+ emit({
310
326
  type: "start",
311
327
  components,
312
328
  validationSetSize: validationSet.length
@@ -318,7 +334,7 @@ async function run(args) {
318
334
  stopReason = "aborted";
319
335
  break;
320
336
  }
321
- if (require_evaluation.costExhausted({
337
+ if (require_reporting.costExhausted({
322
338
  usage: evaluator.usage(),
323
339
  maxCostUsd
324
340
  })) {
@@ -341,16 +357,17 @@ async function run(args) {
341
357
  scores: programs.map((_, index) => averageScore(index)),
342
358
  k: candidateCount
343
359
  });
344
- onEvent?.({
360
+ emit({
345
361
  type: "stepStart",
346
362
  step,
347
363
  poolSize: pool.length
348
364
  });
349
- const batch = sampler({
365
+ const batchIndices = sampler({
350
366
  trainingSet,
351
367
  iteration: step,
352
368
  rng
353
- }).map((index) => trainingSet[index]);
369
+ });
370
+ const batch = batchIndices.map((index) => trainingSet[index]);
354
371
  const samples = [];
355
372
  for (let slot = 0; slot < candidateCount; slot += 1) {
356
373
  const programIndex = softmaxSample({
@@ -372,7 +389,7 @@ async function run(args) {
372
389
  outputs: evaluation.outputs,
373
390
  ...evaluation.feedback === void 0 ? {} : { feedback: evaluation.feedback }
374
391
  });
375
- programScores[programIndex].push(require_evaluation.measuredMean(evaluation) ?? 0);
392
+ programScores[programIndex].push(require_reporting.measuredMean(evaluation) ?? 0);
376
393
  }
377
394
  if (samples.length === 0) {
378
395
  stopReason = "budgetExhausted";
@@ -412,7 +429,7 @@ async function run(args) {
412
429
  high
413
430
  });
414
431
  } catch (err) {
415
- onEvent?.({
432
+ emit({
416
433
  type: "error",
417
434
  step,
418
435
  err
@@ -427,36 +444,54 @@ async function run(args) {
427
444
  });
428
445
  }
429
446
  let stepBest;
447
+ const batchIds = batchIndices.map((index) => trainingIds[index]);
448
+ const scheduled = [];
449
+ let owed = 0;
430
450
  for (const entry of built) {
431
- if (!budget.canAfford(batch.length)) {
451
+ if (!budget.canAfford(owed + batch.length)) {
432
452
  stopReason = "budgetExhausted";
433
453
  break;
434
454
  }
435
- let score;
436
- try {
437
- score = require_evaluation.measuredMean(await evaluator.evaluate({
438
- candidate: entry.candidate,
439
- batch,
440
- ids: batch.map((datum, index) => instanceId({
441
- datum,
442
- index
443
- })),
444
- split: "train",
445
- phase: "minibatch",
446
- candidateId: programs.length,
447
- iteration: step
448
- }));
449
- } catch (err) {
450
- if (err instanceof require_evaluation.BudgetExhausted) {
451
- stopReason = "budgetExhausted";
452
- break;
455
+ owed += evaluator.countUncached({
456
+ candidate: entry.candidate,
457
+ ids: batchIds,
458
+ split: "train"
459
+ });
460
+ scheduled.push(entry);
461
+ }
462
+ const poolBase = programs.length;
463
+ const scored = await mapDistinct({
464
+ items: scheduled,
465
+ limit: concurrency,
466
+ key: (entry) => require_reporting.candidateHash(entry.candidate),
467
+ task: async (entry, index) => {
468
+ try {
469
+ return { score: require_reporting.measuredMean(await evaluator.evaluate({
470
+ candidate: entry.candidate,
471
+ batch,
472
+ ids: batchIds,
473
+ split: "train",
474
+ phase: "minibatch",
475
+ candidateId: poolBase + index,
476
+ iteration: step
477
+ })) };
478
+ } catch (err) {
479
+ if (err instanceof require_reporting.BudgetExhausted) return { stop: true };
480
+ throw err;
453
481
  }
454
- throw err;
455
482
  }
483
+ });
484
+ for (const [index, outcome] of scored.entries()) {
485
+ if (outcome.stop === true) {
486
+ stopReason = "budgetExhausted";
487
+ break;
488
+ }
489
+ const { score } = outcome;
456
490
  if (score === void 0) continue;
491
+ const entry = scheduled[index];
457
492
  programs.push(entry.candidate);
458
493
  programScores.push([score]);
459
- onEvent?.({
494
+ emit({
460
495
  type: "candidate",
461
496
  step,
462
497
  strategy: entry.strategy,
@@ -479,38 +514,78 @@ async function run(args) {
479
514
  }
480
515
  }
481
516
  if (signal?.aborted) stopReason = "aborted";
482
- const finalists = [];
517
+ const contenders = [];
518
+ let owedForFinalists = 0;
483
519
  for (const index of evenlySpacedIndices({
484
520
  length: winners.length,
485
521
  count: candidateCount + 1
486
522
  })) {
487
523
  const winner = winners[index];
488
- if (!budget.canAfford(evaluator.countUncached({
524
+ const uncached = evaluator.countUncached({
489
525
  candidate: winner.candidate,
490
526
  ids: validationIds,
491
527
  split: "val"
492
- }))) break;
493
- let score;
494
- try {
495
- score = require_evaluation.measuredMean(await evaluator.evaluate({
496
- candidate: winner.candidate,
497
- batch: validationSet,
498
- ids: validationIds,
499
- split: "val",
500
- phase: index === 0 ? "seed" : "validation",
501
- candidateId: index,
502
- iteration: step
503
- }));
504
- } catch (err) {
505
- if (err instanceof require_evaluation.BudgetExhausted || signal?.aborted) break;
506
- throw err;
507
- }
508
- if (score !== void 0) finalists.push({
528
+ });
529
+ if (!budget.canAfford(owedForFinalists + uncached)) break;
530
+ owedForFinalists += uncached;
531
+ contenders.push({
532
+ index,
509
533
  candidate: winner.candidate,
510
- score,
511
534
  step: winner.step
512
535
  });
513
536
  }
537
+ const sweeps = await mapDistinct({
538
+ items: contenders,
539
+ limit: concurrency,
540
+ key: (contender) => require_reporting.candidateHash(contender.candidate),
541
+ task: async (contender) => {
542
+ if (signal?.aborted) return { stop: true };
543
+ try {
544
+ const evaluation = await evaluator.evaluate({
545
+ candidate: contender.candidate,
546
+ batch: validationSet,
547
+ ids: validationIds,
548
+ split: "val",
549
+ phase: contender.index === 0 ? "seed" : "validation",
550
+ candidateId: contender.index,
551
+ iteration: step
552
+ });
553
+ return {
554
+ score: require_reporting.measuredMean(evaluation),
555
+ evaluation
556
+ };
557
+ } catch (err) {
558
+ if (err instanceof require_reporting.BudgetExhausted || signal?.aborted) return { stop: true };
559
+ throw err;
560
+ }
561
+ }
562
+ });
563
+ const finalists = [];
564
+ let acceptedCandidates = -1;
565
+ let acceptedScore = Number.NEGATIVE_INFINITY;
566
+ for (const [position, outcome] of sweeps.entries()) {
567
+ if (outcome.stop === true) break;
568
+ if (outcome.score === void 0) continue;
569
+ const contender = contenders[position];
570
+ finalists.push({
571
+ candidate: contender.candidate,
572
+ score: outcome.score,
573
+ step: contender.step
574
+ });
575
+ if (outcome.score > acceptedScore) {
576
+ acceptedScore = outcome.score;
577
+ acceptedCandidates += 1;
578
+ emit({
579
+ type: "candidateAccepted",
580
+ step: contender.step,
581
+ candidateId: acceptedCandidates,
582
+ candidate: contender.candidate,
583
+ aggregateScore: outcome.score,
584
+ instanceScores: require_reporting.instanceRow(outcome.evaluation),
585
+ ...trackBestOutputs ? { outputs: outcome.evaluation.outputs } : {}
586
+ });
587
+ }
588
+ }
514
589
  const seedScore = finalists[0]?.score ?? 0;
515
590
  finalists.sort((a, b) => b.score - a.score);
516
591
  const best = finalists[0] ?? {
@@ -526,7 +601,7 @@ async function run(args) {
526
601
  candidateId: null,
527
602
  iteration: step
528
603
  }))?.outputs;
529
- const testScore = testSet === void 0 ? void 0 : require_evaluation.measuredMean(await evaluator.evaluate({
604
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
530
605
  candidate: best.candidate,
531
606
  batch: testSet,
532
607
  ids: testIds,
@@ -535,13 +610,17 @@ async function run(args) {
535
610
  candidateId: null,
536
611
  iteration: step,
537
612
  charge: false
538
- }));
539
- onEvent?.({
613
+ });
614
+ const testScore = heldOut === void 0 ? void 0 : require_reporting.measuredMean(heldOut);
615
+ emit({
540
616
  type: "finish",
541
617
  reason: stopReason,
618
+ bestCandidateId: acceptedCandidates,
542
619
  bestScore: best.score,
543
620
  metricCalls: budget.spent(),
544
- ...testScore === void 0 ? {} : { testScore }
621
+ ...testScore === void 0 ? {} : { testScore },
622
+ ...heldOut === void 0 ? {} : { testInstanceScores: require_reporting.instanceRow(heldOut) },
623
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
545
624
  });
546
625
  return {
547
626
  bestCandidate: best.candidate,
@@ -557,7 +636,8 @@ async function run(args) {
557
636
  ...bestOutputs === void 0 ? {} : { bestOutputs },
558
637
  ...testScore === void 0 ? {} : {
559
638
  testScore,
560
- testMetricCalls: testSet?.length ?? 0
639
+ testMetricCalls: evaluator.unchargedCalls(),
640
+ testUsage: evaluator.unchargedUsage()
561
641
  },
562
642
  stopReason
563
643
  };
@@ -594,7 +674,11 @@ async function run(args) {
594
674
  const drops = /* @__PURE__ */ new Set();
595
675
  for (let draw = 0; draw < Math.min(wanted, held); draw += 1) drops.add(rng.nextInt(held));
596
676
  const next = { ...candidate };
597
- for (const [name, demos] of blocks) next[name] = require_demos.formatDemos(demos.filter((_, index) => !drops.has(index)), renderDemo === void 0 ? {} : { render: renderDemo });
677
+ for (const [name, demos] of blocks) next[name] = require_demos.replaceDemos({
678
+ text: next[name] ?? "",
679
+ demos: demos.filter((_, index) => !drops.has(index)),
680
+ ...renderDemo === void 0 ? {} : { render: renderDemo }
681
+ });
598
682
  return next;
599
683
  }
600
684
  /**
@@ -615,8 +699,12 @@ async function run(args) {
615
699
  };
616
700
  const next = { ...candidate };
617
701
  for (const name of demoComponents) {
618
- const kept = require_demos.parseDemos(next[name] ?? "");
619
- next[name] = require_demos.formatDemos([...kept, demo], renderDemo === void 0 ? {} : { render: renderDemo });
702
+ const text = next[name] ?? "";
703
+ next[name] = require_demos.replaceDemos({
704
+ text,
705
+ demos: [...require_demos.parseDemos(text), demo],
706
+ ...renderDemo === void 0 ? {} : { render: renderDemo }
707
+ });
620
708
  }
621
709
  return next;
622
710
  }
@@ -644,6 +732,7 @@ async function run(args) {
644
732
  const response = await reflect({
645
733
  prompt: buildPrompt({
646
734
  components: ruleComponents,
735
+ current: Object.fromEntries(ruleComponents.map((name) => [name, candidate[name] ?? ""])),
647
736
  input: bucket.datum,
648
737
  ...better === void 0 ? {} : { better },
649
738
  ...worse === void 0 ? {} : { worse }
@@ -684,6 +773,7 @@ async function run(args) {
684
773
  metricCalls: budget.spent(),
685
774
  reflectionCalls,
686
775
  cacheHits: evaluator.cacheHits(),
776
+ usage: evaluator.usage(),
687
777
  rngState: rng.state(),
688
778
  ...sampler.state === void 0 ? {} : { sampler: sampler.state() },
689
779
  ...cached === void 0 ? {} : { cache: cached }
@@ -694,14 +784,51 @@ async function run(args) {
694
784
  await onCheckpoint(takeSnapshot(completed));
695
785
  }
696
786
  }
697
- function defaultInstanceId(args) {
698
- return String(args.index);
787
+ /**
788
+ * Runs `items` concurrently, except that items sharing a key run one after the
789
+ * other.
790
+ *
791
+ * Two identical candidates cost one evaluation and one cache hit when they are
792
+ * scored in sequence, and two evaluations when they overlap: the cache is only
793
+ * written once a rollout returns. Serializing the duplicates is what keeps a
794
+ * fan-out from buying a second copy of a score the run has already paid for.
795
+ */
796
+ async function mapDistinct(args) {
797
+ const { items, limit, key, task } = args;
798
+ const groups = /* @__PURE__ */ new Map();
799
+ items.forEach((item, index) => {
800
+ const group = groups.get(key(item));
801
+ if (group === void 0) {
802
+ groups.set(key(item), [index]);
803
+ return;
804
+ }
805
+ group.push(index);
806
+ });
807
+ const results = new Array(items.length);
808
+ await require_concurrency.mapWithConcurrency({
809
+ items: [...groups.values()],
810
+ limit,
811
+ task: async (indices) => {
812
+ for (const index of indices) results[index] = await task(items[index], index);
813
+ }
814
+ });
815
+ return results;
816
+ }
817
+ /**
818
+ * Range checks on the search knobs, run at construction so a configuration
819
+ * that could never terminate is refused before a task is ever handed to it.
820
+ */
821
+ function assertSimbaConfig(config) {
822
+ const { concurrency = 1 } = config;
823
+ if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error(`concurrency must be a positive integer, received ${concurrency}`);
699
824
  }
700
825
  //#endregion
701
826
  exports.SimbaOptimizer = SimbaOptimizer;
702
827
  exports.buildAdvicePrompt = buildAdvicePrompt;
703
828
  exports.buildBuckets = buildBuckets;
704
829
  exports.evenlySpacedIndices = evenlySpacedIndices;
830
+ exports.isCandidateAccepted = require_reporting.isCandidateAccepted;
831
+ exports.isRunFinished = require_reporting.isRunFinished;
705
832
  exports.parseAdvice = parseAdvice;
706
833
  exports.percentile = percentile;
707
834
  exports.samplePoisson = samplePoisson;
@@ -1,8 +1,8 @@
1
- import { n as Candidate, t as Adapter, u as TextModel } from "../types-CWv4IQFF.cjs";
1
+ import { _ as TextModel, a as RunFinished, c as Adapter, i as Reporter, l as Candidate, n as OptimizerEvent, o as isCandidateAccepted, r as ReportableEvent, s as isRunFinished, t as CandidateAccepted, v as UsageTotals } from "../reporting-DQbAohc9.cjs";
2
2
  import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.cjs";
3
3
  import { t as Rng } from "../rng-BR5MOedA.cjs";
4
- import { r as DemoRenderer } from "../demos-BTuzFNsp.cjs";
5
- import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-B7SpRwl7.cjs";
4
+ import { r as DemoRenderer } from "../demos-CU9dy2oT.cjs";
5
+ import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-Bh5DPRMH.cjs";
6
6
  import { t as BatchSampler } from "../sampling-axOwfZf5.cjs";
7
7
  //#region src/simba/advice.d.ts
8
8
  interface AdviceTrajectory<Output = unknown> {
@@ -13,6 +13,12 @@ interface AdviceTrajectory<Output = unknown> {
13
13
  interface AdvicePromptArgs<Datum = unknown, Output = unknown> {
14
14
  /** Components the advice is wanted for, named so each can be addressed. */
15
15
  components: readonly string[];
16
+ /**
17
+ * What each of those components currently says. Advice is appended to this
18
+ * text rather than replacing it, so a proposer that cannot see it writes
19
+ * blind: it restates what is already there, and cannot correct it.
20
+ */
21
+ current: Record<string, string>;
16
22
  input: Datum;
17
23
  /** The higher scoring run of this instance, when there is one to contrast. */
18
24
  better?: AdviceTrajectory<Output>;
@@ -56,6 +62,17 @@ interface SimbaConfig {
56
62
  minibatchSize?: number;
57
63
  /** Programs sampled per step, and candidates built from them. Default 6. */
58
64
  candidates?: number;
65
+ /**
66
+ * How many evaluations may be in flight at once. Default 1.
67
+ *
68
+ * Covers the two places a step's work is independent — scoring the
69
+ * candidates a step built, and sweeping the finalists at the end of the run
70
+ * — and nothing else. The trajectory samples and the mutations that read
71
+ * them are deliberately left in sequence: each of those reads state the one
72
+ * before it wrote, so overlapping them would make a seeded run depend on
73
+ * which call returned first.
74
+ */
75
+ concurrency?: number;
59
76
  /** Steps to run. Default 8. */
60
77
  maxSteps?: number;
61
78
  /** Demos a candidate may hold before the loop starts dropping them. Default 4. */
@@ -97,6 +114,8 @@ interface SimbaSnapshot {
97
114
  metricCalls: number;
98
115
  reflectionCalls: number;
99
116
  cacheHits: number;
117
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
118
+ usage?: UsageTotals;
100
119
  rngState: number;
101
120
  sampler?: unknown;
102
121
  cache?: [string, CachedScore][];
@@ -121,7 +140,8 @@ interface SimbaTask<Datum, Trajectory = unknown, Output = unknown, K extends str
121
140
  index: number;
122
141
  }) => string;
123
142
  cache?: EvaluationCache | false;
124
- onEvent?: (event: SimbaEvent<NoInfer<K>>) => void;
143
+ /** Observers of the run. Every one sees every event; none can fail it. */
144
+ reporters?: readonly Reporter<SimbaEvent<NoInfer<K>>>[];
125
145
  onCheckpoint?: (snapshot: SimbaSnapshot) => void | Promise<void>;
126
146
  resumeFrom?: SimbaSnapshot;
127
147
  }
@@ -143,17 +163,18 @@ type SimbaEvent<K extends string = string> = {
143
163
  /** The program it was mutated from; 0 is the seed. */
144
164
  sourceProgram: number;
145
165
  minibatchScore: number;
146
- } | {
166
+ } | ({
167
+ type: "candidateAccepted";
168
+ /** The step whose winner this was. */
169
+ step: number;
170
+ } & CandidateAccepted<K>) | {
147
171
  type: "error";
148
172
  step: number;
149
173
  err: unknown;
150
- } | {
174
+ } | ({
151
175
  type: "finish";
152
176
  reason: SimbaStopReason;
153
- bestScore: number;
154
- metricCalls: number;
155
- testScore?: number;
156
- };
177
+ } & RunFinished);
157
178
  interface SimbaResult<K extends string = string, Output = unknown> extends OptimizerResult<K, SimbaStopReason, Output> {
158
179
  /** The seed's own validation score, so the lift the run bought is readable. */
159
180
  seedScore: number;
@@ -286,4 +307,4 @@ declare function evenlySpacedIndices(args: {
286
307
  count: number;
287
308
  }): number[];
288
309
  //#endregion
289
- export { type AdvicePromptArgs, type AdvicePromptBuilder, type AdviceTrajectory, type SimbaBucket, type SimbaConfig, type SimbaEvent, type SimbaFinalist, SimbaOptimizer, type SimbaResult, type SimbaRollout, type SimbaSample, type SimbaSnapshot, type SimbaStopReason, type SimbaStrategy, type SimbaTask, buildAdvicePrompt, buildBuckets, evenlySpacedIndices, parseAdvice, percentile, samplePoisson, softmaxWeights, topKPlusBaseline };
310
+ export { type AdvicePromptArgs, type AdvicePromptBuilder, type AdviceTrajectory, type CandidateAccepted, type OptimizerEvent, type ReportableEvent, type Reporter, type RunFinished, type SimbaBucket, type SimbaConfig, type SimbaEvent, type SimbaFinalist, SimbaOptimizer, type SimbaResult, type SimbaRollout, type SimbaSample, type SimbaSnapshot, type SimbaStopReason, type SimbaStrategy, type SimbaTask, buildAdvicePrompt, buildBuckets, evenlySpacedIndices, isCandidateAccepted, isRunFinished, parseAdvice, percentile, samplePoisson, softmaxWeights, topKPlusBaseline };