@namzu/sdk 62.11.0 → 62.13.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 (192) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/connector/index.d.ts +1 -1
  3. package/dist/connector/index.d.ts.map +1 -1
  4. package/dist/connector/index.js +1 -1
  5. package/dist/connector/index.js.map +1 -1
  6. package/dist/connector/mcp/client.d.ts +96 -0
  7. package/dist/connector/mcp/client.d.ts.map +1 -1
  8. package/dist/connector/mcp/client.js +488 -13
  9. package/dist/connector/mcp/client.js.map +1 -1
  10. package/dist/connector/mcp/index.d.ts +1 -0
  11. package/dist/connector/mcp/index.d.ts.map +1 -1
  12. package/dist/connector/mcp/index.js +1 -0
  13. package/dist/connector/mcp/index.js.map +1 -1
  14. package/dist/connector/mcp/tasks.d.ts +40 -0
  15. package/dist/connector/mcp/tasks.d.ts.map +1 -0
  16. package/dist/connector/mcp/tasks.js +55 -0
  17. package/dist/connector/mcp/tasks.js.map +1 -0
  18. package/dist/constants/mcp/index.d.ts +1 -1
  19. package/dist/constants/mcp/index.d.ts.map +1 -1
  20. package/dist/constants/mcp/index.js +5 -0
  21. package/dist/constants/mcp/index.js.map +1 -1
  22. package/dist/eval/bootstrap.d.ts +122 -0
  23. package/dist/eval/bootstrap.d.ts.map +1 -0
  24. package/dist/eval/bootstrap.js +227 -0
  25. package/dist/eval/bootstrap.js.map +1 -0
  26. package/dist/eval/compare.d.ts +109 -0
  27. package/dist/eval/compare.d.ts.map +1 -0
  28. package/dist/eval/compare.js +324 -0
  29. package/dist/eval/compare.js.map +1 -0
  30. package/dist/eval/disclosure.d.ts +70 -0
  31. package/dist/eval/disclosure.d.ts.map +1 -0
  32. package/dist/eval/disclosure.js +64 -0
  33. package/dist/eval/disclosure.js.map +1 -0
  34. package/dist/eval/experiment.d.ts +23 -0
  35. package/dist/eval/experiment.d.ts.map +1 -1
  36. package/dist/eval/experiment.js +87 -15
  37. package/dist/eval/experiment.js.map +1 -1
  38. package/dist/eval/from-turn.d.ts.map +1 -1
  39. package/dist/eval/from-turn.js +5 -0
  40. package/dist/eval/from-turn.js.map +1 -1
  41. package/dist/eval/index.d.ts +13 -2
  42. package/dist/eval/index.d.ts.map +1 -1
  43. package/dist/eval/index.js +6 -1
  44. package/dist/eval/index.js.map +1 -1
  45. package/dist/eval/report-stats.d.ts +77 -0
  46. package/dist/eval/report-stats.d.ts.map +1 -0
  47. package/dist/eval/report-stats.js +184 -0
  48. package/dist/eval/report-stats.js.map +1 -0
  49. package/dist/eval/run-options.d.ts +30 -0
  50. package/dist/eval/run-options.d.ts.map +1 -0
  51. package/dist/eval/run-options.js +20 -0
  52. package/dist/eval/run-options.js.map +1 -0
  53. package/dist/eval/run-summary.d.ts +36 -0
  54. package/dist/eval/run-summary.d.ts.map +1 -0
  55. package/dist/eval/run-summary.js +57 -0
  56. package/dist/eval/run-summary.js.map +1 -0
  57. package/dist/eval/scorers.d.ts +32 -0
  58. package/dist/eval/scorers.d.ts.map +1 -1
  59. package/dist/eval/scorers.js +77 -0
  60. package/dist/eval/scorers.js.map +1 -1
  61. package/dist/eval/types.d.ts +74 -0
  62. package/dist/eval/types.d.ts.map +1 -1
  63. package/dist/manager/session/turn-recorder.d.ts +3 -0
  64. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  65. package/dist/manager/session/turn-recorder.js +20 -5
  66. package/dist/manager/session/turn-recorder.js.map +1 -1
  67. package/dist/public-runtime.d.ts +5 -2
  68. package/dist/public-runtime.d.ts.map +1 -1
  69. package/dist/public-runtime.js +7 -2
  70. package/dist/public-runtime.js.map +1 -1
  71. package/dist/public-types.d.ts +2 -0
  72. package/dist/public-types.d.ts.map +1 -1
  73. package/dist/registry/command/kernel-commands.d.ts +3 -0
  74. package/dist/registry/command/kernel-commands.d.ts.map +1 -1
  75. package/dist/registry/command/kernel-commands.js +76 -5
  76. package/dist/registry/command/kernel-commands.js.map +1 -1
  77. package/dist/runtime/query/abandon-turn.d.ts.map +1 -1
  78. package/dist/runtime/query/abandon-turn.js +2 -2
  79. package/dist/runtime/query/abandon-turn.js.map +1 -1
  80. package/dist/runtime/query/index.d.ts.map +1 -1
  81. package/dist/runtime/query/index.js +15 -0
  82. package/dist/runtime/query/index.js.map +1 -1
  83. package/dist/runtime/query/iteration/index.d.ts +18 -1
  84. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  85. package/dist/runtime/query/iteration/index.js +80 -1
  86. package/dist/runtime/query/iteration/index.js.map +1 -1
  87. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  88. package/dist/runtime/query/iteration/phases/tool-review.js +18 -1
  89. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  90. package/dist/runtime/query/prelude-lease.d.ts.map +1 -1
  91. package/dist/runtime/query/prelude-lease.js +2 -2
  92. package/dist/runtime/query/prelude-lease.js.map +1 -1
  93. package/dist/runtime/query/turn/answer-claims.d.ts +52 -0
  94. package/dist/runtime/query/turn/answer-claims.d.ts.map +1 -0
  95. package/dist/runtime/query/turn/answer-claims.js +348 -0
  96. package/dist/runtime/query/turn/answer-claims.js.map +1 -0
  97. package/dist/runtime/query/turn/outcome-ledger.d.ts +7 -0
  98. package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -1
  99. package/dist/runtime/query/turn/outcome-ledger.js +13 -0
  100. package/dist/runtime/query/turn/outcome-ledger.js.map +1 -1
  101. package/dist/store/goal/index.d.ts +3 -1
  102. package/dist/store/goal/index.d.ts.map +1 -1
  103. package/dist/store/goal/index.js +6 -0
  104. package/dist/store/goal/index.js.map +1 -1
  105. package/dist/store/session-log/index.d.ts +1 -0
  106. package/dist/store/session-log/index.d.ts.map +1 -1
  107. package/dist/store/session-log/index.js +1 -0
  108. package/dist/store/session-log/index.js.map +1 -1
  109. package/dist/store/session-log/turn-holder.d.ts +26 -0
  110. package/dist/store/session-log/turn-holder.d.ts.map +1 -0
  111. package/dist/store/session-log/turn-holder.js +79 -0
  112. package/dist/store/session-log/turn-holder.js.map +1 -0
  113. package/dist/telemetry/runtime-accessors.d.ts +14 -0
  114. package/dist/telemetry/runtime-accessors.d.ts.map +1 -1
  115. package/dist/telemetry/runtime-accessors.js +32 -0
  116. package/dist/telemetry/runtime-accessors.js.map +1 -1
  117. package/dist/tools/goal/confirmation.d.ts +154 -0
  118. package/dist/tools/goal/confirmation.d.ts.map +1 -0
  119. package/dist/tools/goal/confirmation.js +290 -0
  120. package/dist/tools/goal/confirmation.js.map +1 -0
  121. package/dist/tools/goal/index.d.ts +4 -1
  122. package/dist/tools/goal/index.d.ts.map +1 -1
  123. package/dist/tools/goal/index.js +27 -2
  124. package/dist/tools/goal/index.js.map +1 -1
  125. package/dist/tools/hand-back/index.d.ts +48 -0
  126. package/dist/tools/hand-back/index.d.ts.map +1 -0
  127. package/dist/tools/hand-back/index.js +129 -0
  128. package/dist/tools/hand-back/index.js.map +1 -0
  129. package/dist/tools/schedules/schedule-tool.d.ts.map +1 -1
  130. package/dist/tools/schedules/schedule-tool.js +15 -1
  131. package/dist/tools/schedules/schedule-tool.js.map +1 -1
  132. package/dist/tools/schedules/types.d.ts +10 -0
  133. package/dist/tools/schedules/types.d.ts.map +1 -1
  134. package/dist/types/connector/mcp.d.ts +18 -0
  135. package/dist/types/connector/mcp.d.ts.map +1 -1
  136. package/dist/types/goal/index.d.ts +12 -0
  137. package/dist/types/goal/index.d.ts.map +1 -1
  138. package/dist/types/session/config.d.ts +8 -0
  139. package/dist/types/session/config.d.ts.map +1 -1
  140. package/dist/types/session/hand-back.d.ts +29 -0
  141. package/dist/types/session/hand-back.d.ts.map +1 -0
  142. package/dist/types/session/hand-back.js +11 -0
  143. package/dist/types/session/hand-back.js.map +1 -0
  144. package/dist/types/session/outcome.d.ts +37 -0
  145. package/dist/types/session/outcome.d.ts.map +1 -1
  146. package/dist/types/session/outcome.js.map +1 -1
  147. package/dist/types/session/turn.d.ts +19 -1
  148. package/dist/types/session/turn.d.ts.map +1 -1
  149. package/dist/types/session/turn.js.map +1 -1
  150. package/package.json +1 -1
  151. package/src/connector/index.ts +1 -0
  152. package/src/connector/mcp/client.ts +531 -14
  153. package/src/connector/mcp/index.ts +1 -0
  154. package/src/connector/mcp/tasks.ts +81 -0
  155. package/src/constants/mcp/index.ts +6 -1
  156. package/src/eval/bootstrap.ts +314 -0
  157. package/src/eval/compare.ts +473 -0
  158. package/src/eval/disclosure.ts +136 -0
  159. package/src/eval/experiment.ts +127 -15
  160. package/src/eval/from-turn.ts +5 -0
  161. package/src/eval/index.ts +51 -0
  162. package/src/eval/report-stats.ts +257 -0
  163. package/src/eval/run-options.ts +49 -0
  164. package/src/eval/run-summary.ts +104 -0
  165. package/src/eval/scorers.ts +83 -0
  166. package/src/eval/types.ts +76 -0
  167. package/src/manager/session/turn-recorder.ts +25 -4
  168. package/src/public-runtime.ts +53 -0
  169. package/src/public-types.ts +9 -0
  170. package/src/registry/command/kernel-commands.ts +94 -6
  171. package/src/runtime/query/abandon-turn.ts +2 -1
  172. package/src/runtime/query/index.ts +18 -0
  173. package/src/runtime/query/iteration/index.ts +85 -1
  174. package/src/runtime/query/iteration/phases/tool-review.ts +24 -1
  175. package/src/runtime/query/prelude-lease.ts +7 -2
  176. package/src/runtime/query/turn/answer-claims.ts +413 -0
  177. package/src/runtime/query/turn/outcome-ledger.ts +12 -0
  178. package/src/store/goal/index.ts +11 -0
  179. package/src/store/session-log/index.ts +8 -0
  180. package/src/store/session-log/turn-holder.ts +95 -0
  181. package/src/telemetry/runtime-accessors.ts +29 -0
  182. package/src/tools/goal/confirmation.ts +375 -0
  183. package/src/tools/goal/index.ts +37 -1
  184. package/src/tools/hand-back/index.ts +150 -0
  185. package/src/tools/schedules/schedule-tool.ts +19 -1
  186. package/src/tools/schedules/types.ts +10 -0
  187. package/src/types/connector/mcp.ts +18 -0
  188. package/src/types/goal/index.ts +13 -0
  189. package/src/types/session/config.ts +8 -0
  190. package/src/types/session/hand-back.ts +31 -0
  191. package/src/types/session/outcome.ts +42 -0
  192. package/src/types/session/turn.ts +19 -1
