@tangle-network/agent-eval 0.189.0 → 0.190.1

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 (146) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +3 -2
  3. package/dist/adapters/http.d.ts +10 -1
  4. package/dist/adapters/http.d.ts.map +1 -1
  5. package/dist/adapters/http.js +75 -7
  6. package/dist/adapters/http.js.map +1 -1
  7. package/dist/analyst/index.d.ts +66 -3
  8. package/dist/analyst/index.d.ts.map +1 -1
  9. package/dist/analyst/index.js +212 -7
  10. package/dist/analyst/index.js.map +1 -1
  11. package/dist/{benchmark-command-BhhAYstU.js → benchmark-command-BXIr_k8D.js} +12 -11
  12. package/dist/benchmark-command-BXIr_k8D.js.map +1 -0
  13. package/dist/benchmarks/index.d.ts +1 -1
  14. package/dist/benchmarks/index.js +3 -3
  15. package/dist/campaign/index.d.ts +4 -4
  16. package/dist/campaign/index.js +7 -7
  17. package/dist/{campaign-BWoSQPSU.js → campaign-B7q74uIh.js} +256 -11
  18. package/dist/campaign-B7q74uIh.js.map +1 -0
  19. package/dist/{campaign-evidence-CBlGJ3kC.js → campaign-evidence-D40-LHIL.js} +6 -5
  20. package/dist/campaign-evidence-D40-LHIL.js.map +1 -0
  21. package/dist/{chat-client-C4dg7Ng_.js → chat-client-B_ItsSBw.js} +2 -2
  22. package/dist/{chat-client-C4dg7Ng_.js.map → chat-client-B_ItsSBw.js.map} +1 -1
  23. package/dist/{chat-json-call-JVGbfx95.js → chat-json-call-CXHBvUz8.js} +2 -2
  24. package/dist/{chat-json-call-JVGbfx95.js.map → chat-json-call-CXHBvUz8.js.map} +1 -1
  25. package/dist/claim-BGOUqoMq.d.ts +38 -0
  26. package/dist/claim-BGOUqoMq.d.ts.map +1 -0
  27. package/dist/cli.js +3 -3
  28. package/dist/contract/index.d.ts +4 -4
  29. package/dist/contract/index.js +8 -8
  30. package/dist/{default-registry-m2jhbG6Q.js → default-registry-16I-Bxlf.js} +2 -2
  31. package/dist/{default-registry-m2jhbG6Q.js.map → default-registry-16I-Bxlf.js.map} +1 -1
  32. package/dist/{define-agent-eval-98sgUaC1.js → define-agent-eval-DPizhHfy.js} +4 -4
  33. package/dist/{define-agent-eval-98sgUaC1.js.map → define-agent-eval-DPizhHfy.js.map} +1 -1
  34. package/dist/{define-agent-eval-q6890HU4.d.ts → define-agent-eval-DblGedQk.d.ts} +4 -3
  35. package/dist/define-agent-eval-DblGedQk.d.ts.map +1 -0
  36. package/dist/diagnosis/index.d.ts +14 -2
  37. package/dist/diagnosis/index.d.ts.map +1 -1
  38. package/dist/diagnosis/index.js +11 -4
  39. package/dist/diagnosis/index.js.map +1 -1
  40. package/dist/{dspy-rlm-engine-DLYttKBm.js → dspy-rlm-engine-Cs5LzJ-f.js} +3 -3
  41. package/dist/{dspy-rlm-engine-DLYttKBm.js.map → dspy-rlm-engine-Cs5LzJ-f.js.map} +1 -1
  42. package/dist/{eval-campaign-C0Q1Eeo_.js → eval-campaign-CwsBn0Gm.js} +2 -2
  43. package/dist/{eval-campaign-C0Q1Eeo_.js.map → eval-campaign-CwsBn0Gm.js.map} +1 -1
  44. package/dist/experiment/index.d.ts +2 -1
  45. package/dist/experiment/index.d.ts.map +1 -1
  46. package/dist/experiment/index.js +1 -1
  47. package/dist/exporters-XMCYJlqT.d.ts +161 -0
  48. package/dist/exporters-XMCYJlqT.d.ts.map +1 -0
  49. package/dist/{external-optimizer-process-C3quNeKV.js → external-optimizer-process-BnSmrkgL.js} +3 -3
  50. package/dist/{external-optimizer-process-C3quNeKV.js.map → external-optimizer-process-BnSmrkgL.js.map} +1 -1
  51. package/dist/{external-optimizer-subprocess-BDwgKLO1.js → external-optimizer-subprocess-kbcPu5po.js} +3 -3
  52. package/dist/{external-optimizer-subprocess-BDwgKLO1.js.map → external-optimizer-subprocess-kbcPu5po.js.map} +1 -1
  53. package/dist/{feedback-trajectory-B3ZSkfAq.d.ts → feedback-trajectory-BYjh7NEM.d.ts} +2 -2
  54. package/dist/{feedback-trajectory-B3ZSkfAq.d.ts.map → feedback-trajectory-BYjh7NEM.d.ts.map} +1 -1
  55. package/dist/{index-CnJOPR_E.d.ts → index-CJv56rhX.d.ts} +673 -244
  56. package/dist/index-CJv56rhX.d.ts.map +1 -0
  57. package/dist/{index-Dk1dOUfm.d.ts → index-CWoIJHmy.d.ts} +4 -4
  58. package/dist/{index-Dk1dOUfm.d.ts.map → index-CWoIJHmy.d.ts.map} +1 -1
  59. package/dist/{index-Bcc9PkOz.d.ts → index-D4pC7Tvd.d.ts} +3 -2
  60. package/dist/index-D4pC7Tvd.d.ts.map +1 -0
  61. package/dist/{index-BqDJ6fUK.d.ts → index-I4UJGRno.d.ts} +74 -2
  62. package/dist/index-I4UJGRno.d.ts.map +1 -0
  63. package/dist/index.d.ts +66 -17
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +375 -31
  66. package/dist/index.js.map +1 -1
  67. package/dist/{journal-CDP0Vhx_.js → journal-c3qY0SDP.js} +246 -5
  68. package/dist/journal-c3qY0SDP.js.map +1 -0
  69. package/dist/{kind-factory-BGwN0nE6.js → kind-factory-ByZeXDqF.js} +2 -2
  70. package/dist/{kind-factory-BGwN0nE6.js.map → kind-factory-ByZeXDqF.js.map} +1 -1
  71. package/dist/ledger-core/index.d.ts +2 -2
  72. package/dist/ledger-core/index.js +2 -2
  73. package/dist/{llm-client-DF9bPNYA.js → llm-client-KYQBh9C8.js} +2 -2
  74. package/dist/{llm-client-DF9bPNYA.js.map → llm-client-KYQBh9C8.js.map} +1 -1
  75. package/dist/{llm-judge-M_l_WXw2.js → llm-judge-BnxAv7ed.js} +1892 -1378
  76. package/dist/llm-judge-BnxAv7ed.js.map +1 -0
  77. package/dist/mint-C9Lojt1V.d.ts +147 -0
  78. package/dist/mint-C9Lojt1V.d.ts.map +1 -0
  79. package/dist/openapi.json +1 -1
  80. package/dist/pipelines/index.d.ts +1 -1
  81. package/dist/pipelines/index.js +1 -1
  82. package/dist/pipelines/index.js.map +1 -1
  83. package/dist/{produced-state-BOY98IJO.js → produced-state-BXjXJHJT.js} +4 -4
  84. package/dist/{produced-state-BOY98IJO.js.map → produced-state-BXjXJHJT.js.map} +1 -1
  85. package/dist/{raw-provider-sink-C96CcqeG.js → raw-provider-sink-DYh4Bszw.js} +2 -2
  86. package/dist/{raw-provider-sink-C96CcqeG.js.map → raw-provider-sink-DYh4Bszw.js.map} +1 -1
  87. package/dist/{redact-DxwlUAvh.js → redact-B7rt9Zot.js} +130 -10
  88. package/dist/redact-B7rt9Zot.js.map +1 -0
  89. package/dist/{redact-DPoH1-OW.d.ts → redact-CopuMxll.d.ts} +7 -5
  90. package/dist/redact-CopuMxll.d.ts.map +1 -0
  91. package/dist/rl.d.ts +3 -2
  92. package/dist/rl.d.ts.map +1 -1
  93. package/dist/rl.js +1 -1
  94. package/dist/rollout/index.d.ts +3 -2
  95. package/dist/search-ledger-types-C9anU2DX.d.ts +489 -0
  96. package/dist/search-ledger-types-C9anU2DX.d.ts.map +1 -0
  97. package/dist/{semantic-concept-judge-Cm2K2wAo.js → semantic-concept-judge-ClB73cei.js} +6 -6
  98. package/dist/{semantic-concept-judge-Cm2K2wAo.js.map → semantic-concept-judge-ClB73cei.js.map} +1 -1
  99. package/dist/{server-C8XvqCg8.js → server-DtetGGoA.js} +2 -2
  100. package/dist/{server-C8XvqCg8.js.map → server-DtetGGoA.js.map} +1 -1
  101. package/dist/{skillopt-optimization-method-BVSB676o.js → skillopt-optimization-method-l2vDBtKp.js} +363 -18
  102. package/dist/skillopt-optimization-method-l2vDBtKp.js.map +1 -0
  103. package/dist/{spans-W0o8pbEg.d.ts → spans-BWtjk_9c.d.ts} +2 -2
  104. package/dist/{spans-W0o8pbEg.d.ts.map → spans-BWtjk_9c.d.ts.map} +1 -1
  105. package/dist/{statistical-heldout-plAHG82H.d.ts → statistical-heldout-CLpjYffJ.d.ts} +2 -36
  106. package/dist/statistical-heldout-CLpjYffJ.d.ts.map +1 -0
  107. package/dist/{store-otlp-D0AV4cvB.js → store-otlp-84DH0OEH.js} +2 -2
  108. package/dist/{store-otlp-D0AV4cvB.js.map → store-otlp-84DH0OEH.js.map} +1 -1
  109. package/dist/{store-tool-spans-8PAyhRqb.d.ts → store-tool-spans-BBXs8hDP.d.ts} +2 -2
  110. package/dist/{store-tool-spans-8PAyhRqb.d.ts.map → store-tool-spans-BBXs8hDP.d.ts.map} +1 -1
  111. package/dist/{store-tool-spans-B_0mXvYl.js → store-tool-spans-DUiqyHvR.js} +5 -5
  112. package/dist/{store-tool-spans-B_0mXvYl.js.map → store-tool-spans-DUiqyHvR.js.map} +1 -1
  113. package/dist/traces.d.ts +3 -63
  114. package/dist/traces.d.ts.map +1 -1
  115. package/dist/traces.js +6 -195
  116. package/dist/traces.js.map +1 -1
  117. package/dist/wire/index.d.ts +1 -1
  118. package/dist/wire/index.js +1 -1
  119. package/docs/adapters-observability.md +31 -27
  120. package/docs/charter.md +1 -1
  121. package/docs/design/mlbenchmarks-book-review.md +1 -1
  122. package/docs/distributed-driver.md +1 -5
  123. package/docs/experiment.md +15 -0
  124. package/docs/public-api.md +181 -71
  125. package/docs/redaction.md +7 -1
  126. package/docs/search-ledger.md +174 -0
  127. package/docs/trace-contracts.md +48 -3
  128. package/package.json +1 -1
  129. package/dist/benchmark-command-BhhAYstU.js.map +0 -1
  130. package/dist/campaign-BWoSQPSU.js.map +0 -1
  131. package/dist/campaign-evidence-CBlGJ3kC.js.map +0 -1
  132. package/dist/define-agent-eval-q6890HU4.d.ts.map +0 -1
  133. package/dist/index-Bcc9PkOz.d.ts.map +0 -1
  134. package/dist/index-BqDJ6fUK.d.ts.map +0 -1
  135. package/dist/index-CnJOPR_E.d.ts.map +0 -1
  136. package/dist/journal-CDP0Vhx_.js.map +0 -1
  137. package/dist/llm-judge-M_l_WXw2.js.map +0 -1
  138. package/dist/mint-DsS38v5c.d.ts +0 -305
  139. package/dist/mint-DsS38v5c.d.ts.map +0 -1
  140. package/dist/redact-DPoH1-OW.d.ts.map +0 -1
  141. package/dist/redact-DxwlUAvh.js.map +0 -1
  142. package/dist/search-ledger-types-D7KDVg-o.d.ts +0 -302
  143. package/dist/search-ledger-types-D7KDVg-o.d.ts.map +0 -1
  144. package/dist/skillopt-optimization-method-BVSB676o.js.map +0 -1
  145. package/dist/statistical-heldout-plAHG82H.d.ts.map +0 -1
  146. package/docs/search-history-receipts.md +0 -233
