@tangle-network/agent-eval 0.126.6 → 0.127.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 (129) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +2 -2
  3. package/dist/analyst/index.d.ts +52 -40
  4. package/dist/analyst/index.js +4 -4
  5. package/dist/belief-state/index.d.ts +34 -36
  6. package/dist/belief-state/index.js +2 -2
  7. package/dist/benchmarks/index.d.ts +30 -12
  8. package/dist/benchmarks/index.js +12 -10
  9. package/dist/builder-eval/index.js +1 -1
  10. package/dist/campaign/index.d.ts +432 -146
  11. package/dist/campaign/index.js +11 -9
  12. package/dist/{chunk-ARU2PZFM.js → chunk-424XHVKV.js} +43 -23
  13. package/dist/chunk-424XHVKV.js.map +1 -0
  14. package/dist/{chunk-NUKSVU3W.js → chunk-5DTSBUL2.js} +2 -2
  15. package/dist/{chunk-BTACVO2W.js → chunk-65QETEKG.js} +542 -258
  16. package/dist/chunk-65QETEKG.js.map +1 -0
  17. package/dist/{chunk-CGG5SLH3.js → chunk-ARCFIY7U.js} +2 -2
  18. package/dist/{chunk-P22LJ3Y2.js → chunk-CLELFRQG.js} +6 -4
  19. package/dist/chunk-CLELFRQG.js.map +1 -0
  20. package/dist/{chunk-LUNF2SEL.js → chunk-DJKY2TSY.js} +3 -3
  21. package/dist/chunk-DPUHNQLN.js +232 -0
  22. package/dist/chunk-DPUHNQLN.js.map +1 -0
  23. package/dist/{chunk-IPYXE555.js → chunk-EJGRPCO3.js} +36 -13
  24. package/dist/chunk-EJGRPCO3.js.map +1 -0
  25. package/dist/{chunk-K6IAZZ6L.js → chunk-FA3MFYNA.js} +132 -110
  26. package/dist/chunk-FA3MFYNA.js.map +1 -0
  27. package/dist/chunk-IG35YDN3.js +614 -0
  28. package/dist/chunk-IG35YDN3.js.map +1 -0
  29. package/dist/{chunk-DPZAEKA6.js → chunk-IMGJZBFG.js} +84 -56
  30. package/dist/chunk-IMGJZBFG.js.map +1 -0
  31. package/dist/{chunk-H5UD2323.js → chunk-IN455XKS.js} +53 -1
  32. package/dist/chunk-IN455XKS.js.map +1 -0
  33. package/dist/{chunk-PJQFMIOX.js → chunk-MHELPNRP.js} +31 -1
  34. package/dist/chunk-MHELPNRP.js.map +1 -0
  35. package/dist/{chunk-IILEIWGW.js → chunk-NL33OXEP.js} +41 -36
  36. package/dist/chunk-NL33OXEP.js.map +1 -0
  37. package/dist/{chunk-474LBSOX.js → chunk-OD5NQ2CS.js} +41 -16
  38. package/dist/chunk-OD5NQ2CS.js.map +1 -0
  39. package/dist/{chunk-QOTFXW5L.js → chunk-P5W7RQKK.js} +2 -2
  40. package/dist/{chunk-IR3KBHOY.js → chunk-P6FYH6K4.js} +39 -3
  41. package/dist/chunk-P6FYH6K4.js.map +1 -0
  42. package/dist/{chunk-J5SQWP6Y.js → chunk-PBE2LOSS.js} +18 -2
  43. package/dist/chunk-PBE2LOSS.js.map +1 -0
  44. package/dist/{chunk-E3IADPLG.js → chunk-PRB53E3J.js} +50 -224
  45. package/dist/chunk-PRB53E3J.js.map +1 -0
  46. package/dist/{chunk-X4UCIOTZ.js → chunk-S5YLIBFX.js} +2 -2
  47. package/dist/{chunk-7VYEGHWF.js → chunk-SZLVEKMJ.js} +41 -20
  48. package/dist/chunk-SZLVEKMJ.js.map +1 -0
  49. package/dist/{chunk-LKKT3IVV.js → chunk-TSN7JT6D.js} +6 -4
  50. package/dist/{chunk-LKKT3IVV.js.map → chunk-TSN7JT6D.js.map} +1 -1
  51. package/dist/{chunk-M7AH34KV.js → chunk-UTA2TGC4.js} +32 -16
  52. package/dist/chunk-UTA2TGC4.js.map +1 -0
  53. package/dist/{chunk-MAX3TN3C.js → chunk-UWZZKKU7.js} +7 -19
  54. package/dist/chunk-UWZZKKU7.js.map +1 -0
  55. package/dist/{chunk-ZVCHKKOP.js → chunk-WED5KLZQ.js} +39 -15
  56. package/dist/chunk-WED5KLZQ.js.map +1 -0
  57. package/dist/{chunk-OCFJACJU.js → chunk-X6VOPMLD.js} +74 -14
  58. package/dist/chunk-X6VOPMLD.js.map +1 -0
  59. package/dist/{chunk-MOXWMGPC.js → chunk-Y5CYOL42.js} +159 -70
  60. package/dist/chunk-Y5CYOL42.js.map +1 -0
  61. package/dist/{chunk-FO7HEH76.js → chunk-YJBNWCAA.js} +2 -2
  62. package/dist/chunk-ZPUEEM5U.js +179 -0
  63. package/dist/chunk-ZPUEEM5U.js.map +1 -0
  64. package/dist/{chunk-KE2VWPZX.js → chunk-ZUUWPZCV.js} +3 -3
  65. package/dist/cli.js +4 -4
  66. package/dist/contract/index.d.ts +464 -126
  67. package/dist/contract/index.js +425 -211
  68. package/dist/contract/index.js.map +1 -1
  69. package/dist/control.d.ts +34 -32
  70. package/dist/control.js +2 -2
  71. package/dist/hosted/index.d.ts +142 -41
  72. package/dist/hosted/index.js +23 -1
  73. package/dist/index.d.ts +365 -256
  74. package/dist/index.js +315 -226
  75. package/dist/index.js.map +1 -1
  76. package/dist/meta-eval/index.d.ts +32 -32
  77. package/dist/meta-eval/index.js +2 -2
  78. package/dist/multishot/index.d.ts +1 -0
  79. package/dist/openapi.json +1 -1
  80. package/dist/pipelines/index.js +2 -2
  81. package/dist/reporting.d.ts +97 -74
  82. package/dist/reporting.js +5 -4
  83. package/dist/rl.d.ts +177 -131
  84. package/dist/rl.js +232 -126
  85. package/dist/rl.js.map +1 -1
  86. package/dist/rollout/index.d.ts +53 -63
  87. package/dist/rollout/index.js +7 -5
  88. package/dist/{run-campaign-FFRM3RH5.js → run-campaign-3A2EBYBE.js} +4 -3
  89. package/dist/supervisor-run/index.d.ts +7 -15
  90. package/dist/supervisor-run/index.js +2 -2
  91. package/dist/traces.d.ts +66 -41
  92. package/dist/traces.js +9 -5
  93. package/dist/wire/index.d.ts +1 -0
  94. package/dist/wire/index.js +2 -2
  95. package/docs/concepts.md +18 -1
  96. package/docs/hosted-ingest-spec.md +37 -40
  97. package/docs/insight-report.md +57 -7
  98. package/docs/research-report-methodology.md +20 -21
  99. package/docs/rollout.md +13 -8
  100. package/package.json +21 -14
  101. package/dist/chunk-474LBSOX.js.map +0 -1
  102. package/dist/chunk-7VYEGHWF.js.map +0 -1
  103. package/dist/chunk-ARU2PZFM.js.map +0 -1
  104. package/dist/chunk-BTACVO2W.js.map +0 -1
  105. package/dist/chunk-DPZAEKA6.js.map +0 -1
  106. package/dist/chunk-E3IADPLG.js.map +0 -1
  107. package/dist/chunk-H5UD2323.js.map +0 -1
  108. package/dist/chunk-IILEIWGW.js.map +0 -1
  109. package/dist/chunk-IPYXE555.js.map +0 -1
  110. package/dist/chunk-IR3KBHOY.js.map +0 -1
  111. package/dist/chunk-J5SQWP6Y.js.map +0 -1
  112. package/dist/chunk-K6IAZZ6L.js.map +0 -1
  113. package/dist/chunk-M7AH34KV.js.map +0 -1
  114. package/dist/chunk-MAX3TN3C.js.map +0 -1
  115. package/dist/chunk-MOXWMGPC.js.map +0 -1
  116. package/dist/chunk-OCFJACJU.js.map +0 -1
  117. package/dist/chunk-P22LJ3Y2.js.map +0 -1
  118. package/dist/chunk-PJQFMIOX.js.map +0 -1
  119. package/dist/chunk-ZVCHKKOP.js.map +0 -1
  120. package/dist/chunk-ZZUXHH3R.js +0 -99
  121. package/dist/chunk-ZZUXHH3R.js.map +0 -1
  122. /package/dist/{chunk-NUKSVU3W.js.map → chunk-5DTSBUL2.js.map} +0 -0
  123. /package/dist/{chunk-CGG5SLH3.js.map → chunk-ARCFIY7U.js.map} +0 -0
  124. /package/dist/{chunk-LUNF2SEL.js.map → chunk-DJKY2TSY.js.map} +0 -0
  125. /package/dist/{chunk-QOTFXW5L.js.map → chunk-P5W7RQKK.js.map} +0 -0
  126. /package/dist/{chunk-X4UCIOTZ.js.map → chunk-S5YLIBFX.js.map} +0 -0
  127. /package/dist/{chunk-FO7HEH76.js.map → chunk-YJBNWCAA.js.map} +0 -0
  128. /package/dist/{chunk-KE2VWPZX.js.map → chunk-ZUUWPZCV.js.map} +0 -0
  129. /package/dist/{run-campaign-FFRM3RH5.js.map → run-campaign-3A2EBYBE.js.map} +0 -0
