dsh-llm-verifier 0.2.1 → 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.
- package/README.md +15 -11
- package/lib/client.js +23 -3
- package/lib/client.js.map +1 -1
- package/lib/index.js +377 -115
- package/lib/index.js.map +1 -1
- package/lib/types/auto.d.ts +32 -12
- package/lib/types/auto.d.ts.map +1 -1
- package/lib/types/auto.js +50 -38
- package/lib/types/auto.js.map +1 -1
- package/lib/types/client-i18n.d.ts +21 -1
- package/lib/types/client-i18n.d.ts.map +1 -1
- package/lib/types/client-i18n.js +22 -4
- package/lib/types/client-i18n.js.map +1 -1
- package/lib/types/client.d.ts.map +1 -1
- package/lib/types/client.js +1 -1
- package/lib/types/client.js.map +1 -1
- package/lib/types/engine.d.ts +19 -0
- package/lib/types/engine.d.ts.map +1 -1
- package/lib/types/engine.js +54 -4
- package/lib/types/engine.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +32 -43
- package/lib/types/index.js.map +1 -1
- package/lib/types/router.d.ts +31 -0
- package/lib/types/router.d.ts.map +1 -1
- package/lib/types/router.js +181 -17
- package/lib/types/router.js.map +1 -1
- package/lib/types/statistics.d.ts +28 -0
- package/lib/types/statistics.d.ts.map +1 -1
- package/lib/types/statistics.js +101 -0
- package/lib/types/statistics.js.map +1 -1
- package/package.json +1 -1
- package/src/auto.test.ts +42 -23
- package/src/auto.ts +55 -46
- package/src/client-i18n.ts +32 -4
- package/src/client.test.ts +48 -3
- package/src/client.tsx +2 -0
- package/src/config.test.ts +6 -3
- package/src/engine.test.ts +40 -1
- package/src/engine.ts +56 -4
- package/src/index.ts +34 -44
- package/src/router.test.ts +191 -3
- package/src/router.ts +171 -14
- package/src/statistics.test.ts +116 -0
- 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
|
|
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([
|
|
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)
|
|
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())
|
|
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
|
|
1299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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" &&
|
|
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
|
-
*
|
|
2102
|
+
* Criteria that do not clear the acceptance threshold.
|
|
1890
2103
|
*
|
|
1891
|
-
* The
|
|
1892
|
-
*
|
|
1893
|
-
*
|
|
1894
|
-
*
|
|
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
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
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
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
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
|
-
|
|
3239
|
-
|
|
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
|
|
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,
|
|
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),
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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 {
|
|
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
|