package/docs/redaction.md CHANGED
@@ -24,9 +24,11 @@ The report lists the JSON Pointer, category and detector of each change, never t
24
24
  | `share` | Also phone, IP and postal-address fields; pseudonymizes user, account, tenant, session and request ids | 64 KiB |
25
25
  | `strict` | Also prompt, completion, message, tool payload and other raw-content fields, and `data:` media | 4 KiB |
26
26
 
27
- A string that holds a credential is replaced whole with `[REDACTED:<detector>]`.
27
+ A string that holds a credential shape is replaced whole with `[REDACTED:<detector>]`.
28
28
  Personal data inside prose is replaced in place.
29
29
  Pass `knownSecrets` to remove exact values in any form: as written, base64, base64url or URL-encoded.
30
+ Each occurrence is cut out where it stands, so an error message that contains the key keeps the rest of its text.
31
+ A whole-string base64 payload that holds a known secret at any byte offset is replaced whole.
30
32
 
31
33
  ## Field names
32
34
 
@@ -34,6 +36,10 @@ Pass `knownSecrets` to remove exact values in any form: as written, base64, base
34
36
  `apiKey`, `x-api-key`, `client.secret`, `refreshToken` and `OPENAI_API_KEY` are credentials.
35
37
  Token counts and model limits are not: `inputTokens`, `outputTokens`, `max_tokens`, `token_count`, `gen_ai.usage.input_tokens` and `next_page_token` survive.
