@tangle-network/agent-eval 0.120.0 → 0.120.2

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 (171) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +1 -1
  3. package/dist/analyst/index.d.ts +0 -3111
  4. package/dist/analyst/index.js +0 -403
  5. package/dist/analyst/index.js.map +0 -1
  6. package/dist/authenticity/index.d.ts +0 -161
  7. package/dist/authenticity/index.js +0 -215
  8. package/dist/authenticity/index.js.map +0 -1
  9. package/dist/belief-state/index.d.ts +0 -1301
  10. package/dist/belief-state/index.js +0 -2152
  11. package/dist/belief-state/index.js.map +0 -1
  12. package/dist/benchmarks/index.d.ts +0 -974
  13. package/dist/benchmarks/index.js +0 -60
  14. package/dist/benchmarks/index.js.map +0 -1
  15. package/dist/builder-eval/index.d.ts +0 -695
  16. package/dist/builder-eval/index.js +0 -366
  17. package/dist/builder-eval/index.js.map +0 -1
  18. package/dist/campaign/index.d.ts +0 -7454
  19. package/dist/campaign/index.js +0 -272
  20. package/dist/campaign/index.js.map +0 -1
  21. package/dist/chunk-32BZXMSO.js +0 -3878
  22. package/dist/chunk-32BZXMSO.js.map +0 -1
  23. package/dist/chunk-3A246TSA.js +0 -998
  24. package/dist/chunk-3A246TSA.js.map +0 -1
  25. package/dist/chunk-3RF76KTD.js +0 -84
  26. package/dist/chunk-3RF76KTD.js.map +0 -1
  27. package/dist/chunk-3YYRZDON.js +0 -45
  28. package/dist/chunk-3YYRZDON.js.map +0 -1
  29. package/dist/chunk-4I2E3LLO.js +0 -1030
  30. package/dist/chunk-4I2E3LLO.js.map +0 -1
  31. package/dist/chunk-ARU2PZFM.js +0 -312
  32. package/dist/chunk-ARU2PZFM.js.map +0 -1
  33. package/dist/chunk-BOD4O7OF.js +0 -40
  34. package/dist/chunk-BOD4O7OF.js.map +0 -1
  35. package/dist/chunk-DPZAEKA6.js +0 -880
  36. package/dist/chunk-DPZAEKA6.js.map +0 -1
  37. package/dist/chunk-DTJ6QUQB.js +0 -131
  38. package/dist/chunk-DTJ6QUQB.js.map +0 -1
  39. package/dist/chunk-GGE4NNQT.js +0 -65
  40. package/dist/chunk-GGE4NNQT.js.map +0 -1
  41. package/dist/chunk-H5UD2323.js +0 -286
  42. package/dist/chunk-H5UD2323.js.map +0 -1
  43. package/dist/chunk-HHWE3POT.js +0 -94
  44. package/dist/chunk-HHWE3POT.js.map +0 -1
  45. package/dist/chunk-HKUCJ437.js +0 -787
  46. package/dist/chunk-HKUCJ437.js.map +0 -1
  47. package/dist/chunk-JHCHEVET.js +0 -274
  48. package/dist/chunk-JHCHEVET.js.map +0 -1
  49. package/dist/chunk-JHOJHHU7.js +0 -867
  50. package/dist/chunk-JHOJHHU7.js.map +0 -1
  51. package/dist/chunk-JM2SKQMS.js +0 -750
  52. package/dist/chunk-JM2SKQMS.js.map +0 -1
  53. package/dist/chunk-JN2FCO5W.js +0 -7958
  54. package/dist/chunk-JN2FCO5W.js.map +0 -1
  55. package/dist/chunk-K4DBDHLK.js +0 -158
  56. package/dist/chunk-K4DBDHLK.js.map +0 -1
  57. package/dist/chunk-K6N6XJJX.js +0 -306
  58. package/dist/chunk-K6N6XJJX.js.map +0 -1
  59. package/dist/chunk-MA6HLL3S.js +0 -65
  60. package/dist/chunk-MA6HLL3S.js.map +0 -1
  61. package/dist/chunk-MAZ26DC7.js +0 -99
  62. package/dist/chunk-MAZ26DC7.js.map +0 -1
  63. package/dist/chunk-MOXWMGPC.js +0 -577
  64. package/dist/chunk-MOXWMGPC.js.map +0 -1
  65. package/dist/chunk-NJC7U437.js +0 -626
  66. package/dist/chunk-NJC7U437.js.map +0 -1
  67. package/dist/chunk-NPCTHQIO.js +0 -91
  68. package/dist/chunk-NPCTHQIO.js.map +0 -1
  69. package/dist/chunk-ONWEPEDO.js +0 -57
  70. package/dist/chunk-ONWEPEDO.js.map +0 -1
  71. package/dist/chunk-OYZAPX5G.js +0 -1526
  72. package/dist/chunk-OYZAPX5G.js.map +0 -1
  73. package/dist/chunk-PC4UYEBM.js +0 -166
  74. package/dist/chunk-PC4UYEBM.js.map +0 -1
  75. package/dist/chunk-PICTDURQ.js +0 -766
  76. package/dist/chunk-PICTDURQ.js.map +0 -1
  77. package/dist/chunk-PJQFMIOX.js +0 -1182
  78. package/dist/chunk-PJQFMIOX.js.map +0 -1
  79. package/dist/chunk-PXD6ZFNY.js +0 -1107
  80. package/dist/chunk-PXD6ZFNY.js.map +0 -1
  81. package/dist/chunk-PXE2VKMX.js +0 -140
  82. package/dist/chunk-PXE2VKMX.js.map +0 -1
  83. package/dist/chunk-PZ5AY32C.js +0 -10
  84. package/dist/chunk-PZ5AY32C.js.map +0 -1
  85. package/dist/chunk-QBRSJK47.js +0 -622
  86. package/dist/chunk-QBRSJK47.js.map +0 -1
  87. package/dist/chunk-QWMPPZ3X.js +0 -550
  88. package/dist/chunk-QWMPPZ3X.js.map +0 -1
  89. package/dist/chunk-S3UZOQ5Y.js +0 -328
  90. package/dist/chunk-S3UZOQ5Y.js.map +0 -1
  91. package/dist/chunk-S5TT5R3L.js +0 -2668
  92. package/dist/chunk-S5TT5R3L.js.map +0 -1
  93. package/dist/chunk-T4SQEITX.js +0 -95
  94. package/dist/chunk-T4SQEITX.js.map +0 -1
  95. package/dist/chunk-TT4KNT67.js +0 -124
  96. package/dist/chunk-TT4KNT67.js.map +0 -1
  97. package/dist/chunk-U5CHZ5M3.js +0 -357
  98. package/dist/chunk-U5CHZ5M3.js.map +0 -1
  99. package/dist/chunk-ULOKLHIQ.js +0 -1937
  100. package/dist/chunk-ULOKLHIQ.js.map +0 -1
  101. package/dist/chunk-VI2UW6B6.js +0 -162
  102. package/dist/chunk-VI2UW6B6.js.map +0 -1
  103. package/dist/chunk-VQMK5FMP.js +0 -247
  104. package/dist/chunk-VQMK5FMP.js.map +0 -1
  105. package/dist/chunk-VSMTAMNK.js +0 -53
  106. package/dist/chunk-VSMTAMNK.js.map +0 -1
  107. package/dist/chunk-VZSRQ272.js +0 -149
  108. package/dist/chunk-VZSRQ272.js.map +0 -1
  109. package/dist/chunk-WW2A73HW.js +0 -159
  110. package/dist/chunk-WW2A73HW.js.map +0 -1
  111. package/dist/chunk-X4UCIOTZ.js +0 -136
  112. package/dist/chunk-X4UCIOTZ.js.map +0 -1
  113. package/dist/chunk-XDIRG3TO.js +0 -1266
  114. package/dist/chunk-XDIRG3TO.js.map +0 -1
  115. package/dist/chunk-XJYR7XFV.js +0 -317
  116. package/dist/chunk-XJYR7XFV.js.map +0 -1
  117. package/dist/chunk-ZET2UAYW.js +0 -89
  118. package/dist/chunk-ZET2UAYW.js.map +0 -1
  119. package/dist/chunk-ZZUXHH3R.js +0 -99
  120. package/dist/chunk-ZZUXHH3R.js.map +0 -1
  121. package/dist/cli.d.ts +0 -1
  122. package/dist/cli.js +0 -112
  123. package/dist/cli.js.map +0 -1
  124. package/dist/contract/index.d.ts +0 -4969
  125. package/dist/contract/index.js +0 -1653
  126. package/dist/contract/index.js.map +0 -1
  127. package/dist/control.d.ts +0 -1013
  128. package/dist/control.js +0 -34
  129. package/dist/control.js.map +0 -1
  130. package/dist/fuzz.d.ts +0 -759
  131. package/dist/fuzz.js +0 -714
  132. package/dist/fuzz.js.map +0 -1
  133. package/dist/hosted/index.d.ts +0 -730
  134. package/dist/hosted/index.js +0 -14
  135. package/dist/hosted/index.js.map +0 -1
  136. package/dist/index.d.ts +0 -16780
  137. package/dist/index.js +0 -12168
  138. package/dist/index.js.map +0 -1
  139. package/dist/matrix/index.d.ts +0 -155
  140. package/dist/matrix/index.js +0 -8
  141. package/dist/matrix/index.js.map +0 -1
  142. package/dist/meta-eval/index.d.ts +0 -1030
  143. package/dist/meta-eval/index.js +0 -417
  144. package/dist/meta-eval/index.js.map +0 -1
  145. package/dist/multishot/index.d.ts +0 -579
  146. package/dist/multishot/index.js +0 -589
  147. package/dist/multishot/index.js.map +0 -1
  148. package/dist/openapi.json +0 -992
  149. package/dist/pipelines/index.d.ts +0 -567
  150. package/dist/pipelines/index.js +0 -515
  151. package/dist/pipelines/index.js.map +0 -1
  152. package/dist/reporting.d.ts +0 -1277
  153. package/dist/reporting.js +0 -48
  154. package/dist/reporting.js.map +0 -1
  155. package/dist/rl.d.ts +0 -4092
  156. package/dist/rl.js +0 -1724
  157. package/dist/rl.js.map +0 -1
  158. package/dist/run-campaign-HNFPJET4.js +0 -14
  159. package/dist/run-campaign-HNFPJET4.js.map +0 -1
  160. package/dist/storyboard/index.d.ts +0 -279
  161. package/dist/storyboard/index.js +0 -767
  162. package/dist/storyboard/index.js.map +0 -1
  163. package/dist/trace-attributes.d.ts +0 -52
  164. package/dist/trace-attributes.js +0 -62
  165. package/dist/trace-attributes.js.map +0 -1
  166. package/dist/traces.d.ts +0 -2343
  167. package/dist/traces.js +0 -249
  168. package/dist/traces.js.map +0 -1
  169. package/dist/wire/index.d.ts +0 -1252
  170. package/dist/wire/index.js +0 -81
  171. package/dist/wire/index.js.map +0 -1
