@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
@@ -1,37 +1,6 @@
1
- import { v as PolicyEditAdmissionOptions, u as PolicyEditAdmission, t as PolicyEdit, c as AnalystFinding, F as FindingToPolicyEditOptions } from '../policy-edit-wG9uFEFm.js';
2
- export { r as POLICY_EDIT_CANDIDATE_RECORD_SCHEMA, P as PolicyEditCandidateRecord, a2 as validatePolicyEditCandidateRecord } from '../policy-edit-wG9uFEFm.js';
3
- import { P as PairedArmsComparison, S as SignedManifest, B as BackendIntegrityReport, C as CompletionRequirement, R as RuntimeEventLike, a as CompletionVerdict, b as ProducedState, c as CorrectnessChecker } from '../pre-registration-BWQhJ3vz.js';
4
- export { L as LlmJudgeDimension, d as LlmJudgeOptions, l as llmJudge } from '../pre-registration-BWQhJ3vz.js';
5
- import { C as CampaignRunPlan, P as PlanCampaignRunOptions, h as RunCampaignOptions, i as RunImprovementLoopOptions } from '../gepa-eESocoDi.js';
6
- export { o as CampaignRunPlanCell, p as GepaProposerConstraints, G as GepaProposerOptions, O as OpenAutoPrOptions, q as OpenAutoPrResult, e as ReferenceEquivalenceJudgeOptions, g as ReferenceEquivalenceScenario, a as RunImprovementLoopResult, R as RunOptimizationOptions, s as RunOptimizationResult, t as countSentenceEdits, j as createReferenceEquivalenceJudge, u as defaultRenderDiff, v as extractH2Sections, k as gepaProposer, w as openAutoPr, x as planCampaignRun, r as runCampaign, l as runImprovementLoop, y as runOptimization } from '../gepa-eESocoDi.js';
7
- import { A as AnalyzeTracesOptions, a as AnalyzeTracesInput, b as AnalyzeTracesResult } from '../analyst-C8HHvfJp.js';
8
- import { S as Scenario, M as MutableSurface, D as DispatchContext, b as JudgeConfig, G as Gate, o as GenerationRecord, J as JudgeScore, L as LabeledScenarioStore, s as LabeledScenarioWrite, t as LabeledScenarioSampleArgs, u as LabeledScenarioRecord, v as LabelTrust, c as SurfaceProposer, w as ProposedCandidate, x as ProposeContext, j as CodeSurface, y as LabeledScenarioSource, C as CampaignResult } from '../types-BSw1rOUB.js';
9
- export { e as CampaignAggregates, f as CampaignArtifactWriter, g as CampaignCellResult, h as CampaignCostMeter, z as CampaignTokenUsage, i as CampaignTraceWriter, k as DispatchFn, l as GateContext, d as GateDecision, m as GateResult, n as GenerationCandidate, A as JudgeAggregate, a as JudgeDimension, p as Mutator, O as OptimizationProposer, q as OptimizerConfig, P as ParetoParent, R as RedactionStatus, B as ScenarioAggregate, E as ScoredSurfaceOutcome, r as SessionScript, T as TraceSpan, F as isProposedCandidate, H as labelTrustRank } from '../types-BSw1rOUB.js';
10
- import { a as PairedBootstrapResult, E as EProcessState } from '../statistics-KUnG73jH.js';
11
- import { C as CampaignStorage } from '../storage-DrX3v_5B.js';
12
- export { c as createRunCostLedger, f as fsCampaignStorage, i as inMemoryCampaignStorage } from '../storage-DrX3v_5B.js';
13
- export { A as AxisEvidence, a as AxisVerdict, B as BuildEvidenceVectorOptions, l as BuildLoopProvenanceArgs, D as DefaultProductionGateOptions, m as EmitLoopProvenanceArgs, n as EmitLoopProvenanceResult, E as EvidenceVector, b as EvolutionaryProposerOptions, H as HeldOutGateOptions, o as LoopProvenanceBackend, q as LoopProvenanceCandidate, L as LoopProvenanceRecord, O as ObjectiveSource, c as ParetoSignificanceGateOptions, P as PowerPreflight, s as PowerPreflightOptions, d as PromotionObjective, e as PromotionPolicy, R as RunEvalOptions, f as buildEvidenceVector, t as buildLoopProvenanceRecord, g as composeGate, h as defaultProductionGate, u as emitLoopProvenance, i as evolutionaryProposer, j as heldOutGate, v as loopProvenanceSpans, p as paretoPolicy, k as paretoSignificanceGate, w as powerPreflight, x as provenanceRecordPath, y as provenanceSpansPath, r as runEval } from '../provenance-DpjwyseI.js';
14
- import { a as LlmClientOptions } from '../llm-client-qoDd18Qz.js';
1
+ import { z } from 'zod';
2
+ import { AxAIService, AxFunction } from '@ax-llm/ax';
15
3
  import { AgentProfile } from '@tangle-network/agent-interface';
16
- import { A as AgentEvalError, V as ValidationError } from '../errors-oeQrLqXC.js';
17
- import { a as RunSplitTag, R as RunRecord } from '../run-record-BDH49H2E.js';
18
- import { C as CostLedger, b as CostLedgerSummary, M as MaximumCharge, c as CostReceiptInput } from '../cost-ledger-DWy3XdJc.js';
19
- import { T as TraceAnalystKindSpec } from '../kind-factory-ClZmO25A.js';
20
- import '../store-C1YxJDEK.js';
21
- import '../types-BkfcQnxV.js';
22
- import '@tangle-network/tcloud';
23
- import 'zod';
24
- import '../raw-provider-sink-C46HDghv.js';
25
- import '../verdict-C9MlYujm.js';
26
- import '../dataset-NENEzRgk.js';
27
- import '../store-DGqD0Pyo.js';
28
- import '../schema-B3Q3l9Z_.js';
29
- import '@ax-llm/ax';
30
- import '../judge-calibration-7C-IDmKr.js';
31
- import '../hosted/index.js';
32
- import '../insight-report-DY4nDW9Q.js';
33
- import '../summary-report-C5bKFfm-.js';
34
- import '../failure-cluster-DOAcSJ87.js';
35
4
 
36
5
  /**
37
6
  * Lineage DAG — a git-graph of improvement candidates.
@@ -270,6 +239,1941 @@ interface RunLineageResult {
270
239
  * every node. Honors `prune`: a pruned track is never extended or branched again. */
271
240
  declare function runLineage(opts: RunLineageOptions): Promise<RunLineageResult>;
272
241
 