36
38
  `author`, `auth_type`, `secret_name` and `content_type` also survive, because they name or describe a value instead of holding it.
39
+
40
+ agent-interface's `looksLikeCredential` stays the candidate schemas' refusal check, and the core does not call it.
41
+ On 629,707 strings from real sessions and VerticalBench runs, it flagged 280 strings the core passes, and all 280 were `Bearer ${token}`-style code or placeholders.
42
+ The core flagged 39 credentials that `looksLikeCredential` passes.
37
43
  A number under a credential name survives, because it is a limit or a count.
38
44
 
39
45
  ## Share-safety verdict
@@ -0,0 +1,174 @@
1
+ # Search ledger
2
+
3
+ A selected prompt, profile, or patch is not a record of the search that produced it.
4
+ The search ledger is that record: a hash-chained event log of one search's nodes, edges, and cells, where every decision carries the evidence that justified it.
5
+ It is the search's only lineage record and its only resume checkpoint.
6
+
7
+ ```text
8
+ search-ledger.jsonl canonical JSONL, SHA-256 hash chain, trusted-head pin
9
+ ├── blobs/<sha256>.json surfaces, profiles, rationales, diffs, RunRecords
10
+ ├── SearchState the one projection: invariants and the read model
11
+ └── SearchHistoryReceipt bounded proof envelope for compareOptimizationMethods
12
+ ```
13
+
14
+ ## Terms
15
+
16
+ - **Node:** a content-addressed artifact, for example an AgentProfile or a prompt.
17
+ `nodeId` is `searchNodeId(searchId, artifactDigest)`, so identical content in one search is one node.
18
+ - **Edge:** the proposal that derived a node from its parents.
19
+ It records the operator, the proposer, the redacted rationale, and one parent-to-child diff per parent.
20
+ `edgeId` is `searchEdgeId(searchId, childNodeId, proposalKey)`, where the proposal key names the proposal within the search, so a resumed search recognizes its own edges.
21
+ - **Cell:** one node on one task in one split at one repeat.
22
+ `cellId` is `searchCellId(searchId, nodeId, taskId, split, rep)`.
23
+ Each attempt at a cell has the run id `cellId:attempt`, the id its RunRecord carries.
24
+ - **Unit:** the claim's independent unit, usually the task's source.
25
+ Repeats and sibling tasks of one unit average inside it before any statistic.
26
+ - **Splits:** `train` is the proposer's feedback, `selection` is private to the policy and allocator, and `test` is sealed for the claim.
27
+
28
+ ## Events
29
+
30
+ | Event | Records |
31
+ |---|---|
32
+ | `search-opened` | Subject, process, artifact kind, objective with judge and claim, the three splits with their tasks and units, policy, budget, containment, derivation, identity. First and once. |
33
+ | `operation-started`, `operation-recorded` | A non-cell spend, such as one proposer call, with its reservation and then its outcome and accounting. |
34
+ | `node-registered` | A node, its artifact digest and declared surfaces. |
35
+ | `edge-recorded` | Parents (primary first), operator (`seed`, `draft`, `improve`, `debug`, `merge`, `derive`), attribution, proposer, selection rule, rationale, diffs, label. |
36
+ | `cell-allocated` | A planned cell with its stage (`root`, `train`, `screen`, `rung`, `claim`, `external`), lane, and reservation. |
37
+ | `cell-settled` | One attempt: outcome, accounting, time, placement, identity, surface evidence, trace reference, and optionally its RunRecord blob. |
38
+ | `cell-cancelled` | A cell that will not run: `pruned`, `budget`, `deadline`, or `aborted`. |
39
+ | `node-decided` | `advanced {rung}`, `pruned`, `invalid`, `finalist`, `selected`, or `rejected`, with its rule, reason, and the estimate it used. May repeat; the latest wins. |
40
+ | `search-closed` | The stop reason and the claim: power, finalists, the selected node, and `ship`, `hold`, or `test-cannot-resolve`. Last and once. |
41
+
42
+ Edge attribution says how the parents are known and is never inferred from timing or order:
43
+
44
+ - `explicit`: the proposer that created the child emitted the edge.
45
+ - `correlated`: an importer joined an optimizer's own parent record by content digest, for example GEPA's `parentIndices`.
46
+ - `unknown`: no parent record exists, and the edge names no parent.
47
+
48
+ ## Invariants
49
+
50
+ `SearchState` applies every entry and refuses one that breaks an invariant.
51
+ The same code runs in the producer's journal, the kernel, and the Intelligence verifier.
52
+
53
+ - **Order:** `search-opened` comes first; nothing follows `search-closed`. More work on a closed search is a derived search.
54
+ - **Graph:** a node exists before an edge or cell names it, and a cell's node already has an edge. A parent registered before its child and already in the tree, so the graph is acyclic. A parent in another search appears only on a `derive` edge that matches `derivedFrom`.
55
+ - **Re-proposal:** identical content is a second edge into the existing node, counted as a re-proposal.
56
+ - **Splits:** a task belongs to one split and one unit. With `heldOutUnits`, no test unit appears in train or selection. Stages match splits: `claim` cells run on test, `screen` and `rung` on selection.
57
+ - **Seal:** only the root (the first registered node) and nodes decided `finalist` run test cells.
58
+ - **Attempts:** attempts count from 1 without gaps. A `passed` or `failed` outcome is final; only a retryable `errored` outcome admits another attempt.
59
+ - **Budget:** at every `cell-allocated` and `operation-started`, committed spend plus open reservations plus the unspent claim reserve plus the new reservation stays within `maxUsd`. Spend above a reservation is recorded as overspend, never refused. An unknown cost counts as its proven floor.
60
+ - **Completion:** `search-closed` needs every allocated cell settled or cancelled, every started operation recorded, and every node with an edge and a terminal decision. A `ship` claim needs a promoted finalist and held-out test units; any other claim keeps the root.
61
+
62
+ A ledger written under another schema tag, such as the retired `tangle.search-ledger.v1` candidate-slot format, is refused with the tag named.
63
+ It is never translated.
64
+
65
+ ## Reading the state
66
+
67
+ `ledger.state()` and every append return a `SearchStateView`.
68
+ Its `header`, `head`, `audit`, `completion`, and `closed` are plain values.
69
+ Its node, edge, cell, unit-score, and lineage reads go to the live indexes, so taking a view costs the same at any search size.
70
+ A view is valid until the ledger applies its next entry; a later read throws instead of mixing two ledger positions.
71
+
72
+ ```ts
73
+ const state = await ledger.state()
74
+ state.audit.cells // { allocated, settled, cancelled, open }
75
+ state.scoredCells(nodeId, 'selection') // [{ cellId, unitId, attempt, score }]
76
+ state.unitScores(nodeId, 'selection') // [{ unitId, sum, count, mean }]
77
+ state.lineage(record.search) // { depth, ordinal, rep, containingRunId } for mintRolloutRows
78
+ ```
79
+
80
+ A unit's mean sums its cells in cellId order, so it depends on the set of cells and not on the order they settled in.
81
+
82
+ ## Estimates
83
+
84
+ `estimateNode(state, nodeId, { against, split })` is the one statistic of a node.
85
+ It averages each node's scored cells inside their units, pairs the units both nodes scored, and runs `pairedDeltaTest` on the per-unit deltas.
86
+ Unscored cells (errored, cancelled or in flight) are absent, never zero.
87
+
88
+ | Pairs | `method` | Reported |
89
+ |---|---|---|
90
+ | 0 or 1 | `none` | nothing; the view shows "unknown (1 unit)" |
91
+ | 2 to 5 | `insufficient` | `delta` only |
92
+ | 6 to 19 | `descriptive` | `delta`, the bootstrap `interval` as spread, and `exactSignP` |
93
+ | 20 or more | `bootstrap` | `delta` and the decision-grade `interval` |
94
+
95
+ The thresholds are the library's own: `minimumPairsForPairedDeltaTest(0.95)` and `BOOTSTRAP_GATE_MIN_N`.
96
+ `units` counts the units the node scored; `pairs` counts the units both nodes scored.
97
+ `delta` is node minus `against` in the metric's units, so an improvement on a `minimize` objective is negative.
98
+ `exactSignP` is one-sided toward improvement in the objective's direction.
99
+ When every paired delta is equal, the estimate is `indeterminate`: its interval would have zero width, so it carries neither an interval nor a p-value.
100
+
101
+ `cellSetDigest` digests the contrast and exactly the cells read: id, unit, attempt and score, in cellId order.
102
+ Its first 32 bits seed the bootstrap, and `estimator` is `SEARCH_ESTIMATOR`, whose revision digests every parameter of the computation.
103
+ Equal cells therefore give equal bits in any process and in any row order.
104
+ A verifier that reads cells from its own store calls `estimateNodeFromCells` with them; `searchCellSetDigest` tells it whether a node's cells changed.
105
+
106
+ ```ts
107
+ const estimate = estimateNode(state, childId, { against: parentId, split: 'selection' })
108
+ await recorder.decideNode({ nodeId: childId, decision, basis: estimate, rule, reason })
109
+ ```
110
+
111
+ `searchPosterior(state, { split })` gives every node a normal posterior on its improvement over the root, for sampling parents.
112
+ Its mean is the node's mean per-unit improvement over the root, oriented so that larger is better in either direction.
113
+ Its variance is the search's pooled between-unit variance of those improvements divided by the node's shared units, so a node measured on one unit has a wide posterior, not none.
114
+ The root sits at exactly 0, and the pooled variance stays null until some node shares 2 units with the root.
115
+ Posterior numbers steer spend and claim nothing; a claim comes from the sealed test split.
116
+
117
+ ## Record a search
118
+
119
+ `SearchRecorder` writes each fact the moment it exists.
120
+ Ids are deterministic and every write checks the ledger's state first, so a resumed search continues its ledger instead of conflicting with its own history.
121
+ Rationales and labels are redacted with the `share` profile before they are hashed; surfaces are stored as they ran.
122
+
123
+ ```ts
124
+ import { openSearchLedger, SearchRecorder, surfaceNode } from '@tangle-network/agent-eval/campaign'
125
+
126
+ const ledger = openSearchLedger({ path: `${runDir}/search-ledger.jsonl`, searchId })
127
+ const recorder = await SearchRecorder.open({ ledger }, opening)
128
+ const root = await recorder.registerNode(surfaceNode(recorder, baseline))
129
+ await recorder.recordEdge({
130
+ childNodeId: root.nodeId, parents: [], operator: 'seed', attribution: 'explicit',
131
+ proposer: null, proposalKey: 'baseline', rationale: { unknown: 'the starting surface' }, diffs: [],
132
+ })
133
+ const cellId = await recorder.allocateCell({ nodeId: root.nodeId, taskId, split: 'selection', rep: 0, stage: 'root' })
134
+ await recorder.settleCell({ cellId, outcome, accounting, identity, runRecord })
135
+ ```
136
+
137
+ `runOptimization({ searchLedger: { ledger, identity } })` and `selfImprove` record through the recorder and return the receipt on `searchHistory`.
138
+ The loop's scenarios are its proposer's feedback, so they are the train split; its promotions are budget decisions and it makes no claim.
139
+
140
+ `gepaOptimizationMethod({ searchLedger: { identity } })` records GEPA's search when it finishes.
141
+ `importGepaPopulation` turns the population into nodes and `correlated` edges and reports collapsed duplicates.
142
+ `importExternalEvaluations` turns every callback evaluation into an `external` cell; a candidate GEPA evaluated but kept out of its population gets an `unknown` edge and is decided `pruned`.
143
+
144
+ ## Receipts and `require-complete`
145
+
146
+ `createSearchHistoryReceipt({ producerId, runId, ledger })` reads the ledger's bytes, replays them, and binds the byte digest, the audit digest, and a bounded summary.
147
+ A receipt is complete exactly when the ledger holds `search-closed`, which the completion invariant admits only when nothing is outstanding.
148
+
149
+ ```ts
150
+ const comparison = await compareOptimizationMethods({
151
+ // ...methods, partitions, dispatch, judges, runDir
152
+ searchHistoryPolicy: 'require-complete',
153
+ searchHistoryVerification: 'ledger',
154
+ storage,
155
+ })
156
+ ```
157
+
158
+ Under `require-complete`, a missing, malformed, producer-mismatched, or open search refuses the final comparison before its first test dispatch.
159
+ `searchHistoryVerification: 'ledger'` also resolves the receipt's URI through `storage.read`, checks the bytes' SHA-256 and length, and replays them.
160
+ `verifySearchHistoryReceipt` checks the envelope alone; `assertSearchHistoryMatchesState` checks it against a replayed state.
161
+
162
+ A receipt does not prove that the optimizer searched well, that a node is correct or safe, or that a winner generalizes.
163
+ Those claims need the sealed test split, the claim's power check, and held-out evaluation.
164
+
165
+ ## Files
166
+
167
+ - `src/campaign/search-ledger-types.ts`: the event and audit types.
168
+ - `src/campaign/search-ledger.ts`: schemas, canonical ordering, the codec, and `FileSearchLedger`.
169
+ - `src/campaign/search-state.ts`: `SearchState`, the invariants and read model, and the id functions.
170
+ - `src/campaign/estimate-node.ts`: `estimateNode`, `estimateNodeFromCells` and `searchPosterior`.
171
+ - `src/campaign/search-ledger-recording.ts`: `SearchRecorder` and the surface helpers.
172
+ - `src/campaign/gepa-search-import.ts`: the GEPA population and evaluation importers.
173
+ - `src/campaign/search-history-receipt.ts`: receipts and admission.
174
+ - `src/ledger-core/`: hashing, locking, durable appends, chain verification, and the trusted-head pin.
@@ -4,7 +4,7 @@ A trace contract states which path an agent run must take, and checks the record
4
4
  Two runs can give the same answer while one of them calls a forbidden tool; an output check passes both, and a contract fails the wrong one.
