dsh-llm-verifier 0.2.1 → 0.3.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 (45) hide show
  1. package/README.md +15 -11
  2. package/lib/client.js +23 -3
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +389 -115
  5. package/lib/index.js.map +1 -1
  6. package/lib/types/auto.d.ts +32 -12
  7. package/lib/types/auto.d.ts.map +1 -1
  8. package/lib/types/auto.js +50 -38
  9. package/lib/types/auto.js.map +1 -1
  10. package/lib/types/client-i18n.d.ts +21 -1
  11. package/lib/types/client-i18n.d.ts.map +1 -1
  12. package/lib/types/client-i18n.js +22 -4
  13. package/lib/types/client-i18n.js.map +1 -1
  14. package/lib/types/client.d.ts.map +1 -1
  15. package/lib/types/client.js +1 -1
  16. package/lib/types/client.js.map +1 -1
  17. package/lib/types/engine.d.ts +19 -0
  18. package/lib/types/engine.d.ts.map +1 -1
  19. package/lib/types/engine.js +54 -4
  20. package/lib/types/engine.js.map +1 -1
  21. package/lib/types/index.d.ts.map +1 -1
  22. package/lib/types/index.js +32 -43
  23. package/lib/types/index.js.map +1 -1
  24. package/lib/types/router.d.ts +31 -0
  25. package/lib/types/router.d.ts.map +1 -1
  26. package/lib/types/router.js +192 -17
  27. package/lib/types/router.js.map +1 -1
  28. package/lib/types/statistics.d.ts +28 -0
  29. package/lib/types/statistics.d.ts.map +1 -1
  30. package/lib/types/statistics.js +101 -0
  31. package/lib/types/statistics.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/auto.test.ts +42 -23
  34. package/src/auto.ts +55 -46
  35. package/src/client-i18n.ts +32 -4
  36. package/src/client.test.ts +48 -3
  37. package/src/client.tsx +2 -0
  38. package/src/config.test.ts +6 -3
  39. package/src/engine.test.ts +40 -1
  40. package/src/engine.ts +56 -4
  41. package/src/index.ts +34 -44
  42. package/src/router.test.ts +218 -3
  43. package/src/router.ts +182 -14
  44. package/src/statistics.test.ts +116 -0
  45. package/src/statistics.ts +112 -0
package/lib/index.js CHANGED
@@ -384,6 +384,51 @@ function blankStats() {
384
384
  function unorderedPair(a, b) {
385
385
  return a < b ? a + "," + b : b + "," + a;
386
386
  }
387
+ /**
388
+ * Deterministic A/B slot for one pivot-round pair, balanced by construction.
389
+ *
390
+ * The round used to emit `[candidate, pivot]` for every edge, so the ring leaders sat
391
+ * in trajectory B in all of their extra matches: with a judge that merely prefers slot A
392
+ * the pivots averaged 0.36 against 0.60 for everyone else and sank in the final ranking.
393
+ * Alternating on the sum of the two RANKS — not the raw indices: the pivot set is
394
+ * selected by score, so its indices can share a parity and a parity rule would then
395
+ * handicap half the field — gives every pivot and every candidate both slots within one
396
+ * edge of each other, without a second model call.
397
+ *
398
+ * Deliberately NOT in core.ts: `pivotRoundPairs` is compared against the upstream Python
399
+ * reference in `parity.test.ts`, so the orientation stays an orchestration-side decision.
400
+ * @param pair - one unordered pair from the pivot round.
401
+ * @param pivotRanks - pivot index → its rank among the pivots.
402
+ * @param nonPivotRanks - candidate index → its rank among the non-pivots.
403
+ * @returns The pair in the order it should be presented.
404
+ */
405
+ function orientRoundPairs(pairs) {
406
+ const balance = /* @__PURE__ */ new Map();
407
+ return pairs.map(([a, b]) => {
408
+ const first = balance.get(a) ?? 0;
409
+ const [x, y] = (balance.get(b) ?? 0) < first ? [b, a] : [a, b];
410
+ balance.set(x, (balance.get(x) ?? 0) + 1);
411
+ balance.set(y, (balance.get(y) ?? 0) - 1);
412
+ return [x, y];
413
+ });
414
+ }
415
+ /**
416
+ * Arbitrary but fixed A/B slot for the single pair of a two-candidate select.
417
+ *
418
+ * There is only one match, so nothing can cancel a preference; the mix at least stops
419
+ * candidate 0 from always sitting in slot A. Real comparisons go through `compare`,
420
+ * where `routedRepeats` runs an even number of rounds instead.
421
+ * @param a - first candidate index of the pair.
422
+ * @param b - second candidate index of the pair.
423
+ * @returns The pair in the order it should be presented.
424
+ */
425
+ function orientPair(a, b) {
426
+ const lo = Math.min(a, b);
427
+ const hi = Math.max(a, b);
428
+ let mixed = Math.imul(lo + 1, 2654435761) ^ Math.imul(hi + 1, 2246822519);
429
+ mixed = Math.imul(mixed ^ mixed >>> 15, 625341585);
430
+ return ((mixed ^ mixed >>> 13) & 1) === 0 ? [a, b] : [b, a];
431
+ }
387
432
  function judgeLabel(client) {
388
433
  return client.label?.trim() || client.provider + "/" + client.model;
389
434
  }
@@ -739,10 +784,11 @@ var VerifierEngine = class {
739
784
  };
740
785
  }
