@tangle-network/agent-eval 0.122.8 → 0.123.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 (29) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/analyst/index.js +4 -4
  3. package/dist/benchmarks/index.js +5 -5
  4. package/dist/campaign/index.d.ts +140 -11
  5. package/dist/campaign/index.js +5 -5
  6. package/dist/{chunk-ULOKLHIQ.js → chunk-A5S77LSE.js} +3 -3
  7. package/dist/{chunk-MUS5DOH7.js → chunk-FC5NDO3E.js} +2 -2
  8. package/dist/{chunk-MI2H23RV.js → chunk-G2GPNLSX.js} +2 -2
  9. package/dist/{chunk-ZDAKRZG5.js → chunk-GS3FJGUF.js} +25 -6
  10. package/dist/{chunk-ZDAKRZG5.js.map → chunk-GS3FJGUF.js.map} +1 -1
  11. package/dist/{chunk-S6IL3R5I.js → chunk-HZJF4IUO.js} +72 -33
  12. package/dist/{chunk-S6IL3R5I.js.map → chunk-HZJF4IUO.js.map} +1 -1
  13. package/dist/{chunk-PXD6ZFNY.js → chunk-IR3KBHOY.js} +19 -1
  14. package/dist/{chunk-PXD6ZFNY.js.map → chunk-IR3KBHOY.js.map} +1 -1
  15. package/dist/{chunk-WW2A73HW.js → chunk-NUKSVU3W.js} +2 -2
  16. package/dist/{chunk-BVMWQDC5.js → chunk-VJ7T5WIO.js} +3 -3
  17. package/dist/contract/index.d.ts +62 -11
  18. package/dist/contract/index.js +36 -11
  19. package/dist/contract/index.js.map +1 -1
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.js +8 -8
  22. package/dist/openapi.json +1 -1
  23. package/dist/traces.js +3 -3
  24. package/package.json +1 -1
  25. /package/dist/{chunk-ULOKLHIQ.js.map → chunk-A5S77LSE.js.map} +0 -0
  26. /package/dist/{chunk-MUS5DOH7.js.map → chunk-FC5NDO3E.js.map} +0 -0
  27. /package/dist/{chunk-MI2H23RV.js.map → chunk-G2GPNLSX.js.map} +0 -0
  28. /package/dist/{chunk-WW2A73HW.js.map → chunk-NUKSVU3W.js.map} +0 -0
  29. /package/dist/{chunk-BVMWQDC5.js.map → chunk-VJ7T5WIO.js.map} +0 -0
@@ -2386,6 +2386,15 @@ type RunImprovementLoopOptions<TScenario extends Scenario, TArtifact> = RunOptim
2386
2386
  /** Holdout scenarios kept OUT of the training optimization pool — used
2387
2387
  * ONLY to score baseline vs winner for the gate. */
2388
2388
  holdoutScenarios: TScenario[];
2389
+ /** Holdout policy. Default `'measured'`: baseline + winner are re-scored on
2390
+ * `holdoutScenarios` and the gate decides on that held-out comparison.
2391
+ * `'deferred'`: the improvement-set (search) campaigns run exactly as usual,
2392
+ * but ZERO holdout cells are dispatched, the gate is forced to `'hold'`, and
2393
+ * the result + provenance record carry `holdout: 'deferred'` with NO
2394
+ * held-out lift — for callers that measure the held-out comparison in a
2395
+ * separate later run instead of faking a static holdout scenario and
2396
+ * recording a meaningless lift. */
2397
+ holdout?: 'measured' | 'deferred';
2389
2398
  /** Promotion gate. Substrate strongly recommends `defaultProductionGate`
2390
2399
  * for production wiring (composes red-team / reward-hacking / canary /
2391
2400
  * heldout). */
@@ -2413,6 +2422,10 @@ interface RunImprovementLoopResult<TArtifact, TScenario extends Scenario> extend
2413
2422
  neutralizedOnHoldout?: CampaignResult<TArtifact, TScenario>;
2414
2423
  neutralizedSurface?: MutableSurface;
2415
2424
  gateResult: Awaited<ReturnType<Gate<TArtifact, TScenario>['decide']>>;