5
5
 
6
6
  The checker is deterministic and calls no model.
7
- It reads any span array that has `name`, `kind` or span-kind attributes, timestamps, and attributes: agent-eval `TraceStore` spans, otel-bridge spans, or OTLP rows that `traces check` maps.
7
+ It reads any span array that has `name`, `kind` or span-kind attributes, timestamps, and attributes: agent-eval `TraceStore` spans or OTLP rows that `traces check` maps.
8
8
 
9
9
  ## Write a contract
10
10
 
@@ -27,7 +27,7 @@ Write the contract as JSON and compile it with `compileTraceContractSpec`:
27
27
 
28
28
  | Key | Checks |
29
29
  |---|---|
30
- | `run.requireCompleted` | The run reached a terminal status and recorded an end time. |
30
+ | `run.requireCompleted` | The run status is `completed` and its end time is recorded. A failed or aborted run fails. |
31
31
  | `run.allowedStatuses` | The run status is one of `running`, `completed`, `failed`, `aborted`. |
32
32
  | `run.maxDurationMs` | End minus start. An unknown duration fails. |
33
33
  | `tools.required` | Each tool is called at least once. |
@@ -36,7 +36,9 @@ Write the contract as JSON and compile it with `compileTraceContractSpec`:
36
36
  | `tools.maxCalls`, `tools.maxCallsPerTool` | Ceilings on tool calls, overall and per tool. |