741
786
  if (options.candidates.length === 2) {
742
- const { rewards, judgeRewards, judgeOk, judgeErrors, judgeCalls, stats } = await this.scorePairs(options, [[0, 1]], signal);
787
+ const single = orientPair(0, 1);
788
+ const { rewards, judgeRewards, judgeOk, judgeErrors, judgeCalls, stats } = await this.scorePairs(options, [single], signal);
743
789
  const wins = [0, 0];
744
790
  const counts = [0, 0];
745
- accumulatePairs([[0, 1]], rewards, wins, counts);
791
+ accumulatePairs([single], rewards, wins, counts);
746
792
  const ranked = rankScores(wins, counts);
747
793
  const index = ranked[0].index;
748
794
  const judges = this.clients.map((client, k) => {
@@ -787,7 +833,7 @@ var VerifierEngine = class {
787
833
  accumulatePairs(ring, ringScores.rewards, firstWins, firstCounts);
788
834
  const pivots = topPivots(firstWins, firstCounts, options.pivots ?? 2);
789
835
  const ringPairs = new Set(ring.map((pair) => unorderedPair(pair[0], pair[1])));
790
- const rounds = pivotRoundPairs(options.candidates.length, pivots).filter((pair) => !ringPairs.has(unorderedPair(pair[0], pair[1])));
836
+ const rounds = orientRoundPairs(pivotRoundPairs(options.candidates.length, pivots).filter((pair) => !ringPairs.has(unorderedPair(pair[0], pair[1]))));
791
837
  const roundScores = await this.scorePairs(options, rounds, signal);
792
838
  const allRewards = new Map([...ringScores.rewards, ...roundScores.rewards]);
793
839
  const wins = new Array(options.candidates.length).fill(0);
@@ -1108,6 +1154,25 @@ function blockText$1(blocks) {
1108
1154
  visit(blocks);
1109
1155
  return parts.join("\n").trim();
1110
1156
  }
1157
+ /**
1158
+ * Text of one assistant turn, excluding reasoning blocks.
1159
+ *
1160
+ * Used only as the newest checkpoint's narration. Prose is not evidence, but for a
1161
+ * deliverable that lives in prose (a review, an analysis) it is the only thing that
1162
+ * describes the current state at all, so the judge receives it explicitly labelled
1163
+ * as a claim instead of being shown nothing about the deliverable.
1164
+ * @param blocks - content blocks of an `assistant/message` event.
1165
+ * @returns The joined text, trimmed.
1166
+ */
1167
+ function narrativeText(blocks) {
1168
+ const parts = [];
1169
+ const visit = (items) => {
1170
+ for (const block of items) if (block.type === "text") parts.push(block.text);
1171
+ else if (block.type === "tool-result") visit(block.content);
1172
+ };
1173
+ visit(blocks);
1174
+ return parts.join("\n").trim();
1175
+ }
1111
1176
  function successful(event) {
1112
1177
  return event.data.error === void 0 && event.data.message.content.every((block) => block.isError !== true);
1113
1178
  }
@@ -1130,6 +1195,8 @@ function buildEvidenceIndex(events) {
1130
1195
  const teamTasks = /* @__PURE__ */ new Map();
1131
1196
  const paired = /* @__PURE__ */ new Map();
1132
1197
  const currentTeamTasks = /* @__PURE__ */ new Map();
1198
+ let narration;
1199
+ const dispatchedTools = /* @__PURE__ */ new Map();
1133
1200
  for (const rawEvent of relevant) {
1134
1201
  const event = rawEvent;
1135
1202
  if (event.type === "tool/call") calls.set(String(event.data.callId), rawEvent);
@@ -1141,8 +1208,21 @@ function buildEvidenceIndex(events) {
1141
1208
  currentTeamTasks.set(data.task.id, { ...data.task });
1142
1209
  teamTasks.set(event.seq, [...currentTeamTasks.values()]);
1143
1210
  }
1211
+ } else if (event.type === "assistant/message") {
1212
+ const blocks = event.data?.message?.content ?? event.data?.content;
1213
+ const text = narrativeText(Array.isArray(blocks) ? blocks : []);
1214
+ if (text) narration = {
1215
+ seq: event.seq,
1216
+ text
1217
+ };
1144
1218
  } else if (event.type === "tool/ptc-dispatch" || event.type === "tool/code-dispatch") {
1145
1219
  const data = event.data;
1220
+ const rootCallId = typeof data.rootCallId === "string" && data.rootCallId ? data.rootCallId : void 0;
1221
+ if (rootCallId) {
1222
+ const names = dispatchedTools.get(rootCallId) ?? /* @__PURE__ */ new Set();
1223
+ names.add(data.name);
1224
+ dispatchedTools.set(rootCallId, names);
1225
+ }
1146
1226
  if (data.isError !== true && (!Array.isArray(data.content) || data.content.every((b) => b.isError !== true))) {
1147
1227
  const subCallId = String(data.subCallId ?? "code:" + event.seq);
1148
1228
  const content = Array.isArray(data.content) ? data.content : [];
@@ -1157,7 +1237,9 @@ function buildEvidenceIndex(events) {
1157
1237
  }
1158
1238
  for (const [callId, call] of calls) {
1159
1239
  const result = results.get(callId);
1160
- if (result) paired.set(callId, {
1240
+ if (!result) continue;
1241
+ if (onlyBookkeepingDispatches(dispatchedTools.get(callId))) continue;
1242
+ paired.set(callId, {
1161
1243
  name: call.data.name,
1162
1244
  callSeq: call.seq,
1163
1245
  resultSeq: result.seq,
@@ -1168,7 +1250,8 @@ function buildEvidenceIndex(events) {
1168
1250
  problemSeq: taskStartSeq,
1169
1251
  calls: paired,
1170
1252
  todos,
1171
- teamTasks
1253
+ teamTasks,
1254
+ narration
1172
1255
  };
1173
1256
  }
1174
1257
  function parseTrustedWorkflow(value, callId, callSeq, resultSeq, maxCandidates, maxItemChars, maxInputChars) {
@@ -1225,23 +1308,68 @@ function canonicalTodoSnapshots(index) {
1225
1308
  return values;
1226
1309
  }
1227
1310
  /**
1311
+ * Tools whose successful output only maintains the agent's own bookkeeping.
1312
+ *
1313
+ * A checkpoint already renders the todo/team snapshot these tools wrote, so their
1314
+ * own result repeats it while displacing the real work output that came just before
1315
+ * them.
1316
+ *
1317
+ * `present` belongs here for the same reason: declaring deliverables produces no
1318
+ * independent output, and it is always the LAST call of a turn. A live session ran
1319
+ * typecheck + the full suite, committed, then presented — and every stop boundary
1320
+ * showed the judge "Latest observed tool output at routing time (run_code):
1321
+ * presented: 8" instead of the verification run one call earlier. Following its own
1322
+ * rule ("a state without real verification should not exceed K"), the judge capped
1323
+ * the newest checkpoint at exactly K = 52.6% against a 0.8 threshold on four
1324
+ * consecutive routes, so each one steered "continue the unfinished work" for work
1325
+ * that was finished and verified. The `deliverables/presented` event still records
1326
+ * what was presented; the tool result adds nothing the judge can grade.
1327
+ */
1328
+ const BOOKKEEPING_TOOLS = /* @__PURE__ */ new Set([
1329
+ "todo_write",
1330
+ "create_goal",
1331
+ "get_goal",
1332
+ "update_goal",
1333
+ "interrupt_agent",
1334
+ "list_agents",
1335
+ "exit_plan_mode",
1336
+ "skill",
1337
+ "present"
1338
+ ]);
1339
+ /** Whether a wrapper's dispatches were all bookkeeping; undefined/empty means it dispatched nothing. */
1340
+ function onlyBookkeepingDispatches(names) {
1341
+ if (names === void 0 || names.size === 0) return false;
1342
+ for (const name of names) if (!BOOKKEEPING_TOOLS.has(name)) return false;
1343
+ return true;
1344
+ }
1345
+ /**
1228
1346
  * Observed tool evidence available at one checkpoint.
1229
1347
  *
1230
1348
  * A checkpoint rendered from todo/team text alone can never clear the progress
1231
1349
  * threshold: the judge prompt explicitly refuses to credit a state that carries
1232
1350
  * no observed output. The most recent successful tool result at or before the
1233
1351
  * checkpoint is therefore attached as evidence.
1352
+ *
1353
+ * Bookkeeping tools are skipped: the checkpoint already renders the todo/team
1354
+ * snapshot they wrote, so their own result repeats it while displacing the real
1355
+ * output produced just before them (a live session attached `create_goal`,
1356
+ * `update_goal` and `interrupt_agent` output to checkpoints that had already run
1357
+ * the task's test suite).
1234
1358
  * @param index - evidence index of the current task.
1235
- * @param seq - checkpoint sequence number.
1359
+ * @param seq - checkpoint sequence number, or `Infinity` for the current state.
1236
1360
  * @param budget - maximum characters the evidence may occupy.
1361
+ * @param current - render the newest output in the task instead of the newest one before `seq`.
1237
1362
  * @returns Evidence block, or '' when the task produced none yet.
1238
1363
  */
1239
- function checkpointEvidence(index, seq, budget) {
1364
+ function checkpointEvidence(index, seq, budget, current = false) {
1240
1365
  if (budget < 64) return "";
1241
1366
  let latest;
1242
- for (const pair of index.calls.values()) if (pair.resultSeq <= seq && (latest === void 0 || pair.resultSeq > latest.resultSeq)) latest = pair;
1367
+ for (const pair of index.calls.values()) {
1368
+ if (BOOKKEEPING_TOOLS.has(pair.name) || !pair.text.trim()) continue;
1369
+ if (pair.resultSeq <= seq && (latest === void 0 || pair.resultSeq > latest.resultSeq)) latest = pair;
1370
+ }
1243
1371
  if (latest === void 0) return "";
1244
- const prefix = "\n\nLatest observed tool output before this checkpoint (" + latest.name + "):\n";
1372
+ const prefix = current ? "\n\nLatest observed tool output at routing time (" + latest.name + "):\n" : "\n\nLatest observed tool output before this checkpoint (" + latest.name + "):\n";
1245
1373
  if (prefix.length >= budget) return "";
1246
1374
  return prefix + sanitizeVerifierText(latest.text, budget - prefix.length);
1247
1375
  }
@@ -1276,6 +1404,12 @@ const MAX_ROUTED_CHECKPOINTS = 6;
1276
1404
  * The result fits both caps by construction: each step is at most min(maxItemChars,
1277
1405
  * maxInputChars / kept.length) characters, so the combined length can never exceed
1278
1406
  * maxInputChars and boundDecision() no longer drops the whole route.
1407
+ *
1408
+ * The newest checkpoint is also the state the route is judging, so it is rendered as
1409
+ * the CURRENT state: its evidence is the newest observed output in the task rather
1410
+ * than the newest one before the last todo snapshot (which is often several tool calls
1411
+ * stale), and it carries the agent's latest prose as an explicitly labelled claim (see
1412
+ * {@link currentNarration}), because prose deliverables never reach a tool.
1279
1413
  * @param index - evidence index of the current task.
1280
1414
  * @param sources - checkpoints in chronological order.
1281
1415
  * @param maxItemChars - hard per-item cap enforced by boundDecision().
@@ -1295,13 +1429,35 @@ function renderCheckpointSteps(index, sources, maxItemChars, maxInputChars) {
1295
1429
  return {
1296
1430
  steps: kept.map((source, position) => {
1297
1431
  const note = position === 0 && omitted > 0 ? "Earlier " + omitted + " checkpoint(s) omitted; showing the " + kept.length + " most recent.\n" : "";
1298
- const evidence = checkpointEvidence(index, source.seq, evidenceBudget);
1299
- return sanitizeVerifierText(note + source.label + source.body, Math.max(1, stepCap - evidence.length)) + evidence;
1432
+ const isCurrent = position === kept.length - 1;
1433
+ const observed = checkpointEvidence(index, isCurrent ? Number.POSITIVE_INFINITY : source.seq, isCurrent ? Math.floor(evidenceBudget / 2) : evidenceBudget, isCurrent);
1434
+ const narration = isCurrent ? currentNarration(index, evidenceBudget - observed.length) : "";
1435
+ return sanitizeVerifierText(note + source.label + source.body, Math.max(1, stepCap - observed.length - narration.length)) + observed + narration;
1300
1436
  }),
1301
1437
  evidenceSeqs: kept.map((source) => source.seq),
1302
1438
  omitted
1303
1439
  };
1304
1440
  }
1441
+ /**
1442
+ * The agent's newest unverified prose, attached to the current checkpoint.
1443
+ *
1444
+ * Progress checkpoints are graded against "would this state satisfy the task", and
1445
+ * for work whose deliverable is prose the todo snapshot plus one tool result say
1446
+ * nothing about it: every checkpoint then scores "certainly NO" even though the
1447
+ * deliverable exists (a live review task scored 0% on all four checkpoints while the
1448
+ * final session acceptance of the same work passed). The block is labelled as a claim
1449
+ * so the judge can weigh it without treating it as observed output.
1450
+ * @param index - evidence index of the current task.
1451
+ * @param budget - maximum characters the narration may occupy.
1452
+ * @returns Narration block, or '' when the task produced no prose or has no budget.
1453
+ */
1454
+ function currentNarration(index, budget) {
1455
+ const narration = index.narration;
1456
+ if (narration === void 0 || budget < 64) return "";
1457
+ const prefix = "\n\nNewest agent narration in this task (the agent's own claim — NOT observed evidence):\n";
1458
+ if (87 >= budget) return "";
1459
+ return prefix + sanitizeVerifierText(narration.text, budget - 87);
1460
+ }
1305
1461
  function canonicalTeamTaskSnapshots(index) {
1306
1462
  const values = [];
1307
1463
  let previous = "";
@@ -1368,7 +1524,8 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
1368
1524
  reason: "changed durable todo snapshots",
1369
1525
  fingerprint: stableHash({
1370
1526
  kind: "track",
1371
- snapshots
1527
+ snapshots,
1528
+ steps: rendered.steps
1372
1529
  }),
1373
1530
  steps: rendered.steps,
1374
1531
  checkpoints: rendered.steps.map((_, i) => i + 1),
@@ -1389,7 +1546,8 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
1389
1546
  reason: "changed durable team tasks",
1390
1547
  fingerprint: stableHash({
1391
1548
  kind: "track",
1392
- teamSnapshots
1549
+ teamSnapshots,
1550
+ steps: rendered.steps
1393
1551
  }),
1394
1552
  steps: rendered.steps,
1395
1553
  checkpoints: rendered.steps.map((_, i) => i + 1),
@@ -1398,12 +1556,35 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
1398
1556
  }
1399
1557
  }
1400
1558
  }
1559
+ /**
1560
+ * Artifacts that only the classifier can turn into candidates.
1561
+ *
1562
+ * Todo and team snapshots are deliberately absent. They are the structured track
1563
+ * route's own input, and that route runs first — so listing them here could only make
1564
+ * the hint true in shapes the structured pass already claimed (or in a task that
1565
+ * already ran an explicit `verifier_track`, where re-classifying the same snapshots is
1566
+ * not wanted). The one shape left out is a snapshot series whose lists are all shorter
1567
+ * than two items, which is not worth a classification call.
1568
+ */
1569
+ const HINT_ARTIFACTS = /* @__PURE__ */ new Set([
1570
+ "subagent",
1571
+ "subagent_fork",
1572
+ "workflow",
1573
+ "exit_plan_mode"
1574
+ ]);
1575
+ /**
1576
+ * Whether a smart-mode stop boundary is worth a semantic classification call.
1577
+ *
1578
+ * The semantic phase only runs when the structured pass produced nothing, so this
1579
+ * answers "is there material the structured pass never consumes?" — never "are there
1580
+ * todo/team snapshots?", which the structured pass would have used already.
1581
+ * @param events - Session event log.
1582
+ * @returns True when a subagent/workflow/plan artifact exists.
1583
+ */
1401
1584
  function semanticRouteHint(events) {
1402
1585
  const index = buildEvidenceIndex(events);
1403
1586
  if (!index) return false;
1404
- if ([...index.calls.values()].some((pair) => pair.name === "subagent" || pair.name === "subagent_fork" || pair.name === "workflow" || pair.name === "exit_plan_mode")) return true;
1405
- if (canonicalTodoSnapshots(index).length >= 2) return true;
1406
- if (canonicalTeamTaskSnapshots(index).length >= 2) return true;
1587
+ for (const pair of index.calls.values()) if (HINT_ARTIFACTS.has(pair.name)) return true;
1407
1588
  return false;
1408
1589
  }
1409
1590
  function buildSemanticRoutePrompt(problem, events, maxCandidates, maxItemChars = 2e4, maxInputChars = 6e4) {
@@ -1413,6 +1594,7 @@ function buildSemanticRoutePrompt(problem, events, maxCandidates, maxItemChars =
1413
1594
  let used = 0;
1414
1595
  let omitted = 0;
1415
1596
  for (const [callId, pair] of [...index.calls.entries()].reverse()) {
1597
+ if (BOOKKEEPING_TOOLS.has(pair.name)) continue;
1416
1598
  const text = sanitizeVerifierText(pair.text, maxItemChars);
1417
1599
  if (artifacts.length > 0 && used + text.length > maxInputChars) {
1418
1600
  omitted += 1;
@@ -1513,7 +1695,8 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
1513
1695
  const perItem = itemBudget(output.candidateCallIds.length, maxItemChars, maxInputChars);
1514
1696
  const candidates = output.candidateCallIds.map((callId, i) => {
1515
1697
  const pair = index.calls.get(callId);
1516
- return pair ? {
1698
+ if (!pair || BOOKKEEPING_TOOLS.has(pair.name)) return void 0;
1699
+ return {
1517
1700
  id: callId,
1518
1701
  groupId: "semantic",
1519
1702
  label: pair.name + " " + (i + 1),
@@ -1521,7 +1704,7 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
1521
1704
  callId,
1522
1705
  fromSeq: pair.callSeq,
1523
1706
  toSeq: pair.resultSeq
1524
- } : void 0;
1707
+ };
1525
1708
  }).filter((candidate) => candidate !== void 0);
1526
1709
  if (candidates.length !== output.candidateCallIds.length) return void 0;
1527
1710
  const fingerprint = stableHash({
@@ -1556,6 +1739,26 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
1556
1739
  * @param criteriaCount - number of criteria evaluated per comparison.
1557
1740
  * @returns The planned model-call count, never below 1.
1558
1741
  */
1742
+ /**
1743
+ * Scoring repeats a routed decision actually runs.
1744
+ *
1745
+ * `compare` judges ONE unordered pair, and `VerifierEngine.compare` only swaps the
1746
+ * candidates on odd repeat indices — with the shipped default of a single round the
1747
+ * first candidate therefore always sat in slot A, so the winner was partly decided by
1748
+ * listing order. Rounding its count up to an even number averages a swapped round and
1749
+ * cancels that.
1750
+ *
1751
+ * `select` does not need it: its ring is symmetric by construction and the pivot round
1752
+ * is oriented per pair by the engine, so one round is already unbiased. `track` scores
1753
+ * a single checkpoint list and has no slots at all.
1754
+ * @param decision - the routed decision about to run.
1755
+ * @param configured - the configured auto-route repeat count.
1756
+ * @returns The repeat count to pass to the engine.
1757
+ */
1758
+ function routedRepeats(decision, configured) {
1759
+ if (decision.kind !== "compare") return configured;
1760
+ return configured % 2 === 0 ? configured : configured + 1;
1761
+ }
1559
1762
  function estimateRoutedCalls(decision, repeats, criteriaCount) {
1560
1763
  if (decision.kind === "compare") return Math.max(1, criteriaCount * repeats);
1561
1764
  if (decision.kind === "track") return Math.max(1, repeats);
@@ -1754,7 +1957,23 @@ function parseSessionVerdict(value) {
1754
1957
  if (start < 0 || end <= start) return void 0;
1755
1958
  try {
1756
1959
  const parsed = JSON.parse(text.slice(start, end + 1));
1757
- return typeof parsed === "object" && parsed !== null ? parsed : void 0;
1960
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1961
+ const row = parsed;
1962
+ const criteria = [];
1963
+ for (const entry of Array.isArray(row.criteria) ? row.criteria : []) {
1964
+ const item = typeof entry === "object" && entry !== null ? entry : {};
1965
+ if (typeof item.id !== "string" || typeof item.scoreA !== "number" || !Number.isFinite(item.scoreA)) continue;
1966
+ criteria.push({
1967
+ id: item.id,
1968
+ ...typeof item.name === "string" ? { name: item.name } : {},
1969
+ score: item.scoreA
1970
+ });
1971
+ }
1972
+ return {
1973
+ winner: row.winner,
1974
+ score: row.score,
1975
+ criteria
1976
+ };
1758
1977
  } catch {
1759
1978
  return;
1760
1979
  }
@@ -1810,7 +2029,8 @@ function analyzeAutoTask(events, policy) {
1810
2029
  manualVerdicts.push({
1811
2030
  seq: result.seq,
1812
2031
  winner: verdict?.winner,
1813
- score: verdict?.score
2032
+ score: verdict?.score,
2033
+ criteria: verdict?.criteria
1814
2034
  });
1815
2035
  }
1816
2036
  for (const entry of successfulDispatches) {
@@ -1819,11 +2039,16 @@ function analyzeAutoTask(events, policy) {
1819
2039
  manualVerdicts.push({
1820
2040
  seq: entry.seq,
1821
2041
  winner: verdict?.winner,
1822
- score: verdict?.score
2042
+ score: verdict?.score,
2043
+ criteria: verdict?.criteria
1823
2044
  });
1824
2045
  }
1825
2046
  const hasManualSessionVerification = manualVerdicts.length > 0;
1826
- const manualVerificationAccepted = manualVerdicts.some((verdict) => verdict.winner === "A" && typeof verdict.score === "number" && verdict.score >= policy.threshold && !stale(verdict.seq));
2047
+ const manualVerificationAccepted = manualVerdicts.some((verdict) => verdict.winner === "A" && typeof verdict.score === "number" && sessionAccepted({
2048
+ score: verdict.score,
2049
+ winner: "A",
2050
+ criteria: verdict.criteria
2051
+ }, policy.threshold) && !stale(verdict.seq));
1827
2052
  if (policy.mode === "manual") return {
1828
2053
  taskStartSeq,
1829
2054
  toolCalls,
@@ -1886,49 +2111,40 @@ function analyzeAutoTask(events, policy) {
1886
2111
  };
1887
2112
  }
1888
2113
  /**
1889
- * Session/task acceptance budget.
2114
+ * Criteria that do not clear the acceptance threshold.
1890
2115
  *
1891
- * The automatic verifier enforces its per-task and per-session budget through
1892
- * {@link AutoVerifierRouter} reservations, which also count the routing phases;
1893
- * this standalone counter is kept as a public utility for orchestrators that
1894
- * need the same accounting outside the router.
2116
+ * The acceptance score is the MEAN over criteria, so a session with one requirement at
2117
+ * zero and the others perfect averaged ~0.67 and cleared the 0.65 default: a single
2118
+ * failed requirement was arithmetically invisible. The gate therefore also requires
2119
+ * every criterion to clear the threshold on its own.
2120
+ * @param criteria - per-criterion A-side scores, when the judge reported them.
2121
+ * @param threshold - acceptance threshold.
2122
+ * @returns The failing criteria, in report order.
1895
2123
  */
1896
- var AutoVerificationBudget = class {
1897
- states = /* @__PURE__ */ new Map();
1898
- claim(agent, evidence, policy) {
1899
- if (!evidence.eligible) return false;
1900
- const id = String(agent.id);
1901
- const state = this.states.get(id) ?? {
1902
- taskStartSeq: evidence.taskStartSeq,
1903
- taskAttempts: 0,
1904
- sessionAttempts: 0,
1905
- lastEvaluatedSeq: -1
1906
- };
1907
- if (state.taskStartSeq !== evidence.taskStartSeq) {
1908
- state.taskStartSeq = evidence.taskStartSeq;
1909
- state.taskAttempts = 0;
1910
- state.lastEvaluatedSeq = -1;
1911
- }
1912
- const lastSeq = sessionEvents(agent.session).at(-1)?.seq ?? -1;
1913
- if (lastSeq <= state.lastEvaluatedSeq || state.taskAttempts >= policy.maxPerTask || state.sessionAttempts >= policy.maxPerSession) {
1914
- this.states.set(id, state);
1915
- return false;
1916
- }
1917
- state.taskAttempts += 1;
1918
- state.sessionAttempts += 1;
1919
- state.lastEvaluatedSeq = lastSeq;
1920
- this.states.set(id, state);
1921
- return true;
1922
- }
1923
- release(agent) {
1924
- this.states.delete(String(agent.id));
1925
- }
1926
- };
1927
- function automaticFeedback(score, baselineScore, winner, threshold) {
2124
+ function failedAcceptanceCriteria(criteria, threshold) {
2125
+ return (criteria ?? []).filter((criterion) => !(criterion.score >= threshold));
2126
+ }
2127
+ /**
2128
+ * Whether a session acceptance clears the gate.
2129
+ *
2130
+ * The empty-work baseline is a fixed sentence that always scores 0, so the comparison
2131
+ * itself is decorative; what actually decides is the session's own score, the winner
2132
+ * against that baseline, and (since the mean could hide a failed requirement) every
2133
+ * criterion clearing the threshold.
2134
+ * @param evidence - session acceptance result (score, winner and per-criterion scores).
2135
+ * @param threshold - acceptance threshold.
2136
+ * @returns True only when the session may conclude.
2137
+ */
2138
+ function sessionAccepted(evidence, threshold) {
2139
+ if (evidence.winner !== "A" || !(evidence.score >= threshold)) return false;
2140
+ return failedAcceptanceCriteria(evidence.criteria, threshold).length === 0;
2141
+ }
2142
+ function automaticFeedback(score, baselineScore, winner, threshold, failedCriteria = []) {
1928
2143
  const percent = (value) => (value * 100).toFixed(1) + "%";
1929
2144
  return [
1930
2145
  "[Automatic verifier gate]",
1931
2146
  `The independent verifier did not clear this task for completion: evidence score ${percent(score)}, baseline ${percent(baselineScore)}, verdict ${winner}, required ${percent(threshold)}.`,
2147
+ ...failedCriteria.length > 0 ? ["Criteria below the threshold: " + failedCriteria.map((criterion) => (criterion.name ?? criterion.id) + " " + percent(criterion.score)).join("; ") + "."] : [],
1932
2148
  "Re-open the task requirements, inspect the actual tool outputs for unresolved errors or missing proof, make any necessary corrections, and run a directly relevant verification command before concluding. Do not merely restate that the task is complete."
1933
2149
  ].join("\n");
1934
2150
  }
@@ -2069,12 +2285,100 @@ const VERIFIER_TOOL_NAMES = [
2069
2285
  "verifier_track",
2070
2286
  "verifier_current_session"
2071
2287
  ];
2288
+ /** Upper bound on the stored checkpoint progression; one explicit call can legitimately carry 32 steps. */
2289
+ const MAX_VERDICT_SCORES = 64;
2290
+ /** Upper bound on the stored per-criterion breakdown; the default rubric has three criteria. */
2291
+ const MAX_VERDICT_CRITERIA = 16;
2292
+ /**
2293
+ * Compact summary of what the judges decided, stored beside the call counters so
2294
+ * the dashboard can answer "why did this fail?" instead of only "how much did it cost".
2295
+ *
2296
+ * Pure on purpose: this mapping used to live inside the plugin closure, where the two
2297
+ * bugs it carried (a track verdict reported as the historical minimum, a comparison
2298
+ * reported as the loser's score under a threshold it never used) could not be tested.
2299
+ * @param toolName - the verifier tool that produced the value.
2300
+ * @param value - its rendered result.
2301
+ * @param phase - which stage produced it (explicit | compare | select | track | final | ...).
2302
+ * @param thresholds - resolved acceptance thresholds.
2303
+ * @returns A verdict summary; score fields are omitted when the tool has none.
2304
+ */
2305
+ function summarizeVerdict(toolName, value, phase, thresholds) {
2306
+ const row = typeof value === "object" && value !== null ? value : {};
2307
+ const numberAt = (key) => typeof row[key] === "number" && Number.isFinite(row[key]) ? row[key] : void 0;
2308
+ const scores = Array.isArray(row.scores) ? row.scores.filter((entry) => typeof entry === "number" && Number.isFinite(entry)) : [];
2309
+ const winner = row.winner === "A" || row.winner === "B" || row.winner === "tie" ? row.winner : void 0;
2310
+ if (toolName === "verifier_route_classify") return {
2311
+ phase,
2312
+ outcome: "classified"
2313
+ };
2314
+ if (toolName === "verifier_select") {
2315
+ const index = numberAt("index");
2316
+ const best = index === void 0 ? void 0 : scores[index];
2317
+ return {
2318
+ phase,
2319
+ outcome: "ranked",
2320
+ ...best !== void 0 ? { score: best } : {}
2321
+ };
2322
+ }
2323
+ if (toolName === "verifier_track") {
2324
+ const latest = scores.length > 0 ? scores[scores.length - 1] : void 0;
2325
+ const threshold = thresholds.autoTrackCompletionThreshold;
2326
+ return {
2327
+ phase,
2328
+ outcome: latest !== void 0 && latest >= threshold ? "passed" : "below-threshold",
2329
+ ...latest !== void 0 ? { score: latest } : {},
2330
+ ...scores.length > 1 ? { scores: [...scores] } : {},
2331
+ threshold
2332
+ };
2333
+ }
2334
+ if (toolName === "verifier_compare") {
2335
+ const scoreA = numberAt("score") ?? numberAt("scoreA");
2336
+ const scoreB = numberAt("scoreB");
2337
+ const score = winner === "B" ? scoreB : scoreA;
2338
+ return {
2339
+ phase,
2340
+ outcome: winner === "tie" ? "tie" : "compared",
2341
+ ...score !== void 0 ? { score } : {},
2342
+ ...scoreB !== void 0 ? { scoreB } : {},
2343
+ ...winner !== void 0 ? { winner } : {}
2344
+ };
2345
+ }
2346
+ const score = numberAt("score") ?? numberAt("scoreA");
2347
+ const baselineScore = numberAt("baselineScore");
2348
+ const threshold = thresholds.autoVerifyThreshold;
2349
+ const criteria = Array.isArray(row.criteria) ? row.criteria.map((entry) => typeof entry === "object" && entry !== null ? entry : {}).filter((entry) => typeof entry.id === "string" && typeof entry.scoreA === "number" && Number.isFinite(entry.scoreA)).slice(0, MAX_VERDICT_CRITERIA).map((entry) => ({
2350
+ id: entry.id,
2351
+ score: entry.scoreA
2352
+ })) : [];
2353
+ const belowThreshold = criteria.filter((entry) => !(entry.score >= threshold));
2354
+ return {
2355
+ phase,
2356
+ outcome: winner === "tie" ? "tie" : winner === "A" && score !== void 0 && score >= threshold && belowThreshold.length === 0 ? "passed" : "below-threshold",
2357
+ ...score !== void 0 ? { score } : {},
2358
+ ...baselineScore !== void 0 ? { baselineScore } : {},
2359
+ ...criteria.length > 0 ? { criteria } : {},
2360
+ ...winner !== void 0 ? { winner } : {},
2361
+ threshold
2362
+ };
2363
+ }
2072
2364
  function cleanVerdict(input) {
2073
2365
  if (typeof input !== "object" || input === null || Array.isArray(input)) return void 0;
2074
2366
  const verdict = {};
2075
2367
  if (typeof input.phase === "string") verdict.phase = input.phase;
2076
2368
  if (typeof input.outcome === "string") verdict.outcome = input.outcome;
2077
2369
  if (typeof input.score === "number" && Number.isFinite(input.score)) verdict.score = input.score;
2370
+ if (Array.isArray(input.scores)) {
2371
+ const scores = input.scores.filter((entry) => typeof entry === "number" && Number.isFinite(entry)).slice(0, MAX_VERDICT_SCORES);
2372
+ if (scores.length > 0) verdict.scores = scores;
2373
+ }
2374
+ if (typeof input.scoreB === "number" && Number.isFinite(input.scoreB)) verdict.scoreB = input.scoreB;
2375
+ if (Array.isArray(input.criteria)) {
2376
+ const criteria = input.criteria.filter((entry) => typeof entry === "object" && entry !== null && typeof entry.id === "string" && typeof entry.score === "number" && Number.isFinite(entry.score)).slice(0, MAX_VERDICT_CRITERIA).map((entry) => ({
2377
+ id: entry.id,
2378
+ score: entry.score
2379
+ }));
2380
+ if (criteria.length > 0) verdict.criteria = criteria;
2381
+ }
2078
2382
  if (typeof input.baselineScore === "number" && Number.isFinite(input.baselineScore)) verdict.baselineScore = input.baselineScore;
2079
2383
  if (input.winner === "A" || input.winner === "B" || input.winner === "tie") verdict.winner = input.winner;
2080
2384
  if (typeof input.threshold === "number" && Number.isFinite(input.threshold)) verdict.threshold = input.threshold;
@@ -2842,54 +3146,12 @@ function apply(ctx, config = {}) {
2842
3146
  const requireEnabled = () => {
2843
3147
  if (!current().enabled) throw new Error("llm-verifier: verifier tools are disabled — enable them in Settings → LLM Verifier");
2844
3148
  };
2845
- /**
2846
- * Compact summary of what the judges decided, stored beside the call counters so
2847
- * the dashboard can answer "why did this fail?" instead of only "how much did it cost".
2848
- * @param toolName - the verifier tool that produced the value.
2849
- * @param value - its rendered result.
2850
- * @param phase - which stage produced it (explicit | compare | select | track | final | ...).
2851
- * @returns A verdict summary; score fields are omitted when the tool has none.
2852
- */
2853
3149
  const verdictFrom = (toolName, value, phase) => {
2854
3150
  const selected = current();
2855
- const row = typeof value === "object" && value !== null ? value : {};
2856
- const numberAt = (key) => typeof row[key] === "number" && Number.isFinite(row[key]) ? row[key] : void 0;
2857
- const scores = Array.isArray(row.scores) ? row.scores.filter((entry) => typeof entry === "number" && Number.isFinite(entry)) : [];
2858
- const winner = row.winner === "A" || row.winner === "B" || row.winner === "tie" ? row.winner : void 0;
2859
- if (toolName === "verifier_route_classify") return {
2860
- phase,
2861
- outcome: "classified"
2862
- };
2863
- if (toolName === "verifier_select") {
2864
- const index = numberAt("index");
2865
- const best = index === void 0 ? void 0 : scores[index];
2866
- return {
2867
- phase,
2868
- outcome: "ranked",
2869
- ...best !== void 0 ? { score: best } : {}
2870
- };
2871
- }
2872
- if (toolName === "verifier_track") {
2873
- const worst = scores.length > 0 ? Math.min(...scores) : void 0;
2874
- const threshold = selected.autoTrackCompletionThreshold;
2875
- return {
2876
- phase,
2877
- outcome: worst !== void 0 && worst >= threshold ? "passed" : "below-threshold",
2878
- ...worst !== void 0 ? { score: worst } : {},
2879
- threshold
2880
- };
2881
- }
2882
- const score = numberAt("score") ?? numberAt("scoreA");
2883
- const baselineScore = numberAt("baselineScore");
2884
- const threshold = selected.autoVerifyThreshold;
2885
- return {
2886
- phase,
2887
- outcome: winner === "tie" ? "tie" : winner === "A" && score !== void 0 && score >= threshold ? "passed" : "below-threshold",
2888
- ...score !== void 0 ? { score } : {},
2889
- ...baselineScore !== void 0 ? { baselineScore } : {},
2890
- ...winner !== void 0 ? { winner } : {},
2891
- threshold
2892
- };
3151
+ return summarizeVerdict(toolName, value, phase, {
3152
+ autoVerifyThreshold: selected.autoVerifyThreshold,
3153
+ autoTrackCompletionThreshold: selected.autoTrackCompletionThreshold
3154
+ });
2893
3155
  };
2894
3156
  const record = async (toolName, agent, operation, phase = "explicit") => {
2895
3157
  const startedAt = Date.now();
@@ -2996,6 +3258,11 @@ function apply(ctx, config = {}) {
2996
3258
  score: compared.scoreA,
2997
3259
  baselineScore: compared.scoreB,
2998
3260
  winner: compared.winner,
3261
+ criteria: compared.criteria.map((row) => ({
3262
+ id: row.id,
3263
+ name: row.name,
3264
+ score: row.scoreA
3265
+ })),
2999
3266
  fromSeq: extracted.fromSeq,
3000
3267
  toSeq: extracted.toSeq,
3001
3268
  omittedCharacters: extracted.omittedCharacters,
@@ -3235,8 +3502,9 @@ function apply(ctx, config = {}) {
3235
3502
  }
3236
3503
  const verdict = parseVerdictLetter(classified.text);
3237
3504
  if (verdict === void 0) {
3238
- autoRouter.fail(agent, taskReservation, false);
3239
- ctx.logger.warn("llm-verifier team task verification produced no verdict line; task " + task.id + " was not gated");
3505
+ const strict = selected.autoVerifyMode === "strict";
3506
+ autoRouter.fail(agent, taskReservation, strict);
3507
+ ctx.logger.warn("llm-verifier team task verification produced no verdict line; task " + task.id + (strict ? " stays blocked" : " was not gated"));
3240
3508
  continue;
3241
3509
  }
3242
3510
  if (verdict.score >= selected.autoVerifyThreshold) {
@@ -3330,7 +3598,8 @@ function apply(ctx, config = {}) {
3330
3598
  }
3331
3599
  if (!stillCurrent()) return;
3332
3600
  if (decision) {
3333
- const expectedCalls = estimateRoutedCalls(decision, selected.autoVerifyRepeats, DEFAULT_CRITERIA.length) * selected.judges.length;
3601
+ const repeats = routedRepeats(decision, selected.autoVerifyRepeats);
3602
+ const expectedCalls = estimateRoutedCalls(decision, repeats, DEFAULT_CRITERIA.length) * selected.judges.length;
3334
3603
  const reservation = autoRouter.reserve(agent, decision.kind, decision.fingerprint, expectedCalls, policy);
3335
3604
  if (reservation === void 0) {
3336
3605
  const exhausted = autoRouter.budgetExhausted(agent, expectedCalls, policy);
@@ -3354,7 +3623,7 @@ function apply(ctx, config = {}) {
3354
3623
  if (reservation) try {
3355
3624
  const extracted = await extractTask(agent, evidence.taskStartSeq, admittedLastSeq, selected.autoVerifyMaxChars, signal);
3356
3625
  if (decision.kind === "compare") {
3357
- const result = await compareCandidates(agent, extracted.problem, decision.candidates[0].content, decision.candidates[1].content, selected.autoVerifyRepeats, signal, extracted.images, "compare");
3626
+ const result = await compareCandidates(agent, extracted.problem, decision.candidates[0].content, decision.candidates[1].content, repeats, signal, extracted.images, "compare");
3358
3627
  if (!stillCurrent()) {
3359
3628
  autoRouter.fail(agent, reservation, false);
3360
3629
  return;
@@ -3365,7 +3634,7 @@ function apply(ctx, config = {}) {
3365
3634
  return;
3366
3635
  }
3367
3636
  if (decision.kind === "select") {
3368
- const result = await selectCandidates(agent, extracted.problem, decision.candidates.map((candidate) => candidate.content), selected.autoVerifyRepeats, signal, extracted.images, "select");
3637
+ const result = await selectCandidates(agent, extracted.problem, decision.candidates.map((candidate) => candidate.content), repeats, signal, extracted.images, "select");
3369
3638
  if (!stillCurrent()) {
3370
3639
  autoRouter.fail(agent, reservation, false);
3371
3640
  return;
@@ -3375,14 +3644,14 @@ function apply(ctx, config = {}) {
3375
3644
  agent.steer(routeFeedback(decision, "Ranking:\n" + ranking + "\nProceed with " + decision.candidates[result.index].label + "."));
3376
3645
  return;
3377
3646
  }
3378
- const result = await trackProgress(agent, extracted.problem, decision.steps, decision.checkpoints, selected.autoVerifyRepeats, signal, extracted.images, "track");
3647
+ const result = await trackProgress(agent, extracted.problem, decision.steps, decision.checkpoints, repeats, signal, extracted.images, "track");
3379
3648
  if (!stillCurrent()) {
3380
3649
  autoRouter.fail(agent, reservation, false);
3381
3650
  return;
3382
3651
  }
3383
3652
  if (!autoRouter.commit(agent, reservation, admittedLastSeq)) return;
3384
3653
  const detail = result.scores.map((score, index) => "Checkpoint step " + decision.checkpoints[index] + ": " + (score * 100).toFixed(1) + "%").join("\n");
3385
- const continuation = result.scores.some((score) => score < selected.autoTrackCompletionThreshold) ? "\nContinue the unfinished work." : "\nPrepare final delivery evidence; final session verification is mandatory.";
3654
+ const continuation = (result.scores.length > 0 ? result.scores[result.scores.length - 1] : 0) < selected.autoTrackCompletionThreshold ? "\nContinue the unfinished work." : "\nPrepare final delivery evidence; final session verification is mandatory.";
3386
3655
  agent.steer(routeFeedback(decision, detail + continuation));
3387
3656
  return;
3388
3657
  } catch (error) {
@@ -3449,13 +3718,18 @@ function apply(ctx, config = {}) {
3449
3718
  autoRouter.fail(agent, finalReservation, false);
3450
3719
  return;
3451
3720
  }
3452
- if (result.winner === "A" && result.score >= selected.autoVerifyThreshold) autoRouter.commit(agent, finalReservation);
3721
+ const failed = failedAcceptanceCriteria(result.criteria, selected.autoVerifyThreshold);
3722
+ if (sessionAccepted({
3723
+ score: result.score,
3724
+ winner: result.winner,
3725
+ criteria: result.criteria
3726
+ }, selected.autoVerifyThreshold)) autoRouter.commit(agent, finalReservation);
3453
3727
  else {
3454
3728
  autoRouter.fail(agent, finalReservation, selected.autoVerifyMode === "strict");
3455
3729
  agent.steer(createUserMessage({
3456
3730
  content: [{
3457
3731
  type: "text",
3458
- text: automaticFeedback(result.score, result.baselineScore, result.winner, selected.autoVerifyThreshold)
3732
+ text: automaticFeedback(result.score, result.baselineScore, result.winner, selected.autoVerifyThreshold, failed)
3459
3733
  }],
3460
3734
  source: {
3461
3735
  kind: "plugin",
@@ -3843,6 +4117,6 @@ function apply(ctx, config = {}) {
3843
4117
  }));
3844
4118
  }
3845
4119
  //#endregion
3846
- export { AutoVerificationBudget, AutoVerifierRouter, Config, DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, GRANULARITY, LETTERS, MAX_ROUTED_CHECKPOINTS, MAX_TEAM_TASK_METADATA_CHARS, RequestLimiter, SCALE_DESCRIPTION, ScoreCache, SingleFlight, StatisticsStore, UNTRUSTED_EVIDENCE_NOTE, VERIFIER_TOOL_NAMES, VerifierEngine, accumulatePairs, analyzeAutoTask, analyzeStructuredRoute, apply, automaticFeedback, boundDecision, bradleyTerry, buildEvidenceIndex, buildPairwisePrompt, buildPlanPreReviewPrompt, buildProgressPrompt, buildSemanticRoutePrompt, buildTeamTaskVerificationPrompt, callVerifier, emptyRunStats, errorDetails, estimateRoutedCalls, evidenceNonce, extractProgressScore, extractScore, inject, inspectTeamTasks, isSubagentSession, latestDirectUserSeq, mergeStatisticsOverviews, name, normalizeCriteria, normalizeScoreLetter, parseSemanticRoute, parseStatisticsQuery, parseVerdictLetter, pivotRoundPairs, planFromArguments, rankScores, renderDelimitedBlock, resolveCacheFile, resolveStatisticsFile, resolveTopicDataDir, ringCycle, seededRandom, semanticDecision, semanticRouteHint, stableHash, topPivots };
4120
+ export { AutoVerifierRouter, Config, DEFAULT_CRITERIA, DEFAULT_GROUND_TRUTH_NOTE, GRANULARITY, LETTERS, MAX_ROUTED_CHECKPOINTS, MAX_TEAM_TASK_METADATA_CHARS, RequestLimiter, SCALE_DESCRIPTION, ScoreCache, SingleFlight, StatisticsStore, UNTRUSTED_EVIDENCE_NOTE, VERIFIER_TOOL_NAMES, VerifierEngine, accumulatePairs, analyzeAutoTask, analyzeStructuredRoute, apply, automaticFeedback, boundDecision, bradleyTerry, buildEvidenceIndex, buildPairwisePrompt, buildPlanPreReviewPrompt, buildProgressPrompt, buildSemanticRoutePrompt, buildTeamTaskVerificationPrompt, callVerifier, emptyRunStats, errorDetails, estimateRoutedCalls, evidenceNonce, extractProgressScore, extractScore, failedAcceptanceCriteria, inject, inspectTeamTasks, isSubagentSession, latestDirectUserSeq, mergeStatisticsOverviews, name, normalizeCriteria, normalizeScoreLetter, orientRoundPairs, parseSemanticRoute, parseStatisticsQuery, parseVerdictLetter, pivotRoundPairs, planFromArguments, rankScores, renderDelimitedBlock, resolveCacheFile, resolveStatisticsFile, resolveTopicDataDir, ringCycle, routedRepeats, seededRandom, semanticDecision, semanticRouteHint, sessionAccepted, stableHash, summarizeVerdict, topPivots };
3847
4121
 
3848
4122
  //# sourceMappingURL=index.js.map