package/dist/traces.js CHANGED
@@ -27,8 +27,8 @@ import {
27
27
  scoreTraceInsightReadiness,
28
28
  tokenizeDomainWords,
29
29
  traceAnalystOnRunComplete
30
- } from "./chunk-OCFJACJU.js";
31
- import "./chunk-H5UD2323.js";
30
+ } from "./chunk-X6VOPMLD.js";
31
+ import "./chunk-IN455XKS.js";
32
32
  import {
33
33
  extractUsage,
34
34
  extractUsageFromResponse,
@@ -49,7 +49,7 @@ import {
49
49
  TRACE_ANALYST_ACTOR_DESCRIPTION,
50
50
  TRACE_ANALYST_ACTOR_DESCRIPTION_VERSION,
51
51
  analyzeTraces
52
- } from "./chunk-NUKSVU3W.js";
52
+ } from "./chunk-5DTSBUL2.js";
53
53
  import {
54
54
  DEFAULT_REDACTION_RULES,
55
55
  REDACTION_VERSION,
@@ -66,15 +66,17 @@ import {
66
66
  applyToolSpanOtlpAttributes,
67
67
  asString,
68
68
  buildTraceAnalystTools,
69
+ classifyOtlpSpanRole,
69
70
  extractOtlpAttributes,
70
71
  firstStringAttr,
71
72
  inferOtlpKind,
73
+ isOtlpModelCall,
72
74
  projectOtlpFlatLine,
73
75
  readOtlpStatus,
74
76
  stringField,
75
77
  traceAnalystFunctionGroup,
76
78
  traceSpanKindToOpenInferenceKind
77
- } from "./chunk-IR3KBHOY.js";
79
+ } from "./chunk-P6FYH6K4.js";
78
80
  import {
79
81
  RunIntegrityError,
80
82
  assertRunCaptured,
@@ -91,7 +93,7 @@ import {
91
93
  TraceEmitter,
92
94
  llmSpanFromProvider
93
95
  } from "./chunk-VQMK5FMP.js";
94
- import "./chunk-IILEIWGW.js";
96
+ import "./chunk-NL33OXEP.js";
95
97
  import {
96
98
  FAILURE_CLASSES,
97
99
  TRACE_SCHEMA_VERSION,
@@ -193,6 +195,7 @@ export {
193
195
  buildTraceInsightContext,
194
196
  buildTraceInsightPrompt,
195
197
  captureFetchToRawSink,
198
+ classifyOtlpSpanRole,
196
199
  contextInputTokens,
197
200
  convertTraceStoresToOtlp,
198
201
  createOtelExporter,
@@ -216,6 +219,7 @@ export {
216
219
  inferOtlpKind,
217
220
  isJudgeSpan,
218
221
  isLlmSpan,
222
+ isOtlpModelCall,
219
223
  isRetrievalSpan,
220
224
  isSandboxSpan,
221
225
  isToolSpan,
@@ -640,6 +640,7 @@ type ProviderRedactor = (event: RawProviderEvent) => RawProviderEvent;
640
640
  * OpenAI-compatible `/v1/chat/completions` client with:
641
641
  * - Exponential-backoff retry on 429 + 5xx gateway errors (502/503/504).
642
642
  * - Retry on transient network errors (fetch failed, AbortError, ECONNRESET).
643
+ * - One retry at temperature 1 when a model explicitly requires it.
643
644
  * - Graceful json_schema → json_object degrade on 400 with schema-reject body.
644
645
  * - Fenced-JSON stripping (```json ... ```) for models that wrap structured output.
645
646
  * - Configurable base URL + api key / bearer, works with LiteLLM proxies, OpenAI
@@ -34,8 +34,8 @@ import {
34
34
  runRpcOnce,
35
35
  startServer,
36
36
  startServerAsync
37
- } from "../chunk-FO7HEH76.js";
38
- import "../chunk-J5SQWP6Y.js";
37
+ } from "../chunk-YJBNWCAA.js";
38
+ import "../chunk-PBE2LOSS.js";
39
39
  import "../chunk-WS3NZZQQ.js";
40
40
  import "../chunk-VI2UW6B6.js";
41
41
  import "../chunk-PC4UYEBM.js";
package/docs/concepts.md CHANGED
@@ -43,6 +43,18 @@ Agent sessions, worker coordination, sandbox execution, and runtime-specific pro
43
43
 
44
44
  Traces, datasets, optimization, statistics, and reports build on these objects.
45
45
 
46
+ ## Release check results
47
+
48
+ Every entry in `GateResult.contributingGates` has a `status` of `pass`, `fail`, or `not_evaluated`.
49
+ `pass` and `fail` mean the check ran with sufficient input.
50
+ `not_evaluated` means the check lacked enough evidence to run.
51
+ `defaultProductionGate` always requires held-out significance.
52
+ Its other checks are optional until their input is configured or their name is included in `requiredChecks`.
53
+ A required check with missing or insufficient evidence remains `not_evaluated` and holds the release decision.
54
+ An absent optional check records `not_evaluated` and never appears as a successful check.
55
+ Run history is shared input only.
56
+ Enable reward-hacking and canary monitoring independently with `rewardHacking` and `canary`.
57
+
46
58
  When the thing being evaluated is an agent that should keep working, use
47
59
  [`runAgentControlLoop`](./control-runtime.md). It turns validators into a
48
60
  runtime loop: observe typed state, validate it, decide the next action, act,
@@ -138,10 +150,15 @@ const verifier = new MultiLayerVerifier([
138
150
 
139
151
  const report = await verifier.run({ env: { runner, workdir, ... } })
140
152
  report.allPass // boolean: every layer passed
141
- report.blendedScore // 0..1: weighted aggregate
153
+ report.taskScore // complete task score, or undefined
154
+ report.blendedScore // diagnostic weighted aggregate, possibly partial
142
155
  report.layers // per-layer status, findings, duration
143
156
  ```
144
157
 
158
+ Use `taskScore` when creating task labels or training data.
159
+ An errored, timed-out, skipped, or incomplete scoring panel leaves `taskScore` undefined.
160
+ Use `blendedScore` only to inspect the measurements that did complete.
161
+
145
162
  Two rules that will save you bugs:
146
163
 
147
164
  1. **Run both gates.** Build gates catch code that doesn't compile; structural assertions catch missing files. Run both unconditionally: they catch orthogonal failures.
@@ -1,9 +1,7 @@
1
- # Hosted-ingest wire spec: `2026-05-26.v1`
1
+ # Hosted-ingest wire spec: `2026-07-24.v1`
2
2
 
3
- The schema **every** orchestrator (ours, partners' self-hosted ones,
4
- any future open implementation) must accept. Frozen under semver:
5
- **new minors only add optional fields. Breaking changes mean a major
6
- bump and a new `HostedWireVersion` literal.**
3
+ This is the only hosted-ingest wire format implemented by the current package.
4
+ Clients and servers reject every other wire version.
7
5
 
8
6
  This is the contract between `@tangle-network/agent-eval` and any hosted
9
7
  or self-hosted orchestrator. A builder can:
@@ -26,12 +24,12 @@ Two endpoints, both `POST`, both JSON. Headers on every request:
26
24
  | `Authorization` | `Bearer <tenant-key>` (the orchestrator issues this) |
27
25
  | `Content-Type` | `application/json` |
28
26
  | `X-Tangle-Tenant-Id` | The tenant's stable id (the orchestrator's primary key for the tenant) |
29
- | `X-Tangle-Wire-Version` | `2026-05-26.v1` (this spec) |
30
- | `Idempotency-Key` (optional) | UUID; servers MUST treat repeated keys as dedup |
27
+ | `X-Tangle-Wire-Version` | `2026-07-24.v1` (this spec) |
28
+ | `Idempotency-Key` | Non-empty request key, at most 256 characters; clients generate one per call and reuse it across retries |
31
29
 
32
- Responses are JSON of shape `{ accepted: number, rejected: Array<{ index, reason }> }`. The
33
- server SHOULD return 202 (accepted, async) or 200 (accepted, synchronous);
34
- both are equivalent for the wire's purposes.
30
+ Responses are JSON of shape `{ accepted: number, rejected: Array<{ index, reason }> }`.
31
+ Clients validate the complete response before returning it.
32
+ The server can return 202 for asynchronous acceptance or 200 for synchronous acceptance.
35
33
 
36
34
  ### `POST /v1/ingest/eval-runs`
37
35
 
@@ -75,6 +73,7 @@ interface EvalRunEvent {
75
73
  totalCostUsd: number
76
74
  totalDurationMs: number
77
75
  errorMessage?: string // present when status === 'errored'
76
+ insightReport?: InsightReport // current report contract
78
77
  }
79
78
  ```
80
79
 
@@ -86,7 +85,7 @@ interface EvalRunGenerationSnapshot {
86
85
  surfaceHash: string // stable hash of the candidate surface (pivot key)
87
86
  surface?: MutableSurface // OMITTED to avoid PII when consumer prefers
88
87
  cells: EvalRunCellScore[]
89
- compositeMean: number
88
+ compositeMean: number | null // null when no cell has a task-quality label
90
89
  costUsd: number
91
90
  durationMs: number
92
91
  }
@@ -98,12 +97,14 @@ interface EvalRunGenerationSnapshot {
98
97
  interface EvalRunCellScore {
99
98
  scenarioId: string
100
99
  rep: number // 0 for the default; > 0 when reps > 1
101
- compositeMean: number // composite across all judges + dimensions
102
- dimensions: Record< // outer key = judge name; inner = dimension name score
100
+ compositeMean: number | null // null when the cell is unscored
101
+ dimensions: Record< // successful scores; failed or missing judges are absent
103
102
  string,
104
103
  Record<string, number>
105
104
  >
106
- errorMessage?: string // present when the dispatch threw
105
+ terminalOutcome: 'succeeded' | 'failed' | 'cancelled' | 'incomplete' | 'unknown'
106
+ executionErrorCount: number | null // null when the producer cannot classify errors
107
+ errorMessage?: string // present for a dispatch or judge error
107
108
  }
108
109
  ```
109
110
 
@@ -116,10 +117,14 @@ interface TraceSpanEvent {
116
117
  spanId: string
117
118
  parentSpanId?: string
118
119
  name: string
119
- startTimeUnixNano: number
120
- endTimeUnixNano: number
120
+ startTimeUnixNano: string // canonical unsigned 64-bit integer encoded in base 10
121
+ endTimeUnixNano: string // canonical unsigned 64-bit integer encoded in base 10
121
122
  attributes: Record<string, string | number | boolean>
122
- events?: Array<{ timeUnixNano, name, attributes? }>
123
+ events?: Array<{
124
+ timeUnixNano: string // canonical unsigned 64-bit integer encoded in base 10
125
+ name: string
126
+ attributes?: Record<string, string | number | boolean>
127
+ }>
123
128
  status?: { code: 'OK' | 'ERROR' | 'UNSET', message? }
124
129
 
125
130
  // Tangle additions (all optional) for pivoting
@@ -143,12 +148,14 @@ Any orchestrator implementing this spec MUST:
143
148
  a clear error message). The major component is the breaking-change axis.
144
149
  3. **Validate tenant isolation**: queries with `tenantId` X never return
145
150
  data tagged with `tenantId` Y. Test this adversarially.
146
- 4. **Honor idempotency**: when an `Idempotency-Key` matches a prior
147
- request from the same tenant in the last 24h, return the same response
148
- without double-processing.
149
- 5. **Persist eval-runs durably**: at least the event + cell scores must
151
+ 4. **Honor idempotency**: require an `Idempotency-Key`; when it matches a prior request to the same endpoint from the same tenant in the last 24 hours, return the same response without processing the body again.
152
+ 5. **Deduplicate spans**: store at most one span for each `(tenantId, traceId, spanId)` identity.
153
+ Accept an exact duplicate as already stored and reject a conflicting payload with the same identity.
154
+ 6. **Keep run state monotonic**: accept forward lifecycle transitions, but never replace `finished` or `errored` with a late event.
155
+ Delayed events can add a missing generation without replacing terminal totals, labels, or status.
156
+ 7. **Persist eval-runs durably**: at least the event + cell scores must
150
157
  survive an orchestrator restart. Trace spans MAY be best-effort.
151
- 6. **Provide read access**: GET endpoints for the tenant to list + fetch
158
+ 8. **Provide read access**: GET endpoints for the tenant to list + fetch
152
159
  their own runs. Wire format for reads is NOT part of this spec: each
153
160
  orchestrator can pick its own (REST + JSON, gRPC, GraphQL).
154
161
 
@@ -163,9 +170,9 @@ Servers SHOULD also:
163
170
 
164
171
  ## Reference implementation
165
172
 
166
- `examples/hosted-ingest-server/`: a minimal hono-based receiver. ~200
167
- LOC. Validates auth, accepts ingest, stores in memory, exposes a
168
- read endpoint. Runs anywhere Node runs.
173
+ `examples/hosted-ingest-server/` is a Hono receiver for local development and contract tests.
174
+ It validates auth, request and response shapes, versions, exact nanosecond strings, request keys, span identity, and monotonic run state.
175
+ It is not production storage because process restart clears its in-memory data.
169
176
 
170
177
  ```sh
171
178
  TENANT_KEY=dev-token TENANT_ID=acme pnpm tsx examples/hosted-ingest-server/server.ts
@@ -185,19 +192,9 @@ receiver's `GET /v1/runs` lists it back.
185
192
 
186
193
  ---
187
194
 
188
- ## Versioning
195
+ ## Version
189
196
 
190
- `HostedWireVersion` is `"2026-05-26.v1"`.
191
-
192
- - Adding an optional field no version change.
193
- - Adding a new endpoint or new event type → minor wire bump
194
- (`2026-05-26.v2`).
195
- - Changing the shape of an existing field, removing a field, or
196
- changing semantics of an existing field → major wire bump
197
- (`2026-11-XX.v1`); a server may accept both versions during a
198
- transition window.
199
-
200
- Servers MUST reject requests with `X-Tangle-Wire-Version` they don't
201
- support, with a 400 listing the versions they DO accept.
202
-
203
- The version string IS the spec id: pin against it.
197
+ `HostedWireVersion` is `"2026-07-24.v1"`.
198
+ The current clients emit only this value.
199
+ Servers return 400 for every other value and list the accepted version.
200
+ There are no compatibility readers or migration branches.
@@ -16,7 +16,7 @@ This page walks every section with a real (synthetic) example and explains how t
16
16
  ```ts
17
17
  interface InsightReport {
18
18
  n: number // runs analyzed
19
- execution: ExecutionInsight // duration, tokens, models, failures
19
+ execution: ExecutionInsight // duration, tokens, errors, terminal outcomes
20
20
  composite: ScalarDistribution // always
21
21
  perDimension: Record<string, ScalarDistribution> // when judgeScores carry dimensions
22
22
  costQuality: { cost: ScalarDistribution; pareto: ParetoFigureSpec } // always
@@ -36,14 +36,28 @@ interface InsightReport {
36
36
  ## `execution`: runtime facts, separate from quality
37
37
 
38
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.
39
+ It reports duration, optional queue time, direct input, output, reasoning, cache-read, and cache-write tokens, model-call coverage, model cohorts, execution errors, terminal outcomes, and separately reported orchestration aggregates.
40
40
  These fields describe what ran; they do not claim whether the task succeeded.
41
+ `executionErrors` counts child or internal errors reported by the producer.
42
+ `terminalOutcomes` reads only `RunRecord.terminalOutcome`, which must come from root-run or process evidence.
43
+ A child tool error can therefore appear in a run whose terminal outcome is `succeeded`.
44
+ Current OTel and code-agent adapters also preserve process, guardrail, judge, propagated-parent, and unknown error counts in `RunRecord.outcome.raw`.
45
+ These counters are diagnostic and never become task-quality scores.
41
46
 
42
47
  ```jsonc
43
48
  {
44
49
  "execution": {
45
50
  "durationMs": { "n": 30, "p50": 5400, "p95": 82000, "min": 900, "max": 190000 },
46
- "queueMs": { "n": 0, "histogram": [] },
51
+ "queueMs": {
52
+ "n": 0,
53
+ "mean": null,
54
+ "p50": null,
55
+ "p95": null,
56
+ "stddev": null,
57
+ "min": null,
58
+ "max": null,
59
+ "histogram": []
60
+ },
47
61
  "tokenUsage": {
48
62
  "totals": { "input": 50132, "output": 471783, "reasoning": 12000, "cached": 60489565, "cacheWrite": 3032227 },
49
63
  "input": { "n": 30, "p50": 25, "p95": 56 },
@@ -62,12 +76,40 @@ These fields describe what ran; they do not claim whether the task succeeded.
62
76
  },
63
77
  "modelCalls": { "runs": 20, "events": 42, "reportingRuns": 30 },
64
78
  "models": [{ "model": "claude-opus@2026-07-01", "runs": 20 }],
65
- "failures": { "runs": 2, "fraction": 0.067, "reportedErrorEvents": 3, "reportingRuns": 30 }
79
+ "executionErrors": {
80
+ "runs": 2,
81
+ "fraction": 0.067,
82
+ "events": 3,
83
+ "reportingRuns": 30,
84
+ "errorSpanEvents": 3,
85
+ "errorSpanReportingRuns": 30,
86
+ "byTerminalOutcome": {
87
+ "succeeded": { "withErrors": 1, "withoutErrors": 26, "unreported": 0 },
88
+ "failed": { "withErrors": 0, "withoutErrors": 1, "unreported": 0 },
89
+ "cancelled": { "withErrors": 0, "withoutErrors": 0, "unreported": 0 },
90
+ "incomplete": { "withErrors": 0, "withoutErrors": 0, "unreported": 0 },
91
+ "unknown": { "withErrors": 1, "withoutErrors": 1, "unreported": 0 }
92
+ }
93
+ },
94
+ "terminalOutcomes": {
95
+ "succeeded": 27,
96
+ "failed": 1,
97
+ "cancelled": 0,
98
+ "incomplete": 0,
99
+ "unknown": 2
100
+ }
66
101
  }
67
102
  }
68
103
  ```
69
104
 
70
105
  Use `distribution.n` for optional fields to distinguish an uncaptured category from a recorded zero.
106
+ When `distribution.n` is zero, `mean`, percentiles, standard deviation, minimum, and maximum are `null`.
107
+ Use `executionErrors.reportingRuns` to assess error-telemetry coverage.
108
+ `errorSpanEvents` preserves the exact child-span error count separately from other reported execution errors.
109
+ The error fraction uses `reportingRuns` as its denominator and is `null` when no run reported error telemetry, so missing telemetry is not treated as a clean run.
110
+ `byTerminalOutcome` is a cross-tab, not a causal recovery claim.
111
+ It keeps reported errors, reported zeroes, and missing error telemetry separate for every terminal result.
112
+ Missing terminal evidence counts as `unknown`, not `failed`.
71
113
  Never add `aggregateUsage` to direct `tokenUsage`: orchestration spans may repeat model-call usage from other traces.
72
114
  Cost remains in `costQuality`, where observed, estimated, and uncaptured USD stay separate.
73
115
 
@@ -97,7 +139,9 @@ Always present. The basic "where are my numbers" view.
97
139
  }
98
140
  ```
99
141
 
100
- **Read first:** the `composite.mean`. If it's < 0.5, your agent has a ceiling problem, not a tuning problem.
142
+ Read `composite.mean` only when `composite.n > 0`.
143
+ A `null` mean means task quality was not measured, not that quality was zero.
144
+ When a measured mean is below 0.5, inspect the lowest-scoring runs before tuning.
101
145
 
102
146
  **Read next:** `perDimension`. If `clarity` is high but `concision` is low, your prompts get the right ideas in too many words: different fix than "wrong ideas."
103
147
 
@@ -204,13 +248,19 @@ Populated when baseline + candidate candidates are present (auto-detected from t
204
248
  "ci95": [0.04, 0.10], // bootstrap CI on the delta
205
249
  "pValue": 0.0008, // paired t-test
206
250
  "n": 40, // paired observations
207
- "cohensD": 0.41,
251
+ "unpairedBaselineRuns": 2,
252
+ "unpairedCandidateRuns": 1,
253
+ "cohensD": 0.41, // paired Cohen's dz; null when delta variance is zero
208
254
  "mde": 0.06, // min detectable effect at current n, 80% power
209
- "requiredN": 38 // n needed for observed delta at 80% power
255
+ "requiredN": 38 // paired n needed at 80% power; null when dz is undefined
210
256
  }
211
257
  }
212
258
  ```
213
259
 
260
+ Rows pair only when `(experimentId, scenarioId, seed)` matches.
261
+ Missing `scenarioId` and duplicate identities fail loudly.
262
+ Unmatched rows are reported and excluded from paired statistics.
263
+
214
264
  **Decision rule:**
215
265
  - `ci95[0] > threshold` → **SHIP.** Lower bound above your delta threshold means the lift is real at 95% confidence.
216
266
  - `ci95[0] ≤ threshold < ci95[1]` → **INCONCLUSIVE.** Expand the corpus or wait for more data.
@@ -28,10 +28,10 @@ corresponding function or option so the doc and the code don't drift.
28
28
 
29
29
  ## Pairing
30
30
 
31
- Pairs are joined by `(experimentId, seed)` so the comparator and candidate
32
- share scenario *and* seed. This is the same join `gainHistogram` uses; see
33
- `pairScoresByKey` in `src/summary-report.ts`. Records on the wrong split or
34
- with non-finite scores are dropped before pairing.
31
+ Pairs are joined by `(experimentId, scenarioId, seed)` so the comparator and candidate share the same experiment, scenario, and repetition.
32
+ This is the same join used by `gainHistogram`; see `pairRunRecords` in `src/paired-arms.ts`.
33
+ Missing or duplicate identities are rejected.
34
+ Records on the wrong split or with non-finite scores are excluded before pairing, and unmatched valid rows are reported.
35
35
 
36
36
  ## Decision rule
37
37
 
@@ -44,36 +44,35 @@ In order: first match wins:
44
44
  not sufficient*; even a `promote` gate must clear the paired test below.
45
45
  4. Paired N < `RESEARCH_REPORT_HARD_PAIR_FLOOR` → `needs_more_data` with a
46
46
  "below hard floor" reason. Bootstrap CIs degenerate at this size.
47
- 5. ROPE configured AND paired-delta CI ⊂ ROPE → `equivalent`.
48
- 6. Paired-delta CI upper bound < 0 → `reject` (CI excludes a non-negative
47
+ 5. `split: 'search'` → `hold`.
48
+ Search results guide optimization but cannot authorize a release.
49
+ 6. ROPE configured AND paired-delta CI ⊂ ROPE → `equivalent`.
50
+ 7. Paired-delta CI upper bound < 0 → `reject` (CI excludes a non-negative
49
51
  effect). Note: this uses **paired delta only**: not the marginal mean.
50
- 7. Paired N < `minPairs` (soft floor) → `needs_more_data` with the MDE at
52
+ 8. Paired N < `minPairs` (soft floor) → `needs_more_data` with the MDE at
51
53
  current N attached so the verdict is actionable.
52
- 8. BH-adjusted q ≤ `fdr` AND CI lower bound > 0 → `promote`. The BH q-value
54
+ 9. BH-adjusted q ≤ `fdr` AND CI lower bound > 0 → `promote`. The BH q-value
53
55
  controls FDR across all candidates in the same sweep; the bootstrap CI
54
56
  provides an effect-size guarantee independent of the test.
55
- 9. Otherwise → `hold`.
57
+ 10. Otherwise → `hold`.
56
58
 
57
59
  ## Statistical primitives used
58
60
 
59
61
  | Quantity | Function | Source file |
60
62
  |---|---|---|
61
63
  | Marginal CI on score mean | `confidenceInterval` | `statistics.ts` |
62
- | Cohen's d vs comparator | `cohensD` | `statistics.ts` |
64
+ | Paired Cohen's dz vs comparator | `pairedCohensDz` | `statistics.ts` |
63
65
  | Wilcoxon signed-rank (paired) | `wilcoxonSignedRank` | `statistics.ts` |
64
- | BH-FDR q-values | `benjaminiHochberg` | `power-analysis.ts` |
65
- | Paired bootstrap CI on median delta | `pairedBootstrap` | `paired-stats.ts` |
66
- | Bayesian-bootstrap-style Pr(Δ>0), Pr(Δ∈ROPE) | `bootstrapMeanSamples` | `summary-report.ts` (private) |
67
- | Minimum detectable paired effect | `pairedMde` | `power-analysis.ts` |
66
+ | BH-FDR q-values | `benjaminiHochberg` | `statistics.ts` |
67
+ | Paired bootstrap CI on median delta | `pairedBootstrap` | `statistics.ts` |
68
+ | Bayesian-bootstrap Pr(Δ>0), Pr(Δ∈ROPE) | `bayesianBootstrapMeanSamples` | `summary-report.ts` (private) |
69
+ | Minimum detectable paired effect | `pairedMde` | `statistics.ts` |
68
70
  | Run fingerprint | `hashJson(canonicalize(...))` | `pre-registration.ts` |
69
71
 
70
- The Pr(Δ>0) and Pr(Δ∈ROPE) summaries use the bootstrap-prior duality of
71
- [Rubin 1981]: under a non-informative Dirichlet prior, the bootstrap
72
- distribution of a sample statistic is its posterior. We expose these as
73
- posterior summaries on the **mean** delta and the bootstrap CI on the
74
- **median** delta: the median is more robust to the heavy-tailed score
75
- distributions seen in agent benchmarks; the mean lets us read off the
76
- Bayesian-style probability of superiority in a single number.
72
+ The Pr(Δ>0) and Pr(Δ∈ROPE) summaries use Rubin's Bayesian bootstrap.
73
+ Each posterior draw assigns the observed paired deltas Dirichlet(1, ..., 1) weights, implemented as normalized independent Exponential(1) draws.
74
+ The posterior summaries apply to the **mean** delta.
75
+ The separate frequentist bootstrap CI applies to the **median** delta because the median is more robust to heavy-tailed agent scores.
77
76
 
78
77
  ## MDE
79
78
 
package/docs/rollout.md CHANGED
@@ -25,24 +25,29 @@ Rule applied: where fields conflicted, RunRecord-derived semantics won; the ledg
25
25
  | `schema: "tangle.rollout.v1"` | ledger | wire key is `schema` (PR #410's `format` key retired) |
26
26
  | `rollout_id` / `parent_rollout_id` | ledger | minted lines use `runId` as `rollout_id` (deterministic); multi-agent producers mint UUIDs and point workers at their supervisor |
27
27
  | `run_id` | both | `RunRecord.runId` |
28
- | `experiment_id`, `candidate_id` | PR #410 | added to the wire (nullable, optional-on-read for pre-unification ledgers) |
28
+ | `experiment_id`, `candidate_id` | PR #410 | required keys; `null` means the producer did not record the value |
29
29
  | `generation`, `candidate_index` | ledger | kept as improvement-loop coordinates; now `integer \| null` (`null` = not an improvement loop, `-1` = baseline) |
30
30
  | `role` | ledger | enum extended with `agent` for solo eval runs (mint default) |
31
- | `task.split` | conflict | **RunRecord semantics win**: canonical trainable value is `search` (the optimizer-readable pool); `dev`/`holdout` as in `RunSplitTag`; ledger's `canary` kept; ledger's `train` validates as a legacy alias of `search` and counts as trainable, but new producers emit `search` |
31
+ | `task.split` | conflict | **RunRecord semantics win**: `search` is the trainable pool; `dev` and `holdout` follow `RunSplitTag`; `canary` is retained for release checks |
32
32
  | `task.seed`, `task.rep` | ledger | seed from `RunRecord.seed`; rep 0 for minted solo runs |
33
33
  | `policy.*` | ledger | + `prompt_hash`, `config_hash`, `agent_profile_cell_id` from PR #410's RunRecord provenance |
34
34
  | `messages` | ledger | canonical OpenAI chat-with-tools incl. `reasoning_content`; minted lines inline the final llm span's conversation |
35
35
  | `steps` | PR #410 | optional trace-span projections (llm/tool), absent on harness-store-derived lines |
36
- | `outcome.reward` | conflict | **merged**: `number \| null`; `null` means no verdict exists (a labeled gap, never 0); minted lines always carry a number (`holdoutScore ?? searchScore ?? 0`), forced to 0 when realness-gated |
37
- | `outcome.realness_gated` | PR #410 | added (optional-on-read, absent = false); the anti-Goodhart gate travels into the data and SFT export refuses gated lines |
38
- | `outcome.reward_source` / `verdict` / `metrics` / `is_completed` / `is_truncated` / `error` | ledger | unchanged; mint fills `metrics` from `RunRecord.outcome.raw` |
36
+ | `outcome.reward` | conflict | **merged**: `number \| null`; `null` means no verdict exists (a labeled gap, never 0); minting requires an explicit RunRecord task score and rejects execution-only records instead of labeling them 0; realness-gated scores are forced to 0 |
37
+ | `outcome.realness_gated` | PR #410 | required boolean; the anti-Goodhart decision travels with the row and every training export refuses gated lines |
38
+ | `outcome.reward_source` / `verdict` / `metrics` | ledger | unchanged; mint fills `metrics` from `RunRecord.outcome.raw` |
39
+ | `outcome.is_completed` / `is_truncated` / `error` | conflict | mint derives terminal fields from the required `RunRecord.terminalOutcome`; producers use the explicit `unknown` value when terminal evidence is unavailable |
39
40
  | `cost.*` | ledger | superset of PR #410's costUsd/totalTokens; `cost.usd` is `null` when `costProvenance.kind === 'uncaptured'` (never a fake 0) |
40
41
  | `artifacts.*`, `provenance.*` | ledger | `provenance.capture` gains `mint` alongside `settle-time` / `backfill` |
41
42
  | gap discipline | ledger | records without trace spans become labeled gap lines (`messages: []`, `provenance.gap`) AND are listed in `missingTraces`; PR #410's silent skip retired |
42
43
 
43
44
  ## Export filters (fail-closed)
44
45
 
45
- - SFT: `reward 1` (configurable) not realness-gated trainable split non-empty transcript. Rows are `{messages, metadata}`.
46
- - Reward rows: every line with a non-null reward, failures included; unlabeled lines excluded (a gap is not a zero).
47
- - Verifiers / RFT: transcript-bearing lines only; RFT additionally requires prompt turns before the first assistant turn.
46
+ - SFT, reward rows, Verifiers, and RFT default to completed, non-truncated, error-free, ungated `search` runs with reward greater than `0`.
47
+ - `minimumQualityExclusive` can raise the quality floor.
48
+ - Held-out rows require `allowHeldOutTrainingData: true`.
49
+ - SFT also requires a non-empty transcript.
50
+ - Reward rows require a non-empty user prompt.
51
+ - Verifiers requires both prompt and completion turns.
52
+ - RFT requires prompt turns before the first assistant turn.
48
53
  - Release (`rollout-release`): trainable split only; proposer lines dropped unless `--include-proposers`; every string value scrubbed by the 9 deterministic rules (idempotent, so a second pass counts zero); `--push` requires `huggingface-cli` + `HF_TOKEN` and never prints the token.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-eval",
3
- "version": "0.126.6",
3
+ "version": "0.127.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": {
@@ -160,30 +160,37 @@
160
160
  "verify:package": "pnpm run check:skill && node scripts/verify-package-exports.mjs"
161
161
  },
162
162
  "dependencies": {
163
- "@asteasolutions/zod-to-openapi": "^8.5.0",
164
- "@ax-llm/ax": "^23.0.1",
165
- "@hono/node-server": "^2.0.0",
163
+ "@asteasolutions/zod-to-openapi": "^9.1.0",
164
+ "@ax-llm/ax": "^23.0.5",
165
+ "@hono/node-server": "^2.0.11",
166
166
  "@tangle-network/agent-core": "^0.4.20",
167
167
  "@tangle-network/agent-interface": "^0.32.0",
168
168
  "@tangle-network/tcloud": "^0.4.14",
169
- "hono": "^4.12.30",
170
- "zod": "^4.3.6"
169
+ "hono": "^4.12.32",
170
+ "zod": "^4.4.3"
171
171
  },
172
172
  "devDependencies": {
173
- "@biomejs/biome": "^2.4.15",
174
- "@types/node": "^25.6.0",
173
+ "@biomejs/biome": "^2.5.5",
174
+ "@types/node": "^26.1.1",
175
+ "esbuild": "^0.28.1",
175
176
  "husky": "^9.1.7",
176
- "lint-staged": "^17.0.5",
177
- "openapi3-ts": "^4.5.0",
177
+ "lint-staged": "^17.2.0",
178
+ "openapi3-ts": "^4.6.0",
178
179
  "tsup": "^8.0.0",
179
- "typescript": "^5.7.0",
180
- "vitest": "^3.0.0",
180
+ "typescript": "^6.0.3",
181
+ "vitest": "^4.1.10",
181
182
  "yaml": "2.9.0"
182
183
  },
183
184
  "pnpm": {
184
185
  "minimumReleaseAge": 4320,
186
+ "minimumReleaseAgeExclude": [
187
+ "esbuild",
188
+ "vite"
189
+ ],
185
190
  "overrides": {
186
- "postcss@<8.5.10": "^8.5.10",
191
+ "esbuild@>=0.27.3 <0.28.1": "^0.28.1",
192
+ "postcss@<8.5.18": "^8.5.18",
193
+ "vite@>=7.0.0 <=7.3.4": "^7.3.5",
187
194
  "ws@>=8.0.0 <8.21.0": "^8.21.0"
188
195
  }
189
196
  },
@@ -199,5 +206,5 @@
199
206
  ]
200
207
  },
201
208
  "license": "MIT",
202
- "packageManager": "pnpm@10.22.0"
209
+ "packageManager": "pnpm@10.34.5"
203
210
  }