37
37
  | `tools.requiredOrder`, `tools.orderMode` | Each tool is called, and each comes before the next. |
38
38
  | `tools.arguments` | An RFC 6901 JSON Pointer check (`exists`, `equals`, `oneOf`, `type`) on tool-call arguments. A call without captured arguments fails. |
39
- | `llm.maxCalls`, `llm.maxTotalTokens` | Ceilings on model calls and on input plus output tokens. An unrecorded token count fails. |
39
+ | `llm.maxCalls`, `llm.maxTotalTokens` | Ceilings on model calls and on input plus output tokens. Input includes cache-read and cache-write tokens when the span records them, per the OTel GenAI definition — a cached call's prompt-token count alone omits most of what it spent. An unrecorded token count fails. |
40
+ | `tools.enforced` | The trace records the tools the harness offered the model (`gen_ai.tool.definitions`), every call is one of them, and every offered tool is in `tools.allowed`. A trace that records no offered tools fails. |
41
+ | `retries.reads`, `retries.writes` | A tool called again with the same arguments repeats its side effect. A read may repeat. A write may repeat only when every call carries the same idempotency key at `idempotencyKey`. Any other tool may not repeat. |
40
42
  | `llm.allowedModels` | Every model call names a listed model. A call without a model fails. |
41
43
  | `scope.root` | Check only the one span that matches this predicate and its descendants: one sub-agent or one search-tree node. Zero or several matches make every rule an error. |