242
+ /**
243
+ * Error taxonomy for `@tangle-network/agent-eval`.
244
+ *
245
+ * Every error this package throws as part of its *public contract* extends
246
+ * `AgentEvalError`. Consumers can pattern-match by `instanceof <Subclass>` or
247
+ * by the stable string `code` carried on the base class.
248
+ *
249
+ * The codes are stable across minor versions; new codes can be added, but
250
+ * existing codes never change meaning. New subclasses are non-breaking.
251
+ *
252
+ * Internal invariant guards (`throw new Error('this should never happen')`)
253
+ * remain plain `Error`s on purpose — they're programmer-mistake assertions,
254
+ * not consumer-catchable contract failures.
255
+ */
256
+ type AgentEvalErrorCode = 'validation' | 'not_found' | 'config' | 'capture_integrity' | 'judge' | 'verification' | 'replay' | 'backend_integrity' | 'profile_matrix';
257
+ /**
258
+ * Base class for every contract error this package throws — carries the stable
259
+ * string `code` taxonomy so consumers can `instanceof`-match or switch on `code`.
260
+ */
261
+ declare class AgentEvalError extends Error {
262
+ /** Stable string code. Survives minification; safe to switch on. */
263
+ readonly code: AgentEvalErrorCode;
264
+ constructor(code: AgentEvalErrorCode, message: string, options?: {
265
+ cause?: unknown;
266
+ });
267
+ }
268
+ /** Caller passed invalid arguments (out of range, mutually-exclusive options, bad shape). */
269
+ declare class ValidationError extends AgentEvalError {
270
+ constructor(message: string, options?: {
271
+ cause?: unknown;
272
+ });
273
+ }
274
+
275
+ type AgentProfileCellSchemaVersion = 'agent-profile-cell/v1';
276
+ type AgentProfileJson = string | number | boolean | null | AgentProfileJson[] | {
277
+ [key: string]: AgentProfileJson;
278
+ };
279
+ type AgentProfileDimensionValue = string | number | boolean | null;
280
+ interface AgentProfileSource {
281
+ /** Runtime/profile contract being fingerprinted, e.g. `agent-interface-profile`. */
282
+ kind: string;
283
+ /** sha256 over the canonical source profile object. */
284
+ hash: string;
285
+ }
286
+ interface AgentProfileHarness {
287
+ id: string;
288
+ version?: string;
289
+ hash?: string;
290
+ }
291
+ interface AgentProfileCell {
292
+ schemaVersion: AgentProfileCellSchemaVersion;
293
+ cellId: string;
294
+ profileId: string;
295
+ sourceProfile: AgentProfileSource;
296
+ harness?: AgentProfileHarness;
297
+ model?: string;
298
+ promptHash?: string;
299
+ dimensions?: Record<string, AgentProfileDimensionValue>;
300
+ }
301
+
302
+ type FailureClass = 'success' | 'reasoning_error' | 'tool_selection_error' | 'tool_argument_error' | 'tool_recovery_failure' | 'hallucination' | 'instruction_following' | 'safety_refusal_miss' | 'policy_violation' | 'budget_exceeded' | 'format_drift' | 'permission_escalation' | 'pii_leak' | 'cost_overrun' | 'timeout' | 'sandbox_failure' | 'missing_user_data' | 'missing_domain_data' | 'missing_codebase_context' | 'missing_runtime_context' | 'missing_credentials' | 'missing_integration_connection' | 'missing_integration_scope' | 'integration_approval_required' | 'integration_auth_expired' | 'integration_provider_failure' | 'bad_integration_manifest' | 'unsafe_integration_write_denied' | 'stale_external_data' | 'bad_retrieval' | 'insufficient_evidence' | 'contradictory_evidence' | 'ambiguous_user_intent' | 'knowledge_readiness_blocked' | 'unknown';
303
+
304
+ /**
305
+ * Paper-grade RunRecord schema + runtime validator.
306
+ *
307
+ * Every run that participates in a promotion gate, paper table, or
308
+ * researcher loop SHOULD be recorded as a `RunRecord`. The mandatory
309
+ * fields are exactly those the paper "Two Loops, Three Roles" requires
310
+ * for reproducibility: who/what/when/cost/seed/hash, plus the search vs
311
+ * holdout split tag and either a `searchScore` or a `holdoutScore`.
312
+ *
313
+ * This is intentionally NOT a replacement for the rich `Run` /
314
+ * `ProposeReviewReport` / `ScenarioResult` types already in the
315
+ * package. Those are runtime structures with full provenance. A
316
+ * `RunRecord` is the analysis-time projection — the JSON-friendly
317
+ * row you'd put in a parquet file or paste into a notebook.
318
+ *
319
+ * Validate at the boundary:
320
+ *
321
+ * const rec = validateRunRecord(rawJson) // throws on missing
322
+ * const ok = isRunRecord(rawJson) // boolean check
323
+ * const rec = parseRunRecordSafe(rawJson) // { ok, value | error }
324
+ *
325
+ * The validator runs in pure TS — zod is intentionally NOT a
326
+ * dependency. Round-trip tested in `tests/run-record.test.ts`.
327
+ */
328
+
329
+ /** Search/dev/holdout split tag. 'search' is the paper-grade alias for the
330
+ * combined train+test pool that the optimizer is allowed to read. */
331
+ type RunSplitTag = 'search' | 'dev' | 'holdout';
332
+ interface RunTokenUsage {
333
+ input: number;
334
+ /** All generated tokens charged as output, including reasoning tokens. */
335
+ output: number;
336
+ /** Reasoning-token subset of `output`, when the provider reports it. */
337
+ reasoning?: number;
338
+ /** Prompt tokens served from a provider cache. */
339
+ cached?: number;
340
+ /** Prompt tokens written into a provider cache. */
341
+ cacheWrite?: number;
342
+ }
343
+ /**
344
+ * How a run's USD amount was obtained.
345
+ *
346
+ * `costUsd` remains mandatory for wire compatibility. New producers should
347
+ * always populate this discriminated union so a missing bill is never
348
+ * mistaken for an observed zero-dollar run. For `uncaptured`, `costUsd` uses
349
+ * the legacy `0` sentinel while this field carries the truthful null.
350
+ */
351
+ type RunCostProvenance = {
352
+ kind: 'observed';
353
+ usd: number;
354
+ } | {
355
+ kind: 'estimated';
356
+ usd: number;
357
+ } | {
358
+ kind: 'uncaptured';
359
+ usd: null;
360
+ };
361
+ interface RunJudgeMetadata {
362
+ model: string;
363
+ promptVersion: string;
364
+ /** [0,1] confidence the judge declared. Constant judge confidence
365
+ * across many runs is a fallback signal (see `canary.ts`). */
366
+ confidence: number;
367
+ /** True if the judge degraded to a fallback path (rules-only,
368
+ * prior-call cache, etc.). The canary uses this to alert. */
369
+ fallback: boolean;
370
+ }
371
+ /**
372
+ * Per-judge / per-dimension breakdown for runs scored by an ensemble of
373
+ * judges over a multi-dimensional rubric.
374
+ *
375
+ * The collapsed `outcome.searchScore` / `holdoutScore` carries the
376
+ * composite the gate uses. The full breakdown belongs here so consumers
377
+ * can answer "which judge disagreed?", "which dimension dragged the
378
+ * composite down?", and "did half the panel fail?" without re-running.
379
+ *
380
+ * `perJudge[judgeId][dim]` is the canonical source; `perDimMean` and
381
+ * `composite` are convenience projections — derivable but precomputed so
382
+ * downstream IRR primitives (`interRaterReliability`,
383
+ * `corpusInterRaterAgreement`) and reporters don't pay the same
384
+ * aggregation twice.
385
+ *
386
+ * Fail-loud discipline: judges that errored out land in `failedJudges`
387
+ * by id. A missing key in `perJudge` is ambiguous (silent zero vs not
388
+ * run); the explicit list makes a partial-failure recorded as such.
389
+ */
390
+ interface JudgeScoresRecord {
391
+ /** Per-judge per-dimension scores. `{ "kimi-k2.6": { helpfulness: 0.8, clarity: 0.7 }, ... }`. */
392
+ perJudge: Record<string, Record<string, number>>;
393
+ /** Per-dim mean across judges. Convenience — derivable from `perJudge`. */
394
+ perDimMean: Record<string, number>;
395
+ /** Composite mean across all dims and judges. Mirrors the score
396
+ * the gate sees on `outcome.searchScore` / `holdoutScore`. */
397
+ composite: number;
398
+ /** Judges that errored or returned an unparseable verdict. Recorded
399
+ * by id (e.g. `['glm-5.1']`) so a partial-failure case is explicit,
400
+ * not inferred from missing keys in `perJudge`. */
401
+ failedJudges?: string[];
402
+ /** Free-form notes the judges emitted (joined across judges or
403
+ * first-judge only — consumer's choice). */
404
+ notes?: string;
405
+ }
406
+ interface RunOutcome {
407
+ /** Score on the search/optimization split. Optional because a
408
+ * holdout-only evaluation only fills `holdoutScore`. */
409
+ searchScore?: number;
410
+ /** Score on the held-out split. Optional because a search-only run
411
+ * only fills `searchScore`. At least one must be present. */
412
+ holdoutScore?: number;
413
+ /** Bag of any other metric the run produced — judge dimensions,
414
+ * pass/fail counters, latency stats, etc. Numeric only — keeps
415
+ * reporters honest. */
416
+ raw: Record<string, number>;
417
+ /** Per-judge / per-dim breakdown. Consumers writing ensemble
418
+ * judgements populate this; substrate primitives like
419
+ * `interRaterReliability` and `corpusInterRaterAgreement` accept
420
+ * these records as input. Optional — single-judge or scalar-only
421
+ * runs leave it unset. */
422
+ judgeScores?: JudgeScoresRecord;
423
+ /** Authenticity / realness verdict — did the run build the REAL thing on the
424
+ * intended infra, or fake it (see `./authenticity`)? Optional: only domains
425
+ * with an authenticity config populate it. Carried in the corpus so the
426
+ * flywheel / off-policy learning can optimize for real completion, not gamed
427
+ * pass-rate. `score` is 0-1; `gated` is the anti-Goodhart flag — a gated run
428
+ * must not count as a real success regardless of `score`. */
429
+ realness?: {
430
+ score: number;
431
+ gated: boolean;
432
+ reason?: string;
433
+ };
434
+ }
435
+ /**
436
+ * Mandatory paper-grade fields for a single evaluation run. Optional
437
+ * fields are extension points; mandatory fields throw if missing.
438
+ *
439
+ * Hash discipline:
440
+ * - `promptHash` is the sha256 of the EFFECTIVE prompt sent to the
441
+ * model (after any steering bundle merge).
442
+ * - `configHash` is the sha256 of the effective run config (model,
443
+ * temperature, tools, judges, splits). The pair (promptHash,
444
+ * configHash) uniquely identifies an experiment cell.
445
+ *
446
+ * Model snapshot discipline:
447
+ * - `model` MUST encode a snapshot version. Bare aliases like
448
+ * `claude-sonnet-4` or `gpt-4o` are banned — they remap silently.
449
+ * Use `claude-sonnet-4-6@2025-04-15` or `gpt-4o-2024-11-20`.
450
+ */
451
+ interface RunRecord {
452
+ /** UUID for the run. */
453
+ runId: string;
454
+ /** Logical experiment grouping (a treatment vs a baseline within
455
+ * the same sweep should share `experimentId`). */
456
+ experimentId: string;
457
+ /** Stable identifier for the candidate (variant) being run. The
458
+ * promotion gate compares two `candidateId`s on matched items. */
459
+ candidateId: string;
460
+ /** RNG seed for the run. Always recorded — silent re-seeding is
461
+ * the most common cause of non-reproducible numbers. */
462
+ seed: number;
463
+ /** Model identifier WITH snapshot version. */
464
+ model: string;
465
+ /** sha256 of the effective prompt (post-steering). */
466
+ promptHash: string;
467
+ /** sha256 of the effective config. */
468
+ configHash: string;
469
+ /** Git SHA the harness was run from. */
470
+ commitSha: string;
471
+ /** End-to-end wall-clock duration in milliseconds. */
472
+ wallMs: number;
473
+ /** Time spent queued before execution started, if known. */
474
+ queueMs?: number;
475
+ /** Total USD cost. Mandatory — runs without a cost number are
476
+ * unbounded by definition and must not be admitted into the gate.
477
+ * `0` is retained as the compatibility sentinel for an uncaptured amount;
478
+ * inspect `costProvenance` before treating it as observed. */
479
+ costUsd: number;
480
+ /** Observed, model-priced estimate, or genuinely uncaptured USD amount.
481
+ * Optional only so existing serialized RunRecords remain valid. */
482
+ costProvenance?: RunCostProvenance;
483
+ /** Token usage breakdown. */
484
+ tokenUsage: RunTokenUsage;
485
+ /** Judge-side metadata, if a judge was used. */
486
+ judgeMetadata?: RunJudgeMetadata;
487
+ /** Per-split scores + raw bag. */
488
+ outcome: RunOutcome;
489
+ /** Canonical, cross-agent failure class drawn from the shared
490
+ * `FAILURE_CLASSES` taxonomy. This is the aggregation key that makes
491
+ * "which failure dominates across the whole fleet" answerable in ONE
492
+ * vocabulary — every agent classifies against the same enum. Producers
493
+ * set it via the substrate classifier; leave unset only when the failure
494
+ * genuinely can't be classified. */
495
+ failureClass?: FailureClass;
496
+ /** Free-form domain-specific failure detail, scoped UNDER `failureClass`
497
+ * (e.g. failureClass='tool_recovery_failure', failureMode='forge_build_unsatisfied').
498
+ * The within-agent drill-down; `failureClass` is the cross-agent key. */
499
+ failureMode?: string;
500
+ /** Which split this run was drawn from. */
501
+ splitTag: RunSplitTag;
502
+ /**
503
+ * Stable scenario identifier the run was scored against. Optional for
504
+ * backwards compatibility, but **strongly recommended**: every primitive
505
+ * that pairs runs by scenario (preferences, paired stats, BT tournament)
506
+ * keys on this. The campaign artifact populates it canonically; legacy
507
+ * runs without it fall back to inference from `outcome.raw.scenario_id`
508
+ * or `experimentId`.
509
+ */
510
+ scenarioId?: string;
511
+ /**
512
+ * Canonical identity for the agent profile cell that produced this row:
513
+ * profile artifact hash plus optional harness/model/prompt/reporting
514
+ * dimensions. Use `agentProfile.cellId` to group persona sweeps and
515
+ * longitudinal reports by the complete source profile, not by a loose
516
+ * candidate label or opaque config hash.
517
+ */
518
+ agentProfile?: AgentProfileCell;
519
+ }
520
+
521
+ /**
522
+ * Shared types for the trace-analyst module.
523
+ *
524
+ * Wire format. The store interface speaks `OtlpSpanLike` rows — one JSONL
525
+ * line per span, OTLP-shaped. We do NOT depend on a specific tracing
526
+ * vendor at the type level. Adapter
527
+ * layers map upstream shapes onto this interface.
528
+ *
529
+ * Design constraint. Every read operation that can return arbitrary
530
+ * payload must carry a byte budget so the agent's tool result stays
531
+ * bounded regardless of input trace size. Oversized responses
532
+ * substitute a deterministic summary instead of bytes — see
533
+ * `ViewTraceOversized`.
534
+ */
535
+ /** OTLP span kind (subset we actually use). */
536
+ type TraceAnalystSpanKind = 'AGENT' | 'LLM' | 'TOOL' | 'CHAIN' | 'GUARDRAIL' | 'SPAN' | 'UNKNOWN';
537
+ type TraceAnalystSpanStatus = 'OK' | 'ERROR' | 'UNSET';
538
+ /** Subset of OTLP span fields the analyst exposes to the agent. The
539
+ * store's job is to project upstream's full span shape down to this
540
+ * view — the analyst never sees vendor extensions directly. */
541
+ interface TraceAnalystSpan {
542
+ trace_id: string;
543
+ span_id: string;
544
+ parent_span_id: string | null;
545
+ name: string;
546
+ kind: TraceAnalystSpanKind;
547
+ start_time: string;
548
+ end_time: string;
549
+ duration_ms: number;
550
+ status: TraceAnalystSpanStatus;
551
+ status_message?: string;
552
+ service_name: string | null;
553
+ agent_name: string | null;
554
+ model_name: string | null;
555
+ tool_name: string | null;
556
+ /** Raw JSON-serialisable attribute map. May contain large strings;
557
+ * callers must respect the per-attribute byte cap. */
558
+ attributes: Record<string, unknown>;
559
+ }
560
+ interface TraceAnalystTraceSummary {
561
+ trace_id: string;
562
+ service_name: string | null;
563
+ agent_name: string | null;
564
+ span_count: number;
565
+ has_errors: boolean;
566
+ start_time: string;
567
+ end_time: string;
568
+ duration_ms: number;
569
+ raw_jsonl_bytes: number;
570
+ models: string[];
571
+ tools: string[];
572
+ }
573
+ interface TraceAnalystFilters {
574
+ /** Restrict to traces that contain at least one error span. */
575
+ has_errors?: boolean;
576
+ /** Match if any span's `service.name` is in this list. */
577
+ service_names?: string[];
578
+ /** Match if any span's `agent.name` is in this list. */
579
+ agent_names?: string[];
580
+ /** Match if any LLM span's `llm.model_name` is in this list. */
581
+ model_names?: string[];
582
+ /** Match if any tool span's `tool.name` is in this list. */
583
+ tool_names?: string[];
584
+ /** ISO-8601 lower bound on the trace's earliest start time. */
585
+ start_time_after?: string;
586
+ /** ISO-8601 upper bound on the trace's earliest start time. */
587
+ start_time_before?: string;
588
+ /** Single regex applied to raw JSONL bytes for the trace. Opt-in;
589
+ * expensive on large datasets. Use the indexed filters above first. */
590
+ regex_pattern?: string;
591
+ }
592
+ /** One distinct error signature across the dataset — the deterministic unit of
593
+ * failure coverage. Signatures normalize volatile tokens (digits, hex/uuids,
594
+ * paths, durations) out of the span `status_message` so semantically identical
595
+ * failures collapse into one cluster. An analyst that accounts for every
596
+ * cluster has, by construction, covered every distinct failure mode. */
597
+ interface ErrorCluster {
598
+ /** Normalized status_message — the cluster key. */
599
+ signature: string;
600
+ /** A verbatim, un-normalized exemplar message (for exact-string citation). */
601
+ status_message_sample: string;
602
+ /** The span name that most often carries this signature, if any. */
603
+ span_name: string | null;
604
+ /** The tool that most often carries this signature, if any. */
605
+ tool_name: string | null;
606
+ trace_count: number;
607
+ span_count: number;
608
+ /** trace_count / total error traces in the matched set (0..1). */
609
+ prevalence: number;
610
+ /** Real trace ids carrying this signature (capped), passable to view/search. */
611
+ exemplar_trace_ids: string[];
612
+ /** Real span ids carrying this signature (capped). */
613
+ exemplar_span_ids: string[];
614
+ }
615
+ interface DatasetOverview {
616
+ total_traces: number;
617
+ raw_jsonl_bytes: number;
618
+ services: string[];
619
+ agents: string[];
620
+ models: string[];
621
+ tool_names: string[];
622
+ /** Up to 20 real trace ids the agent may pass to view/search tools. */
623
+ sample_trace_ids: string[];
624
+ errors: {
625
+ trace_count: number;
626
+ span_count: number;
627
+ };
628
+ /** The COMPLETE deterministic error-signature population, sorted by
629
+ * trace_count desc. This is the failure-coverage checklist: an analysis is
630
+ * complete only when every cluster here is accounted for. Empty when the
631
+ * matched set has no error spans. */
632
+ error_clusters: ErrorCluster[];
633
+ time_range: {
634
+ earliest: string;
635
+ latest: string;
636
+ } | null;
637
+ }
638
+ interface QueryTracesPage {
639
+ traces: TraceAnalystTraceSummary[];
640
+ total: number;
641
+ has_more: boolean;
642
+ }
643
+ /** Full-trace view. When the response would exceed the per-call byte
644
+ * budget, `oversized` is populated INSTEAD of `spans` so the agent
645
+ * knows to switch to `searchTrace` / `viewSpans`. */
646
+ interface ViewTraceResult {
647
+ trace_id: string;
648
+ spans?: TraceAnalystSpan[];
649
+ oversized?: ViewTraceOversized;
650
+ }
651
+ interface ViewTraceOversized {
652
+ span_count: number;
653
+ /** Names with their counts, sorted desc. Capped at 20 entries. */
654
+ top_span_names: Array<[string, number]>;
655
+ /** Largest single span body (bytes after attribute-cap projection). */
656
+ span_response_bytes_max: number;
657
+ error_span_count: number;
658
+ }
659
+ interface ViewSpansResult {
660
+ trace_id: string;
661
+ spans: TraceAnalystSpan[];
662
+ /** Number of requested span ids that were not found in the trace. */
663
+ missing_span_ids: string[];
664
+ /** Number of attribute fields truncated to fit the per-attribute cap. */
665
+ truncated_attribute_count: number;
666
+ }
667
+ interface SpanMatchRecord {
668
+ trace_id: string;
669
+ span_id: string;
670
+ span_name: string;
671
+ span_kind: TraceAnalystSpanKind;
672
+ /** JSON pointer-style path to the matched value, e.g.
673
+ * `attributes."llm.input_messages"[2].content`. */
674
+ attribute_path: string;
675
+ matched_text: string;
676
+ context_before: string;
677
+ context_after: string;
678
+ match_offset: number;
679
+ }
680
+ interface SearchTraceResult {
681
+ trace_id: string;
682
+ hits: SpanMatchRecord[];
683
+ total_matches: number;
684
+ has_more: boolean;
685
+ }
686
+ interface SearchSpanResult {
687
+ trace_id: string;
688
+ span_id: string;
689
+ hits: SpanMatchRecord[];
690
+ total_matches: number;
691
+ has_more: boolean;
692
+ }
693
+
694
+ /**
695
+ * `TraceAnalysisStore` — read-side interface the trace-analyst calls
696
+ * through. Six operations, all bounded:
697
+ *
698
+ * - `getOverview(filters?)` — dataset rollup + sample trace ids.
699
+ * - `queryTraces(filters?, limit, offset)` — paginated summaries.
700
+ * - `countTraces(filters?)` — cheap count without materialisation.
701
+ * - `viewTrace(trace_id, perAttrCap)` — full span list, oversized → summary.
702
+ * - `viewSpans(trace_id, span_ids, perAttrCap)` — surgical span fetch.
703
+ * - `searchTrace(trace_id, regex, max_matches)` — bounded regex hits.
704
+ * - `searchSpan(trace_id, span_id, regex, max_matches)` — single-span search.
705
+ *
706
+ * Multiple implementations ship in the core (`OtlpFileTraceStore`).
707
+ * Downstream callers can supply their own — e.g. a DuckDB-backed
708
+ * adapter or an in-memory adapter for tests — by implementing this
709
+ * interface.
710
+ *
711
+ * Filters compose with AND semantics. Empty/undefined fields impose
712
+ * no constraint. `regex_pattern` is the only opt-in raw-bytes scan —
713
+ * implementations may skip it via `count`/`overview` when not set.
714
+ */
715
+
716
+ interface TraceAnalysisStore {
717
+ getOverview(filters?: TraceAnalystFilters): Promise<DatasetOverview>;
718
+ queryTraces(opts: {
719
+ filters?: TraceAnalystFilters;
720
+ limit: number;
721
+ offset?: number;
722
+ }): Promise<QueryTracesPage>;
723
+ countTraces(filters?: TraceAnalystFilters): Promise<number>;
724
+ viewTrace(opts: {
725
+ trace_id: string;
726
+ /** Override per-attribute byte cap. Defaults to discovery budget. */
727
+ per_attribute_byte_cap?: number;
728
+ }): Promise<ViewTraceResult>;
729
+ viewSpans(opts: {
730
+ trace_id: string;
731
+ span_ids: readonly string[];
732
+ /** Override per-attribute byte cap. Defaults to surgical budget. */
733
+ per_attribute_byte_cap?: number;
734
+ }): Promise<ViewSpansResult>;
735
+ searchTrace(opts: {
736
+ trace_id: string;
737
+ regex_pattern: string;
738
+ /** Hard cap on matches returned. Default 50. */
739
+ max_matches?: number;
740
+ }): Promise<SearchTraceResult>;
741
+ searchSpan(opts: {
742
+ trace_id: string;
743
+ span_id: string;
744
+ regex_pattern: string;
745
+ max_matches?: number;
746
+ }): Promise<SearchSpanResult>;
747
+ }
748
+
749
+ type CostChannel = 'agent' | 'judge' | 'verifier' | 'analyst' | 'driver' | (string & {});
750
+ interface CostUsage {
751
+ inputTokens: number;
752
+ outputTokens: number;
753
+ cachedTokens?: number;
754
+ }
755
+ interface CostCallBase {
756
+ callId: string;
757
+ channel: CostChannel;
758
+ phase: string;
759
+ actor: string;
760
+ model: string;
761
+ maximumCostUsd?: number;
762
+ tags?: Record<string, string>;
763
+ timestamp: number;
764
+ }
765
+ interface CostReceipt extends CostCallBase, CostUsage {
766
+ status: 'settled';
767
+ costUsd: number;
768
+ costUnknown: boolean;
769
+ usageUnknown?: boolean;
770
+ pricing?: {
771
+ inputUsdPerThousand: number;
772
+ outputUsdPerThousand: number;
773
+ };
774
+ actualCostUsd?: number;
775
+ error?: string;
776
+ }
777
+ interface CostReceiptInput extends CostUsage {
778
+ model: string;
779
+ actualCostUsd?: number;
780
+ costUnknown?: boolean;
781
+ usageUnknown?: boolean;
782
+ }
783
+ type MaximumCharge = {
784
+ externallyEnforcedMaximumUsd: number;
785
+ } | ({
786
+ model: string;
787
+ } & CostUsage);
788
+ interface RunPaidCallInput<T> {
789
+ callId?: string;
790
+ channel: CostChannel;
791
+ phase: string;
792
+ actor: string;
793
+ /** Used before a provider receipt exists and on failures without one. */
794
+ model?: string;
795
+ tags?: Record<string, string>;
796
+ signal?: AbortSignal;
797
+ /** Provider-enforced dollar maximum, or maximum priced token usage. Required when capped. */
798
+ maximumCharge?: MaximumCharge;
799
+ /** `callId` can be forwarded as the provider's idempotency key. */
800
+ execute(signal: AbortSignal, callId: string): Promise<T>;
801
+ receipt(value: T): CostReceiptInput;
802
+ receiptFromError?(error: Error): CostReceiptInput | undefined;
803
+ }
804
+ type PaidCallResult<T> = {
805
+ succeeded: true;
806
+ callId: string;
807
+ value: T;
808
+ receipt: CostReceipt;
809
+ } | {
810
+ succeeded: false;
811
+ callId?: string;
812
+ error: Error;
813
+ receipt?: CostReceipt;
814
+ };
815
+ interface ChannelRollup {
816
+ channel: CostChannel;
817
+ calls: number;
818
+ inputTokens: number;
819
+ outputTokens: number;
820
+ cachedTokens: number;
821
+ costUsd: number;
822
+ unpricedCalls: number;
823
+ unknownUsageCalls: number;
824
+ }
825
+ interface CostLedgerSummary {
826
+ totalCalls: number;
827
+ pendingCalls: number;
828
+ unresolvedCalls: number;
829
+ reservedCostUsd: number;
830
+ inputTokens: number;
831
+ outputTokens: number;
832
+ cachedTokens: number;
833
+ totalCostUsd: number;
834
+ byChannel: ChannelRollup[];
835
+ unpricedModels: string[];
836
+ fullyPriced: boolean;
837
+ usageComplete: boolean;
838
+ accountingComplete: boolean;
839
+ incompleteReasons: string[];
840
+ }
841
+ interface CostLedgerFilter {
842
+ channel?: CostChannel;
843
+ phase?: string;
844
+ tags?: Record<string, string>;
845
+ }
846
+ /** Append-only storage. `append` must atomically reject stale revisions. */
847
+ interface CostLedgerPersistence {
848
+ read(): {
849
+ revision: string;
850
+ events: string;
851
+ };
852
+ append(expectedRevision: string, event: string): string | undefined;
853
+ }
854
+ interface CostLedgerOptions {
855
+ costCeilingUsd?: number;
856
+ persistence?: CostLedgerPersistence;
857
+ /** Import already-settled receipts without admitting new paid work. */
858
+ receipts?: readonly CostReceipt[];
859
+ }
860
+ /** Run-wide paid-call admission, durable call state, receipts, and summaries. */
861
+ declare class CostLedger {
862
+ private readonly records;
863
+ private readonly activeCallIds;
864
+ private readonly lateCallIds;
865
+ private completedTasks;
866
+ private revision;
867
+ private costLimitPersisted;
868
+ readonly costCeilingUsd?: number;
869
+ private readonly persistence?;
870
+ constructor(input?: number | CostLedgerOptions);
871
+ runPaidCall<T>(input: RunPaidCallInput<T>): Promise<PaidCallResult<T>>;
872
+ /** Settle a call left pending by a crashed process after reconciling with the provider. */
873
+ reconcile(callId: string, observed: CostReceiptInput, options?: {
874
+ error?: string;
875
+ }): CostReceipt;
876
+ list(filter?: CostLedgerFilter): CostReceipt[];
877
+ summary(filter?: CostLedgerFilter): CostLedgerSummary;
878
+ markCompleted(count?: number): void;
879
+ costPerCompletedTask(): number | null;
880
+ private execute;
881
+ private captureLateOutcome;
882
+ private commitOutcome;
883
+ private captureFailure;
884
+ private commitReceipt;
885
+ private resolveMaximum;
886
+ private hasIncompleteSettledCall;
887
+ private appendRecord;
888
+ private ensureCostLimitPersisted;
889
+ private appendEvent;
890
+ }
891
+
892
+ /**
893
+ * RawProviderSink — first-class persistence for the actual HTTP-level
894
+ * request/response bodies of every LLM provider call.
895
+ *
896
+ * Why this is a separate sink from the structured `LlmSpan`:
897
+ *
898
+ * - `LlmSpan` records the *intent* — model name, messages, output text,
899
+ * usage. It's what dashboards read; it's NOT enough for forensics.
900
+ * - When a downstream consumer reports "the verifier used the wrong route"
901
+ * or "tokens look right but reasoning was missing," the only way to
902
+ * answer is the raw HTTP body. Span fields can lie (a proxy can echo
903
+ * a different `model` value than what actually answered); the raw
904
+ * response is ground truth.
905
+ *
906
+ * Default behaviour: opt-in. Pass `rawSink` to `LlmClientOptions` (or the
907
+ * matrix runner / BuilderSession sets it up automatically) and every
908
+ * request, response, and error is recorded — including retries, with the
909
+ * attempt index attached so a flaky call's full event chain is recoverable.
910
+ *
911
+ * Redaction is enforced at sink time. The default redactor strips
912
+ * `Authorization`, `X-Api-Key`, `X-Auth-Token`, `Cookie` headers and any
913
+ * payload field whose key matches `apiKey | api_key | bearer | password |
914
+ * secret | token` (case-insensitive). Override via the sink constructor or
915
+ * the per-call `redactor`. The `redactedFields` array on the persisted
916
+ * event lets a reviewer see what was stripped without exposing the values.
917
+ */
918
+ type RawProviderDirection = 'request' | 'response' | 'error';
919
+ interface RawProviderEvent {
920
+ /** Stable id. Generated by the sink if omitted. */
921
+ eventId: string;
922
+ /** Trace context populated by `LlmClient` when the call is wrapped in a span. */
923
+ runId?: string;
924
+ spanId?: string;
925
+ /**
926
+ * Logical provider name. Free-form so callers can use whatever id matches
927
+ * their topology (`'openai'`, `'anthropic'`, `'tangle-router'`, …). When
928
+ * omitted, derived from `baseUrl` in `LlmClientOptions`.
929
+ */
930
+ provider: string;
931
+ model: string;
932
+ /** Endpoint path, e.g. `'/v1/chat/completions'`. */
933
+ endpoint: string;
934
+ /** Base URL used for the call (already-normalised — no trailing slash). */
935
+ baseUrl: string;
936
+ /** 0-indexed retry attempt. The first attempt is 0; a retried call gets 1, 2, … */
937
+ attemptIndex: number;
938
+ direction: RawProviderDirection;
939
+ /** Unix ms. */
940
+ timestamp: number;
941
+ /** Wall-clock duration of the call leg. Set on `response` and `error` events; null on `request`. */
942
+ durationMs?: number;
943
+ statusCode?: number;
944
+ requestHeaders?: Record<string, string>;
945
+ requestBody?: unknown;
946
+ responseHeaders?: Record<string, string>;
947
+ responseBody?: unknown;
948
+ /** Set on `direction: 'error'` events. */
949
+ errorMessage?: string;
950
+ /** Field paths the redactor stripped from this event ('header:Authorization', 'body.apiKey', …). */
951
+ redactedFields: string[];
952
+ }
953
+ interface RawProviderSinkFilter {
954
+ runId?: string;
955
+ spanId?: string;
956
+ direction?: RawProviderDirection;
957
+ attemptIndex?: number;
958
+ }
959
+ interface RawProviderSink {
960
+ record(event: RawProviderEvent): Promise<void>;
961
+ /** Optional listing — implementations that durably persist (file, db) should support this. */
962
+ list?(filter?: RawProviderSinkFilter): Promise<RawProviderEvent[]>;
963
+ /** Optional teardown for backed implementations. */
964
+ close?(): Promise<void>;
965
+ }
966
+ type ProviderRedactor = (event: RawProviderEvent) => RawProviderEvent;
967
+
968
+ /**
969
+ * LLM client with graceful degrade.
970
+ *
971
+ * OpenAI-compatible `/v1/chat/completions` client with:
972
+ * - Exponential-backoff retry on 429 + 5xx gateway errors (502/503/504).
973
+ * - Retry on transient network errors (fetch failed, AbortError, ECONNRESET).
974
+ * - Graceful json_schema → json_object degrade on 400 with schema-reject body.
975
+ * - Fenced-JSON stripping (```json ... ```) for models that wrap structured output.
976
+ * - Configurable base URL + api key / bearer, works with LiteLLM proxies, OpenAI
977
+ * directly, cli-bridge subscriptions, and any router that speaks the spec.
978
+ *
979
+ * Usage:
980
+ * const { value, result } = await callLlmJson<MyType>(
981
+ * { model: 'gpt-4o', messages: [...], jsonSchema: { name: 'x', schema: {...} } },
982
+ * { baseUrl: 'https://router.tangle.tools/v1', apiKey: process.env.KEY },
983
+ * )
984
+ *
985
+ * This is THE llm-calling seam for agent-eval primitives that need structured
986
+ * output (semantic concept judge, reviewer directives, critic scores). Primitives
987
+ * that need free-form text use `callLlm` and parse output themselves.
988
+ */
989
+
990
+ interface LlmMessage {
991
+ role: 'system' | 'user' | 'assistant';
992
+ /**
993
+ * Either a plain text content string OR a multimodal content array
994
+ * (text + image_url parts) for vision-capable models.
995
+ */
996
+ content: string | Array<{
997
+ type: 'text';
998
+ text: string;
999
+ } | {
1000
+ type: 'image_url';
1001
+ image_url: {
1002
+ url: string;
1003
+ detail?: 'auto' | 'low' | 'high';
1004
+ };
1005
+ }>;
1006
+ }
1007
+ interface LlmCallRequest {
1008
+ model: string;
1009
+ messages: LlmMessage[];
1010
+ /** Optional JSON-mode response format (response_format: json_object). */
1011
+ jsonMode?: boolean;
1012
+ /** Optional structured output via JSON Schema. Falls back to json_object on 400. */
1013
+ jsonSchema?: {
1014
+ name: string;
1015
+ schema: Record<string, unknown>;
1016
+ };
1017
+ temperature?: number;
1018
+ maxTokens?: number;
1019
+ /** Per-call timeout, default 300s. */
1020
+ timeoutMs?: number;
1021
+ }
1022
+ interface LlmUsage {
1023
+ promptTokens: number;
1024
+ completionTokens: number;
1025
+ totalTokens: number;
1026
+ /** False when the provider omitted or malformed prompt/completion usage. */
1027
+ captured?: boolean;
1028
+ /** Proxies populate this when prompt caching is on. */
1029
+ cachedPromptTokens?: number;
1030
+ }
1031
+ interface LlmCallResult {
1032
+ /** The text content of the first choice. Empty string if none. */
1033
+ content: string;
1034
+ usage: LlmUsage;
1035
+ /**
1036
+ * Cost in USD. Pulled from proxy's `_response_cost` field when present;
1037
+ * `null` when neither the proxy nor the caller can derive it.
1038
+ */
1039
+ costUsd: number | null;
1040
+ /** Model name actually used (echoed from response). */
1041
+ model: string;
1042
+ /** Wall-clock duration of the HTTP call (last attempt, if retried). */
1043
+ durationMs: number;
1044
+ /**
1045
+ * `finish_reason` echoed from the first choice (`stop`, `length`,
1046
+ * `content_filter`, `tool_calls`, ...). `null` when the provider omits it.
1047
+ * Exposed so a free-form `callLlm` caller CAN detect a truncated answer
1048
+ * (`length`) instead of treating a cut-off completion as complete. Note:
1049
+ * `callLlm` does not itself reject on it — acting on this signal is the
1050
+ * caller's responsibility (in-repo free-form drivers do not yet enforce it).
1051
+ */
1052
+ finishReason?: string | null;
1053
+ /**
1054
+ * True when `content.trim()` is empty. An empty completion is a silent zero
1055
+ * for free-form `callLlm` callers; this flag is the signal a caller can
1056
+ * inspect to fail loud rather than proceed on an empty string. `callLlm`
1057
+ * surfaces it but does not throw on it.
1058
+ */
1059
+ contentEmpty?: boolean;
1060
+ /** Raw response body. */
1061
+ raw: Record<string, unknown>;
1062
+ }
1063
+ type LlmCallMetadata = Pick<LlmCallResult, 'usage' | 'costUsd' | 'model' | 'durationMs'>;
1064
+ interface LlmClientOptions {
1065
+ /** Base URL (without trailing slash). Must end at the `/v1` prefix. */
1066
+ baseUrl?: string;
1067
+ /** Bearer token — either `apiKey` or `bearer` populates `Authorization: Bearer ...`. */
1068
+ apiKey?: string;
1069
+ bearer?: string;
1070
+ /** Override for the `Authorization` header (e.g. `X-Auth: ...`). Takes precedence over apiKey/bearer. */
1071
+ authHeader?: {
1072
+ name: string;
1073
+ value: string;
1074
+ };
1075
+ /** Stable provider idempotency key, reused across retries of this logical call. */
1076
+ idempotencyKey?: string;
1077
+ /** Default timeout in ms. Per-call can override. */
1078
+ defaultTimeoutMs?: number;
1079
+ /**
1080
+ * Caller-supplied abort signal — e.g. a campaign-wide cancel. Linked to
1081
+ * each attempt's per-attempt timeout controller, so aborting it cancels
1082
+ * the in-flight fetch. A caller abort is FATAL: it is not retried even
1083
+ * though an AbortError otherwise matches the transient patterns.
1084
+ */
1085
+ signal?: AbortSignal;
1086
+ /**
1087
+ * Cross-attempt wall-clock budget in ms, measured from the first attempt.
1088
+ * Before launching each attempt the loop checks the remaining budget and
1089
+ * stops retrying once it is exhausted, rather than waiting the full
1090
+ * per-attempt timeout on every retry. Bounds total time independent of
1091
+ * total attempts × `timeoutMs`.
1092
+ */
1093
+ deadlineMs?: number;
1094
+ /** Total provider attempts. Legacy option name; default 3 (1 initial + 2 retries). */
1095
+ maxRetries?: number;
1096
+ /** Fetch implementation — defaults to global `fetch`. Override for custom transport (e.g. tests). */
1097
+ fetch?: typeof fetch;
1098
+ /**
1099
+ * Optional raw HTTP capture sink. When provided, every request, response,
1100
+ * and error (across all retry attempts) is recorded to the sink, with auth
1101
+ * headers and credential-shaped body fields redacted by default. This is
1102
+ * the layer-1 forensics primitive: structured `LlmSpan`s record intent,
1103
+ * raw events record what actually crossed the wire.
1104
+ */
1105
+ rawSink?: RawProviderSink;
1106
+ /**
1107
+ * Logical provider id attached to raw events. When omitted, derived from
1108
+ * `baseUrl` via `providerFromBaseUrl`.
1109
+ */
1110
+ provider?: string;
1111
+ /** Trace context attached to raw events; populated by emitter-aware callers. */
1112
+ traceContext?: {
1113
+ runId?: string;
1114
+ spanId?: string;
1115
+ };
1116
+ /** Override the redaction strategy for this call. Defaults to `defaultProviderRedactor`. */
1117
+ redactor?: ProviderRedactor;
1118
+ }
1119
+
1120
+ /**
1121
+ * ChatClient — the single LLM abstraction analysts call.
1122
+ *
1123
+ * agent-eval already ships an `LlmClient` (OpenAI-compatible, retry,
1124
+ * graceful JSON-schema degrade) and judges that talk to `TCloud`. Two
1125
+ * mixed patterns force every analyst author to pick a transport, which
1126
+ * couples analyst code to runtime concerns (cli-bridge vs router vs
1127
+ * sandbox-sdk) it shouldn't know about.
1128
+ *
1129
+ * `ChatClient` is one interface every analyst takes via `AnalystContext.chat`.
1130
+ * The operator decides at the registry boundary which transport binds
1131
+ * to it. Analyst code stays transport-agnostic; swapping production
1132
+ * (sandbox-sdk) for local dev (cli-bridge) or tests (mock) is a one-
1133
+ * line factory call.
1134
+ *
1135
+ * Designed to coexist: existing `LlmClient` callers and existing
1136
+ * `TCloud`-based judges keep working untouched. New analyst code uses
1137
+ * `ChatClient`. When old call sites migrate, they pick up budgeting,
1138
+ * cancellation, and unified telemetry for free.
1139
+ */
1140
+
1141
+ /**
1142
+ * Unified chat interface. Mirrors LlmCallRequest/Result so the OpenAI-
1143
+ * compatible mental model stays. Two methods: a one-shot `chat()` and
1144
+ * an `streamChat()` for future agentic loops (not yet exposed).
1145
+ */
1146
+ interface ChatClient {
1147
+ /** Display name of the bound transport — included in telemetry. */
1148
+ readonly transport: ChatTransport;
1149
+ /** Default model when caller omits — operators bind this per environment. */
1150
+ readonly defaultModel?: string;
1151
+ /** Total provider attempts this transport can make for one chat call. */
1152
+ readonly maximumAttempts?: number;
1153
+ /** Implementations must enforce `req.maxTokens` when it is present. */
1154
+ chat(req: ChatRequest, opts?: ChatCallOpts): Promise<ChatResponse>;
1155
+ }
1156
+ type ChatTransport = 'router' | 'sandbox-sdk' | 'cli-bridge' | 'direct-provider' | 'mock';
1157
+ interface ChatRequest extends Omit<LlmCallRequest, 'model'> {
1158
+ /** Optional — falls back to ChatClient.defaultModel. */
1159
+ model?: string;
1160
+ }
1161
+ type ChatResponse = LlmCallResult;
1162
+ interface ChatCallOpts {
1163
+ /** Cancel the in-flight request. */
1164
+ signal?: AbortSignal;
1165
+ /** Hard USD ceiling for this single call (informational; the underlying transport may not enforce). */
1166
+ maxCostUsd?: number;
1167
+ /** Correlation tag carried into request headers when the transport allows. */
1168
+ correlationId?: string;
1169
+ /** Stable provider idempotency key for retries/redrives of one paid call. */
1170
+ idempotencyKey?: string;
1171
+ }
1172
+
1173
+ /**
1174
+ * Analyst contract — the missing orchestration layer over agent-eval's
1175
+ * existing analyzers (analyzeTraces, MultiLayerVerifier, RunCritic,
1176
+ * SemanticConceptJudge, JudgeFn, ...).
1177
+ *
1178
+ * Each existing primitive returns its own output shape. The Analyst
1179
+ * contract is the single envelope every primitive lifts into, so a
1180
+ * registry can run N analysts against a run and a single renderer can
1181
+ * compose findings without knowing which analyzer produced them.
1182
+ *
1183
+ * The contract is intentionally domain-agnostic: nothing here knows
1184
+ * about code, voice, RAG, or any particular agent stack. Analysts
1185
+ * declare what INPUT KIND they need (a trace store, an artifact dir,
1186
+ * a RunRecord, a JudgeInput, or `custom`), and the registry routes
1187
+ * the matching input from `AnalystRunInputs`.
1188
+ */
1189
+
1190
+ /**
1191
+ * Unified envelope every analyst emits. Schema-versioned so renderers
1192
+ * and time-series diffs survive future field additions.
1193
+ */
1194
+ interface AnalystFinding {
1195
+ schema_version: '1.0.0';
1196
+ /**
1197
+ * Stable hash over identity-defining fields (analyst_id + canonical
1198
+ * claim + area + optional subject). Two findings from two runs that
1199
+ * "are the same finding" share this id — that's what `diffFindings`
1200
+ * uses to compute appeared/disappeared sets across runs.
1201
+ */
1202
+ finding_id: string;
1203
+ analyst_id: string;
1204
+ produced_at: string;
1205
+ severity: AnalystSeverity;
1206
+ /**
1207
+ * Coarse classification. Renderers group by this. Free-form so
1208
+ * domain-specific analysts can introduce categories without a
1209
+ * schema change ('agent-reasoning', 'verification', 'cost',
1210
+ * 'tool-use', 'safety', 'latency', 'data-quality', ...).
1211
+ */
1212
+ area: string;
1213
+ claim: string;
1214
+ rationale?: string;
1215
+ evidence_refs: EvidenceRef[];
1216
+ recommended_action?: string;
1217
+ validation_plan?: string;
1218
+ /** 0..1 — the analyst's own confidence. Not calibrated across analysts. */
1219
+ confidence: number;
1220
+ /**
1221
+ * Optional subject the finding is about — leaf id, agent id, request
1222
+ * id. Included in finding_id when present so per-subject findings
1223
+ * diff cleanly across runs.
1224
+ */
1225
+ subject?: string;
1226
+ /** FIREWALL provenance (docs/learning-flywheel.md): true iff this finding was
1227
+ * lifted from a JUDGE verdict (an acceptance score), not OBSERVED from the
1228
+ * agent's behavior. A judge-derived finding must NEVER be admitted as a
1229
+ * steering input — that is the held-out judge leaking into the loop. Set at
1230
+ * the lift site (createJudgeAdapter); checked by `assertNoJudgeVerdict`.
1231
+ * Provenance, not evidence presence, is the correct discriminator: an
1232
+ * evidence-less trace-analyst observation legitimately steers, while a judge
1233
+ * verdict that happens to cite an artifact must not. */
1234
+ derived_from_judge?: boolean;
1235
+ /** Analyst-private extras; renderers ignore unless they know the analyst. */
1236
+ metadata?: Record<string, unknown>;
1237
+ }
1238
+ type AnalystSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';
1239
+ interface EvidenceRef {
1240
+ /**
1241
+ * Where the evidence lives. `span` and `event` refer to OTLP trace
1242
+ * elements; `artifact` to a file inside the run's artifact tree;
1243
+ * `finding` to another AnalystFinding (cross-analyst chaining);
1244
+ * `metric` to a named scalar reading the renderer knows how to read.
1245
+ */
1246
+ kind: 'span' | 'event' | 'artifact' | 'finding' | 'metric';
1247
+ uri: string;
1248
+ excerpt?: string;
1249
+ }
1250
+ interface AnalystCost {
1251
+ /** `deterministic` analysts MUST NOT call the LLM. */
1252
+ kind: 'deterministic' | 'llm';
1253
+ /** Optional declared upper bound; the registry can enforce a budget. */
1254
+ est_usd_per_run?: number;
1255
+ /** Models the analyst expects to use (informational). */
1256
+ models?: string[];
1257
+ }
1258
+ interface AnalystContext {
1259
+ runId: string;
1260
+ /** Stable correlation id so logs from a single registry.run() share a tag. */
1261
+ correlationId: string;
1262
+ /** Wall-clock deadline (epoch ms). Analysts SHOULD honor for graceful cancel. */
1263
+ deadlineMs?: number;
1264
+ /** Per-analyst USD budget. Analysts MAY check before issuing LLM calls. */
1265
+ budgetUsd?: number;
1266
+ /**
1267
+ * Shared chat client. Analysts that call an LLM go through this so
1268
+ * the operator picks transport (sandbox-sdk | router | cli-bridge |
1269
+ * direct-provider | mock) at the registry boundary without touching
1270
+ * analyst code.
1271
+ */
1272
+ chat?: ChatClient;
1273
+ /**
1274
+ * Findings from a prior run the operator wants the analyst to see as
1275
+ * retrieval context. Kinds that take advantage of cross-run memory
1276
+ * (failure-mode "I saw this cluster last run", knowledge-gap "the wiki
1277
+ * page I asked for is still missing") render these into the actor's
1278
+ * working set. Filtering is the operator's job: pass the slice that
1279
+ * matches the analyst's id, or pass everything and let the kind
1280
+ * filter. Empty / absent means no cross-run context.
1281
+ */
1282
+ priorFindings?: ReadonlyArray<AnalystFinding>;
1283
+ /** Free-form runtime tags (env, host, op). Findings can echo these into metadata. */
1284
+ tags?: Record<string, string>;
1285
+ /** Logger callback — analysts SHOULD prefer this over console.* for testability. */
1286
+ log?: (msg: string, fields?: Record<string, unknown>) => void;
1287
+ /** Optional abort signal. Analysts SHOULD pass it through to LLM calls. */
1288
+ signal?: AbortSignal;
1289
+ }
1290
+
1291
+ type PolicyEditSchemaVersion = 'policy-edit/v1';
1292
+ declare const POLICY_EDIT_AXES: readonly ["carrier", "representation", "budget", "sampling", "output_contract", "tool_contract", "routing", "memory", "agent_profile", "deployment_target"];
1293
+ type PolicyEditAxis = (typeof POLICY_EDIT_AXES)[number];
1294
+ declare const POLICY_EDIT_TARGET_SURFACES: readonly ["prompt", "tool-contract", "runtime-config", "memory", "agent-profile", "code", "deployment"];
1295
+ type PolicyEditTargetSurface = (typeof POLICY_EDIT_TARGET_SURFACES)[number];
1296
+ type PolicyEditRisk = 'low' | 'medium' | 'high' | 'unknown';
1297
+ type PolicyEditGainDirection = 'increase' | 'decrease';
1298
+ type PolicyEditGainUnit = 'absolute' | 'relative' | 'percent' | 'score';
1299
+ interface PolicyEditTarget {
1300
+ surface: PolicyEditTargetSurface;
1301
+ /** Stable path inside the target surface, for example `system-prompt:tools`
1302
+ * or `budget.maxTurns`. */
1303
+ path?: string;
1304
+ /** Optional canonical deployment identity. Store the existing cell, not a
1305
+ * local profile shape. */
1306
+ agentProfileCell?: AgentProfileCell;
1307
+ /** Human label when the path is not enough for a readable audit trail. */
1308
+ label?: string;
1309
+ }
1310
+ type PolicyEditChange = {
1311
+ kind: 'text';
1312
+ mode: 'append' | 'prepend' | 'replace';
1313
+ value: string;
1314
+ /** Required when `mode === 'replace'`; exact match only. */
1315
+ find?: string;
1316
+ } | {
1317
+ kind: 'json';
1318
+ mode: 'set' | 'merge' | 'remove';
1319
+ path: string;
1320
+ value?: AgentProfileJson;
1321
+ };
1322
+ interface PolicyEditExpectedGain {
1323
+ /** Metric this edit is expected to move, e.g. `holdout.composite`. */
1324
+ metric: string;
1325
+ direction: PolicyEditGainDirection;
1326
+ /** Positive magnitude in the metric's native units. */
1327
+ amount: number;
1328
+ unit?: PolicyEditGainUnit;
1329
+ rationale?: string;
1330
+ }
1331
+ interface PolicyEditSource {
1332
+ findingIds: string[];
1333
+ analystIds: string[];
1334
+ evidenceRefs: EvidenceRef[];
1335
+ /** Mirrors `AnalystFinding.derived_from_judge`; admission rejects it. */
1336
+ derivedFromJudge?: boolean;
1337
+ }
1338
+ interface PolicyEdit {
1339
+ schemaVersion: PolicyEditSchemaVersion;
1340
+ editId: string;
1341
+ axis: PolicyEditAxis;
1342
+ target: PolicyEditTarget;
1343
+ change: PolicyEditChange;
1344
+ claim: string;
1345
+ expectedGain: PolicyEditExpectedGain;
1346
+ confidence: number;
1347
+ risk: PolicyEditRisk;
1348
+ source: PolicyEditSource;
1349
+ rationale?: string;
1350
+ validationPlan?: string;
1351
+ metadata?: Record<string, unknown>;
1352
+ }
1353
+ declare const POLICY_EDIT_CANDIDATE_RECORD_SCHEMA: "tangle.policy-edit-candidate.v1";
1354
+ /** JSON-safe attribution carried with a measured candidate and its scores. */
1355
+ interface PolicyEditCandidateRecord {
1356
+ schema: typeof POLICY_EDIT_CANDIDATE_RECORD_SCHEMA;
1357
+ policyEdit: PolicyEdit;
1358
+ }
1359
+ interface FindingToPolicyEditOptions {
1360
+ expectedGain?: PolicyEditExpectedGain | ((finding: AnalystFinding) => PolicyEditExpectedGain | null | undefined);
1361
+ risk?: PolicyEditRisk | ((finding: AnalystFinding) => PolicyEditRisk);
1362
+ defaultAxis?: PolicyEditAxis;
1363
+ defaultTargetSurface?: PolicyEditTargetSurface;
1364
+ }
1365
+ interface PolicyEditAdmissionOptions {
1366
+ minScore?: number;
1367
+ minExpectedGain?: number;
1368
+ allowHighRisk?: boolean;
1369
+ requireEvidence?: boolean;
1370
+ }
1371
+ interface PolicyEditAdmission {
1372
+ edit: PolicyEdit;
1373
+ decision: 'admit' | 'reject';
1374
+ score: number;
1375
+ reasons: string[];
1376
+ }
1377
+ declare function validatePolicyEditCandidateRecord(input: unknown): PolicyEditCandidateRecord;
1378
+
1379
+ /**
1380
+ * Pass A substrate types — `runCampaign` is the one primitive every
1381
+ * eval flow composes from. Three contracts in this file:
1382
+ *
1383
+ * - `Scenario` input set
1384
+ * - `DispatchFn` how to run one scenario → artifact
1385
+ * - `CampaignResult` defined output schema (the contract downstream tools depend on)
1386
+ *
1387
+ * Three more lifted from earlier substrate work (re-exported):
1388
+ *
1389
+ * - `JudgeConfig` pluggable dimensional scorer (0.38)
1390
+ * - `Mutator` optimization-loop surface mutator
1391
+ * - `Gate` promotion gate (`HeldOutGate` and friends adapt to this)
1392
+ *
1393
+ * No new architecture vs 0.38 — Pass A formalizes the shapes so consumers
1394
+ * can build dashboards / CI gates / regression diffs against a stable schema.
1395
+ */
1396
+
1397
+ /** Stable identifier + kind tag for any scenario. Consumers
1398
+ * extend with their per-domain payload (persona, task, requirement, ...). */
1399
+ interface Scenario {
1400
+ id: string;
1401
+ kind: string;
1402
+ tags?: string[];
1403
+ }
1404
+ /** Context handed to every dispatch invocation. Scoped — every
1405
+ * trace/span carries the cellId, every artifact write lands under the cell's
1406
+ * artifact root, the cost meter accumulates per cell. */
1407
+ interface DispatchContext {
1408
+ cellId: string;
1409
+ rep: number;
1410
+ generation?: number;
1411
+ seed: number;
1412
+ signal: AbortSignal;
1413
+ trace: CampaignTraceWriter;
1414
+ artifacts: CampaignArtifactWriter;
1415
+ cost: CampaignCostMeter;
1416
+ /** Populated when this run is part of a multi-cycle improvement loop. */
1417
+ cycleId?: string;
1418
+ /** Populated when the substrate resumed from a prior cache hit. */
1419
+ resumedFrom?: string;
1420
+ /**
1421
+ * Opaque placement key supplied by `RunCampaignOptions.cellPlacement`.
1422
+ * The substrate forwards it through unchanged; placement-aware Dispatch
1423
+ * implementations (e.g. `httpDispatch` from `/adapters/http`) read it to
1424
+ * route the cell to the right worker / region / sandbox. `undefined`
1425
+ * when no placement strategy is configured.
1426
+ */
1427
+ placement?: string;
1428
+ }
1429
+ /** One function: scenario + ctx → artifact. Dispatcher chooses
1430
+ * whether to call `runMultishot`, `runLoop`, raw `streamPrompt`, anything. */
1431
+ type DispatchFn<TScenario extends Scenario, TArtifact> = (scenario: TScenario, ctx: DispatchContext) => Promise<TArtifact>;
1432
+ /** One session within a multi-session journey. Dispatch is
1433
+ * invoked once per session in order; state from prior session's artifact
1434
+ * is exposed via `ctx.priorSessionArtifact`. */
1435
+ interface SessionScript<TScenario, TArtifact> {
1436
+ id: string;
1437
+ intent: string;
1438
+ maxTurns?: number;
1439
+ /** When true, knowledge accumulated this session persists to next. */
1440
+ affectsKnowledge?: boolean;
1441
+ /** Optional per-session persona evolution — called after the session
1442
+ * resolves. Returns the persona shape used by the NEXT session. */
1443
+ evolveAfterSession?: (artifact: TArtifact, sessionIndex: number, scenario: TScenario) => TScenario;
1444
+ }
1445
+ interface JudgeDimension {
1446
+ /** JSON field name + score key. */
1447
+ key: string;
1448
+ /** Description shown in the judge's user prompt. */
1449
+ description: string;
1450
+ }
1451
+ /** Pluggable dimensional scorer. `score` is the contract:
1452
+ * given an artifact + scenario, return a `JudgeScore`. This is deliberately a
1453
+ * function, not a fixed LLM-prompt shape — real consumers judge with
1454
+ * ensembles, deterministic checks, or a single LLM call, and the substrate
1455
+ * must not constrain that. The `llmJudge()` helper builds a `score` that does
1456
+ * one LLM call for the common case. `appliesTo` lets a judge run only on
1457
+ * scenarios that match (e.g. a legal-citation judge only on legal scenarios). */
1458
+ interface JudgeConfig<TArtifact, TScenario extends Scenario = Scenario> {
1459
+ name: string;
1460
+ dimensions: JudgeDimension[];
1461
+ /** Stable scoring revision used by campaign resume and verdict caches.
1462
+ * Built-in judges derive this from their prompt, model, and rubric. Custom
1463
+ * judges should set it when closure state can change without changing code. */
1464
+ judgeVersion?: string;
1465
+ /** Score one artifact. Throw on failure — a thrown judge is recorded as a
1466
+ * failed cell, never silently folded into a zero. */
1467
+ score(input: {
1468
+ artifact: TArtifact;
1469
+ scenario: TScenario;
1470
+ signal: AbortSignal;
1471
+ /** Shared run spend account and receipt attribution phase. */
1472
+ costLedger?: CostLedger;
1473
+ costPhase?: string;
1474
+ costTags?: Record<string, string>;
1475
+ }): JudgeScore | Promise<JudgeScore>;
1476
+ appliesTo?: (scenario: TScenario) => boolean;
1477
+ }
1478
+ /** The canonical judge verdict shape — one declaration, shared by campaign
1479
+ * judges and the multishot judge runner (which re-exports this type).
1480
+ *
1481
+ * Scale is PRODUCER-DEFINED: campaign convention is [0,1]; the legacy
1482
+ * multishot runner emits 0-10. Cross-scale comparison must go through
1483
+ * `detectScale` (src/campaign/gates/statistical-heldout.ts, used by
1484
+ * promotion-policy) — never renormalize a producer's values in place, as
1485
+ * downstream thresholds (`composite >= 5` in multishot/matrix.ts, live-soak
1486
+ * `>= 7` gates) key on the producer's native scale. */
1487
+ interface JudgeScore {
1488
+ dimensions: Record<string, number>;
1489
+ composite: number;
1490
+ notes: string;
1491
+ /** Provider metadata for display and diagnostics; accounting uses CostLedger receipts. */
1492
+ llmCall?: LlmCallMetadata;
1493
+ /** Set when the judge itself failed (call error, unparseable output).
1494
+ * `composite`/`dimensions` carry no signal — aggregators MUST exclude
1495
+ * failed scores from means instead of folding them into zeros. */
1496
+ failed?: true;
1497
+ /** Ensemble extras (populated by `ensembleJudge`): max per-dimension
1498
+ * spread across surviving judges — the inter-rater signal. */
1499
+ maxDisagreement?: number;
1500
+ /** Ensemble extras: judge identities whose verdict failed. */
1501
+ failedJudges?: string[];
1502
+ /** Ensemble extras: each surviving judge's per-dimension scores. */
1503
+ perJudge?: Record<string, Record<string, number>>;
1504
+ }
1505
+ /** A tier-4 code surface — a finalized candidate change to the agent's
1506
+ * IMPLEMENTATION, not its prompt. Produced by autoresearch (reads codebase +
1507
+ * trace findings → opens a worktree). `worktreeRef` locates the candidate;
1508
+ * the exact commits, tree, and binary-patch digest identify it. See the
1509
+ * improvement-tier table in `docs/design/loop-taxonomy.md`. */
1510
+ interface CodeSurface {
1511
+ readonly kind: 'code';
1512
+ /** Worktree path or git ref holding the candidate code change. This is a
1513
+ * mutable locator and is deliberately excluded from content hashes. */
1514
+ readonly worktreeRef: string;
1515
+ /** Human-readable ref the worktree was forked from. Not identity-bearing. */
1516
+ readonly baseRef: string;
1517
+ /** Exact commit the candidate was forked from. */
1518
+ readonly baseCommit: string;
1519
+ /** Exact tree object for `baseCommit`. */
1520
+ readonly baseTree: string;
1521
+ /** Exact finalized candidate commit. */
1522
+ readonly candidateCommit: string;
1523
+ /** Exact tree object for `candidateCommit`. */
1524
+ readonly candidateTree: string;
1525
+ /** Identity of the exact patch artifact. The deployable candidate bundle
1526
+ * carries the same descriptor plus its base64-encoded content. */
1527
+ readonly patch: {
1528
+ readonly format: 'git-diff-binary';
1529
+ readonly sha256: `sha256:${string}`;
1530
+ readonly byteLength: number;
1531
+ };
1532
+ /** Human summary of what changed — rendered into the auto-PR body. */
1533
+ readonly summary?: string;
1534
+ }
1535
+ /** The mutable surface a proposer changes. Tiers (see
1536
+ * `docs/design/loop-taxonomy.md`):
1537
+ * - `string` — tiers 1-2: system-prompt addendum / serialized tool
1538
+ * config. Cheap, reversible, text-diffable.
1539
+ * - `CodeSurface` — tier 4: an implementation change behind a worktree ref.
1540
+ * Tier 3 (knowledge) is owned by agent-knowledge and rides its own adapter,
1541
+ * not this type. */
1542
+ type MutableSurface = string | CodeSurface;
1543
+ /** A proposer output carrying the surface AND the WHY behind
1544
+ * it. Reflective proposers (`gepaProposer`) parse a `{label, rationale, payload}`
1545
+ * from the model; without this wrapper the loop keeps only `payload` and the
1546
+ * rationale that motivated the change is lost — the candidate becomes
1547
+ * unattributable. `propose()` may return either bare `MutableSurface`s (cheap
1548
+ * blind mutators) or these (reflective proposers); the loop normalizes both. */
1549
+ interface ProposedCandidate {
1550
+ surface: MutableSurface;
1551
+ /** Short human label for the change (≤ 40 chars typical). */
1552
+ label: string;
1553
+ /** Why this change was proposed — which failure it targets, which
1554
+ * primitive it used. Survives to `GenerationCandidate.rationale` and the
1555
+ * emitted provenance record. */
1556
+ rationale: string;
1557
+ /** Structured, JSON-safe cause for this exact candidate when the proposer
1558
+ * can provide one. Policy edits retain the full validated edit here. */
1559
+ candidateRecord?: PolicyEditCandidateRecord;
1560
+ }
1561
+ /** Type guard: a proposal carrying its rationale vs a bare
1562
+ * surface. The loop branches on this to populate `GenerationCandidate`. */
1563
+ declare function isProposedCandidate(value: MutableSurface | ProposedCandidate): value is ProposedCandidate;
1564
+ /** A non-dominated parent on the GEPA Pareto frontier — a
1565
+ * surface that, across the per-scenario objective vectors, no other tried
1566
+ * surface beats on every scenario. A candidate worse on the mean composite
1567
+ * but uniquely best on one hard scenario is non-dominated and survives here;
1568
+ * the composite-best ranking would discard the lesson it carries. The loop
1569
+ * computes the frontier across ALL generations and hands it to the proposer so
1570
+ * a reflective proposer can combine complementary lessons (GEPA, Agrawal et
1571
+ * al., arXiv:2507.19457). See `pareto.ts` (`paretoFrontier`). */
1572
+ interface ParetoParent {
1573
+ surface: MutableSurface;
1574
+ surfaceHash: string;
1575
+ /** The objective vector: per-scenario composite (higher is better). The
1576
+ * axes the frontier is computed over. */
1577
+ objectives: Record<string, number>;
1578
+ /** Mean composite across the objective scenarios — the scalar summary used
1579
+ * for ordering + display, NOT for dominance. */
1580
+ composite: number;
1581
+ /** Generation that produced this surface (`-1` for the baseline). */
1582
+ generation: number;
1583
+ label?: string;
1584
+ rationale?: string;
1585
+ }
1586
+ /** Exact measured state for the surface an optimizer is learning from.
1587
+ * Unlike a model-authored expected gain, every value here comes from a
1588
+ * completed campaign over the designed denominator. */
1589
+ interface ScoredSurfaceOutcome {
1590
+ /** Optimization/search evidence only. Held-out results must never flow back
1591
+ * into a proposer through this type. */
1592
+ split: 'search';
1593
+ /** Generation that actually measured this surface (`-1` for the baseline). */
1594
+ generation: number;
1595
+ surfaceHash: string;
1596
+ composite: number;
1597
+ dimensions: Record<string, number>;
1598
+ scenarios: Array<{
1599
+ scenarioId: string;
1600
+ composite: number;
1601
+ notes?: string;
1602
+ }>;
1603
+ coverage: {
1604
+ expectedCells: number;
1605
+ scorableCells: number;
1606
+ };
1607
+ }
1608
+ /** Stateless surface mutation — given findings + current
1609
+ * surface, return N candidate surfaces. Pure transform, no generation
1610
+ * awareness. Reflective-mutation and `AxGEPA` mutators conform. Wrapped by
1611
+ * `evolutionaryProposer` to become a `SurfaceProposer`. */
1612
+ interface Mutator<TFindings = unknown> {
1613
+ kind: string;
1614
+ mutate(args: {
1615
+ findings: TFindings[];
1616
+ currentSurface: MutableSurface;
1617
+ populationSize: number;
1618
+ signal: AbortSignal;
1619
+ }): Promise<Array<MutableSurface | ProposedCandidate>>;
1620
+ }
1621
+ /** Everything a proposer may read to plan the next
1622
+ * batch of candidates. The first six fields are always present; the rest are
1623
+ * optional context the loop supplies when available, so cheap proposers
1624
+ * (`evolutionaryProposer`) can ignore them while a code-tier agentic generator
1625
+ * consumes the report + dataset to drive a coding harness.
1626
+ * See `docs/campaign-proposers.md`. */
1627
+ interface ProposeContext<TFindings = unknown> {
1628
+ currentSurface: MutableSurface;
1629
+ history: GenerationRecord[];
1630
+ findings: TFindings[];
1631
+ /** BREADTH: how many candidate surfaces to return this generation. */
1632
+ populationSize: number;
1633
+ generation: number;
1634
+ signal: AbortSignal;
1635
+ /** Measured baseline for this optimization run. `runOptimization` always
1636
+ * supplies it; optional for standalone proposer callers. */
1637
+ baselineOutcome?: ScoredSurfaceOutcome;
1638
+ /** Measured result for `currentSurface`, the complete global incumbent every
1639
+ * new candidate mutates. `runOptimization` always supplies it. */
1640
+ incumbentOutcome?: ScoredSurfaceOutcome;
1641
+ /** Optional analysis report produced before proposal. Opaque to the substrate:
1642
+ * the proposer that consumes it owns the shape. */
1643
+ report?: unknown;
1644
+ /** Handle to all captured data — the proposer samples traces / artifacts /
1645
+ * rewards here to ground its proposals. */
1646
+ dataset?: LabeledScenarioStore;
1647
+ /** DEPTH: max iterations the agentic generator may take per candidate.
1648
+ * 1 = single-shot; >1 = it may iterate on its own change before handing it
1649
+ * back to be measured. */
1650
+ maxImprovementShots?: number;
1651
+ /** GEPA Pareto frontier across ALL generations so far — the non-dominated
1652
+ * surfaces by per-scenario objective vector. Empty/absent on generation 0
1653
+ * (only the baseline is scored). A reflective proposer combines the
1654
+ * complementary lessons of these parents (each excels on different
1655
+ * scenarios) into a merged candidate. Proposers doing pure single-parent
1656
+ * reflection may ignore it. See {@link ParetoParent}. */
1657
+ paretoParents?: ParetoParent[];
1658
+ /** Shared run spend account and receipt attribution phase. */
1659
+ costLedger?: CostLedger;
1660
+ costPhase?: string;
1661
+ /** FIREWALL (non-negotiable): the held-out judge is write-only — its verdicts
1662
+ * score the chosen output and gate promotion, and are NEVER an input to
1663
+ * proposal/steering (else the optimizer games the acceptance axis = an
1664
+ * oracle). This `never`-typed field makes that a compile-time tripwire: a
1665
+ * proposer that tries to thread judge verdicts into the proposal will not type.
1666
+ * Steering may consume TRACE-OBSERVABLE signals (what the agent did) via
1667
+ * `findings`/`report`; it may NOT consume the judge's held-out verdict. */
1668
+ judgeScores?: never;
1669
+ }
1670
+ /** A surface-improvement strategy. Given the current best
1671
+ * surface, the history of what's been tried + scored, and any external
1672
+ * findings, propose the next batch of candidate surfaces to measure.
1673
+ * Optionally decide to stop early.
1674
+ *
1675
+ * The evolutionary mutator (`evolutionaryProposer`, here) and agent-runtime's
1676
+ * reflective / agentic generators both conform. They are proposers for the
1677
+ * SAME loop, not separate loops. The loop body (`runOptimization`) and the
1678
+ * gated promotion shell (`runImprovementLoop`) are proposer-agnostic.
1679
+ *
1680
+ * This is THE optimization proposer — every optimizer is a factory
1681
+ * `xProposer(opts): SurfaceProposer` (`evolutionaryProposer`, `aceProposer`,
1682
+ * `gepaProposer`, `skillOptProposer`, `traceAnalystProposer`, `haloProposer`,
1683
+ * `memoryCurationProposer`, `fapoProposer`), all exported from `/campaign` and
1684
+ * drivable by `selfImprove({ proposer })`. Not to be confused with the
1685
+ * behavior-fuzzing `MutationProposer` (`fuzz/types`), a scenario generator for
1686
+ * a different loop.
1687
+ */
1688
+ interface SurfaceProposer<TFindings = unknown> {
1689
+ kind: string;
1690
+ /** Plan: propose N candidate surfaces for the next generation. A proposer
1691
+ * may return bare `MutableSurface`s or `ProposedCandidate`s that carry the
1692
+ * `{label, rationale}` motivating the change — the loop threads the
1693
+ * rationale into `GenerationCandidate` and the emitted provenance. */
1694
+ propose(ctx: ProposeContext<TFindings>): Promise<Array<MutableSurface | ProposedCandidate>>;
1695
+ /** Decide: stop early when the proposer judges the search converged or
1696
+ * exhausted. Default (omitted) runs all `maxGenerations`. */
1697
+ decide?(args: {
1698
+ history: GenerationRecord[];
1699
+ }): {
1700
+ stop: boolean;
1701
+ reason?: string;
1702
+ };
1703
+ }
1704
+ /** Optional vocabulary alias. The loop is the optimizer; this object is the
1705
+ * proposer inside that loop. */
1706
+ type OptimizationProposer<TFindings = unknown> = SurfaceProposer<TFindings>;
1707
+ interface OptimizerConfigBase {
1708
+ populationSize: number;
1709
+ maxGenerations: number;
1710
+ surfaceExtractor: (profile: unknown) => MutableSurface;
1711
+ }
1712
+ interface OptimizerConfig extends OptimizerConfigBase {
1713
+ proposer: SurfaceProposer;
1714
+ }
1715
+ /** Five-valued verdict taxonomy (MOSS-paper alignment). */
1716
+ type GateDecision = 'ship' | 'hold' | 'need_more_work' | 'model_ceiling' | 'arch_ceiling';
1717
+ interface GateContext<TArtifact, TScenario extends Scenario> {
1718
+ candidateArtifacts: Map<string, TArtifact>;
1719
+ baselineArtifacts?: Map<string, TArtifact>;
1720
+ /** Candidate (winner) judge scores, keyed by cellId. */
1721
+ judgeScores: Map<string, Record<string, JudgeScore>>;
1722
+ /** Baseline judge scores, keyed by cellId. SEPARATE from `judgeScores` —
1723
+ * baseline + candidate share cellIds (same scenarios), so a single map
1724
+ * cannot represent both. A gate computing a holdout delta MUST read
1725
+ * candidate from `judgeScores` and baseline from here. */
1726
+ baselineJudgeScores?: Map<string, Record<string, JudgeScore>>;
1727
+ /** Neutralized-arm judge scores, keyed by cellId — the winner surface with its
1728
+ * content footprint-matched-blanked (via a `neutralize` fn). Same scenarios as
1729
+ * `judgeScores`. Present ONLY when `runImprovementLoop` was given a `neutralize`
1730
+ * function. A placebo gate (`neutralizationGate`) compares this arm's lift
1731
+ * against the candidate's to reject decorative wins (lift from footprint, not
1732
+ * content). Undefined otherwise. */
1733
+ neutralizedJudgeScores?: Map<string, Record<string, JudgeScore>>;
1734
+ /** Neutralized-arm artifacts, keyed by cellId. Present alongside
1735
+ * `neutralizedJudgeScores`. */
1736
+ neutralizedArtifacts?: Map<string, TArtifact>;
1737
+ scenarios: TScenario[];
1738
+ cost: {
1739
+ candidate: number;
1740
+ baseline: number;
1741
+ };
1742
+ /** Shared run spend account and receipt attribution phase. */
1743
+ costLedger?: CostLedger;
1744
+ costPhase?: string;
1745
+ signal: AbortSignal;
1746
+ }
1747
+ interface GateResult {
1748
+ decision: GateDecision;
1749
+ reasons: string[];
1750
+ contributingGates: Array<{
1751
+ name: string;
1752
+ passed: boolean;
1753
+ detail: unknown;
1754
+ }>;
1755
+ delta?: number;
1756
+ }
1757
+ /** Composable promotion gate. */
1758
+ interface Gate<TArtifact = unknown, TScenario extends Scenario = Scenario> {
1759
+ name: string;
1760
+ decide(ctx: GateContext<TArtifact, TScenario>): Promise<GateResult>;
1761
+ }
1762
+ /** Scoped trace writer handed to each dispatch — every span
1763
+ * auto-tagged with the cellId so traces filter cleanly. */
1764
+ interface CampaignTraceWriter {
1765
+ span(name: string, attributes?: Record<string, unknown>): TraceSpan;
1766
+ flush(): Promise<void>;
1767
+ }
1768
+ interface TraceSpan {
1769
+ end(attributes?: Record<string, unknown>): void;
1770
+ setAttribute(key: string, value: unknown): void;
1771
+ }
1772
+ /** Scoped artifact writer — `write(path, content)` lands under
1773
+ * `<runDir>/<cellId>/<path>`. */
1774
+ interface CampaignArtifactWriter {
1775
+ write(path: string, content: string | Uint8Array): Promise<string>;
1776
+ writeJson(path: string, value: unknown): Promise<string>;
1777
+ }
1778
+ /** Token usage accumulated for a cell. Aliased to the canonical `RunTokenUsage`
1779
+ * (run-record.ts, same package) so a cell maps onto a `RunRecord` for the
1780
+ * backend-integrity guard with ONE source of truth — a field added to
1781
+ * `RunTokenUsage` is a compile error here, not a silent drift. */
1782
+ type CampaignTokenUsage = RunTokenUsage;
1783
+ /** Cell-scoped paid-call entry point. The dispatch places every paid operation
1784
+ * inside `runPaidCall`; the returned provider result supplies one receipt with
1785
+ * cost, tokens, and resolved model. Calls made outside this method are not
1786
+ * admitted or captured. */
1787
+ interface CampaignCostMeter {
1788
+ /** The only paid-call path. Returns a typed result; callers must inspect it. */
1789
+ runPaidCall<T>(input: Omit<RunPaidCallInput<T>, 'channel' | 'phase' | 'tags'> & {
1790
+ channel?: CostChannel;
1791
+ }): Promise<PaidCallResult<T>>;
1792
+ }
1793
+ /** Source tag — required on every store write. Used by the
1794
+ * default training-source filter (production-trace samples NOT used as
1795
+ * training scenarios unless explicitly opted in). */
1796
+ type LabeledScenarioSource = 'production-trace' | 'eval-run' | 'manual' | 'red-team' | 'synthetic';
1797
+ type RedactionStatus = 'raw' | 'redacted-pii' | 'redacted-secrets' | 'fully-redacted';
1798
+ /** How much a label can be trusted to evaluate against — the gold-admission
1799
+ * gate. Strictly ordered: a record qualifies for a `minTrust` filter when its
1800
+ * trust rank is >= the requested rank.
1801
+ *
1802
+ * - `unverified` — label is a heuristic (e.g. raw outcome success/fail).
1803
+ * Fine as corpus; MUST NOT enter a gold set that lift
1804
+ * numbers are computed against.
1805
+ * - `verified-signal` — an external signal confirmed the outcome (PR merged,
1806
+ * tests green, user did not retry, downstream check).
1807
+ * - `human-rated` — a human explicitly rated or corrected the artifact.
1808
+ *
1809
+ * Absent on a write ⇒ treated as `unverified` (fail-closed: a writer must
1810
+ * explicitly assert trust to make a record gold-eligible — it never happens
1811
+ * by accident). */
1812
+ type LabelTrust = 'unverified' | 'verified-signal' | 'human-rated';
1813
+ /** Ordinal rank for a label-trust tier; absent ⇒ `unverified` (rank 0). */
1814
+ declare function labelTrustRank(trust: LabelTrust | undefined): number;
1815
+ /** Required-provenance write. The store rejects writes that
1816
+ * lack provenance — a default-on flywheel without provenance is the
1817
+ * data-poisoning vector flagged in the alignment review. */
1818
+ interface LabeledScenarioWrite<TScenario extends Scenario = Scenario, TArtifact = unknown> {
1819
+ scenario: TScenario;
1820
+ artifact: TArtifact;
1821
+ judgeScores: Record<string, JudgeScore>;
1822
+ source: LabeledScenarioSource;
1823
+ sourceVersionHash: string;
1824
+ capturedAt: string;
1825
+ redactionStatus: RedactionStatus;
1826
+ /** Gold-admission trust tier. Absent ⇒ `unverified` (fail-closed): the
1827
+ * record is corpus, never gold. A writer must explicitly assert
1828
+ * `verified-signal` or `human-rated` to make it eligible for a gold
1829
+ * sample. See {@link LabelTrust}. */
1830
+ labelTrust?: LabelTrust;
1831
+ /** Optional per-source rate-limit bucket key (e.g., the tenant id). */
1832
+ rateLimitBucket?: string;
1833
+ }
1834
+ interface LabeledScenarioRecord<TScenario extends Scenario = Scenario, TArtifact = unknown> extends LabeledScenarioWrite<TScenario, TArtifact> {
1835
+ /** Stable hash of (scenario.id, source, capturedAt, sourceVersionHash). */
1836
+ recordHash: string;
1837
+ /** Substrate-assigned split — train if captured before the campaign's
1838
+ * `temporalCutoff`, test if after. Explicit override allowed via filter. */
1839
+ split: 'train' | 'test';
1840
+ }
1841
+ interface LabeledScenarioSampleArgs {
1842
+ count: number;
1843
+ /** REQUIRED — substrate refuses to sample without an explicit split. */
1844
+ split: 'train' | 'test';
1845
+ /** REQUIRED — only records captured before this timestamp are returned.
1846
+ * Enforces temporal split discipline (test scenarios captured AFTER train
1847
+ * cannot enter the training pool). */
1848
+ capturedBefore: string;
1849
+ filter?: {
1850
+ kind?: string;
1851
+ source?: LabeledScenarioSource | LabeledScenarioSource[];
1852
+ minComposite?: number;
1853
+ maxComposite?: number;
1854
+ /** Gold gate: only records whose trust rank is >= this tier are
1855
+ * returned. `sample({ split: 'test', minTrust: 'verified-signal' })` is
1856
+ * the canonical "give me the gold set" call. Absent ⇒ no trust gate
1857
+ * (corpus-level read). */
1858
+ minTrust?: LabelTrust;
1859
+ };
1860
+ }
1861
+ interface LabeledScenarioStore {
1862
+ observe(write: LabeledScenarioWrite): Promise<void>;
1863
+ sample(args: LabeledScenarioSampleArgs): Promise<LabeledScenarioRecord[]>;
1864
+ size(): Promise<{
1865
+ train: number;
1866
+ test: number;
1867
+ bySource: Record<string, number>;
1868
+ /** Count by trust tier — tells the flywheel how much gold it has
1869
+ * accumulated vs. raw corpus. */
1870
+ byTrust: Record<LabelTrust, number>;
1871
+ }>;
1872
+ }
1873
+ interface CampaignCellResult<TArtifact> {
1874
+ /** Manifest that produced this cell. Resumability refuses to reuse a cell
1875
+ * whose manifest differs from the current run. */
1876
+ manifestHash?: string;
1877
+ cellId: string;
1878
+ scenarioId: string;
1879
+ rep: number;
1880
+ generation?: number;
1881
+ artifact: TArtifact;
1882
+ judgeScores: Record<string, JudgeScore>;
1883
+ costUsd: number;
1884
+ /** True when at least one priced receipt used the model table instead of a provider bill. */
1885
+ costEstimated?: boolean;
1886
+ /** Exact durable receipts required to reuse this cached result. */
1887
+ costCallIds?: string[];
1888
+ /** Agent-call token usage committed by `ctx.cost.runPaidCall`.
1889
+ * `{ input: 0, output: 0 }` when no paid agent call was recorded. */
1890
+ tokenUsage: CampaignTokenUsage;
1891
+ /** Concrete model from the latest committed agent receipt. Consumed by
1892
+ * `buildRunRecord` to pin the model when the declared profile uses a
1893
+ * runtime-resolved sentinel. */
1894
+ resolvedModel?: string;
1895
+ durationMs: number;
1896
+ seed: number;
1897
+ cached: boolean;
1898
+ error?: string;
1899
+ }
1900
+ interface JudgeAggregate {
1901
+ mean: number;
1902
+ stdev: number;
1903
+ ci95: [number, number];
1904
+ n: number;
1905
+ }
1906
+ interface ScenarioAggregate {
1907
+ meanComposite: number;
1908
+ ci95: [number, number];
1909
+ n: number;
1910
+ }
1911
+ interface GenerationRecord {
1912
+ generationIndex: number;
1913
+ candidates: GenerationCandidate[];
1914
+ promoted: string[];
1915
+ }
1916
+ /** One scored candidate surface in a generation. `dimensions` + `scenarios`
1917
+ * let a reflective proposer ground its next proposal on WHICH
1918
+ * dimensions the candidate is weakest on and WHICH scenarios it best/worst
1919
+ * handled — the evidence a blind `Mutator` cannot see. */
1920
+ interface GenerationCandidate {
1921
+ surfaceHash: string;
1922
+ composite: number;
1923
+ ci95: [number, number];
1924
+ /** Exact surface this candidate mutated. */
1925
+ parentSurfaceHash?: string;
1926
+ /** Measured search-split composite of the exact parent surface. */
1927
+ parentComposite?: number;
1928
+ /** Candidate composite minus its parent's composite. Present only when the
1929
+ * candidate completed the designed denominator. */
1930
+ observedDeltaFromParent?: number;
1931
+ /** Whether this candidate had a scorable result for every designed campaign
1932
+ * cell and was therefore eligible for ranking, promotion, and Pareto
1933
+ * selection. Older externally-authored records may omit this field; loop
1934
+ * records always populate it. */
1935
+ eligibleForPromotion?: boolean;
1936
+ /** Exact denominator receipt for selection eligibility. Scores stay
1937
+ * descriptive: an incomplete candidate is retained with its observed score
1938
+ * and errors instead of receiving an invented penalty. */
1939
+ coverage?: {
1940
+ expectedCells: number;
1941
+ scorableCells: number;
1942
+ unscorableCells: Array<{
1943
+ cellId: string;
1944
+ reason: string;
1945
+ }>;
1946
+ };
1947
+ /** Mean score per judge dimension across all cells (scenarios × reps ×
1948
+ * judges that reported the dimension). */
1949
+ dimensions: Record<string, number>;
1950
+ /** Per-scenario composite (mean over reps + judges), plus the judge's
1951
+ * free-form `notes` for that scenario — the "why it scored low" evidence a
1952
+ * reflective proposer grounds its next edit on. Keep `notes` GENERALIZABLE
1953
+ * (which checks/lines/dimensions failed and how), NOT case-specific ground
1954
+ * truth: leaking expected answers into the prompt is memorization, and the
1955
+ * held-out gate would reject it anyway. */
1956
+ scenarios: Array<{
1957
+ scenarioId: string;
1958
+ composite: number;
1959
+ notes?: string;
1960
+ }>;
1961
+ /** Proposer-supplied short label for the change. Present when the proposer
1962
+ * returned a `ProposedCandidate`; absent for bare-surface mutators. */
1963
+ label?: string;
1964
+ /** Proposer-supplied rationale — WHY this candidate was proposed. The
1965
+ * "because rationale Z" the audit requires to survive to the result.
1966
+ * Present when the proposer returned a `ProposedCandidate`. */
1967
+ rationale?: string;
1968
+ /** Exact structured cause threaded from the proposer, when available. */
1969
+ candidateRecord?: PolicyEditCandidateRecord;
1970
+ }
1971
+ interface CampaignAggregates {
1972
+ byJudge: Record<string, JudgeAggregate>;
1973
+ byScenario: Record<string, ScenarioAggregate>;
1974
+ /** Canonical campaign accounting, including worker and judge calls. */
1975
+ cost: CostLedgerSummary;
1976
+ /** Compatibility alias of `cost.totalCostUsd`. */
1977
+ totalCostUsd: number;
1978
+ cellsExecuted: number;
1979
+ cellsSkipped: number;
1980
+ cellsCached: number;
1981
+ cellsFailed: number;
1982
+ }
1983
+ interface CampaignResult<TArtifact = unknown, TScenario extends Scenario = Scenario> {
1984
+ /** sha256(scenarios, judges, dispatch source ref, optimizer config, seed). Stable identity for reruns. */
1985
+ manifestHash: string;
1986
+ seed: number;
1987
+ startedAt: string;
1988
+ endedAt: string;
1989
+ durationMs: number;
1990
+ cells: Array<CampaignCellResult<TArtifact>>;
1991
+ aggregates: CampaignAggregates;
1992
+ optimization?: {
1993
+ generations: GenerationRecord[];
1994
+ winnerSurfaceHash?: string;
1995
+ };
1996
+ gate?: GateResult;
1997
+ prUrl?: string;
1998
+ runDir: string;
1999
+ artifactsByPath: Record<string, string>;
2000
+ /** Substrate strips the input scenarios to id+kind for the result manifest;
2001
+ * consumers needing full payload look it up via the original input. The
2002
+ * type parameter `TScenario` is propagated for downstream consumers that
2003
+ * want narrowed types when extending `CampaignResult`. */
2004
+ scenarios: Array<Pick<TScenario, 'id' | 'kind'>>;
2005
+ }
2006
+
2007
+ /**
2008
+ * `llmJudge` — the single-LLM-call bridge that turns a rubric prompt into a
2009
+ * canonical campaign `JudgeConfig`.
2010
+ *
2011
+ * The `JudgeConfig` contract (src/campaign/types.ts) is deliberately a
2012
+ * function, not a fixed LLM-prompt shape: real consumers judge with
2013
+ * ensembles, deterministic checks, or one LLM call. `ensembleJudge`
2014
+ * (src/judge-panel.ts) covers the multi-model case; `buildAgreementJudge`
2015
+ * (src/campaign/distillation) covers the pure-comparator case. `llmJudge`
2016
+ * covers the common single-call case the `JudgeConfig` doc-comment names:
2017
+ * one model call against `prompt`, parsed into the canonical `JudgeScore`
2018
+ * (`{ dimensions, composite, notes }`) on the campaign [0,1] scale.
2019
+ *
2020
+ * Transport is injected as a `ChatClient` (src/analyst/chat-client.ts) — the
2021
+ * substrate's transport-agnostic LLM seam — so the judge stays decoupled from
2022
+ * router-vs-sandbox-vs-cli-bridge and is unit-testable with the `mock`
2023
+ * transport. The composite is computed by `weightedComposite` (the same
2024
+ * sum-normalized weighting `ensembleJudge` uses), so a lift is attributable to
2025
+ * the dimension scores, not to a bespoke reducer.
2026
+ *
2027
+ * Fail-loud throughout: an unparseable model response throws `JudgeParseError`;
2028
+ * a response missing a declared dimension throws; an out-of-range score throws.
2029
+ * A thrown judge is recorded by the campaign engine as a failed cell, never
2030
+ * folded into a silent zero.
2031
+ */
2032
+
2033
+ /** A rubric dimension as a bare key or the full `{ key, description }` shape. A
2034
+ * bare string uses the key as its own description. */
2035
+ type LlmJudgeDimension = string | JudgeDimension;
2036
+ interface LlmJudgeOptions<TArtifact, TScenario extends Scenario = Scenario> {
2037
+ /** The injected LLM transport. One `chat()` call per `score()`. Required —
2038
+ * there is no default route, so a misconfigured judge fails at construction,
2039
+ * never silently against the free-tier router. */
2040
+ chat: ChatClient;
2041
+ /** Rubric dimensions the model scores. Each becomes a `[0,1]` field of the
2042
+ * returned `JudgeScore.dimensions`. Defaults to a single `quality` dimension. */
2043
+ dimensions?: LlmJudgeDimension[];
2044
+ /** Model id. Falls back to `chat.defaultModel`; one of the two MUST resolve. */
2045
+ model?: string;
2046
+ /** Explicit scoring revision for opaque transport or renderer changes. */
2047
+ judgeVersion?: string;
2048
+ temperature?: number;
2049
+ maxTokens?: number;
2050
+ /** Composite weights forwarded to `weightedComposite`: a partial map selects
2051
+ * AND weights exactly the named dimensions. Omit for a uniform mean. */
2052
+ weights?: Record<string, number>;
2053
+ /** Scale the model is prompted to score on, normalized into `[0,1]`:
2054
+ * - `'unit'` (default): the model returns `[0,1]` directly.
2055
+ * - `'ten'`: the model returns `[0,10]`; divided by 10 here.
2056
+ * The prompt is annotated with the expected range either way. */
2057
+ scale?: 'unit' | 'ten';
2058
+ /** Run this judge only on matching scenarios (mirrors `JudgeConfig.appliesTo`). */
2059
+ appliesTo?: (scenario: TScenario) => boolean;
2060
+ /** Render the artifact + scenario into the user message. Default:
2061
+ * pretty-printed JSON of `{ scenario, artifact }`. */
2062
+ renderUser?: (input: {
2063
+ artifact: TArtifact;
2064
+ scenario: TScenario;
2065
+ }) => string;
2066
+ /** Strict runtime contract; its JSON Schema is sent to the provider. */
2067
+ costLedger?: CostLedger;
2068
+ responseSchema?: {
2069
+ name: string;
2070
+ schema: z.ZodObject;
2071
+ };
2072
+ }
2073
+ /**
2074
+ * Build a campaign-shaped `JudgeConfig` whose `score()` makes ONE LLM call
2075
+ * against `prompt` and reduces the model's per-dimension scores to a canonical
2076
+ * `JudgeScore` in `[0,1]`.
2077
+ *
2078
+ * The model is instructed to return JSON `{ "dimensions": { <key>: <number>, … },
2079
+ * "notes": "…" }`; the helper strips fenced JSON, validates every declared
2080
+ * dimension is present and in range, normalizes by `scale`, and composites via
2081
+ * `weightedComposite`.
2082
+ */
2083
+ declare function llmJudge<TArtifact = unknown, TScenario extends Scenario = Scenario>(name: string, prompt: string, opts: LlmJudgeOptions<TArtifact, TScenario>): JudgeConfig<TArtifact, TScenario>;
2084
+
2085
+ interface ReferenceEquivalenceScenario extends Scenario {
2086
+ userRequest: string;
2087
+ expectedAnswer: string;
2088
+ }
2089
+ interface ReferenceEquivalenceJudgeOptions {
2090
+ /** Injected transport. No implicit provider or credentials are selected. */
2091
+ chat: ChatClient;
2092
+ /** Falls back to the ChatClient's default model. */
2093
+ model?: string;
2094
+ /** Used only by the direct-call adapter. */
2095
+ signal?: AbortSignal;
2096
+ /** Optional receipt destination for direct calls; campaigns supply their own. */
2097
+ costLedger?: CostLedger;
2098
+ }
2099
+ /** Build the campaign-native expected-answer judge. */
2100
+ declare function createReferenceEquivalenceJudge(options: ReferenceEquivalenceJudgeOptions): JudgeConfig<string, ReferenceEquivalenceScenario>;
2101
+
2102
+ interface AnalyzeTracesInput {
2103
+ /** The user-facing question. Domain framing belongs here, not in the
2104
+ * actor description. */
2105
+ question: string;
2106
+ }
2107
+ interface AnalyzeTracesResult {
2108
+ /** The responder's prose answer. */
2109
+ answer: string;
2110
+ /** Bulleted findings extracted from the responder's structured output. */
2111
+ findings: string[];
2112
+ /** Per-actor-turn snapshots captured via `actorTurnCallback`. */
2113
+ turns: AnalyzeTracesTurnSnapshot[];
2114
+ /** Total turns the actor took. */
2115
+ turnCount: number;
2116
+ /** Token usage by role. */
2117
+ usage: TraceAnalystUsage;
2118
+ /** Full system + assistant + tool message log by role. */
2119
+ chatLog: TraceAnalystChatLog;
2120
+ /** Prompt version that produced this run. */
2121
+ actorPromptVersion: string;
2122
+ }
2123
+ interface TraceAnalystUsage {
2124
+ actor: TraceAnalystUsageEntry[];
2125
+ responder: TraceAnalystUsageEntry[];
2126
+ }
2127
+ interface TraceAnalystUsageEntry {
2128
+ [key: string]: unknown;
2129
+ }
2130
+ interface TraceAnalystChatLog {
2131
+ actor: TraceAnalystChatMessage[];
2132
+ responder: TraceAnalystChatMessage[];
2133
+ }
2134
+ interface TraceAnalystChatMessage {
2135
+ [key: string]: unknown;
2136
+ }
2137
+ interface AnalyzeTracesTurnSnapshot {
2138
+ turn: number;
2139
+ isError: boolean;
2140
+ /** The JS code the actor produced for this turn. */
2141
+ code: string;
2142
+ /** The formatted action-log entry the actor sees on the next turn. */
2143
+ output: string;
2144
+ /** Provider thought (when `actorOptions.showThoughts` is true and the
2145
+ * provider returns it). */
2146
+ thought?: string;
2147
+ }
2148
+ interface AnalyzeTracesOptions {
2149
+ /** Trace data source. Pass either an OTLP-JSONL path or a custom store. */
2150
+ source: string | TraceAnalysisStore;
2151
+ /** Caller-provided AxAIService. */
2152
+ ai: AxAIService;
2153
+ /** Model id forwarded to actor + responder. */
2154
+ model?: string;
2155
+ /** Recursion depth. 0 = no sub-agent dispatch. Default 1. */
2156
+ maxDepth?: number;
2157
+ /** Maximum actor turns. Default 12. */
2158
+ maxTurns?: number;
2159
+ /** Maximum parallel sub-agent calls in batched llmQuery. Default 2. */
2160
+ maxParallelSubagents?: number;
2161
+ /** Override the actor description. */
2162
+ actorDescription?: string;
2163
+ /** Override the subagent description. */
2164
+ subagentDescription?: string;
2165
+ /** Per-turn observability hook. */
2166
+ onTurn?: (turn: AnalyzeTracesTurnSnapshot) => void | Promise<void>;
2167
+ /** Override max runtime characters per turn. Default 6000. */
2168
+ maxRuntimeChars?: number;
2169
+ /** When set, every turn's snapshot is appended to this JSONL file
2170
+ * immediately. If the analyst crashes mid-loop (provider 503,
2171
+ * network error, validator reject) the partial reasoning is still
2172
+ * on disk. Replay the file with the responder afterward to recover
2173
+ * evidence. */
2174
+ progressLogPath?: string;
2175
+ }
2176
+
273
2177
  /**
274
2178
  * Make the trace-analyst's OWN prompt a GEPA-optimizable surface.
275
2179
  *
@@ -377,6 +2281,210 @@ interface FailureModeRecallJudgeOptions {
377
2281
  */
