@tangle-network/agent-eval 0.115.3 → 0.117.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 (134) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/analyst/index.d.ts +16 -11
  3. package/dist/analyst/index.js +33 -25
  4. package/dist/analyst/index.js.map +1 -1
  5. package/dist/{analyze-runs-BYHg6Irm.d.ts → analyze-runs--2x39HZ7.d.ts} +3 -3
  6. package/dist/{baseline-DsNteOgR.d.ts → baseline-DKq3gJpP.d.ts} +6 -3
  7. package/dist/belief-state/index.d.ts +6 -6
  8. package/dist/belief-state/index.js +1 -1
  9. package/dist/benchmarks/index.d.ts +12 -5
  10. package/dist/benchmarks/index.js +11 -10
  11. package/dist/builder-eval/index.d.ts +4 -4
  12. package/dist/builder-eval/index.js +1 -1
  13. package/dist/{calibration-Dz8TQV4y.d.ts → calibration-C8MTS7cw.d.ts} +2 -2
  14. package/dist/campaign/index.d.ts +247 -34
  15. package/dist/campaign/index.js +33 -13
  16. package/dist/chunk-3YYRZDON.js +45 -0
  17. package/dist/chunk-3YYRZDON.js.map +1 -0
  18. package/dist/{chunk-RPDDVKI7.js → chunk-4JLWXDYA.js} +2 -2
  19. package/dist/{chunk-WSBUZMBU.js → chunk-CCZIVI3F.js} +54 -115
  20. package/dist/chunk-CCZIVI3F.js.map +1 -0
  21. package/dist/{chunk-J6P6PK2R.js → chunk-FQNLDL4D.js} +3 -3
  22. package/dist/{chunk-ONM6PEAE.js → chunk-GQCZRZ7L.js} +2 -2
  23. package/dist/chunk-HHWE3POT.js +94 -0
  24. package/dist/chunk-HHWE3POT.js.map +1 -0
  25. package/dist/{chunk-ADYLPOSX.js → chunk-HQPHZGL6.js} +1112 -135
  26. package/dist/chunk-HQPHZGL6.js.map +1 -0
  27. package/dist/{chunk-FAOEFFRT.js → chunk-IDZTTFRR.js} +390 -78
  28. package/dist/chunk-IDZTTFRR.js.map +1 -0
  29. package/dist/{chunk-3LXTCTWL.js → chunk-JSDVRFAP.js} +2 -2
  30. package/dist/{chunk-MHNQWM4I.js → chunk-LQUTGLOZ.js} +5 -1
  31. package/dist/chunk-LQUTGLOZ.js.map +1 -0
  32. package/dist/{chunk-4D5RVB3W.js → chunk-LTVG32KX.js} +30 -5
  33. package/dist/chunk-LTVG32KX.js.map +1 -0
  34. package/dist/{chunk-5S5NJ63F.js → chunk-MGEHEHSN.js} +807 -15
  35. package/dist/chunk-MGEHEHSN.js.map +1 -0
  36. package/dist/{chunk-GY4SYVPJ.js → chunk-NJC7U437.js} +97 -25
  37. package/dist/chunk-NJC7U437.js.map +1 -0
  38. package/dist/{chunk-NYFUT3B3.js → chunk-ODVOOEWQ.js} +31 -10
  39. package/dist/chunk-ODVOOEWQ.js.map +1 -0
  40. package/dist/{chunk-LNQEP766.js → chunk-S2F4J57L.js} +44 -4
  41. package/dist/chunk-S2F4J57L.js.map +1 -0
  42. package/dist/chunk-VCTY3W6J.js +798 -0
  43. package/dist/chunk-VCTY3W6J.js.map +1 -0
  44. package/dist/chunk-VF3XSYTI.js +545 -0
  45. package/dist/chunk-VF3XSYTI.js.map +1 -0
  46. package/dist/{chunk-TLDB7WRY.js → chunk-YZPO4UHR.js} +28 -31
  47. package/dist/chunk-YZPO4UHR.js.map +1 -0
  48. package/dist/{chunk-KG4TD7EQ.js → chunk-ZUXV7UWZ.js} +1425 -697
  49. package/dist/chunk-ZUXV7UWZ.js.map +1 -0
  50. package/dist/cli.js +4 -2
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{code-agent-session-D-g04tcy.d.ts → code-agent-session-CjZsVd19.d.ts} +1 -1
  53. package/dist/contract/index.d.ts +45 -31
  54. package/dist/contract/index.js +58 -19
  55. package/dist/contract/index.js.map +1 -1
  56. package/dist/{control-CcBiAEnn.d.ts → control-6vuGfmDH.d.ts} +5 -5
  57. package/dist/control.d.ts +6 -6
  58. package/dist/cost-ledger-DWy3XdJc.d.ts +183 -0
  59. package/dist/{default-registry-DltpYR5u.d.ts → default-registry-DaK8b3fv.d.ts} +2 -1
  60. package/dist/{emitter-BRchAAAx.d.ts → emitter-CjD7vUwv.d.ts} +2 -2
  61. package/dist/{failure-cluster-C48PiReX.d.ts → failure-cluster-DOAcSJ87.d.ts} +2 -2
  62. package/dist/{feedback-trajectory-pDcz1lQ1.d.ts → feedback-trajectory-BUnM58xL.d.ts} +3 -3
  63. package/dist/fuzz.d.ts +8 -16
  64. package/dist/fuzz.js +72 -42
  65. package/dist/fuzz.js.map +1 -1
  66. package/dist/{gepa-dne9JDPL.d.ts → gepa-eESocoDi.d.ts} +64 -12
  67. package/dist/hosted/index.d.ts +14 -7
  68. package/dist/{index-BTEpx9He.d.ts → index-PdX4VnPA.d.ts} +3 -3
  69. package/dist/index.d.ts +97 -55
  70. package/dist/index.js +343 -244
  71. package/dist/index.js.map +1 -1
  72. package/dist/{insight-report-IwwvqZZv.d.ts → insight-report-DY4nDW9Q.d.ts} +1 -1
  73. package/dist/{integrity-qemeBAyx.d.ts → integrity-DqlBiLyK.d.ts} +1 -1
  74. package/dist/kind-factory-ClZmO25A.d.ts +171 -0
  75. package/dist/{llm-client-DyqEH4jH.d.ts → llm-client-qoDd18Qz.d.ts} +27 -3
  76. package/dist/meta-eval/index.d.ts +8 -7
  77. package/dist/meta-eval/index.js +1 -1
  78. package/dist/multishot/index.d.ts +10 -3
  79. package/dist/openapi.json +1 -1
  80. package/dist/pipelines/index.d.ts +16 -6
  81. package/dist/pipelines/index.js +119 -23
  82. package/dist/pipelines/index.js.map +1 -1
  83. package/dist/{kind-factory-DcNg13sZ.d.ts → policy-edit-wG9uFEFm.d.ts} +114 -167
  84. package/dist/{pre-registration-D8h7ZxNL.d.ts → pre-registration-BWQhJ3vz.d.ts} +23 -4
  85. package/dist/{provenance-Bibyg1U9.d.ts → provenance-DpjwyseI.d.ts} +28 -16
  86. package/dist/{query-Ck190MOd.d.ts → query-CF7PG61p.d.ts} +5 -3
  87. package/dist/{release-report-CCtzajxP.d.ts → release-report-C8G2i5Xi.d.ts} +2 -2
  88. package/dist/reporting.d.ts +10 -9
  89. package/dist/{researcher-Dq-EtpbE.d.ts → researcher-C8XyxQsu.d.ts} +7 -7
  90. package/dist/rl.d.ts +17 -12
  91. package/dist/rl.js +2 -2
  92. package/dist/{rubric-predictive-validity-DYTLjGWu.d.ts → rubric-predictive-validity-p49lLVrE.d.ts} +1 -1
  93. package/dist/{run-campaign-UADIM77S.js → run-campaign-IM26A6PD.js} +4 -2
  94. package/dist/{run-record-B7RTi_ix.d.ts → run-record-BDH49H2E.d.ts} +2 -2
  95. package/dist/{runtime-trajectory-Dws7Kpgi.d.ts → runtime-trajectory-DGBIUt4B.d.ts} +1 -1
  96. package/dist/{schema-SGWcK9wa.d.ts → schema-B3Q3l9Z_.d.ts} +2 -0
  97. package/dist/{semantic-concept-judge-DxJmRkyJ.d.ts → semantic-concept-judge-CXnPEJbf.d.ts} +23 -5
  98. package/dist/{statistics-oUbOJe-S.d.ts → statistics-KUnG73jH.d.ts} +1 -1
  99. package/dist/{storage-Dw_f7WMt.d.ts → storage-DrX3v_5B.d.ts} +12 -1
  100. package/dist/{store-BsVi7ncX.d.ts → store-DGqD0Pyo.d.ts} +1 -1
  101. package/dist/storyboard/index.d.ts +1 -1
  102. package/dist/{summary-report-BJ5aNwZ1.d.ts → summary-report-C5bKFfm-.d.ts} +2 -2
  103. package/dist/{test-graded-scenario-mzYBKspu.d.ts → test-graded-scenario-B0ybnPY7.d.ts} +3 -3
  104. package/dist/traces.d.ts +19 -10
  105. package/dist/traces.js +16 -4
  106. package/dist/{types-C5gJrOVT.d.ts → types-BSw1rOUB.d.ts} +97 -38
  107. package/dist/{types-C7DGg5ex.d.ts → types-BkfcQnxV.d.ts} +15 -0
  108. package/dist/wire/index.d.ts +28 -19
  109. package/dist/wire/index.js +4 -2
  110. package/docs/design/loop-taxonomy.md +1 -2
  111. package/docs/distributed-driver.md +1 -1
  112. package/package.json +3 -3
  113. package/dist/chunk-4D5RVB3W.js.map +0 -1
  114. package/dist/chunk-5S5NJ63F.js.map +0 -1
  115. package/dist/chunk-ADYLPOSX.js.map +0 -1
  116. package/dist/chunk-FAOEFFRT.js.map +0 -1
  117. package/dist/chunk-GY4SYVPJ.js.map +0 -1
  118. package/dist/chunk-I6LVHOV3.js +0 -205
  119. package/dist/chunk-I6LVHOV3.js.map +0 -1
  120. package/dist/chunk-KG4TD7EQ.js.map +0 -1
  121. package/dist/chunk-LNQEP766.js.map +0 -1
  122. package/dist/chunk-MHNQWM4I.js.map +0 -1
  123. package/dist/chunk-NYFUT3B3.js.map +0 -1
  124. package/dist/chunk-QMXXSNC4.js +0 -761
  125. package/dist/chunk-QMXXSNC4.js.map +0 -1
  126. package/dist/chunk-TLDB7WRY.js.map +0 -1
  127. package/dist/chunk-WSBUZMBU.js.map +0 -1
  128. package/dist/cost-ledger-DuSqlw5B.d.ts +0 -113
  129. package/dist/policy-edit-RLn8GWof.d.ts +0 -103
  130. /package/dist/{chunk-RPDDVKI7.js.map → chunk-4JLWXDYA.js.map} +0 -0
  131. /package/dist/{chunk-J6P6PK2R.js.map → chunk-FQNLDL4D.js.map} +0 -0
  132. /package/dist/{chunk-ONM6PEAE.js.map → chunk-GQCZRZ7L.js.map} +0 -0
  133. /package/dist/{chunk-3LXTCTWL.js.map → chunk-JSDVRFAP.js.map} +0 -0
  134. /package/dist/{run-campaign-UADIM77S.js.map → run-campaign-IM26A6PD.js.map} +0 -0
