@tangle-network/agent-eval 0.117.1 → 0.118.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 (143) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/analyst/index.d.ts +2772 -21
  3. package/dist/analyst/index.js +7 -6
  4. package/dist/analyst/index.js.map +1 -1
  5. package/dist/belief-state/index.d.ts +706 -10
  6. package/dist/belief-state/index.js +2 -1
  7. package/dist/belief-state/index.js.map +1 -1
  8. package/dist/benchmarks/index.d.ts +958 -14
  9. package/dist/benchmarks/index.js +12 -10
  10. package/dist/builder-eval/index.d.ts +449 -4
  11. package/dist/builder-eval/index.js +4 -3
  12. package/dist/builder-eval/index.js.map +1 -1
  13. package/dist/campaign/index.d.ts +4275 -73
  14. package/dist/campaign/index.js +12 -10
  15. package/dist/{chunk-VF3XSYTI.js → chunk-33JA4TFA.js} +6 -6
  16. package/dist/{chunk-4JLWXDYA.js → chunk-3EHHMC6E.js} +2 -2
  17. package/dist/{chunk-CCZIVI3F.js → chunk-BFW56GTT.js} +2 -2
  18. package/dist/{chunk-YZPO4UHR.js → chunk-DP3BBMYJ.js} +100 -143
  19. package/dist/chunk-DP3BBMYJ.js.map +1 -0
  20. package/dist/{chunk-E4BUPP7Z.js → chunk-HKUCJ437.js} +38 -66
  21. package/dist/chunk-HKUCJ437.js.map +1 -0
  22. package/dist/{chunk-HZHNRYHK.js → chunk-K6N6XJJX.js} +2 -2
  23. package/dist/chunk-KSDQVPLR.js +286 -0
  24. package/dist/chunk-KSDQVPLR.js.map +1 -0
  25. package/dist/chunk-MA6HLL3S.js +65 -0
  26. package/dist/chunk-MA6HLL3S.js.map +1 -0
  27. package/dist/{chunk-MGEHEHSN.js → chunk-OIIMMLRB.js} +11 -11
  28. package/dist/{chunk-DXZRATT5.js → chunk-OYZAPX5G.js} +3 -3
  29. package/dist/chunk-PXE2VKMX.js +140 -0
  30. package/dist/chunk-PXE2VKMX.js.map +1 -0
  31. package/dist/{chunk-JSJZ4PJ6.js → chunk-Q442S5AS.js} +17 -17
  32. package/dist/{chunk-ODVOOEWQ.js → chunk-QBRSJK47.js} +2 -2
  33. package/dist/{chunk-S2F4J57L.js → chunk-QKEGNI5B.js} +77 -32
  34. package/dist/chunk-QKEGNI5B.js.map +1 -0
  35. package/dist/{chunk-5UF54T55.js → chunk-S3UZOQ5Y.js} +34 -7
  36. package/dist/chunk-S3UZOQ5Y.js.map +1 -0
  37. package/dist/{chunk-FQNLDL4D.js → chunk-SVH2ANFD.js} +136 -4
  38. package/dist/chunk-SVH2ANFD.js.map +1 -0
  39. package/dist/{chunk-GQCZRZ7L.js → chunk-U5CHZ5M3.js} +9 -9
  40. package/dist/{chunk-TVVP3ZZQ.js → chunk-VQMK5FMP.js} +2 -1
  41. package/dist/{chunk-TVVP3ZZQ.js.map → chunk-VQMK5FMP.js.map} +1 -1
  42. package/dist/chunk-WDHBCA3M.js +31 -0
  43. package/dist/chunk-WDHBCA3M.js.map +1 -0
  44. package/dist/{chunk-HQPHZGL6.js → chunk-YLMUS4MM.js} +9 -9
  45. package/dist/{chunk-LQUTGLOZ.js → chunk-ZET2UAYW.js} +15 -65
  46. package/dist/chunk-ZET2UAYW.js.map +1 -0
  47. package/dist/contract/index.d.ts +4012 -38
  48. package/dist/contract/index.js +56 -29
  49. package/dist/contract/index.js.map +1 -1
  50. package/dist/control.d.ts +1013 -9
  51. package/dist/control.js +4 -3
  52. package/dist/fuzz.d.ts +194 -4
  53. package/dist/fuzz.js +3 -3
  54. package/dist/hosted/index.d.ts +498 -17
  55. package/dist/index.d.ts +10982 -1286
  56. package/dist/index.js +97 -80
  57. package/dist/index.js.map +1 -1
  58. package/dist/matrix/index.d.ts +139 -4
  59. package/dist/meta-eval/index.d.ts +862 -15
  60. package/dist/meta-eval/index.js +2 -1
  61. package/dist/meta-eval/index.js.map +1 -1
  62. package/dist/multishot/index.d.ts +214 -14
  63. package/dist/openapi.json +1 -1
  64. package/dist/pipelines/index.d.ts +392 -7
  65. package/dist/pipelines/index.js +5 -3
  66. package/dist/pipelines/index.js.map +1 -1
  67. package/dist/reporting.d.ts +1277 -17
  68. package/dist/rl.d.ts +2359 -28
  69. package/dist/rl.js +6 -5
  70. package/dist/rl.js.map +1 -1
  71. package/dist/storyboard/index.d.ts +86 -1
  72. package/dist/trace-attributes.d.ts +16 -0
  73. package/dist/trace-attributes.js +32 -0
  74. package/dist/trace-attributes.js.map +1 -0
  75. package/dist/traces.d.ts +1970 -697
  76. package/dist/traces.js +49 -32
  77. package/dist/wire/index.d.ts +655 -9
  78. package/docs/insight-report.md +44 -0
  79. package/package.json +9 -3
  80. package/dist/adversarial-B7loGVVX.d.ts +0 -19
  81. package/dist/analyst-C8HHvfJp.d.ts +0 -88
  82. package/dist/analyze-runs--2x39HZ7.d.ts +0 -81
  83. package/dist/baseline-DKq3gJpP.d.ts +0 -141
  84. package/dist/calibration-C8MTS7cw.d.ts +0 -101
  85. package/dist/chunk-5UF54T55.js.map +0 -1
  86. package/dist/chunk-E4BUPP7Z.js.map +0 -1
  87. package/dist/chunk-FQNLDL4D.js.map +0 -1
  88. package/dist/chunk-LQUTGLOZ.js.map +0 -1
  89. package/dist/chunk-S2F4J57L.js.map +0 -1
  90. package/dist/chunk-YZPO4UHR.js.map +0 -1
  91. package/dist/code-agent-session-CjZsVd19.d.ts +0 -87
  92. package/dist/control-6vuGfmDH.d.ts +0 -258
  93. package/dist/cost-ledger-DWy3XdJc.d.ts +0 -183
  94. package/dist/dataset-NENEzRgk.d.ts +0 -115
  95. package/dist/default-registry-DaK8b3fv.d.ts +0 -155
  96. package/dist/emitter-CjD7vUwv.d.ts +0 -122
  97. package/dist/errors-oeQrLqXC.d.ts +0 -74
  98. package/dist/failure-cluster-DOAcSJ87.d.ts +0 -76
  99. package/dist/feedback-trajectory-BUnM58xL.d.ts +0 -348
  100. package/dist/gepa-eESocoDi.d.ts +0 -642
  101. package/dist/index-PdX4VnPA.d.ts +0 -423
  102. package/dist/insight-report-DY4nDW9Q.d.ts +0 -310
  103. package/dist/integrity-DqlBiLyK.d.ts +0 -81
  104. package/dist/judge-calibration-7C-IDmKr.d.ts +0 -145
  105. package/dist/kind-factory-ClZmO25A.d.ts +0 -171
  106. package/dist/llm-client-qoDd18Qz.d.ts +0 -289
  107. package/dist/multi-layer-verifier-BsqKuLyN.d.ts +0 -150
  108. package/dist/off-policy-DiwuKKg7.d.ts +0 -132
  109. package/dist/outcome-store-rnXLEqSn.d.ts +0 -63
  110. package/dist/policy-edit-wG9uFEFm.d.ts +0 -455
  111. package/dist/pre-registration-BWQhJ3vz.d.ts +0 -761
  112. package/dist/provenance-DpjwyseI.d.ts +0 -541
  113. package/dist/query-CF7PG61p.d.ts +0 -35
  114. package/dist/raw-provider-sink-C46HDghv.d.ts +0 -132
  115. package/dist/release-report-C8G2i5Xi.d.ts +0 -236
  116. package/dist/researcher-C8XyxQsu.d.ts +0 -387
  117. package/dist/rubric-predictive-validity-p49lLVrE.d.ts +0 -105
  118. package/dist/run-record-BDH49H2E.d.ts +0 -360
  119. package/dist/runtime-trajectory-DGBIUt4B.d.ts +0 -49
  120. package/dist/schema-B3Q3l9Z_.d.ts +0 -201
  121. package/dist/semantic-concept-judge-CXnPEJbf.d.ts +0 -723
  122. package/dist/sequential-5iSVfzl2.d.ts +0 -139
  123. package/dist/series-convergence-D5OWMBg6.d.ts +0 -33
  124. package/dist/statistics-KUnG73jH.d.ts +0 -494
  125. package/dist/storage-DrX3v_5B.d.ts +0 -50
  126. package/dist/store-C1YxJDEK.d.ts +0 -248
  127. package/dist/store-DGqD0Pyo.d.ts +0 -116
  128. package/dist/summary-report-C5bKFfm-.d.ts +0 -445
  129. package/dist/test-graded-scenario-B0ybnPY7.d.ts +0 -166
  130. package/dist/types-BSw1rOUB.d.ts +0 -634
  131. package/dist/types-BUxNaJ8c.d.ts +0 -108
  132. package/dist/types-BkfcQnxV.d.ts +0 -313
  133. package/dist/verdict-C9MlYujm.d.ts +0 -35
  134. /package/dist/{chunk-VF3XSYTI.js.map → chunk-33JA4TFA.js.map} +0 -0
  135. /package/dist/{chunk-4JLWXDYA.js.map → chunk-3EHHMC6E.js.map} +0 -0
  136. /package/dist/{chunk-CCZIVI3F.js.map → chunk-BFW56GTT.js.map} +0 -0
  137. /package/dist/{chunk-HZHNRYHK.js.map → chunk-K6N6XJJX.js.map} +0 -0
  138. /package/dist/{chunk-MGEHEHSN.js.map → chunk-OIIMMLRB.js.map} +0 -0
  139. /package/dist/{chunk-DXZRATT5.js.map → chunk-OYZAPX5G.js.map} +0 -0
  140. /package/dist/{chunk-JSJZ4PJ6.js.map → chunk-Q442S5AS.js.map} +0 -0
  141. /package/dist/{chunk-ODVOOEWQ.js.map → chunk-QBRSJK47.js.map} +0 -0
  142. /package/dist/{chunk-GQCZRZ7L.js.map → chunk-U5CHZ5M3.js.map} +0 -0
  143. /package/dist/{chunk-HQPHZGL6.js.map → chunk-YLMUS4MM.js.map} +0 -0