378
2282
  declare function failureModeRecallJudge(opts?: FailureModeRecallJudgeOptions): JudgeConfig<AnalystArtifact, AnalystScenario>;
379
2283
 
2284
+ /**
2285
+ * `openAutoPr` — thin shell-out helper for the `runImprovementLoop` preset's
2286
+ * `autoOnPromote: 'pr'` mode. Substitutes for the per-product PR-opening
2287
+ * code consumers duplicated 4 times. The PR body includes the campaign's
2288
+ * manifest hash, gate verdict, and scorecard summary so reviewers can see
2289
+ * exactly what was promoted + why.
2290
+ *
2291
+ * NOT a deploy mechanism — this only OPENS a PR. The human reviews + merges.
2292
+ * The Shape B (`autoOnPromote: 'config'`) live-runtime-mutation path is
2293
+ * deferred to Pass B with the full shadow / canary / rollback stack.
2294
+ */
2295
+
2296
+ interface OpenAutoPrOptions<TArtifact, TScenario extends Scenario> {
2297
+ /** Campaign result to attach to the PR. */
2298
+ result: CampaignResult<TArtifact, TScenario>;
2299
+ /** Gate verdict explaining the promotion. Substrate refuses to open a PR
2300
+ * when `gate.decision !== 'ship'` — fails loud. */
2301
+ gate: GateResult;
2302
+ /** Promoted surface diff — typically the new system prompt addendum or
2303
+ * full profile diff. Substrate writes it as the PR body. */
2304
+ promotedDiff: string;
2305
+ /** GH owner/repo target (e.g., `tangle-network/gtm-agent`). */
2306
+ ghOwner: string;
2307
+ ghRepo: string;
2308
+ /** Branch name for the PR. Default `auto/<manifestHash[:12]>`. */
2309
+ branch?: string;
2310
+ /** PR title. Default includes manifest hash. */
2311
+ title?: string;
2312
+ /** Whether to actually open the PR or just dry-run. Default reads
2313
+ * `GH_AUTO_PR_TOKEN` env — present = open, absent = dry-run. */
2314
+ dryRun?: boolean;
2315
+ /** Test seam — substitute `gh pr create` invocation. */
2316
+ ghExec?: (args: string[]) => {
2317
+ stdout: string;
2318
+ stderr: string;
2319
+ status: number;
2320
+ };
2321
+ }
2322
+ interface OpenAutoPrResult {
2323
+ opened: boolean;
2324
+ prUrl?: string;
2325
+ dryRun: boolean;
2326
+ reason: string;
2327
+ }
2328
+ /**
2329
+ * Open a GitHub PR for a gate-approved surface promotion, attaching the manifest hash, gate verdict, and diff as the PR body.
2330
+ */
2331
+ declare function openAutoPr<TArtifact, TScenario extends Scenario>(options: OpenAutoPrOptions<TArtifact, TScenario>): OpenAutoPrResult;
2332
+
2333
+ interface ContinuousAgreement {
2334
+ /** Cohen's κ_w with quadratic weights, computed on raw [0,1] scores. */
2335
+ weightedKappa: number;
2336
+ /** ICC(2,1): two-way random effects, absolute agreement, single rater. */
2337
+ icc: number;
2338
+ /** Pearson product-moment correlation (averaged over rater pairs if N>2). */
2339
+ pearson: number;
2340
+ /** Spearman rank correlation (averaged over rater pairs if N>2). */
2341
+ spearman: number;
2342
+ /** 95% bootstrap percentile CIs over items. */
2343
+ ci: {
2344
+ icc: [number, number];
2345
+ weightedKappa: [number, number];
2346
+ };
2347
+ /** Number of complete items (no NaN across raters). */
2348
+ n: number;
2349
+ /** Number of raters. */
2350
+ raters: number;
2351
+ }
2352
+
2353
+ interface PairedBootstrapResult {
2354
+ /** Number of paired observations. */
2355
+ n: number;
2356
+ /** Median of paired deltas (after − before). */
2357
+ median: number;
2358
+ /** Mean of paired deltas. */
2359
+ mean: number;
2360
+ /** Lower bound of the bootstrap CI on the chosen statistic. */
2361
+ low: number;
2362
+ /** Upper bound of the bootstrap CI on the chosen statistic. */
2363
+ high: number;
2364
+ /** Confidence level used (e.g. 0.95). */
2365
+ confidence: number;
2366
+ /** Number of bootstrap resamples used. */
2367
+ resamples: number;
2368
+ }
2369
+ /** Result of a McNemar paired-binary significance test. */
2370
+ interface McNemarResult {
2371
+ /** Total paired observations. */
2372
+ n: number;
2373
+ /** Discordant pairs (b + c) — the only ones that carry signal. */
2374
+ nDiscordant: number;
2375
+ /** Pairs where treatment succeeded and control failed ("newly correct"). */
2376
+ b: number;
2377
+ /** Pairs where control succeeded and treatment failed ("newly wrong"). */
2378
+ c: number;
2379
+ /** Continuity-corrected chi-square statistic (reference; exact p drives the call). */
2380
+ statistic: number;
2381
+ /** Two-sided p-value. Exact (binomial sign test on discordant pairs). */
2382
+ pValue: number;
2383
+ }
2384
+ /** A paired binary effect size (treatment rate − control rate) with a CI. */
2385
+ interface RiskDifferenceResult {
2386
+ /** Total paired observations. */
2387
+ n: number;
2388
+ /** Discordant pairs: treatment-win count. */
2389
+ b: number;
2390
+ /** Discordant pairs: control-win count. */
2391
+ c: number;
2392
+ /** Paired risk difference p(treatment) − p(control) = (b − c) / n. */
2393
+ riskDifference: number;
2394
+ /** Lower bound of the CI, clamped to [-1, 1]. */
2395
+ lower: number;
2396
+ /** Upper bound of the CI, clamped to [-1, 1]. */
2397
+ upper: number;
2398
+ /** Confidence level used. */
2399
+ confidence: number;
2400
+ }
2401
+ interface EProcessStep {
2402
+ /** Current wealth W_n — the e-value against H0 after n observations. */
2403
+ wealth: number;
2404
+ /** Observations consumed so far. */
2405
+ n: number;
2406
+ /** True from the first n where W_n ≥ 1/alpha onward (sticky). */
2407
+ decided: boolean;
2408
+ }
2409
+ interface EProcessState extends EProcessStep {
2410
+ alpha: number;
2411
+ maxBet: number;
2412
+ nullMean: number;
2413
+ /** The decision boundary 1/alpha. */
2414
+ threshold: number;
2415
+ /** Observation count at the first threshold crossing; undefined until decided. */
2416
+ decidedAtN?: number;
2417
+ }
2418
+
2419
+ /**
2420
+ * Matched-pair arm comparison — "did the treatment arm beat the baseline arm
2421
+ * on the SAME work items?"
2422
+ *
2423
+ * An arm A/B over run records is only trustworthy when it is PAIRED: the same
2424
+ * task/scenario/seed evaluated under both arms, compared item-by-item, so
2425
+ * inter-item difficulty variance cancels instead of masquerading as an arm
2426
+ * effect. This module owns the two error-prone steps every consumer otherwise
2427
+ * hand-rolls:
2428
+ *
2429
+ * 1. Pairing — matching rows across arms by `pairKey` (and by `repKey`
2430
+ * within multi-rep items), with leftovers REPORTED rather than silently
2431
+ * dropped (a silently unbalanced pairing biases every paired statistic
2432
+ * downstream). Pairing never keys on outcome content: matching reps by
2433
+ * their outcomes deflates discordant-pair counts and makes McNemar
2434
+ * anti-conservative, so reps pair only by (`pairKey`, `repKey`) identity.
2435
+ * 2. Composition — feeding the matched pairs to the correct paired
2436
+ * estimators that already live in `statistics`: `mcnemar` +
2437
+ * `pairedRiskDifference` for pass/fail, `pairedBootstrap` +
2438
+ * `wilcoxonSignedRank` for continuous metrics. No statistic is
2439
+ * re-implemented here.
2440
+ *
2441
+ * The row shape is deliberately structural — callers project a `RunRecord`
2442
+ * (or any record) into `{ pairKey, arm, pass?, metrics? }`. Arm names are
2443
+ * caller-supplied parameters; the module ships no domain literal.
2444
+ */
2445
+
2446
+ /** Paired pass/fail comparison over the pairs where BOTH sides carry `pass`. */
2447
+ interface PairedCorrectness {
2448
+ /** Discordant pairs where the treatment passed and the baseline failed. */
2449
+ b10: number;
2450
+ /** Discordant pairs where the baseline passed and the treatment failed. */
2451
+ b01: number;
2452
+ /** Exact McNemar significance over the paired outcomes (`b === b10`, `c === b01`). */
2453
+ mcnemar: McNemarResult;
2454
+ /** Paired effect size: p(treatment) − p(baseline) with a paired-variance CI. */
2455
+ riskDifference: RiskDifferenceResult;
2456
+ }
2457
+ /** Paired delta summary for one named metric (delta = treatment − baseline). */
2458
+ interface PairedMetricDelta {
2459
+ name: string;
2460
+ /** Pairs where BOTH sides carry a finite value for this metric. */
2461
+ n: number;
2462
+ /** Pairs where at least one side does not carry the metric. */
2463
+ nMissing: number;
2464
+ /** Median paired delta; NaN when `n === 0` (no data ≠ measured zero). */
2465
+ medianDelta: number;
2466
+ /** Mean paired delta; NaN when `n === 0`. */
2467
+ meanDelta: number;
2468
+ /** Bootstrap CI on the paired delta (`pairedBootstrap`); null when
2469
+ * `n === 0` — a zero-width [0, 0] interval on no data would read as a
2470
+ * measured tight null. */
2471
+ bootstrapCi: PairedBootstrapResult | null;
2472
+ /** Wilcoxon signed-rank test on the paired deltas; null when `n === 0`. */
2473
+ wilcoxon: {
2474
+ w: number;
2475
+ p: number;
2476
+ } | null;
2477
+ }
2478
+ interface PairedArmsComparison {
2479
+ nPairs: number;
2480
+ nUnpairedBaseline: number;
2481
+ nUnpairedTreatment: number;
2482
+ /** null when no matched pair carries `pass` on both sides — a pass/fail
2483
+ * verdict over rows that never measured pass/fail would be fabricated. */
2484
+ correctness: PairedCorrectness | null;
2485
+ metricDeltas: PairedMetricDelta[];
2486
+ }
2487
+
380
2488
  /** Whether one candidate attempt produced a usable executable outcome. */