42
44
  | `alternatives.anyOf` | Named paths, each with its own `run`, `tools`, `llm` and `rules`. At least one must pass completely, as well as the base checks. Use it for a legitimate shortcut such as a cache hit. |
@@ -50,6 +52,45 @@ An unknown key or an unknown status is an error that names the closest known wor
50
52
  `lintTraceContractSpec` reports contradictions (a tool that is both required and forbidden) and likely mistakes (an order that uses the default `start-order` mode, which passes overlapping calls).
51
53
  `explainTraceContract` states each compiled rule in one line.
52
54
 
55
+ ## Check what the harness enforced
56
+
57
+ A tool list in a prompt or a CLI flag is a declaration, not proof of enforcement.
58
+ `tools.enforced` compares three sets: the tools the contract allows, the tools the harness offered the model, and the tools the run called.
59
+ `traces` records the offered set from the `system` `init` record of `claude -p --output-format stream-json --verbose` output.
60
+ Any OTel GenAI producer that sets `gen_ai.tool.definitions` works the same way.
61
+
62
+ In a recorded run, `claude -p --tools Read,Grep` also offered eight claude.ai connector tools, so the contract below failed.
63
+ The same run with `--strict-mcp-config` offered only `Read` and `Grep`, and passed.
64
+
65
+ ```json
66
+ {
67
+ "name": "refund-desk-enforced",
68
+ "tools": { "required": ["Read"], "allowed": ["Read", "Grep"], "enforced": true }
69
+ }
70
+ ```
71
+
72
+ ## Check that a retry cannot apply twice
73
+
74
+ A write that times out may still have applied.
75
+ Calling it again with the same arguments applies it twice unless the target deduplicates on an idempotency key.
76
+
77
+ ```json
78
+ {
79
+ "name": "payments",
80
+ "retries": {
81
+ "reads": ["lookup_order"],
82
+ "writes": [{ "tool": "charge_card", "idempotencyKey": "/idempotency_key" }]
83
+ }
84
+ }
85
+ ```
86
+
87
+ The check groups calls by tool and arguments, without the idempotency key.
88
+ A group of more than one call passes for a read, and for a write only when every call carries the same key.
89
+ A repeated call of an unlisted tool fails, because its side effect is unknown.
90
+ A call without captured arguments fails when its tool is called more than once, because the checker cannot tell whether it repeats.
91
+ Grouping matches arguments exactly: a retry that re-encodes a value (`42.5` vs `"42.50"`) is not recognized as the same call.
92
+ Independent of grouping, a declared write whose call itself errored (including a timeout) and carries no idempotency key always fails — its outcome at the target is unknown, so nothing proves a retry, however it was encoded, would be safe.
93
+
53
94
  ## Rule semantics
