dsh-llm-verifier 0.2.0 → 0.3.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 (46) hide show
  1. package/README.md +15 -11
  2. package/lib/client.js +137 -13
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +377 -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 +50 -1
  11. package/lib/types/client-i18n.d.ts.map +1 -1
  12. package/lib/types/client-i18n.js +83 -4
  13. package/lib/types/client-i18n.js.map +1 -1
  14. package/lib/types/client.d.ts +2 -2
  15. package/lib/types/client.d.ts.map +1 -1
  16. package/lib/types/client.js +81 -32
  17. package/lib/types/client.js.map +1 -1
  18. package/lib/types/engine.d.ts +19 -0
  19. package/lib/types/engine.d.ts.map +1 -1
  20. package/lib/types/engine.js +54 -4
  21. package/lib/types/engine.js.map +1 -1
  22. package/lib/types/index.d.ts.map +1 -1
  23. package/lib/types/index.js +32 -43
  24. package/lib/types/index.js.map +1 -1
  25. package/lib/types/router.d.ts +31 -0
  26. package/lib/types/router.d.ts.map +1 -1
  27. package/lib/types/router.js +181 -17
  28. package/lib/types/router.js.map +1 -1
  29. package/lib/types/statistics.d.ts +28 -0
  30. package/lib/types/statistics.d.ts.map +1 -1
  31. package/lib/types/statistics.js +101 -0
  32. package/lib/types/statistics.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/auto.test.ts +42 -23
  35. package/src/auto.ts +55 -46
  36. package/src/client-i18n.ts +93 -4
  37. package/src/client.test.ts +134 -3
  38. package/src/client.tsx +61 -4
  39. package/src/config.test.ts +6 -3
  40. package/src/engine.test.ts +40 -1
  41. package/src/engine.ts +56 -4
  42. package/src/index.ts +34 -44
  43. package/src/router.test.ts +191 -3
  44. package/src/router.ts +171 -14
  45. package/src/statistics.test.ts +116 -0
  46. 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,56 @@ 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
+ const BOOKKEEPING_TOOLS = /* @__PURE__ */ new Set([
1318
+ "todo_write",
1319
+ "create_goal",
1320
+ "get_goal",
1321
+ "update_goal",
1322
+ "interrupt_agent",
1323
+ "list_agents",
1324
+ "exit_plan_mode",
1325
+ "skill"
1326
+ ]);
1327
+ /** Whether a wrapper's dispatches were all bookkeeping; undefined/empty means it dispatched nothing. */
1328
+ function onlyBookkeepingDispatches(names) {
1329
+ if (names === void 0 || names.size === 0) return false;
1330
+ for (const name of names) if (!BOOKKEEPING_TOOLS.has(name)) return false;
1331
+ return true;
1332
+ }
1333
+ /**
1228
1334
  * Observed tool evidence available at one checkpoint.
1229
1335
  *
1230
1336
  * A checkpoint rendered from todo/team text alone can never clear the progress
1231
1337
  * threshold: the judge prompt explicitly refuses to credit a state that carries
1232
1338
  * no observed output. The most recent successful tool result at or before the
1233
1339
  * checkpoint is therefore attached as evidence.
1340
+ *
1341
+ * Bookkeeping tools are skipped: the checkpoint already renders the todo/team
1342
+ * snapshot they wrote, so their own result repeats it while displacing the real
1343
+ * output produced just before them (a live session attached `create_goal`,
1344
+ * `update_goal` and `interrupt_agent` output to checkpoints that had already run
1345
+ * the task's test suite).
1234
1346
  * @param index - evidence index of the current task.
1235
- * @param seq - checkpoint sequence number.
1347
+ * @param seq - checkpoint sequence number, or `Infinity` for the current state.
1236
1348
  * @param budget - maximum characters the evidence may occupy.
1349
+ * @param current - render the newest output in the task instead of the newest one before `seq`.
1237
1350
  * @returns Evidence block, or '' when the task produced none yet.
1238
1351
  */
