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,6 @@
1
- import { n as Candidate, t as Adapter, u as TextModel } from "../types-CWv4IQFF.mjs";
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.mjs";
2
2
  import { n as EvaluationCache, t as CachedScore } from "../cache-CuSo0NJ8.mjs";
3
- import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-DqCoth_w.mjs";
3
+ import { n as OptimizerResult, r as OptimizerTask, s as EvaluationEvent, t as Optimizer } from "../optimizer-Ck6-e_8o.mjs";
4
4
  //#region src/opro/optimize.d.ts
5
5
  /** One instruction that was tried, and what it scored. */
6
6
  /** A history entry plus the system state its score was measured in. */
@@ -111,6 +111,13 @@ interface OproSnapshot {
111
111
  reflectionCalls: number;
112
112
  metricCalls: number;
113
113
  cacheHits: number;
114
+ /** Usage already spent, so a resumed run reports totals and honours ceilings. */
115
+ usage?: UsageTotals;
116
+ /**
117
+ * Candidates accepted so far. Reporters key rows by this id, so restarting it
118
+ * at zero makes a resumed run collide with the run it continues.
119
+ */
120
+ acceptedCandidates?: number;
114
121
  rngState: number;
115
122
  /** Component name -> every text tried for it, with what it scored. */
116
123
  histories: Record<string, RecordedAttempt[]>;
@@ -133,7 +140,8 @@ interface OproTask<Datum, Trajectory = unknown, Output = unknown, K extends stri
133
140
  }) => string;
134
141
  /** Pass `false` to disable caching entirely. */
135
142
  cache?: EvaluationCache | false;
136
- onEvent?: (event: OproEvent<NoInfer<K>>) => void;
143
+ /** Observers of the run. Every one sees every event; none can fail it. */
144
+ reporters?: readonly Reporter<OproEvent<NoInfer<K>>>[];
137
145
  /**
138
146
  * Called with a resumable snapshot after the seed is scored and after every
139
147
  * round. Persist it and a killed run costs the last round, not all of them.
@@ -161,13 +169,13 @@ type OproEvent<K extends string = string> = {
161
169
  score: number;
162
170
  /** True when this attempt became the new incumbent. */
163
171
  accepted: boolean;
164
- } | {
172
+ } | ({
173
+ type: "candidateAccepted";
174
+ round: number;
175
+ } & CandidateAccepted<K>) | ({
165
176
  type: "finish";
166
177
  reason: OproStopReason;
167
- bestScore: number;
168
- metricCalls: number;
169
- testScore?: number;
170
- };
178
+ } & RunFinished);
171
179
  interface OproAttempt<K extends string = string> {
172
180
  round: number;
173
181
  component: K;
@@ -227,4 +235,4 @@ declare function buildOproPrompt(args: {
227
235
  exemplars: readonly string[];
228
236
  }): string;
229
237
  //#endregion
230
- export { type OproAttempt, type OproConfig, type OproEvent, OproOptimizer, type OproPromptBuilder, type OproResult, type OproSnapshot, type OproStopReason, type OproTask, type ScoredAttempt, buildOproPrompt };
238
+ export { type CandidateAccepted, type OproAttempt, type OproConfig, type OproEvent, OproOptimizer, type OproPromptBuilder, type OproResult, type OproSnapshot, type OproStopReason, type OproTask, type OptimizerEvent, type ReportableEvent, type Reporter, type RunFinished, type ScoredAttempt, buildOproPrompt, isCandidateAccepted, isRunFinished };
@@ -1,4 +1,4 @@
1
- import { a as requireMeasuredMean, d as createMemoryCache, f as stableHash, i as measuredMean, l as runFingerprint, n as costExhausted, o as createDeadline, p as createBudget, r as createEvaluator, s as assertResumable, t as BudgetExhausted, u as componentNames } from "../evaluation-BV0nSZVx.mjs";
1
+ import { _ as createMemoryCache, a as isRunFinished, b as createBudget, c as createEvaluator, d as createDeadline, f as assertResumable, h as componentNames, i as isCandidateAccepted, l as measuredMean, m as runFingerprint, n as flushReporters, o as BudgetExhausted, r as instanceRow, s as costExhausted, t as createEmitter, u as requireMeasuredMean, v as defaultInstanceId, y as stableHash } from "../reporting-DY-DC4HG.mjs";
2
2
  import { t as mapWithConcurrency } from "../concurrency-D58PWeSk.mjs";
3
3
  import { t as parseProposedText } from "../text--v4Ffbus.mjs";
4
4
  import { t as createSeededRng } from "../rng-Dtc5eZ_W.mjs";
@@ -42,10 +42,14 @@ var OproOptimizer = class {
42
42
  this.#config = config;
43
43
  }
44
44
  async optimize(task) {
45
- return runOpro({
46
- config: this.#config,
47
- task
48
- });
45
+ try {
46
+ return await runOpro({
47
+ config: this.#config,
48
+ task
49
+ });
50
+ } finally {
51
+ await flushReporters(task.reporters ?? []);
52
+ }
49
53
  }
50
54
  };