@@ -1,1266 +0,0 @@
1
- import {
2
- confidenceInterval
3
- } from "./chunk-PJQFMIOX.js";
4
- import {
5
- CostAccountingIncompleteError,
6
- CostLedger
7
- } from "./chunk-JHOJHHU7.js";
8
- import {
9
- AgentEvalError,
10
- ValidationError
11
- } from "./chunk-ONWEPEDO.js";
12
-
13
- // src/campaign/run-campaign.ts
14
- import { join as join3 } from "path";
15
-
16
- // src/integrity/backend-integrity.ts
17
- var BackendIntegrityError = class extends AgentEvalError {
18
- constructor(message, report) {
19
- super("backend_integrity", message);
20
- this.report = report;
21
- }
22
- report;
23
- };
24
- function summarizeBackendIntegrity(records) {
25
- return summarizeBackendUsage(
26
- records.map((record) => ({
27
- inputTokens: record.tokenUsage.input,
28
- outputTokens: record.tokenUsage.output,
29
- costUsd: record.costUsd
30
- }))
31
- );
32
- }
33
- function summarizeAgentReceiptIntegrity(receipts) {
34
- return summarizeBackendUsage(
35
- receipts.filter((receipt) => receipt.channel === "agent").map((receipt) => ({
36
- inputTokens: receipt.inputTokens,
37
- outputTokens: receipt.outputTokens,
38
- costUsd: receipt.costUsd
39
- }))
40
- );
41
- }
42
- function summarizeBackendUsage(records) {
43
- const totalRecords = records.length;
44
- let stubRecords = 0;
45
- let realRecords = 0;
46
- let uncostedRecords = 0;
47
- let totalInputTokens = 0;
48
- let totalOutputTokens = 0;
49
- let totalCostUsd = 0;
50
- for (const rec of records) {
51
- totalInputTokens += rec.inputTokens;
52
- totalOutputTokens += rec.outputTokens;
53
- totalCostUsd += rec.costUsd;
54
- if (rec.inputTokens === 0 && rec.outputTokens === 0) stubRecords++;
55
- else realRecords++;
56
- if (rec.outputTokens > 0 && rec.costUsd === 0) uncostedRecords++;
57
- }
58
- const verdict = totalRecords === 0 ? "stub" : stubRecords === totalRecords ? "stub" : stubRecords === 0 ? "real" : "mixed";
59
- const diagnosis = buildDiagnosis({
60
- totalRecords,
61
- stubRecords,
62
- realRecords,
63
- uncostedRecords,
64
- totalInputTokens,
65
- totalOutputTokens,
66
- totalCostUsd,
67
- verdict
68
- });
69
- return {
70
- totalRecords,
71
- stubRecords,
72
- realRecords,
73
- uncostedRecords,
74
- totalInputTokens,
75
- totalOutputTokens,
76
- totalCostUsd,
77
- verdict,
78
- diagnosis
79
- };
80
- }
81
- function buildDiagnosis(r) {
82
- if (r.totalRecords === 0) {
83
- return "no records \u2014 eval produced zero runs; backend likely failed before first turn";
84
- }
85
- if (r.verdict === "stub") {
86
- return [
87
- `all ${r.totalRecords} records have zero token usage \u2014 the LLM backend was never called.`,
88
- "common causes: --backend sandbox without a sandbox bridge running; stub model returning hard-coded strings;",
89
- "auth misconfigured so requests were silently dropped before the LLM. Re-run with --backend tcloud and TANGLE_API_KEY set,",
90
- "or boot the cli-bridge / sandbox before invoking the eval."
91
- ].join(" ");
92
- }
93
- if (r.verdict === "mixed") {
94
- const pct = (r.stubRecords / r.totalRecords * 100).toFixed(0);
95
- return [
96
- `${r.stubRecords}/${r.totalRecords} records (${pct}%) have zero token usage \u2014 the backend partially failed.`,
97
- "common causes: rate-limit cascade (429s after the first N personas);",
98
- "transient auth expiry mid-run; provider outage. Treat the affected records as missing data, not agent failures."
99
- ].join(" ");
100
- }
101
- if (r.uncostedRecords > 0) {
102
- const pct = (r.uncostedRecords / r.totalRecords * 100).toFixed(0);
103
- return [
104
- `${r.totalRecords} records with real LLM activity (in=${r.totalInputTokens}, out=${r.totalOutputTokens} tokens).`,
105
- `${r.uncostedRecords} (${pct}%) have output tokens but costUsd=0. Two distinct roots:`,
106
- "(a) cost ledger mis-wired \u2014 no usage propagation from the runtime stream into RunRecord; or",
107
- "(b) the model is unpriced at the source (sandbox/router returned $0 despite real tokens).",
108
- "For (b), price the measured tokens against the substrate table (estimateCost) instead of leaving $0."
109
- ].join(" ");
110
- }
111
- return `${r.totalRecords} records with real LLM activity (in=${r.totalInputTokens}, out=${r.totalOutputTokens} tokens, $${r.totalCostUsd.toFixed(4)}).`;
112
- }
113
- function assertRealBackend(records, opts = {}) {
114
- const report = summarizeBackendIntegrity(records);
115
- return assertBackendReport(report, opts);
116
- }
117
- function assertRealAgentReceipts(receipts, opts = {}) {
118
- const report = summarizeAgentReceiptIntegrity(receipts);
119
- return assertBackendReport(report, opts);
120
- }
121
- function assertBackendReport(report, opts) {
122
- const allowMixed = opts.allowMixed ?? true;
123
- if (report.verdict === "stub") {
124
- throw new BackendIntegrityError(
125
- `backend-integrity: ran against a stub or unconfigured backend \u2014 ${report.diagnosis}`,
126
- report
127
- );
128
- }
129
- if (!allowMixed && report.verdict === "mixed") {
130
- throw new BackendIntegrityError(
131
- `backend-integrity: partial backend failure rejected \u2014 ${report.diagnosis}`,
132
- report
133
- );
134
- }
135
- return report;
136
- }
137
-
138
- // src/verdict-cache.ts
139
- import { createHash } from "crypto";
140
- import { appendFileSync, existsSync, readFileSync } from "fs";
141
- function canonicalizeAt(value, path) {
142
- if (value === null) return "null";
143
- switch (typeof value) {
144
- case "boolean":
145
- return value ? "true" : "false";
146
- case "number":
147
- if (!Number.isFinite(value)) {
148
- throw new Error(
149
- `canonicalJson: non-finite number (${value}) at ${path} \u2014 ambiguity is an error, not a coercion`
150
- );
151
- }
152
- return JSON.stringify(value);
153
- case "string":
154
- return JSON.stringify(value);
155
- case "undefined":
156
- case "function":
157
- case "symbol":
158
- throw new Error(
159
- `canonicalJson: ${typeof value} at ${path} \u2014 ambiguity is an error, not a coercion`
160
- );
161
- case "bigint":
162
- throw new Error(`canonicalJson: bigint at ${path} \u2014 not representable in JSON`);
163
- case "object":
164
- break;
165
- }
166
- const obj = value;
167
- if (typeof obj.toJSON === "function") {
168
- return canonicalizeAt(obj.toJSON(), path);
169
- }
170
- if (Array.isArray(obj)) {
171
- return `[${obj.map((item, i) => canonicalizeAt(item, `${path}[${i}]`)).join(",")}]`;
172
- }
173
- if (obj instanceof Map || obj instanceof Set) {
174
- throw new Error(
175
- `canonicalJson: ${obj instanceof Map ? "Map" : "Set"} at ${path} \u2014 would serialize as '{}'; convert to a plain object/array first`
176
- );
177
- }
178
- const keys = Object.keys(obj).sort();
179
- const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalizeAt(obj[k], `${path}.${k}`)}`);
180
- return `{${parts.join(",")}}`;
181
- }
182
- function canonicalJson(value) {
183
- return canonicalizeAt(value, "$");
184
- }
185
- function contentHash(value) {
186
- return createHash("sha256").update(canonicalJson(value)).digest("hex");
187
- }
188
- function inMemoryVerdictCache() {
189
- const entries = /* @__PURE__ */ new Map();
190
- return {
191
- get: (key) => entries.get(key),
192
- set: (key, score) => {
193
- entries.set(key, score);
194
- }
195
- };
196
- }
197
- function parseCacheLine(line, path, lineNo) {
198
- let parsed;
199
- try {
200
- parsed = JSON.parse(line);
201
- } catch (err) {
202
- throw new Error(
203
- `fileVerdictCache: corrupt JSONL at ${path}:${lineNo} \u2014 ${err instanceof Error ? err.message : String(err)}`
204
- );
205
- }
206
- const rec = parsed;
207
- if (typeof rec !== "object" || rec === null || typeof rec.key !== "string" || typeof rec.score !== "object" || rec.score === null || typeof rec.score.composite !== "number" || typeof rec.score.dimensions !== "object") {
208
- throw new Error(
209
- `fileVerdictCache: invalid record shape at ${path}:${lineNo} \u2014 expected {key, score:{dimensions, composite, notes}}`
210
- );
211
- }
212
- return rec;
213
- }
214
- function fileVerdictCache(path) {
215
- const entries = /* @__PURE__ */ new Map();
216
- if (existsSync(path)) {
217
- const lines = readFileSync(path, "utf8").split("\n");
218
- for (let i = 0; i < lines.length; i++) {
219
- const line = lines[i];
220
- if (line === void 0 || line.trim() === "") continue;
221
- const rec = parseCacheLine(line, path, i + 1);
222
- entries.set(rec.key, rec.score);
223
- }
224
- }
225
- return {
226
- get: (key) => entries.get(key),
227
- set: (key, score) => {
228
- appendFileSync(path, `${JSON.stringify({ key, score })}
229
- `, "utf8");
230
- entries.set(key, score);
231
- }
232
- };
233
- }
234
- function cachedJudge(judge, store, options) {
235
- if (typeof options.judgeVersion !== "string" || options.judgeVersion.trim() === "") {
236
- throw new Error("cachedJudge: judgeVersion is required and must be a non-empty string");
237
- }
238
- const stats = { hits: 0, misses: 0 };
239
- const wrapped = {
240
- name: judge.name,
241
- dimensions: judge.dimensions,
242
- judgeVersion: options.judgeVersion,
243
- async score(input) {
244
- const key = contentHash({
245
- artifact: canonicalJson(input.artifact),
246
- scenarioId: input.scenario.id,
247
- judgeName: judge.name,
248
- dimensions: judge.dimensions,
249
- judgeVersion: options.judgeVersion
250
- });
251
- const cached = await store.get(key);
252
- if (cached !== void 0) {
253
- stats.hits += 1;
254
- return cached;
255
- }
256
- const score = await judge.score(input);
257
- await store.set(key, score);
258
- stats.misses += 1;
259
- return score;
260
- },
261
- stats: () => ({ ...stats })
262
- };
263
- if (judge.appliesTo) wrapped.appliesTo = judge.appliesTo;
264
- return wrapped;
265
- }
266
-
267
- // src/campaign/coverage.ts
268
- function assertCampaignDesign(scenarios, reps) {
269
- if (!Number.isSafeInteger(reps) || reps < 1) {
270
- throw new Error("campaign design requires reps to be a positive safe integer");
271
- }
272
- const scenarioIds = /* @__PURE__ */ new Set();
273
- for (const scenario of scenarios) {
274
- if (typeof scenario.id !== "string" || scenario.id.trim().length === 0) {
275
- throw new Error("campaign design requires every scenario to have a non-empty id");
276
- }
277
- if (scenarioIds.has(scenario.id)) {
278
- throw new Error(`campaign design contains duplicate scenario id '${scenario.id}'`);
279
- }
280
- if (typeof scenario.kind !== "string" || scenario.kind.trim().length === 0) {
281
- throw new Error("campaign design requires every scenario to have a non-empty kind");
282
- }
283
- scenarioIds.add(scenario.id);
284
- }
285
- }
286
- function campaignScenarioIdentity(scenario) {
287
- assertCampaignDesign([scenario], 1);
288
- return {
289
- id: scenario.id,
290
- kind: scenario.kind,
291
- scenarioDigest: `sha256:${contentHash(scenario)}`
292
- };
293
- }
294
- function campaignSplitDigestFromIdentities(scenarios, reps) {
295
- assertCampaignDesign(scenarios, reps);
296
- for (const scenario of scenarios) {
297
- if (!/^sha256:[a-f0-9]{64}$/.test(scenario.scenarioDigest)) {
298
- throw new Error(`campaign scenario '${scenario.id}' has an invalid digest`);
299
- }
300
- }
301
- return `sha256:${contentHash({
302
- schema: "tangle.campaign-split",
303
- scenarios: scenarios.map(({ id, kind, scenarioDigest }) => ({ id, kind, scenarioDigest })),
304
- reps
305
- })}`;
306
- }
307
- function campaignSplitDigest(scenarios, reps) {
308
- assertCampaignDesign(scenarios, reps);
309
- return campaignSplitDigestFromIdentities(scenarios.map(campaignScenarioIdentity), reps);
310
- }
311
- function assertCampaignSplitIdentity(scenarios, reps, splitDigest) {
312
- if (campaignSplitDigestFromIdentities(scenarios, reps) !== splitDigest) {
313
- throw new Error("campaign split digest does not match its retained scenario identities");
314
- }
315
- }
316
- function campaignCoverage(cells, scenarios, reps, requireJudgeScore) {
317
- assertCampaignDesign(scenarios, reps);
318
- const expectedCellIds = designedCellIds(scenarios, reps);
319
- const cellsById = /* @__PURE__ */ new Map();
320
- for (const cell of cells) {
321
- const matches = cellsById.get(cell.cellId) ?? [];
322
- matches.push(cell);
323
- cellsById.set(cell.cellId, matches);
324
- }
325
- const scorableCellIds = [];
326
- const unscorableCells = [];
327
- for (const cellId of expectedCellIds) {
328
- const matches = cellsById.get(cellId) ?? [];
329
- if (matches.length === 0) {
330
- unscorableCells.push({ cellId, reason: "missing campaign cell" });
331
- continue;
332
- }
333
- if (matches.length > 1) {
334
- unscorableCells.push({ cellId, reason: `duplicate campaign cell (${matches.length})` });
335
- continue;
336
- }
337
- const cell = matches[0];
338
- const scoreEntries = Object.entries(cell.judgeScores);
339
- const successfulScores = scoreEntries.map(([, score]) => score).filter((score) => score.failed !== true && Number.isFinite(score.composite));
340
- const nonFiniteScores = scoreEntries.filter(
341
- ([, score]) => score.failed !== true && (!Number.isFinite(score.composite) || Object.values(score.dimensions).some((value) => !Number.isFinite(value)))
342
- );
343
- const reasons = [];
344
- if (cell.error) reasons.push(cell.error);
345
- if (cell.artifact === null || cell.artifact === void 0) reasons.push("missing artifact");
346
- if (!cell.error && requireJudgeScore && successfulScores.length === 0) {
347
- reasons.push("no successful finite judge score");
348
- }
349
- if (scoreEntries.some(([, score]) => score.failed === true)) {
350
- reasons.push("judge score marked failed");
351
- }
352
- if (nonFiniteScores.length > 0) {
353
- reasons.push(
354
- `non-finite judge score: ${nonFiniteScores.map(([name]) => name).sort().join(", ")}`
355
- );
356
- }
357
- if (reasons.length > 0) {
358
- unscorableCells.push({ cellId, reason: reasons.join("; ") });
359
- } else {
360
- scorableCellIds.push(cellId);
361
- }
362
- }
363
- const expected = new Set(expectedCellIds);
364
- for (const cell of cells) {
365
- if (cell.cellId !== `${cell.scenarioId}:${cell.rep}`) {
366
- unscorableCells.push({
367
- cellId: cell.cellId,
368
- reason: "campaign cell id does not match scenario id and rep"
369
- });
370
- continue;
371
- }
372
- if (!expected.has(cell.cellId)) {
373
- unscorableCells.push({ cellId: cell.cellId, reason: "unexpected campaign cell" });
374
- }
375
- }
376
- return {
377
- complete: unscorableCells.length === 0 && scorableCellIds.length === expectedCellIds.length,
378
- expectedCellIds,
379
- scorableCellIds,
380
- unscorableCells
381
- };
382
- }
383
- function formatCoverageFailures(coverage) {
384
- const shown = coverage.unscorableCells.slice(0, 3).map((cell) => `${cell.cellId}: ${cell.reason}`).join("; ");
385
- const remainder = coverage.unscorableCells.length - Math.min(3, coverage.unscorableCells.length);
386
- return remainder > 0 ? `${shown}; +${remainder} more` : shown || "unknown coverage failure";
387
- }
388
- function designedCellIds(scenarios, reps) {
389
- const ids = [];
390
- for (const scenario of scenarios) {
391
- for (let rep = 0; rep < reps; rep++) ids.push(`${scenario.id}:${rep}`);
392
- }
393
- return ids;
394
- }
395
-
396
- // src/campaign/run-dir.ts
397
- import { homedir } from "os";
398
- import { basename, isAbsolute, join } from "path";
399
- function tangleTracesRoot() {
400
- return join(homedir(), ".tangle", "traces");
401
- }
402
- function resolveRunDir(runDir, repo) {
403
- if (isAbsolute(runDir) || runDir.startsWith("mem://")) return runDir;
404
- const r = repo && repo.trim().length > 0 ? repo : basename(process.cwd());
405
- return join(tangleTracesRoot(), r, "runs", runDir);
406
- }
407
-
408
- // src/campaign/storage.ts
409
- import { createRequire } from "module";
410
- import { join as join2 } from "path";
411
-
412
- // src/campaign/search-ledger-file.ts
413
- import { randomUUID } from "crypto";
414
- import {
415
- closeSync,
416
- constants,
417
- existsSync as existsSync2,
418
- fsyncSync,
419
- linkSync,
420
- mkdirSync,
421
- openSync,
422
- readFileSync as readFileSync2,
423
- renameSync,
424
- unlinkSync,
425
- writeSync
426
- } from "fs";
427
- import { hostname } from "os";
428
- import { dirname } from "path";
429
- import { z } from "zod";
430
-
431
- // src/campaign/search-ledger-errors.ts
432
- var SearchLedgerError = class extends ValidationError {
433
- };
434
- var SearchLedgerIntegrityError = class extends SearchLedgerError {
435
- };
436
- var SearchLedgerConflictError = class extends SearchLedgerError {
437
- };
438
-
439
- // src/campaign/search-ledger-file.ts
440
- function appendSearchLedgerLine(path, line) {
441
- mkdirSync(dirname(path), { recursive: true });
442
- const fd = openSync(path, constants.O_CREAT | constants.O_WRONLY | constants.O_APPEND, 384);
443
- try {
444
- writeAll(fd, Buffer.from(line, "utf8"));
445
- fsyncSync(fd);
446
- } finally {
447
- closeSync(fd);
448
- }
449
- fsyncDirectory(dirname(path));
450
- }
451
- function withSearchLedgerFileLock(ledgerPath, run) {
452
- const result = tryWithSearchLedgerFileLock(ledgerPath, run);
453
- if (!result.acquired) {
454
- throw new SearchLedgerIntegrityError(`search ledger lock is held (${ledgerPath})`);
455
- }
456
- return result.value;
457
- }
458
- function tryWithSearchLedgerFileLock(ledgerPath, run) {
459
- mkdirSync(dirname(ledgerPath), { recursive: true });
460
- const lockPath = `${ledgerPath}.lock`;
461
- const owner = acquireLock(lockPath);
462
- if (!owner) return { acquired: false };
463
- try {
464
- return { acquired: true, value: run() };
465
- } finally {
466
- releaseLock(lockPath, owner);
467
- }
468
- }
469
- function writeAll(fd, bytes) {
470
- let offset = 0;
471
- while (offset < bytes.byteLength) {
472
- const written = writeSync(fd, bytes, offset, bytes.byteLength - offset);
473
- if (written <= 0) throw new SearchLedgerIntegrityError("filesystem wrote zero bytes");
474
- offset += written;
475
- }
476
- }
477
- function fsyncDirectory(path) {
478
- const fd = openSync(path, constants.O_RDONLY);
479
- try {
480
- fsyncSync(fd);
481
- } finally {
482
- closeSync(fd);
483
- }
484
- }
485
- function acquireLock(lockPath) {
486
- const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
487
- const ownerBytes = `${canonicalOwner(owner)}
488
- `;
489
- for (let attempt = 0; attempt < 8; attempt += 1) {
490
- const ownerPath = `${lockPath}.${owner.pid}.${owner.nonce}.${attempt}.owner`;
491
- const fd = openSync(ownerPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
492
- try {
493
- writeAll(fd, Buffer.from(ownerBytes, "utf8"));
494
- fsyncSync(fd);
495
- } finally {
496
- closeSync(fd);
497
- }
498
- try {
499
- linkSync(ownerPath, lockPath);
500
- unlinkSync(ownerPath);
501
- return owner;
502
- } catch (error) {
503
- unlinkIfExists(ownerPath);
504
- if (error.code !== "EEXIST") throw error;
505
- }
506
- const holder = readOwner(lockPath);
507
- if (holder.host !== owner.host || isProcessAlive(holder.pid)) {
508
- return void 0;
509
- }
510
- const tombstone = `${lockPath}.stale.${owner.nonce}.${attempt}`;
511
- try {
512
- renameSync(lockPath, tombstone);
513
- unlinkSync(tombstone);
514
- } catch (error) {
515
- if (error.code !== "ENOENT") throw error;
516
- }
517
- }
518
- throw new SearchLedgerIntegrityError(`could not acquire search ledger lock ${lockPath}`);
519
- }
520
- function releaseLock(lockPath, owner) {
521
- if (!existsSync2(lockPath)) return;
522
- const holder = readOwner(lockPath);
523
- if (canonicalOwner(holder) !== canonicalOwner(owner)) {
524
- throw new SearchLedgerIntegrityError(
525
- `search ledger lock owner changed before release (${lockPath})`
526
- );
527
- }
528
- unlinkSync(lockPath);
529
- }
530
- function readOwner(lockPath) {
531
- let raw;
532
- try {
533
- raw = JSON.parse(readFileSync2(lockPath, "utf8"));
534
- } catch (error) {
535
- throw new SearchLedgerIntegrityError(`search ledger lock ${lockPath} is malformed`, {
536
- cause: error
537
- });
538
- }
539
- const parsed = z.object({
540
- pid: z.number().int().positive().safe(),
541
- host: z.string().trim().min(1),
542
- nonce: z.string().trim().min(1)
543
- }).strict().safeParse(raw);
544
- if (!parsed.success) {
545
- throw new SearchLedgerIntegrityError(
546
- `search ledger lock ${lockPath} is malformed: ${parsed.error.issues.map((issue) => `${issue.path.join(".") || "<root>"}: ${issue.message}`).join("; ")}`
547
- );
548
- }
549
- return parsed.data;
550
- }
551
- function canonicalOwner(owner) {
552
- return JSON.stringify({ host: owner.host, nonce: owner.nonce, pid: owner.pid });
553
- }
554
- function isProcessAlive(pid) {
555
- try {
556
- process.kill(pid, 0);
557
- return true;
558
- } catch (error) {
559
- return error.code !== "ESRCH";
560
- }
561
- }
562
- function unlinkIfExists(path) {
563
- try {
564
- unlinkSync(path);
565
- } catch (error) {
566
- if (error.code !== "ENOENT") throw error;
567
- }
568
- }
569
-
570
- // src/campaign/storage.ts
571
- function fsCampaignStorage() {
572
- const nodeRequire = createRequire(import.meta.url);
573
- const { existsSync: existsSync3, mkdirSync: mkdirSync2, readFileSync: readFileSync3, statSync, writeFileSync } = nodeRequire(
574
- "node:fs"
575
- );
576
- return {
577
- ensureDir(dir) {
578
- if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
579
- },
580
- exists(path) {
581
- return existsSync3(path);
582
- },
583
- read(path) {
584
- try {
585
- return readFileSync3(path, "utf8");
586
- } catch {
587
- return void 0;
588
- }
589
- },
590
- write(path, content) {
591
- writeFileSync(path, content);
592
- },
593
- append(path, content, expectedBytes) {
594
- const result = tryWithSearchLedgerFileLock(path, () => {
595
- let actualBytes = 0;
596
- try {
597
- actualBytes = statSync(path).size;
598
- } catch (error) {
599
- if (error.code !== "ENOENT") throw error;
600
- }
601
- if (actualBytes !== expectedBytes) return void 0;
602
- appendSearchLedgerLine(path, content);
603
- return expectedBytes + Buffer.byteLength(content);
604
- });
605
- return result.acquired ? result.value : void 0;
606
- }
607
- };
608
- }
609
- function inMemoryCampaignStorage() {
610
- const files = /* @__PURE__ */ new Map();
611
- const dirs = /* @__PURE__ */ new Set();
612
- return {
613
- ensureDir(dir) {
614
- dirs.add(dir);
615
- },
616
- exists(path) {
617
- return files.has(path) || dirs.has(path);
618
- },
619
- read(path) {
620
- const value = files.get(path);
621
- if (value === void 0) return void 0;
622
- return typeof value === "string" ? value : new TextDecoder().decode(value);
623
- },
624
- write(path, content) {
625
- files.set(path, content);
626
- },
627
- append(path, content, expectedBytes) {
628
- const current = files.get(path);
629
- const currentText = current === void 0 ? "" : typeof current === "string" ? current : new TextDecoder().decode(current);
630
- const currentBytes = new TextEncoder().encode(currentText).byteLength;
631
- if (currentBytes !== expectedBytes) return void 0;
632
- files.set(path, `${currentText}${content}`);
633
- return currentBytes + new TextEncoder().encode(content).byteLength;
634
- }
635
- };
636
- }
637
- function createRunCostLedger(input) {
638
- const path = join2(input.runDir, "cost-ledger.jsonl");
639
- input.storage.ensureDir(input.runDir);
640
- return new CostLedger({
641
- costCeilingUsd: input.costCeilingUsd,
642
- persistence: {
643
- read: () => {
644
- const stored = input.storage.read(path);
645
- if (stored === void 0 && input.storage.exists(path)) {
646
- throw new Error(`CostLedger: cannot read existing event log '${path}'`);
647
- }
648
- const events = stored ?? "";
649
- return {
650
- revision: String(new TextEncoder().encode(events).byteLength),
651
- events
652
- };
653
- },
654
- append: (expectedRevision, event) => {
655
- const expectedBytes = Number(expectedRevision);
656
- if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {
657
- throw new Error(`CostLedger: invalid storage revision '${expectedRevision}'`);
658
- }
659
- if (!input.storage.append) {
660
- throw new Error("CostLedger: CampaignStorage.append is required for paid calls");
661
- }
662
- const next = input.storage.append(path, event, expectedBytes);
663
- return next === void 0 ? void 0 : String(next);
664
- }
665
- }
666
- });
667
- }
668
-
669
- // src/campaign/run-campaign.ts
670
- async function runCampaign(opts) {
671
- const seed = opts.seed ?? 42;
672
- const reps = opts.reps ?? 1;
673
- const resumable = opts.resumable ?? true;
674
- const now = opts.now ?? (() => /* @__PURE__ */ new Date());
675
- const judges = opts.judges ?? [];
676
- const storage = opts.storage ?? fsCampaignStorage();
677
- const costPhase = opts.costPhase ?? "campaign";
678
- assertCampaignDesign(opts.scenarios, reps);
679
- if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
680
- throw new Error("runCampaign: runDir is required and must be a non-empty string");
681
- }
682
- opts.runDir = resolveRunDir(opts.runDir, opts.repo);
683
- storage.ensureDir(opts.runDir);
684
- const costLedger = opts.costLedger ?? createRunCostLedger({
685
- storage,
686
- runDir: opts.runDir,
687
- costCeilingUsd: opts.costCeiling
688
- });
689
- if (opts.costCeiling !== void 0 && costLedger.costCeilingUsd !== opts.costCeiling) {
690
- throw new Error("runCampaign: costCeiling must match the shared CostLedger ceiling");
691
- }
692
- const maxConcurrency = opts.maxConcurrency ?? 2;
693
- const manifestHash = computeManifestHash({
694
- scenarios: opts.scenarios,
695
- judges,
696
- dispatchRef: dispatchRefFor(opts.dispatch, opts.dispatchRef),
697
- seed,
698
- reps
699
- });
700
- const splitDigest = campaignSplitDigest(opts.scenarios, reps);
701
- const startedAt = now();
702
- const runAttemptId = globalThis.crypto.randomUUID();
703
- const cells = [];
704
- const artifactsByPath = {};
705
- const schedule = buildCellSchedule(opts.scenarios, seed, reps);
706
- const abortController = new AbortController();
707
- const lanes = [];
708
- let nextIdx = 0;
709
- const cellsRef = cells;
710
- for (let i = 0; i < maxConcurrency; i++) {
711
- lanes.push(
712
- (async () => {
713
- while (true) {
714
- const myIdx = nextIdx++;
715
- if (myIdx >= schedule.length) return;
716
- const slot = schedule[myIdx];
717
- const result = await executeCell({
718
- slot,
719
- opts,
720
- manifestHash,
721
- resumable,
722
- now,
723
- storage,
724
- buildTraceWriter: opts.buildTraceWriter ?? defaultBuildTraceWriter(storage),
725
- signal: abortController.signal,
726
- dispatchTimeoutMs: opts.dispatchTimeoutMs,
727
- costLedger,
728
- costPhase,
729
- runAttemptId
730
- });
731
- cellsRef.push(result.cell);
732
- Object.assign(artifactsByPath, result.artifactsByPath);
733
- if (opts.labeledStore && opts.labeledStore !== "off" && !result.cell.error) {
734
- await captureToStore({
735
- store: opts.labeledStore,
736
- cell: result.cell,
737
- scenario: slot.scenario,
738
- opts,
739
- now
740
- }).catch((err) => {
741
- console.warn(
742
- `[runCampaign] capture failed for ${result.cell.cellId}: ${err instanceof Error ? err.message : String(err)}`
743
- );
744
- });
745
- }
746
- }
747
- })()
748
- );
749
- }
750
- await Promise.all(lanes);
751
- const endedAt = now();
752
- cellsRef.sort((a, b) => a.cellId.localeCompare(b.cellId));
753
- const campaignCost = costLedger.summary({ tags: { runDir: opts.runDir } });
754
- const aggregates = computeAggregates(
755
- cellsRef,
756
- judges,
757
- seed,
758
- campaignCost
759
- );
760
- return {
761
- manifestHash,
762
- splitDigest,
763
- seed,
764
- reps,
765
- startedAt: startedAt.toISOString(),
766
- endedAt: endedAt.toISOString(),
767
- durationMs: endedAt.getTime() - startedAt.getTime(),
768
- cells: cellsRef,
769
- aggregates,
770
- runDir: opts.runDir,
771
- artifactsByPath,
772
- scenarios: opts.scenarios.map(campaignScenarioIdentity)
773
- };
774
- }
775
- async function executeCell(args) {
776
- const storage = args.storage;
777
- const cellDir = join3(args.opts.runDir, args.slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"));
778
- storage.ensureDir(cellDir);
779
- const stableCostTags = {
780
- runDir: args.opts.runDir,
781
- cellId: args.slot.cellId,
782
- scenarioId: args.slot.scenario.id,
783
- rep: String(args.slot.rep)
784
- };
785
- const costTags = { ...stableCostTags, runAttemptId: args.runAttemptId };
786
- const cachePath = join3(cellDir, "cached-result.json");
787
- if (args.resumable) {
788
- const cached = readCachedCell({
789
- storage,
790
- cachePath,
791
- cellId: args.slot.cellId,
792
- manifestHash: args.manifestHash
793
- });
794
- if (cached.status === "hit") {
795
- enforceDispatchUsage(cached.cell, args.opts.expectUsage ?? "warn");
796
- const cachedHasUsage = cached.cell.costUsd > 0 || cached.cell.tokenUsage.input > 0 || cached.cell.tokenUsage.output > 0;
797
- if (cached.cell.costCallIds === void 0) {
798
- if (cachedHasUsage || Object.keys(cached.cell.judgeScores).length > 0) {
799
- throw new CostAccountingIncompleteError(
800
- `runCampaign: cached cell '${args.slot.cellId}' does not identify its ledger receipts`
801
- );
802
- }
803
- } else if (!Array.isArray(cached.cell.costCallIds) || cached.cell.costCallIds.some(
804
- (callId) => typeof callId !== "string" || callId.trim().length === 0
805
- ) || new Set(cached.cell.costCallIds).size !== cached.cell.costCallIds.length) {
806
- throw new CostAccountingIncompleteError(
807
- `runCampaign: cached cell '${args.slot.cellId}' has invalid ledger receipt IDs`
808
- );
809
- } else {
810
- const restoredCallIds = new Set(
811
- args.costLedger.list({ tags: stableCostTags }).map((receipt) => receipt.callId)
812
- );
813
- const missingCallIds = cached.cell.costCallIds.filter(
814
- (callId) => !restoredCallIds.has(callId)
815
- );
816
- if (missingCallIds.length > 0) {
817
- throw new CostAccountingIncompleteError(
818
- `runCampaign: cached cell '${args.slot.cellId}' is missing ledger receipt(s): ${missingCallIds.join(", ")}`
819
- );
820
- }
821
- }
822
- return { cell: { ...cached.cell, cached: true }, artifactsByPath: {} };
823
- }
824
- }
825
- const startMs = Date.now();
826
- const trace = args.buildTraceWriter(args.slot.cellId, cellDir);
827
- const artifactsByPath = {};
828
- const artifacts = {
829
- async write(path, content) {
830
- const fullPath = join3(cellDir, path);
831
- storage.ensureDir(join3(fullPath, ".."));
832
- storage.write(fullPath, content);
833
- artifactsByPath[`${args.slot.cellId}/${path}`] = fullPath;
834
- return fullPath;
835
- },
836
- async writeJson(path, value) {
837
- return artifacts.write(path, JSON.stringify(value, null, 2));
838
- }
839
- };
840
- const cost = {
841
- async runPaidCall(input) {
842
- const result = await args.costLedger.runPaidCall({
843
- ...input,
844
- channel: input.channel ?? "agent",
845
- phase: args.costPhase,
846
- actor: input.actor,
847
- tags: costTags,
848
- signal: cellAbort.signal
849
- });
850
- if (result.receipt) {
851
- trace.span(`cost.${result.receipt.actor}`, { amountUsd: result.receipt.costUsd }).end();
852
- }
853
- return result;
854
- }
855
- };
856
- const placement = args.opts.cellPlacement?.({
857
- scenario: args.slot.scenario,
858
- rep: args.slot.rep
859
- });
860
- const cellAbort = new AbortController();
861
- const onCampaignAbort = () => cellAbort.abort(args.signal.reason);
862
- if (args.signal.aborted) cellAbort.abort(args.signal.reason);
863
- else args.signal.addEventListener("abort", onCampaignAbort, { once: true });
864
- const ctx = {
865
- cellId: args.slot.cellId,
866
- rep: args.slot.rep,
867
- seed: args.slot.cellSeed,
868
- signal: cellAbort.signal,
869
- trace,
870
- artifacts,
871
- cost,
872
- placement
873
- };
874
- let artifact;
875
- let errorMessage;
876
- const timeoutMs = args.dispatchTimeoutMs;
877
- let timeoutTimer;
878
- try {
879
- const dispatched = args.opts.dispatch(args.slot.scenario, ctx);
880
- if (timeoutMs !== void 0 && timeoutMs > 0) {
881
- artifact = await Promise.race([
882
- dispatched,
883
- new Promise((_, reject) => {
884
- timeoutTimer = setTimeout(() => {
885
- cellAbort.abort(new Error("dispatch timeout"));
886
- reject(
887
- new Error(
888
- `dispatch exceeded ${timeoutMs}ms for cell '${args.slot.cellId}' \u2014 aborted and failed loud (no silent hang)`
889
- )
890
- );
891
- }, timeoutMs);
892
- if (typeof timeoutTimer.unref === "function")
893
- timeoutTimer.unref();
894
- })
895
- ]);
896
- } else {
897
- artifact = await dispatched;
898
- }
899
- } catch (err) {
900
- errorMessage = err instanceof Error ? err.message : String(err);
901
- } finally {
902
- if (timeoutTimer) clearTimeout(timeoutTimer);
903
- args.signal.removeEventListener("abort", onCampaignAbort);
904
- }
905
- const agentReceipts = args.costLedger.list({ channel: "agent", tags: costTags });
906
- const agentCost = args.costLedger.summary({ channel: "agent", tags: costTags });
907
- const tokenUsage = {
908
- input: agentCost.inputTokens,
909
- output: agentCost.outputTokens,
910
- ...agentCost.cachedTokens > 0 ? { cached: agentCost.cachedTokens } : {}
911
- };
912
- const resolvedModel = agentReceipts.at(-1)?.model;
913
- const dispatchResult = {
914
- cellId: args.slot.cellId,
915
- artifact,
916
- error: errorMessage,
917
- costUsd: agentCost.totalCostUsd,
918
- tokenUsage
919
- };
920
- try {
921
- enforceDispatchUsage(dispatchResult, args.opts.expectUsage ?? "warn");
922
- } catch (error) {
923
- await trace.flush();
924
- throw error;
925
- }
926
- const judgeScores = {};
927
- if (artifact !== void 0) {
928
- for (const judge of args.opts.judges ?? []) {
929
- if (judge.appliesTo && !judge.appliesTo(args.slot.scenario)) continue;
930
- try {
931
- const score = await runJudgeCell(judge, {
932
- artifact,
933
- scenario: args.slot.scenario,
934
- signal: args.signal,
935
- costLedger: args.costLedger,
936
- costPhase: args.costPhase,
937
- costTags
938
- });
939
- judgeScores[judge.name] = score;
940
- } catch (err) {
941
- if (err instanceof CostAccountingIncompleteError) {
942
- await trace.flush();
943
- throw err;
944
- }
945
- errorMessage = `judge '${judge.name}' failed: ${err instanceof Error ? err.message : String(err)}`;
946
- break;
947
- }
948
- }
949
- }
950
- await trace.flush();
951
- const costCallIds = args.costLedger.list({ tags: costTags }).map((receipt) => receipt.callId).sort();
952
- const cell = {
953
- manifestHash: args.manifestHash,
954
- cellId: args.slot.cellId,
955
- scenarioId: args.slot.scenario.id,
956
- rep: args.slot.rep,
957
- artifact: artifact ?? null,
958
- judgeScores,
959
- costUsd: agentCost.totalCostUsd,
960
- costEstimated: agentReceipts.some(
961
- (receipt) => receipt.actualCostUsd === void 0 && !receipt.costUnknown
962
- ),
963
- costCallIds,
964
- tokenUsage,
965
- ...resolvedModel ? { resolvedModel } : {},
966
- durationMs: Date.now() - startMs,
967
- seed: args.slot.cellSeed,
968
- cached: false,
969
- error: errorMessage
970
- };
971
- if (!errorMessage && args.resumable) {
972
- storage.write(cachePath, JSON.stringify(cell));
973
- }
974
- return { cell, artifactsByPath };
975
- }
976
- function planCampaignRun(opts) {
977
- const seed = opts.seed ?? 42;
978
- const reps = opts.reps ?? 1;
979
- const resumable = opts.resumable ?? true;
980
- const storage = opts.storage ?? fsCampaignStorage();
981
- assertCampaignDesign(opts.scenarios, reps);
982
- if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
983
- throw new Error("planCampaignRun: runDir is required and must be a non-empty string");
984
- }
985
- opts.runDir = resolveRunDir(opts.runDir, opts.repo);
986
- const manifestHash = computeManifestHash({
987
- scenarios: opts.scenarios,
988
- judges: opts.judges ?? [],
989
- dispatchRef: dispatchRefFor(opts.dispatch, opts.dispatchRef),
990
- seed,
991
- reps
992
- });
993
- const splitDigest = campaignSplitDigest(opts.scenarios, reps);
994
- const cells = buildCellSchedule(opts.scenarios, seed, reps).map((slot) => {
995
- const cachePath = join3(
996
- opts.runDir,
997
- slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"),
998
- "cached-result.json"
999
- );
1000
- if (!resumable) {
1001
- return {
1002
- cellId: slot.cellId,
1003
- scenarioId: slot.scenario.id,
1004
- rep: slot.rep,
1005
- seed: slot.cellSeed,
1006
- cachePath,
1007
- status: "run",
1008
- reason: "resumable-off"
1009
- };
1010
- }
1011
- const cached = readCachedCell({
1012
- storage,
1013
- cachePath,
1014
- cellId: slot.cellId,
1015
- manifestHash
1016
- });
1017
- if (cached.status === "hit") {
1018
- return {
1019
- cellId: slot.cellId,
1020
- scenarioId: slot.scenario.id,
1021
- rep: slot.rep,
1022
- seed: slot.cellSeed,
1023
- cachePath,
1024
- status: "cached"
1025
- };
1026
- }
1027
- return {
1028
- cellId: slot.cellId,
1029
- scenarioId: slot.scenario.id,
1030
- rep: slot.rep,
1031
- seed: slot.cellSeed,
1032
- cachePath,
1033
- status: "run",
1034
- reason: cached.reason
1035
- };
1036
- });
1037
- const cellsCached = cells.filter((cell) => cell.status === "cached").length;
1038
- return {
1039
- manifestHash,
1040
- splitDigest,
1041
- totalCells: cells.length,
1042
- cellsCached,
1043
- cellsToRun: cells.length - cellsCached,
1044
- cells
1045
- };
1046
- }
1047
- function enforceDispatchUsage(cell, mode) {
1048
- if (mode === "off") return;
1049
- if (cell.artifact === null || cell.artifact === void 0) return;
1050
- const zeroTokens = cell.tokenUsage.input === 0 && cell.tokenUsage.output === 0;
1051
- if (cell.costUsd !== 0 || !zeroTokens) return;
1052
- 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)`;
1053
- if (mode === "assert") {
1054
- const report = {
1055
- totalRecords: 1,
1056
- stubRecords: 1,
1057
- realRecords: 0,
1058
- uncostedRecords: 0,
1059
- totalInputTokens: 0,
1060
- totalOutputTokens: 0,
1061
- totalCostUsd: 0,
1062
- verdict: "stub",
1063
- diagnosis: msg
1064
- };
1065
- throw new BackendIntegrityError(`expectUsage: ${msg}`, report);
1066
- }
1067
- console.warn(`[runCampaign] expectUsage: ${msg}`);
1068
- }
1069
- async function runJudgeCell(judge, input) {
1070
- const previousJudgeCalls = new Set(
1071
- input.costLedger?.list({ channel: "judge", tags: input.costTags }).map((receipt) => receipt.callId) ?? []
1072
- );
1073
- try {
1074
- const score = await judge.score(input);
1075
- assertReportedJudgeCallRecorded(judge.name, score, input, previousJudgeCalls);
1076
- return score;
1077
- } catch (error) {
1078
- assertReportedJudgeCallRecorded(judge.name, error, input, previousJudgeCalls, error);
1079
- throw error;
1080
- }
1081
- }
1082
- function assertReportedJudgeCallRecorded(judgeName, value, input, previousCallIds, cause) {
1083
- if (!hasLlmCall(value)) return;
1084
- const recorded = input.costLedger?.list({ channel: "judge", tags: input.costTags }).some((receipt) => !previousCallIds.has(receipt.callId));
1085
- if (recorded) return;
1086
- throw new CostAccountingIncompleteError(
1087
- `runCampaign: judge '${judgeName}' reported a paid LLM call without a CostLedger receipt`,
1088
- cause === void 0 ? void 0 : { cause }
1089
- );
1090
- }
1091
- function hasLlmCall(value) {
1092
- return typeof value === "object" && value !== null && "llmCall" in value && value.llmCall !== void 0;
1093
- }
1094
- function defaultBuildTraceWriter(storage) {
1095
- return (cellId, dir) => {
1096
- const spans = [];
1097
- return {
1098
- span(name, attributes) {
1099
- const startMs = Date.now();
1100
- const record = { name, cellId, startMs, ...attributes ?? {} };
1101
- const finish = {
1102
- end(endAttrs) {
1103
- record.durationMs = Date.now() - startMs;
1104
- if (endAttrs) Object.assign(record, endAttrs);
1105
- spans.push(record);
1106
- },
1107
- setAttribute(key, value) {
1108
- record[key] = value;
1109
- }
1110
- };
1111
- return finish;
1112
- },
1113
- async flush() {
1114
- storage.write(join3(dir, "spans.jsonl"), spans.map((s) => JSON.stringify(s)).join("\n"));
1115
- }
1116
- };
1117
- };
1118
- }
1119
- function buildCellSchedule(scenarios, seed, reps) {
1120
- const schedule = [];
1121
- let cellIndex = 0;
1122
- for (const scenario of scenarios) {
1123
- for (let rep = 0; rep < reps; rep++) {
1124
- const cellId = `${scenario.id}:${rep}`;
1125
- const cellSeed = seed + cellIndex;
1126
- schedule.push({ scenario, rep, cellId, cellSeed });
1127
- cellIndex += 1;
1128
- }
1129
- }
1130
- return schedule;
1131
- }
1132
- function dispatchRefFor(dispatch, override) {
1133
- const ref = override ?? dispatch?.name ?? "anonymous";
1134
- if (typeof ref !== "string" || ref.trim().length === 0) {
1135
- throw new Error("runCampaign: dispatchRef must be a non-empty string when provided");
1136
- }
1137
- return ref;
1138
- }
1139
- function readCachedCell(args) {
1140
- const raw = args.storage.read(args.cachePath);
1141
- if (raw === void 0) return { status: "miss", reason: "missing" };
1142
- try {
1143
- const cached = JSON.parse(raw);
1144
- if (cached.cellId !== args.cellId) return { status: "miss", reason: "cell-mismatch" };
1145
- if (cached.manifestHash !== args.manifestHash) {
1146
- return { status: "miss", reason: "manifest-mismatch" };
1147
- }
1148
- return { status: "hit", cell: cached };
1149
- } catch {
1150
- return { status: "miss", reason: "corrupt" };
1151
- }
1152
- }
1153
- async function captureToStore(args) {
1154
- await args.store.observe({
1155
- scenario: args.scenario,
1156
- artifact: args.cell.artifact,
1157
- judgeScores: args.cell.judgeScores,
1158
- source: args.opts.captureSource ?? "eval-run",
1159
- sourceVersionHash: args.opts.captureSourceVersionHash ?? "unknown",
1160
- capturedAt: args.now().toISOString(),
1161
- redactionStatus: "raw"
1162
- });
1163
- }
1164
- function computeManifestHash(input) {
1165
- return contentHash({
1166
- scenarios: input.scenarios,
1167
- judges: input.judges.map((judge) => ({
1168
- name: judge.name,
1169
- dims: judge.dimensions,
1170
- version: judgeVersionFor(judge)
1171
- })),
1172
- dispatch: input.dispatchRef,
1173
- seed: input.seed,
1174
- reps: input.reps
1175
- });
1176
- }
1177
- function judgeVersionFor(judge) {
1178
- if (judge.judgeVersion !== void 0) {
1179
- const version = judge.judgeVersion.trim();
1180
- if (version.length === 0) {
1181
- throw new Error(`runCampaign: judge '${judge.name}' has an empty judgeVersion`);
1182
- }
1183
- return version;
1184
- }
1185
- return contentHash({
1186
- score: judge.score.toString(),
1187
- appliesTo: judge.appliesTo?.toString() ?? null
1188
- });
1189
- }
1190
- function computeAggregates(cells, judges, seed, cost) {
1191
- const byJudge = {};
1192
- for (const judge of judges) {
1193
- const scores = [];
1194
- for (const cell of cells) {
1195
- const s = cell.judgeScores[judge.name];
1196
- if (s !== void 0) scores.push(s.composite);
1197
- }
1198
- byJudge[judge.name] = aggregate(scores, seed);
1199
- }
1200
- const byScenario = {};
1201
- const scenarioGroups = /* @__PURE__ */ new Map();
1202
- for (const cell of cells) {
1203
- const composites = Object.values(cell.judgeScores).map((s) => s.composite);
1204
- if (composites.length === 0) continue;
1205
- const mean = composites.reduce((a, b) => a + b, 0) / composites.length;
1206
- const arr = scenarioGroups.get(cell.scenarioId) ?? [];
1207
- arr.push(mean);
1208
- scenarioGroups.set(cell.scenarioId, arr);
1209
- }
1210
- for (const [scenarioId, samples] of scenarioGroups) {
1211
- const ag = aggregate(samples, seed);
1212
- byScenario[scenarioId] = { meanComposite: ag.mean, ci95: ag.ci95, n: ag.n };
1213
- }
1214
- return {
1215
- byJudge,
1216
- byScenario,
1217
- cost,
1218
- totalCostUsd: cost.totalCostUsd,
1219
- cellsExecuted: cells.filter((c) => !c.error).length,
1220
- cellsSkipped: cells.filter((c) => c.error?.startsWith("skipped:")).length,
1221
- cellsCached: cells.filter((c) => c.cached).length,
1222
- cellsFailed: cells.filter((c) => c.error && !c.error.startsWith("skipped:")).length
1223
- };
1224
- }
1225
- function aggregate(samples, seed) {
1226
- const n = samples.length;
1227
- if (n === 0) return { mean: 0, stdev: 0, ci95: [0, 0], n: 0 };
1228
- const mean = samples.reduce((a, b) => a + b, 0) / n;
1229
- const variance = samples.reduce((a, b) => a + (b - mean) ** 2, 0) / Math.max(1, n - 1);
1230
- const stdev = Math.sqrt(variance);
1231
- const ci = confidenceInterval(samples, 0.95, { seed, resamples: 1e3 });
1232
- return { mean, stdev, ci95: [ci.lower, ci.upper], n };
1233
- }
1234
-
1235
- export {
1236
- BackendIntegrityError,
1237
- summarizeBackendIntegrity,
1238
- summarizeAgentReceiptIntegrity,
1239
- assertRealBackend,
1240
- assertRealAgentReceipts,
1241
- canonicalJson,
1242
- contentHash,
1243
- inMemoryVerdictCache,
1244
- fileVerdictCache,
1245
- cachedJudge,
1246
- assertCampaignDesign,
1247
- campaignScenarioIdentity,
1248
- campaignSplitDigestFromIdentities,
1249
- campaignSplitDigest,
1250
- assertCampaignSplitIdentity,
1251
- campaignCoverage,
1252
- formatCoverageFailures,
1253
- tangleTracesRoot,
1254
- resolveRunDir,
1255
- SearchLedgerError,
1256
- SearchLedgerIntegrityError,
1257
- SearchLedgerConflictError,
1258
- appendSearchLedgerLine,
1259
- withSearchLedgerFileLock,
1260
- fsCampaignStorage,
1261
- inMemoryCampaignStorage,
1262
- createRunCostLedger,
1263
- runCampaign,
1264
- planCampaignRun
1265
- };
1266
- //# sourceMappingURL=chunk-XDIRG3TO.js.map