381
2489
  type CrossSurfaceAttemptCompleteness = 'complete' | 'missing' | 'invalid';
382
2490
  /** One independently proposed change on one caller-defined surface. */
@@ -636,19 +2744,208 @@ interface CrossSurfaceInteractionReport<TRow extends CrossSurfaceTaskRow = Cross
636
2744
  pairwise: CrossSurfacePairwiseEntry[];
637
2745
  selections: CrossSurfaceSelections;
638
2746
  }
639
-
2747
+
2748
+ /**
2749
+ * Task-paired comparison and deterministic selection for independently
2750
+ * proposed agent surfaces. Unlike factorial cell-mean attribution, every
2751
+ * benefit, regression, and interaction remains attached to its task row.
2752
+ */
2753
+
2754
+ /**
2755
+ * Build the complete cross-surface evidence matrix and derive all three frozen
2756
+ * candidates. The task/candidate/component orders are part of the input so
2757
+ * neither insertion order nor an after-the-fact tie-break can change a result.
2758
+ */
2759
+ declare function analyzeCrossSurfaceInteractions<TRow extends CrossSurfaceTaskRow>(input: AnalyzeCrossSurfaceInteractionsInput<TRow>): CrossSurfaceInteractionReport<TRow>;
2760
+
2761
+ /**
2762
+ * `CampaignStorage` — the filesystem seam `runCampaign` writes through
2763
+ * (run/cell dirs, the resumability cache, per-cell artifacts, trace spans).
2764
+ *
2765
+ * The default (`fsCampaignStorage`) is the Node filesystem — identical
2766
+ * behavior to the inline `node:fs` calls it replaces, so existing CLI
2767
+ * consumers are unaffected. `inMemoryCampaignStorage` keeps everything in a
2768
+ * `Map`, so the substrate runs in environments WITHOUT a filesystem
2769
+ * (Cloudflare Workers, Deno Deploy, other edge runtimes) — the campaign
2770
+ * still produces its `CampaignResult` (cells + aggregates) in memory;
2771
+ * artifacts/traces simply aren't persisted to disk.
2772
+ *
2773
+ * Paths are opaque keys to the in-memory adapter — it does not parse them,
2774
+ * so the same `join(...)`-built paths work unchanged across both adapters.
2775
+ */
2776
+ interface CampaignStorage {
2777
+ /** Ensure a directory exists (recursive). No-op for in-memory. */
2778
+ ensureDir(dir: string): void;
2779
+ /** Does this path exist (as a written file or an ensured dir)? */
2780
+ exists(path: string): boolean;
2781
+ /** Read a UTF-8 file; `undefined` when missing or unreadable. */
2782
+ read(path: string): string | undefined;
2783
+ /** Write a file (string or bytes). Parent dir is assumed ensured. */
2784
+ write(path: string, content: string | Uint8Array): void;
2785
+ /** Append only when the current UTF-8 byte length matches `expectedBytes`.
2786
+ * Returns the new length, or undefined when another writer won. */
2787
+ append?(path: string, content: string, expectedBytes: number): number | undefined;
2788
+ }
2789
+ /** Node-filesystem storage — the default. Lazily requires `node:fs` so the
2790
+ * module imports cleanly in non-Node runtimes (where the caller passes
2791
+ * `inMemoryCampaignStorage` instead and never constructs this).
2792
+ *
2793
+ * `createRequire(import.meta.url)` is the ESM-native lazy require — a bare
2794
+ * `require` is a ReferenceError under `"type": "module"`, which is exactly
2795
+ * the shape this package publishes. */
2796
+ declare function fsCampaignStorage(): CampaignStorage;
2797
+ /** In-memory storage for filesystem-less runtimes. Artifacts + trace spans
2798
+ * live in a `Map` for the duration of the run; the `CampaignResult` is
2799
+ * fully populated, but nothing is persisted to disk. */
2800
+ declare function inMemoryCampaignStorage(): CampaignStorage;
2801
+ /** Open the durable spend account stored beside a logical run. */
2802
+ declare function createRunCostLedger(input: {
2803
+ storage: CampaignStorage;
2804
+ runDir: string;
2805
+ costCeilingUsd?: number;
2806
+ }): CostLedger;
2807
+
2808
+ /**
2809
+ * `runCampaign` — Pass A substrate primitive. ONE function that orchestrates
2810
+ * scenarios → dispatch → artifacts → judges → aggregates, with full
2811
+ * reproducibility (seed + manifest hash), cell-level resumability, bootstrap
2812
+ * CIs, and the `LabeledScenarioStore` capture flywheel.
2813
+ *
2814
+ * Improvement loops (optimizer / gate / autoOnPromote) ride on top of this
2815
+ * primitive but live in `presets/run-improvement-loop.ts`. This file keeps
2816
+ * the core orchestrator minimal — Phase 1 of the Pass A track.
2817
+ */
2818
+
2819
+ interface RunCampaignOptions<TScenario extends Scenario, TArtifact> {
2820
+ scenarios: TScenario[];
2821
+ dispatch: DispatchFn<TScenario, TArtifact>;
2822
+ /**
2823
+ * Stable identity for the dispatch behavior, included in the manifest/cache
2824
+ * key. Set this when the same function name can run different models,
2825
+ * prompts, tools, or external config.
2826
+ */
2827
+ dispatchRef?: string;
2828
+ judges?: JudgeConfig<TArtifact, TScenario>[];
2829
+ /** Required for reproducibility. Default 42. */
2830
+ seed?: number;
2831
+ /** Per-scenario replicates for CI bands. Default 1; raise to 5+ for
2832
+ * bootstrap-tight intervals on critical eval. */
2833
+ reps?: number;
2834
+ /** When true (default), completed cells are cached by
2835
+ * (manifestHash, scenarioId, rep, generation). Re-runs skip cached cells. */
2836
+ resumable?: boolean;
2837
+ /** Optional store — when present, every artifact + judge score is captured
2838
+ * with the configured `captureSource`. Capture is default ON; pass `'off'`
2839
+ * to disable. */
2840
+ labeledStore?: LabeledScenarioStore | 'off';
2841
+ captureSource?: 'production-trace' | 'eval-run' | 'manual' | 'red-team' | 'synthetic';
2842
+ captureSourceVersionHash?: string;
2843
+ /** Hard spend cap. Each paid call reserves its enforced maximum before dispatch. */
2844
+ costCeiling?: number;
2845
+ /** Shared spend account. Improvement loops pass one ledger through every
2846
+ * campaign so the ceiling and returned total are run-wide. */
2847
+ costLedger?: CostLedger;
2848
+ /** Attribution label for receipts recorded by this campaign. */
2849
+ costPhase?: string;
2850
+ /** Max concurrent cells. Default 2. */
2851
+ maxConcurrency?: number;
2852
+ /**
2853
+ * Per-cell dispatch deadline in ms. A `dispatch` that neither resolves nor
2854
+ * rejects within this window is a hang (a stalled model request, an
2855
+ * exhausted runtime resource, a backend that never closes its stream). When
2856
+ * set, the cell's `ctx.signal` is aborted and the cell is recorded as a LOUD
2857
+ * error (`dispatch exceeded <N>ms`) so the campaign proceeds and the failure
2858
+ * is visible — instead of one wedged cell silently hanging the whole run (and
2859
+ * every loop/CI job above it) forever. `undefined`/`0` = unbounded (legacy).
2860
+ */
2861
+ dispatchTimeoutMs?: number;
2862
+ /** Required: where artifacts + traces land. A bare name (not an absolute path)
2863
+ * resolves to the shared `~/.tangle/traces/<repo>/runs/<name>` root so run
2864
+ * bundles never pollute a repo working tree. Pass an absolute path to override. */
2865
+ runDir: string;
2866
+ /** Subject repo for the shared run-dir root (defaults to the CWD basename).
2867
+ * Only consulted when `runDir` is a bare name. */
2868
+ repo?: string;
2869
+ /** Tracing posture. Default is the substrate's `FileSystemTraceStore` rooted
2870
+ * at `<runDir>/traces/`. `'off'` disables capture entirely — substrate
2871
+ * refuses this when the caller wires `autoOnPromote !== 'none'`. */
2872
+ tracing?: 'on' | 'off';
2873
+ /**
2874
+ * Per-cell usage expectation — the early, fine-grained sibling of the
2875
+ * batch `assertRealBackend` guard. A cell that produced an artifact (no
2876
+ * error) but reported `costUsd === 0` AND zero tokens is a stub: the
2877
+ * dispatch never reported LLM activity via `ctx.cost`. Modes:
2878
+ * - `'warn'` (default) — log the offending cell loudly, keep going.
2879
+ * - `'assert'` — throw `BackendIntegrityError` on the first such cell
2880
+ * (fail-fast; recommended for CI campaigns expecting real LLM calls).
2881
+ * - `'off'` — no check (replay / deterministic-only / offline analysis).
2882
+ */
2883
+ expectUsage?: 'assert' | 'warn' | 'off';
2884
+ /** Test seam — override the wall clock for deterministic tests. */
2885
+ now?: () => Date;
2886
+ /** Test seam — override per-cell trace writer factory. */
2887
+ buildTraceWriter?: (cellId: string, dir: string) => CampaignTraceWriter;
2888
+ /** Storage backend for run/cell dirs, the resumability cache, artifacts,
2889
+ * and trace spans. Default: the Node filesystem (`fsCampaignStorage`).
2890
+ * Pass `inMemoryCampaignStorage()` to run in a filesystem-less runtime
2891
+ * (Cloudflare Workers, Deno, edge) — the `CampaignResult` is still
2892
+ * produced; artifacts/traces just aren't persisted to disk. */
2893
+ storage?: CampaignStorage;
2894
+ /**
2895
+ * Optional per-cell placement strategy. Returns an opaque string the
2896
+ * substrate forwards as `ctx.placement` to the Dispatch — placement-aware
2897
+ * Dispatches (e.g. `httpDispatch` from `/adapters/http`) use it to route
2898
+ * each cell to the right worker, region, or sandbox. When unset, every
2899
+ * cell receives `ctx.placement = undefined` and behaves identically to
2900
+ * the in-process case.
2901
+ *
2902
+ * @example
2903
+ * cellPlacement: ({ scenario }) => scenario.tags?.includes('eu') ? 'eu-west' : 'us-east'
2904
+ */
2905
+ cellPlacement?: (input: {
2906
+ scenario: TScenario;
2907
+ rep: number;
2908
+ generation?: number;
2909
+ }) => string | undefined;
2910
+ }
640
2911
  /**
641
- * Task-paired comparison and deterministic selection for independently
642
- * proposed agent surfaces. Unlike factorial cell-mean attribution, every
643
- * benefit, regression, and interaction remains attached to its task row.
2912
+ * Core campaign orchestrator: fan scenarios through dispatch, score with judges, aggregate bootstrap CIs, and persist reproducible `CampaignResult` records.
644
2913
  */
645
-
2914
+ declare function runCampaign<TScenario extends Scenario, TArtifact>(opts: RunCampaignOptions<TScenario, TArtifact>): Promise<CampaignResult<TArtifact, TScenario>>;
2915
+ interface CampaignRunPlanCell {
2916
+ cellId: string;
2917
+ scenarioId: string;
2918
+ rep: number;
2919
+ seed: number;
2920
+ cachePath: string;
2921
+ status: 'cached' | 'run';
2922
+ reason?: 'missing' | 'manifest-mismatch' | 'cell-mismatch' | 'corrupt' | 'resumable-off';
2923
+ }
2924
+ interface CampaignRunPlan {
2925
+ manifestHash: string;
2926
+ totalCells: number;
2927
+ cellsCached: number;
2928
+ cellsToRun: number;
2929
+ cells: CampaignRunPlanCell[];
2930
+ }
2931
+ interface PlanCampaignRunOptions<TScenario extends Scenario, TArtifact> {
2932
+ scenarios: TScenario[];
2933
+ dispatch?: DispatchFn<TScenario, TArtifact>;
2934
+ dispatchRef?: string;
2935
+ judges?: JudgeConfig<TArtifact, TScenario>[];
2936
+ seed?: number;
2937
+ reps?: number;
2938
+ resumable?: boolean;
2939
+ runDir: string;
2940
+ /** Subject repo for the shared run-dir root (see RunCampaignOptions.repo). */
2941
+ repo?: string;
2942
+ storage?: CampaignStorage;
2943
+ }
646
2944
  /**
647
- * Build the complete cross-surface evidence matrix and derive all three frozen
648
- * candidates. The task/candidate/component orders are part of the input so
649
- * neither insertion order nor an after-the-fact tie-break can change a result.
2945
+ * Plan a campaign WITHOUT dispatching: computes the manifest hash and the per-cell
2946
+ * run-vs-cached schedule so callers can preview cost and resumability before spending.
650
2947
  */
651
- declare function analyzeCrossSurfaceInteractions<TRow extends CrossSurfaceTaskRow>(input: AnalyzeCrossSurfaceInteractionsInput<TRow>): CrossSurfaceInteractionReport<TRow>;
2948
+ declare function planCampaignRun<TScenario extends Scenario, TArtifact>(opts: PlanCampaignRunOptions<TScenario, TArtifact>): CampaignRunPlan;
652
2949
 
653
2950
  type EvalFixtureValidationMode = 'vitest' | 'none';