@@ -0,0 +1,104 @@
1
+ import { UNKNOWN } from './disclosure.js'
2
+ import type { RunMode } from './disclosure.js'
3
+ import {
4
+ type RunStats,
5
+ describeIntervals,
6
+ describeSplits,
7
+ groupRuns,
8
+ statsOver,
9
+ } from './report-stats.js'
10
+ import type { ExperimentReport } from './types.js'
11
+
12
+ /** What produced a whole run, across every suite in it. */
13
+ export interface RunDisclosure {
14
+ readonly harnessVersion: string
15
+ readonly sdkVersion: string
16
+ readonly generatedAt: string
17
+ readonly commit: string
18
+ readonly seed: number
19
+ /** Repeats per case; the largest across suites when they differ. */
20
+ readonly repeats: number
21
+ readonly suites: number
22
+ readonly cases: number
23
+ readonly runs: number
24
+ /** `mixed` when suites that used a model differ in kind; a suite with no model at all does not count. */
25
+ readonly mode: RunMode | 'mixed'
26
+ }
27
+
28
+ /** The numbers for every case of every suite taken together. */
29
+ export interface RunSummary extends Omit<RunStats, 'caseSummaries' | 'reliability'> {
30
+ readonly disclosure: RunDisclosure
31
+ /** Present only when cases were repeated. */
32
+ readonly reliability?: RunStats['reliability']
33
+ }
34
+
35
+ export function summarizeRun(
36
+ entries: ReadonlyArray<{ suite: string; report: ExperimentReport }>,
37
+ context: {
38
+ seed: number
39
+ harnessVersion: string
40
+ commit: string
41
+ generatedAt: string
42
+ sdkVersion: string
43
+ },
44
+ ): RunSummary {
45
+ // A case is named by its suite and its id: two suites may reuse a name.
46
+ const cases = entries.flatMap(({ suite, report }) =>
47
+ groupRuns(report.cases ?? [], (name) => `${suite}\u0000${name}`),
48
+ )
49
+ const { caseSummaries: _perCase, reliability, ...stats } = statsOver(cases, context.seed)
50
+ const repeats = Math.max(1, ...entries.map((e) => e.report.repeats ?? 1))
51
+ // A suite that used no model at all says nothing about whether a model
52
+ // ran, so it does not make a run "mixed" with the suites that did.
53
+ const all = entries.map((e) => e.report.disclosure?.mode ?? UNKNOWN)
54
+ const modes = new Set(all.filter((m) => m !== 'no-model'))
55
+ const mode: RunMode | 'mixed' =
56
+ modes.size === 0
57
+ ? all.length === 0
58
+ ? UNKNOWN
59
+ : 'no-model'
60
+ : modes.size === 1
61
+ ? ([...modes][0] as RunMode)
62
+ : 'mixed'
63
+ return {
64
+ ...stats,
65
+ ...(repeats > 1 ? { reliability } : {}),
66
+ disclosure: {
67
+ harnessVersion: context.harnessVersion,
68
+ sdkVersion: context.sdkVersion,
69
+ generatedAt: context.generatedAt,
70
+ commit: context.commit,
71
+ seed: context.seed,
72
+ repeats,
73
+ suites: entries.length,
74
+ cases: stats.cases,
75
+ runs: stats.runs,
76
+ mode,
77
+ },
78
+ }
79
+ }
80
+
81
+ export function formatRunSummary(summary: RunSummary): string {
82
+ const d = summary.disclosure
83
+ const lines = [
84
+ `Whole run: ${summary.passed}/${summary.runs} runs passed across ${d.suites} suite${d.suites === 1 ? '' : 's'}, ${summary.cases} case${summary.cases === 1 ? '' : 's'}${d.repeats > 1 ? ` x ${d.repeats} repeats` : ''}`,
85
+ ]
86
+ for (const line of describeIntervals({
87
+ mean: summary.mean,
88
+ passRate: summary.passRate,
89
+ intervals: summary.intervals,
90
+ })) {
91
+ lines.push(` ${line}`)
92
+ }
93
+ if (summary.reliability) {
94
+ const r = summary.reliability
95
+ lines.push(
96
+ ` reliability across repeats: ${r.passedEveryRepeat} of ${r.cases} cases passed in every one of ${d.repeats} repeats${r.fraction === null ? '' : ` (${(r.fraction * 100).toFixed(1)}%)`}`,
97
+ )
98
+ }
99
+ for (const line of describeSplits(summary.splits)) lines.push(` ${line}`)
100
+ lines.push(
101
+ ` produced by: namzu eval ${d.harnessVersion}, sdk ${d.sdkVersion}, commit ${d.commit}, ${d.generatedAt}, ${d.mode}`,
102
+ )
103
+ return lines.join('\n')
104
+ }
@@ -1,3 +1,5 @@
1
+ import type { HandBackKind } from '../types/session/hand-back.js'
2
+ import type { TurnOutcomeVerdict } from '../types/session/outcome.js'
1
3
  import type { EvalCase, EvalTurn, Score, Scorer } from './types.js'