1239
- function checkpointEvidence(index, seq, budget) {
1352
+ function checkpointEvidence(index, seq, budget, current = false) {
1240
1353
  if (budget < 64) return "";
1241
1354
  let latest;
1242
- for (const pair of index.calls.values()) if (pair.resultSeq <= seq && (latest === void 0 || pair.resultSeq > latest.resultSeq)) latest = pair;
1355
+ for (const pair of index.calls.values()) {
1356
+ if (BOOKKEEPING_TOOLS.has(pair.name) || !pair.text.trim()) continue;
1357
+ if (pair.resultSeq <= seq && (latest === void 0 || pair.resultSeq > latest.resultSeq)) latest = pair;
1358
+ }
1243
1359
  if (latest === void 0) return "";
1244
- const prefix = "\n\nLatest observed tool output before this checkpoint (" + latest.name + "):\n";
1360
+ 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
1361
  if (prefix.length >= budget) return "";
1246
1362
  return prefix + sanitizeVerifierText(latest.text, budget - prefix.length);
1247
1363
  }
@@ -1276,6 +1392,12 @@ const MAX_ROUTED_CHECKPOINTS = 6;
1276
1392
  * The result fits both caps by construction: each step is at most min(maxItemChars,
1277
1393
  * maxInputChars / kept.length) characters, so the combined length can never exceed
1278
1394
  * maxInputChars and boundDecision() no longer drops the whole route.
1395
+ *
1396
+ * The newest checkpoint is also the state the route is judging, so it is rendered as
1397
+ * the CURRENT state: its evidence is the newest observed output in the task rather
1398
+ * than the newest one before the last todo snapshot (which is often several tool calls
1399
+ * stale), and it carries the agent's latest prose as an explicitly labelled claim (see
1400
+ * {@link currentNarration}), because prose deliverables never reach a tool.
1279
1401
  * @param index - evidence index of the current task.
1280
1402
  * @param sources - checkpoints in chronological order.
1281
1403
  * @param maxItemChars - hard per-item cap enforced by boundDecision().
@@ -1295,13 +1417,35 @@ function renderCheckpointSteps(index, sources, maxItemChars, maxInputChars) {
1295
1417
  return {
1296
1418
  steps: kept.map((source, position) => {
1297
1419
  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;
1420
+ const isCurrent = position === kept.length - 1;
1421
+ const observed = checkpointEvidence(index, isCurrent ? Number.POSITIVE_INFINITY : source.seq, isCurrent ? Math.floor(evidenceBudget / 2) : evidenceBudget, isCurrent);
1422
+ const narration = isCurrent ? currentNarration(index, evidenceBudget - observed.length) : "";
1423
+ return sanitizeVerifierText(note + source.label + source.body, Math.max(1, stepCap - observed.length - narration.length)) + observed + narration;
1300
1424
  }),
1301
1425
  evidenceSeqs: kept.map((source) => source.seq),
1302
1426
  omitted
1303
1427
  };
1304
1428
  }