@@ -2,6 +2,9 @@
2
2
 
3
3
  The single shape every analysis call returns. `selfImprove()` embeds it in `SelfImproveResult.insight`; `analyzeRuns()` returns it directly. The hosted-tier wire format carries it on `EvalRunEvent.insightReport?`.
4
4
 
5
+ Use `summarizeExecution({ runs })` when observed traces have no task-quality labels.
6
+ It returns only `execution` and `costProvenance`, so callers do not need to fabricate a quality score to report runtime facts.
7
+
5
8
  Every section is **opt-in based on what your data supports** — the function never invents signal. If your runs don't carry judge scores, `judges` is empty. If there's no baseline/candidate split, `lift` is undefined. The shape is consistent; population is honest.
6
9
 
7
10
  This page walks every section with a real (synthetic) example and explains how to act on it.
@@ -13,6 +16,7 @@ This page walks every section with a real (synthetic) example and explains how t
13
16
  ```ts
14
17
  interface InsightReport {
15
18
  n: number // runs analyzed
19
+ execution: ExecutionInsight // duration, tokens, models, failures
16
20
  composite: ScalarDistribution // always
17
21
  perDimension: Record<string, ScalarDistribution> // when judgeScores carry dimensions
18
22
  costQuality: { cost: ScalarDistribution; pareto: ParetoFigureSpec } // always
@@ -29,6 +33,46 @@ interface InsightReport {
29
33
 
30
34
  ---
31
35
 
36
+ ## `execution` — runtime facts, separate from quality
37
+
38
+ Always present.
39
+ It reports duration, optional queue time, direct input, output, reasoning, cache-read, and cache-write tokens, model-call coverage, model cohorts, explicit failures, and separately reported orchestration aggregates.
40
+ These fields describe what ran; they do not claim whether the task succeeded.
41
+
42
+ ```jsonc
43
+ {
44
+ "execution": {
45
+ "durationMs": { "n": 30, "p50": 5400, "p95": 82000, "min": 900, "max": 190000 },
46
+ "queueMs": { "n": 0, "histogram": [] },
47
+ "tokenUsage": {
48
+ "totals": { "input": 50132, "output": 471783, "reasoning": 12000, "cached": 60489565, "cacheWrite": 3032227 },
49
+ "input": { "n": 30, "p50": 25, "p95": 56 },
50
+ "output": { "n": 30, "p50": 230, "p95": 2651 },
51
+ "reasoning": { "n": 12, "p50": 800, "p95": 2400 },
52
+ "cached": { "n": 20, "p50": 94193, "p95": 310178 },
53
+ "cacheWrite": { "n": 20, "p50": 3070, "p95": 11148 }
54
+ },
55
+ "aggregateUsage": {
56
+ "runs": 2,
57
+ "tokenUsage": {
58
+ "totals": { "input": 5000, "output": 176829, "reasoning": 0, "cached": 0, "cacheWrite": 0 }
59
+ },
60
+ "costUsd": { "n": 0 },
61
+ "totalCostUsd": 0
62
+ },
63
+ "modelCalls": { "runs": 20, "events": 42, "reportingRuns": 30 },
64
+ "models": [{ "model": "claude-opus@2026-07-01", "runs": 20 }],
65
+ "failures": { "runs": 2, "fraction": 0.067, "reportedErrorEvents": 3, "reportingRuns": 30 }
66
+ }
67
+ }
68
+ ```
69
+
70
+ Use `distribution.n` for optional fields to distinguish an uncaptured category from a recorded zero.
71
+ Never add `aggregateUsage` to direct `tokenUsage`: orchestration spans may repeat model-call usage from other traces.
72
+ Cost remains in `costQuality`, where observed, estimated, and uncaptured USD stay separate.
73
+
74
+ ---
75
+
32
76
  ## `n` + `composite` + `perDimension` — distributional summary
33
77
 
34
78
  Always present. The basic "where are my numbers" view.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-eval",
3
- "version": "0.117.1",
3
+ "version": "0.118.0",
4
4
  "description": "Evaluate and improve AI agents from runs, traces, judges, and feedback. Compare candidates, cluster failures, measure lift, and gate releases.",
5
5
  "homepage": "https://github.com/tangle-network/agent-eval#readme",
6
6
  "repository": {
@@ -49,6 +49,11 @@
49
49
  "import": "./dist/traces.js",
50
50
  "default": "./dist/traces.js"
51
51
  },
52
+ "./trace-attributes": {
53
+ "types": "./dist/trace-attributes.d.ts",
54
+ "import": "./dist/trace-attributes.js",
55
+ "default": "./dist/trace-attributes.js"
56
+ },
52
57
  "./wire": {
53
58
  "types": "./dist/wire/index.d.ts",
54
59
  "import": "./dist/wire/index.js",
@@ -130,7 +135,7 @@
130
135
  "access": "public"
131
136
  },
132
137
  "scripts": {
133
- "build": "tsup && pnpm openapi",
138
+ "build": "tsup && node scripts/build-declarations.mjs && pnpm openapi",
134
139
  "dev": "tsup --watch",
135
140
  "prepare": "husky",
136
141
  "prepublishOnly": "pnpm build",
@@ -146,7 +151,8 @@
146
151
  "@asteasolutions/zod-to-openapi": "^8.5.0",
147
152
  "@ax-llm/ax": "^19.0.25",
148
153
  "@hono/node-server": "^2.0.0",
149
- "@tangle-network/agent-interface": "^0.22.0",
154
+ "@tangle-network/agent-core": "^0.4.11",
155
+ "@tangle-network/agent-interface": "^0.26.0",
150
156
  "@tangle-network/tcloud": "^0.4.14",
151
157
  "hono": "^4.12.30",
152
158
  "zod": "^4.3.6"
@@ -1,19 +0,0 @@
1
- /**
2
- * Adversarial mutation contract.
3
- *
4
- * `AdversarialMutation<S>` is the scenario-mutation strategy the fuzz harness
5
- * (`fuzzAgent`, src/fuzz) drives: paraphrase, edge-case substitution, or
6
- * compositional combination of a scenario the policy currently passes, looking
7
- * for the tail inputs that break it. The harness supplies the loop; consumers
8
- * supply the mutations and the failure detector.
9
- */
10
- interface AdversarialMutation<S> {
11
- id: string;
12
- /**
13
- * Mutate one scenario. Return null to skip; return one or more new
14
- * scenarios. The harness deduplicates by `mutateScenarioId(scenario)`.
15
- */
16
- mutate(parent: S, rng: () => number): Promise<S[]> | S[];
17
- }
18
-
19
- export type { AdversarialMutation as A };
@@ -1,88 +0,0 @@
1
- import { AxAIService } from '@ax-llm/ax';
2
- import { T as TraceAnalysisStore } from './store-C1YxJDEK.js';
3
-
4
- interface AnalyzeTracesInput {
5
- /** The user-facing question. Domain framing belongs here, not in the
6
- * actor description. */
7
- question: string;
8
- }
9
- interface AnalyzeTracesResult {
10
- /** The responder's prose answer. */
11
- answer: string;
12
- /** Bulleted findings extracted from the responder's structured output. */
13
- findings: string[];
14
- /** Per-actor-turn snapshots captured via `actorTurnCallback`. */
15
- turns: AnalyzeTracesTurnSnapshot[];
16
- /** Total turns the actor took. */
17
- turnCount: number;
18
- /** Token usage by role. */
19
- usage: TraceAnalystUsage;
20
- /** Full system + assistant + tool message log by role. */
21
- chatLog: TraceAnalystChatLog;
22
- /** Prompt version that produced this run. */
23
- actorPromptVersion: string;
24
- }
25
- interface TraceAnalystUsage {
26
- actor: TraceAnalystUsageEntry[];
27
- responder: TraceAnalystUsageEntry[];
28
- }
29
- interface TraceAnalystUsageEntry {
30
- [key: string]: unknown;
31
- }
32
- interface TraceAnalystChatLog {
33
- actor: TraceAnalystChatMessage[];
34
- responder: TraceAnalystChatMessage[];
35
- }
36
- interface TraceAnalystChatMessage {
37
- [key: string]: unknown;
38
- }
39
- interface AnalyzeTracesTurnSnapshot {
40
- turn: number;
41
- isError: boolean;
42
- /** The JS code the actor produced for this turn. */
43
- code: string;
44
- /** The formatted action-log entry the actor sees on the next turn. */
45
- output: string;
46
- /** Provider thought (when `actorOptions.showThoughts` is true and the
47
- * provider returns it). */
48
- thought?: string;
49
- }
50
- interface AnalyzeTracesOptions {
51
- /** Trace data source. Pass either an OTLP-JSONL path or a custom store. */
52
- source: string | TraceAnalysisStore;
53
- /** Caller-provided AxAIService. */
54
- ai: AxAIService;
55
- /** Model id forwarded to actor + responder. */
56
- model?: string;
57
- /** Recursion depth. 0 = no sub-agent dispatch. Default 1. */
58
- maxDepth?: number;
59
- /** Maximum actor turns. Default 12. */
60
- maxTurns?: number;
61
- /** Maximum parallel sub-agent calls in batched llmQuery. Default 2. */
62
- maxParallelSubagents?: number;
63
- /** Override the actor description. */
64
- actorDescription?: string;
65
- /** Override the subagent description. */
66
- subagentDescription?: string;
67
- /** Per-turn observability hook. */
68
- onTurn?: (turn: AnalyzeTracesTurnSnapshot) => void | Promise<void>;
69
- /** Override max runtime characters per turn. Default 6000. */
70
- maxRuntimeChars?: number;
71
- /** When set, every turn's snapshot is appended to this JSONL file
72
- * immediately. If the analyst crashes mid-loop (provider 503,
73
- * network error, validator reject) the partial reasoning is still
74
- * on disk. Replay the file with the responder afterward to recover
75
- * evidence. */
76
- progressLogPath?: string;
77
- }
78
- /**
79
- * Run the trace analyst.
80
- *
81
- * Throws:
82
- * - `TraceFileMissingError` if `source` is a path and doesn't exist.
83
- * - `AxAgentClarificationError` if the analyst asks for clarification.
84
- * - Provider errors (auth, rate limits) propagate from the AI service.
85
- */
86
- declare function analyzeTraces(input: AnalyzeTracesInput, options: AnalyzeTracesOptions): Promise<AnalyzeTracesResult>;
87
-
88
- export { type AnalyzeTracesOptions as A, type AnalyzeTracesInput as a, type AnalyzeTracesResult as b, type AnalyzeTracesTurnSnapshot as c, analyzeTraces as d };
@@ -1,81 +0,0 @@
1
- import { A as AnalystRegistry } from './default-registry-DaK8b3fv.js';
2
- import { D as DatasetScenario } from './dataset-NENEzRgk.js';
3
- import { R as RunRecord } from './run-record-BDH49H2E.js';
4
- import { I as InsightReport } from './insight-report-DY4nDW9Q.js';
5
-
6
- /**
7
- * # `analyzeRuns()` — turn a set of agent runs into an actionable decision packet.
8
- *
9
- * Wires the substrate's statistical, calibration, clustering, Pareto, and
10
- * release-confidence primitives into one `InsightReport`. Two top-level
11
- * entry points use this function:
12
- *
13
- * - `selfImprove()` calls it on the campaign output to attach a packet
14
- * to every run.
15
- * - Consumers with observed `RunRecord[]` (production traces, gold
16
- * corpora, approve/reject tables) call it directly via `analyzeRuns()`
17
- * for analysis without a closed loop.
18
- *
19
- * Every section is opt-in based on what the input data supports — the
20
- * function never invents signal. If runs carry no judge scores, `judges`
21
- * is empty. If there's no baseline/candidate split, `lift` is undefined.
22
- * If no `analyst` is wired, `failureClusters` is undefined.
23
- *
24
- * The `recommendations` array is the human-readable layer; everything
25
- * else is the evidence backing each recommendation.
26
- */
27
-
28
- interface AnalyzeRunsOptions {
29
- /** The runs to analyze. */
30
- runs: RunRecord[];
31
- /** Which split to score against when reading composite from RunOutcome.
32
- * Default: holdout when ANY run has a `holdoutScore`, else search. */
33
- split?: 'search' | 'holdout' | 'auto';
34
- /** Pairwise analysis configuration. When both `baselineCandidateId` and
35
- * `candidateCandidateId` are present, lift is computed on paired
36
- * (experimentId, seed) tuples shared between the two sides. */
37
- baselineCandidateId?: string;
38
- candidateCandidateId?: string;
39
- /** Canary scenarios — checked against every run's raw output for
40
- * holdout contamination. */
41
- canaryScenarios?: DatasetScenario[];
42
- /** Analyst registry for failure clustering. When omitted, the
43
- * `failureClusters` section is left undefined. */
44
- analyst?: AnalystRegistry;
45
- /** Downstream outcome metric per run (e.g. engagement rate, approval
46
- * rate, downstream pass rate). When present, the report includes
47
- * `outcomeCorrelation` + a simple linear reward model fit. */
48
- outcomeSignal?: {
49
- metric: string;
50
- valueByRunId: Record<string, number>;
51
- };
52
- /** Multi-rater feedback for inter-rater agreement. Each entry is one
53
- * rater's score for one run. Two or more raters → kappa + disagreement
54
- * triage list. */
55
- raterScores?: Array<{
56
- runId: string;
57
- rater: string;
58
- score: number;
59
- }>;
60
- /** Number of histogram bins for distributional summaries. Default 12. */
61
- histogramBins?: number;
62
- /** Decision threshold — the smallest composite lift the caller cares
63
- * about. Used by the recommendations engine to call ship vs hold.
64
- * Default 0.02. */
65
- decisionThreshold?: number;
66
- /** Optional prior-period runs. When set, the report includes
67
- * `priorPeriodComparison` with per-metric Welch-CI deltas and
68
- * recommendations fire on statistically significant regressions.
69
- * The two windows do NOT have to share scenarios — the comparison
70
- * is two-sample unpaired (the substrate's `lift` field uses paired
71
- * bootstrap on shared (experimentId, seed) tuples; this is the
72
- * shape for "this week vs last week" rather than "candidate vs
73
- * baseline within a campaign"). */
74
- baselineRuns?: RunRecord[];
75
- /** Human-readable label for the baseline window, e.g. "vs prior 7
76
- * days", "vs v3.1 release". Surfaces in recommendations + UI. */
77
- baselineLabel?: string;
78
- }
79
- declare function analyzeRuns(opts: AnalyzeRunsOptions): Promise<InsightReport>;
80
-
81
- export { type AnalyzeRunsOptions as A, analyzeRuns as a };
@@ -1,141 +0,0 @@
1
- import { T as TraceStore } from './store-DGqD0Pyo.js';
2
- import { S as Span, e as TraceEvent } from './schema-B3Q3l9Z_.js';
3
-
4
- /**
5
- * Tool-use metrics — derived purely from trace data.
6
- *
7
- * No scoring assumptions: consumers supply optional ground-truth tool
8
- * selections per turn + optional "information used downstream" signals.
9
- * Without those, we still compute descriptive metrics (error rate,
10
- * retry rate, duplicate-call rate) that are useful on their own.
11
- */
12
-
13
- interface ToolUseMetrics {
14
- runId: string;
15
- totalCalls: number;
16
- /** Calls whose arguments were captured and can be compared for duplication. */
17
- callsWithCapturedArgs: number;
18
- byTool: Record<string, ToolStats>;
19
- errorRate: number;
20
- /** Ratio of captured-argument calls already seen with the same tool name and arguments. */
21
- duplicateRate: number;
22
- /** Ratio of error calls followed by ≥1 retry on same tool. */
23
- retryRate: number;
24
- /** Optional: of the calls agent made, fraction the evaluator marked as "correct selection". */
25
- selectionAccuracy?: number;
26
- }
27
- interface ToolStats {
28
- calls: number;
29
- callsWithCapturedArgs: number;
30
- errors: number;
31
- avgLatencyMs: number;
32
- duplicates: number;
33
- }
34
- interface ToolUseOptions {
35
- /** Map of spanId → whether the evaluator judged the tool selection correct. Optional. */
36
- selectionLabels?: Record<string, boolean>;
37
- }
38
- declare function computeToolUseMetrics(store: TraceStore, runId: string, options?: ToolUseOptions): Promise<ToolUseMetrics>;
39
-
40
- /**
41
- * Trajectory — ordered, structured view over a run's spans.
42
- *
43
- * A pure function `buildTrajectory(store, runId) → Trajectory` returns
44
- * a topologically ordered list of `TrajectoryStep` with parent-child
45
- * grouping collapsed into a single line-of-agent-work. Separate
46
- * analyzers (stuck-loop detection, waste ratio) live in
47
- * `pipelines/` and consume the trajectory.
48
- */
49
-
50
- interface TrajectoryStep {
51
- index: number;
52
- span: Span;
53
- /** Depth in the span tree from the root. 0 = top-level. */
54
- depth: number;
55
- /** Events attached to this span. */
56
- events: TraceEvent[];
57
- }
58
- interface Trajectory {
59
- runId: string;
60
- steps: TrajectoryStep[];
61
- llmTurns: number;
62
- toolCalls: number;
63
- judgeVerdicts: number;
64
- retrievals: number;
65
- totalDurationMs: number;
66
- }
67
- declare function buildTrajectory(store: TraceStore, runId: string): Promise<Trajectory>;
68
-
69
- /**
70
- * Baseline regression detection.
71
- *
72
- * Lifted from ADC baseline.ts. Every promotion-blocking signal boils down
73
- * to: "is this run measurably worse than baseline?" — with enough
74
- * statistical rigor to distinguish noise from drift.
75
- *
76
- * Uses:
77
- * - Welch's t-test (unequal variance) for per-metric mean comparison
78
- * - Cohen's d for effect size magnitude
79
- * - IQR for stability flag (unstable samples can't be trusted for comparisons)
80
- *
81
- * Returns a structured verdict: improved | regressed | stable | unstable.
82
- */
83
- interface MetricSamples {
84
- /** Stable metric key (e.g. "overallScore", "firstTokenMs"). */
85
- metric: string;
86
- /** Whether higher values are better. */
87
- higherIsBetter: boolean;
88
- baseline: number[];
89
- candidate: number[];
90
- }
91
- interface MetricVerdict {
92
- metric: string;
93
- baselineMean: number;
94
- candidateMean: number;
95
- delta: number;
96
- cohensD: number;
97
- welchT: number;
98
- welchDf: number;
99
- welchP: number;
100
- stable: boolean;
101
- /** IQR of the combined samples — used as a rough stability indicator. */
102
- iqr: number;
103
- verdict: 'improved' | 'regressed' | 'stable' | 'unstable';
104
- }
105
- interface BaselineReport {
106
- metrics: MetricVerdict[];
107
- /** True if any critical metric regressed. */
108
- hasRegression: boolean;
109
- /** True if any metric is unstable (too noisy to judge). */
110
- hasUnstable: boolean;
111
- }
112
- interface BaselineOptions {
113
- /** Effect size threshold for meaningful delta (default 0.5 — medium effect). */
114
- effectThreshold?: number;
115
- /** p-value threshold for statistical significance (default 0.05). */
116
- alpha?: number;
117
- /** IQR/mean ratio above which samples are flagged unstable (default 0.30). */
118
- unstableCvThreshold?: number;
119
- }
120
- /**
121
- * Compare candidate samples against baseline per metric. Verdict logic:
122
- * - unstable: IQR/|mean| > threshold on either set — not enough signal
123
- * - improved: meaningful effect in the "better" direction AND p < alpha
124
- * - regressed: meaningful effect in the "worse" direction AND p < alpha
125
- * - stable: otherwise (no significant change)
126
- */
127
- declare function compareToBaseline(samples: MetricSamples[], options?: BaselineOptions): BaselineReport;
128
- /** Inter-quartile range; 0 when the sample has no spread. */
129
- declare function iqr(xs: number[]): number;
130
- /**
131
- * Welch's t-test — unequal-variance two-sample t. Uses the same Student-t
132
- * CDF as `pairedTTest` (via incomplete beta); falls back to normal tail
133
- * when df is large.
134
- */
135
- declare function welchsTTest(a: number[], b: number[]): {
136
- t: number;
137
- df: number;
138
- p: number;
139
- };
140
-
141
- export { type BaselineOptions as B, type MetricSamples as M, type TrajectoryStep as T, type BaselineReport as a, type Trajectory as b, computeToolUseMetrics as c, type MetricVerdict as d, type ToolStats as e, type ToolUseMetrics as f, type ToolUseOptions as g, buildTrajectory as h, compareToBaseline as i, iqr as j, welchsTTest as w };
@@ -1,101 +0,0 @@
1
- import { T as TraceStore } from './store-DGqD0Pyo.js';
2
- import { R as Run } from './schema-B3Q3l9Z_.js';
3
- import { O as OutcomeFilter, b as OutcomeStore } from './outcome-store-rnXLEqSn.js';
4
-
5
- /**
6
- * Correlation study — "does our eval score predict real-world outcomes?"
7
- *
8
- * This is the load-bearing signal. Takes a TraceStore + OutcomeStore,
9
- * joins on runId, computes Pearson + Spearman + bootstrap CI for every
10
- * (evalMetric, outcomeMetric) pair the caller declares.
11
- *
12
- * Without this number the framework is ornamental. With it and r > 0.6
13
- * the framework is a moat — no other agent-eval tool publishes one.
14
- */
15
-
16
- interface EvalMetricSpec {
17
- id: string;
18
- /** Extract a scalar from a run (defaults cover score/pass/durationMs/costUsd/tokens). */
19
- extract?: (run: Run, store: TraceStore) => Promise<number | null>;
20
- }
21
- interface OutcomePair {
22
- evalMetric: string;
23
- outcomeMetric: string;
24
- }
25
- interface CorrelationResult {
26
- evalMetric: string;
27
- outcomeMetric: string;
28
- n: number;
29
- pearson: number;
30
- spearman: number;
31
- /** 95% bootstrap CI for Pearson. */
32
- pearsonCi95: {
33
- lower: number;
34
- upper: number;
35
- };
36
- /** Rough verdict: 'strong' ≥ 0.7, 'moderate' ≥ 0.4, else 'weak'. */
37
- verdict: 'strong' | 'moderate' | 'weak';
38
- }
39
- interface CorrelationStudyResult {
40
- pairs: CorrelationResult[];
41
- joinedSamples: number;
42
- skippedRuns: number;
43
- }
44
- interface CorrelationStudyOptions {
45
- /** Only join outcomes captured within this window after run.startedAt. */
46
- maxCaptureLagMs?: number;
47
- /** Restrict to a subset of outcomes (cohort, region, source). */
48
- outcomeFilter?: OutcomeFilter;
49
- /** Which outcome per run to use when multiple exist. Default 'latest'. */
50
- reduction?: 'latest' | 'mean' | 'max';
51
- /** Bootstrap iterations for the CI. Default 500. */
52
- bootstrapIterations?: number;
53
- }
54
- declare function correlationStudy(traceStore: TraceStore, outcomeStore: OutcomeStore, evalMetrics: EvalMetricSpec[], outcomeMetricNames: string[], options?: CorrelationStudyOptions): Promise<CorrelationStudyResult>;
55
-
56
- /**
57
- * Calibration curve — binned "if eval says X, what does reality show?"
58
- *
59
- * Companion to correlationStudy. Raw correlation is a single number;
60
- * the calibration curve shows *where* the eval is well-calibrated vs
61
- * overconfident / underconfident. Buckets the eval metric, computes
62
- * mean outcome per bucket, reports expected-calibration-error (ECE).
63
- */
64
-
65
- interface CalibrationBin {
66
- lower: number;
67
- upper: number;
68
- n: number;
69
- evalMean: number;
70
- outcomeMean: number;
71
- /** |outcomeMean − evalMean|; contributes to ECE weighted by n/total. */
72
- gap: number;
73
- }
74
- interface CalibrationReport {
75
- evalMetric: string;
76
- outcomeMetric: string;
77
- n: number;
78
- bins: CalibrationBin[];
79
- /** Expected Calibration Error — Σ (n_i/N) × |outcomeMean_i − evalMean_i|. */
80
- ece: number;
81
- /** Max bin gap — upper bound on miscalibration. */
82
- maxGap: number;
83
- }
84
- interface CalibrationOptions {
85
- bins?: number;
86
- /** Equal-width (fixed bin edges) or equal-frequency (quantile bins). */
87
- binning?: 'equal-width' | 'equal-frequency';
88
- /** Clip eval values to [lo, hi] before binning. */
89
- range?: {
90
- lo: number;
91
- hi: number;
92
- };
93
- }
94
- interface CalibrationPair {
95
- evalScore: number;
96
- outcome: number;
97
- }
98
- declare function calibrationCurve(traceStore: TraceStore, outcomeStore: OutcomeStore, evalMetric: EvalMetricSpec, outcomeMetric: string, options?: CalibrationOptions): Promise<CalibrationReport | null>;
99
- declare function calibrationFromPairs(inputPairs: CalibrationPair[], evalMetric: string, outcomeMetric: string, options?: CalibrationOptions): CalibrationReport | null;
100
-
101
- export { type CalibrationBin as C, type EvalMetricSpec as E, type OutcomePair as O, type CalibrationOptions as a, type CalibrationPair as b, type CalibrationReport as c, type CorrelationResult as d, type CorrelationStudyOptions as e, type CorrelationStudyResult as f, calibrationCurve as g, calibrationFromPairs as h, correlationStudy as i };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/run-record.ts"],"sourcesContent":["/**\n * Paper-grade RunRecord schema + runtime validator.\n *\n * Every run that participates in a promotion gate, paper table, or\n * researcher loop SHOULD be recorded as a `RunRecord`. The mandatory\n * fields are exactly those the paper \"Two Loops, Three Roles\" requires\n * for reproducibility: who/what/when/cost/seed/hash, plus the search vs\n * holdout split tag and either a `searchScore` or a `holdoutScore`.\n *\n * This is intentionally NOT a replacement for the rich `Run` /\n * `ProposeReviewReport` / `ScenarioResult` types already in the\n * package. Those are runtime structures with full provenance. A\n * `RunRecord` is the analysis-time projection — the JSON-friendly\n * row you'd put in a parquet file or paste into a notebook.\n *\n * Validate at the boundary:\n *\n * const rec = validateRunRecord(rawJson) // throws on missing\n * const ok = isRunRecord(rawJson) // boolean check\n * const rec = parseRunRecordSafe(rawJson) // { ok, value | error }\n *\n * The validator runs in pure TS — zod is intentionally NOT a\n * dependency. Round-trip tested in `tests/run-record.test.ts`.\n */\n\nimport type { AgentProfileCell } from './agent-profile-cell'\nimport { validateAgentProfileCell } from './agent-profile-cell'\nimport { ValidationError } from './errors'\nimport type { FailureClass } from './trace/schema'\n\n/** Search/dev/holdout split tag. 'search' is the paper-grade alias for the\n * combined train+test pool that the optimizer is allowed to read. */\nexport type RunSplitTag = 'search' | 'dev' | 'holdout'\n\nexport interface RunTokenUsage {\n input: number\n output: number\n cached?: number\n}\n\n/**\n * How a run's USD amount was obtained.\n *\n * `costUsd` remains mandatory for wire compatibility. New producers should\n * always populate this discriminated union so a missing bill is never\n * mistaken for an observed zero-dollar run. For `uncaptured`, `costUsd` uses\n * the legacy `0` sentinel while this field carries the truthful null.\n */\nexport type RunCostProvenance =\n | { kind: 'observed'; usd: number }\n | { kind: 'estimated'; usd: number }\n | { kind: 'uncaptured'; usd: null }\n\nexport interface RunJudgeMetadata {\n model: string\n promptVersion: string\n /** [0,1] confidence the judge declared. Constant judge confidence\n * across many runs is a fallback signal (see `canary.ts`). */\n confidence: number\n /** True if the judge degraded to a fallback path (rules-only,\n * prior-call cache, etc.). The canary uses this to alert. */\n fallback: boolean\n}\n\n/**\n * Per-judge / per-dimension breakdown for runs scored by an ensemble of\n * judges over a multi-dimensional rubric.\n *\n * The collapsed `outcome.searchScore` / `holdoutScore` carries the\n * composite the gate uses. The full breakdown belongs here so consumers\n * can answer \"which judge disagreed?\", \"which dimension dragged the\n * composite down?\", and \"did half the panel fail?\" without re-running.\n *\n * `perJudge[judgeId][dim]` is the canonical source; `perDimMean` and\n * `composite` are convenience projections — derivable but precomputed so\n * downstream IRR primitives (`interRaterReliability`,\n * `corpusInterRaterAgreement`) and reporters don't pay the same\n * aggregation twice.\n *\n * Fail-loud discipline: judges that errored out land in `failedJudges`\n * by id. A missing key in `perJudge` is ambiguous (silent zero vs not\n * run); the explicit list makes a partial-failure recorded as such.\n */\nexport interface JudgeScoresRecord {\n /** Per-judge per-dimension scores. `{ \"kimi-k2.6\": { helpfulness: 0.8, clarity: 0.7 }, ... }`. */\n perJudge: Record<string, Record<string, number>>\n /** Per-dim mean across judges. Convenience — derivable from `perJudge`. */\n perDimMean: Record<string, number>\n /** Composite mean across all dims and judges. Mirrors the score\n * the gate sees on `outcome.searchScore` / `holdoutScore`. */\n composite: number\n /** Judges that errored or returned an unparseable verdict. Recorded\n * by id (e.g. `['glm-5.1']`) so a partial-failure case is explicit,\n * not inferred from missing keys in `perJudge`. */\n failedJudges?: string[]\n /** Free-form notes the judges emitted (joined across judges or\n * first-judge only — consumer's choice). */\n notes?: string\n}\n\nexport interface RunOutcome {\n /** Score on the search/optimization split. Optional because a\n * holdout-only evaluation only fills `holdoutScore`. */\n searchScore?: number\n /** Score on the held-out split. Optional because a search-only run\n * only fills `searchScore`. At least one must be present. */\n holdoutScore?: number\n /** Bag of any other metric the run produced — judge dimensions,\n * pass/fail counters, latency stats, etc. Numeric only — keeps\n * reporters honest. */\n raw: Record<string, number>\n /** Per-judge / per-dim breakdown. Consumers writing ensemble\n * judgements populate this; substrate primitives like\n * `interRaterReliability` and `corpusInterRaterAgreement` accept\n * these records as input. Optional — single-judge or scalar-only\n * runs leave it unset. */\n judgeScores?: JudgeScoresRecord\n /** Authenticity / realness verdict — did the run build the REAL thing on the\n * intended infra, or fake it (see `./authenticity`)? Optional: only domains\n * with an authenticity config populate it. Carried in the corpus so the\n * flywheel / off-policy learning can optimize for real completion, not gamed\n * pass-rate. `score` is 0-1; `gated` is the anti-Goodhart flag — a gated run\n * must not count as a real success regardless of `score`. */\n realness?: { score: number; gated: boolean; reason?: string }\n}\n\n/**\n * Mandatory paper-grade fields for a single evaluation run. Optional\n * fields are extension points; mandatory fields throw if missing.\n *\n * Hash discipline:\n * - `promptHash` is the sha256 of the EFFECTIVE prompt sent to the\n * model (after any steering bundle merge).\n * - `configHash` is the sha256 of the effective run config (model,\n * temperature, tools, judges, splits). The pair (promptHash,\n * configHash) uniquely identifies an experiment cell.\n *\n * Model snapshot discipline:\n * - `model` MUST encode a snapshot version. Bare aliases like\n * `claude-sonnet-4` or `gpt-4o` are banned — they remap silently.\n * Use `claude-sonnet-4-6@2025-04-15` or `gpt-4o-2024-11-20`.\n */\nexport interface RunRecord {\n /** UUID for the run. */\n runId: string\n /** Logical experiment grouping (a treatment vs a baseline within\n * the same sweep should share `experimentId`). */\n experimentId: string\n /** Stable identifier for the candidate (variant) being run. The\n * promotion gate compares two `candidateId`s on matched items. */\n candidateId: string\n /** RNG seed for the run. Always recorded — silent re-seeding is\n * the most common cause of non-reproducible numbers. */\n seed: number\n /** Model identifier WITH snapshot version. */\n model: string\n /** sha256 of the effective prompt (post-steering). */\n promptHash: string\n /** sha256 of the effective config. */\n configHash: string\n /** Git SHA the harness was run from. */\n commitSha: string\n /** End-to-end wall-clock duration in milliseconds. */\n wallMs: number\n /** Time spent queued before execution started, if known. */\n queueMs?: number\n /** Total USD cost. Mandatory — runs without a cost number are\n * unbounded by definition and must not be admitted into the gate.\n * `0` is retained as the compatibility sentinel for an uncaptured amount;\n * inspect `costProvenance` before treating it as observed. */\n costUsd: number\n /** Observed, model-priced estimate, or genuinely uncaptured USD amount.\n * Optional only so existing serialized RunRecords remain valid. */\n costProvenance?: RunCostProvenance\n /** Token usage breakdown. */\n tokenUsage: RunTokenUsage\n /** Judge-side metadata, if a judge was used. */\n judgeMetadata?: RunJudgeMetadata\n /** Per-split scores + raw bag. */\n outcome: RunOutcome\n /** Canonical, cross-agent failure class drawn from the shared\n * `FAILURE_CLASSES` taxonomy. This is the aggregation key that makes\n * \"which failure dominates across the whole fleet\" answerable in ONE\n * vocabulary — every agent classifies against the same enum. Producers\n * set it via the substrate classifier; leave unset only when the failure\n * genuinely can't be classified. */\n failureClass?: FailureClass\n /** Free-form domain-specific failure detail, scoped UNDER `failureClass`\n * (e.g. failureClass='tool_recovery_failure', failureMode='forge_build_unsatisfied').\n * The within-agent drill-down; `failureClass` is the cross-agent key. */\n failureMode?: string\n /** Which split this run was drawn from. */\n splitTag: RunSplitTag\n /**\n * Stable scenario identifier the run was scored against. Optional for\n * backwards compatibility, but **strongly recommended**: every primitive\n * that pairs runs by scenario (preferences, paired stats, BT tournament)\n * keys on this. The campaign artifact populates it canonically; legacy\n * runs without it fall back to inference from `outcome.raw.scenario_id`\n * or `experimentId`.\n */\n scenarioId?: string\n /**\n * Canonical identity for the agent profile cell that produced this row:\n * profile artifact hash plus optional harness/model/prompt/reporting\n * dimensions. Use `agentProfile.cellId` to group persona sweeps and\n * longitudinal reports by the complete source profile, not by a loose\n * candidate label or opaque config hash.\n */\n agentProfile?: AgentProfileCell\n}\n\n// ── Validation ───────────────────────────────────────────────────────\n\nconst MANDATORY_TOP_LEVEL = [\n 'runId',\n 'experimentId',\n 'candidateId',\n 'seed',\n 'model',\n 'promptHash',\n 'configHash',\n 'commitSha',\n 'wallMs',\n 'costUsd',\n 'tokenUsage',\n 'outcome',\n 'splitTag',\n] as const\n\nconst SPLIT_TAGS: ReadonlyArray<RunSplitTag> = ['search', 'dev', 'holdout']\n\nexport class RunRecordValidationError extends ValidationError {\n readonly path: string\n constructor(message: string, path = '') {\n super(path ? `${message} (at ${path})` : message)\n this.path = path\n }\n}\n\n/**\n * Strict validator. Throws `RunRecordValidationError` on the first\n * missing or wrongly-typed field. Returns the input cast to\n * `RunRecord` on success — the validator does not coerce.\n */\nexport function validateRunRecord(input: unknown): RunRecord {\n if (input === null || typeof input !== 'object') {\n throw new RunRecordValidationError('expected object')\n }\n const obj = input as Record<string, unknown>\n\n for (const key of MANDATORY_TOP_LEVEL) {\n if (!(key in obj)) {\n throw new RunRecordValidationError(`missing mandatory field \"${key}\"`)\n }\n }\n\n expectString(obj.runId, 'runId')\n expectString(obj.experimentId, 'experimentId')\n expectString(obj.candidateId, 'candidateId')\n expectFiniteNumber(obj.seed, 'seed')\n expectString(obj.model, 'model')\n expectString(obj.promptHash, 'promptHash')\n expectString(obj.configHash, 'configHash')\n expectString(obj.commitSha, 'commitSha')\n expectFiniteNumber(obj.wallMs, 'wallMs')\n if (obj.queueMs !== undefined) expectFiniteNumber(obj.queueMs, 'queueMs')\n expectFiniteNumber(obj.costUsd, 'costUsd')\n if (obj.costProvenance !== undefined) {\n validateCostProvenance(obj.costProvenance, obj.costUsd as number)\n }\n\n // Snapshot discipline: bare model aliases are not paper-grade.\n if (!modelHasSnapshot(obj.model as string)) {\n throw new RunRecordValidationError(\n `model \"${obj.model}\" lacks a snapshot version (use 'name@YYYY-MM-DD' or 'name-YYYYMMDD')`,\n 'model',\n )\n }\n\n // Token usage.\n const tu = obj.tokenUsage\n if (tu === null || typeof tu !== 'object') {\n throw new RunRecordValidationError('tokenUsage must be an object', 'tokenUsage')\n }\n const tuRec = tu as Record<string, unknown>\n expectFiniteNumber(tuRec.input, 'tokenUsage.input')\n expectFiniteNumber(tuRec.output, 'tokenUsage.output')\n if (tuRec.cached !== undefined) expectFiniteNumber(tuRec.cached, 'tokenUsage.cached')\n\n // Judge metadata, optional.\n if (obj.judgeMetadata !== undefined) {\n const jm = obj.judgeMetadata\n if (jm === null || typeof jm !== 'object') {\n throw new RunRecordValidationError('judgeMetadata must be an object', 'judgeMetadata')\n }\n const jmRec = jm as Record<string, unknown>\n expectString(jmRec.model, 'judgeMetadata.model')\n expectString(jmRec.promptVersion, 'judgeMetadata.promptVersion')\n expectFiniteNumber(jmRec.confidence, 'judgeMetadata.confidence')\n if (typeof jmRec.fallback !== 'boolean') {\n throw new RunRecordValidationError(\n 'judgeMetadata.fallback must be boolean',\n 'judgeMetadata.fallback',\n )\n }\n }\n\n // Outcome.\n const out = obj.outcome\n if (out === null || typeof out !== 'object') {\n throw new RunRecordValidationError('outcome must be an object', 'outcome')\n }\n const outRec = out as Record<string, unknown>\n if (outRec.searchScore !== undefined)\n expectFiniteNumber(outRec.searchScore, 'outcome.searchScore')\n if (outRec.holdoutScore !== undefined)\n expectFiniteNumber(outRec.holdoutScore, 'outcome.holdoutScore')\n if (outRec.searchScore === undefined && outRec.holdoutScore === undefined) {\n throw new RunRecordValidationError(\n 'outcome must define searchScore or holdoutScore (or both)',\n 'outcome',\n )\n }\n const raw = outRec.raw\n if (raw === null || typeof raw !== 'object') {\n throw new RunRecordValidationError('outcome.raw must be an object', 'outcome.raw')\n }\n for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {\n expectFiniteNumber(v, `outcome.raw.${k}`)\n }\n // Realness verdict, optional.\n if (outRec.realness !== undefined) {\n const r = outRec.realness\n if (r === null || typeof r !== 'object') {\n throw new RunRecordValidationError('outcome.realness must be an object', 'outcome.realness')\n }\n const rr = r as Record<string, unknown>\n expectFiniteNumber(rr.score, 'outcome.realness.score')\n if (typeof rr.gated !== 'boolean') {\n throw new RunRecordValidationError(\n 'outcome.realness.gated must be a boolean',\n 'outcome.realness.gated',\n )\n }\n }\n\n // Per-judge / per-dim breakdown, optional.\n if (outRec.judgeScores !== undefined) {\n validateJudgeScores(outRec.judgeScores, 'outcome.judgeScores')\n }\n\n // Failure mode optional.\n if (obj.failureMode !== undefined) expectString(obj.failureMode, 'failureMode')\n\n if (obj.agentProfile !== undefined) {\n try {\n const profile = validateAgentProfileCell(obj.agentProfile)\n if (profile.model !== undefined && profile.model !== obj.model) {\n throw new RunRecordValidationError(\n `agentProfile.model \"${profile.model}\" does not match model \"${obj.model}\"`,\n 'agentProfile.model',\n )\n }\n if (profile.promptHash !== undefined && profile.promptHash !== obj.promptHash) {\n throw new RunRecordValidationError(\n `agentProfile.promptHash \"${profile.promptHash}\" does not match promptHash \"${obj.promptHash}\"`,\n 'agentProfile.promptHash',\n )\n }\n } catch (error) {\n if (error instanceof RunRecordValidationError) throw error\n if (error instanceof Error) {\n throw new RunRecordValidationError(error.message, 'agentProfile')\n }\n throw error\n }\n }\n\n // Split tag.\n if (typeof obj.splitTag !== 'string' || !SPLIT_TAGS.includes(obj.splitTag as RunSplitTag)) {\n throw new RunRecordValidationError(\n `splitTag must be one of ${SPLIT_TAGS.join(', ')}, got ${String(obj.splitTag)}`,\n 'splitTag',\n )\n }\n\n return input as RunRecord\n}\n\n/**\n * Resolve provenance for both new and legacy records.\n *\n * Legacy producers sometimes set `outcome.raw.cost_estimated = 1`. A positive\n * unlabeled amount is treated as observed, matching the historical contract.\n * Zero without an explicit label is conservatively uncaptured: claiming an\n * observed $0 would be stronger than the serialized evidence supports.\n */\nexport function resolveRunCostProvenance(\n run: Pick<RunRecord, 'costUsd' | 'costProvenance' | 'outcome'>,\n): RunCostProvenance {\n if (run.costProvenance) return run.costProvenance\n if (run.outcome.raw.cost_estimated === 1) {\n return { kind: 'estimated', usd: run.costUsd }\n }\n if (run.costUsd > 0) return { kind: 'observed', usd: run.costUsd }\n return { kind: 'uncaptured', usd: null }\n}\n\nfunction validateCostProvenance(input: unknown, costUsd: number): void {\n if (input === null || typeof input !== 'object') {\n throw new RunRecordValidationError('costProvenance must be an object', 'costProvenance')\n }\n const value = input as Record<string, unknown>\n if (value.kind !== 'observed' && value.kind !== 'estimated' && value.kind !== 'uncaptured') {\n throw new RunRecordValidationError(\n 'costProvenance.kind must be observed, estimated, or uncaptured',\n 'costProvenance.kind',\n )\n }\n if (value.kind === 'uncaptured') {\n if (value.usd !== null) {\n throw new RunRecordValidationError(\n 'uncaptured costProvenance.usd must be null',\n 'costProvenance.usd',\n )\n }\n if (costUsd !== 0) {\n throw new RunRecordValidationError(\n 'uncaptured costProvenance requires the compatibility costUsd sentinel 0',\n 'costUsd',\n )\n }\n return\n }\n expectFiniteNumber(value.usd, 'costProvenance.usd')\n if ((value.usd as number) < 0) {\n throw new RunRecordValidationError(\n 'costProvenance.usd must be non-negative',\n 'costProvenance.usd',\n )\n }\n if (value.usd !== costUsd) {\n throw new RunRecordValidationError(\n 'costProvenance.usd must equal costUsd',\n 'costProvenance.usd',\n )\n }\n}\n\n/** Boolean validator — convenience for filtering arrays. */\nexport function isRunRecord(input: unknown): input is RunRecord {\n try {\n validateRunRecord(input)\n return true\n } catch {\n return false\n }\n}\n\n/** Non-throwing validator — returns a discriminated union. */\nexport function parseRunRecordSafe(\n input: unknown,\n): { ok: true; value: RunRecord } | { ok: false; error: RunRecordValidationError } {\n try {\n return { ok: true, value: validateRunRecord(input) }\n } catch (e) {\n if (e instanceof RunRecordValidationError) return { ok: false, error: e }\n throw e\n }\n}\n\n/** Round-trip helper — `JSON.parse(JSON.stringify(record))` then validate. */\nexport function roundTripRunRecord(record: RunRecord): RunRecord {\n const json = JSON.stringify(record)\n return validateRunRecord(JSON.parse(json))\n}\n\n// ── Internals ────────────────────────────────────────────────────────\n\nfunction expectString(value: unknown, path: string): void {\n if (typeof value !== 'string' || value.length === 0) {\n throw new RunRecordValidationError(`expected non-empty string`, path)\n }\n}\n\nfunction expectFiniteNumber(value: unknown, path: string): void {\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new RunRecordValidationError(`expected finite number`, path)\n }\n}\n\nfunction validateJudgeScores(value: unknown, path: string): void {\n if (value === null || typeof value !== 'object') {\n throw new RunRecordValidationError('judgeScores must be an object', path)\n }\n const rec = value as Record<string, unknown>\n\n const perJudge = rec.perJudge\n if (perJudge === null || typeof perJudge !== 'object') {\n throw new RunRecordValidationError('perJudge must be an object', `${path}.perJudge`)\n }\n for (const [judgeId, dims] of Object.entries(perJudge as Record<string, unknown>)) {\n if (dims === null || typeof dims !== 'object') {\n throw new RunRecordValidationError(\n 'per-judge entry must be an object of dimension scores',\n `${path}.perJudge.${judgeId}`,\n )\n }\n for (const [dim, score] of Object.entries(dims as Record<string, unknown>)) {\n expectFiniteNumber(score, `${path}.perJudge.${judgeId}.${dim}`)\n }\n }\n\n const perDimMean = rec.perDimMean\n if (perDimMean === null || typeof perDimMean !== 'object') {\n throw new RunRecordValidationError('perDimMean must be an object', `${path}.perDimMean`)\n }\n for (const [dim, mean] of Object.entries(perDimMean as Record<string, unknown>)) {\n expectFiniteNumber(mean, `${path}.perDimMean.${dim}`)\n }\n\n expectFiniteNumber(rec.composite, `${path}.composite`)\n\n if (rec.failedJudges !== undefined) {\n if (!Array.isArray(rec.failedJudges)) {\n throw new RunRecordValidationError(\n 'failedJudges must be an array of strings',\n `${path}.failedJudges`,\n )\n }\n for (let i = 0; i < rec.failedJudges.length; i++) {\n const id = rec.failedJudges[i]\n if (typeof id !== 'string' || id.length === 0) {\n throw new RunRecordValidationError(\n 'failedJudges entry must be a non-empty string',\n `${path}.failedJudges[${i}]`,\n )\n }\n }\n }\n\n if (rec.notes !== undefined && typeof rec.notes !== 'string') {\n throw new RunRecordValidationError('notes must be a string', `${path}.notes`)\n }\n}\n\n/**\n * Heuristic snapshot check. Accepts:\n * - `name@YYYY-MM-DD` (Anthropic style: `claude-sonnet-4-6@2025-04-15`)\n * - `name-YYYYMMDD` (OpenAI style: `gpt-4o-2024-11-20`)\n * - `name@<arbitrary-token>` (allow opaque snapshots like `@v3`)\n * - explicit `:date-...` Vertex-style tags\n *\n * Rejects bare aliases like `claude-sonnet-4` or `gpt-4o` that remap\n * silently as providers ship new snapshots.\n */\nexport function modelHasSnapshot(model: string): boolean {\n if (model.includes('@')) return true\n if (/-\\d{8}$/.test(model)) return true\n if (/-\\d{4}-\\d{2}-\\d{2}$/.test(model)) return true\n if (/:date-/.test(model)) return true\n return false\n}\n"],"mappings":"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","names":[]}