654
2951
  interface EvalFixtureFile {
@@ -710,6 +3007,185 @@ declare function loadEvalFixtureScenarios(evalsDir: string, options?: LoadEvalFi
710
3007
  */
711
3008
  declare function planEvalFixtureRun<TArtifact = unknown>(options: PlanEvalFixtureRunOptions<TArtifact>): EvalFixtureRunPlan;
712
3009
 
3010
+ /**
3011
+ * Compose multiple `Gate` implementations — every gate must pass for the
3012
+ * composite to ship. Closes the alignment reviewer's "default-only
3013
+ * heldOutGate + costGate would happily promote a reward-hacked prompt"
3014
+ * concern by making safety gates first-class composable defaults.
3015
+ */
3016
+
3017
+ /** Compose gates — all must `ship` for the composite to `ship`. First
3018
+ * non-ship verdict short-circuits the composite verdict, but ALL gates run
3019
+ * (so the result records every gate's reason — useful for diagnostics). */
3020
+ declare function composeGate<TArtifact = unknown, TScenario extends Scenario = Scenario>(...gates: Array<Gate<TArtifact, TScenario>>): Gate<TArtifact, TScenario>;
3021
+
3022
+ /**
3023
+ * Dataset — versioned, sliceable, content-hashed scenario collection.
3024
+ *
3025
+ * Scenarios stop being ephemeral arrays and become first-class
3026
+ * artifacts. Every Dataset carries:
3027
+ * - content hash (sha256 over canonicalized scenario array)
3028
+ * - provenance (contributor, createdAt, sourceUrl)
3029
+ * - split labels (train | dev | test | holdout)
3030
+ * - difficulty tiers (easy | medium | hard | extreme)
3031
+ * - tags (free-form, per-scenario)
3032
+ *
3033
+ * `Dataset.slice({ difficulty, split, holdout, seed })` returns a
3034
+ * deterministic, reproducible subset. Holdout slices are locked: you
3035
+ * can read them but `mutate` throws, which prevents "oh I'll just
3036
+ * tweak that one scenario" contamination drift.
3037
+ */
3038
+ type DatasetSplit = 'train' | 'dev' | 'test' | 'holdout';
3039
+ type DatasetDifficulty = 'easy' | 'medium' | 'hard' | 'extreme';
3040
+ interface DatasetScenario {
3041
+ id: string;
3042
+ /** Arbitrary payload; the framework doesn't interpret it. */
3043
+ payload: unknown;
3044
+ split?: DatasetSplit;
3045
+ difficulty?: DatasetDifficulty;
3046
+ /** Canary token that MUST NOT round-trip through a correct agent output. */
3047
+ canary?: string;
3048
+ /**
3049
+ * Behavioral-canary forbidden pattern. A string OR a serialized regex
3050
+ * (`/.../flags`) that the agent under test MUST NOT emit. Used by
3051
+ * {@link import('./canary').checkBehavioralCanary | checkBehavioralCanary},
3052
+ * which inverts the contamination-style semantic: presence in the
3053
+ * agent output is a LEAK / failure, not a positive signal.
3054
+ *
3055
+ * Falls back to {@link canary} when omitted.
3056
+ */
3057
+ forbiddenPattern?: string;
3058
+ tags?: Record<string, string>;
3059
+ }
3060
+
3061
+ /**
3062
+ * Red-team battery — adversarial scenario corpus with per-category
3063
+ * scorers.
3064
+ *
3065
+ * Every scenario is a `DatasetScenario` with a `redTeam: {...}` payload
3066
+ * that declares: category, expected refusal, attack surface. Scorers
3067
+ * inspect the agent's output + trajectory and return a PASS/FAIL per
3068
+ * category. Aggregated via `redTeamReport`.
3069
+ *
3070
+ * The built-in corpus is intentionally conservative — 40 cases
3071
+ * covering the most-seen attack patterns. Extend via `extendCorpus`.
3072
+ */
3073
+
3074
+ type RedTeamCategory = 'prompt_injection_direct' | 'prompt_injection_indirect' | 'jailbreak_persona' | 'jailbreak_dan' | 'pii_leak' | 'permission_escalation' | 'data_exfil' | 'policy_override';
3075
+ interface RedTeamPayload {
3076
+ category: RedTeamCategory;
3077
+ /** The hostile input the agent sees. */
3078
+ input: string;
3079
+ /**
3080
+ * What a safe agent should do: "refuse" means explicit refusal,
3081
+ * "ignore" means process benignly without executing the attack.
3082
+ */
3083
+ expected: 'refuse' | 'ignore';
3084
+ /** Substring(s) that MUST NOT appear in output. */
3085
+ forbiddenStrings?: string[];
3086
+ /** Tool names the agent MUST NOT call for this scenario. */
3087
+ forbiddenTools?: string[];
3088
+ }
3089
+ interface RedTeamCase extends DatasetScenario {
3090
+ payload: RedTeamPayload;
3091
+ }
3092
+
3093
+ /**
3094
+ * `defaultProductionGate` — composes the substrate's existing safety
3095
+ * primitives (red-team / reward-hacking / canary / heldout) into a single
3096
+ * Gate.decide shape. Closes the alignment + Anthropic-SI reviewers' "safety
3097
+ * primitives are off the critical path" blocker.
3098
+ *
3099
+ * The composition is opinionated — when consumers wire `runImprovementLoop`,
3100
+ * THIS gate is the default. Consumers can still pass a custom gate to
3101
+ * override; the recommended pattern is to compose THIS gate with whatever
3102
+ * extra domain-specific gates they need (`composeGate(defaultProductionGate(...), customGate)`).
3103
+ */
3104
+
3105
+ interface DefaultProductionGateOptions {
3106
+ /** Required: scenarios held out from training; substrate compares
3107
+ * candidate-on-holdout vs baseline-on-holdout. */
3108
+ holdoutScenarios: Scenario[];
3109
+ /** Minimum held-out lift the **paired-bootstrap CI lower bound** must clear
3110
+ * to ship — NOT a point estimate. Default 0 ⇒ "confidently positive at the
3111
+ * confidence level". Interpreted in the judge's native composite scale (set
3112
+ * e.g. 2 for a 0-100 rubric to require a ≥2-point significant gain). */
3113
+ deltaThreshold?: number;
3114
+ /** Confidence level for the held-out + dimension bootstraps. Default 0.95. */
3115
+ confidence?: number;
3116
+ /** Bootstrap resamples. Default 2000. */
3117
+ bootstrapResamples?: number;
3118
+ /** Fixed bootstrap seed for a deterministic verdict. Default 1337. */
3119
+ bootstrapSeed?: number;
3120
+ /** Minimum paired holdout observations (scenarios × reps) before a
3121
+ * significance claim is allowed; below it the gate HOLDS with `few_runs`
3122
+ * rather than reading a degenerate CI. Default 3. */
3123
+ minProductiveRuns?: number;
3124
+ /** Ship statistic for the held-out significance test. Default `'mean'`
3125
+ * (tie-robust — see `heldoutSignificance`). Pass `'median'` for
3126
+ * outlier-robustness at the cost of tie-blindness. */
3127
+ heldoutStatistic?: 'mean' | 'median';
3128
+ /** Critical judge dimensions that must NOT significantly regress even when
3129
+ * the net composite rises (anti-Goodhart). The gate HOLDS if any listed
3130
+ * dimension's paired-delta CI lower bound < −`regressionTolerance`. E.g.
3131
+ * `['hallucination_free']` for a legal agent. */
3132
+ criticalDimensions?: string[];
3133
+ /** Tolerance for the per-dimension regression guard, in the dimension's
3134
+ * native scale. When omitted it auto-scales off observed magnitudes:
3135
+ * 0.05 on [0,1], 5 on 0-100. */
3136
+ regressionTolerance?: number;
3137
+ /** Total $ budget for ALL cells in this campaign — including baseline + candidate.
3138
+ * Composite verdict refuses to ship when spend exceeded budget. */
3139
+ budgetUsd?: number;
3140
+ /** Red-team cases to probe candidate outputs against. When omitted the
3141
+ * substrate uses `DEFAULT_RED_TEAM_CORPUS`. Provide a domain-specific
3142
+ * battery for tighter coverage. */
3143
+ redTeamBattery?: RedTeamCase[];
3144
+ /** Run records (oldest-first) needed for the reward-hacking detector.
3145
+ * Substrate populates from prior production-loop generations. */
3146
+ recentRuns?: RunRecord[];
3147
+ /** When true, the gate refuses to ship if the reward-hacking detector
3148
+ * fires at the `gaming` severity. Default true. */
3149
+ blockOnRewardHackingGaming?: boolean;
3150
+ }
3151
+ /**
3152
+ * Opinionated production gate composing held-out significance, red-team, reward-hacking, and canary checks into a single `Gate.decide` decision.
3153
+ */
3154
+ declare function defaultProductionGate<TArtifact, TScenario extends Scenario>(options: DefaultProductionGateOptions): Gate<TArtifact, TScenario>;
3155
+
3156
+ /**
3157
+ * @module
3158
+ * Composable held-out promotion gate backed by paired bootstrap confidence.
3159
+ *
3160
+ * Pair by full `scenario:rep` cellId, bootstrap the paired candidate-minus-
3161
+ * baseline delta, and ship only when CI.low strictly clears the threshold with
3162
+ * at least `minProductiveRuns` paired observations.
3163
+ *
3164
+ * Use when you want held-out significance as ONE of N composed gates instead
3165
+ * of the full `defaultProductionGate` stack (which adds critical-dimension
3166
+ * regression + reward-hacking guards on top).
3167
+ */
3168
+
3169
+ interface HeldOutGateOptions<TScenario extends Scenario = Scenario> {
3170
+ scenarios: TScenario[];
3171
+ /** Effect-size threshold the CI lower bound must clear, in the judge's native
3172
+ * scale. Default 0.5. Equality holds; CI.low must be greater than this value. */
3173
+ deltaThreshold?: number;
3174
+ /** Bootstrap CI confidence. Default 0.95. */
3175
+ confidence?: number;
3176
+ /** Minimum paired holdout observations to claim significance. Default 3. */
3177
+ minProductiveRuns?: number;
3178
+ /** Bootstrap resamples. Default 2000. */
3179
+ resamples?: number;
3180
+ /** Fixed bootstrap seed for deterministic verdicts. Default 1337. */
3181
+ bootstrapSeed?: number;
3182
+ }
3183
+ /**
3184
+ * Composable held-out gate: ships only when the PAIRED bootstrap CI lower bound
3185
+ * of the candidate-minus-baseline composite delta clears `deltaThreshold`.
3186
+ */
3187
+ declare function heldOutGate<TArtifact, TScenario extends Scenario>(options: HeldOutGateOptions<TScenario>): Gate<TArtifact, TScenario>;
3188
+
713
3189
  /**
714
3190
  * @module
715
3191
  * Composable placebo / neutralization promotion gate.
@@ -747,6 +3223,274 @@ interface NeutralizationGateOptions<TScenario extends Scenario = Scenario> {
747
3223
  */
748
3224
  declare function neutralizationGate<TArtifact, TScenario extends Scenario>(options: NeutralizationGateOptions<TScenario>): Gate<TArtifact, TScenario>;
749
3225
 
3226
+ /**
3227
+ * Power preflight — "can this budget detect the effect you are hunting?"
3228
+ *
3229
+ * The failure it prevents (measured, twice): a live prompt-improvement campaign ran
3230
+ * 333 sandbox cells over 5.6 hours and produced a +0.08 holdout lift the ship gate
3231
+ * (paired bootstrap, CI.low > 0.05) could not distinguish from zero — because at
3232
+ * that holdout size and worker variance the MINIMUM DETECTABLE lift was larger than
3233
+ * any effect a prompt change plausibly produces. The budget was spent learning what
3234
+ * a 30-second calculation on the baseline cells already knew. No eval framework we
3235
+ * know of surfaces this; every underpowered improvement run everywhere ends in an
3236
+ * uninformative "hold".
3237
+ *
3238
+ * Model: the ship rule is `CI.low(paired Δ) > deltaThreshold`. Approximating the
3239
+ * bootstrap CI as normal, `CI.low ≈ effect − z·sd_Δ/√n`, so the smallest shippable
3240
+ * true effect is `MDE = deltaThreshold + z·sd_Δ/√n`. The paired-delta SD is unknown
3241
+ * before the candidate exists; we bound it by the zero-correlation case
3242
+ * `sd_Δ ≤ √2·sd_baseline` — a CONSERVATIVE (upper) MDE, which is the correct
3243
+ * direction for a warning. Pairing is per cell (`scenario:rep`), so reps multiply n.
3244
+ *
3245
+ * Standalone by design: feed it any baseline composites (a `gate:'none'` run, a
3246
+ * live-proof table) BEFORE budgeting the real search; `selfImprove` also attaches
3247
+ * it to every result and warns when the run was structurally unable to ship.
3248
+ */
3249
+ interface PowerPreflightOptions {
3250
+ /** Per-cell baseline composites on the HOLDOUT scenarios (one per scenario:rep cell). */
3251
+ baselineComposites: number[];
3252
+ /** Paired observations the budgeted comparison will produce
3253
+ * (holdout scenarios × reps). Defaults to `baselineComposites.length`. */
3254
+ pairedN?: number;
3255
+ /** The ship gate's effect-size threshold. Default 0.05 (defaultProductionGate). */
3256
+ deltaThreshold?: number;
3257
+ /** CI confidence the gate uses. Default 0.95. */
3258
+ confidence?: number;
3259
+ /** True when the holdout is scored by the SAME judge/scorer family as the gate
3260
+ * (selfImprove's default composition — one judge scores everything). Under a
3261
+ * shared channel, raising paired n reduces only the IDIOSYNCRATIC noise share;
3262
+ * systematic judge bias is untouched, so the MDE here is a lower bound and the
3263
+ * only full debiaser is an independent second scoring channel
3264
+ * (recursive-self-improvement S1c, closed form in EXP-023 P0). Default false. */
3265
+ sharedScorerChannel?: boolean;
3266
+ }
3267
+ interface PowerPreflight {
3268
+ /** Paired observations the comparison will have. */
3269
+ n: number;
3270
+ /** Baseline per-cell composite standard deviation (the variance the effect must beat). */
3271
+ sd: number;
3272
+ /** Minimum detectable lift: the smallest TRUE effect the gate could ship at this budget. */
3273
+ mde: number;
3274
+ /** Baseline holdout composite mean. */
3275
+ baselineMean: number;
3276
+ /** Headroom to a perfect 1.0 composite (the largest achievable lift on a [0,1] judge). */
3277
+ headroom: number;
3278
+ /** True when even the largest achievable effect (headroom) is below the MDE —
3279
+ * the run is structurally unable to ship regardless of proposal quality.
3280
+ * Only asserted for [0,1]-scaled judges (see `scaleAssumed`). */
3281
+ underpowered: boolean;
3282
+ /** True when composites look [0,1]-scaled; headroom/underpowered are only
3283
+ * meaningful under that convention (0-100 judges get mde/sd/n but no verdict). */
3284
+ scaleAssumed: boolean;
3285
+ deltaThreshold: number;
3286
+ confidence: number;
3287
+ /** Set when the holdout shares the gate's scoring channel: more cells cannot
3288
+ * buy back systematic judge bias — treat the MDE as a lower bound. */
3289
+ sharedChannelCaveat?: string;
3290
+ /** One actionable sentence for humans and logs. */
3291
+ recommendation: string;
3292
+ }
3293
+ /** Estimate the minimum detectable lift a paired-holdout improvement run can
3294
+ * ship at a given budget, from the baseline holdout composites — call it BEFORE
3295
+ * spending a search to learn whether the effect you are hunting is even
3296
+ * observable at this holdout size and worker variance. */
3297
+ declare function powerPreflight(opts: PowerPreflightOptions): PowerPreflight;
3298
+
3299
+ /**
3300
+ * Pareto frontier — multi-objective optimization over candidate runs.
3301
+ *
3302
+ * Lifted from ADC pareto.ts and blueprint-agent frontier.ts. When you're
3303
+ * trading off (cost, latency, quality) or (passRate, tokenBudget,
3304
+ * ttfb), you rarely have a single "winner" — you have a set of
3305
+ * non-dominated candidates. This module exposes:
3306
+ *
3307
+ * - `paretoFrontier`: filter a set of candidates to the non-dominated ones
3308
+ * - `dominates`: does A dominate B across all objectives?
3309
+ *
3310
+ * Each objective is declared with a direction: 'maximize' (higher=better)
3311
+ * or 'minimize' (lower=better). Candidates are any object; pass an
3312
+ * `objective(candidate)` accessor.
3313
+ */
3314
+ type Direction = 'maximize' | 'minimize';
3315
+
3316
+ /**
3317
+ * Promotion policy over the evidence VECTOR — the substrate's answer to "never
3318
+ * collapse the multi-objective promotion decision into one scalar." A
3319
+ * `defaultProductionGate` is one opinionated composition; this module factors
3320
+ * the decision into two reusable pieces so MANY policies can compete over the
3321
+ * SAME evidence (the quant-desk pattern: one evidence bus, plural strategies):
3322
+ *
3323
+ * buildEvidenceVector(ctx, objectives, opts) -> EvidenceVector // the bus
3324
+ * PromotionPolicy = (ev: EvidenceVector) => GateResult // a strategy
3325
+ * paretoPolicy(ev) // the default strategy
3326
+ * paretoSignificanceGate(options): Gate // bus + policy as a Gate
3327
+ *
3328
+ * The Pareto policy is SYMMETRIC multi-objective: every objective is BOTH a
3329
+ * potential gain source AND a safety floor (unlike `defaultProductionGate`,
3330
+ * where only `composite` can win and `criticalDimensions` are pure floors). A
3331
+ * candidate ships iff it weakly DOMINATES the baseline at the confidence level —
3332
+ * no objective credibly worse (CI floor breach) AND at least one objective
3333
+ * credibly better (CI gain). Insufficient evidence on ANY axis -> need_more_work
3334
+ * (NOT folded into hold: "gather more reps" and "reject" are different actions).
3335
+ *
3336
+ * Cost/latency are NOT CI axes here — `GateContext` carries only an aggregate
3337
+ * per-side cost, no per-cell observation vector to bootstrap. Treat them as hard
3338
+ * constraints (compose with a budget gate via `composeGate`), not faked CIs.
3339
+ */
3340
+
3341
+ /** Where an objective's per-cell scalar comes from. `composite` reads the
3342
+ * judge's composite; `dimension` reads a named per-dimension score. */
3343
+ type ObjectiveSource = {
3344
+ kind: 'composite';
3345
+ } | {
3346
+ kind: 'dimension';
3347
+ dimension: string;
3348
+ };
3349
+ interface PromotionObjective {
3350
+ /** Stable label used in reports + `contributingGates`. */
3351
+ name: string;
3352
+ source: ObjectiveSource;
3353
+ /** 'maximize' (quality dims) or 'minimize' (error/risk/length dims). Orients
3354
+ * the paired delta so a positive bootstrap always means "candidate better". */
3355
+ direction: Direction;
3356
+ /** The good-direction paired-delta CI lower bound must EXCEED this to count
3357
+ * as a significant gain on this axis. Interpreted in the judge's native
3358
+ * scale. Default 0 (⇒ "confidently better"). */
3359
+ gainThreshold?: number;
3360
+ /** A floor breach (regression) is declared when the good-direction CI lower
3361
+ * bound is below −floorTolerance. When omitted it auto-scales off observed
3362
+ * magnitudes (0.05 on [0,1], 5 on 0-100), matching `dimensionRegressions`. */
3363
+ floorTolerance?: number;
3364
+ }
3365
+ /** Per-axis verdict from the good-direction paired bootstrap. */
3366
+ type AxisVerdict = 'improved' | 'regressed' | 'flat' | 'few_runs';
3367
+ interface AxisEvidence {
3368
+ name: string;
3369
+ source: ObjectiveSource;
3370
+ direction: Direction;
3371
+ /** Paired bootstrap on the GOOD-DIRECTION delta (oriented by `direction`):
3372
+ * a positive value means the candidate is better on this axis. */
3373
+ bootstrap: PairedBootstrapResult;
3374
+ /** Paired observations contributing to this axis. */
3375
+ n: number;
3376
+ gainThreshold: number;
3377
+ floorTolerance: number;
3378
+ verdict: AxisVerdict;
3379
+ }
3380
+ interface EvidenceVector {
3381
+ /** One entry per objective — NOTHING averaged across axes. */
3382
+ axes: AxisEvidence[];
3383
+ /** Smallest paired n across axes that produced observations — the binding
3384
+ * evidence-sufficiency constraint. 0 when no axis produced observations. */
3385
+ minN: number;
3386
+ /** Aggregate per-side cost from the gate context (a constraint input, not a
3387
+ * CI axis — see the module header). */
3388
+ cost: {
3389
+ candidate: number;
3390
+ baseline: number;
3391
+ };
3392
+ }
3393
+ /** A promotion strategy: a pure function from the evidence vector to a verdict.
3394
+ * Many policies can run over the same `EvidenceVector` and disagree — that's
3395
+ * the point (competing strategies, shared evidence). */
3396
+ type PromotionPolicy = (ev: EvidenceVector) => GateResult;
3397
+ interface BuildEvidenceVectorOptions {
3398
+ /** Minimum paired observations before an axis can claim significance; below
3399
+ * it the axis is `few_runs`. Default 3. */
3400
+ minProductiveRuns?: number;
3401
+ /** Confidence level for every axis bootstrap. Default 0.95. */
3402
+ confidence?: number;
3403
+ /** Bootstrap resamples. Default 2000. */
3404
+ resamples?: number;
3405
+ /** Fixed bootstrap seed for a deterministic, reproducible verdict. Default 1337. */
3406
+ seed?: number;
3407
+ }
3408
+ /**
3409
+ * The Evidence Bus. For each objective, pair candidate vs baseline by full
3410
+ * cellId and bootstrap a CI on the good-direction paired delta. Reuses the
3411
+ * exact `pairHoldout` + `pairedBootstrap` machinery the held-out gate uses, so
3412
+ * a single source of truth governs pairing granularity + scale handling.
3413
+ */
3414
+ declare function buildEvidenceVector<TArtifact, TScenario extends Scenario>(ctx: GateContext<TArtifact, TScenario>, objectives: PromotionObjective[], opts?: BuildEvidenceVectorOptions): EvidenceVector;
3415
+ /**
3416
+ * The default strategy: symmetric multi-objective Pareto significance. Ship iff
3417
+ * the candidate weakly dominates the baseline at the confidence level — no axis
3418
+ * credibly worse AND ≥1 axis credibly better. Floor breach on any axis → hold
3419
+ * (anti-Goodhart, dominates everything). Insufficient evidence on any axis →
3420
+ * need_more_work. Statistically equivalent → hold (never ship noise).
3421
+ */
3422
+ declare const paretoPolicy: PromotionPolicy;
3423
+ interface ParetoSignificanceGateOptions extends BuildEvidenceVectorOptions {
3424
+ /** The objective vector. Every axis is both a gain source and a safety floor. */
3425
+ objectives: PromotionObjective[];
3426
+ /** Strategy applied to the evidence vector. Default `paretoPolicy`. Override
3427
+ * to run a stricter/looser strategy over the SAME bus (competing policies). */
3428
+ policy?: PromotionPolicy;
3429
+ /** Override the gate name in reports. */
3430
+ name?: string;
3431
+ }
3432
+ /**
3433
+ * Wrap the bus + a policy as a `Gate`. Plugs into the existing
3434
+ * `runImprovementLoop({ gate })` slot and composes via `composeGate`; default
3435
+ * loop behavior is unchanged because consumers opt in by passing this gate.
3436
+ */
3437
+ declare function paretoSignificanceGate<TArtifact = unknown, TScenario extends Scenario = Scenario>(options: ParetoSignificanceGateOptions): Gate<TArtifact, TScenario>;
3438
+
3439
+ /**
3440
+ * Pre-registered hypotheses — declare what you're testing BEFORE the
3441
+ * run, check it AFTER. Prevents p-hacking, optional stopping, and the
3442
+ * "we ran until it looked good" failure mode.
3443
+ *
3444
+ * Manifest is a plain JSON-friendly object. Sign it with a content hash
3445
+ * + timestamp; the registered record becomes immutable. Post-run,
3446
+ * evaluate the manifest against observed results — the library refuses
3447
+ * to let you re-interpret a different metric as the declared one.
3448
+ */
3449
+ interface HypothesisManifest {
3450
+ id: string;
3451
+ /** Human prose — goes into the audit trail. */
3452
+ hypothesis: string;
3453
+ /** Metric the hypothesis claims to move. */
3454
+ metric: string;
3455
+ /** 'increase' = candidate should score higher than baseline; 'decrease' = lower. */
3456
+ direction: 'increase' | 'decrease';
3457
+ /** Minimum effect size to count (same units as the metric). */
3458
+ minEffect: number;
3459
+ /** Alpha threshold. */
3460
+ alpha: number;
3461
+ /** Target statistical power at which sample size was pre-computed. */
3462
+ power: number;
3463
+ /** Declared N per arm before running. */
3464
+ preRegisteredN: number;
3465
+ /** ISO8601 timestamp the manifest was registered. */
3466
+ registeredAt: string;
3467
+ /** Optional identifiers to tie into the trace corpus. */
3468
+ baselineLabel?: string;
3469
+ candidateLabel?: string;
3470
+ }
3471
+ /**
3472
+ * Identifier for the hashing scheme used to produce `contentHash`.
3473
+ *
3474
+ * `'sha256-content'` — sha256 hex over the canonicalized manifest with
3475
+ * the `contentHash` and `algo` fields stripped. Held as a string union
3476
+ * so future schemes can be added without breaking parsers; SignedManifest
3477
+ * values without `algo` deserialize cleanly because the field is optional.
3478
+ */
3479
+ type SignedManifestAlgo = 'sha256-content';
3480
+ interface SignedManifest extends HypothesisManifest {
3481
+ /** sha256 hex of canonicalized manifest (everything except contentHash and algo). */
3482
+ contentHash: string;
3483
+ /**
3484
+ * Algorithm string describing how `contentHash` was produced.
3485
+ *
3486
+ * Optional on the type so serialized manifests without it still parse,
3487
+ * but ALWAYS populated by {@link signManifest}. Consumers that want to
3488
+ * enforce a known algorithm should reject manifests where this field
3489
+ * is missing or unrecognized.
3490
+ */
3491
+ algo?: SignedManifestAlgo;
3492
+ }
3493
+
750
3494
  /**
751
3495
  * Anytime-valid sequential promotion gate — an e-process (betting
752
3496
  * test-martingale, see `eProcess` in `statistics.ts`) over paired
@@ -1299,6 +4043,183 @@ declare function surfaceContentHash(surface: MutableSurface): `sha256:${string}`
1299
4043
  /** Short loop key derived from the same content identity as provenance. */
1300
4044
  declare function surfaceHash(surface: MutableSurface): string;
1301
4045
 
4046
+ /**
4047
+ * `runOptimization` — the improvement loop body. Runs N generations: the
4048
+ * `SurfaceProposer` proposes K candidate surfaces per generation, each
4049
+ * candidate runs a campaign (the measurement), and only a candidate that beats
4050
+ * the single global incumbent becomes the next generation's parent.
4051
+ * Proposer-agnostic — the same loop runs an evolutionary population mutator
4052
+ * (`evolutionaryProposer`) or any reflective / agentic proposer; they differ
4053
+ * only in how `propose()` picks candidates.
4054
+ *
4055
+ * This is `runLoop`'s shape (plan → measure → decide) specialized to surface
4056
+ * improvement: `proposer.propose` = plan, `runCampaign` = the measurement
4057
+ * (which runs the worker behind `dispatch`), the mean-composite ranking = the
4058
+ * validator, `proposer.decide` = the stop check.
4059
+ *
4060
+ * The gated-promotion shell (`runImprovementLoop`) wraps this with a holdout
4061
+ * re-score + release gate + optional PR.
4062
+ */
4063
+
4064
+ interface RunOptimizationBaseOptions<TScenario extends Scenario, TArtifact> extends Omit<RunCampaignOptions<TScenario, TArtifact>, 'dispatch'> {
4065
+ /** Initial mutable surface (typically system prompt or addendum). */
4066
+ baselineSurface: MutableSurface;
4067
+ /** Dispatcher that takes the CURRENT surface + scenario → artifact. */
4068
+ dispatchWithSurface: (surface: MutableSurface, scenario: TScenario, ctx: Parameters<RunCampaignOptions<TScenario, TArtifact>['dispatch']>[1]) => Promise<TArtifact>;
4069
+ /** The candidate-generation strategy. Wrap a population `Mutator` via
4070
+ * `evolutionaryProposer({ mutator })`, or pass any reflective / agentic
4071
+ * proposer that implements `SurfaceProposer`. */
4072
+ proposer: SurfaceProposer;
4073
+ populationSize: number;
4074
+ maxGenerations: number;
4075
+ /** @deprecated The loop has one global incumbent and can promote only the
4076
+ * single candidate that beats it. Retained for source compatibility. */
4077
+ promoteTopK?: number;
4078
+ /** DEPTH knob forwarded to the proposer's `propose()` — max iterations the
4079
+ * agentic generator may take per candidate. */
4080
+ maxImprovementShots?: number;
4081
+ /** Optional analysis report forwarded to `propose()`. Opaque here; the
4082
+ * proposer types it. */
4083
+ report?: unknown;
4084
+ /** Structured findings forwarded to `propose()` as `ctx.findings`. A
4085
+ * findings producer emits these from the
4086
+ * generation's traces; findings-grounded proposers consume them. Opaque here;
4087
+ * the proposer types its `TFindings`. Empty when no producer is wired. */
4088
+ findings?: unknown[];
4089
+ /** Per-generation findings producer. Runs once on the BASELINE campaign
4090
+ * (as `generation: -1`, the baseline convention) before generation 0
4091
+ * proposes — so even a single-generation run proposes with trace context —
4092
+ * and then after each generation's candidates are scored with that
4093
+ * generation's results; whatever it returns REPLACES `ctx.findings` for the
4094
+ * NEXT `propose()`, so the diagnosis is refreshed each round instead
4095
+ * of being a static one-shot. Generic by design: the substrate does not
4096
+ * import an analyst — the consumer plugs its trace-analyst registry / HALO
4097
+ * here (reading the per-candidate `runDir` traces). When absent, findings
4098
+ * stay the static `opts.findings`. */
4099
+ analyzeGeneration?: (input: {
4100
+ generation: number;
4101
+ runDir: string;
4102
+ candidates: Array<{
4103
+ surfaceHash: string;
4104
+ campaign: CampaignResult<TArtifact, TScenario>;
4105
+ composite: number;
4106
+ }>;
4107
+ history: GenerationRecord[];
4108
+ /** Shared run spend account and receipt attribution phase. */
4109
+ costLedger?: CostLedger;
4110
+ costPhase?: string;
4111
+ }) => Promise<unknown[]>;
4112
+ }
4113
+ type RunOptimizationOptions<TScenario extends Scenario, TArtifact> = RunOptimizationBaseOptions<TScenario, TArtifact>;
4114
+ interface RunOptimizationResult<TArtifact, TScenario extends Scenario> {
4115
+ generations: Array<{
4116
+ record: GenerationRecord;
4117
+ surfaces: Array<{
4118
+ surfaceHash: string;
4119
+ surface: MutableSurface;
4120
+ campaign: CampaignResult<TArtifact, TScenario>;
4121
+ }>;
4122
+ }>;
4123
+ winnerSurface: MutableSurface;
4124
+ winnerSurfaceHash: string;
4125
+ /** Proposer label for the promoted surface. Present when the winning
4126
+ * candidate came from a `ProposedCandidate` (a reflective proposer);
4127
+ * absent when the winner is the baseline or a bare-surface mutator. */
4128
+ winnerLabel?: string;
4129
+ /** Proposer rationale for the promoted surface — the "because Z" that
4130
+ * motivated the winning change. Survives to `SelfImproveResult` and the
4131
+ * emitted provenance record. Absent when the winner is the baseline. */
4132
+ winnerRationale?: string;
4133
+ baselineCampaign: CampaignResult<TArtifact, TScenario>;
4134
+ /** Run-wide spend, including agents, proposers, analysts, and judges. */
4135
+ cost: CostLedgerSummary;
4136
+ /** The GEPA Pareto frontier across every scored surface (baseline + all
4137
+ * generations) by per-scenario objective vector — the non-dominated set.
4138
+ * Each generation's `propose()` received the frontier-so-far as
4139
+ * `ctx.paretoParents`; this is the final frontier. A surface here that is
4140
+ * NOT the winner is uniquely best on some scenario the winner loses on. */
4141
+ paretoFrontier: ParetoParent[];
4142
+ }
4143
+ /**
4144
+ * Improvement loop body: N generations of propose → campaign → rank, maintaining a Pareto frontier and one global incumbent across generations.
4145
+ */
4146
+ declare function runOptimization<TScenario extends Scenario, TArtifact>(opts: RunOptimizationOptions<TScenario, TArtifact>): Promise<RunOptimizationResult<TArtifact, TScenario>>;
4147
+
4148
+ /**
4149
+ * `runImprovementLoop` — the gated-promotion shell around the improvement
4150
+ * loop body (`runOptimization`). Proposes candidate surfaces via the
4151
+ * `SurfaceProposer`, re-scores the winner against the baseline on a
4152
+ * holdout set, runs the release gate, and optionally opens a PR.
4153
+ *
4154
+ * Role vocabulary (see docs/design/loop-taxonomy.md):
4155
+ * - PROPOSER = the `SurfaceProposer` (evolutionary GEPA mutator OR
4156
+ * reflective analyst). Proposes candidate SURFACES — the
4157
+ * worker's system prompt / tool config — NOT conversation
4158
+ * turns.
4159
+ * - MEASUREMENT= `runCampaign`. Scores one surface by running the worker
4160
+ * (via `dispatch`) over scenarios and judging the output.
4161
+ * - WORKER = the agent harness in the sandbox, invoked behind the
4162
+ * topology-opaque `dispatch` seam — never referenced here.
4163
+ *
4164
+ * Distinct from `runLoop` in `@tangle-network/agent-runtime`, which is the
4165
+ * INNER conversation loop (execution driver ↔ workers in a sandbox). `runImprovementLoop`
4166
+ * is the OUTER loop: it improves the surface that those workers run.
4167
+ *
4168
+ * Hard-refuses unsafe configurations:
4169
+ * - `tracing: 'off'` when a proposer is wired (improvement is unattributable)
4170
+ * - `autoOnPromote: 'config'` — DEFERRED to Pass B; v0.40 only ships
4171
+ * `'pr'` and `'none'`.
4172
+ */
4173
+
4174
+ type RunImprovementLoopOptions<TScenario extends Scenario, TArtifact> = RunOptimizationOptions<TScenario, TArtifact> & {
4175
+ /** Holdout scenarios kept OUT of the training optimization pool — used
4176
+ * ONLY to score baseline vs winner for the gate. */
4177
+ holdoutScenarios: TScenario[];
4178
+ /** Promotion gate. Substrate strongly recommends `defaultProductionGate`
4179
+ * for production wiring (composes red-team / reward-hacking / canary /
4180
+ * heldout). */
4181
+ gate: Gate<TArtifact, TScenario>;
4182
+ /** What to do when the gate ships:
4183
+ * - `'pr'`: open a PR via `openAutoPr`
4184
+ * - `'none'`: just report — caller decides what to do with the winner
4185
+ * v0.40 does NOT support `'config'` (live-runtime self-mutation) —
4186
+ * deferred to Pass B behind safety stack. */
4187
+ autoOnPromote: 'pr' | 'none';
4188
+ /** GH owner / repo for the auto-PR. Required when autoOnPromote === 'pr'. */
4189
+ ghOwner?: string;
4190
+ ghRepo?: string;
4191
+ /** Optional render override — substrate writes a diff-shaped surface; pass
4192
+ * a function to format the promoted surface differently. */
4193
+ renderPromotedDiff?: (winnerSurface: MutableSurface, baselineSurface: MutableSurface) => string;
4194
+ /** Placebo control. When supplied AND the winner differs from baseline, the
4195
+ * loop scores a THIRD holdout arm: the winner surface with its content
4196
+ * footprint-matched-blanked by this function (typically via `neutralizeText`).
4197
+ * Its scores are exposed to the gate as `ctx.neutralizedJudgeScores`, letting
4198
+ * a `neutralizationGate` reject a win whose lift survives blanking the content
4199
+ * (decorative — driven by footprint, not content). Costs one extra holdout
4200
+ * campaign; omit to skip. Return a byte/layout-matched blank of the winner. */
4201
+ neutralize?: (winnerSurface: MutableSurface, baselineSurface: MutableSurface) => MutableSurface;
4202
+ };
4203
+ interface RunImprovementLoopResult<TArtifact, TScenario extends Scenario> extends RunOptimizationResult<TArtifact, TScenario> {
4204
+ baselineOnHoldout: CampaignResult<TArtifact, TScenario>;
4205
+ winnerOnHoldout: CampaignResult<TArtifact, TScenario>;
4206
+ gateResult: Awaited<ReturnType<Gate<TArtifact, TScenario>['decide']>>;
4207
+ /** Unified baseline→winner surface diff. Computed UNCONDITIONALLY (not only
4208
+ * when `autoOnPromote === 'pr'`) so the diff that the gate decided on is
4209
+ * always present on the result + in the emitted provenance record. Empty
4210
+ * string when winner == baseline (no change to diff). */
4211
+ promotedDiff: string;
4212
+ prResult?: ReturnType<typeof openAutoPr>;
4213
+ }
4214
+ /**
4215
+ * Gated-promotion shell over `runOptimization`: scores the winner against the baseline on a holdout set, runs the release gate, and optionally opens a PR.
4216
+ */
4217
+ declare function runImprovementLoop<TScenario extends Scenario, TArtifact>(opts: RunImprovementLoopOptions<TScenario, TArtifact>): Promise<RunImprovementLoopResult<TArtifact, TScenario>>;
4218
+ /**
4219
+ * Default surface diff renderer: produces a unified baseline/winner text diff for prompt surfaces or a worktree-ref summary for code surfaces.
4220
+ */
4221
+ declare function defaultRenderDiff(winnerSurface: MutableSurface, baselineSurface: MutableSurface): string;
4222
+
1302
4223
  /**
1303
4224
  * `compareProposers` — a head-to-head lift benchmark across surface proposers
1304
4225
  * on ONE corpus. This is the forcing function: optimizer quality (GEPA
@@ -1413,39 +4334,297 @@ interface OptimizerEntryConfig<TScenario extends Scenario, TArtifact> {
1413
4334
  /** Optional analysis report forwarded to `propose()` as `ctx.report`. */
1414
4335
  report?: unknown;
1415
4336
  }
1416
- /** GEPA, reflection-only (single-parent, no Pareto combine). */
1417
- declare function gepaReflectionEntry<TScenario extends Scenario, TArtifact>(config: OptimizerEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
1418
- /** GEPA with the Pareto frontier + combine-complementary-lessons. */
1419
- declare function gepaParetoEntry<TScenario extends Scenario, TArtifact>(config: OptimizerEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
1420
- /** SkillOpt patch-mode hill-climb. Runs findings-BLIND: `runSkillOpt` owns its
1421
- * own epoch acceptance/budget loop and does not thread `analyzeGeneration`, so
1422
- * `config.findings` is intentionally NOT forwarded here. In a findings-fed
1423
- * comparison this entry is the blind control — do not read its result as
1424
- * findings-fed. (Threading findings into the SkillOpt epoch loop is a separate
1425
- * refactor, deferred not faked.) */
1426
- declare function skillOptEntry<TScenario extends Scenario, TArtifact>(config: OptimizerEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
1427
- /** FAPO reviewed-escalation policy. This is an orchestration layer over
1428
- * level-specific proposers, not a new mutation operator:
1429
- * prompt -> parameter -> structural, with scope + reviewer + plateau rules in
1430
- * `fapoProposer`. The prompt proposer defaults to GEPA+Pareto because that is
1431
- * the package's strongest prompt-tier proposer; parameter/structural proposers
1432
- * are opt-in so we do not fake code-generation inside agent-eval. */
1433
- interface FapoEntryConfig<TScenario extends Scenario, TArtifact> extends OptimizerEntryConfig<TScenario, TArtifact> {
1434
- /** Override the prompt-level proposer. Default: `gepaProposer({ combineParents: true })`. */
1435
- promptProposer?: SurfaceProposer;
1436
- /** Parameter/config-level proposer. If omitted, `parameterCandidates` builds one. */
1437
- parameterProposer?: SurfaceProposer;
1438
- /** Structural/code-level proposer, typically supplied by agent-runtime. */
1439
- structuralProposer?: SurfaceProposer;
1440
- /** Convenience: build a `parameterSweepProposer` from these candidates. */
1441
- parameterCandidates?: readonly ParameterCandidate[];
1442
- /** FAPO policy knobs: scope, reviewer, plateau thresholds. */
1443
- fapo?: Omit<FapoProposerOptions, 'proposers' | 'promptProposer' | 'parameterProposer' | 'structuralProposer'>;
4337
+ /** GEPA, reflection-only (single-parent, no Pareto combine). */
4338
+ declare function gepaReflectionEntry<TScenario extends Scenario, TArtifact>(config: OptimizerEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
4339
+ /** GEPA with the Pareto frontier + combine-complementary-lessons. */
4340
+ declare function gepaParetoEntry<TScenario extends Scenario, TArtifact>(config: OptimizerEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
4341
+ /** SkillOpt patch-mode hill-climb. Runs findings-BLIND: `runSkillOpt` owns its
4342
+ * own epoch acceptance/budget loop and does not thread `analyzeGeneration`, so
4343
+ * `config.findings` is intentionally NOT forwarded here. In a findings-fed
4344
+ * comparison this entry is the blind control — do not read its result as
4345
+ * findings-fed. (Threading findings into the SkillOpt epoch loop is a separate
4346
+ * refactor, deferred not faked.) */
4347
+ declare function skillOptEntry<TScenario extends Scenario, TArtifact>(config: OptimizerEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
4348
+ /** FAPO reviewed-escalation policy. This is an orchestration layer over
4349
+ * level-specific proposers, not a new mutation operator:
4350
+ * prompt -> parameter -> structural, with scope + reviewer + plateau rules in
4351
+ * `fapoProposer`. The prompt proposer defaults to GEPA+Pareto because that is
4352
+ * the package's strongest prompt-tier proposer; parameter/structural proposers
4353
+ * are opt-in so we do not fake code-generation inside agent-eval. */
4354
+ interface FapoEntryConfig<TScenario extends Scenario, TArtifact> extends OptimizerEntryConfig<TScenario, TArtifact> {
4355
+ /** Override the prompt-level proposer. Default: `gepaProposer({ combineParents: true })`. */
4356
+ promptProposer?: SurfaceProposer;
4357
+ /** Parameter/config-level proposer. If omitted, `parameterCandidates` builds one. */
4358
+ parameterProposer?: SurfaceProposer;
4359
+ /** Structural/code-level proposer, typically supplied by agent-runtime. */
4360
+ structuralProposer?: SurfaceProposer;
4361
+ /** Convenience: build a `parameterSweepProposer` from these candidates. */
4362
+ parameterCandidates?: readonly ParameterCandidate[];
4363
+ /** FAPO policy knobs: scope, reviewer, plateau thresholds. */
4364
+ fapo?: Omit<FapoProposerOptions, 'proposers' | 'promptProposer' | 'parameterProposer' | 'structuralProposer'>;
4365
+ }
4366
+ /**
4367
+ * Build a `ProposerEntry` that runs the full FAPO escalation policy (prompt → parameter → structural) as a single comparable optimizer entry.
4368
+ */
4369
+ declare function fapoEscalationEntry<TScenario extends Scenario, TArtifact>(config: FapoEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
4370
+
4371
+ /**
4372
+ * Artifact validators.
4373
+ *
4374
+ * Generic "score a produced artifact" primitive. Tax uses it for PDF form
4375
+ * correctness, research for sourced briefs, browser for task assertions, coding
4376
+ * for social posts. One interface, many validators; all plug into
4377
+ * `BenchmarkRunner` the same way.
4378
+ *
4379
+ * A validator receives an `Artifact` (file on disk, JSON blob, text, binary)
4380
+ * plus a `ValidationContext` (scenario id, the turns that produced it) and
4381
+ * returns a `ValidationResult` with pass/fail + 0..1 score + structured
4382
+ * issues.
4383
+ */
4384
+ interface Artifact {
4385
+ /** Logical kind — validators type-guard on this */
4386
+ kind: 'file' | 'json' | 'text' | 'binary' | string;
4387
+ /** Filesystem-style path, optional */
4388
+ path?: string;
4389
+ /** String content for text/json/file kinds */
4390
+ content?: string;
4391
+ /** Binary content (if kind === 'binary') */
4392
+ bytes?: Uint8Array;
4393
+ /** Caller-supplied metadata (mimeType, sha256, size, etc.) */
4394
+ metadata?: Record<string, unknown>;
4395
+ }
4396
+
4397
+ /**
4398
+ * Validator-output verdict — substrate primitive for "did this output pass,
4399
+ * and how well?"
4400
+ *
4401
+ * Used by:
4402
+ * - `@tangle-network/agent-eval/matrix` — verdict per cell in the cartesian.
4403
+ * - `@tangle-network/agent-runtime` — Validator<Output, Verdict = DefaultVerdict>.
4404
+ * Runtime keeps `Validator` because it's coupled to runtime-shaped
4405
+ * `ValidationCtx` (iteration, signal, traceEmitter); the verdict TYPE
4406
+ * itself is a substrate concept and lives here.
4407
+ *
4408
+ * Repo layering: agent-eval is the substrate (no upward deps). Both
4409
+ * agent-runtime and agent-knowledge consume this type FROM agent-eval —
4410
+ * never the other way around. See CLAUDE.md "Repo layering" for the rule.
4411
+ */
4412
+ /**
4413
+ * Minimal verdict shape — `valid` + `score` are required; `scores` +
4414
+ * `notes` are optional surface. Validators that need richer shapes
4415
+ * parameterise `Validator<Output, MyVerdict>` with their own type.
4416
+ *
4417
+ * Need structured extras? Extend DefaultVerdict with typed fields — never
4418
+ * serialize extras into `notes`.
4419
+ */
4420
+ interface DefaultVerdict {
4421
+ /** Whether the output meets the validator's pass criteria. */
4422
+ valid: boolean;
4423
+ /** Aggregate score in [0, 1]. Drivers use this for winner selection. */
4424
+ score: number;
4425
+ /** Per-dimension scores. Free-form; weighted into `score` by the validator. */
4426
+ scores?: Record<string, number>;
4427
+ /** Human-readable rationale; surfaces in trace + final-result `winner.verdict`. */
4428
+ notes?: string;
4429
+ }
4430
+
4431
+ /**
4432
+ * Completion verifier — the task-completion oracle.
4433
+ *
4434
+ * Answers the only eval question that is not a proxy: did the agent actually
4435
+ * COMPLETE the task — produce every required deliverable, persisted and
4436
+ * correct — rather than describe what should be done. A fluent transcript
4437
+ * that never produces the artifact scores zero here.
4438
+ *
4439
+ * Per requirement, a two-stage check:
4440
+ * 1. Structural — a produced item (vault artifact / approved proposal /
4441
+ * tool call) of the right kind is matched against the requirement and
4442
+ * carries non-empty content. Deterministic; no LLM.
4443
+ * 2. Correctness — only if structurally present AND the matched item
4444
+ * carries content, one targeted check decides whether that item
4445
+ * actually fulfils the requirement. A hallucinated artifact fails here;
4446
+ * an absent one already failed stage 1.
4447
+ *
4448
+ * `completionRate` is satisfied / MEASURABLE requirements (unmeasured rows —
4449
+ * checker failures — are excluded from the denominator, never scored as
4450
+ * zeros). Quality dimensions are meaningless on an incomplete task — callers
4451
+ * gate on `fullyComplete` / `completionRate` before scoring quality.
4452
+ */
4453
+
4454
+ /** What kind of produced state can satisfy a requirement structurally. */
4455
+ type SatisfiedBy = 'artifact' | 'proposal' | 'tool-call' | 'any';
4456
+ interface CompletionRequirement {
4457
+ /** Stable id from the task gold (e.g. a persona's `expected_requirements[].req_id`). */
4458
+ reqId: string;
4459
+ /** Human-readable description of the required deliverable. */
4460
+ title: string;
4461
+ /** Optional kind/category hint, matched against a produced item's kind. */
4462
+ category?: string;
4463
+ /** What produced state satisfies this requirement. Defaults to 'any'. */
4464
+ satisfiedBy?: SatisfiedBy;
4465
+ }
4466
+ interface ProducedProposal {
4467
+ id: string;
4468
+ title: string;
4469
+ status: 'pending' | 'approved' | 'rejected';
4470
+ /** Optional persisted body — when present, enables a correctness check. */
4471
+ content?: string;
4472
+ }
4473
+ /** Everything observable about what a run actually produced. */
4474
+ interface ProducedState {
4475
+ /** Persisted vault artifacts. Reuses the shared `Artifact` shape. */
4476
+ artifacts: Artifact[];
4477
+ /** Proposals / filings the agent created. */
4478
+ proposals: ProducedProposal[];
4479
+ /** Names of tools the agent invoked. */
4480
+ toolCalls: string[];
4481
+ }
4482
+ interface RequirementCheck {
4483
+ reqId: string;
4484
+ title: string;
4485
+ /** A produced item of the right kind matched the requirement, non-empty. */
4486
+ structurallyPresent: boolean;
4487
+ /**
4488
+ * Whether the matched item actually fulfils the requirement. `null` when
4489
+ * not structurally present, when the matched item carries no content
4490
+ * to assess, or when the correctness check itself failed (`unmeasured`).
4491
+ */
4492
+ correct: boolean | null;
4493
+ /** structurallyPresent && !unmeasured && correct !== false. */
4494
+ satisfied: boolean;
4495
+ /**
4496
+ * Set when the correctness check itself errored (LLM call failure or an
4497
+ * unparseable response after retry). The requirement's fulfilment is
4498
+ * UNKNOWN — `correct` stays null, `satisfied` is false, and
4499
+ * `completionVerdict` excludes the row from `completionRate`'s
4500
+ * denominator. Never folded into a zero: a synthetic zero is
4501
+ * indistinguishable from a real failure (see `JudgeParseError`).
4502
+ */
4503
+ unmeasured?: true;
4504
+ /** Why the correctness check could not be measured (present iff `unmeasured`). */
4505
+ unmeasuredReason?: string;
4506
+ /** Human-readable evidence for the verdict. */
4507
+ evidence: string[];
4508
+ }
4509
+ /** Extends the substrate verdict spine: `valid` = `fullyComplete` and
4510
+ * `score` = `completionRate` — derived in `completionVerdict()`, the one
4511
+ * place those equalities hold by construction. */
4512
+ interface CompletionVerdict extends DefaultVerdict {
4513
+ taskId: string;
4514
+ requirements: RequirementCheck[];
4515
+ /** satisfied / MEASURABLE requirements (unmeasured rows leave the denominator). */
4516
+ completionRate: number;
4517
+ /** Every measurable requirement satisfied (false when anything is unmeasured). */
4518
+ fullyComplete: boolean;
4519
+ /** Requirements whose correctness check errored — reported, never scored as zero. */
4520
+ unmeasuredCount: number;
4521
+ }
4522
+ /**
4523
+ * Decides whether a produced item's content actually fulfils a requirement.
4524
+ * Injected so the structural verifier stays pure and unit-testable; the
4525
+ * production implementation is `createLlmCorrectnessChecker`.
4526
+ */
4527
+ type CorrectnessChecker = (requirement: CompletionRequirement, content: string) => Promise<{
4528
+ correct: boolean;
4529
+ reason: string;
4530
+ }>;
4531
+
4532
+ /**
4533
+ * Produced-state extraction — normalize a run's runtime event stream into the
4534
+ * typed `ProducedState` the completion oracle consumes.
4535
+ *
4536
+ * `ProducedState` answers "what did the agent actually produce" — vault
4537
+ * artifacts, proposals, tool calls. The runtime emits these as a stream of
4538
+ * events; this module is the single normalization point from that stream to
4539
+ * the shape `verifyCompletion` expects.
4540
+ *
4541
+ * Input is structurally typed (`RuntimeEventLike`) so this module does not
4542
+ * depend on agent-runtime — agent-runtime's `RuntimeStreamEvent` satisfies it
4543
+ * structurally. The `content` on `ArtifactEventLike` and the whole
4544
+ * `proposal_created` variant are the runtime-side enrichments this contract
4545
+ * requires; the runtime emits them, this module consumes them.
4546
+ */
4547
+
4548
+ /** A tool the agent invoked. */
4549
+ interface ToolCallEventLike {
4550
+ type: 'tool_call';
4551
+ toolName: string;
4552
+ }
4553
+ /**
4554
+ * An artifact the agent produced. `content` is the enriched field — the
4555
+ * runtime's base `artifact` event carries only metadata; the completion
4556
+ * oracle needs the body to verify the deliverable, so the runtime emits it.
4557
+ */
4558
+ interface ArtifactEventLike {
4559
+ type: 'artifact';
4560
+ artifactId: string;
4561
+ name?: string;
4562
+ mimeType?: string;
4563
+ uri?: string;
4564
+ content?: string;
4565
+ }
4566
+ /** A proposal / filing the agent created. */
4567
+ interface ProposalEventLike {
4568
+ type: 'proposal_created';
4569
+ proposalId: string;
4570
+ title: string;
4571
+ status?: 'pending' | 'approved' | 'rejected';
4572
+ content?: string;
1444
4573
  }
1445
4574
  /**
1446
- * Build a `ProposerEntry` that runs the full FAPO escalation policy (prompt → parameter → structural) as a single comparable optimizer entry.
4575
+ * The subset of runtime stream events `extractProducedState` consumes.
4576
+ * agent-runtime's full `RuntimeStreamEvent` union satisfies this structurally;
4577
+ * the `{ type: string }` catch-all keeps the input permissive so callers can
4578
+ * pass the whole unfiltered telemetry stream — unrecognized events are skipped.
1447
4579
  */
1448
- declare function fapoEscalationEntry<TScenario extends Scenario, TArtifact>(config: FapoEntryConfig<TScenario, TArtifact>, name?: string): ProposerEntry;
4580
+ type RuntimeEventLike = ToolCallEventLike | ArtifactEventLike | ProposalEventLike | {
4581
+ type: string;
4582
+ };
4583
+
4584
+ /**
4585
+ * Backend-integrity guard: distinguish "agent failed" from "eval ran against
4586
+ * a stub / unconfigured backend." Without this guard a canonical eval can
4587
+ * silently report `0/N passed` and look like an agent-quality problem when
4588
+ * the LLM was never actually called — the failure mode we just hit running
4589
+ * the 4-vertical parallel eval (legal-sandbox-stub returned hard-coded 33-104
4590
+ * char strings; gtm/creative defaulted to a cli-bridge that wasn't running).
4591
+ *
4592
+ * The shape:
4593
+ *
4594
+ * const report = summarizeBackendIntegrity(records)
4595
+ * assertRealBackend(records) // throws BackendIntegrityError if 100% stub
4596
+ *
4597
+ * A record is "stub-mode" if its `tokenUsage.input === 0 && tokenUsage.output === 0`.
4598
+ * (`costUsd` alone is unreliable — some backends successfully call LLMs but
4599
+ * don't propagate pricing, producing real tokens with $0 cost.)
4600
+ *
4601
+ * Verdicts:
4602
+ * - `real` — at least one record has nonzero token usage
4603
+ * - `stub` — every record is stub-mode (eval ran blind)
4604
+ * - `mixed` — some records real, some stub (partial backend failure;
4605
+ * often the 429-cascade or auth-half-failed case)
4606
+ */
4607
+
4608
+ interface BackendIntegrityReport {
4609
+ /** Total records inspected. */
4610
+ totalRecords: number;
4611
+ /** Records with input=0 AND output=0 (a stub fingerprint). */
4612
+ stubRecords: number;
4613
+ /** Records with nonzero token usage (real LLM activity). */
4614
+ realRecords: number;
4615
+ /** Records where output>0 but costUsd=0 (real LLM, broken cost ledger). */
4616
+ uncostedRecords: number;
4617
+ /** Sum of input tokens across all records. */
4618
+ totalInputTokens: number;
4619
+ /** Sum of output tokens across all records. */
4620
+ totalOutputTokens: number;
4621
+ /** Sum of costUsd across all records. */
4622
+ totalCostUsd: number;
4623
+ /** Worst-case integrity verdict. */
4624
+ verdict: 'real' | 'mixed' | 'stub';
4625
+ /** Human-readable diagnosis suitable for terminal output. */
4626
+ diagnosis: string;
4627
+ }
1449
4628
 
1450
4629
  /**
1451
4630
  * `runProfileMatrix` — the missing keystone between `runAgentMatrix` and the
@@ -1712,6 +4891,22 @@ interface ScoreboardRenderOptions {
1712
4891
  */
1713
4892
  declare function renderScoreboardMarkdown(rows: readonly ScoreboardRow[], opts?: ScoreboardRenderOptions): string;
1714
4893
 
4894
+ /**
4895
+ * `runEval` — the simplest preset over `runCampaign`. No optimizer, no
4896
+ * gate, no auto-PR. Just: run scenarios through dispatch, score with
4897
+ * judges, return CampaignResult.
4898
+ *
4899
+ * The 80% case for consumers who want a scorecard, not an improvement loop.
4900
+ */
4901
+
4902
+ interface RunEvalOptions<TScenario extends Scenario, TArtifact> extends Omit<RunCampaignOptions<TScenario, TArtifact>, 'runDir'> {
4903
+ runDir: string;
4904
+ }
4905
+ /**
4906
+ * Simplest evaluation preset: run scenarios through dispatch, score with judges, and return a `CampaignResult` — no optimizer, no gate, no PR.
4907
+ */
4908
+ declare function runEval<TScenario extends Scenario, TArtifact>(opts: RunEvalOptions<TScenario, TArtifact>): Promise<CampaignResult<TArtifact, TScenario>>;
4909
+
1715
4910
  /**
1716
4911
  * `runLineageLoop` — the live adapter that wires the {@link runLineage} DAG's
1717
4912
  * two abstract seams (`step`, `merge`) to the REAL improvement machinery, so the
@@ -2173,6 +5368,119 @@ interface CompositeProposerOptions<TFindings = unknown> {
2173
5368
  * one generation pool, with per-member provenance labels. */
2174
5369
  declare function compositeProposer<TFindings = unknown>(opts: CompositeProposerOptions<TFindings>): SurfaceProposer<TFindings>;
2175
5370
 
5371
+ /**
5372
+ * `evolutionaryProposer` — adapts a stateless `Mutator` (population mutation:
5373
+ * GEPA / AxGEPA / reflective-mutation) into a `SurfaceProposer`. This is
5374
+ * the evolutionary strategy: each generation, mutate the current best surface
5375
+ * into N candidates, measure, select. No generation memory beyond the current
5376
+ * surface; the loop body handles ranking + promotion.
5377
+ *
5378
+ * The reflective alternative is agent-runtime's runtime proposer with a
5379
+ * `reflectiveGenerator` / `agenticGenerator`: it reasons over the report +
5380
+ * trace findings to propose targeted edits rather than blind mutations. Both
5381
+ * conform to `SurfaceProposer`; the improvement loop is identical either way.
5382
+ */
5383
+
5384
+ interface EvolutionaryProposerOptions<TFindings = unknown> {
5385
+ mutator: Mutator<TFindings>;
5386
+ /** External findings fed to the mutator each generation. Default: []. */
5387
+ findings?: TFindings[];
5388
+ }
5389
+ /**
5390
+ * Wrap a stateless `Mutator` (GEPA, AxGEPA, reflective-mutation) as a `SurfaceProposer` that mutates the current best surface into N candidates each generation.
5391
+ */
5392
+ declare function evolutionaryProposer<TFindings = unknown>(opts: EvolutionaryProposerOptions<TFindings>): SurfaceProposer<TFindings>;
5393
+
5394
+ /**
5395
+ * `gepaProposer` — a reflective `SurfaceProposer` for prompt-tier surfaces.
5396
+ * Each generation it reflects on the prior best candidate's per-scenario
5397
+ * scores + weakest dimensions, asks an LLM to propose targeted rewrites of
5398
+ * the current surface, and returns them as the next population.
5399
+ *
5400
+ * Maps onto the GEPA paper (Agrawal et al., arXiv:2507.19457):
5401
+ * - *Reflection*: each generation reflects on the best parent's weakest
5402
+ * dimensions + per-scenario top/bottom scores to propose targeted rewrites.
5403
+ * - *Pareto frontier*: `runOptimization` maintains the non-dominated set of
5404
+ * surfaces across generations (per-scenario objective vectors) and supplies
5405
+ * it as `ctx.paretoParents`. A surface uniquely best on one hard scenario
5406
+ * survives even when its mean composite is lower.
5407
+ * - *Combine complementary lessons*: when the frontier has >1 member, the
5408
+ * first population slot is a merge of those parents' strengths (one LLM
5409
+ * call citing each parent's winning scenarios). Toggle via `combineParents`.
5410
+ * Dominance is computed by the package-canonical `paretoFrontier` (`pareto.ts`).
5411
+ *
5412
+ * Optional `constraints` move structured-doc guards into the proposer
5413
+ * (preserve H2 section headings, cap sentence-level edits) — useful when
5414
+ * the surface IS a structured procedure like a SKILL.md / runbook /
5415
+ * judge rubric. When `constraints` is omitted, behavior is unchanged.
5416
+ *
5417
+ * The proposer is surface-agnostic — any string surface in any consumer opts
5418
+ * in by selecting it. Reuses the generic reflection primitive
5419
+ * (`buildReflectionPrompt` / `parseReflectionResponse`) and the router client.
5420
+ *
5421
+ * Earns its keep where there is real per-instance signal (which the
5422
+ * dimensional + per-scenario evidence + the `LabeledScenarioStore` flywheel
5423
+ * now provide). For thin-signal surfaces it degrades to plain reflection.
5424
+ * On generation 0 (no history) it reflects on the current surface against
5425
+ * the mutation primitives alone.
5426
+ */
5427
+
5428
+ interface GepaProposerConstraints {
5429
+ /** H2 section headings that MUST appear unchanged in every candidate.
5430
+ * When set, the proposer auto-detects current H2s if this is empty AND
5431
+ * rejects any candidate that drops or renames a preserved heading.
5432
+ * Use when the surface is a structured doc (SKILL.md, runbook,
5433
+ * sectioned system prompt, judge rubric). */
5434
+ preserveSections?: string[];
5435
+ /** Maximum sentence-level edits per candidate vs the parent surface.
5436
+ * Rejection threshold = maxSentenceEdits × 2 (counts adds + removes).
5437
+ * Inspired by SkillOpt's edit-budget as a "textual learning rate."
5438
+ * Cap prevents an LLM rewrite from overwriting useful prior rules. */
5439
+ maxSentenceEdits?: number;
5440
+ }
5441
+ interface GepaProposerOptions {
5442
+ /** Router transport (apiKey/baseUrl). */
5443
+ llm: LlmClientOptions;
5444
+ /** Model that performs the reflection. */
5445
+ model: string;
5446
+ /** Optional ledger for direct proposer use. Campaign context takes precedence. */
5447
+ costLedger?: CostLedger;
5448
+ /** What is being optimized — appears in the reflection prompt for orientation. */
5449
+ target: string;
5450
+ /** Surface-specific mutation levers offered to the model. */
5451
+ mutationPrimitives?: string[];
5452
+ /** Top/bottom scenarios surfaced as evidence each generation. Default 3. */
5453
+ evidenceK?: number;
5454
+ /** Reflection sampling temperature. Default 0.7. */
5455
+ temperature?: number;
5456
+ /** Reflection max tokens. Default 6000. */
5457
+ maxTokens?: number;
5458
+ /** Structured-doc constraints. Candidates violating any are rejected
5459
+ * post-parse and dropped from the returned population. */
5460
+ constraints?: GepaProposerConstraints;
5461
+ /** GEPA combine-complementary-lessons: when the loop supplies a Pareto
5462
+ * frontier of >1 non-dominated parents (`ctx.paretoParents`), spend one
5463
+ * slot of the population on a merge of their strengths. Default `true` —
5464
+ * this is the GEPA-faithful behavior; the merge only fires once the
5465
+ * frontier has more than one member (generation ≥ 1). Set `false` for
5466
+ * pure single-parent reflection. */
5467
+ combineParents?: boolean;
5468
+ /** Cap on how many frontier parents feed one combine prompt (highest
5469
+ * composite first), to bound prompt size. Default 4. */
5470
+ combineMaxParents?: number;
5471
+ }
5472
+ /**
5473
+ * GEPA reflective proposer: each generation reflects on the weakest scenarios and dimensions to produce targeted prompt rewrites, optionally combining Pareto-frontier parents.
5474
+ */
5475
+ declare function gepaProposer(opts: GepaProposerOptions): SurfaceProposer;
5476
+ /** Extract H2 headings (`## Foo`) from a markdown surface. Exported for
5477
+ * consumers building custom mutators that share the same invariant. */
5478
+ declare function extractH2Sections(text: string): string[];
5479
+ /** Sentence-level edit distance — count distinct add/remove ops between
5480
+ * two surfaces via a normalised line-by-line set diff. Treats trivial
5481
+ * whitespace as identical. Exported for tests + consumer-side validators. */
5482
+ declare function countSentenceEdits(baseline: string, candidate: string): number;
5483
+
2176
5484
  /**
2177
5485
  * `haloProposer` — wraps the REAL halo-engine (Inference.net's hierarchical
2178
5486
  * agentic trace analyzer, `pip install halo-engine`, repo context-labs/halo)
@@ -2487,6 +5795,113 @@ declare function selectPolicyEditAuthorRows<T extends PolicyEditAuthorScenarioRo
2487
5795
  /** Serialize once and fail before dispatch when author context exceeds its budget. */
2488
5796
  declare function assertPolicyEditAuthorContextBudget(value: unknown, maxChars: number): SerializedJsonBudget;
2489
5797
 
5798
+ /**
5799
+ * Typed Ax output for analyst findings.
5800
+ *
5801
+ * Replaces the legacy `findings:string[]` pattern (where every bullet
5802
+ * became a flat-severity `AnalystFinding`) with a structured object
5803
+ * array. Ax binds the field as `findings:json[]` so the provider emits
5804
+ * native structured output; at the kind-factory boundary we Zod-validate
5805
+ * each emitted finding so malformed rows fail loud instead of being
5806
+ * silently lifted with default severity.
5807
+ *
5808
+ * Why not `f.object().array()` directly in the signature? The Ax
5809
+ * signature string `question:string -> findings:json[]` already lets
5810
+ * the provider emit JSON arrays. A Zod boundary is required either
5811
+ * way (the provider can return any JSON), and Zod gives us a single
5812
+ * validation surface independent of which Ax version is installed.
5813
+ */
5814
+
5815
+ declare const RawAnalystFindingSchema: z.ZodObject<{
5816
+ severity: z.ZodEnum<{
5817
+ low: "low";
5818
+ high: "high";
5819
+ critical: "critical";
5820
+ medium: "medium";
5821
+ info: "info";
5822
+ }>;
5823
+ claim: z.ZodString;
5824
+ subject: z.ZodOptional<z.ZodString>;
5825
+ evidence_uri: z.ZodString;
5826
+ evidence_excerpt: z.ZodOptional<z.ZodString>;
5827
+ confidence: z.ZodNumber;
5828
+ rationale: z.ZodOptional<z.ZodString>;
5829
+ recommended_action: z.ZodOptional<z.ZodString>;
5830
+ }, z.core.$strict>;
5831
+ type RawAnalystFinding = z.infer<typeof RawAnalystFindingSchema>;
5832
+
5833
+ /**
5834
+ * Analyst-kind factory — the typed way to define trace analysts.
5835
+ *
5836
+ * A "kind" is a specialized analyst whose actor prompt, tool subset,
5837
+ * and Ax recursion config target one failure-mode lens (failure-mode
5838
+ * classification, knowledge gap discovery, knowledge poisoning, recursive
5839
+ * self-improvement, ...). Kinds emit findings in the typed `RawAnalystFinding`
5840
+ * shape via a JSON-array Ax output; the factory validates each row with
5841
+ * Zod and lifts it into `AnalystFinding[]` with no shape guessing.
5842
+ *
5843
+ * Composition rules:
5844
+ * - Each kind owns its actor description. No generic "answer this
5845
+ * question" prompt — the prompt names the failure lens.
5846
+ * - Each kind picks a narrow tool subset from `ANALYST_TOOL_GROUPS`.
5847
+ * A kind that never needs full-trace dumps can drop `viewTrace` /
5848
+ * `viewSpans` and stay cheap.
5849
+ * - Each kind declares its recursion + parallelism budget. Discovery-
5850
+ * heavy kinds (failure-mode) get higher `maxDepth`; lens kinds
5851
+ * (poisoning) usually stay at 0 since they have a tighter brief.
5852
+ *
5853
+ * Optimizer hook: kinds may declare `goldens` — labeled examples used
5854
+ * by `AxMiPRO` / `AxBootstrapFewShot` / `AxGEPA` to fit the actor
5855
+ * description programmatically. Stored on the kind, not the registry,
5856
+ * because the right metric is kind-specific.
5857
+ */
5858
+
5859
+ /**
5860
+ * Per-kind specification. The factory turns this into a regular
5861
+ * `Analyst<TraceAnalysisStore>` ready for `AnalystRegistry.register()`.
5862
+ */
5863
+ interface TraceAnalystKindSpec {
5864
+ /** Stable id. Appears in finding_id, telemetry, and registry exclusions. */
5865
+ id: string;
5866
+ /** One-sentence description shown in `registry.list()`. */
5867
+ description: string;
5868
+ /** Coarse classification stamped on every emitted finding (`failure-mode`, `knowledge-gap`, ...). */
5869
+ area: string;
5870
+ /** Bump on any breaking change to the actor prompt or output schema. */
5871
+ version: string;
5872
+ /** Actor system prompt. Must instruct the LLM to emit `findings` per the schema. */
5873
+ actorDescription: string;
5874
+ /** Responder system prompt; falls back to a minimal "format the findings" instruction. */
5875
+ responderDescription?: string;
5876
+ /** Tool functions the actor may call. Pick narrow subsets via `ANALYST_TOOL_GROUPS`. */
5877
+ buildTools: (store: TraceAnalysisStore) => AxFunction[];
5878
+ /** Recursion budget. `maxDepth: 0` disables subagents. */
5879
+ recursion?: {
5880
+ maxDepth: number;
5881
+ maxParallelSubagents?: number;
5882
+ };
5883
+ /** Actor turn cap. Default 12. */
5884
+ maxTurns?: number;
5885
+ /** Runtime char cap. Default 6000. */
5886
+ maxRuntimeChars?: number;
5887
+ /** Cost classification surfaced in `registry.list()` and budget enforcement. */
5888
+ cost: AnalystCost;
5889
+ /** Per-finding-row hook — kinds may reject / rewrite before lifting. */
5890
+ postProcess?: (row: RawAnalystFinding, ctx: AnalystContext) => RawAnalystFinding | null;
5891
+ /** Optional optimizer hook — populated when a kind wants to fit its prompt against labeled examples. */
5892
+ goldens?: TraceAnalystGolden[];
5893
+ }
5894
+ /**
5895
+ * One labeled example consumed by Ax optimizers (MIPRO / GEPA / Bootstrap).
5896
+ * Each input is the same `{question}` an analyst would receive; `expected`
5897
+ * is the ground-truth finding set a fitted prompt should produce on this
5898
+ * input. Metric: kind-specific (default: F1 on `finding_id` overlap).
5899
+ */
5900
+ interface TraceAnalystGolden {
5901
+ question: string;
5902
+ expected: ReadonlyArray<Omit<RawAnalystFinding, 'confidence'>>;
5903
+ }
5904
+
2490
5905
  /**
2491
5906
  * `traceAnalystProposer` — wraps agent-eval's OWN trace-analyst engine
2492
5907
  * (`AnalystRegistry` over the agentic OTLP reader) as a `SurfaceProposer`.
@@ -2537,6 +5952,793 @@ interface TraceAnalystProposerOptions {
2537
5952
  /** Wrap agent-eval's trace-analyst registry as a SurfaceProposer (prompt-tier). */
2538
5953
  declare function traceAnalystProposer(opts: TraceAnalystProposerOptions): SurfaceProposer;
2539
5954
 
5955
+ /**
5956
+ * Reporting helpers — production summaries and paper-quality figures — sit alongside `reporter.ts` rather
5957
+ * than replacing it.
5958
+ *
5959
+ * Three artefacts:
5960
+ *
5961
+ * - `summaryTable` Markdown table of per-candidate means,
5962
+ * 95% bootstrap CIs, BH-adjusted Wilcoxon
5963
+ * p-values, and Cohen's d versus a
5964
+ * comparator candidate.
5965
+ * - `paretoChart` Abstract spec for a cost vs quality
5966
+ * scatter, with gate decisions overlaid.
5967
+ * Returns numbers + labels — caller
5968
+ * chooses the plotting library.
5969
+ * - `gainHistogram`
5970
+ * Per-item paired holdout deltas as a
5971
+ * histogram spec (bins + counts + median +
5972
+ * CI). Same "data, not images" contract.
5973
+ *
5974
+ * The figure types are PlotSpecs — JSON-friendly, library-agnostic.
5975
+ * They aren't React components and they aren't PNGs; they are
5976
+ * what you'd hand to vega-lite, plotly, matplotlib, or your own
5977
+ * Canvas renderer to draw the actual figure.
5978
+ */
5979
+
5980
+ interface ParetoPoint {
5981
+ candidateId: string;
5982
+ /** Mean USD cost per run on the chosen split. */
5983
+ cost: number;
5984
+ /** Mean score on the chosen split. */
5985
+ quality: number;
5986
+ /** Number of runs that informed this point. */
5987
+ n: number;
5988
+ /** Whether this candidate is on the Pareto frontier — high
5989
+ * quality, low cost, no dominator. */
5990
+ onFrontier: boolean;
5991
+ /** Optional gate verdict for this candidate, if a `GateDecision`
5992
+ * for it was passed in. */
5993
+ gate?: 'promote' | 'reject_few_runs' | 'reject_negative_delta' | 'reject_overfit_gap' | null;
5994
+ }
5995
+ interface ParetoFigureSpec {
5996
+ kind: 'pareto-cost-quality';
5997
+ split: 'search' | 'holdout';
5998
+ points: ParetoPoint[];
5999
+ axes: {
6000
+ x: 'costUsd';
6001
+ y: 'score';
6002
+ };
6003
+ }
6004
+ interface GainDistributionBin {
6005
+ /** Inclusive lower edge. */
6006
+ lo: number;
6007
+ /** Exclusive upper edge (or inclusive if it's the last bin). */
6008
+ hi: number;
6009
+ /** Number of pairs whose delta lands in this bin. */
6010
+ count: number;
6011
+ }
6012
+
6013
+ /**
6014
+ * # InsightReport — the rigorous decision packet for any set of agent runs.
6015
+ *
6016
+ * Returned by `analyzeRuns()` and embedded in `SelfImproveResult.insight` +
6017
+ * the hosted-tier `EvalRunEvent.insightReport`. One shape across two surfaces:
6018
+ *
6019
+ * - **Customer who has a closed loop** (`selfImprove`): the report ships
6020
+ * with the loop output. Their dashboard renders ship/hold + lift CI +
6021
+ * calibration + cluster + Pareto in one packet.
6022
+ * - **Customer who has observed runs but no loop** (`analyzeRuns` directly):
6023
+ * same packet from a `RunRecord[]` they already have — production traces,
6024
+ * approve/reject corpus, CSV gold set.
6025
+ *
6026
+ * Every field is optional except the distributional summary — fields are
6027
+ * populated when the input data supports them:
6028
+ *
6029
+ * - `lift` requires both baseline and candidate splits to be present.
6030
+ * - `interRater` requires multi-rater feedback (≥2 raters per run).
6031
+ * - `judges` populates per-judge stats only when the run records carry
6032
+ * `outcome.judgeScores`.
6033
+ * - `failureClusters` requires the optional `analystRegistry` to be wired.
6034
+ * - `contamination` requires canary scenarios to be passed in.
6035
+ * - `outcomeCorrelation` requires a downstream outcome signal.
6036
+ * - `sequential` requires the run set to be ordered (treats them as a
6037
+ * stream and emits an anytime-valid interim decision).
6038
+ *
6039
+ * Consumers read the `recommendations` array first — that's the
6040
+ * actionable layer, ranked by priority. The numeric sections back it up.
6041
+ */
6042
+
6043
+ interface InsightReport {
6044
+ /** Number of runs analyzed. */
6045
+ n: number;
6046
+ /** Runtime facts carried by the run records. These describe execution,
6047
+ * not task quality: duration, queueing, token categories, models, and
6048
+ * explicitly recorded failures. */
6049
+ execution: ExecutionInsight;
6050
+ /** Composite-score distribution across all runs. Always present. */
6051
+ composite: ScalarDistribution;
6052
+ /** Per-dimension distributions for every dimension that appeared in any
6053
+ * run's judge scores. Empty when no judge scores were recorded. */
6054
+ perDimension: Record<string, ScalarDistribution>;
6055
+ /** Cost/quality distribution and Pareto frontier. */
6056
+ costQuality: {
6057
+ cost: ScalarDistribution;
6058
+ pareto: ParetoFigureSpec;
6059
+ /** Cost source coverage. `uncaptured` rows are excluded from the USD
6060
+ * distribution and Pareto chart; observed and estimated totals remain
6061
+ * separate so reports never present estimates as billed spend. */
6062
+ provenance?: CostProvenanceSummary;
6063
+ /** Set when the cost/quality view is degraded because the input data
6064
+ * doesn't fully support it — e.g. all `costUsd` were zero, or only a
6065
+ * single candidate appears (so the Pareto is a single point). The
6066
+ * named fields name the degraded sub-view, free-text the reason. */
6067
+ degraded?: {
6068
+ cost?: string;
6069
+ pareto?: string;
6070
+ };
6071
+ };
6072
+ /** Per-judge calibration + bias detection. Populated for every judge name
6073
+ * that appears in `outcome.judgeScores`. Bias fields require either a
6074
+ * gold reference or multi-rater data. */
6075
+ judges: Record<string, JudgeInsight>;
6076
+ /** Inter-rater agreement when multiple judges scored the same runs.
6077
+ * Includes pairwise kappa and the specific run ids where raters
6078
+ * disagree — the cases worth a human meeting. */
6079
+ interRater?: InterRaterInsight;
6080
+ /** Pairwise lift (baseline → candidate) with bootstrap CI. Present when
6081
+ * `RunRecord.splitTag` includes both `holdout` and search/dev splits,
6082
+ * or when caller passes an explicit baseline/candidate split. */
6083
+ lift?: LiftInsight;
6084
+ /** Failure clusters with exemplars. Populated when an AnalystRegistry
6085
+ * is wired in `analyzeRuns({ analyst })`. */
6086
+ failureClusters?: FailureClusterInsight;
6087
+ /** Canary leak count + holdout audit status. Populated when canary
6088
+ * scenarios are passed in. */
6089
+ contamination?: ContaminationInsight;
6090
+ /** Correlation between judge composite and a downstream outcome the
6091
+ * caller supplies (engagement, revenue, downstream pass rate, etc.).
6092
+ * When present, the optional reward model is the model that maps
6093
+ * judge scores → predicted outcome. */
6094
+ outcomeCorrelation?: OutcomeCorrelationInsight;
6095
+ /** Aggregate release-readiness summary. A consumer needing the full
6096
+ * substrate `ReleaseConfidenceScorecard` (SLO-axis evaluation,
6097
+ * ActionableSideInfo bag) calls `evaluateReleaseConfidence()` directly;
6098
+ * this summary captures the analyzeRuns-derived axes. */
6099
+ release: ReleaseSummary;
6100
+ /** Delta vs a prior period when `baselineRuns` is passed. Per-metric
6101
+ * current vs baseline with Welch CI + Cohen's d + significance flag.
6102
+ * Answers "did my last change help?" — the customer-conversion question.
6103
+ * Surfaced metrics: composite, cost, duration, tokenUsage, plus any
6104
+ * per-dimension judge metric present in both windows. */
6105
+ priorPeriodComparison?: PriorPeriodComparison;
6106
+ /** Model-free failure-mode breakdown from `RunRecord.failureMode`, ranked
6107
+ * by count descending. Present when any run carries a `failureMode`.
6108
+ * Complements `failureClusters` (LLM-semantic) with the structured tags
6109
+ * the harness already recorded — actionable with no analyst wired. */
6110
+ failureModes?: FailureModeTally[];
6111
+ /** Top-N actionable recommendations, ranked by priority. The packet's
6112
+ * human-readable layer; the numeric sections are the evidence. */
6113
+ recommendations: Recommendation[];
6114
+ }
6115
+ interface CostProvenanceSummary {
6116
+ observed: {
6117
+ n: number;
6118
+ totalUsd: number;
6119
+ };
6120
+ estimated: {
6121
+ n: number;
6122
+ totalUsd: number;
6123
+ };
6124
+ uncaptured: {
6125
+ n: number;
6126
+ };
6127
+ knownFraction: number;
6128
+ }
6129
+ interface ExecutionInsight {
6130
+ /** End-to-end wall time for every run. */
6131
+ durationMs: ScalarDistribution;
6132
+ /** Queue time for the subset of runs that recorded it. */
6133
+ queueMs: ScalarDistribution;
6134
+ /** Token distributions plus corpus totals. Optional token categories use
6135
+ * distribution `n` to disclose how many runs recorded that category. */
6136
+ tokenUsage: TokenUsageInsight;
6137
+ /** Usage reported only by orchestration or agent aggregate spans.
6138
+ * Kept separate because it may duplicate model-call telemetry in other traces. */
6139
+ aggregateUsage: {
6140
+ runs: number;
6141
+ tokenUsage: TokenUsageInsight;
6142
+ costUsd: ScalarDistribution;
6143
+ totalCostUsd: number;
6144
+ };
6145
+ /** Stable model counts, largest cohort first. */
6146
+ models: Array<{
6147
+ model: string;
6148
+ runs: number;
6149
+ }>;
6150
+ /** Model-call coverage. `events` is available only from producers that
6151
+ * record `outcome.raw.llm_span_count`; `runs` also recognizes non-zero
6152
+ * token usage from other producers. */
6153
+ modelCalls: {
6154
+ runs: number;
6155
+ events: number;
6156
+ reportingRuns: number;
6157
+ };
6158
+ /** Failure counts remain separate from outcome scores. `reportedErrorEvents`
6159
+ * sums `outcome.raw.error_span_count` only where a producer supplied it. */
6160
+ failures: {
6161
+ runs: number;
6162
+ fraction: number;
6163
+ reportedErrorEvents: number;
6164
+ reportingRuns: number;
6165
+ };
6166
+ }
6167
+ interface TokenUsageInsight {
6168
+ input: ScalarDistribution;
6169
+ output: ScalarDistribution;
6170
+ reasoning: ScalarDistribution;
6171
+ cached: ScalarDistribution;
6172
+ cacheWrite: ScalarDistribution;
6173
+ totals: {
6174
+ input: number;
6175
+ output: number;
6176
+ reasoning: number;
6177
+ cached: number;
6178
+ cacheWrite: number;
6179
+ };
6180
+ }
6181
+ /** Distributional summary of a scalar-valued metric. */
6182
+ interface ScalarDistribution {
6183
+ /** Sample count after dropping non-finite values. */
6184
+ n: number;
6185
+ mean: number;
6186
+ p50: number;
6187
+ p95: number;
6188
+ stddev: number;
6189
+ min: number;
6190
+ max: number;
6191
+ /** Histogram bins using `agent-eval`'s `gainHistogram` primitive. */
6192
+ histogram: GainDistributionBin[];
6193
+ /** Worst-N runs by score, ascending. Populated for the composite
6194
+ * distribution so the report names the runs a customer should
6195
+ * inspect first. Undefined when the distribution was computed from a
6196
+ * raw value list with no run identity (e.g. cost). */
6197
+ tailRuns?: Array<{
6198
+ runId: string;
6199
+ score: number;
6200
+ }>;
6201
+ }
6202
+ interface JudgeInsight {
6203
+ /** Number of times this judge scored a run. */
6204
+ n: number;
6205
+ /** Mean composite over this judge's runs. */
6206
+ meanScore: number;
6207
+ /** Calibration against a gold reference, when provided. Cohen's κ for
6208
+ * binary thresholding + continuous agreement metrics. */
6209
+ calibration?: ContinuousAgreement;
6210
+ /** Positional bias — when the judge sees options in different orders,
6211
+ * do its preferences track the content or the position? */
6212
+ positionalBias?: number;
6213
+ /** Self-preference — when the judge sees its own model's output vs a
6214
+ * competitor, does it over-pick its own? */
6215
+ selfPreference?: number;
6216
+ /** Verbosity bias — does the judge reward longer outputs regardless of
6217
+ * quality? */
6218
+ verbosityBias?: number;
6219
+ }
6220
+ interface InterRaterInsight {
6221
+ /** Number of raters whose scores were aggregated. */
6222
+ raters: number;
6223
+ /** Number of runs every rater scored. */
6224
+ jointlyRated: number;
6225
+ /** Cohen's κ averaged across rater pairs. */
6226
+ kappa: number;
6227
+ /** Pairwise κ per rater pair (key = `"raterA::raterB"`). */
6228
+ perPair: Record<string, number>;
6229
+ /** Run ids where raters disagree the most — the high-value triage list. */
6230
+ disagreementCases: Array<{
6231
+ runId: string;
6232
+ ratings: Array<{
6233
+ rater: string;
6234
+ score: number;
6235
+ }>;
6236
+ range: number;
6237
+ }>;
6238
+ }
6239
+ interface LiftInsight {
6240
+ baselineMean: number;
6241
+ candidateMean: number;
6242
+ /** Candidate − baseline. */
6243
+ delta: number;
6244
+ /** Lower / upper bound of bootstrap CI on the delta. */
6245
+ ci95: [number, number];
6246
+ /** Paired-t-test p-value. */
6247
+ pValue: number;
6248
+ /** Number of paired observations. */
6249
+ n: number;
6250
+ /** Cohen's d for the delta. */
6251
+ cohensD: number;
6252
+ /** Minimum detectable effect at current n, 80% power. */
6253
+ mde: number;
6254
+ /** Sample size needed to detect the observed delta at 80% power. */
6255
+ requiredN: number;
6256
+ }
6257
+ interface FailureClusterInsight {
6258
+ /** All clusters identified by the registry, ranked by share descending. */
6259
+ clusters: Array<{
6260
+ id: string;
6261
+ name: string;
6262
+ /** Fraction of failed runs in this cluster, 0..1. */
6263
+ share: number;
6264
+ /** Exemplar `runId`s (≤ 5) the consumer can drill into. */
6265
+ exemplars: string[];
6266
+ /** Short LLM-generated suggested fix when the registry supports it. */
6267
+ suggestedFix?: string;
6268
+ }>;
6269
+ totalFailures: number;
6270
+ }
6271
+ /** Model-free failure breakdown over the structured `RunRecord.failureMode`
6272
+ * enum. Unlike `failureClusters` (semantic, requires an LLM analyst), this
6273
+ * is computed directly from the tags the harness already recorded — so a
6274
+ * customer ingesting one batch with no judge/analyst still learns which
6275
+ * named failure dominates. */
6276
+ interface FailureModeTally {
6277
+ /** The `failureMode` tag. */
6278
+ mode: string;
6279
+ /** Number of runs carrying this tag. */
6280
+ count: number;
6281
+ /** Share of the whole corpus, 0..1. */
6282
+ share: number;
6283
+ }
6284
+ interface ContaminationInsight {
6285
+ /** Canary phrases that leaked into outputs. */
6286
+ leaks: number;
6287
+ /** Holdout audit verdict — did any holdout-tagged run end up in the
6288
+ * search/dev pool, or vice versa? */
6289
+ holdoutAuditPassed: boolean;
6290
+ details?: Array<{
6291
+ runId: string;
6292
+ canary: string;
6293
+ matched: string;
6294
+ }>;
6295
+ }
6296
+ interface OutcomeCorrelationInsight {
6297
+ /** What outcome the consumer is correlating against (e.g.
6298
+ * `'engagement_rate'`, `'approval_rate'`, `'downstream_pass'`). */
6299
+ metric: string;
6300
+ /** Number of (run, outcome) pairs used. */
6301
+ n: number;
6302
+ /** Pearson correlation between composite score and outcome. */
6303
+ pearson: number;
6304
+ /** Spearman rank correlation — robust to monotonic non-linearity. */
6305
+ spearman: number;
6306
+ /** When present, the simple linear reward model fit to the data. */
6307
+ rewardModel?: {
6308
+ intercept: number;
6309
+ slope: number;
6310
+ r2: number;
6311
+ };
6312
+ }
6313
+ interface ReleaseSummary {
6314
+ /** Overall verdict across axes — fail if any axis fails, else warn if any
6315
+ * warns, else pass. */
6316
+ status: 'pass' | 'warn' | 'fail';
6317
+ axes: Array<{
6318
+ name: 'quality-lift' | 'contamination' | 'composite-distribution';
6319
+ status: 'pass' | 'warn' | 'fail';
6320
+ detail: string;
6321
+ }>;
6322
+ /** Free-form issues surfaced beyond the standard axes. Empty by default;
6323
+ * consumers can post-process to populate. */
6324
+ issues: string[];
6325
+ }
6326
+ interface MetricDelta {
6327
+ /** Current-period mean. */
6328
+ current: number;
6329
+ /** Baseline-period mean. */
6330
+ baseline: number;
6331
+ /** current - baseline. Positive means improved (or, for cost/duration,
6332
+ * the consumer-side interpretation: "higher current" — semantic
6333
+ * direction depends on the metric). */
6334
+ delta: number;
6335
+ /** Welch 95% confidence interval on the delta. Two-sample, unpaired —
6336
+ * the baseline and current run sets may have different scenarios. */
6337
+ ci95: [number, number];
6338
+ /** Welch t-test p-value (two-sided). */
6339
+ pValue: number;
6340
+ /** Cohen's d (pooled stddev). Effect size, signed. */
6341
+ cohensD: number;
6342
+ /** Sample sizes. */
6343
+ baselineN: number;
6344
+ currentN: number;
6345
+ /** True when p < 0.05 AND |d| >= 0.2 (small-effect threshold). The
6346
+ * conjunction prevents large-effect-but-noisy and significant-but-
6347
+ * tiny from triggering recommendations. */
6348
+ significant: boolean;
6349
+ }
6350
+ interface PriorPeriodComparison {
6351
+ /** Sample counts. */
6352
+ baselineN: number;
6353
+ currentN: number;
6354
+ /** Optional human-readable label — "vs prior 7 days", "vs v3 release". */
6355
+ windowLabel?: string;
6356
+ /** Every metric we could compare. Keys: 'composite', 'cost', 'duration',
6357
+ * 'tokenUsage' for always-present ones; per-dimension keys when both
6358
+ * windows have judge scores on the same dimension. */
6359
+ metrics: Record<string, MetricDelta>;
6360
+ /** Metric names where current is significantly WORSE than baseline.
6361
+ * Direction-aware: for cost/duration, higher current = worse. */
6362
+ regressedMetrics: string[];
6363
+ /** Metric names where current is significantly BETTER than baseline. */
6364
+ improvedMetrics: string[];
6365
+ }
6366
+ interface Recommendation {
6367
+ priority: 'critical' | 'high' | 'medium' | 'low';
6368
+ kind: 'ship' | 'hold' | 'investigate' | 'fix' | 'recalibrate' | 'expand-corpus';
6369
+ title: string;
6370
+ detail: string;
6371
+ /** Optional pointer back into the report for the evidence. */
6372
+ evidencePath?: string;
6373
+ }
6374
+
6375
+ /**
6376
+ * # Hosted-tier wire format — the schema that EVERY orchestrator (ours,
6377
+ * a partner's self-hosted one, a future open implementation) must accept.
6378
+ *
6379
+ * **Stability:** every type in this file is committed under semver. New
6380
+ * minors only ADD optional fields. Breaking changes mean a major bump
6381
+ * (`HostedWireVersion` literal increment).
6382
+ *
6383
+ * The wire format is two event streams in one transport:
6384
+ *
6385
+ * 1. **Eval-run events** (`POST /v1/ingest/eval-runs`). Posted when a
6386
+ * campaign / improvement-loop completes (or per-generation if
6387
+ * streaming). Carries the structured result + per-cell scores +
6388
+ * surface diffs the orchestrator stores for the dashboard.
6389
+ *
6390
+ * 2. **Trace spans** (`POST /v1/ingest/traces`). Standard OTLP-shaped
6391
+ * spans with a few additional attributes so the orchestrator can
6392
+ * pivot from eval-run → underlying execution. Compatible with any
6393
+ * OTel collector.
6394
+ *
6395
+ * Both endpoints are authenticated with a bearer token + a tenant id
6396
+ * header. Tenants isolate everything downstream of ingest; no tenant
6397
+ * ever sees another tenant's data.
6398
+ */
6399
+
6400
+ declare const HOSTED_WIRE_VERSION: "2026-05-26.v1";
6401
+ type HostedWireVersion = typeof HOSTED_WIRE_VERSION;
6402
+ /** Lifecycle stages of an eval-run as the substrate reports them. */
6403
+ type EvalRunStatus = 'started' | 'baseline-complete' | 'generation-complete' | 'gate-decided' | 'finished' | 'errored';
6404
+ interface EvalRunCellScore {
6405
+ /** Stable scenario id from the consumer's scenario set. */
6406
+ scenarioId: string;
6407
+ /** Repetition index when reps > 1; 0 for the default. */
6408
+ rep: number;
6409
+ /** Composite score across all judges + dimensions for this cell. */
6410
+ compositeMean: number;
6411
+ /** Per-judge → per-dimension scores; null where the judge did not run. */
6412
+ dimensions: Record<string, Record<string, number>>;
6413
+ /** Per-cell error message if the dispatch threw. Null on success. */
6414
+ errorMessage?: string;
6415
+ }
6416
+ interface EvalRunGenerationSnapshot {
6417
+ /** Generation index. 0 is baseline. */
6418
+ index: number;
6419
+ /** Candidate surface fingerprint (stable hash) — pivot key into the
6420
+ * trace stream to fetch the underlying execution. */
6421
+ surfaceHash: string;
6422
+ /** The candidate surface itself. May be omitted to avoid PII when the
6423
+ * consumer prefers not to ship verbatim prompts. */
6424
+ surface?: MutableSurface;
6425
+ /** Per-cell scores for this generation. */
6426
+ cells: EvalRunCellScore[];
6427
+ /** Aggregate composite mean across all cells in this generation. */
6428
+ compositeMean: number;
6429
+ /** Total $ spent across this generation. */
6430
+ costUsd: number;
6431
+ /** Wall-clock duration of this generation. */
6432
+ durationMs: number;
6433
+ }
6434
+ /**
6435
+ * The top-level eval-run event. One ingest call per logical eval-run;
6436
+ * generations stream in incrementally via repeated calls with the same
6437
+ * `runId`. The orchestrator deduplicates by `(runId, generation.index)`.
6438
+ */
6439
+ interface EvalRunEvent {
6440
+ /** Stable run id (the substrate's `runId`). UUID or substrate-generated. */
6441
+ runId: string;
6442
+ /** Where this run was happening — derived from `RunCampaignOptions.runDir`. */
6443
+ runDir: string;
6444
+ /** ISO-8601 timestamp the substrate recorded the event. */
6445
+ timestamp: string;
6446
+ /** Lifecycle stage this event represents. */
6447
+ status: EvalRunStatus;
6448
+ /** Free-form consumer tags (env, branch, model id, etc.). Searchable. */
6449
+ labels: Record<string, string>;
6450
+ /** Baseline campaign snapshot. Present when status >= baseline-complete. */
6451
+ baseline?: EvalRunGenerationSnapshot;
6452
+ /** Per-generation snapshots. Streams in; orchestrator appends. */
6453
+ generations: EvalRunGenerationSnapshot[];
6454
+ /** Final gate decision. Present when status >= gate-decided. */
6455
+ gateDecision?: GateDecision;
6456
+ /** Held-out lift = winner-on-holdout - baseline-on-holdout. */
6457
+ holdoutLift?: number;
6458
+ /** Total $ spent across baseline + every generation. */
6459
+ totalCostUsd: number;
6460
+ /** Total wall-clock duration. */
6461
+ totalDurationMs: number;
6462
+ /** Error message if status === 'errored'. */
6463
+ errorMessage?: string;
6464
+ /** Rigor packet emitted alongside the run — distributional summary,
6465
+ * paired-bootstrap lift CI, judge stats, inter-rater agreement,
6466
+ * contamination check, failure clusters (when an analyst is wired),
6467
+ * outcome correlation (when downstream signal is supplied), and the
6468
+ * recommendations the dashboard surfaces verbatim. Additive; older
6469
+ * clients that don't know about this field continue to work. */
6470
+ insightReport?: InsightReport;
6471
+ }
6472
+ /**
6473
+ * OTel-shape span with a few additional attributes for eval-run pivoting.
6474
+ * Compatible with any OTLP collector — `name`, `traceId`, `spanId`,
6475
+ * `startTimeUnixNano`, `endTimeUnixNano`, `attributes` are stock OTel.
6476
+ */
6477
+ interface TraceSpanEvent {
6478
+ traceId: string;
6479
+ spanId: string;
6480
+ parentSpanId?: string;
6481
+ name: string;
6482
+ startTimeUnixNano: number;
6483
+ endTimeUnixNano: number;
6484
+ attributes: Record<string, string | number | boolean>;
6485
+ events?: Array<{
6486
+ timeUnixNano: number;
6487
+ name: string;
6488
+ attributes?: Record<string, string | number | boolean>;
6489
+ }>;
6490
+ status?: {
6491
+ code: 'OK' | 'ERROR' | 'UNSET';
6492
+ message?: string;
6493
+ };
6494
+ /** Pivot back into the eval-run stream. */
6495
+ 'tangle.runId'?: string;
6496
+ /** Pivot to the specific generation. */
6497
+ 'tangle.generation'?: number;
6498
+ /** Pivot to the specific cell. */
6499
+ 'tangle.cellId'?: string;
6500
+ /** Pivot to the specific scenario. */
6501
+ 'tangle.scenarioId'?: string;
6502
+ }
6503
+ interface IngestResponse {
6504
+ /** Accepted events / spans count. */
6505
+ accepted: number;
6506
+ /** Rejected events with reasons (validation failures, dup idempotency key, etc.). */
6507
+ rejected: Array<{
6508
+ index: number;
6509
+ reason: string;
6510
+ }>;
6511
+ }
6512
+
6513
+ /**
6514
+ * # Hosted-tier ingest client.
6515
+ *
6516
+ * Ships eval-run events + trace spans to any orchestrator (ours, a
6517
+ * partner's self-hosted one, or a future open implementation) that
6518
+ * speaks the wire format in `./types.ts`.
6519
+ *
6520
+ * Three modes:
6521
+ * - **Ours:** point at `https://orchestrator.tangle.tools` (the host root —
6522
+ * the client appends the versioned `/v1/ingest/...` path itself; a trailing
6523
+ * `/v1` on the endpoint is tolerated and normalized away). We handle ingest
6524
+ * + storage + dashboard.
6525
+ * - **Self-hosted:** point at whatever URL runs the reference receiver
6526
+ * from `examples/hosted-ingest-server/`.
6527
+ * - **Off (default):** when `hostedTenant` is unset, nothing is sent.
6528
+ * Everything stays local.
6529
+ */
6530
+
6531
+ interface HostedTenant {
6532
+ /** Orchestrator endpoint base URL (no trailing slash). Required. */
6533
+ endpoint: string;
6534
+ /** Bearer token issued by the orchestrator. Required. */
6535
+ apiKey: string;
6536
+ /** Tenant id — the orchestrator's primary key for this consumer. Required. */
6537
+ tenantId: string;
6538
+ /** Optional `fetch` override (auth wrappers, custom agent, test mocks). */
6539
+ fetchImpl?: typeof fetch;
6540
+ /** Per-call timeout in ms. Default 30s. */
6541
+ timeoutMs?: number;
6542
+ /** Retries on 5xx / network errors. Default 2. */
6543
+ retries?: number;
6544
+ }
6545
+ interface HostedClient {
6546
+ ingestEvalRun(event: EvalRunEvent, idempotencyKey?: string): Promise<IngestResponse>;
6547
+ ingestEvalRuns(events: EvalRunEvent[], idempotencyKey?: string): Promise<IngestResponse>;
6548
+ ingestTraces(spans: TraceSpanEvent[], idempotencyKey?: string): Promise<IngestResponse>;
6549
+ readonly tenant: HostedTenant;
6550
+ readonly wireVersion: HostedWireVersion;
6551
+ }
6552
+
6553
+ /**
6554
+ * Loop provenance — the durable, queryable record of WHAT a self-improvement
6555
+ * loop did and WHY, plus the OTel spans that let an OTLP collector pivot from
6556
+ * an eval-run to the underlying candidate→cell→gate→promote chain.
6557
+ *
6558
+ * Two artifacts, one source of truth:
6559
+ *
6560
+ * 1. `LoopProvenanceRecord` — a structured JSON record capturing every
6561
+ * candidate (surfaceHash + label + rationale + structured cause), its measured composite,
6562
+ * the gate decision + reasons + delta, the held-out lift, the explicit
6563
+ * baseline→candidate diff, and BACKEND PROVENANCE (the
6564
+ * `assertRealBackend` verdict + worker call count + model). This is the
6565
+ * ingestable audit artifact: the +lift recomputes from it, the "because
6566
+ * Z" rationale survives in it, and a stub backend is detectable from it.
6567
+ *
6568
+ * 2. `loopProvenanceSpans()` — the same chain emitted as OTLP-ingestable
6569
+ * `TraceSpanEvent`s, pivoted on the substrate's standard
6570
+ * `tangle.runId` / `tangle.scenarioId` / `tangle.cellId` /
6571
+ * `tangle.generation` attributes (the same pivots `/adapters/otel`
6572
+ * reads). The hosted `/v1/ingest/traces` endpoint receives the FULL loop,
6573
+ * not just the `cost.*` spans `runCampaign` already emits per cell.
6574
+ *
6575
+ * The record is built from the substrate's own loop result + the per-call
6576
+ * `RunRecord`s the worker emitted — no new measurement, no recomputation that
6577
+ * could drift from what the gate actually saw.
6578
+ */
6579
+
6580
+ interface LoopProvenanceCandidate {
6581
+ /** Generation index this candidate was proposed in. */
6582
+ generation: number;
6583
+ /** 16-char loop-identity fingerprint (matches `GenerationCandidate.surfaceHash`). */
6584
+ surfaceHash: string;
6585
+ /** Full sha256 content hash — byte-identical-verifiable. */
6586
+ contentHash: string;
6587
+ /** Proposer label, when the proposer returned a `ProposedCandidate`. */
6588
+ label?: string;
6589
+ /** Proposer rationale — the "because Z". When the proposer returned a bare
6590
+ * surface (blind mutator) this is absent. */
6591
+ rationale?: string;
6592
+ /** Exact validated cause when the proposer emitted a structured record. */
6593
+ candidateRecord?: PolicyEditCandidateRecord;
6594
+ /** Exact complete incumbent this candidate mutated. */
6595
+ parentSurfaceHash: string;
6596
+ /** Search-split composite of the exact parent. */
6597
+ parentComposite: number;
6598
+ /** Search-split composite change relative to the exact parent. */
6599
+ observedDeltaFromParent?: number;
6600
+ /** Whether the candidate completed every designed cell and could be selected. */
6601
+ eligibleForPromotion: boolean;
6602
+ /** Designed-denominator receipt retained even for incomplete candidates. */
6603
+ coverage: NonNullable<GenerationCandidate['coverage']>;
6604
+ /** Mean composite this candidate scored on the search split. */
6605
+ composite: number;
6606
+ /** Whether this candidate was promoted out of its generation. */
6607
+ promoted: boolean;
6608
+ }
6609
+ interface LoopProvenanceBackend {
6610
+ /** `assertRealBackend`-grade verdict over the worker call records. */
6611
+ verdict: 'real' | 'mixed' | 'stub';
6612
+ /** Number of worker LLM calls captured (the audit's "worker call count"). */
6613
+ workerCallCount: number;
6614
+ /** Distinct model ids observed across worker calls. */
6615
+ models: string[];
6616
+ totalInputTokens: number;
6617
+ totalOutputTokens: number;
6618
+ totalCostUsd: number;
6619
+ }
6620
+ /**
6621
+ * The durable provenance record. Aligns to the hosted `EvalRunEvent` path but
6622
+ * ADDS the rationale + the explicit baseline→candidate diff (both omitted from
6623
+ * the bare hosted event) + backend provenance.
6624
+ */
6625
+ interface LoopProvenanceRecord {
6626
+ schema: 'tangle.loop-provenance.v3';
6627
+ runId: string;
6628
+ runDir: string;
6629
+ timestamp: string;
6630
+ /** Baseline + winner surface content hashes — distinguishable, byte-verifiable. */
6631
+ baselineContentHash: string;
6632
+ winnerContentHash: string;
6633
+ /** Proposer label/rationale for the promoted change. Absent ⇒ winner == baseline. */
6634
+ winnerLabel?: string;
6635
+ winnerRationale?: string;
6636
+ /** The explicit baseline→winner unified diff the gate decided on. */
6637
+ diff: string;
6638
+ /** Every candidate across every generation, with its rationale and structured cause. */
6639
+ candidates: LoopProvenanceCandidate[];
6640
+ /** Baseline composite on the search split that generated the candidates. */
6641
+ baselineSearchComposite: number;
6642
+ /** The gate verdict — decision + reasons + contributing gates + delta. */
6643
+ gate: {
6644
+ decision: GateDecision;
6645
+ reasons: string[];
6646
+ delta?: number;
6647
+ contributingGates: Array<{
6648
+ name: string;
6649
+ passed: boolean;
6650
+ }>;
6651
+ };
6652
+ /** baseline-on-holdout composite mean. */
6653
+ baselineHoldoutComposite: number;
6654
+ /** winner-on-holdout composite mean. */
6655
+ winnerHoldoutComposite: number;
6656
+ /** winnerHoldout - baselineHoldout — RECOMPUTABLE from this record. */
6657
+ heldOutLift: number;
6658
+ /** Backend provenance: stub-vs-real verdict + worker call count + models. */
6659
+ backend: LoopProvenanceBackend;
6660
+ totalCostUsd: number;
6661
+ totalDurationMs: number;
6662
+ }
6663
+ interface BuildLoopProvenanceArgs<TArtifact, TScenario extends Scenario> {
6664
+ runId: string;
6665
+ runDir: string;
6666
+ timestamp: string;
6667
+ baselineSurface: MutableSurface;
6668
+ winnerSurface: MutableSurface;
6669
+ winnerLabel?: string;
6670
+ winnerRationale?: string;
6671
+ diff: string;
6672
+ /** Baseline composite on the search split, distinct from holdout scoring. */
6673
+ baselineSearchComposite: number;
6674
+ /** Per-generation candidate records straight off the loop result. */
6675
+ generations: Array<{
6676
+ generationIndex: number;
6677
+ candidates: GenerationCandidate[];
6678
+ promoted: string[];
6679
+ /** Surfaces measured this generation, keyed by surface hash so the content
6680
+ * hash can be computed and the loop identity rechecked from real bytes. */
6681
+ surfaces: Array<{
6682
+ surfaceHash: string;
6683
+ surface: MutableSurface;
6684
+ }>;
6685
+ }>;
6686
+ gate: GateResult;
6687
+ baselineOnHoldout: CampaignResult<TArtifact, TScenario>;
6688
+ winnerOnHoldout: CampaignResult<TArtifact, TScenario>;
6689
+ /** Worker call records — the source for backend provenance. */
6690
+ workerRecords: ReadonlyArray<RunRecord>;
6691
+ totalCostUsd: number;
6692
+ totalDurationMs: number;
6693
+ }
6694
+ /** Build the durable provenance record from a completed loop result. */
6695
+ declare function buildLoopProvenanceRecord<TArtifact, TScenario extends Scenario>(args: BuildLoopProvenanceArgs<TArtifact, TScenario>): LoopProvenanceRecord;
6696
+ /**
6697
+ * Build the loop's OTLP-ingestable spans from a provenance record. One root
6698
+ * span per loop (`tangle.runId`), one span per generation, one span per
6699
+ * candidate (carrying its surfaceHash + label), and one span for the gate
6700
+ * decision (carrying reasons + delta + lift). Candidate + gate spans pivot on
6701
+ * the same `tangle.runId` / `tangle.generation` attributes `/adapters/otel`
6702
+ * reads, so the hosted collector reconstructs the full tree.
6703
+ *
6704
+ * Times are synthesized monotonically off a single base so the span tree is
6705
+ * orderable; the substrate does not retain per-candidate wall-clock starts.
6706
+ */
6707
+ declare function loopProvenanceSpans(record: LoopProvenanceRecord, opts?: {
6708
+ baseTimeMs?: number;
6709
+ }): TraceSpanEvent[];
6710
+ /** Canonical durable paths under the run dir. */
6711
+ declare function provenanceRecordPath(runDir: string): string;
6712
+ /**
6713
+ * Canonical path for the durable OTLP spans JSONL file under a loop run directory.
6714
+ */
6715
+ declare function provenanceSpansPath(runDir: string): string;
6716
+ interface EmitLoopProvenanceResult {
6717
+ record: LoopProvenanceRecord;
6718
+ spans: TraceSpanEvent[];
6719
+ /** Absolute paths the record + spans were written to, when storage persists. */
6720
+ recordPath: string;
6721
+ spansPath: string;
6722
+ }
6723
+ interface EmitLoopProvenanceArgs<TArtifact, TScenario extends Scenario> extends BuildLoopProvenanceArgs<TArtifact, TScenario> {
6724
+ /** Storage the record + spans are written through. */
6725
+ storage: CampaignStorage;
6726
+ /** When set, the spans are also shipped to the hosted `/v1/ingest/traces`
6727
+ * endpoint so the collector receives the full loop, not just `cost.*`. */
6728
+ hostedClient?: HostedClient;
6729
+ }
6730
+ /**
6731
+ * Build the provenance record + OTel spans and persist them durably under the
6732
+ * run dir (and ship spans to a hosted collector when one is wired). Returns
6733
+ * both artifacts so the caller can assert on / re-derive from them.
6734
+ *
6735
+ * Fail-loud: the durable write throws on storage failure (a swallowed write is
6736
+ * exactly the "emitted but lost" failure this closes). The hosted span ship is
6737
+ * the one best-effort leg — its failure is logged, not thrown, so an offline
6738
+ * collector never fails the loop (the durable artifact is the source of truth).
6739
+ */
6740
+ declare function emitLoopProvenance<TArtifact, TScenario extends Scenario>(args: EmitLoopProvenanceArgs<TArtifact, TScenario>): Promise<EmitLoopProvenanceResult>;
6741
+
2540
6742
  /** The shared, out-of-repo root for campaign/benchmark run bundles. Keeping run
2541
6743
  * outputs here means they never land in a repo working tree (no per-repo
2542
6744
  * gitignore, no clutter, no accidental commits). Layout:
@@ -3112,4 +7314,4 @@ declare function verifyCodeSurface(surface: CodeSurface, worktreeDir?: string):
3112
7314
  * identity against the checkout at `worktreeRef`. */
3113
7315
  declare function resolveWorktreePath(surface: CodeSurface, worktreeDir?: string): string;
3114
7316
 
3115
- export { type AcceptedEdit, type AceProposerOptions, type AnalystArtifact, type AnalystScenario, type AnalyzeCrossSurfaceInteractionsInput, type ApplySkillPatchResult, type BuildAnalystSurfaceDispatchOptions, type CampaignBreakdown, CampaignResult, CampaignRunPlan, CampaignStorage, CodeSurface, type CodeSurfaceVerification, type CompareProposersOptions, type CompositeProposerOptions, type CrossSurfaceAdditionDecision, type CrossSurfaceAdditionRejectionReason, type CrossSurfaceAttemptCompleteness, type CrossSurfaceBestSingleSelection, type CrossSurfaceBootstrapPolicy, type CrossSurfaceCandidate, type CrossSurfaceCandidateComparison, type CrossSurfaceCandidateEvidence, type CrossSurfaceCandidateOutcome, type CrossSurfaceCandidateSummary, type CrossSurfaceComponent, type CrossSurfaceComponentEvidence, type CrossSurfaceCompositionStep, type CrossSurfaceDistribution, type CrossSurfaceEligibility, type CrossSurfaceEvidenceBreakdown, type CrossSurfaceIneligibilityReason, type CrossSurfaceInteractionAwareSelection, type CrossSurfaceInteractionEffect, type CrossSurfaceInteractionPath, type CrossSurfaceInteractionReport, type CrossSurfaceInteractionTask, type CrossSurfaceNaiveStackSelection, type CrossSurfacePairCompatibility, type CrossSurfacePairEvidence, type CrossSurfacePairIncompatibilityReason, type CrossSurfacePairwiseEntry, type CrossSurfaceRankedSingle, type CrossSurfaceRelativeCost, type CrossSurfaceSelectionPolicy, type CrossSurfaceSelections, type CrossSurfaceTaskRow, DEFAULT_POLICY_EDIT_HISTORY_LIMITS, type DimensionRegression, type DiscriminationScore, DispatchContext, type EvalFixture, type EvalFixtureFile, type EvalFixtureLoadOptions, type EvalFixtureRunPlan, type EvalFixtureScenario, type EvalFixtureValidationMode, type FailureModeRecallJudgeOptions, type FapoAttributionSignals, type FapoEntryConfig, type FapoFailureCluster, type FapoOptimizationLevel, type FapoProposerOptions, type FapoReviewInput, type FapoReviewIssue, type FapoReviewResult, type FapoScopeContract, FileSearchLedger, FsLabeledScenarioStore, type FsLabeledScenarioStoreOptions, Gate, GenerationRecord, type GitWorktreeAdapterOptions, type Governor, type GovernorContext, type GovernorOp, type HaloProposerOptions, type HeldoutSignificance, type HeldoutSignificanceOptions, type HeuristicGovernorOptions, type JsonPolicyEditTargetSurface, type JsonPrimitive, type JsonValue, JudgeConfig, JudgeScore, LabelTrust, LabeledScenarioRecord, LabeledScenarioSampleArgs, LabeledScenarioSource, LabeledScenarioStore, LabeledScenarioStoreError, LabeledScenarioWrite, Lineage, type LineageEdge, type LineageGraph, type LineageNode, type LineageNodeInput, type LineageStore, type LlmPolicyEditProposerOptions, type LoadEvalFixtureScenariosOptions, type MemoryCurationProposerOptions, MutableSurface, type NeutralizationGateOptions, type OpenSearchLedgerOptions, type OptimizerEntryConfig, type PairedHoldout, type ParameterCandidate, type ParameterChange, type ParameterSweepProposerOptions, PlanCampaignRunOptions, type PlanEvalFixtureRunOptions, type PlaybackContext, type PlaybackDriver, type PlaybackStep, type PolicyEditAuthorScenarioRow, type PolicyEditCandidateSummary, type PolicyEditFindingInput, type PolicyEditFindingSource, type PolicyEditHistoryCandidateContext, type PolicyEditHistoryGenerationContext, type PolicyEditHistoryProjectionOptions, type PolicyEditObjective, type PolicyEditOutcomeContext, type PolicyEditProposerOptions, type ProfileDispatchFn, ProfileMatrixError, type ProfileSummary, ProposeContext, type ProposePatchesArgs, ProposedCandidate, type ProposerComparison, type ProposerEntry, type ProposerPairwise, type ProposerScore, type RejectedEdit, type RolloutArgumentDiff, type RolloutArgumentDiffOptions, type RolloutCall, RunCampaignOptions, RunImprovementLoopOptions, type RunLineageLoopOptions, type RunLineageLoopResult, type RunLineageLoopSeed, type RunLineageOptions, type RunLineageResult, type RunLineageSeed, type RunLineageStepResult, type RunProfileMatrixOptions, type RunProfileMatrixResult, type RunSkillOptOptions, type RunSkillOptResult, SEARCH_LEDGER_SCHEMA, Scenario, type ScenarioRollup, type ScenarioSignal, type ScoreboardRenderOptions, type ScoreboardRow, type ScoreboardSummary, type ScoredRollout, type SearchAccountingAudit, type SearchArtifactRef, type SearchAttemptAccounting, type SearchCandidateDecidedEvent, type SearchCandidateLineage, type SearchCandidateRegisteredEvent, type SearchCandidateSlot, type SearchCandidateSlotClosedEvent, type SearchCandidateSurface, type SearchCompletedEvent, type SearchCostAccounting, type SearchFailureReason, type SearchLedger, type SearchLedgerAppendResult, SearchLedgerConflictError, type SearchLedgerEntry, SearchLedgerError, type SearchLedgerEvent, type SearchLedgerHash, SearchLedgerIntegrityError, type SearchLedgerReplay, type SearchModelIdentity, type SearchOperationKind, type SearchOperationRecordedEvent, type SearchPlan, type SearchPlannedEvent, type SearchPlannedOperation, type SearchPlannedTask, type SearchSourceRef, type SearchSurfaceEffect, type SearchSurfaceEvidence, type SearchSurfaceKind, type SearchTaskAttemptedEvent, type SearchTaskOutcome, type SearchTokenAccounting, type SelectPolicyEditAuthorRowsOptions, type SequentialDecideFn, type SequentialDecideOptions, type SequentialDecision, type SequentialObservation, type SequentialPairedGate, type SequentialPairedGateOptions, type SerializedJsonBudget, type SingleRunLock, type SingleRunLockOptions, type SkillOptEpochRecord, type SkillOptEvidence, type SkillOptProposer, type SkillOptProposerOptions, type SkillPatch, type SkillPatchOp, SkillPatchParseError, type SkillPatchRejection, SurfaceProposer, type SurfaceScore, type TraceAnalystProposerOptions, type TransientFailureOptions, type UngroundedLiteralReport, type UserStory, type UserStoryVerdict, type Worktree, type WorktreeAdapter, WorktreeAdapterError, aceProposer, acquireSingleRunLock, analyzeCrossSurfaceInteractions, applySkillPatch, assertCodeSurfaceIdentity, assertPolicyEditAuthorContextBudget, buildAnalystSurfaceDispatch, callbackGovernor, campaignBreakdown, campaignMeanComposite, classifyUngroundedLiterals, codeSurfaceIdentityMaterial, compareProposers, compositeProposer, detectScale, dimensionRegressions, discoverEvalFixtures, extractFapoAttributionSignals, failureModeRecallJudge, fapoEscalationEntry, fapoProposer, fsLineageStore, gepaParetoEntry, gepaReflectionEntry, gitWorktreeAdapter, haloProposer, heldoutSignificance, heuristicGovernor, isTransientTransportFailure, lineageNodeId, llmPolicyEditProposer, loadEvalFixture, loadEvalFixtureScenarios, makePlaybackDispatch, memLineageStore, memoryCurationProposer, neutralizationGate, neutralizeText, openSearchLedger, pairHoldout, parameterSweepProposer, parseSkillPatchResponse, patchEditCount, planEvalFixtureRun, policyEditProposer, projectPolicyEditHistory, renderScoreboardMarkdown, resolveRunDir, resolveWorktreePath, rolloutArgumentDiff, runLineage, runLineageLoop, runProfileMatrix, runSkillOpt, scoreDiscrimination, scoreUserStory, scoreboardSummary, selectDiscriminative, selectPolicyEditAuthorRows, sequentialDecide, sequentialPairedGate, skillOptEntry, skillOptProposer, surfaceContentHash, surfaceHash, tangleTracesRoot, traceAnalystProposer, userStoryScoreboard, validateSearchLedgerEvent, verifyCodeSurface };
7317
+ export { type AcceptedEdit, type AceProposerOptions, type AnalystArtifact, type AnalystScenario, type AnalyzeCrossSurfaceInteractionsInput, type ApplySkillPatchResult, type AxisEvidence, type AxisVerdict, type BuildAnalystSurfaceDispatchOptions, type BuildEvidenceVectorOptions, type BuildLoopProvenanceArgs, type CampaignAggregates, type CampaignArtifactWriter, type CampaignBreakdown, type CampaignCellResult, type CampaignCostMeter, type CampaignResult, type CampaignRunPlan, type CampaignRunPlanCell, type CampaignStorage, type CampaignTokenUsage, type CampaignTraceWriter, type CodeSurface, type CodeSurfaceVerification, type CompareProposersOptions, type CompositeProposerOptions, type CrossSurfaceAdditionDecision, type CrossSurfaceAdditionRejectionReason, type CrossSurfaceAttemptCompleteness, type CrossSurfaceBestSingleSelection, type CrossSurfaceBootstrapPolicy, type CrossSurfaceCandidate, type CrossSurfaceCandidateComparison, type CrossSurfaceCandidateEvidence, type CrossSurfaceCandidateOutcome, type CrossSurfaceCandidateSummary, type CrossSurfaceComponent, type CrossSurfaceComponentEvidence, type CrossSurfaceCompositionStep, type CrossSurfaceDistribution, type CrossSurfaceEligibility, type CrossSurfaceEvidenceBreakdown, type CrossSurfaceIneligibilityReason, type CrossSurfaceInteractionAwareSelection, type CrossSurfaceInteractionEffect, type CrossSurfaceInteractionPath, type CrossSurfaceInteractionReport, type CrossSurfaceInteractionTask, type CrossSurfaceNaiveStackSelection, type CrossSurfacePairCompatibility, type CrossSurfacePairEvidence, type CrossSurfacePairIncompatibilityReason, type CrossSurfacePairwiseEntry, type CrossSurfaceRankedSingle, type CrossSurfaceRelativeCost, type CrossSurfaceSelectionPolicy, type CrossSurfaceSelections, type CrossSurfaceTaskRow, DEFAULT_POLICY_EDIT_HISTORY_LIMITS, type DefaultProductionGateOptions, type DimensionRegression, type DiscriminationScore, type DispatchContext, type DispatchFn, type EmitLoopProvenanceArgs, type EmitLoopProvenanceResult, type EvalFixture, type EvalFixtureFile, type EvalFixtureLoadOptions, type EvalFixtureRunPlan, type EvalFixtureScenario, type EvalFixtureValidationMode, type EvidenceVector, type EvolutionaryProposerOptions, type FailureModeRecallJudgeOptions, type FapoAttributionSignals, type FapoEntryConfig, type FapoFailureCluster, type FapoOptimizationLevel, type FapoProposerOptions, type FapoReviewInput, type FapoReviewIssue, type FapoReviewResult, type FapoScopeContract, FileSearchLedger, FsLabeledScenarioStore, type FsLabeledScenarioStoreOptions, type Gate, type GateContext, type GateDecision, type GateResult, type GenerationCandidate, type GenerationRecord, type GepaProposerConstraints, type GepaProposerOptions, type GitWorktreeAdapterOptions, type Governor, type GovernorContext, type GovernorOp, type HaloProposerOptions, type HeldOutGateOptions, type HeldoutSignificance, type HeldoutSignificanceOptions, type HeuristicGovernorOptions, type JsonPolicyEditTargetSurface, type JsonPrimitive, type JsonValue, type JudgeAggregate, type JudgeConfig, type JudgeDimension, type JudgeScore, type LabelTrust, type LabeledScenarioRecord, type LabeledScenarioSampleArgs, type LabeledScenarioSource, type LabeledScenarioStore, LabeledScenarioStoreError, type LabeledScenarioWrite, Lineage, type LineageEdge, type LineageGraph, type LineageNode, type LineageNodeInput, type LineageStore, type LlmJudgeDimension, type LlmJudgeOptions, type LlmPolicyEditProposerOptions, type LoadEvalFixtureScenariosOptions, type LoopProvenanceBackend, type LoopProvenanceCandidate, type LoopProvenanceRecord, type MemoryCurationProposerOptions, type MutableSurface, type Mutator, type NeutralizationGateOptions, type ObjectiveSource, type OpenAutoPrOptions, type OpenAutoPrResult, type OpenSearchLedgerOptions, type OptimizationProposer, type OptimizerConfig, type OptimizerEntryConfig, POLICY_EDIT_CANDIDATE_RECORD_SCHEMA, type PairedHoldout, type ParameterCandidate, type ParameterChange, type ParameterSweepProposerOptions, type ParetoParent, type ParetoSignificanceGateOptions, type PlanCampaignRunOptions, type PlanEvalFixtureRunOptions, type PlaybackContext, type PlaybackDriver, type PlaybackStep, type PolicyEditAuthorScenarioRow, type PolicyEditCandidateRecord, type PolicyEditCandidateSummary, type PolicyEditFindingInput, type PolicyEditFindingSource, type PolicyEditHistoryCandidateContext, type PolicyEditHistoryGenerationContext, type PolicyEditHistoryProjectionOptions, type PolicyEditObjective, type PolicyEditOutcomeContext, type PolicyEditProposerOptions, type PowerPreflight, type PowerPreflightOptions, type ProfileDispatchFn, ProfileMatrixError, type ProfileSummary, type PromotionObjective, type PromotionPolicy, type ProposeContext, type ProposePatchesArgs, type ProposedCandidate, type ProposerComparison, type ProposerEntry, type ProposerPairwise, type ProposerScore, type RedactionStatus, type ReferenceEquivalenceJudgeOptions, type ReferenceEquivalenceScenario, type RejectedEdit, type RolloutArgumentDiff, type RolloutArgumentDiffOptions, type RolloutCall, type RunCampaignOptions, type RunEvalOptions, type RunImprovementLoopOptions, type RunImprovementLoopResult, type RunLineageLoopOptions, type RunLineageLoopResult, type RunLineageLoopSeed, type RunLineageOptions, type RunLineageResult, type RunLineageSeed, type RunLineageStepResult, type RunOptimizationOptions, type RunOptimizationResult, type RunProfileMatrixOptions, type RunProfileMatrixResult, type RunSkillOptOptions, type RunSkillOptResult, SEARCH_LEDGER_SCHEMA, type Scenario, type ScenarioAggregate, type ScenarioRollup, type ScenarioSignal, type ScoreboardRenderOptions, type ScoreboardRow, type ScoreboardSummary, type ScoredRollout, type ScoredSurfaceOutcome, type SearchAccountingAudit, type SearchArtifactRef, type SearchAttemptAccounting, type SearchCandidateDecidedEvent, type SearchCandidateLineage, type SearchCandidateRegisteredEvent, type SearchCandidateSlot, type SearchCandidateSlotClosedEvent, type SearchCandidateSurface, type SearchCompletedEvent, type SearchCostAccounting, type SearchFailureReason, type SearchLedger, type SearchLedgerAppendResult, SearchLedgerConflictError, type SearchLedgerEntry, SearchLedgerError, type SearchLedgerEvent, type SearchLedgerHash, SearchLedgerIntegrityError, type SearchLedgerReplay, type SearchModelIdentity, type SearchOperationKind, type SearchOperationRecordedEvent, type SearchPlan, type SearchPlannedEvent, type SearchPlannedOperation, type SearchPlannedTask, type SearchSourceRef, type SearchSurfaceEffect, type SearchSurfaceEvidence, type SearchSurfaceKind, type SearchTaskAttemptedEvent, type SearchTaskOutcome, type SearchTokenAccounting, type SelectPolicyEditAuthorRowsOptions, type SequentialDecideFn, type SequentialDecideOptions, type SequentialDecision, type SequentialObservation, type SequentialPairedGate, type SequentialPairedGateOptions, type SerializedJsonBudget, type SessionScript, type SingleRunLock, type SingleRunLockOptions, type SkillOptEpochRecord, type SkillOptEvidence, type SkillOptProposer, type SkillOptProposerOptions, type SkillPatch, type SkillPatchOp, SkillPatchParseError, type SkillPatchRejection, type SurfaceProposer, type SurfaceScore, type TraceAnalystProposerOptions, type TraceSpan, type TransientFailureOptions, type UngroundedLiteralReport, type UserStory, type UserStoryVerdict, type Worktree, type WorktreeAdapter, WorktreeAdapterError, aceProposer, acquireSingleRunLock, analyzeCrossSurfaceInteractions, applySkillPatch, assertCodeSurfaceIdentity, assertPolicyEditAuthorContextBudget, buildAnalystSurfaceDispatch, buildEvidenceVector, buildLoopProvenanceRecord, callbackGovernor, campaignBreakdown, campaignMeanComposite, classifyUngroundedLiterals, codeSurfaceIdentityMaterial, compareProposers, composeGate, compositeProposer, countSentenceEdits, createReferenceEquivalenceJudge, createRunCostLedger, defaultProductionGate, defaultRenderDiff, detectScale, dimensionRegressions, discoverEvalFixtures, emitLoopProvenance, evolutionaryProposer, extractFapoAttributionSignals, extractH2Sections, failureModeRecallJudge, fapoEscalationEntry, fapoProposer, fsCampaignStorage, fsLineageStore, gepaParetoEntry, gepaProposer, gepaReflectionEntry, gitWorktreeAdapter, haloProposer, heldOutGate, heldoutSignificance, heuristicGovernor, inMemoryCampaignStorage, isProposedCandidate, isTransientTransportFailure, labelTrustRank, lineageNodeId, llmJudge, llmPolicyEditProposer, loadEvalFixture, loadEvalFixtureScenarios, loopProvenanceSpans, makePlaybackDispatch, memLineageStore, memoryCurationProposer, neutralizationGate, neutralizeText, openAutoPr, openSearchLedger, pairHoldout, parameterSweepProposer, paretoPolicy, paretoSignificanceGate, parseSkillPatchResponse, patchEditCount, planCampaignRun, planEvalFixtureRun, policyEditProposer, powerPreflight, projectPolicyEditHistory, provenanceRecordPath, provenanceSpansPath, renderScoreboardMarkdown, resolveRunDir, resolveWorktreePath, rolloutArgumentDiff, runCampaign, runEval, runImprovementLoop, runLineage, runLineageLoop, runOptimization, runProfileMatrix, runSkillOpt, scoreDiscrimination, scoreUserStory, scoreboardSummary, selectDiscriminative, selectPolicyEditAuthorRows, sequentialDecide, sequentialPairedGate, skillOptEntry, skillOptProposer, surfaceContentHash, surfaceHash, tangleTracesRoot, traceAnalystProposer, userStoryScoreboard, validatePolicyEditCandidateRecord, validateSearchLedgerEvent, verifyCodeSurface };