2
4
 
3
5
  /**
@@ -152,6 +154,87 @@ export function containsScorer(...required: string[]): Scorer {
152
154
  }
153
155
  }
154
156
 
157
+ /**
158
+ * The turn's outcome record has one of the given verdicts.
159
+ *
160
+ * Reads the record the kernel settled from what actually ran, not the answer's
161
+ * words. A turn with no record scores unavailable: there is nothing to read,
162
+ * which is not the same as a wrong verdict.
163
+ */
164
+ export function outcomeScorer(verdicts: readonly TurnOutcomeVerdict[]): Scorer {
165
+ return {
166
+ name: 'outcome',
167
+ // The record either says one of the verdicts or it does not.
168
+ severity: 'gate',
169
+ score(turn: EvalTurn): Score {
170
+ const outcome = turn.outcome
171
+ if (!outcome) {
172
+ return { score: 0, unavailable: true, reason: 'the turn has no outcome record' }
173
+ }
174
+ const ok = verdicts.includes(outcome.verdict)
175
+ return {
176
+ score: ok ? 1 : 0,
177
+ reason: ok
178
+ ? `verdict ${outcome.verdict}`
179
+ : `verdict ${outcome.verdict}, expected one of ${verdicts.join(', ')}`,
180
+ details: { verdict: outcome.verdict, expected: verdicts },
181
+ }
182
+ },
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Whether the answer's plain check claims agree with the turn's outcome
188
+ * record: 1 when `compareAnswerToOutcome` found no mismatch, 0 when it found
189
+ * one, with the claim and what the record holds in the detail.
190
+ *
191
+ * Pass `{ expectMismatch: true }` for a case that is built to contradict
192
+ * itself, so the suite proves the comparison fires as well as that it stays
193
+ * silent.
194
+ */
195
+ export function claimMismatchScorer(opts: { expectMismatch?: boolean } = {}): Scorer {
196
+ const expectMismatch = opts.expectMismatch === true
197
+ return {
198
+ name: 'claim-mismatch',
199
+ severity: 'gate',
200
+ score(turn: EvalTurn): Score {
201
+ const found = turn.claimMismatch
202
+ const ok = expectMismatch ? found !== undefined : found === undefined
203
+ return {
204
+ score: ok ? 1 : 0,
205
+ reason: found
206
+ ? `${found.said}, but the record: ${found.recordSays}`
207
+ : 'no claim in the answer contradicts the record',
208
+ details: { claimMismatch: found ?? null, expectMismatch },
209
+ }
210
+ },
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Score whether the turn was handed back to a person.
216
+ *
217
+ * Pass `{ expectKind }` for a case built so the model hands the task back, and
218
+ * the scorer checks it did, and for that kind; with nothing it checks the turn
219
+ * was not handed back. A gate: a scripted suite pins the kernel's side of it.
220
+ */
221
+ export function handedBackScorer(opts: { expectKind?: HandBackKind } = {}): Scorer {
222
+ return {
223
+ name: 'handed-back',
224
+ severity: 'gate',
225
+ score(turn: EvalTurn): Score {
226
+ const found = turn.handedBack
227
+ const ok =
228
+ opts.expectKind === undefined ? found === undefined : found?.kind === opts.expectKind
229
+ return {
230
+ score: ok ? 1 : 0,
231
+ reason: found ? `handed back (${found.kind}): ${found.blocker}` : 'not handed back',
232
+ details: { handedBack: found ?? null, expectKind: opts.expectKind ?? null },
233
+ }
234
+ },
235
+ }
236
+ }
237
+
155
238
  /**
156
239
  * Judge the turn with a caller-supplied predicate.
157
240
  *
package/src/eval/types.ts CHANGED
@@ -1,6 +1,20 @@
1
+ import type { AnswerClaimMismatch, TurnOutcome } from '../types/session/outcome.js'
1
2
  import type { StepResult } from '../types/session/step.js'
3
+ import type { CaseIntervals } from './bootstrap.js'
4
+ import type { ReportDisclosure } from './disclosure.js'
5
+ import type { RepeatReliability, SplitSummary } from './report-stats.js'
2
6
  import type { ScoreUncertainty } from './uncertainty.js'
3
7
 
8
+ /**
9
+ * Which part of a suite a case belongs to.
10
+ *
11
+ * `development` cases are the ones the harness was shaped against; a number
12
+ * measured on them says how well it fits them. `held-out` cases were not
13
+ * looked at while it was shaped, and only they say how it does on cases it
14
+ * has not seen.
15
+ */
16
+ export type EvalSplit = 'development' | 'held-out'
17
+
4
18
  /**
5
19
  * One case in a dataset: an input, and what a good run looks like.
6
20
  *
@@ -19,6 +33,11 @@ export interface EvalCase<TInput = unknown> {
19
33
  expectedTools?: readonly string[]
20
34
  /** Per-case scorer overrides; falls back to the dataset's scorers. */
21
35
  scorers?: readonly Scorer[]
36
+ /**
37
+ * `development` or `held-out`. Left out, the case is reported as
38
+ * unassigned. See {@link EvalSplit}.
39
+ */
40
+ split?: EvalSplit
22
41
  }
23
42
 
24
43
  /** What actually happened, handed to every scorer. */
@@ -36,6 +55,18 @@ export interface EvalTurn {
36
55
  durationMs: number
37
56
  /** Set when the turn threw rather than settling. */
38
57
  error?: string
58
+ /**
59
+ * What the turn changed and what checked it, from the kernel's own record of
60
+ * its tool calls. Absent for a turn that never ran the loop.
61
+ */
62
+ outcome?: TurnOutcome
63
+ /**
64
+ * A plain claim in the answer that the outcome record contradicts, found by
65
+ * `compareAnswerToOutcome`. Absent when there is none.
66
+ */
67
+ claimMismatch?: AnswerClaimMismatch
68
+ /** The model handed the task back to a person and the turn ended there. Absent otherwise. */
69
+ handedBack?: import('../types/session/hand-back.js').HandBack
39
70
  }
40
71
 
41
72
  /**
@@ -143,6 +174,10 @@ export interface CaseResult {
143
174
  * mattered. Absent when no gate missed.
144
175
  */
145
176
  failedGates?: readonly string[]
177
+ /** Which run of this case this is, from 1. Set on every run of a report that has `repeats`. */
178
+ repeat?: number
179
+ /** The case's declared split, copied here so a report can be cut by it. */
180
+ split?: EvalSplit
146
181
  }
147
182
 
148
183
  export interface ExperimentReport {
@@ -177,4 +212,45 @@ export interface ExperimentReport {
177
212
  */
178
213
  byScorer: Record<string, number>
179
214
  durationMs: number
215
+ /**
216
+ * Times each case ran. When it is above 1, `cases` holds every run of
217
+ * every case, in case order, each marked with its `repeat`, and `passed`,
218
+ * `failed` and `inconclusive` count runs. Absent in a report written before
219
+ * repeats existed, which is read as 1.
220
+ */
221
+ repeats?: number
222
+ /** Seed of the bootstrap behind {@link intervals}. */
223
+ seed?: number
224
+ /**
225
+ * Passed runs over judged runs. An inconclusive run is in neither number,
226
+ * so it cannot pull the rate either way.
227
+ */
228
+ passRate?: number
229
+ /**
230
+ * 95% intervals for {@link mean} and {@link passRate}, by a bootstrap over
231
+ * cases. Absent from a report that predates it.
232
+ */
233
+ intervals?: CaseIntervals
234
+ /** Per case: how many of its repeats passed. Present when `repeats` is above 1. */
235
+ caseSummaries?: readonly CaseRepeatSummary[]
236
+ /** Reliability across repeats. Present when `repeats` is above 1. */
237
+ reliability?: RepeatReliability
238
+ /** Numbers per split. Keys appear only for splits that have cases. */
239
+ splits?: Partial<Record<EvalSplit | 'unassigned', SplitSummary>>
240
+ /** What produced this report. */
241
+ disclosure?: ReportDisclosure
242
+ }
243
+
244
+ /** How the repeats of one case went. */
245
+ export interface CaseRepeatSummary {
246
+ case: string
247
+ split?: EvalSplit
248
+ repeats: number
249
+ passed: number
250
+ failed: number
251
+ inconclusive: number
252
+ /** Mean score over the judged repeats; null when none was judged. */
253
+ mean: number | null
254
+ /** Every repeat passed. An inconclusive repeat means this is false. */
255
+ passedEveryRepeat: boolean
180
256
  }
@@ -6,6 +6,7 @@ import {
6
6
  import { EMPTY_TOKEN_USAGE } from '../../constants/limits.js'
7
7
  import { NAMZU } from '../../constants/telemetry/index.js'
8
8
  import { resolveModelPricing } from '../../pricing/index.js'
9
+ import { compareAnswerToOutcome } from '../../runtime/query/turn/answer-claims.js'
9
10
  import type { SessionTokenBudget } from '../../store/budget/index.js'
10
11
  import type { SessionCheckpointStore } from '../../store/checkpoint/index.js'
11
12
  import { foldSessionLog } from '../../store/session-log/incremental.js'
@@ -16,6 +17,7 @@ import {
16
17
  type SessionLogHead,
17
18
  type SessionLogWriteHealth,
18
19
  SessionMessageFold,
20
+ turnHolder,
19
21
  withMessageId,
20
22
  } from '../../store/session-log/index.js'
21
23
  import type { SpillRef } from '../../store/session-log/spill.js'
@@ -39,6 +41,7 @@ import {
39
41
  type SessionEventType,
40
42
  isEphemeralEvent,
41
43
  } from '../../types/session/events.js'
44
+ import type { HandBack } from '../../types/session/hand-back.js'
42
45
  import type { ProjectId, TopicId } from '../../types/session/ids.js'
43
46
  import type { TurnOutcome } from '../../types/session/outcome.js'
44
47
  import type { SessionRecord } from '../../types/session/records.js'
@@ -515,6 +518,12 @@ export class TurnRecorder {
515
518
  else this.#turn.outcome = outcome
516
519
  }
517
520
 
521
+ /** The model handed the task back; set once, as the turn ends. */
522
+ setHandedBack(handBack: HandBack | undefined): void {
523
+ if (handBack === undefined) delete this.#turn.handedBack
524
+ else this.#turn.handedBack = handBack
525
+ }
526
+
518
527
  setSteps(steps: readonly StepResult[]): void {
519
528
  this.#turn.steps = steps
520
529
  }
@@ -611,6 +620,11 @@ export class TurnRecorder {
611
620
  settlement(status: TurnSettlement['status']): LiveTurnSettlement {
612
621
  const turn = this.getTurn()
613
622
  const resultMessageId = this.#lastAssistantEntry()?.id
623
+ // A task handed back says what blocks it in the model's words; those are not
624
+ // an answer to read claims out of.
625
+ const claimMismatch = turn.handedBack
626
+ ? undefined
627
+ : compareAnswerToOutcome(turn.result, turn.outcome)
614
628
  return {
615
629
  status,
616
630
  iterations: turn.currentIteration,
@@ -624,6 +638,8 @@ export class TurnRecorder {
624
638
  abandonedTaskIds: [...(turn.abandonedTaskIds ?? [])],
625
639
  abandonedJobIds: [...(turn.abandonedJobIds ?? [])],
626
640
  ...(turn.outcome ? { outcome: turn.outcome } : {}),
641
+ ...(claimMismatch ? { claimMismatch } : {}),
642
+ ...(turn.handedBack ? { handedBack: turn.handedBack } : {}),
627
643
  }
628
644
  }
629
645
 
@@ -636,8 +652,7 @@ export class TurnRecorder {
636
652
  async open(options: TurnRecorderOpenOptions): Promise<RecordedMessage[]> {
637
653
  if (this.#phase !== 'new') throw new Error('TurnRecorder.open was already called.')
638
654
  this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_TURN_LEASE_TTL_MS
639
- this.#leaseHolder =
640
- options.holder ?? options.lease?.holder ?? `namzu:${process.pid}:${this.turnId}`
655
+ this.#leaseHolder = options.holder ?? options.lease?.holder ?? turnHolder(this.turnId)
641
656
  try {
642
657
  if (options.lease) {
643
658
  this.#lease = options.lease
@@ -1560,9 +1575,15 @@ export function eventDraft(
1560
1575
  ...payload
1561
1576
  } = event as SessionEvent & Record<string, unknown>
1562
1577
  const draft = JSON.parse(JSON.stringify(payload)) as Record<string, unknown>
1563
- // The outcome rides the live settlement only; the log keeps it in its own record.
1578
+ // The outcome, the claim mismatch and the hand-back ride the live settlement
1579
+ // only; the log keeps the outcome in its own record, the other two nowhere.
1564
1580
  if (draft.settlement !== undefined && typeof draft.settlement === 'object') {
1565
- const { outcome: _outcome, ...settlement } = draft.settlement as Record<string, unknown>
1581
+ const {
1582
+ outcome: _outcome,
1583
+ claimMismatch: _claimMismatch,
1584
+ handedBack: _handedBack,
1585
+ ...settlement
1586
+ } = draft.settlement as Record<string, unknown>
1566
1587
  draft.settlement = settlement
1567
1588
  }
1568
1589
  return { ...draft, ...(turnId ? { turnId } : {}) } as Parameters<SessionLog['append']>[1]
@@ -283,6 +283,14 @@ export {
283
283
  SESSION_GOAL_TOOL_NAMES,
284
284
  buildSessionGoalTools,
285
285
  } from './tools/goal/index.js'
286
+ export {
287
+ DEFAULT_GOAL_REFUSALS,
288
+ GoalConfirmation,
289
+ NO_CHECK_REASON_CODE,
290
+ decideGoalConfirmation,
291
+ describeGoalEnding,
292
+ recordSentence,
293
+ } from './tools/goal/confirmation.js'
286
294
  export { SessionGoalActivation } from './manager/goal/activation.js'
287
295
  export { buildToolResultHashes, hashToolResult } from './utils/hash.js'
288
296
  export {
@@ -902,6 +910,7 @@ export {
902
910
  isRecognizedModernError,
903
911
  isResourceNotFoundError,
904
912
  isUnsupportedProtocolVersionError,
913
+ MCP_TASKS_EXTENSION,
905
914
  LocalExecutionContext,
906
915
  mcpEraCacheKey,
907
916
  MCPHttpStatusError,
@@ -1399,11 +1408,28 @@ export { anyOf, hasToolCall, stepCountIs } from './types/session/step.js'
1399
1408
  // ─── evaluation harness ──────────────────────────────────────────────────
1400
1409
 
1401
1410
  export {
1411
+ BOOTSTRAP_RESAMPLES,
1412
+ DEFAULT_EVAL_SEED,
1413
+ EvalUsageError,
1414
+ MIN_CASES_FOR_INTERVAL,
1415
+ bootstrapCases,
1416
+ bootstrapPairedDifference,
1417
+ compareEvalReports,
1418
+ exactSignTestP,
1419
+ formatEvalComparison,
1420
+ formatRunSummary,
1421
+ isEvalUsageError,
1422
+ loadEvalReportFile,
1423
+ summarizeRun,
1424
+ withEvalRunOptions,
1402
1425
  compareHarnessTrials,
1403
1426
  reviewHarnessCandidate,
1427
+ claimMismatchScorer,
1428
+ handedBackScorer,
1404
1429
  completionScorer,
1405
1430
  containsScorer,
1406
1431
  customScorer,
1432
+ outcomeScorer,
1407
1433
  evalTurnFromQuery,
1408
1434
  evalTurnFromTurn,
1409
1435
  formatReport,
@@ -1413,6 +1439,33 @@ export {
1413
1439
  trajectoryScorer,
1414
1440
  } from './eval/index.js'
1415
1441
 
1442
+ // ─── answer against record ───────────────────────────────────────────────
1443
+
1444
+ export {
1445
+ ANSWER_CLAIM_MAX_CHARS,
1446
+ ANSWER_CLAIM_MAX_QUOTE_CHARS,
1447
+ compareAnswerToOutcome,
1448
+ describeAnswerClaimMismatch,
1449
+ } from './runtime/query/turn/answer-claims.js'
1450
+
1451
+ // ─── handing the task back ───────────────────────────────────────────────
1452
+
1453
+ export {
1454
+ HAND_BACK_GUIDANCE,
1455
+ HAND_BACK_GUIDANCE_CONTRIBUTION_ID,
1456
+ HAND_BACK_MAX_CHARS,
1457
+ HAND_BACK_RECEIPT,
1458
+ HAND_BACK_SIBLING_REFUSAL,
1459
+ HAND_BACK_TOOL_NAME,
1460
+ HandBackInputSchema,
1461
+ buildHandBackTool,
1462
+ describeHandBack,
1463
+ handBackKindSentence,
1464
+ handBackModelLines,
1465
+ handBackReason,
1466
+ parseHandBack,
1467
+ } from './tools/hand-back/index.js'
1468
+
1416
1469
  // ─── metrics ─────────────────────────────────────────────────────────────
1417
1470
  //
1418
1471
  // Exported so a host can record its own measurements onto the same series
@@ -406,7 +406,16 @@ export type {
406
406
  SessionGoalStore,
407
407
  } from './store/goal/index.js'
408
408
  export type { ResolveGoalRoundAuthority } from './tools/goal/index.js'
409
+ export type {
410
+ GoalConfirmationOptions,
411
+ GoalDecision,
412
+ GoalDecisionInput,
413
+ GoalEnding,
414
+ GoalEndingReport,
415
+ GoalStatement,
416
+ } from './tools/goal/confirmation.js'
409
417
  export type { ActiveSessionGoal } from './manager/goal/activation.js'
418
+ export type { HandBack, HandBackKind } from './types/session/hand-back.js'
410
419
 
411
420
  export type {
412
421
  AgentHandle,
@@ -8,7 +8,7 @@ import {
8
8
  StaleGoalError,
9
9
  } from '../../store/goal/index.js'
10
10
  import type { HostCommandDescriptor } from '../../types/command/index.js'
11
- import type { GoalRef, SessionGoal } from '../../types/goal/index.js'
11
+ import type { GoalConfirmedBy, GoalRef, SessionGoal } from '../../types/goal/index.js'
12
12
  import type { SessionId, TenantId } from '../../types/ids/index.js'
13
13
  import type { Skill } from '../../types/skills/index.js'
14
14
  import { isInvocableBy, skillInvocation } from '../../types/skills/index.js'
@@ -48,9 +48,12 @@ export interface GoalCommandScope {
48
48
  readonly tenantId: TenantId
49
49
  /** Process-local permission for automatic continuation. */
50
50
  readonly activation?: SessionGoalActivation
51
+ /** What a goal that does not choose is confirmed by; shown by `/goal status`. Default `model`. */
52
+ readonly defaultConfirmedBy?: GoalConfirmedBy
51
53
  }
52
54
 
53
- const GOAL_ARGUMENTS = '[status|set <objective>|<objective>|edit <objective>|pause|resume|clear]'
55
+ const GOAL_ARGUMENTS =
56
+ '[status|set [--confirmed-by record|model] <objective>|<objective>|edit <objective>|pause|resume|clear]'
54
57
  const GOAL_USAGE = `Usage: /goal ${GOAL_ARGUMENTS}`
55
58
 
56
59
  type ParsedGoalCommand =
@@ -78,6 +81,60 @@ function parseGoalCommand(args: readonly string[]): ParsedGoalCommand {
78
81
  return { kind: 'create', objective: input }
79
82
  }
80
83
 
84
+ type ConfirmedByFlag =
85
+ | {
86
+ readonly kind: 'ok'
87
+ readonly args: readonly string[]
88
+ readonly confirmedBy?: GoalConfirmedBy
89
+ }
90
+ | { readonly kind: 'invalid' }
91
+
92
+ /**
93
+ * Takes `--confirmed-by <model|record>` (or `=`) out of the arguments, but only
94
+ * where a flag stands: at the front, right after `set`, or closing the command. In the middle the words
95
+ * belong to the objective, so a goal about the flag itself keeps its text.
96
+ */
97
+ function takeConfirmedBy(args: readonly string[]): ConfirmedByFlag {
98
+ const head: string[] = []
99
+ let i = 0
100
+ if ((args[0] ?? '').toLowerCase() === 'set') {
101
+ head.push(args[0] ?? '')
102
+ i = 1
103
+ }
104
+ let found: GoalConfirmedBy | undefined
105
+ for (; i < args.length; i++) {
106
+ const arg = args[i] ?? ''
107
+ let value: string
108
+ if (arg === '--confirmed-by') {
109
+ value = args[i + 1] ?? ''
110
+ i++
111
+ } else if (arg.startsWith('--confirmed-by=')) {
112
+ value = arg.slice('--confirmed-by='.length)
113
+ } else {
114
+ break
115
+ }
116
+ if (value !== 'model' && value !== 'record') return { kind: 'invalid' }
117
+ found = value
118
+ }
119
+ let tail = args.slice(i)
120
+ // A flag may also close the command; in the middle its words are the objective's.
121
+ const last = tail[tail.length - 1] ?? ''
122
+ const beforeLast = tail[tail.length - 2] ?? ''
123
+ let closing: string | undefined
124
+ if (last.startsWith('--confirmed-by=')) {
125
+ closing = last.slice('--confirmed-by='.length)
126
+ tail = tail.slice(0, -1)
127
+ } else if (beforeLast === '--confirmed-by') {
128
+ closing = last
129
+ tail = tail.slice(0, -2)
130
+ }
131
+ if (closing !== undefined) {
132
+ if (closing !== 'model' && closing !== 'record') return { kind: 'invalid' }
133
+ found = closing
134
+ }
135
+ return { kind: 'ok', args: [...head, ...tail], ...(found ? { confirmedBy: found } : {}) }
136
+ }
137
+
81
138
  function goalRef(goal: SessionGoal): GoalRef {
82
139
  return { id: goal.id, revision: goal.revision }
83
140
  }
@@ -92,10 +149,19 @@ function goalCommands(goal: SessionGoal, armed: boolean): string {
92
149
  return '/goal set <objective>, /goal clear'
93
150
  }
94
151
 
95
- function renderGoal(title: string, goal: SessionGoal, armed: boolean): string {
152
+ function renderGoal(
153
+ title: string,
154
+ goal: SessionGoal,
155
+ armed: boolean,
156
+ defaultConfirmedBy: GoalConfirmedBy = 'model',
157
+ ): string {
96
158
  return [
97
159
  title,
98
160
  `Status: ${goal.phase}`,
161
+ ...(goal.confirmedBy === 'record' ||
162
+ (goal.confirmedBy === undefined && defaultConfirmedBy === 'record')
163
+ ? ['Confirmed by: the record of the work']
164
+ : []),
99
165
  `Automatic continuation: ${armed ? 'enabled' : 'paused'}`,
100
166
  `Automatic turns: ${goal.roundsAdmitted} / ${goal.maxGoalRounds}`,
101
167
  ...(goal.blockedReason
@@ -108,7 +174,12 @@ function renderGoal(title: string, goal: SessionGoal, armed: boolean): string {
108
174
  }
109
175
 
110
176
  function renderScopedGoal(title: string, goal: SessionGoal, scope: GoalCommandScope): string {
111
- return renderGoal(title, goal, scope.activation?.isArmed(scope.sessionId, goal) ?? false)
177
+ return renderGoal(
178
+ title,
179
+ goal,
180
+ scope.activation?.isArmed(scope.sessionId, goal) ?? false,
181
+ scope.defaultConfirmedBy,
182
+ )
112
183
  }
113
184
 
114
185
  function missingGoal(action: string) {
@@ -125,7 +196,20 @@ async function runGoalCommand(scope: GoalCommandScope | undefined, args: readonl
125
196
  reason: 'Goals need a durable session. This host did not supply one for the current command.',
126
197
  }
127
198
  }
128
- const command = parseGoalCommand(args)
199
+ const flag = takeConfirmedBy(args)
200
+ if (flag.kind === 'invalid') {
201
+ return {
202
+ kind: 'refused' as const,
203
+ reason: `--confirmed-by takes record or model.\n${GOAL_USAGE}`,
204
+ }
205
+ }
206
+ const command = parseGoalCommand(flag.args)
207
+ if (flag.confirmedBy !== undefined && command.kind !== 'create') {
208
+ return {
209
+ kind: 'refused' as const,
210
+ reason: `--confirmed-by is chosen when a goal is set.\n${GOAL_USAGE}`,
211
+ }
212
+ }
129
213
  try {
130
214
  const current = await scope.store.getGoal(scope.sessionId, scope.tenantId)
131
215
  switch (command.kind) {
@@ -154,7 +238,11 @@ async function runGoalCommand(scope: GoalCommandScope | undefined, args: readonl
154
238
  }
155
239
  }
156
240
  const created = await scope.store.createGoal(
157
- { sessionId: scope.sessionId, objective: command.objective },
241
+ {
242
+ sessionId: scope.sessionId,
243
+ objective: command.objective,
244
+ ...(flag.confirmedBy !== undefined ? { confirmedBy: flag.confirmedBy } : {}),
245
+ },
158
246
  scope.tenantId,
159
247
  )
160
248
  scope.activation?.arm(created)
@@ -4,6 +4,7 @@ import {
4
4
  DiskSessionLog,
5
5
  type SessionLease,
6
6
  type SessionLog,
7
+ turnHolder,
7
8
  } from '../../store/session-log/index.js'
8
9
  import { NamzuError } from '../../types/errors/index.js'
9
10
  import type { SessionId, TurnId } from '../../types/ids/index.js'
@@ -86,7 +87,7 @@ export async function withSessionLease<T>(
86
87
  ): Promise<T> {
87
88
  if (lease) return operation(lease)
88
89
  const claimed = await log.claim({
89
- holder: `namzu:${process.pid}:${log.sessionId}`,
90
+ holder: turnHolder(log.sessionId),
90
91
  ttlMs: OPERATION_LEASE_TTL_MS,
91
92
  })
92
93
  if (!claimed) {