51
55
  /**
@@ -90,7 +94,8 @@ function buildOproPrompt(args) {
90
94
  async function runOpro(args) {
91
95
  const { config, task } = args;
92
96
  const { proposalsPerRound = DEFAULT_PROPOSALS_PER_ROUND, concurrency = 1, maxRounds = Number.POSITIVE_INFINITY, maxReflectionCalls = Number.POSITIVE_INFINITY, seed = 0, historySize = DEFAULT_HISTORY_SIZE, exemplars = DEFAULT_EXEMPLARS, scoringSetSize, fullEvalInterval = DEFAULT_FULL_EVAL_INTERVAL, scoreScale = DEFAULT_SCORE_SCALE, buildPrompt = buildOproPrompt, trackBestOutputs = false, checkpointCache = true } = config;
93
- const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, renderDatum = renderDefault, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, onEvent, onCheckpoint, resumeFrom, signal } = task;
97
+ const { seedCandidate, trainingSet, validationSet = trainingSet, testSet, adapter, reflect, maxMetricCalls, renderDatum = renderDefault, cache, cacheNamespace, retry, maxCostUsd, maxWallClockMs, instanceId = defaultInstanceId, reporters = [], onCheckpoint, resumeFrom, signal } = task;
98
+ const emit = createEmitter(reporters);
94
99
  const deadline = createDeadline({ maxWallClockMs });
95
100
  const components = componentNames(seedCandidate);
96
101
  if (trainingSet.length === 0) throw new Error("optimize requires a non-empty trainingSet");
@@ -133,8 +138,9 @@ async function runOpro(args) {
133
138
  ...evaluationCache === void 0 ? {} : { cache: evaluationCache },
134
139
  trackOutputs: trackBestOutputs,
135
140
  cacheHits: resumeFrom?.cacheHits ?? 0,
141
+ ...resumeFrom?.usage === void 0 ? {} : { usage: resumeFrom.usage },
136
142
  ...signal === void 0 ? {} : { signal },
137
- onEvaluation: (event) => onEvent?.({
143
+ onEvaluation: (event) => emit({
138
144
  type: "evaluation",
139
145
  ...event
140
146
  })
@@ -159,7 +165,7 @@ async function runOpro(args) {
159
165
  let round = resumeFrom?.round ?? 0;
160
166
  let reflectionCalls = resumeFrom?.reflectionCalls ?? 0;
161
167
  let stopReason = "maxRounds";
162
- onEvent?.({
168
+ emit({
163
169
  type: "start",
164
170
  components,
165
171
  validationSetSize: validationSet.length
@@ -192,14 +198,36 @@ async function runOpro(args) {
192
198
  iteration: round
193
199
  });
194
200
  }
201
+ /** What screening a candidate would cost, in rollouts nothing has cached. */
202
+ function screenCost(candidate) {
203
+ return scoringSet === void 0 ? evaluator.countUncached({
204
+ candidate,
205
+ ids: validationIds,
206
+ split: "val"
207
+ }) : evaluator.countUncached({
208
+ candidate,
209
+ ids: scoringIds,
210
+ split: "train"
211
+ });
212
+ }
195
213
  const seedEvaluation = resumeFrom === void 0 ? await sweep(seedCandidate, "seed") : void 0;