2425
+ /** Present iff the loop ran with `holdout: 'deferred'`. When set,
2426
+ * `baselineOnHoldout`/`winnerOnHoldout` are the shared EMPTY campaign (zero
2427
+ * cells dispatched) and the gate verdict is the forced `'hold'`. */
2428
+ holdout?: 'deferred';
2416
2429
  /** Unified baseline→winner surface diff. Computed UNCONDITIONALLY (not only
2417
2430
  * when `autoOnPromote === 'pr'`) so the diff that the gate decided on is
2418
2431
  * always present on the result + in the emitted provenance record. Empty
@@ -3715,12 +3728,18 @@ interface LoopProvenanceRecord {
3715
3728
  detail: unknown;
3716
3729
  }>;
3717
3730
  };
3718
- /** baseline-on-holdout composite mean. */
3719
- baselineHoldoutComposite: number;
3720
- /** winner-on-holdout composite mean. */
3721
- winnerHoldoutComposite: number;
3722
- /** winnerHoldout - baselineHoldout — RECOMPUTABLE from this record. */
3723
- heldOutLift: number;
3731
+ /** Present iff the loop ran with `holdout: 'deferred'` the held-out
3732
+ * comparison was intentionally not measured in this run, so the holdout
3733
+ * composites and `heldOutLift` are ABSENT rather than recorded as a
3734
+ * meaningless 0. */
3735
+ holdout?: 'deferred';
3736
+ /** baseline-on-holdout composite mean. Absent when `holdout === 'deferred'`. */
3737
+ baselineHoldoutComposite?: number;
3738
+ /** winner-on-holdout composite mean. Absent when `holdout === 'deferred'`. */
3739
+ winnerHoldoutComposite?: number;
3740
+ /** winnerHoldout - baselineHoldout — RECOMPUTABLE from this record. Absent
3741
+ * when `holdout === 'deferred'` (no held-out measurement ran). */
3742
+ heldOutLift?: number;
3724
3743
  /** Backend provenance: stub-vs-real verdict + worker call count + models. */
3725
3744
  backend: LoopProvenanceBackend;
3726
3745
  totalCostUsd: number;