1429
+ /**
1430
+ * The agent's newest unverified prose, attached to the current checkpoint.
1431
+ *
1432
+ * Progress checkpoints are graded against "would this state satisfy the task", and
1433
+ * for work whose deliverable is prose the todo snapshot plus one tool result say
1434
+ * nothing about it: every checkpoint then scores "certainly NO" even though the
1435
+ * deliverable exists (a live review task scored 0% on all four checkpoints while the
1436
+ * final session acceptance of the same work passed). The block is labelled as a claim
1437
+ * so the judge can weigh it without treating it as observed output.
1438
+ * @param index - evidence index of the current task.
1439
+ * @param budget - maximum characters the narration may occupy.
1440
+ * @returns Narration block, or '' when the task produced no prose or has no budget.
1441
+ */
1442
+ function currentNarration(index, budget) {
1443
+ const narration = index.narration;
1444
+ if (narration === void 0 || budget < 64) return "";
1445
+ const prefix = "\n\nNewest agent narration in this task (the agent's own claim — NOT observed evidence):\n";
1446
+ if (87 >= budget) return "";
1447
+ return prefix + sanitizeVerifierText(narration.text, budget - 87);
1448
+ }
1305
1449
  function canonicalTeamTaskSnapshots(index) {
1306
1450
  const values = [];
1307
1451
  let previous = "";
@@ -1368,7 +1512,8 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
1368
1512
  reason: "changed durable todo snapshots",
1369
1513
  fingerprint: stableHash({
1370
1514
  kind: "track",
1371
- snapshots
1515
+ snapshots,
1516
+ steps: rendered.steps
1372
1517
  }),
1373
1518
  steps: rendered.steps,
1374
1519
  checkpoints: rendered.steps.map((_, i) => i + 1),
@@ -1389,7 +1534,8 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
1389
1534
  reason: "changed durable team tasks",
1390
1535
  fingerprint: stableHash({
1391
1536
  kind: "track",
1392
- teamSnapshots
1537
+ teamSnapshots,
1538
+ steps: rendered.steps
1393
1539
  }),
1394
1540
  steps: rendered.steps,
1395
1541
  checkpoints: rendered.steps.map((_, i) => i + 1),
@@ -1398,12 +1544,35 @@ function analyzeStructuredRoute(events, maxCandidates = 8, maxItemChars = 2e4, m
1398
1544
  }
1399
1545
  }
1400
1546
  }
1547
+ /**
1548
+ * Artifacts that only the classifier can turn into candidates.
1549
+ *
1550
+ * Todo and team snapshots are deliberately absent. They are the structured track
1551
+ * route's own input, and that route runs first — so listing them here could only make
1552
+ * the hint true in shapes the structured pass already claimed (or in a task that
1553
+ * already ran an explicit `verifier_track`, where re-classifying the same snapshots is
1554
+ * not wanted). The one shape left out is a snapshot series whose lists are all shorter
1555
+ * than two items, which is not worth a classification call.
1556
+ */
1557
+ const HINT_ARTIFACTS = /* @__PURE__ */ new Set([
1558
+ "subagent",
1559
+ "subagent_fork",
1560
+ "workflow",
1561
+ "exit_plan_mode"
1562
+ ]);
1563
+ /**
1564
+ * Whether a smart-mode stop boundary is worth a semantic classification call.
1565
+ *
1566
+ * The semantic phase only runs when the structured pass produced nothing, so this
1567
+ * answers "is there material the structured pass never consumes?" — never "are there
1568
+ * todo/team snapshots?", which the structured pass would have used already.
1569
+ * @param events - Session event log.
1570
+ * @returns True when a subagent/workflow/plan artifact exists.
1571
+ */
1401
1572
  function semanticRouteHint(events) {
1402
1573
  const index = buildEvidenceIndex(events);
1403
1574
  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;
1575
+ for (const pair of index.calls.values()) if (HINT_ARTIFACTS.has(pair.name)) return true;
1407
1576
  return false;
1408
1577
  }