@@ -2,11 +2,16 @@ import {
2
2
  confidenceInterval
3
3
  } from "./chunk-PJQFMIOX.js";
4
4
  import {
5
- AgentEvalError
5
+ CostAccountingIncompleteError,
6
+ CostLedger
7
+ } from "./chunk-VCTY3W6J.js";
8
+ import {
9
+ AgentEvalError,
10
+ ValidationError
6
11
  } from "./chunk-ONWEPEDO.js";
7
12
 
8
13
  // src/campaign/run-campaign.ts
9
- import { join as join2 } from "path";
14
+ import { join as join3 } from "path";
10
15
 
11
16
  // src/integrity/backend-integrity.ts
12
17
  var BackendIntegrityError = class extends AgentEvalError {
@@ -215,6 +220,7 @@ function cachedJudge(judge, store, options) {
215
220
  const wrapped = {
216
221
  name: judge.name,
217
222
  dimensions: judge.dimensions,
223
+ judgeVersion: options.judgeVersion,
218
224
  async score(input) {
219
225
  const key = contentHash({
220
226
  artifact: canonicalJson(input.artifact),
@@ -246,34 +252,209 @@ function tangleTracesRoot() {
246
252
  return join(homedir(), ".tangle", "traces");
247
253
  }
248
254
  function resolveRunDir(runDir, repo) {
249
- if (isAbsolute(runDir)) return runDir;
255
+ if (isAbsolute(runDir) || runDir.startsWith("mem://")) return runDir;
250
256
  const r = repo && repo.trim().length > 0 ? repo : basename(process.cwd());
251
257
  return join(tangleTracesRoot(), r, "runs", runDir);
252
258
  }
253
259
 
254
260
  // src/campaign/storage.ts
255
261
  import { createRequire } from "module";
262
+ import { join as join2 } from "path";
263
+
264
+ // src/campaign/search-ledger-file.ts
265
+ import { randomUUID } from "crypto";
266
+ import {
267
+ closeSync,
268
+ constants,
269
+ existsSync as existsSync2,
270
+ fsyncSync,
271
+ linkSync,
272
+ mkdirSync,
273
+ openSync,
274
+ readFileSync as readFileSync2,
275
+ renameSync,
276
+ unlinkSync,
277
+ writeSync
278
+ } from "fs";
279
+ import { hostname } from "os";
280
+ import { dirname } from "path";
281
+ import { z } from "zod";
282
+
283
+ // src/campaign/search-ledger-errors.ts
284
+ var SearchLedgerError = class extends ValidationError {
285
+ };
286
+ var SearchLedgerIntegrityError = class extends SearchLedgerError {
287
+ };
288
+ var SearchLedgerConflictError = class extends SearchLedgerError {
289
+ };
290
+
291
+ // src/campaign/search-ledger-file.ts
292
+ function appendSearchLedgerLine(path, line) {
293
+ mkdirSync(dirname(path), { recursive: true });
294
+ const fd = openSync(path, constants.O_CREAT | constants.O_WRONLY | constants.O_APPEND, 384);
295
+ try {
296
+ writeAll(fd, Buffer.from(line, "utf8"));
297
+ fsyncSync(fd);
298
+ } finally {
299
+ closeSync(fd);
300
+ }
301
+ fsyncDirectory(dirname(path));
302
+ }
303
+ function withSearchLedgerFileLock(ledgerPath, run) {
304
+ const result = tryWithSearchLedgerFileLock(ledgerPath, run);
305
+ if (!result.acquired) {
306
+ throw new SearchLedgerIntegrityError(`search ledger lock is held (${ledgerPath})`);
307
+ }
308
+ return result.value;
309
+ }
310
+ function tryWithSearchLedgerFileLock(ledgerPath, run) {
311
+ mkdirSync(dirname(ledgerPath), { recursive: true });
312
+ const lockPath = `${ledgerPath}.lock`;
313
+ const owner = acquireLock(lockPath);
314
+ if (!owner) return { acquired: false };
315
+ try {
316
+ return { acquired: true, value: run() };
317
+ } finally {
318
+ releaseLock(lockPath, owner);
319
+ }
320
+ }
321
+ function writeAll(fd, bytes) {
322
+ let offset = 0;
323
+ while (offset < bytes.byteLength) {
324
+ const written = writeSync(fd, bytes, offset, bytes.byteLength - offset);
325
+ if (written <= 0) throw new SearchLedgerIntegrityError("filesystem wrote zero bytes");
326
+ offset += written;
327
+ }
328
+ }
329
+ function fsyncDirectory(path) {
330
+ const fd = openSync(path, constants.O_RDONLY);
331
+ try {
332
+ fsyncSync(fd);
333
+ } finally {
334
+ closeSync(fd);
335
+ }
336
+ }
337
+ function acquireLock(lockPath) {
338
+ const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
339
+ const ownerBytes = `${canonicalOwner(owner)}
340
+ `;
341
+ for (let attempt = 0; attempt < 8; attempt += 1) {
342
+ const ownerPath = `${lockPath}.${owner.pid}.${owner.nonce}.${attempt}.owner`;
343
+ const fd = openSync(ownerPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
344
+ try {
345
+ writeAll(fd, Buffer.from(ownerBytes, "utf8"));
346
+ fsyncSync(fd);
347
+ } finally {
348
+ closeSync(fd);
349
+ }
350
+ try {
351
+ linkSync(ownerPath, lockPath);
352
+ unlinkSync(ownerPath);
353
+ return owner;
354
+ } catch (error) {
355
+ unlinkIfExists(ownerPath);
356
+ if (error.code !== "EEXIST") throw error;
357
+ }
358
+ const holder = readOwner(lockPath);
359
+ if (holder.host !== owner.host || isProcessAlive(holder.pid)) {
360
+ return void 0;
361
+ }
362
+ const tombstone = `${lockPath}.stale.${owner.nonce}.${attempt}`;
363
+ try {
364
+ renameSync(lockPath, tombstone);
365
+ unlinkSync(tombstone);
366
+ } catch (error) {
367
+ if (error.code !== "ENOENT") throw error;
368
+ }
369
+ }
370
+ throw new SearchLedgerIntegrityError(`could not acquire search ledger lock ${lockPath}`);
371
+ }
372
+ function releaseLock(lockPath, owner) {
373
+ if (!existsSync2(lockPath)) return;
374
+ const holder = readOwner(lockPath);
375
+ if (canonicalOwner(holder) !== canonicalOwner(owner)) {
376
+ throw new SearchLedgerIntegrityError(
377
+ `search ledger lock owner changed before release (${lockPath})`
378
+ );
379
+ }
380
+ unlinkSync(lockPath);
381
+ }
382
+ function readOwner(lockPath) {
383
+ let raw;
384
+ try {
385
+ raw = JSON.parse(readFileSync2(lockPath, "utf8"));
386
+ } catch (error) {
387
+ throw new SearchLedgerIntegrityError(`search ledger lock ${lockPath} is malformed`, {
388
+ cause: error
389
+ });
390
+ }
391
+ const parsed = z.object({
392
+ pid: z.number().int().positive().safe(),
393
+ host: z.string().trim().min(1),
394
+ nonce: z.string().trim().min(1)
395
+ }).strict().safeParse(raw);
396
+ if (!parsed.success) {
397
+ throw new SearchLedgerIntegrityError(
398
+ `search ledger lock ${lockPath} is malformed: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ")}`
399
+ );
400
+ }
401
+ return parsed.data;
402
+ }
403
+ function canonicalOwner(owner) {
404
+ return JSON.stringify({ host: owner.host, nonce: owner.nonce, pid: owner.pid });
405
+ }
406
+ function isProcessAlive(pid) {
407
+ try {
408
+ process.kill(pid, 0);
409
+ return true;
410
+ } catch (error) {
411
+ return error.code !== "ESRCH";
412
+ }
413
+ }
414
+ function unlinkIfExists(path) {
415
+ try {
416
+ unlinkSync(path);
417
+ } catch (error) {
418
+ if (error.code !== "ENOENT") throw error;
419
+ }
420
+ }
421
+
422
+ // src/campaign/storage.ts
256
423
  function fsCampaignStorage() {
257
424
  const nodeRequire = createRequire(import.meta.url);
258
- const { existsSync: existsSync2, mkdirSync, readFileSync: readFileSync2, writeFileSync } = nodeRequire(
425
+ const { existsSync: existsSync3, mkdirSync: mkdirSync2, readFileSync: readFileSync3, statSync, writeFileSync } = nodeRequire(
259
426
  "node:fs"
260
427
  );
261
428
  return {
262
429
  ensureDir(dir) {
263
- if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
430
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
264
431
  },
265
432
  exists(path) {
266
- return existsSync2(path);
433
+ return existsSync3(path);
267
434
  },
268
435
  read(path) {
269
436
  try {
270
- return readFileSync2(path, "utf8");
437
+ return readFileSync3(path, "utf8");
271
438
  } catch {
272
439
  return void 0;
273
440
  }
274
441
  },
275
442
  write(path, content) {
276
443
  writeFileSync(path, content);
444
+ },
445
+ append(path, content, expectedBytes) {
446
+ const result = tryWithSearchLedgerFileLock(path, () => {
447
+ let actualBytes = 0;
448
+ try {
449
+ actualBytes = statSync(path).size;
450
+ } catch (error) {
451
+ if (error.code !== "ENOENT") throw error;
452
+ }
453
+ if (actualBytes !== expectedBytes) return void 0;
454
+ appendSearchLedgerLine(path, content);
455
+ return expectedBytes + Buffer.byteLength(content);
456
+ });
457
+ return result.acquired ? result.value : void 0;
277
458
  }
278
459
  };
279
460
  }
@@ -294,24 +475,72 @@ function inMemoryCampaignStorage() {
294
475
  },
295
476
  write(path, content) {
296
477
  files.set(path, content);
478
+ },
479
+ append(path, content, expectedBytes) {
480
+ const current = files.get(path);
481
+ const currentText = current === void 0 ? "" : typeof current === "string" ? current : new TextDecoder().decode(current);
482
+ const currentBytes = new TextEncoder().encode(currentText).byteLength;
483
+ if (currentBytes !== expectedBytes) return void 0;
484
+ files.set(path, `${currentText}${content}`);
485
+ return currentBytes + new TextEncoder().encode(content).byteLength;
297
486
  }
298
487
  };
299
488
  }
489
+ function createRunCostLedger(input) {
490
+ const path = join2(input.runDir, "cost-ledger.jsonl");
491
+ input.storage.ensureDir(input.runDir);
492
+ return new CostLedger({
493
+ costCeilingUsd: input.costCeilingUsd,
494
+ persistence: {
495
+ read: () => {
496
+ const stored = input.storage.read(path);
497
+ if (stored === void 0 && input.storage.exists(path)) {
498
+ throw new Error(`CostLedger: cannot read existing event log '${path}'`);
499
+ }
500
+ const events = stored ?? "";
501
+ return {
502
+ revision: String(new TextEncoder().encode(events).byteLength),
503
+ events
504
+ };
505
+ },
506
+ append: (expectedRevision, event) => {
507
+ const expectedBytes = Number(expectedRevision);
508
+ if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {
509
+ throw new Error(`CostLedger: invalid storage revision '${expectedRevision}'`);
510
+ }
511
+ if (!input.storage.append) {
512
+ throw new Error("CostLedger: CampaignStorage.append is required for paid calls");
513
+ }
514
+ const next = input.storage.append(path, event, expectedBytes);
515
+ return next === void 0 ? void 0 : String(next);
516
+ }
517
+ }
518
+ });
519
+ }
300
520
 
301
521
  // src/campaign/run-campaign.ts
302
522
  async function runCampaign(opts) {
303
523
  const seed = opts.seed ?? 42;
304
524
  const reps = opts.reps ?? 1;
305
525
  const resumable = opts.resumable ?? true;
306
- const maxConcurrency = opts.maxConcurrency ?? 2;
307
526
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
308
527
  const judges = opts.judges ?? [];
309
528
  const storage = opts.storage ?? fsCampaignStorage();
529
+ const costPhase = opts.costPhase ?? "campaign";
310
530
  if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
311
531
  throw new Error("runCampaign: runDir is required and must be a non-empty string");
312
532
  }
313
533
  opts.runDir = resolveRunDir(opts.runDir, opts.repo);
314
534
  storage.ensureDir(opts.runDir);
535
+ const costLedger = opts.costLedger ?? createRunCostLedger({
536
+ storage,
537
+ runDir: opts.runDir,
538
+ costCeilingUsd: opts.costCeiling
539
+ });
540
+ if (opts.costCeiling !== void 0 && costLedger.costCeilingUsd !== opts.costCeiling) {
541
+ throw new Error("runCampaign: costCeiling must match the shared CostLedger ceiling");
542
+ }
543
+ const maxConcurrency = opts.maxConcurrency ?? 2;
315
544
  const manifestHash = computeManifestHash({
316
545
  scenarios: opts.scenarios,
317
546
  judges,
@@ -320,11 +549,10 @@ async function runCampaign(opts) {
320
549
  reps
321
550
  });
322
551
  const startedAt = now();
552
+ const runAttemptId = globalThis.crypto.randomUUID();
323
553
  const cells = [];
324
554
  const artifactsByPath = {};
325
555
  const schedule = buildCellSchedule(opts.scenarios, seed, reps);
326
- let totalCostUsd = 0;
327
- let costCeilingReached = false;
328
556
  const abortController = new AbortController();
329
557
  const lanes = [];
330
558
  let nextIdx = 0;
@@ -336,10 +564,6 @@ async function runCampaign(opts) {
336
564
  const myIdx = nextIdx++;
337
565
  if (myIdx >= schedule.length) return;
338
566
  const slot = schedule[myIdx];
339
- if (costCeilingReached) {
340
- cellsRef.push(skippedCell(slot, "cost_ceiling_reached"));
341
- continue;
342
- }
343
567
  const result = await executeCell({
344
568
  slot,
345
569
  opts,
@@ -349,15 +573,13 @@ async function runCampaign(opts) {
349
573
  storage,
350
574
  buildTraceWriter: opts.buildTraceWriter ?? defaultBuildTraceWriter(storage),
351
575
  signal: abortController.signal,
352
- dispatchTimeoutMs: opts.dispatchTimeoutMs
576
+ dispatchTimeoutMs: opts.dispatchTimeoutMs,
577
+ costLedger,
578
+ costPhase,
579
+ runAttemptId
353
580
  });
354
581
  cellsRef.push(result.cell);
355
- enforceCellUsage(result.cell, opts.expectUsage ?? "warn");
356
- totalCostUsd += result.cell.costUsd;
357
582
  Object.assign(artifactsByPath, result.artifactsByPath);
358
- if (opts.costCeiling !== void 0 && totalCostUsd >= opts.costCeiling) {
359
- costCeilingReached = true;
360
- }
361
583
  if (opts.labeledStore && opts.labeledStore !== "off" && !result.cell.error) {
362
584
  await captureToStore({
363
585
  store: opts.labeledStore,
@@ -378,10 +600,12 @@ async function runCampaign(opts) {
378
600
  await Promise.all(lanes);
379
601
  const endedAt = now();
380
602
  cellsRef.sort((a, b) => a.cellId.localeCompare(b.cellId));
603
+ const campaignCost = costLedger.summary({ tags: { runDir: opts.runDir } });
381
604
  const aggregates = computeAggregates(
382
605
  cellsRef,
383
606
  judges,
384
- seed
607
+ seed,
608
+ campaignCost
385
609
  );
386
610
  return {
387
611
  manifestHash,
@@ -398,9 +622,16 @@ async function runCampaign(opts) {
398
622
  }
399
623
  async function executeCell(args) {
400
624
  const storage = args.storage;
401
- const cellDir = join2(args.opts.runDir, args.slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"));
625
+ const cellDir = join3(args.opts.runDir, args.slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"));
402
626
  storage.ensureDir(cellDir);
403
- const cachePath = join2(cellDir, "cached-result.json");
627
+ const stableCostTags = {
628
+ runDir: args.opts.runDir,
629
+ cellId: args.slot.cellId,
630
+ scenarioId: args.slot.scenario.id,
631
+ rep: String(args.slot.rep)
632
+ };
633
+ const costTags = { ...stableCostTags, runAttemptId: args.runAttemptId };
634
+ const cachePath = join3(cellDir, "cached-result.json");
404
635
  if (args.resumable) {
405
636
  const cached = readCachedCell({
406
637
  storage,
@@ -409,6 +640,33 @@ async function executeCell(args) {
409
640
  manifestHash: args.manifestHash
410
641
  });
411
642
  if (cached.status === "hit") {
643
+ enforceDispatchUsage(cached.cell, args.opts.expectUsage ?? "warn");
644
+ const cachedHasUsage = cached.cell.costUsd > 0 || cached.cell.tokenUsage.input > 0 || cached.cell.tokenUsage.output > 0;
645
+ if (cached.cell.costCallIds === void 0) {
646
+ if (cachedHasUsage || Object.keys(cached.cell.judgeScores).length > 0) {
647
+ throw new CostAccountingIncompleteError(
648
+ `runCampaign: cached cell '${args.slot.cellId}' does not identify its ledger receipts`
649
+ );
650
+ }
651
+ } else if (!Array.isArray(cached.cell.costCallIds) || cached.cell.costCallIds.some(
652
+ (callId) => typeof callId !== "string" || callId.trim().length === 0
653
+ ) || new Set(cached.cell.costCallIds).size !== cached.cell.costCallIds.length) {
654
+ throw new CostAccountingIncompleteError(
655
+ `runCampaign: cached cell '${args.slot.cellId}' has invalid ledger receipt IDs`
656
+ );
657
+ } else {
658
+ const restoredCallIds = new Set(
659
+ args.costLedger.list({ tags: stableCostTags }).map((receipt) => receipt.callId)
660
+ );
661
+ const missingCallIds = cached.cell.costCallIds.filter(
662
+ (callId) => !restoredCallIds.has(callId)
663
+ );
664
+ if (missingCallIds.length > 0) {
665
+ throw new CostAccountingIncompleteError(
666
+ `runCampaign: cached cell '${args.slot.cellId}' is missing ledger receipt(s): ${missingCallIds.join(", ")}`
667
+ );
668
+ }
669
+ }
412
670
  return { cell: { ...cached.cell, cached: true }, artifactsByPath: {} };
413
671
  }
414
672
  }
@@ -417,8 +675,8 @@ async function executeCell(args) {
417
675
  const artifactsByPath = {};
418
676
  const artifacts = {
419
677
  async write(path, content) {
420
- const fullPath = join2(cellDir, path);
421
- storage.ensureDir(join2(fullPath, ".."));
678
+ const fullPath = join3(cellDir, path);
679
+ storage.ensureDir(join3(fullPath, ".."));
422
680
  storage.write(fullPath, content);
423
681
  artifactsByPath[`${args.slot.cellId}/${path}`] = fullPath;
424
682
  return fullPath;
@@ -427,31 +685,20 @@ async function executeCell(args) {
427
685
  return artifacts.write(path, JSON.stringify(value, null, 2));
428
686
  }
429
687
  };
430
- let costSoFar = 0;
431
- const tokensSoFar = { input: 0, output: 0 };
432
- let resolvedModel;
433
688
  const cost = {
434
- observe(amount, source) {
435
- costSoFar += amount;
436
- trace.span(`cost.${source}`, { amountUsd: amount }).end();
437
- },
438
- observeTokens(usage) {
439
- tokensSoFar.input += usage.input;
440
- tokensSoFar.output += usage.output;
441
- if (usage.cached) tokensSoFar.cached = (tokensSoFar.cached ?? 0) + usage.cached;
442
- },
443
- observeModel(model) {
444
- const trimmed = model?.trim();
445
- if (trimmed) resolvedModel = trimmed;
446
- },
447
- current() {
448
- return costSoFar;
449
- },
450
- tokens() {
451
- return { ...tokensSoFar };
452
- },
453
- resolvedModel() {
454
- return resolvedModel;
689
+ async runPaidCall(input) {
690
+ const result = await args.costLedger.runPaidCall({
691
+ ...input,
692
+ channel: input.channel ?? "agent",
693
+ phase: args.costPhase,
694
+ actor: input.actor,
695
+ tags: costTags,
696
+ signal: cellAbort.signal
697
+ });
698
+ if (result.receipt) {
699
+ trace.span(`cost.${result.receipt.actor}`, { amountUsd: result.receipt.costUsd }).end();
700
+ }
701
+ return result;
455
702
  }
456
703
  };
457
704
  const placement = args.opts.cellPlacement?.({
@@ -503,23 +750,53 @@ async function executeCell(args) {
503
750
  if (timeoutTimer) clearTimeout(timeoutTimer);
504
751
  args.signal.removeEventListener("abort", onCampaignAbort);
505
752
  }
753
+ const agentReceipts = args.costLedger.list({ channel: "agent", tags: costTags });
754
+ const agentCost = args.costLedger.summary({ channel: "agent", tags: costTags });
755
+ const tokenUsage = {
756
+ input: agentCost.inputTokens,
757
+ output: agentCost.outputTokens,
758
+ ...agentCost.cachedTokens > 0 ? { cached: agentCost.cachedTokens } : {}
759
+ };
760
+ const resolvedModel = agentReceipts.at(-1)?.model;
761
+ const dispatchResult = {
762
+ cellId: args.slot.cellId,
763
+ artifact,
764
+ error: errorMessage,
765
+ costUsd: agentCost.totalCostUsd,
766
+ tokenUsage
767
+ };
768
+ try {
769
+ enforceDispatchUsage(dispatchResult, args.opts.expectUsage ?? "warn");
770
+ } catch (error) {
771
+ await trace.flush();
772
+ throw error;
773
+ }
506
774
  const judgeScores = {};
507
775
  if (artifact !== void 0) {
508
776
  for (const judge of args.opts.judges ?? []) {
509
777
  if (judge.appliesTo && !judge.appliesTo(args.slot.scenario)) continue;
510
778
  try {
511
- judgeScores[judge.name] = await runJudgeCell(judge, {
779
+ const score = await runJudgeCell(judge, {
512
780
  artifact,
513
781
  scenario: args.slot.scenario,
514
- signal: args.signal
782
+ signal: args.signal,
783
+ costLedger: args.costLedger,
784
+ costPhase: args.costPhase,
785
+ costTags
515
786
  });
787
+ judgeScores[judge.name] = score;
516
788
  } catch (err) {
789
+ if (err instanceof CostAccountingIncompleteError) {
790
+ await trace.flush();
791
+ throw err;
792
+ }
517
793
  errorMessage = `judge '${judge.name}' failed: ${err instanceof Error ? err.message : String(err)}`;
518
794
  break;
519
795
  }
520
796
  }
521
797
  }
522
798
  await trace.flush();
799
+ const costCallIds = args.costLedger.list({ tags: costTags }).map((receipt) => receipt.callId).sort();
523
800
  const cell = {
524
801
  manifestHash: args.manifestHash,
525
802
  cellId: args.slot.cellId,
@@ -527,8 +804,12 @@ async function executeCell(args) {
527
804
  rep: args.slot.rep,
528
805
  artifact: artifact ?? null,
529
806
  judgeScores,
530
- costUsd: costSoFar,
531
- tokenUsage: { ...tokensSoFar },
807
+ costUsd: agentCost.totalCostUsd,
808
+ costEstimated: agentReceipts.some(
809
+ (receipt) => receipt.actualCostUsd === void 0 && !receipt.costUnknown
810
+ ),
811
+ costCallIds,
812
+ tokenUsage,
532
813
  ...resolvedModel ? { resolvedModel } : {},
533
814
  durationMs: Date.now() - startMs,
534
815
  seed: args.slot.cellSeed,
@@ -557,7 +838,7 @@ function planCampaignRun(opts) {
557
838
  reps
558
839
  });
559
840
  const cells = buildCellSchedule(opts.scenarios, seed, reps).map((slot) => {
560
- const cachePath = join2(
841
+ const cachePath = join3(
561
842
  opts.runDir,
562
843
  slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"),
563
844
  "cached-result.json"
@@ -608,12 +889,12 @@ function planCampaignRun(opts) {
608
889
  cells
609
890
  };
610
891
  }
611
- function enforceCellUsage(cell, mode) {
612
- if (mode === "off" || cell.error) return;
892
+ function enforceDispatchUsage(cell, mode) {
893
+ if (mode === "off") return;
613
894
  if (cell.artifact === null || cell.artifact === void 0) return;
614
895
  const zeroTokens = cell.tokenUsage.input === 0 && cell.tokenUsage.output === 0;
615
896
  if (cell.costUsd !== 0 || !zeroTokens) return;
616
- const msg = `cell '${cell.cellId}' produced an artifact but reported zero cost and zero tokens \u2014 the dispatch never reported LLM usage via ctx.cost.observe/observeTokens (a stub cell)`;
897
+ const msg = `cell '${cell.cellId}' produced an artifact but reported zero cost and zero tokens \u2014 the dispatch made no paid call through ctx.cost.runPaidCall (a stub cell)`;
617
898
  if (mode === "assert") {
618
899
  const report = {
619
900
  totalRecords: 1,
@@ -631,7 +912,29 @@ function enforceCellUsage(cell, mode) {
631
912
  console.warn(`[runCampaign] expectUsage: ${msg}`);
632
913
  }
633
914
  async function runJudgeCell(judge, input) {
634
- return judge.score(input);
915
+ const previousJudgeCalls = new Set(
916
+ input.costLedger?.list({ channel: "judge", tags: input.costTags }).map((receipt) => receipt.callId) ?? []
917
+ );
918
+ try {
919
+ const score = await judge.score(input);
920
+ assertReportedJudgeCallRecorded(judge.name, score, input, previousJudgeCalls);
921
+ return score;
922
+ } catch (error) {
923
+ assertReportedJudgeCallRecorded(judge.name, error, input, previousJudgeCalls, error);
924
+ throw error;
925
+ }
926
+ }
927
+ function assertReportedJudgeCallRecorded(judgeName, value, input, previousCallIds, cause) {
928
+ if (!hasLlmCall(value)) return;
929
+ const recorded = input.costLedger?.list({ channel: "judge", tags: input.costTags }).some((receipt) => !previousCallIds.has(receipt.callId));
930
+ if (recorded) return;
931
+ throw new CostAccountingIncompleteError(
932
+ `runCampaign: judge '${judgeName}' reported a paid LLM call without a CostLedger receipt`,
933
+ cause === void 0 ? void 0 : { cause }
934
+ );
935
+ }
936
+ function hasLlmCall(value) {
937
+ return typeof value === "object" && value !== null && "llmCall" in value && value.llmCall !== void 0;
635
938
  }
636
939
  function defaultBuildTraceWriter(storage) {
637
940
  return (cellId, dir) => {
@@ -653,26 +956,11 @@ function defaultBuildTraceWriter(storage) {
653
956
  return finish;
654
957
  },
655
958
  async flush() {
656
- storage.write(join2(dir, "spans.jsonl"), spans.map((s) => JSON.stringify(s)).join("\n"));
959
+ storage.write(join3(dir, "spans.jsonl"), spans.map((s) => JSON.stringify(s)).join("\n"));
657
960
  }
658
961
  };
659
962
  };
660
963
  }
661
- function skippedCell(slot, reason) {
662
- return {
663
- cellId: slot.cellId,
664
- scenarioId: slot.scenario.id,
665
- rep: slot.rep,
666
- artifact: null,
667
- judgeScores: {},
668
- costUsd: 0,
669
- tokenUsage: { input: 0, output: 0 },
670
- durationMs: 0,
671
- seed: slot.cellSeed,
672
- cached: false,
673
- error: `skipped: ${reason}`
674
- };
675
- }
676
964
  function buildCellSchedule(scenarios, seed, reps) {
677
965
  const schedule = [];
678
966
  let cellIndex = 0;
@@ -721,13 +1009,30 @@ async function captureToStore(args) {
721
1009
  function computeManifestHash(input) {
722
1010
  return contentHash({
723
1011
  scenarios: input.scenarios,
724
- judges: input.judges.map((j) => ({ name: j.name, dims: j.dimensions })),
1012
+ judges: input.judges.map((judge) => ({
1013
+ name: judge.name,
1014
+ dims: judge.dimensions,
1015
+ version: judgeVersionFor(judge)
1016
+ })),
725
1017
  dispatch: input.dispatchRef,
726
1018
  seed: input.seed,
727
1019
  reps: input.reps
728
1020
  });
729
1021
  }
730
- function computeAggregates(cells, judges, seed) {
1022
+ function judgeVersionFor(judge) {
1023
+ if (judge.judgeVersion !== void 0) {
1024
+ const version = judge.judgeVersion.trim();
1025
+ if (version.length === 0) {
1026
+ throw new Error(`runCampaign: judge '${judge.name}' has an empty judgeVersion`);
1027
+ }
1028
+ return version;
1029
+ }
1030
+ return contentHash({
1031
+ score: judge.score.toString(),
1032
+ appliesTo: judge.appliesTo?.toString() ?? null
1033
+ });
1034
+ }
1035
+ function computeAggregates(cells, judges, seed, cost) {
731
1036
  const byJudge = {};
732
1037
  for (const judge of judges) {
733
1038
  const scores = [];
@@ -754,7 +1059,8 @@ function computeAggregates(cells, judges, seed) {
754
1059
  return {
755
1060
  byJudge,
756
1061
  byScenario,
757
- totalCostUsd: cells.reduce((a, c) => a + c.costUsd, 0),
1062
+ cost,
1063
+ totalCostUsd: cost.totalCostUsd,
758
1064
  cellsExecuted: cells.filter((c) => !c.error).length,
759
1065
  cellsSkipped: cells.filter((c) => c.error?.startsWith("skipped:")).length,
760
1066
  cellsCached: cells.filter((c) => c.cached).length,
@@ -782,9 +1088,15 @@ export {
782
1088
  cachedJudge,
783
1089
  tangleTracesRoot,
784
1090
  resolveRunDir,
1091
+ SearchLedgerError,
1092
+ SearchLedgerIntegrityError,
1093
+ SearchLedgerConflictError,
1094
+ appendSearchLedgerLine,
1095
+ withSearchLedgerFileLock,
785
1096
  fsCampaignStorage,
786
1097
  inMemoryCampaignStorage,
1098
+ createRunCostLedger,
787
1099
  runCampaign,
788
1100
  planCampaignRun
789
1101
  };
790
- //# sourceMappingURL=chunk-FAOEFFRT.js.map
1102
+ //# sourceMappingURL=chunk-IDZTTFRR.js.map