@@ -3767,8 +3786,22 @@ interface SelfImproveBudget {
3767
3786
  holdoutFraction?: number;
3768
3787
  /** Explicit held-out scenarios; overrides `holdoutFraction`. */
3769
3788
  holdoutScenarios?: Scenario[];
3789
+ /** Holdout policy. Default `'measured'`: split, re-score baseline vs winner
3790
+ * on the held-out set, gate on that comparison. `'deferred'`: run the
3791
+ * improvement-set campaigns + search promotion, dispatch ZERO holdout cells,
3792
+ * force the gate to `'hold'`, return `lift: undefined`, and record
3793
+ * `holdout: 'deferred'` in the provenance record — for callers that measure
3794
+ * the held-out comparison in a separate later run instead of faking a
3795
+ * static holdout scenario and recording a meaningless lift. Unless
3796
+ * `holdoutScenarios` reserves an explicit set, ALL scenarios train. */
3797
+ holdout?: 'measured' | 'deferred';
3770
3798
  /** Per-scenario replicates per cell — raises bootstrap-CI tightness. Default 1. */
3771
3799
  reps?: number;
3800
+ /** DEPTH dial forwarded to the proposer's `propose()` as
3801
+ * `ctx.maxImprovementShots` — max iterations an agentic candidate generator
3802
+ * may take per candidate (verify-in-session retries). Unset ⇒ the
3803
+ * proposer's own default. */
3804
+ maxImprovementShots?: number;
3772
3805
  /** @deprecated Must be 1 when supplied. The loop promotes only a candidate
3773
3806
  * that replaces its single global incumbent. */
3774
3807
  promoteTopK?: number;
@@ -3801,7 +3834,7 @@ type SelfImproveProgressEvent = {
3801
3834
  } | {
3802
3835
  kind: 'gate.decided';
3803
3836
  decision: string;
3804
- lift: number;
3837
+ lift?: number;
3805
3838
  } | {
3806
3839
  kind: 'power.estimated';
3807
3840
  n: number;
@@ -3835,6 +3868,18 @@ interface SelfImproveOptions<TScenario extends Scenario, TArtifact> {
3835
3868
  baselineSurface: MutableSurface;
3836
3869
  /** Budget + loop shape. All fields optional. */
3837
3870
  budget?: SelfImproveBudget;
3871
+ /**
3872
+ * Complete prior measurement of `baselineSurface` over the TRAIN split.
3873
+ * Forwarded to the loop body, which validates its surface hash, scenario
3874
+ * split, seed (42), reps, and coverage, then skips the baseline search
3875
+ * campaign entirely — no baseline dispatch, no resumability lookup. The
3876
+ * train split is `scenarios` minus the holdout split, so premeasure with
3877
+ * exactly that scenario set (explicit `budget.holdoutScenarios`, or
3878
+ * `budget.holdout: 'deferred'` with no reserved set, makes the train split
3879
+ * deterministic). Prior spend stays in the imported campaign aggregates and
3880
+ * is not re-added to this run's cost ledger.
3881
+ */
3882
+ premeasuredBaseline?: PremeasuredOptimizationBaseline<TArtifact, TScenario>;
3838
3883
  /** Custom surface proposer. Default is `gepaProposer` configured from `llm` +
3839
3884
  * `mutationPrimitives`. */
3840
3885
  proposer?: SurfaceProposer;
@@ -3934,12 +3979,16 @@ interface SelfImproveOptions<TScenario extends Scenario, TArtifact> {
3934
3979
  findings?: unknown[];
3935
3980
  }
3936
3981
  interface SelfImproveResult<TScenario extends Scenario, TArtifact> {
3937
- /** Composite mean across all scenarios, baseline run. */
3982
+ /** Composite mean across all scenarios, baseline run. When
3983
+ * `budget.holdout === 'deferred'` this is measured on the improvement
3984
+ * (search) split — no holdout campaign ran. */
3938
3985
  baseline: {
3939
3986
  compositeMean: number;
3940
3987
  perScenario: Record<string, number>;
3941
3988
  };
3942
- /** Composite mean on the held-out set, winner run. */
3989
+ /** Composite mean on the held-out set, winner run. When
3990
+ * `budget.holdout === 'deferred'` this is the winner's improvement-set
3991
+ * (search) measurement — no holdout campaign ran. */
3943
3992
  winner: {
3944
3993
  compositeMean: number;
3945
3994
  perScenario: Record<string, number>;
@@ -3953,8 +4002,10 @@ interface SelfImproveResult<TScenario extends Scenario, TArtifact> {
3953
4002
  rationale?: string;
3954
4003
  };
3955
4004
  /** `winner.compositeMean - baselineOnHoldout.compositeMean`. Positive
3956
- * means the gate observed improvement. */
3957
- lift: number;
4005
+ * means the gate observed improvement. Absent iff
4006
+ * `budget.holdout === 'deferred'` — no held-out measurement ran, so there
4007
+ * is no lift to report (never a fabricated 0). */
4008
+ lift?: number;
3958
4009
  /** The explicit baseline→winner unified diff. Always present (empty string
3959
4010
  * when winner == baseline). */
3960
4011
  diff: string;
@@ -36,7 +36,7 @@ import {
36
36
  runReferenceEquivalenceJudge,
37
37
  surfaceContentHash,
38
38
  surfaceHash
39
- } from "../chunk-S6IL3R5I.js";
39
+ } from "../chunk-HZJF4IUO.js";
40
40
  import {
41
41
  campaignSplitDigest,
42
42
  createRunCostLedger,
@@ -48,9 +48,9 @@ import {
48
48
  import {
49
49
  buildDefaultAnalystRegistry,
50
50
  createChatClient
51
- } from "../chunk-BVMWQDC5.js";
51
+ } from "../chunk-VJ7T5WIO.js";
52
52
  import "../chunk-HHWE3POT.js";
53
- import "../chunk-MUS5DOH7.js";
53
+ import "../chunk-FC5NDO3E.js";
54
54
  import {
55
55
  FileSystemOutcomeStore,
56
56
  InMemoryOutcomeStore
@@ -70,7 +70,7 @@ import {
70
70
  import "../chunk-PXE2VKMX.js";
71
71
  import "../chunk-ZET2UAYW.js";
72
72
  import "../chunk-GGE4NNQT.js";
73
- import "../chunk-PXD6ZFNY.js";
73
+ import "../chunk-IR3KBHOY.js";
74
74
  import "../chunk-PC4UYEBM.js";
75
75
  import {
76
76
  parseRunRecordSafe
@@ -125,6 +125,15 @@ function meanComposite(byScenario) {
125
125
  perScenario
126
126
  };
127
127
  }
128
+ function winnerSearchCampaign(result) {
129
+ for (let i = result.generations.length - 1; i >= 0; i--) {
130
+ const measured = result.generations[i]?.surfaces.find(
131
+ (s) => s.surfaceHash === result.winnerSurfaceHash
132
+ );
133
+ if (measured) return measured.campaign;
134
+ }
135
+ return result.baselineCampaign;
136
+ }
128
137
  async function selfImprove(opts) {
129
138
  const startedAt = Date.now();
130
139
  const requestedRunDir = opts.runDir ?? `mem://selfImprove-${startedAt}`;
@@ -147,18 +156,20 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
147
156
  const populationSize = budget.populationSize ?? 2;
148
157
  const maxConcurrency = budget.maxConcurrency ?? 2;
149
158
  const holdoutFraction = budget.holdoutFraction ?? 0.25;
159
+ const holdoutMode = budget.holdout ?? "measured";
160
+ const holdoutDeferred = holdoutMode === "deferred";
150
161
  const expectUsage = opts.expectUsage ?? "assert";
151
162
  const explicitHoldout = budget.holdoutScenarios;
152
163
  const { train, holdout } = explicitHoldout ? {
153
164
  train: opts.scenarios.filter((s) => !explicitHoldout.some((h) => h.id === s.id)),
154
165
  holdout: explicitHoldout
155
- } : splitTrainHoldout(opts.scenarios, holdoutFraction);
166
+ } : holdoutDeferred ? { train: opts.scenarios, holdout: [] } : splitTrainHoldout(opts.scenarios, holdoutFraction);
156
167
  if (train.length === 0) {
157
168
  throw new Error(
158
169
  "selfImprove: train split is empty. Reduce holdoutFraction or pass more scenarios."
159
170
  );
160
171
  }
161
- if (holdout.length === 0) {
172
+ if (holdout.length === 0 && !holdoutDeferred) {
162
173
  throw new Error("selfImprove: holdout split is empty. Pass more scenarios.");
163
174
  }
164
175
  const proposer = opts.proposer ?? gepaProposer({
@@ -182,6 +193,7 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
182
193
  const result = await runImprovementLoop({
183
194
  scenarios: train,
184
195
  baselineSurface: opts.baselineSurface,
196
+ premeasuredBaseline: opts.premeasuredBaseline,
185
197
  dispatchWithSurface: opts.agent,
186
198
  proposer,
187
199
  judges: [opts.judge],
@@ -189,7 +201,9 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
189
201
  maxGenerations: generations,
190
202
  promoteTopK: budget.promoteTopK,
191
203
  reps: budget.reps,
204
+ maxImprovementShots: budget.maxImprovementShots,
192
205
  holdoutScenarios: holdout,
206
+ holdout: holdoutMode,
193
207
  gate,
194
208
  neutralize: opts.neutralize,
195
209
  autoOnPromote: opts.autoOnPromote ?? "none",
@@ -207,8 +221,11 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
207
221
  analyzeGeneration: opts.analyzeGeneration,
208
222
  findings: opts.findings
209
223
  });
210
- const baseline = meanComposite(result.baselineOnHoldout.aggregates.byScenario);
211
- const winnerStats = meanComposite(result.winnerOnHoldout.aggregates.byScenario);
224
+ const winnerSearch = holdoutDeferred ? winnerSearchCampaign(result) : void 0;
225
+ const baseline = meanComposite(
226
+ (holdoutDeferred ? result.baselineCampaign : result.baselineOnHoldout).aggregates.byScenario
227
+ );
228
+ const winnerStats = meanComposite((winnerSearch ?? result.winnerOnHoldout).aggregates.byScenario);
212
229
  let power;
213
230
  const baselineHoldoutComposites = result.baselineOnHoldout.cells.filter((cell) => !cell.error).map((cell) => {
214
231
  const scores = Object.values(cell.judgeScores);
@@ -241,7 +258,10 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
241
258
  opts.onProgress({
242
259
  kind: "gate.decided",
243
260
  decision: result.gateResult.decision,
244
- lift: winnerStats.compositeMean - baseline.compositeMean
261
+ // Deferred holdout has no held-out measurement: in that mode the summary
262
+ // stats are search-split numbers, and emitting their delta as `lift`
263
+ // would misreport a train-split delta as a held-out one. Omit instead.
264
+ ...holdoutDeferred ? {} : { lift: winnerStats.compositeMean - baseline.compositeMean }
245
265
  });
246
266
  }
247
267
  const cost = result.cost;
@@ -249,7 +269,12 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
249
269
  const insight = await analyzeRuns({
250
270
  runs: [
251
271
  ...cellsToRunRecords(result.baselineCampaign.cells, "baseline", runDir, opts.baselineSurface),
252
- ...cellsToRunRecords(result.winnerOnHoldout.cells, "winner", runDir, result.winnerSurface)
272
+ ...cellsToRunRecords(
273
+ (winnerSearch ?? result.winnerOnHoldout).cells,
274
+ "winner",
275
+ runDir,
276
+ result.winnerSurface
277
+ )
253
278
  ],
254
279
  baselineCandidateId: "baseline",
255
280
  candidateCandidateId: "winner"
@@ -278,7 +303,7 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
278
303
  ...result.winnerLabel ? { label: result.winnerLabel } : {},
279
304
  ...result.winnerRationale ? { rationale: result.winnerRationale } : {}
280
305
  },
281
- lift: winnerStats.compositeMean - baseline.compositeMean,
306
+ ...holdoutDeferred ? {} : { lift: winnerStats.compositeMean - baseline.compositeMean },
282
307
  diff: result.promotedDiff,
283
308
  provenance,
284
309
  gateDecision: result.gateResult.decision,