1409
1578
  function buildSemanticRoutePrompt(problem, events, maxCandidates, maxItemChars = 2e4, maxInputChars = 6e4) {
@@ -1413,6 +1582,7 @@ function buildSemanticRoutePrompt(problem, events, maxCandidates, maxItemChars =
1413
1582
  let used = 0;
1414
1583
  let omitted = 0;
1415
1584
  for (const [callId, pair] of [...index.calls.entries()].reverse()) {
1585
+ if (BOOKKEEPING_TOOLS.has(pair.name)) continue;
1416
1586
  const text = sanitizeVerifierText(pair.text, maxItemChars);
1417
1587
  if (artifacts.length > 0 && used + text.length > maxInputChars) {
1418
1588
  omitted += 1;
@@ -1513,7 +1683,8 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
1513
1683
  const perItem = itemBudget(output.candidateCallIds.length, maxItemChars, maxInputChars);
1514
1684
  const candidates = output.candidateCallIds.map((callId, i) => {
1515
1685
  const pair = index.calls.get(callId);
1516
- return pair ? {
1686
+ if (!pair || BOOKKEEPING_TOOLS.has(pair.name)) return void 0;
1687
+ return {
1517
1688
  id: callId,
1518
1689
  groupId: "semantic",
1519
1690
  label: pair.name + " " + (i + 1),
@@ -1521,7 +1692,7 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
1521
1692
  callId,
1522
1693
  fromSeq: pair.callSeq,
1523
1694
  toSeq: pair.resultSeq
1524
- } : void 0;
1695
+ };
1525
1696
  }).filter((candidate) => candidate !== void 0);
1526
1697
  if (candidates.length !== output.candidateCallIds.length) return void 0;
1527
1698
  const fingerprint = stableHash({
@@ -1556,6 +1727,26 @@ function semanticDecision(output, events, maxItemChars = 2e4, maxInputChars = 6e
1556
1727
  * @param criteriaCount - number of criteria evaluated per comparison.
1557
1728
  * @returns The planned model-call count, never below 1.
1558
1729
  */
1730
+ /**
1731
+ * Scoring repeats a routed decision actually runs.
1732
+ *
1733
+ * `compare` judges ONE unordered pair, and `VerifierEngine.compare` only swaps the
1734
+ * candidates on odd repeat indices — with the shipped default of a single round the
1735
+ * first candidate therefore always sat in slot A, so the winner was partly decided by
1736
+ * listing order. Rounding its count up to an even number averages a swapped round and
1737
+ * cancels that.
1738
+ *
1739
+ * `select` does not need it: its ring is symmetric by construction and the pivot round
1740
+ * is oriented per pair by the engine, so one round is already unbiased. `track` scores
1741
+ * a single checkpoint list and has no slots at all.
1742
+ * @param decision - the routed decision about to run.
1743
+ * @param configured - the configured auto-route repeat count.
1744
+ * @returns The repeat count to pass to the engine.
1745
+ */
1746
+ function routedRepeats(decision, configured) {
1747
+ if (decision.kind !== "compare") return configured;
1748
+ return configured % 2 === 0 ? configured : configured + 1;
1749
+ }
1559
1750
  function estimateRoutedCalls(decision, repeats, criteriaCount) {
1560
1751
  if (decision.kind === "compare") return Math.max(1, criteriaCount * repeats);
1561
1752
  if (decision.kind === "track") return Math.max(1, repeats);
@@ -1754,7 +1945,23 @@ function parseSessionVerdict(value) {
1754
1945
  if (start < 0 || end <= start) return void 0;
1755
1946
  try {
1756
1947
  const parsed = JSON.parse(text.slice(start, end + 1));
1757
- return typeof parsed === "object" && parsed !== null ? parsed : void 0;
1948
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1949
+ const row = parsed;
1950
+ const criteria = [];
1951
+ for (const entry of Array.isArray(row.criteria) ? row.criteria : []) {
1952
+ const item = typeof entry === "object" && entry !== null ? entry : {};
1953
+ if (typeof item.id !== "string" || typeof item.scoreA !== "number" || !Number.isFinite(item.scoreA)) continue;
1954
+ criteria.push({
1955
+ id: item.id,
1956
+ ...typeof item.name === "string" ? { name: item.name } : {},
1957
+ score: item.scoreA
1958
+ });
1959
+ }
1960
+ return {
1961
+ winner: row.winner,
1962
+ score: row.score,
1963
+ criteria
1964
+ };
1758
1965
  } catch {
1759
1966
  return;
1760
1967
  }
@@ -1810,7 +2017,8 @@ function analyzeAutoTask(events, policy) {
1810
2017
  manualVerdicts.push({
1811
2018
  seq: result.seq,
1812
2019
  winner: verdict?.winner,
1813
- score: verdict?.score
2020
+ score: verdict?.score,
2021
+ criteria: verdict?.criteria
1814
2022
  });
1815
2023
  }
1816
2024
  for (const entry of successfulDispatches) {
@@ -1819,11 +2027,16 @@ function analyzeAutoTask(events, policy) {
1819
2027
  manualVerdicts.push({
1820
2028
  seq: entry.seq,
1821
2029
  winner: verdict?.winner,
1822
- score: verdict?.score
2030
+ score: verdict?.score,
2031
+ criteria: verdict?.criteria
1823
2032
  });
1824
2033
  }
1825
2034
  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));
2035
+ const manualVerificationAccepted = manualVerdicts.some((verdict) => verdict.winner === "A" && typeof verdict.score === "number" && sessionAccepted({
2036
+ score: verdict.score,
2037
+ winner: "A",
2038
+ criteria: verdict.criteria
2039
+ }, policy.threshold) && !stale(verdict.seq));
1827
2040
  if (policy.mode === "manual") return {
1828
2041
  taskStartSeq,
1829
2042
  toolCalls,
@@ -1886,49 +2099,40 @@ function analyzeAutoTask(events, policy) {
1886
2099
  };
1887
2100
  }
1888
2101
  /**
1889
- * Session/task acceptance budget.
2102
+ * Criteria that do not clear the acceptance threshold.
1890
2103
  *
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.
2104
+ * The acceptance score is the MEAN over criteria, so a session with one requirement at
2105
+ * zero and the others perfect averaged ~0.67 and cleared the 0.65 default: a single
2106
+ * failed requirement was arithmetically invisible. The gate therefore also requires
2107
+ * every criterion to clear the threshold on its own.
2108
+ * @param criteria - per-criterion A-side scores, when the judge reported them.
2109
+ * @param threshold - acceptance threshold.
2110
+ * @returns The failing criteria, in report order.
1895
2111
  */
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) {
2112
+ function failedAcceptanceCriteria(criteria, threshold) {
2113
+ return (criteria ?? []).filter((criterion) => !(criterion.score >= threshold));
2114
+ }
2115
+ /**
2116
+ * Whether a session acceptance clears the gate.
2117
+ *
2118
+ * The empty-work baseline is a fixed sentence that always scores 0, so the comparison
2119
+ * itself is decorative; what actually decides is the session's own score, the winner
2120
+ * against that baseline, and (since the mean could hide a failed requirement) every
2121
+ * criterion clearing the threshold.
2122
+ * @param evidence - session acceptance result (score, winner and per-criterion scores).
2123
+ * @param threshold - acceptance threshold.
2124
+ * @returns True only when the session may conclude.
2125
+ */
2126
+ function sessionAccepted(evidence, threshold) {
2127
+ if (evidence.winner !== "A" || !(evidence.score >= threshold)) return false;
2128
+ return failedAcceptanceCriteria(evidence.criteria, threshold).length === 0;
2129
+ }
2130
+ function automaticFeedback(score, baselineScore, winner, threshold, failedCriteria = []) {
1928
2131
  const percent = (value) => (value * 100).toFixed(1) + "%";
1929
2132
  return [
1930
2133
  "[Automatic verifier gate]",
1931
2134
  `The independent verifier did not clear this task for completion: evidence score ${percent(score)}, baseline ${percent(baselineScore)}, verdict ${winner}, required ${percent(threshold)}.`,
2135
+ ...failedCriteria.length > 0 ? ["Criteria below the threshold: " + failedCriteria.map((criterion) => (criterion.name ?? criterion.id) + " " + percent(criterion.score)).join("; ") + "."] : [],
1932
2136
  "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
2137
  ].join("\n");
1934
2138
  }
@@ -2069,12 +2273,100 @@ const VERIFIER_TOOL_NAMES = [
2069
2273
  "verifier_track",
2070
2274
  "verifier_current_session"
2071
2275
  ];
2276
+ /** Upper bound on the stored checkpoint progression; one explicit call can legitimately carry 32 steps. */
2277
+ const MAX_VERDICT_SCORES = 64;
2278
+ /** Upper bound on the stored per-criterion breakdown; the default rubric has three criteria. */
2279
+ const MAX_VERDICT_CRITERIA = 16;
2280
+ /**
2281
+ * Compact summary of what the judges decided, stored beside the call counters so
2282
+ * the dashboard can answer "why did this fail?" instead of only "how much did it cost".
2283
+ *
2284
+ * Pure on purpose: this mapping used to live inside the plugin closure, where the two
2285
+ * bugs it carried (a track verdict reported as the historical minimum, a comparison
2286
+ * reported as the loser's score under a threshold it never used) could not be tested.
2287
+ * @param toolName - the verifier tool that produced the value.
2288
+ * @param value - its rendered result.
2289
+ * @param phase - which stage produced it (explicit | compare | select | track | final | ...).
2290
+ * @param thresholds - resolved acceptance thresholds.
2291
+ * @returns A verdict summary; score fields are omitted when the tool has none.
2292
+ */
2293
+ function summarizeVerdict(toolName, value, phase, thresholds) {
2294
+ const row = typeof value === "object" && value !== null ? value : {};
2295
+ const numberAt = (key) => typeof row[key] === "number" && Number.isFinite(row[key]) ? row[key] : void 0;
2296
+ const scores = Array.isArray(row.scores) ? row.scores.filter((entry) => typeof entry === "number" && Number.isFinite(entry)) : [];
2297
+ const winner = row.winner === "A" || row.winner === "B" || row.winner === "tie" ? row.winner : void 0;
2298
+ if (toolName === "verifier_route_classify") return {
2299
+ phase,
2300
+ outcome: "classified"
2301
+ };
2302
+ if (toolName === "verifier_select") {
2303
+ const index = numberAt("index");
2304
+ const best = index === void 0 ? void 0 : scores[index];
2305
+ return {
2306
+ phase,
2307
+ outcome: "ranked",
2308
+ ...best !== void 0 ? { score: best } : {}
2309
+ };
2310
+ }
2311
+ if (toolName === "verifier_track") {
2312
+ const latest = scores.length > 0 ? scores[scores.length - 1] : void 0;
2313
+ const threshold = thresholds.autoTrackCompletionThreshold;
2314
+ return {
2315
+ phase,
2316
+ outcome: latest !== void 0 && latest >= threshold ? "passed" : "below-threshold",
2317
+ ...latest !== void 0 ? { score: latest } : {},
2318
+ ...scores.length > 1 ? { scores: [...scores] } : {},
2319
+ threshold
2320
+ };
2321
+ }
2322
+ if (toolName === "verifier_compare") {
2323
+ const scoreA = numberAt("score") ?? numberAt("scoreA");
2324
+ const scoreB = numberAt("scoreB");
2325
+ const score = winner === "B" ? scoreB : scoreA;
2326
+ return {
2327
+ phase,
2328
+ outcome: winner === "tie" ? "tie" : "compared",
2329
+ ...score !== void 0 ? { score } : {},
2330
+ ...scoreB !== void 0 ? { scoreB } : {},
2331
+ ...winner !== void 0 ? { winner } : {}
2332
+ };
2333
+ }
2334
+ const score = numberAt("score") ?? numberAt("scoreA");
2335
+ const baselineScore = numberAt("baselineScore");
2336
+ const threshold = thresholds.autoVerifyThreshold;
2337
+ 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) => ({
2338
+ id: entry.id,
2339
+ score: entry.scoreA
2340
+ })) : [];
2341
+ const belowThreshold = criteria.filter((entry) => !(entry.score >= threshold));
2342
+ return {
2343
+ phase,
2344
+ outcome: winner === "tie" ? "tie" : winner === "A" && score !== void 0 && score >= threshold && belowThreshold.length === 0 ? "passed" : "below-threshold",
2345
+ ...score !== void 0 ? { score } : {},
2346
+ ...baselineScore !== void 0 ? { baselineScore } : {},
2347
+ ...criteria.length > 0 ? { criteria } : {},
2348
+ ...winner !== void 0 ? { winner } : {},
2349
+ threshold
2350
+ };
2351
+ }
2072
2352
  function cleanVerdict(input) {
2073
2353
  if (typeof input !== "object" || input === null || Array.isArray(input)) return void 0;
2074
2354
  const verdict = {};
2075
2355
  if (typeof input.phase === "string") verdict.phase = input.phase;
2076
2356
  if (typeof input.outcome === "string") verdict.outcome = input.outcome;
2077
2357
  if (typeof input.score === "number" && Number.isFinite(input.score)) verdict.score = input.score;
2358
+ if (Array.isArray(input.scores)) {
2359
+ const scores = input.scores.filter((entry) => typeof entry === "number" && Number.isFinite(entry)).slice(0, MAX_VERDICT_SCORES);
2360
+ if (scores.length > 0) verdict.scores = scores;
2361
+ }
2362
+ if (typeof input.scoreB === "number" && Number.isFinite(input.scoreB)) verdict.scoreB = input.scoreB;
2363
+ if (Array.isArray(input.criteria)) {
2364
+ 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) => ({
2365
+ id: entry.id,
2366
+ score: entry.score
2367
+ }));
2368
+ if (criteria.length > 0) verdict.criteria = criteria;
2369
+ }
2078
2370
  if (typeof input.baselineScore === "number" && Number.isFinite(input.baselineScore)) verdict.baselineScore = input.baselineScore;
2079
2371
  if (input.winner === "A" || input.winner === "B" || input.winner === "tie") verdict.winner = input.winner;
2080
2372
  if (typeof input.threshold === "number" && Number.isFinite(input.threshold)) verdict.threshold = input.threshold;
@@ -2842,54 +3134,12 @@ function apply(ctx, config = {}) {
2842
3134
  const requireEnabled = () => {
2843
3135
  if (!current().enabled) throw new Error("llm-verifier: verifier tools are disabled — enable them in Settings → LLM Verifier");
2844
3136
  };
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
3137
  const verdictFrom = (toolName, value, phase) => {
2854
3138
  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
- };
3139
+ return summarizeVerdict(toolName, value, phase, {
3140
+ autoVerifyThreshold: selected.autoVerifyThreshold,
3141
+ autoTrackCompletionThreshold: selected.autoTrackCompletionThreshold
3142
+ });
2893
3143
  };
2894
3144
  const record = async (toolName, agent, operation, phase = "explicit") => {
2895
3145
  const startedAt = Date.now();
@@ -2996,6 +3246,11 @@ function apply(ctx, config = {}) {
2996
3246
  score: compared.scoreA,
2997
3247
  baselineScore: compared.scoreB,
2998
3248
  winner: compared.winner,
3249
+ criteria: compared.criteria.map((row) => ({
3250
+ id: row.id,
3251
+ name: row.name,
3252
+ score: row.scoreA
3253
+ })),
2999
3254
  fromSeq: extracted.fromSeq,
3000
3255
  toSeq: extracted.toSeq,
3001
3256
  omittedCharacters: extracted.omittedCharacters,
@@ -3235,8 +3490,9 @@ function apply(ctx, config = {}) {
3235
3490
  }
3236
3491
  const verdict = parseVerdictLetter(classified.text);
3237
3492
  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");
3493
+ const strict = selected.autoVerifyMode === "strict";
3494
+ autoRouter.fail(agent, taskReservation, strict);
3495
+ ctx.logger.warn("llm-verifier team task verification produced no verdict line; task " + task.id + (strict ? " stays blocked" : " was not gated"));
3240
3496
  continue;
3241
3497
  }
3242
3498
  if (verdict.score >= selected.autoVerifyThreshold) {
@@ -3330,7 +3586,8 @@ function apply(ctx, config = {}) {
3330
3586
  }
3331
3587
  if (!stillCurrent()) return;
3332
3588
  if (decision) {
3333
- const expectedCalls = estimateRoutedCalls(decision, selected.autoVerifyRepeats, DEFAULT_CRITERIA.length) * selected.judges.length;
3589
+ const repeats = routedRepeats(decision, selected.autoVerifyRepeats);
3590
+ const expectedCalls = estimateRoutedCalls(decision, repeats, DEFAULT_CRITERIA.length) * selected.judges.length;
3334
3591
  const reservation = autoRouter.reserve(agent, decision.kind, decision.fingerprint, expectedCalls, policy);
3335
3592
  if (reservation === void 0) {
3336
3593
  const exhausted = autoRouter.budgetExhausted(agent, expectedCalls, policy);
@@ -3354,7 +3611,7 @@ function apply(ctx, config = {}) {
3354
3611
  if (reservation) try {
3355
3612
  const extracted = await extractTask(agent, evidence.taskStartSeq, admittedLastSeq, selected.autoVerifyMaxChars, signal);
3356
3613
  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");
3614
+ const result = await compareCandidates(agent, extracted.problem, decision.candidates[0].content, decision.candidates[1].content, repeats, signal, extracted.images, "compare");
3358
3615
  if (!stillCurrent()) {
3359
3616
  autoRouter.fail(agent, reservation, false);
3360
3617
  return;
@@ -3365,7 +3622,7 @@ function apply(ctx, config = {}) {
3365
3622
  return;
3366
3623
  }
3367
3624
  if (decision.kind === "select") {
3368
- const result = await selectCandidates(agent, extracted.problem, decision.candidates.map((candidate) => candidate.content), selected.autoVerifyRepeats, signal, extracted.images, "select");
3625
+ const result = await selectCandidates(agent, extracted.problem, decision.candidates.map((candidate) => candidate.content), repeats, signal, extracted.images, "select");
3369
3626
  if (!stillCurrent()) {
3370
3627
  autoRouter.fail(agent, reservation, false);
3371
3628
  return;
@@ -3375,14 +3632,14 @@ function apply(ctx, config = {}) {
3375
3632
  agent.steer(routeFeedback(decision, "Ranking:\n" + ranking + "\nProceed with " + decision.candidates[result.index].label + "."));
3376
3633
  return;
3377
3634
  }
3378
- const result = await trackProgress(agent, extracted.problem, decision.steps, decision.checkpoints, selected.autoVerifyRepeats, signal, extracted.images, "track");
3635
+ const result = await trackProgress(agent, extracted.problem, decision.steps, decision.checkpoints, repeats, signal, extracted.images, "track");
3379
3636
  if (!stillCurrent()) {
3380
3637
  autoRouter.fail(agent, reservation, false);
3381
3638
  return;
3382
3639
  }
3383
3640
  if (!autoRouter.commit(agent, reservation, admittedLastSeq)) return;
3384
3641
  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.";
3642
+ 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
3643
  agent.steer(routeFeedback(decision, detail + continuation));
3387
3644
  return;
3388
3645
  } catch (error) {
@@ -3449,13 +3706,18 @@ function apply(ctx, config = {}) {
3449
3706
  autoRouter.fail(agent, finalReservation, false);
3450
3707
  return;
3451
3708
  }
3452
- if (result.winner === "A" && result.score >= selected.autoVerifyThreshold) autoRouter.commit(agent, finalReservation);
3709
+ const failed = failedAcceptanceCriteria(result.criteria, selected.autoVerifyThreshold);
3710
+ if (sessionAccepted({
3711
+ score: result.score,
3712
+ winner: result.winner,
3713
+ criteria: result.criteria
3714
+ }, selected.autoVerifyThreshold)) autoRouter.commit(agent, finalReservation);
3453
3715
  else {
3454
3716
  autoRouter.fail(agent, finalReservation, selected.autoVerifyMode === "strict");
3455
3717
  agent.steer(createUserMessage({
3456
3718
  content: [{
3457
3719
  type: "text",
3458
- text: automaticFeedback(result.score, result.baselineScore, result.winner, selected.autoVerifyThreshold)
3720
+ text: automaticFeedback(result.score, result.baselineScore, result.winner, selected.autoVerifyThreshold, failed)
3459
3721
  }],
3460
3722
  source: {
3461
3723
  kind: "plugin",
@@ -3843,6 +4105,6 @@ function apply(ctx, config = {}) {
3843
4105
  }));
3844
4106
  }
3845
4107
  //#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 };
4108
+ 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
4109
 
3848
4110
  //# sourceMappingURL=index.js.map