54
95
 
55
96
  Every rule reports `pass`, `fail` or `error` in `ContractVerdict.ruleExecutions`.
@@ -68,6 +109,8 @@ A required successor that never ran fails `precedes`, a span without the timesta
68
109
  | `atMost(p, max)`, `tokensAtMost(p, max)` | Call and token ceilings. |
69
110
  | `run({ ... })` | The run status and duration. |
70
111
  | `argument(p, { pointer, check, occurrence })` | A JSON Pointer check on tool arguments. |
112
+ | `toolsOffered(declared)` | The offered tools are recorded, every call is one of them, and every offered tool is declared. |
113
+ | `retrySafe({ reads, writes })` | No call repeats a side effect that is not proven safe to repeat. |
71
114
 
72
115
  Ordering has three modes.
73
116
  `start-order` (the default) needs an `a` that started before each `b`.
@@ -87,6 +130,8 @@ const result = checkTraceContracts(await store.spans({ runId }), [contract])
87
130
 
88
131
  ## Gate CI on a contract
89
132
 
133
+ **Unreleased.** `traces check` lives on `feat/traces-check` in `/traces` (PR #128), proven against this package but not yet mergeable: it needs the traces/agent-runtime redaction-core migration first, so a `/traces` build on this package's contract API does not crash at load. Until then, call `evaluateTraceContract` / `checkTraceContracts` directly, as shown above.
134
+
90
135
  `traces check` runs a contract over a recorded trace and exits with a code a CI step can read:
91
136
 
92
137
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-eval",
3
- "version": "0.189.0",
3
+ "version": "0.190.1",
4
4
  "description": "Evaluate and improve AI agents from runs, traces, judges, and feedback. Compare candidates, cluster failures, measure lift, and gate releases.",
5
5
  "homepage": "https://github.com/tangle-network/agent-eval#readme",
6
6
  "repository": {