196
214
  const seedScore = seedEvaluation === void 0 ? resumeFrom.seedScore : requireMeasuredMean({
197
215
  batch: seedEvaluation,
198
216
  phase: "seed"
199
217
  });
218
+ if (seedEvaluation !== void 0) emit({
219
+ type: "candidateAccepted",
220
+ round: 0,
221
+ candidateId: 0,
222
+ candidate: seedCandidate,
223
+ aggregateScore: seedScore,
224
+ instanceScores: instanceRow(seedEvaluation),
225
+ ...trackBestOutputs ? { outputs: seedEvaluation.outputs } : {}
226
+ });
200
227
  let best = resumeFrom?.best ?? seedCandidate;
201
228
  let bestScore = resumeFrom?.bestScore ?? seedScore;
202
229
  /** Absent on a resumed run until a sweep wins: outputs are not checkpointed. */
230
+ let acceptedCandidates = resumeFrom?.acceptedCandidates ?? 0;
203
231
  let bestOutputs = seedEvaluation?.outputs;
204
232
  let reported = resumeFrom?.reported ?? seedCandidate;
205
233
  let lastSwept = resumeFrom === void 0 ? seedCandidate : resumeFrom.incumbentSwept ? best : {};
@@ -214,6 +242,24 @@ async function runOpro(args) {
214
242
  * what gets reported is the best candidate a full sweep has actually seen —
215
243
  * never a subset number wearing a validation set label.
216
244
  */
245
+ /**
246
+ * The incumbent moved and a full sweep measured it. Emitted from the two
247
+ * places that can be true — a screening run with no scoring set, where the
248
+ * attempt's own evaluation is the sweep, and the cadence that confirms an
249
+ * incumbent later — because a payload assembled twice is one that drifts.
250
+ */
251
+ function emitAccepted(args) {
252
+ acceptedCandidates += 1;
253
+ emit({
254
+ type: "candidateAccepted",
255
+ round,
256
+ candidateId: acceptedCandidates,
257
+ candidate: args.candidate,
258
+ aggregateScore: args.score,
259
+ instanceScores: instanceRow(args.evaluation),
260
+ ...trackBestOutputs ? { outputs: args.evaluation.outputs } : {}
261
+ });
262
+ }
217
263
  async function refreshIncumbent() {
218
264
  if (best === lastSwept || !budget.canAfford(validationSet.length)) return "ok";
219
265
  try {
@@ -224,6 +270,11 @@ async function runOpro(args) {
224
270
  reported = best;
225
271
  bestScore = full;
226
272
  bestOutputs = evaluation.outputs;
273
+ emitAccepted({
274
+ candidate: best,
275
+ evaluation,
276
+ score: full
277
+ });
227
278
  }
228
279
  } catch (err) {
229
280
  if (err instanceof BudgetExhausted || signal?.aborted) return "stop";
@@ -259,6 +310,8 @@ async function runOpro(args) {
259
310
  reflectionCalls,
260
311
  metricCalls: budget.spent(),
261
312
  cacheHits: evaluator.cacheHits(),
313
+ usage: evaluator.usage(),
314
+ acceptedCandidates,
262
315
  rngState: rng.state(),
263
316
  histories: Object.fromEntries([...histories].map(([name, attempts]) => [name, [...attempts]])),
264
317
  ...scoringIndices === void 0 ? {} : { scoringIndices },
@@ -303,7 +356,7 @@ async function runOpro(args) {
303
356
  const history = histories.get(component);
304
357
  const context = contextOf(best, component);
305
358
  const comparable = history.filter((attempt) => attempt.context === context);
306
- onEvent?.({
359
+ emit({
307
360
  type: "roundStart",
308
361
  round,
309
362
  component,
@@ -332,25 +385,43 @@ async function runOpro(args) {
332
385
  const unique = [...new Set(drawn)].filter((text) => text.length > 0 && !tried.has(text));
333
386
  barrenRounds = unique.length === 0 ? barrenRounds + 1 : 0;
334
387
  let roundStop;
388
+ const scheduled = [];
389
+ let owed = 0;
335
390
  for (const text of unique) {
336
391
  const candidate = {
337
392
  ...best,
338
393
  [component]: text
339
394
  };
340
- let evaluation;
341
- try {
342
- evaluation = await screen(candidate, "validation");
343
- } catch (err) {
344
- if (err instanceof BudgetExhausted) {
345
- roundStop = "budgetExhausted";
346
- break;
347
- }
348
- if (signal?.aborted) {
349
- roundStop = "aborted";
350
- break;
395
+ const cost = screenCost(candidate);
396
+ if (!budget.canAfford(owed + cost)) {
397
+ roundStop = "budgetExhausted";
398
+ break;
399
+ }
400
+ owed += cost;
401
+ scheduled.push(candidate);
402
+ }
403
+ const screened = await mapWithConcurrency({
404
+ items: scheduled,
405
+ limit: concurrency,
406
+ task: async (candidate) => {
407
+ if (signal?.aborted) return { stop: "aborted" };
408
+ try {
409
+ return { evaluation: await screen(candidate, "validation") };
410
+ } catch (err) {
411
+ if (err instanceof BudgetExhausted) return { stop: "budgetExhausted" };
412
+ if (signal?.aborted) return { stop: "aborted" };
413
+ throw err;
351
414
  }
352
- throw err;
353
415
  }
416
+ });
417
+ for (const [index, outcome] of screened.entries()) {
418
+ if (outcome.stop !== void 0) {
419
+ roundStop = outcome.stop;
420
+ break;
421
+ }
422
+ const candidate = scheduled[index];
423
+ const text = candidate[component];
424
+ const evaluation = outcome.evaluation;
354
425
  const score = measuredMean(evaluation);
355
426
  if (score === void 0) continue;
356
427
  const accepted = score > bestSearchScore;
@@ -365,7 +436,7 @@ async function runOpro(args) {
365
436
  candidate,
366
437
  score
367
438
  });
368
- onEvent?.({
439
+ emit({
369
440
  type: "attempt",
370
441
  round,
371
442
  component,
@@ -380,6 +451,11 @@ async function runOpro(args) {
380
451
  lastSwept = candidate;
381
452
  bestScore = score;
382
453
  bestOutputs = evaluation.outputs;
454
+ emitAccepted({
455
+ candidate,
456
+ evaluation,
457
+ score
458
+ });
383
459
  }
384
460
  const scaled = scaleScore(score, scoreScale);
385
461
  for (const name of components) {
@@ -393,22 +469,22 @@ async function runOpro(args) {
393
469
  }
394
470
  }
395
471
  round += 1;
472
+ let cadenceStop;
473
+ if (roundStop === void 0 && scoringSet !== void 0 && round % fullEvalInterval === 0 && await refreshIncumbent() === "stop") cadenceStop = signal?.aborted ? "aborted" : "budgetExhausted";
396
474
  await checkpoint();
397
475
  if (roundStop !== void 0) {
398
476
  stopReason = roundStop;
399
477
  break;
400
478
  }
401
- if (scoringSet !== void 0 && round % fullEvalInterval === 0) {
402
- if (await refreshIncumbent() === "stop") {
403
- stopReason = signal?.aborted ? "aborted" : "budgetExhausted";
404
- break;
405
- }
479
+ if (cadenceStop !== void 0) {
480
+ stopReason = cadenceStop;
481
+ break;
406
482
  }
407
483
  }
408
484
  if (scoringSet !== void 0 && !signal?.aborted) await refreshIncumbent();
409
485
  if (signal?.aborted) stopReason = "aborted";
410
- const testScore = testSet === void 0 ? void 0 : measuredMean(await evaluator.evaluate({
411
- candidate: best,
486
+ const heldOut = testSet === void 0 ? void 0 : await evaluator.evaluate({
487
+ candidate: reported,
412
488
  batch: testSet,
413
489
  ids: testIds,
414
490
  split: "test",
@@ -416,13 +492,17 @@ async function runOpro(args) {
416
492
  candidateId: null,
417
493
  iteration: round,
418
494
  charge: false
419
- }));
420
- onEvent?.({
495
+ });
496
+ const testScore = heldOut === void 0 ? void 0 : measuredMean(heldOut);
497
+ emit({
421
498
  type: "finish",
422
499
  reason: stopReason,
500
+ bestCandidateId: acceptedCandidates,
423
501
  bestScore,
424
502
  metricCalls: budget.spent(),
425
- ...testScore === void 0 ? {} : { testScore }
503
+ ...testScore === void 0 ? {} : { testScore },
504
+ ...heldOut === void 0 ? {} : { testInstanceScores: instanceRow(heldOut) },
505
+ ...heldOut === void 0 || !trackBestOutputs ? {} : { testOutputs: heldOut.outputs }
426
506
  });
427
507
  return {
428
508
  snapshot: takeSnapshot(),
@@ -433,7 +513,8 @@ async function runOpro(args) {
433
513
  ...trackBestOutputs ? { bestOutputs } : {},
434
514
  ...testScore === void 0 ? {} : {
435
515
  testScore,
436
- testMetricCalls: evaluator.unchargedCalls()
516
+ testMetricCalls: evaluator.unchargedCalls(),
517
+ testUsage: evaluator.unchargedUsage()
437
518
  },
438
519
  rounds: round,
439
520
  trajectory,
@@ -477,9 +558,5 @@ function assertConfig(config) {
477
558
  for (const [name, value] of positive) if (value !== void 0 && (!Number.isInteger(value) || value < 1)) throw new Error(`${name} must be a positive integer, received ${value}`);
478
559
  if (config.exemplars !== void 0 && (!Number.isInteger(config.exemplars) || config.exemplars < 0)) throw new Error(`exemplars must be a non-negative integer, received ${config.exemplars}`);
479
560
  }
480
- function defaultInstanceId(args) {
481
- const hash = stableHash(args.datum);
482
- return hash === "" ? String(args.index) : hash;
483
- }
484
561
  //#endregion
485
- export { OproOptimizer, buildOproPrompt };
562
+ export { OproOptimizer, buildOproPrompt, isCandidateAccepted, isRunFinished };
@@ -1,4 +1,4 @@
1
- import { d as UsageTotals, i as EvaluationBatch, n as Candidate, o as EvaluationPhase, s as EvaluationSplit, t as Adapter } from "./types-CWv4IQFF.cjs";
1
+ import { c as Adapter, d as EvaluationBatch, l as Candidate, m as EvaluationSplit, p as EvaluationPhase, v as UsageTotals } from "./reporting-DQbAohc9.cjs";
2
2
  import { n as EvaluationCache, t as CachedScore } from "./cache-CuSo0NJ8.cjs";
3
3
  //#region src/budget.d.ts
4
4
  interface Budget {
@@ -106,8 +106,24 @@ interface Evaluator<Datum, Trajectory, Output, K extends string> {
106
106
  cacheHits(): number;
107
107
  /** Rollouts made with `charge: false`, tracked apart from the budget. */
108
108
  unchargedCalls(): number;
109
- /** Tokens and money the run has spent, as far as adapters have reported it. */
109
+ /**
110
+ * Tokens and money the search has spent, as far as adapters have reported
111
+ * it. `maxCostUsd` is checked against this, so what the ceiling does not
112
+ * bound is not counted in it: rollouts made with `charge: false` are in
113
+ * `unchargedUsage` instead.
114
+ */
110
115
  usage(): UsageTotals;
116
+ /**
117
+ * What the rollouts made with `charge: false` cost. Held-out measurement is
118
+ * taken after the search has stopped, so no ceiling bounds it and reporting
119
+ * it inside `usage` would describe a run as having overrun one.
120
+ */
121
+ unchargedUsage(): UsageTotals;
122
+ /**
123
+ * Folds in usage spent outside this evaluator — harvesting runs its own, and
124
+ * a cost ceiling that cannot see it bounds only part of the run.
125
+ */
126
+ absorbUsage(spent: UsageTotals): void;
111
127
  /** Cache contents for checkpointing, when the cache can enumerate them. */
112
128
  entries(): [string, CachedScore][] | undefined;
113
129
  restore(entries: Iterable<readonly [string, CachedScore]>): void;
@@ -140,6 +156,12 @@ declare function createEvaluator<Datum, Trajectory, Output, K extends string>(ar
140
156
  signal?: AbortSignal;
141
157
  /** Resumed counters, so a continued run reports totals rather than deltas. */
142
158
  cacheHits?: number;
159
+ /**
160
+ * Usage the interrupted run had already spent. Without it `maxCostUsd` is a
161
+ * ceiling on the segment rather than on the run, and resuming repeatedly
162
+ * spends it again each time.
163
+ */
164
+ usage?: UsageTotals;
143
165
  /**
144
166
  * Rate limits and 5xx responses are the common case in a long run, and a
145
167
  * transient row costs the instance whichever optimizer is driving: it is
@@ -252,8 +274,25 @@ interface OptimizerResult<K extends string, Stop extends string, Output = unknow
252
274
  bestOutputs?: (Output | undefined)[];
253
275
  metricCalls: number;
254
276
  /**
255
- * Tokens and dollars the run spent, summed from what the adapter reported.
256
- * Zero throughout when the adapter reports no usage.
277
+ * Rollouts served from the cache rather than charged to `metricCalls`. Every
278
+ * optimizer here caches by default, so the same `maxMetricCalls` can buy a
279
+ * search that revisits scored candidates a longer effective run than one
280
+ * that never does — a comparison over `metricCalls` alone hides that. Zero
281
+ * for a run with caching disabled.
282
+ */
283
+ cacheHits: number;
284
+ /**
285
+ * Calls made to a proposal or reflection model, which no metric budget
286
+ * covers — see each optimizer's own accounting for what a call costs there.
287
+ * Absent from a search that proposes nothing of its own: bootstrap search
288
+ * only accepts or rejects rollouts the metric already scored.
289
+ */
290
+ reflectionCalls?: number;
291
+ /**
292
+ * Tokens and dollars the search spent, summed from what the adapter reported.
293
+ * Zero throughout when the adapter reports no usage. `maxCostUsd` is checked
294
+ * against this, so the held-out sweep — which runs after the search has
295
+ * stopped, under no ceiling — is reported apart from it, in `testUsage`.
257
296
  */
258
297
  usage: UsageTotals;
259
298
  /**
@@ -267,6 +306,13 @@ interface OptimizerResult<K extends string, Stop extends string, Output = unknow
267
306
  * measurement rather than search, and so is not charged to `maxMetricCalls`.
268
307
  */
269
308
  testMetricCalls?: number;
309
+ /**
310
+ * Tokens and dollars the held-out sweep cost, for the same reason: no ceiling
311
+ * bounds it, so a caller adding up what a run spent has to see it as its own
312
+ * number rather than find it folded into one `maxCostUsd` was supposed to
313
+ * hold. Absent when no testSet was given.
314
+ */
315
+ testUsage?: UsageTotals;
270
316
  stopReason: Stop;
271
317
  }
272
318
  /**
@@ -1,4 +1,4 @@
1
- import { d as UsageTotals, i as EvaluationBatch, n as Candidate, o as EvaluationPhase, s as EvaluationSplit, t as Adapter } from "./types-CWv4IQFF.mjs";
1
+ import { c as Adapter, d as EvaluationBatch, l as Candidate, m as EvaluationSplit, p as EvaluationPhase, v as UsageTotals } from "./reporting-DQbAohc9.mjs";
2
2
  import { n as EvaluationCache, t as CachedScore } from "./cache-CuSo0NJ8.mjs";
3
3
  //#region src/budget.d.ts
4
4
  interface Budget {
@@ -106,8 +106,24 @@ interface Evaluator<Datum, Trajectory, Output, K extends string> {
106
106
  cacheHits(): number;
107
107
  /** Rollouts made with `charge: false`, tracked apart from the budget. */
108
108
  unchargedCalls(): number;
109
- /** Tokens and money the run has spent, as far as adapters have reported it. */
109
+ /**
110
+ * Tokens and money the search has spent, as far as adapters have reported
111
+ * it. `maxCostUsd` is checked against this, so what the ceiling does not
112
+ * bound is not counted in it: rollouts made with `charge: false` are in
113
+ * `unchargedUsage` instead.
114
+ */
110
115
  usage(): UsageTotals;
116
+ /**
117
+ * What the rollouts made with `charge: false` cost. Held-out measurement is
118
+ * taken after the search has stopped, so no ceiling bounds it and reporting
119
+ * it inside `usage` would describe a run as having overrun one.
120
+ */
121
+ unchargedUsage(): UsageTotals;
122
+ /**
123
+ * Folds in usage spent outside this evaluator — harvesting runs its own, and
124
+ * a cost ceiling that cannot see it bounds only part of the run.
125
+ */
126
+ absorbUsage(spent: UsageTotals): void;
111
127
  /** Cache contents for checkpointing, when the cache can enumerate them. */
112
128
  entries(): [string, CachedScore][] | undefined;
113
129
  restore(entries: Iterable<readonly [string, CachedScore]>): void;
@@ -140,6 +156,12 @@ declare function createEvaluator<Datum, Trajectory, Output, K extends string>(ar
140
156
  signal?: AbortSignal;
141
157
  /** Resumed counters, so a continued run reports totals rather than deltas. */
142
158
  cacheHits?: number;
159
+ /**
160
+ * Usage the interrupted run had already spent. Without it `maxCostUsd` is a
161
+ * ceiling on the segment rather than on the run, and resuming repeatedly
162
+ * spends it again each time.
163
+ */
164
+ usage?: UsageTotals;
143
165
  /**
144
166
  * Rate limits and 5xx responses are the common case in a long run, and a
145
167
  * transient row costs the instance whichever optimizer is driving: it is
@@ -252,8 +274,25 @@ interface OptimizerResult<K extends string, Stop extends string, Output = unknow
252
274
  bestOutputs?: (Output | undefined)[];
253
275
  metricCalls: number;
254
276
  /**
255
- * Tokens and dollars the run spent, summed from what the adapter reported.
256
- * Zero throughout when the adapter reports no usage.
277
+ * Rollouts served from the cache rather than charged to `metricCalls`. Every
278
+ * optimizer here caches by default, so the same `maxMetricCalls` can buy a
279
+ * search that revisits scored candidates a longer effective run than one
280
+ * that never does — a comparison over `metricCalls` alone hides that. Zero
281
+ * for a run with caching disabled.
282
+ */
283
+ cacheHits: number;
284
+ /**
285
+ * Calls made to a proposal or reflection model, which no metric budget
286
+ * covers — see each optimizer's own accounting for what a call costs there.
287
+ * Absent from a search that proposes nothing of its own: bootstrap search
288
+ * only accepts or rejects rollouts the metric already scored.
289
+ */
290
+ reflectionCalls?: number;
291
+ /**
292
+ * Tokens and dollars the search spent, summed from what the adapter reported.
293
+ * Zero throughout when the adapter reports no usage. `maxCostUsd` is checked
294
+ * against this, so the held-out sweep — which runs after the search has
295
+ * stopped, under no ceiling — is reported apart from it, in `testUsage`.
257
296
  */
258
297
  usage: UsageTotals;
259
298
  /**
@@ -267,6 +306,13 @@ interface OptimizerResult<K extends string, Stop extends string, Output = unknow
267
306
  * measurement rather than search, and so is not charged to `maxMetricCalls`.
268
307
  */
269
308
  testMetricCalls?: number;
309
+ /**
310
+ * Tokens and dollars the held-out sweep cost, for the same reason: no ceiling
311
+ * bounds it, so a caller adding up what a run spent has to see it as its own
312
+ * number rather than find it folded into one `maxCostUsd` was supposed to
313
+ * hold. Absent when no testSet was given.
314
+ */
315
+ testUsage?: UsageTotals;
270
316
  stopReason: Stop;
271
317
  }
272
318
  /**