@remnic/bench 9.6.18 → 9.6.19

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/dist/index.js CHANGED
@@ -644,6 +644,140 @@ function benchmarkRecallBudgetForSessionCount(sessionCount) {
644
644
  }
645
645
 
646
646
  // src/adapters/remnic-adapter.ts
647
+ var DEFAULT_ANSWER_SUPPORT_MIN_COVERAGE = 0.34;
648
+ var ANSWER_SUPPORT_STOP_WORDS = /* @__PURE__ */ new Set([
649
+ "about",
650
+ "after",
651
+ "again",
652
+ "also",
653
+ "answer",
654
+ "before",
655
+ "being",
656
+ "could",
657
+ "does",
658
+ "from",
659
+ "have",
660
+ "information",
661
+ "into",
662
+ "just",
663
+ "know",
664
+ "memory",
665
+ "might",
666
+ "please",
667
+ "question",
668
+ "recall",
669
+ "remember",
670
+ "should",
671
+ "that",
672
+ "their",
673
+ "there",
674
+ "these",
675
+ "they",
676
+ "this",
677
+ "those",
678
+ "user",
679
+ "using",
680
+ "what",
681
+ "when",
682
+ "where",
683
+ "which",
684
+ "while",
685
+ "with",
686
+ "would",
687
+ "your"
688
+ ]);
689
+ function resolveAnswerSupportMinCoverage(config) {
690
+ const raw = config?.answerSupportMinCoverage;
691
+ if (raw === void 0) return DEFAULT_ANSWER_SUPPORT_MIN_COVERAGE;
692
+ const parsed = typeof raw === "number" ? raw : Number(raw);
693
+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 1) {
694
+ throw new Error("answerSupportMinCoverage must be a finite number greater than 0 and at most 1.");
695
+ }
696
+ return parsed;
697
+ }
698
+ function resolveSkipExtractionLcmFirst(config) {
699
+ const raw = config?.skipExtractionLcmFirst;
700
+ if (raw === void 0) return true;
701
+ if (typeof raw === "boolean") return raw;
702
+ if (typeof raw === "string") {
703
+ const normalized = raw.trim().toLowerCase();
704
+ if (["true", "1", "yes", "on"].includes(normalized)) return true;
705
+ if (["false", "0", "no", "off"].includes(normalized)) return false;
706
+ }
707
+ throw new Error(
708
+ "skipExtractionLcmFirst must be a boolean or one of true/false, 1/0, yes/no, on/off."
709
+ );
710
+ }
711
+ function shouldIncludeCoreRecallForReplay(options) {
712
+ return options.useCoreMemoryPipeline && (options.replayExtractionMode !== "skip" || !options.skipExtractionLcmFirst);
713
+ }
714
+ function normalizeSupportToken(value) {
715
+ if (value.length > 5 && value.endsWith("ing")) return value.slice(0, -3);
716
+ if (value.length > 4 && value.endsWith("ed")) {
717
+ const base = value.slice(0, -2);
718
+ return /[vs]$/.test(base) ? `${base}e` : base;
719
+ }
720
+ if (value.length > 4 && value.endsWith("es")) return value.slice(0, -2);
721
+ if (value.length > 3 && value.endsWith("s")) return value.slice(0, -1);
722
+ return value;
723
+ }
724
+ function supportTerms(value) {
725
+ return [...new Set(
726
+ (value.toLowerCase().match(/[\p{L}\p{N}][\p{L}\p{N}_-]{2,}/gu) ?? []).filter((term) => !ANSWER_SUPPORT_STOP_WORDS.has(term)).map(normalizeSupportToken).filter((term) => !ANSWER_SUPPORT_STOP_WORDS.has(term) && !/^\d+$/.test(term))
727
+ )];
728
+ }
729
+ function exactContextEvidenceLines(recalledText) {
730
+ return recalledText.split(/\r?\n/).map((line) => line.trim()).filter((line) => {
731
+ if (!line || /^#{1,6}\s/.test(line)) return false;
732
+ return !/^(?:answer guidance:|distinct user-stated targets found:|no (?:direct|historically valid)|these direct temporal statements|this is the most recent|use this list|when answering)/i.test(line);
733
+ });
734
+ }
735
+ function assessRemnicRecallSupport(request, supportThreshold = DEFAULT_ANSWER_SUPPORT_MIN_COVERAGE) {
736
+ if (request.recalledText.trim().length === 0) {
737
+ return { status: "empty", reason: "exact responder context is empty", evidenceCount: 0 };
738
+ }
739
+ const queryTerms = supportTerms(request.query);
740
+ if (queryTerms.length < 2) {
741
+ return {
742
+ status: "unavailable",
743
+ reason: "query has fewer than two distinctive terms for conservative support scoring"
744
+ };
745
+ }
746
+ const evidenceLines = exactContextEvidenceLines(request.recalledText);
747
+ const evidenceTermSets = evidenceLines.map((line) => new Set(supportTerms(line)));
748
+ const matchedTerms = queryTerms.filter(
749
+ (term) => evidenceTermSets.some((terms) => terms.has(term))
750
+ );
751
+ const evidenceCount = evidenceTermSets.filter(
752
+ (terms) => matchedTerms.some((term) => terms.has(term))
753
+ ).length;
754
+ const coverage = matchedTerms.length / queryTerms.length;
755
+ if (evidenceCount === 0) {
756
+ return {
757
+ status: "empty",
758
+ reason: "exact responder context contains no matching evidence terms",
759
+ evidenceCount: 0,
760
+ maxScore: 0,
761
+ supportThreshold
762
+ };
763
+ }
764
+ if (coverage < supportThreshold) {
765
+ return {
766
+ status: "weak",
767
+ reason: "exact responder context has only weak lexical support",
768
+ evidenceCount,
769
+ maxScore: coverage,
770
+ supportThreshold
771
+ };
772
+ }
773
+ return {
774
+ status: "supported",
775
+ reason: "exact responder context has sufficient lexical support",
776
+ evidenceCount,
777
+ maxScore: coverage,
778
+ supportThreshold
779
+ };
780
+ }
647
781
  var BENCH_ADAPTER_SHARED_CONFIG = {
648
782
  qmdEnabled: false,
649
783
  qmdColdTierEnabled: false,
@@ -1733,6 +1867,12 @@ function createAdapterFactory(mode) {
1733
1867
  return async function createAdapter(options = {}) {
1734
1868
  const useCoreMemoryPipeline = shouldUseCoreMemoryPipeline(mode, options);
1735
1869
  const replayExtractionMode = options.replayExtractionMode ?? "await";
1870
+ const answerSupportMinCoverage = resolveAnswerSupportMinCoverage(
1871
+ options.configOverrides
1872
+ );
1873
+ const skipExtractionLcmFirst = resolveSkipExtractionLcmFirst(
1874
+ options.configOverrides
1875
+ );
1736
1876
  const replaySourceValidAtMode = normalizeReplaySourceValidAtMode(
1737
1877
  options.replaySourceValidAtMode
1738
1878
  );
@@ -1999,6 +2139,11 @@ function createAdapterFactory(mode) {
1999
2139
  }
2000
2140
  const recallAsOf = normalizeBenchRecallAsOf(recallOptions.asOf);
2001
2141
  const historicalRecall = recallAsOf !== void 0;
2142
+ const includeCoreRecall = shouldIncludeCoreRecallForReplay({
2143
+ useCoreMemoryPipeline,
2144
+ replayExtractionMode,
2145
+ skipExtractionLcmFirst
2146
+ });
2002
2147
  if (historicalRecall && (!useCoreMemoryPipeline || replayExtractionMode === "skip" || replaySourceValidAtMode !== "historical")) {
2003
2148
  throw new Error(
2004
2149
  "benchmark historical recall requires core replay extraction with replaySourceValidAtMode=historical; enable the core memory pipeline and do not use replayExtractionMode=skip"
@@ -2097,7 +2242,7 @@ function createAdapterFactory(mode) {
2097
2242
  sections.push(trajectoryAnalysisEvidence);
2098
2243
  usedChars += trajectoryAnalysisEvidence.length;
2099
2244
  }
2100
- if (useCoreMemoryPipeline && !requireDirectPersonalHistoryEvidence && !requireDirectTemporalEvidence && !hasTemporalIntervalEvidence && !hasDependencyVersionEvidence && !hasUserImplementationTargetEvidence) {
2245
+ if (includeCoreRecall && !requireDirectPersonalHistoryEvidence && !requireDirectTemporalEvidence && !hasTemporalIntervalEvidence && !hasDependencyVersionEvidence && !hasUserImplementationTargetEvidence) {
2101
2246
  const coreBudget = historicalRecall ? Math.max(0, budget - usedChars) : Math.max(
2102
2247
  0,
2103
2248
  Math.min(
@@ -2135,7 +2280,7 @@ ${coreRecall.trim()}`;
2135
2280
  const suppressBroadSummary = historicalRecall || requireDirectPersonalHistoryEvidence || requireDirectTemporalEvidence || hasTemporalIntervalEvidence || hasDependencyVersionEvidence || hasUserImplementationTargetEvidence || preferFocusedExplicitContext && !!exactReferenceEvidence;
2136
2281
  if (query && !historicalRecall && !hasTemporalIntervalEvidence && !hasDependencyVersionEvidence && !hasUserImplementationTargetEvidence) {
2137
2282
  const remainingAfterCore = Math.max(0, budget - usedChars);
2138
- const searchBudget = useCoreMemoryPipeline ? Math.max(0, Math.floor(remainingAfterCore * 0.75)) : Math.max(0, Math.floor(remainingAfterCore * 0.7));
2283
+ const searchBudget = includeCoreRecall ? Math.max(0, Math.floor(remainingAfterCore * 0.75)) : Math.max(0, Math.floor(remainingAfterCore * 0.7));
2139
2284
  const searchLimit = Math.max(6, Math.min(18, Math.floor(budget / 2e3)));
2140
2285
  const searchResults = await waitForRecall(
2141
2286
  engine.searchContextFull(
@@ -2151,7 +2296,7 @@ ${coreRecall.trim()}`;
2151
2296
  const directTemporalTurnIds = /* @__PURE__ */ new Set();
2152
2297
  for (const result of searchResults) {
2153
2298
  throwIfBenchPhaseAborted(control, "recall");
2154
- const windowRadius = preferFocusedExplicitContext ? 2 : useCoreMemoryPipeline ? 3 : 1;
2299
+ const windowRadius = preferFocusedExplicitContext ? 2 : includeCoreRecall ? 3 : 1;
2155
2300
  const fromTurn = Math.max(0, result.turn_index - windowRadius);
2156
2301
  const toTurn = result.turn_index + windowRadius;
2157
2302
  const expanded = await waitForRecall(
@@ -2159,7 +2304,7 @@ ${coreRecall.trim()}`;
2159
2304
  result.session_id,
2160
2305
  fromTurn,
2161
2306
  toTurn,
2162
- useCoreMemoryPipeline ? 1600 : 600
2307
+ includeCoreRecall ? 1600 : 600
2163
2308
  )
2164
2309
  );
2165
2310
  if (expanded.length === 0) {
@@ -2320,6 +2465,10 @@ ${expanded.map((message) => `[${message.role}]: ${message.content}`).join("\n")}
2320
2465
  const joined = sections.join("\n\n");
2321
2466
  return joined.length > budget ? joined.slice(0, budget) : joined;
2322
2467
  },
2468
+ async assessRecallSupport(request, control) {
2469
+ throwIfBenchPhaseAborted(control, "assessRecallSupport");
2470
+ return assessRemnicRecallSupport(request, answerSupportMinCoverage);
2471
+ },
2323
2472
  async search(query, limit, sessionId, control) {
2324
2473
  throwIfBenchPhaseAborted(control, "search");
2325
2474
  const normalizedSessionId = normalizeOptionalBenchSessionId(sessionId);
@@ -3292,6 +3441,747 @@ ${message.content}`).digest("hex").slice(0, 16);
3292
3441
  return `bench-${index}-${digest}`;
3293
3442
  }
3294
3443
 
3444
+ // src/adapters/mcp-memory-adapter.ts
3445
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3446
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3447
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
3448
+ import { fileURLToPath } from "url";
3449
+ import { existsSync } from "fs";
3450
+ var McpMemoryBackendError = class extends Error {
3451
+ code;
3452
+ detail;
3453
+ constructor(result) {
3454
+ super(`${result.error}: ${result.detail}`, { cause: result.cause });
3455
+ this.name = "McpMemoryBackendError";
3456
+ this.code = result.error;
3457
+ this.detail = result.detail;
3458
+ }
3459
+ };
3460
+ var DEFAULT_TOOL_NAMES = {
3461
+ store: ["store_memory", "add_memory", "memory_store", "create_memory"],
3462
+ recall: ["search_memory", "recall", "memory_search", "search_memories"],
3463
+ correct: ["correct_memory", "update_memory", "memory_correct", "update_memories"],
3464
+ reset: ["delete_memory", "clear_memories", "memory_delete", "reset_memory"]
3465
+ };
3466
+ var ARGUMENT_ALIASES = {
3467
+ namespace: ["namespace", "scope", "user_id", "userId", "run_id"],
3468
+ sessionId: ["sessionId", "session_id", "session", "conversation_id"],
3469
+ content: ["content", "text", "memory", "message", "correction"],
3470
+ role: ["role", "speaker"],
3471
+ timestamp: ["timestamp", "at", "created_at"],
3472
+ query: ["query", "search", "q", "text"],
3473
+ limit: ["limit", "top_k", "count", "max_results"]
3474
+ };
3475
+ var TOOL_OPERATIONS = Object.freeze([
3476
+ "store",
3477
+ "recall",
3478
+ "correct",
3479
+ "reset"
3480
+ ]);
3481
+ var ARGUMENT_SEMANTICS = Object.freeze([
3482
+ "namespace",
3483
+ "sessionId",
3484
+ "content",
3485
+ "role",
3486
+ "timestamp",
3487
+ "query",
3488
+ "limit"
3489
+ ]);
3490
+ var DEFAULT_PREFLIGHT_TIMEOUT_MS = 3e4;
3491
+ var namespaceCounter = 0;
3492
+ function createNamespacePrefix() {
3493
+ namespaceCounter += 1;
3494
+ return `remnic-bench-${process.pid.toString(36)}-${Date.now().toString(36)}-${namespaceCounter.toString(36)}`;
3495
+ }
3496
+ var SdkMcpToolClient = class _SdkMcpToolClient {
3497
+ constructor(client) {
3498
+ this.client = client;
3499
+ }
3500
+ client;
3501
+ static async connect(config, control) {
3502
+ const client = new Client({ name: "remnic-bench", version: "1.0.0" });
3503
+ const transport = config.type === "stdio" ? new StdioClientTransport({
3504
+ command: config.command,
3505
+ args: config.args,
3506
+ cwd: config.cwd,
3507
+ env: config.env,
3508
+ stderr: "inherit"
3509
+ }) : new StreamableHTTPClientTransport(new URL(config.url), {
3510
+ requestInit: {
3511
+ headers: {
3512
+ ...config.bearerToken ? { authorization: `Bearer ${config.bearerToken}` } : {},
3513
+ ...config.headers ?? {}
3514
+ }
3515
+ }
3516
+ });
3517
+ try {
3518
+ await client.connect(transport, { signal: control?.signal });
3519
+ return new _SdkMcpToolClient(client);
3520
+ } catch (error) {
3521
+ await raceWithSignal(client.close(), AbortSignal.timeout(2e3), "MCP SDK client close").catch(() => {
3522
+ });
3523
+ throw error;
3524
+ }
3525
+ }
3526
+ async listTools(control) {
3527
+ const result = await this.client.listTools(void 0, { signal: control?.signal });
3528
+ return result.tools;
3529
+ }
3530
+ async callTool(name, args, control) {
3531
+ const result = await this.client.callTool({ name, arguments: args }, void 0, { signal: control?.signal });
3532
+ if ("toolResult" in result) {
3533
+ return { structuredContent: { result: result.toolResult } };
3534
+ }
3535
+ return result;
3536
+ }
3537
+ async close() {
3538
+ await this.client.close();
3539
+ }
3540
+ };
3541
+ var McpMemoryBackend = class {
3542
+ constructor(options) {
3543
+ this.options = options;
3544
+ validateMcpToolMapping(options.tools);
3545
+ if (options.timeoutMs !== void 0 && (!Number.isInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
3546
+ throw new Error("MCP adapter timeoutMs must be a positive integer");
3547
+ }
3548
+ this.label = options.label ?? "mcp";
3549
+ this.namespacePrefix = options.namespacePrefix ?? createNamespacePrefix();
3550
+ this.timeoutMs = options.timeoutMs ?? DEFAULT_PREFLIGHT_TIMEOUT_MS;
3551
+ }
3552
+ options;
3553
+ label;
3554
+ namespacePrefix;
3555
+ client;
3556
+ resolvedTools;
3557
+ preflightResult;
3558
+ sessions = /* @__PURE__ */ new Set();
3559
+ messageCounts = /* @__PURE__ */ new Map();
3560
+ timeoutMs;
3561
+ async preflight(control) {
3562
+ if (this.preflightResult) return this.preflightResult;
3563
+ try {
3564
+ this.preflightResult = await withTimeoutControl(
3565
+ this.timeoutMs,
3566
+ control,
3567
+ "MCP adapter preflight",
3568
+ async (boundedControl) => {
3569
+ await this.ensureResolved(boundedControl);
3570
+ const sessionId = "conformance";
3571
+ const oldToken = `canary-old-${this.namespacePrefix}`;
3572
+ const newToken = `canary-new-${this.namespacePrefix}`;
3573
+ await this.store(sessionId, [{ role: "user", content: oldToken }], boundedControl, true);
3574
+ const before = await this.recall(sessionId, oldToken, 4e3, boundedControl, true);
3575
+ if (!before.some((item) => item.includes(oldToken))) {
3576
+ throw new Error("stored canary was not returned by recall");
3577
+ }
3578
+ const applied = await this.correct(
3579
+ sessionId,
3580
+ `Correction: replace ${oldToken} with ${newToken}.`,
3581
+ void 0,
3582
+ boundedControl,
3583
+ true
3584
+ );
3585
+ if (!applied) {
3586
+ throw new Error("correction canary was explicitly refused (applied=false)");
3587
+ }
3588
+ const after = await this.recall(sessionId, newToken, 4e3, boundedControl, true);
3589
+ if (!after.some((item) => item.includes(newToken))) {
3590
+ throw new Error("corrected canary was not returned by recall");
3591
+ }
3592
+ if (after.some((item) => item.includes(oldToken))) {
3593
+ throw new Error("retired canary was still returned after correction");
3594
+ }
3595
+ await this.reset(sessionId, boundedControl, true);
3596
+ const deleted = await this.recall(sessionId, newToken, 4e3, boundedControl, true);
3597
+ if (deleted.some((item) => item.includes(newToken) || item.includes(oldToken))) {
3598
+ throw new Error("reset did not remove the conformance canary namespace");
3599
+ }
3600
+ return {
3601
+ ok: true,
3602
+ value: {
3603
+ tools: Object.fromEntries(
3604
+ Object.entries(this.resolvedTools).map(([key, value]) => [key, value.name])
3605
+ ),
3606
+ namespace: this.namespacePrefix
3607
+ }
3608
+ };
3609
+ }
3610
+ );
3611
+ } catch (cause) {
3612
+ const conformanceSession = this.scopedSession("conformance");
3613
+ if (this.client && this.resolvedTools && this.sessions.has(conformanceSession)) {
3614
+ try {
3615
+ await withTimeoutControl(
3616
+ Math.min(this.timeoutMs, 2e3),
3617
+ void 0,
3618
+ "MCP conformance cleanup",
3619
+ (cleanupControl) => this.reset("conformance", cleanupControl, true)
3620
+ );
3621
+ } catch {
3622
+ }
3623
+ }
3624
+ await this.closeClient();
3625
+ this.preflightResult = {
3626
+ ok: false,
3627
+ error: "backend_unusable",
3628
+ detail: cause instanceof Error ? cause.message : String(cause),
3629
+ cause
3630
+ };
3631
+ }
3632
+ return this.preflightResult;
3633
+ }
3634
+ async assertUsable(control) {
3635
+ if (this.options.skipPreflight) {
3636
+ await this.ensureResolved(control);
3637
+ return;
3638
+ }
3639
+ const result = await this.preflight(control);
3640
+ if (!result.ok) throw new McpMemoryBackendError(result);
3641
+ }
3642
+ async store(sessionId, messages, control, duringPreflight = false) {
3643
+ if (!duringPreflight) await this.assertUsable(control);
3644
+ const scoped = this.scopedSession(sessionId);
3645
+ this.sessions.add(scoped);
3646
+ for (const message of messages) {
3647
+ const payload = await this.invoke(
3648
+ "store",
3649
+ {
3650
+ namespace: this.namespacePrefix,
3651
+ sessionId: scoped,
3652
+ content: message.content,
3653
+ role: message.role,
3654
+ timestamp: message.timestamp
3655
+ },
3656
+ control
3657
+ );
3658
+ if (!validateMutationAcknowledgement("store", payload)) {
3659
+ throw invalidResponse("store", "server returned a negative acknowledgement");
3660
+ }
3661
+ this.messageCounts.set(scoped, (this.messageCounts.get(scoped) ?? 0) + 1);
3662
+ }
3663
+ }
3664
+ async recall(sessionId, query, budgetChars = 16e3, control, duringPreflight = false) {
3665
+ if (!duringPreflight) await this.assertUsable(control);
3666
+ const raw = await this.invoke(
3667
+ "recall",
3668
+ {
3669
+ namespace: this.namespacePrefix,
3670
+ sessionId: this.scopedSession(sessionId),
3671
+ query,
3672
+ limit: 20
3673
+ },
3674
+ control
3675
+ );
3676
+ const strings = validateRecallResponse(raw);
3677
+ const output = [];
3678
+ let used = 0;
3679
+ for (const value of strings) {
3680
+ if (used >= budgetChars) break;
3681
+ const remaining = budgetChars - used;
3682
+ output.push(value.slice(0, remaining));
3683
+ used += Math.min(value.length, remaining);
3684
+ }
3685
+ return output;
3686
+ }
3687
+ async correct(sessionId, text, at, control, duringPreflight = false) {
3688
+ if (!duringPreflight) await this.assertUsable(control);
3689
+ const scoped = this.scopedSession(sessionId);
3690
+ this.sessions.add(scoped);
3691
+ const payload = await this.invoke(
3692
+ "correct",
3693
+ {
3694
+ namespace: this.namespacePrefix,
3695
+ sessionId: scoped,
3696
+ content: text,
3697
+ timestamp: at
3698
+ },
3699
+ control
3700
+ );
3701
+ return validateMutationAcknowledgement("correct", payload);
3702
+ }
3703
+ async reset(sessionId, control, duringPreflight = false) {
3704
+ if (!duringPreflight) await this.assertUsable(control);
3705
+ const targets = sessionId ? [this.scopedSession(sessionId)] : [...this.sessions];
3706
+ for (const target of targets) {
3707
+ const payload = await this.invoke(
3708
+ "reset",
3709
+ {
3710
+ namespace: this.namespacePrefix,
3711
+ sessionId: target
3712
+ },
3713
+ control
3714
+ );
3715
+ if (!validateMutationAcknowledgement("reset", payload)) {
3716
+ throw invalidResponse("reset", "server returned a negative acknowledgement");
3717
+ }
3718
+ this.sessions.delete(target);
3719
+ this.messageCounts.delete(target);
3720
+ }
3721
+ }
3722
+ getStats(sessionId) {
3723
+ const targets = sessionId ? [this.scopedSession(sessionId)] : [...this.messageCounts.keys()];
3724
+ return {
3725
+ totalMessages: targets.reduce((sum, target) => sum + (this.messageCounts.get(target) ?? 0), 0),
3726
+ totalSummaryNodes: 0,
3727
+ maxDepth: 0
3728
+ };
3729
+ }
3730
+ async destroy() {
3731
+ try {
3732
+ if (this.client && this.resolvedTools) {
3733
+ await withTimeoutControl(
3734
+ Math.min(this.timeoutMs, 2e3),
3735
+ void 0,
3736
+ "MCP adapter cleanup",
3737
+ (cleanupControl) => this.reset(void 0, cleanupControl, true)
3738
+ );
3739
+ }
3740
+ } finally {
3741
+ await this.closeClient();
3742
+ }
3743
+ }
3744
+ scopedSession(sessionId) {
3745
+ return `${this.namespacePrefix}:${sessionId}`;
3746
+ }
3747
+ async ensureResolved(control) {
3748
+ if (this.resolvedTools) return;
3749
+ let candidate;
3750
+ try {
3751
+ const pendingClient = (this.options.clientFactory ?? SdkMcpToolClient.connect)(this.options.transport, control);
3752
+ candidate = await raceWithSignal(pendingClient, control?.signal, "MCP client connection", async (lateClient) => {
3753
+ await closeToolClient(lateClient, this.timeoutMs);
3754
+ });
3755
+ const listed = await raceWithSignal(candidate.listTools(control), control?.signal, "MCP tool discovery");
3756
+ const resolved = resolveTools(listed, this.options.tools);
3757
+ this.client = candidate;
3758
+ this.resolvedTools = resolved;
3759
+ } catch (cause) {
3760
+ if (candidate) await closeToolClient(candidate, this.timeoutMs);
3761
+ this.client = void 0;
3762
+ this.resolvedTools = void 0;
3763
+ throw new McpMemoryBackendError({
3764
+ ok: false,
3765
+ error: "transport_failure",
3766
+ detail: cause instanceof Error ? cause.message : String(cause),
3767
+ cause
3768
+ });
3769
+ }
3770
+ }
3771
+ async invoke(operation, values, control) {
3772
+ await this.ensureResolved(control);
3773
+ const tool = this.resolvedTools[operation];
3774
+ const args = buildArguments(tool, values);
3775
+ try {
3776
+ const result = await this.client.callTool(tool.name, args, control);
3777
+ if (result.isError) {
3778
+ throw new Error(
3779
+ extractStrings(readToolPayload(result, tool.resultPath)).join("; ") || "MCP tool returned isError=true"
3780
+ );
3781
+ }
3782
+ return readToolPayload(result, tool.resultPath);
3783
+ } catch (cause) {
3784
+ if (cause instanceof McpMemoryBackendError) throw cause;
3785
+ throw new McpMemoryBackendError({
3786
+ ok: false,
3787
+ error: "tool_failure",
3788
+ detail: `${operation} tool ${tool.name} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
3789
+ cause
3790
+ });
3791
+ }
3792
+ }
3793
+ async closeClient() {
3794
+ const client = this.client;
3795
+ this.client = void 0;
3796
+ this.resolvedTools = void 0;
3797
+ if (client) await closeToolClient(client, this.timeoutMs);
3798
+ }
3799
+ };
3800
+ async function createMcpMemoryAdapter(options) {
3801
+ const backend = new McpMemoryBackend(options);
3802
+ const adapter = {
3803
+ label: backend.label,
3804
+ namespacePrefix: backend.namespacePrefix,
3805
+ preflight: (control) => backend.preflight(control),
3806
+ store: (sessionId, messages, control) => backend.store(sessionId, messages, control),
3807
+ async recall(sessionId, query, budgetChars, _options, control) {
3808
+ return (await backend.recall(sessionId, query, budgetChars, control)).join("\n");
3809
+ },
3810
+ async search(query, limit, sessionId, control) {
3811
+ const recalled = await backend.recall(sessionId ?? "global", query, 64e3, control);
3812
+ return recalled.slice(0, limit).map((snippet, turnIndex) => ({
3813
+ turnIndex,
3814
+ role: "memory",
3815
+ snippet,
3816
+ sessionId: sessionId ?? "global"
3817
+ }));
3818
+ },
3819
+ async correct(sessionId, text, at, control) {
3820
+ return { applied: await backend.correct(sessionId, text, at, control) };
3821
+ },
3822
+ reset: (sessionId, control) => backend.reset(sessionId, control),
3823
+ getStats: async (sessionId) => backend.getStats(sessionId),
3824
+ destroy: () => backend.destroy()
3825
+ };
3826
+ try {
3827
+ if (!options.skipPreflight) {
3828
+ const preflight = await adapter.preflight();
3829
+ if (!preflight.ok) throw new McpMemoryBackendError(preflight);
3830
+ }
3831
+ return adapter;
3832
+ } catch (error) {
3833
+ await backend.destroy();
3834
+ throw error;
3835
+ }
3836
+ }
3837
+ function createMcpDemoMemoryAdapter(options = {}) {
3838
+ return createMcpMemoryAdapter({
3839
+ ...options,
3840
+ transport: resolveDemoTransport()
3841
+ });
3842
+ }
3843
+ async function createMcpMemCorrectAdapter(options) {
3844
+ const backend = new McpMemoryBackend(options);
3845
+ const adapter = {
3846
+ label: backend.label,
3847
+ namespacePrefix: backend.namespacePrefix,
3848
+ preflight: () => backend.preflight(),
3849
+ reset: () => backend.reset(),
3850
+ ingestTurn: (sessionKey, role, text, at) => backend.store(sessionKey, [{ role, content: text, timestamp: at }]),
3851
+ recall: (query, sessionKey) => backend.recall(sessionKey, query),
3852
+ correct: async (text, sessionKey, at) => {
3853
+ const applied = await backend.correct(sessionKey, text, at);
3854
+ if (!applied) {
3855
+ throw new McpMemoryBackendError({
3856
+ ok: false,
3857
+ error: "tool_failure",
3858
+ detail: "correct tool refused the correction (applied=false)"
3859
+ });
3860
+ }
3861
+ },
3862
+ runMaintenance: async () => {
3863
+ },
3864
+ destroy: () => backend.destroy()
3865
+ };
3866
+ try {
3867
+ if (!options.skipPreflight) {
3868
+ const preflight = await adapter.preflight();
3869
+ if (!preflight.ok) throw new McpMemoryBackendError(preflight);
3870
+ }
3871
+ return adapter;
3872
+ } catch (error) {
3873
+ await backend.destroy();
3874
+ throw error;
3875
+ }
3876
+ }
3877
+ function createMcpDemoMemCorrectAdapter(options = {}) {
3878
+ return createMcpMemCorrectAdapter({
3879
+ ...options,
3880
+ transport: resolveDemoTransport()
3881
+ });
3882
+ }
3883
+ function resolveDemoTransport() {
3884
+ const packaged = fileURLToPath(new URL("./demo/mcp-memory-server.js", import.meta.url));
3885
+ const development = fileURLToPath(new URL("../../dist/demo/mcp-memory-server.js", import.meta.url));
3886
+ if (existsSync(packaged) || existsSync(development)) {
3887
+ return {
3888
+ type: "stdio",
3889
+ command: process.execPath,
3890
+ args: [existsSync(packaged) ? packaged : development]
3891
+ };
3892
+ }
3893
+ const source = fileURLToPath(new URL("../demo/mcp-memory-server.ts", import.meta.url));
3894
+ if (!existsSync(source)) {
3895
+ throw new Error("Packaged MCP demo server is missing from @remnic/bench");
3896
+ }
3897
+ return {
3898
+ type: "stdio",
3899
+ command: process.execPath,
3900
+ args: ["--import", "tsx", source]
3901
+ };
3902
+ }
3903
+ function validateMcpToolMapping(value) {
3904
+ if (value === void 0) return;
3905
+ if (!isPlainRecord(value)) {
3906
+ throw new Error("MCP tool mapping must be a plain object");
3907
+ }
3908
+ for (const operation of Object.keys(value).sort()) {
3909
+ if (!TOOL_OPERATIONS.includes(operation)) {
3910
+ throw new Error(`MCP tool mapping contains unknown operation: ${operation}`);
3911
+ }
3912
+ const entry = value[operation];
3913
+ if (typeof entry === "string") {
3914
+ if (entry.trim().length === 0) {
3915
+ throw new Error(`MCP ${operation} tool name must be a non-empty string`);
3916
+ }
3917
+ continue;
3918
+ }
3919
+ if (!isPlainRecord(entry)) {
3920
+ throw new Error(`MCP ${operation} tool mapping must be a string or object`);
3921
+ }
3922
+ for (const key of Object.keys(entry).sort()) {
3923
+ if (key !== "name" && key !== "arguments" && key !== "resultPath") {
3924
+ throw new Error(`MCP ${operation} tool mapping contains unknown field: ${key}`);
3925
+ }
3926
+ }
3927
+ if (typeof entry.name !== "string" || entry.name.trim().length === 0) {
3928
+ throw new Error(`MCP ${operation} tool mapping requires a non-empty name`);
3929
+ }
3930
+ if (entry.resultPath !== void 0) {
3931
+ if (typeof entry.resultPath !== "string" || !isSafeResultPath(entry.resultPath)) {
3932
+ throw new Error(`MCP ${operation} resultPath must be a non-empty safe dot path`);
3933
+ }
3934
+ }
3935
+ if (entry.arguments !== void 0) {
3936
+ if (!isPlainRecord(entry.arguments)) {
3937
+ throw new Error(`MCP ${operation} arguments mapping must be a plain object`);
3938
+ }
3939
+ for (const semantic of Object.keys(entry.arguments).sort()) {
3940
+ if (!ARGUMENT_SEMANTICS.includes(semantic)) {
3941
+ throw new Error(`MCP ${operation} arguments contain unknown semantic: ${semantic}`);
3942
+ }
3943
+ const argumentName = entry.arguments[semantic];
3944
+ if (typeof argumentName !== "string" || argumentName.trim().length === 0) {
3945
+ throw new Error(`MCP ${operation} argument ${semantic} must map to a non-empty string`);
3946
+ }
3947
+ }
3948
+ }
3949
+ }
3950
+ }
3951
+ function resolveTools(listed, mapping = {}) {
3952
+ validateMcpToolMapping(mapping);
3953
+ const byName = new Map(listed.map((tool) => [tool.name, tool]));
3954
+ const resolved = {};
3955
+ for (const operation of Object.keys(DEFAULT_TOOL_NAMES)) {
3956
+ const explicit = mapping[operation];
3957
+ const entry = typeof explicit === "string" ? { name: explicit } : explicit;
3958
+ const name = entry?.name ?? DEFAULT_TOOL_NAMES[operation].find((candidate) => byName.has(candidate));
3959
+ if (!name || !byName.has(name)) {
3960
+ const expected = entry?.name ?? DEFAULT_TOOL_NAMES[operation].join(", ");
3961
+ throw new Error(
3962
+ `missing ${operation} tool (expected ${expected}); server exposed ${[...byName.keys()].sort().join(", ") || "no tools"}`
3963
+ );
3964
+ }
3965
+ const tool = byName.get(name);
3966
+ resolved[operation] = {
3967
+ name,
3968
+ arguments: entry?.arguments ?? {},
3969
+ resultPath: entry?.resultPath,
3970
+ schemaProperties: Object.keys(tool.inputSchema?.properties ?? {})
3971
+ };
3972
+ if (!resolveArgumentName(resolved[operation], "namespace") && !resolveArgumentName(resolved[operation], "sessionId")) {
3973
+ throw new Error(
3974
+ `unsafe ${operation} tool ${name}: operation requires a schema-declared namespace or sessionId argument mapping`
3975
+ );
3976
+ }
3977
+ }
3978
+ return resolved;
3979
+ }
3980
+ function buildArguments(tool, values) {
3981
+ const args = {};
3982
+ for (const [semantic, value] of Object.entries(values)) {
3983
+ if (value === void 0) continue;
3984
+ const key = resolveArgumentName(tool, semantic);
3985
+ if (key) args[key] = value;
3986
+ }
3987
+ return args;
3988
+ }
3989
+ function resolveArgumentName(tool, semantic) {
3990
+ const explicit = tool.arguments[semantic];
3991
+ if (explicit !== void 0) {
3992
+ return tool.schemaProperties.includes(explicit) ? explicit : void 0;
3993
+ }
3994
+ return ARGUMENT_ALIASES[semantic].find((alias) => tool.schemaProperties.includes(alias));
3995
+ }
3996
+ function readToolPayload(result, resultPath) {
3997
+ let value = result.structuredContent;
3998
+ if (value === void 0) {
3999
+ if (result.content === void 0) return void 0;
4000
+ const texts = result.content.filter((item) => item.type === "text" && typeof item.text === "string").map((item) => item.text);
4001
+ if (texts.length === 1) {
4002
+ try {
4003
+ value = JSON.parse(texts[0]);
4004
+ } catch {
4005
+ value = texts[0];
4006
+ }
4007
+ } else {
4008
+ value = texts;
4009
+ }
4010
+ }
4011
+ if (!resultPath) return value;
4012
+ for (const segment of resultPath.split(".")) {
4013
+ if (segment === "__proto__" || segment === "prototype" || segment === "constructor") {
4014
+ return void 0;
4015
+ }
4016
+ if (!value || typeof value !== "object" || !(segment in value)) return void 0;
4017
+ value = value[segment];
4018
+ }
4019
+ return value;
4020
+ }
4021
+ function extractStrings(value) {
4022
+ if (typeof value === "string") return value.trim() ? [value] : [];
4023
+ if (Array.isArray(value)) return value.flatMap(extractStrings);
4024
+ if (!value || typeof value !== "object") return [];
4025
+ const record = value;
4026
+ for (const preferred of ["memories", "results", "items", "data", "content", "text", "memory"]) {
4027
+ if (preferred in record) {
4028
+ const found = extractStrings(record[preferred]);
4029
+ if (found.length > 0) return found;
4030
+ }
4031
+ }
4032
+ return Object.keys(record).sort().flatMap((key) => extractStrings(record[key]));
4033
+ }
4034
+ function validateRecallResponse(value) {
4035
+ if (value === void 0 || value === null) {
4036
+ throw invalidResponse("recall", "response did not contain a result payload");
4037
+ }
4038
+ if (typeof value === "string") return value.trim().length > 0 ? [value] : [];
4039
+ if (Array.isArray(value)) {
4040
+ if (value.length === 0) return [];
4041
+ const strings = value.flatMap(extractRecallItem);
4042
+ if (strings.length === 0) {
4043
+ throw invalidResponse("recall", "result array contained no recognizable memory text");
4044
+ }
4045
+ return strings;
4046
+ }
4047
+ if (!isPlainRecord(value)) {
4048
+ throw invalidResponse("recall", "result payload must be a string, array, or object");
4049
+ }
4050
+ for (const key of [
4051
+ "memories",
4052
+ "results",
4053
+ "items",
4054
+ "hits",
4055
+ "data",
4056
+ "payload",
4057
+ "result",
4058
+ "content",
4059
+ "text",
4060
+ "memory"
4061
+ ]) {
4062
+ if (!(key in value)) continue;
4063
+ const nested = value[key];
4064
+ if (Array.isArray(nested) && nested.length === 0) return [];
4065
+ if (typeof nested === "string" && nested.trim().length === 0) return [];
4066
+ const strings = extractRecallItem(nested);
4067
+ if (strings.length > 0) return strings;
4068
+ throw invalidResponse("recall", `response field ${key} contained no recognizable memory text`);
4069
+ }
4070
+ throw invalidResponse("recall", "response object contained no recognized result field");
4071
+ }
4072
+ function extractRecallItem(value) {
4073
+ if (typeof value === "string") return value.trim().length > 0 ? [value] : [];
4074
+ if (Array.isArray(value)) return value.flatMap(extractRecallItem);
4075
+ if (!isPlainRecord(value)) return [];
4076
+ for (const key of [
4077
+ "text",
4078
+ "content",
4079
+ "memory",
4080
+ "snippet",
4081
+ "value",
4082
+ "memories",
4083
+ "results",
4084
+ "items",
4085
+ "hits",
4086
+ "data",
4087
+ "payload",
4088
+ "result"
4089
+ ]) {
4090
+ if (key in value) return extractRecallItem(value[key]);
4091
+ }
4092
+ return [];
4093
+ }
4094
+ function validateMutationAcknowledgement(operation, value) {
4095
+ if (typeof value === "boolean") return value;
4096
+ if (typeof value === "string") {
4097
+ const normalized = value.trim().toLowerCase();
4098
+ if (["ok", "true", "stored", "applied", "corrected", "deleted", "cleared"].includes(normalized)) {
4099
+ return true;
4100
+ }
4101
+ if (["false", "rejected", "not applied"].includes(normalized)) return false;
4102
+ throw invalidResponse(operation, "text response was not a recognized acknowledgement");
4103
+ }
4104
+ if (!isPlainRecord(value)) {
4105
+ throw invalidResponse(operation, "response did not contain a mutation acknowledgement");
4106
+ }
4107
+ const keys = operation === "store" ? ["stored", "created", "success", "ok"] : operation === "correct" ? ["applied", "corrected", "success", "ok"] : ["deleted", "cleared", "success", "ok"];
4108
+ for (const key of keys) {
4109
+ if (!(key in value)) continue;
4110
+ if (typeof value[key] !== "boolean") {
4111
+ throw invalidResponse(operation, `acknowledgement field ${key} must be boolean`);
4112
+ }
4113
+ return value[key];
4114
+ }
4115
+ for (const wrapper of ["data", "payload", "result"]) {
4116
+ if (wrapper in value) return validateMutationAcknowledgement(operation, value[wrapper]);
4117
+ }
4118
+ throw invalidResponse(operation, "response object contained no recognized acknowledgement field");
4119
+ }
4120
+ function invalidResponse(operation, detail) {
4121
+ return new McpMemoryBackendError({
4122
+ ok: false,
4123
+ error: "invalid_response",
4124
+ detail: `${operation} tool returned an invalid response: ${detail}`
4125
+ });
4126
+ }
4127
+ function isPlainRecord(value) {
4128
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4129
+ const prototype = Object.getPrototypeOf(value);
4130
+ return prototype === Object.prototype || prototype === null;
4131
+ }
4132
+ function isSafeResultPath(value) {
4133
+ const segments = value.split(".");
4134
+ return segments.length > 0 && segments.every(
4135
+ (segment) => segment.length > 0 && segment !== "__proto__" && segment !== "prototype" && segment !== "constructor" && /^[A-Za-z0-9_-]+$/.test(segment)
4136
+ );
4137
+ }
4138
+ async function withTimeoutControl(timeoutMs, externalControl, label, operation) {
4139
+ const timeoutController = new AbortController();
4140
+ const timer = setTimeout(
4141
+ () => timeoutController.abort(new Error(`${label} timed out after ${timeoutMs}ms`)),
4142
+ timeoutMs
4143
+ );
4144
+ const signal = externalControl?.signal ? AbortSignal.any([externalControl.signal, timeoutController.signal]) : timeoutController.signal;
4145
+ try {
4146
+ return await raceWithSignal(operation({ signal }), signal, label);
4147
+ } finally {
4148
+ clearTimeout(timer);
4149
+ }
4150
+ }
4151
+ async function closeToolClient(client, timeoutMs) {
4152
+ await raceWithSignal(client.close(), AbortSignal.timeout(Math.min(timeoutMs, 2e3)), "MCP client close").catch(
4153
+ () => {
4154
+ }
4155
+ );
4156
+ }
4157
+ async function raceWithSignal(promise, signal, label, onLateSuccess) {
4158
+ if (!signal) return promise;
4159
+ let aborted = signal.aborted;
4160
+ let abortListener;
4161
+ const abortPromise = new Promise((_resolve, reject) => {
4162
+ const rejectForAbort = () => {
4163
+ aborted = true;
4164
+ reject(signal.reason instanceof Error ? signal.reason : new Error(`${label} aborted`));
4165
+ };
4166
+ if (signal.aborted) rejectForAbort();
4167
+ else {
4168
+ abortListener = rejectForAbort;
4169
+ signal.addEventListener("abort", rejectForAbort, { once: true });
4170
+ }
4171
+ });
4172
+ if (onLateSuccess) {
4173
+ void promise.then(async (value) => {
4174
+ if (aborted) await onLateSuccess(value);
4175
+ }).catch(() => {
4176
+ });
4177
+ }
4178
+ try {
4179
+ return await Promise.race([promise, abortPromise]);
4180
+ } finally {
4181
+ if (abortListener) signal.removeEventListener("abort", abortListener);
4182
+ }
4183
+ }
4184
+
3295
4185
  // src/adapters/timeout-guard.ts
3296
4186
  var BENCHMARK_TIMEOUT_ABORT_GRACE_MS = 1500;
3297
4187
  function resolveBenchmarkPhaseTimeoutMs(config) {
@@ -3585,6 +4475,32 @@ function wrapJudge(judge, run) {
3585
4475
  }
3586
4476
  });
3587
4477
  }
4478
+ if (judge.judgeMemCorrectCorrectionAcceptance) {
4479
+ wrapped.judgeMemCorrectCorrectionAcceptance = (request, control) => run("judge.memcorrect.correctionAcceptance", async (signal) => {
4480
+ const merged = mergeBenchPhaseControl(signal, control);
4481
+ try {
4482
+ return await judge.judgeMemCorrectCorrectionAcceptance(
4483
+ request,
4484
+ merged.control
4485
+ );
4486
+ } finally {
4487
+ merged.cleanup();
4488
+ }
4489
+ });
4490
+ }
4491
+ if (judge.judgeMemCorrectStaleMemoryHarm) {
4492
+ wrapped.judgeMemCorrectStaleMemoryHarm = (request, control) => run("judge.memcorrect.staleMemoryHarm", async (signal) => {
4493
+ const merged = mergeBenchPhaseControl(signal, control);
4494
+ try {
4495
+ return await judge.judgeMemCorrectStaleMemoryHarm(
4496
+ request,
4497
+ merged.control
4498
+ );
4499
+ } finally {
4500
+ merged.cleanup();
4501
+ }
4502
+ });
4503
+ }
3588
4504
  return wrapped;
3589
4505
  }
3590
4506
  function readPositiveIntegerConfig(remnicConfig, key) {
@@ -4483,6 +5399,7 @@ var BENCHMARK_RESULT_SCHEMA = {
4483
5399
  properties: {
4484
5400
  provider: { type: "string" },
4485
5401
  model: { type: "string" },
5402
+ rubricVersion: { type: "string" },
4486
5403
  baseUrl: { type: "string" },
4487
5404
  reasoningEffort: { type: "string" }
4488
5405
  }
@@ -4649,8 +5566,411 @@ function mergeContaminationManifests(...manifests) {
4649
5566
  return merged;
4650
5567
  }
4651
5568
 
5569
+ // src/report-card.ts
5570
+ var PRIMARY_METRIC_ORDER = [
5571
+ "overall_score",
5572
+ "llm_judge",
5573
+ "accuracy",
5574
+ "answer_accuracy",
5575
+ "exact_match",
5576
+ "f1",
5577
+ "uptake_at_next"
5578
+ ];
5579
+ var CORRECTION_METRICS = [
5580
+ {
5581
+ name: "uptake_at_next",
5582
+ label: "Correction visible next",
5583
+ description: "Share of corrections visible at the first post-correction probe.",
5584
+ direction: "Higher is better"
5585
+ },
5586
+ {
5587
+ name: "non_resurrection",
5588
+ label: "Stale fact stayed retired",
5589
+ description: "Share of corrected facts that did not return during maintenance and re-ingest.",
5590
+ direction: "Higher is better"
5591
+ },
5592
+ {
5593
+ name: "false_apply",
5594
+ label: "False corrections",
5595
+ description: "Share of anti-events that incorrectly changed memory.",
5596
+ direction: "Lower is better"
5597
+ }
5598
+ ];
5599
+ function escapeHtml(value) {
5600
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll(`"`, "&quot;").replaceAll("'", "&#39;");
5601
+ }
5602
+ function isRecord(value) {
5603
+ return !!value && typeof value === "object" && !Array.isArray(value);
5604
+ }
5605
+ function finiteNumber(value) {
5606
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
5607
+ }
5608
+ function formatNumber(value) {
5609
+ if (Number.isInteger(value)) return String(value);
5610
+ return value.toFixed(4).replace(/0+$/, "").replace(/\.$/, "");
5611
+ }
5612
+ function formatPercent(value) {
5613
+ return value === void 0 ? "Not measured" : `${formatNumber(value * 100)}%`;
5614
+ }
5615
+ function formatDimensionMetric(name, value) {
5616
+ const percentageMetric = name.includes("rate") || name.includes("precision") || name.includes("recall") || name.includes("success") || name.includes("alignment") || name.includes("lift");
5617
+ return percentageMetric ? formatPercent(value) : formatNumber(value);
5618
+ }
5619
+ function aggregateMeans(result) {
5620
+ const means = {};
5621
+ for (const name of Object.keys(result.results.aggregates).sort()) {
5622
+ const mean4 = finiteNumber(result.results.aggregates[name]?.mean);
5623
+ if (mean4 !== void 0) means[name] = mean4;
5624
+ }
5625
+ return means;
5626
+ }
5627
+ function correctionMetricValues(result) {
5628
+ const benchmarkOptions = result.config.benchmarkOptions;
5629
+ const persistedBundle = isRecord(benchmarkOptions) ? benchmarkOptions.aggregateMetrics : void 0;
5630
+ const values = {};
5631
+ if (isRecord(persistedBundle)) {
5632
+ for (const name of Object.keys(persistedBundle).sort()) {
5633
+ const value = finiteNumber(persistedBundle[name]);
5634
+ if (value !== void 0) values[name] = value;
5635
+ }
5636
+ }
5637
+ const means = aggregateMeans(result);
5638
+ for (const metric of CORRECTION_METRICS) {
5639
+ if (values[metric.name] === void 0 && means[metric.name] !== void 0) {
5640
+ values[metric.name] = means[metric.name];
5641
+ }
5642
+ }
5643
+ return values;
5644
+ }
5645
+ function resultStatus(result) {
5646
+ const failure = result.meta.failureReason ?? "";
5647
+ const typedBackendFailure = /(?:backend[ _-]?unusable|transport[ _-]?failure|tool[ _-]?failure|invalid[ _-]?response)/i;
5648
+ if (result.meta.status === "partial" && result.results.tasks.length === 0 && typedBackendFailure.test(failure)) {
5649
+ return "backend-unusable";
5650
+ }
5651
+ if (result.meta.status === "partial") return "partial";
5652
+ if (result.results.tasks.length === 0) return "unscored";
5653
+ return "complete";
5654
+ }
5655
+ function statusCopy(status) {
5656
+ switch (status) {
5657
+ case "backend-unusable":
5658
+ return {
5659
+ label: "Backend unusable",
5660
+ detail: "The memory backend failed before a trustworthy score could be produced."
5661
+ };
5662
+ case "partial":
5663
+ return {
5664
+ label: "Partial run",
5665
+ detail: "The run stopped early. Scores describe completed tasks only and are not a full result."
5666
+ };
5667
+ case "unscored":
5668
+ return {
5669
+ label: "No scored tasks",
5670
+ detail: "The run completed without task-level evidence, so no overall score is claimed."
5671
+ };
5672
+ case "complete":
5673
+ return {
5674
+ label: "Complete run",
5675
+ detail: "All persisted task results are included in this report card."
5676
+ };
5677
+ }
5678
+ }
5679
+ function primaryMetric(result) {
5680
+ const means = aggregateMeans(result);
5681
+ const correction = correctionMetricValues(result);
5682
+ if (result.meta.benchmark === "memcorrect-v1" && correction.uptake_at_next !== void 0) {
5683
+ return { name: "uptake_at_next", value: correction.uptake_at_next };
5684
+ }
5685
+ for (const name of PRIMARY_METRIC_ORDER) {
5686
+ if (means[name] !== void 0) return { name, value: means[name] };
5687
+ }
5688
+ return void 0;
5689
+ }
5690
+ function renderScore(result, status) {
5691
+ const metric = primaryMetric(result);
5692
+ if (status === "backend-unusable" || status === "unscored" || !metric) {
5693
+ return `<div class="score score--na"><span class="score__value">N/A</span><span class="score__label">No defensible score</span></div>`;
5694
+ }
5695
+ const value = metric.value >= 0 && metric.value <= 1 ? formatPercent(metric.value) : formatNumber(metric.value);
5696
+ return `<div class="score"><span class="score__value">${escapeHtml(value)}</span><span class="score__label">Overall score \xB7 ${escapeHtml(metric.name)}</span><span class="score__note">Recorded mean of the named primary metric; no cross-metric composite.</span></div>`;
5697
+ }
5698
+ function renderCorrectionSpotlight(result) {
5699
+ const values = correctionMetricValues(result);
5700
+ const tasks = [...result.results.tasks].sort(
5701
+ (left, right) => left.taskId.localeCompare(right.taskId)
5702
+ );
5703
+ const accepted = tasks.filter((task) => task.scores.uptake_at_next === 1).length;
5704
+ const notObserved = tasks.filter((task) => task.scores.uptake_at_next === 0).length;
5705
+ const staleReturned = tasks.filter((task) => task.scores.non_resurrection === 0).length;
5706
+ const measuredCorrectionTasks = accepted + notObserved;
5707
+ const measuredRetirementTasks = tasks.filter(
5708
+ (task) => task.scores.non_resurrection === 0 || task.scores.non_resurrection === 1
5709
+ ).length;
5710
+ const cards = CORRECTION_METRICS.map((metric) => {
5711
+ const value = values[metric.name];
5712
+ const state = value === void 0 ? "not-measured" : "measured";
5713
+ return ` <article class="ledger-card ledger-card--${state}">
5714
+ <p class="eyebrow">${escapeHtml(metric.direction)}</p>
5715
+ <p class="ledger-card__value">${formatPercent(value)}</p>
5716
+ <h3>${escapeHtml(metric.label)}</h3>
5717
+ <p>${escapeHtml(metric.description)}</p>
5718
+ </article>`;
5719
+ }).join("\n");
5720
+ const countSummary = measuredCorrectionTasks > 0 || measuredRetirementTasks > 0 ? ` <p class="ledger-summary"><strong>${accepted}</strong> visible at the next probe \xB7 <strong>${notObserved}</strong> not observed at the next probe \xB7 <strong>${staleReturned}</strong> stale-fact return${staleReturned === 1 ? "" : "s"}</p>` : ` <p class="ledger-summary ledger-summary--empty">This run did not record MemCorrect scenario outcomes.</p>`;
5721
+ return ` <section class="spotlight" aria-labelledby="correction-heading">
5722
+ <div class="section-heading">
5723
+ <div>
5724
+ <p class="eyebrow">Correction ledger</p>
5725
+ <h2 id="correction-heading">Did the system take the correction\u2014and keep it?</h2>
5726
+ </div>
5727
+ <p>Values come from persisted MemCorrect metrics when present. Missing evidence stays missing.</p>
5728
+ </div>
5729
+ <div class="ledger">
5730
+ ${cards}
5731
+ </div>
5732
+ ${countSummary}
5733
+ </section>`;
5734
+ }
5735
+ function taskContext(task) {
5736
+ const details = task.details;
5737
+ if (!isRecord(details)) return { shape: "\u2014", category: "\u2014" };
5738
+ return {
5739
+ shape: typeof details.shape === "string" ? details.shape : "\u2014",
5740
+ category: typeof details.category === "string" ? details.category : "\u2014"
5741
+ };
5742
+ }
5743
+ function taskScore(task, name) {
5744
+ const value = finiteNumber(task.scores[name]);
5745
+ return value === void 0 ? "\u2014" : formatPercent(value);
5746
+ }
5747
+ function renderScenarioDrilldown(result) {
5748
+ const tasks = [...result.results.tasks].sort(
5749
+ (left, right) => left.taskId.localeCompare(right.taskId)
5750
+ );
5751
+ if (tasks.length === 0) {
5752
+ return ` <section aria-labelledby="scenario-heading">
5753
+ <div class="section-heading"><div><p class="eyebrow">Evidence</p><h2 id="scenario-heading">Scenario drill-down</h2></div></div>
5754
+ <p class="empty-state">No task-level scenarios were persisted for this run.</p>
5755
+ </section>`;
5756
+ }
5757
+ const rows = tasks.map((task) => {
5758
+ const context = taskContext(task);
5759
+ return ` <tr>
5760
+ <th scope="row">${escapeHtml(task.taskId)}</th>
5761
+ <td>${escapeHtml(context.shape)}</td>
5762
+ <td>${escapeHtml(context.category)}</td>
5763
+ <td>${taskScore(task, "uptake_at_next")}</td>
5764
+ <td>${taskScore(task, "non_resurrection")}</td>
5765
+ <td>${taskScore(task, "false_apply")}</td>
5766
+ </tr>`;
5767
+ }).join("\n");
5768
+ return ` <section aria-labelledby="scenario-heading">
5769
+ <div class="section-heading">
5770
+ <div><p class="eyebrow">Evidence</p><h2 id="scenario-heading">Scenario drill-down</h2></div>
5771
+ <p>\u201C\u2014\u201D means the scenario did not record that metric; it is not a zero.</p>
5772
+ </div>
5773
+ <div class="table-wrap">
5774
+ <table>
5775
+ <thead><tr><th>Scenario</th><th>Shape</th><th>Category</th><th>Correction next</th><th>Stayed retired</th><th>False apply</th></tr></thead>
5776
+ <tbody>
5777
+ ${rows}
5778
+ </tbody>
5779
+ </table>
5780
+ </div>
5781
+ </section>`;
5782
+ }
5783
+ function renderDimensions(result) {
5784
+ const means = aggregateMeans(result);
5785
+ const cards = MEMORY_EVAL_DIMENSIONS.map((dimension) => {
5786
+ const measured = dimension.metrics.filter((metric) => means[metric.name] !== void 0);
5787
+ const body = measured.length === 0 ? `<p class="dimension__empty">Not measured in this run. No pass or fail is inferred.</p>` : `<dl>${measured.map((metric) => `
5788
+ <div><dt>${escapeHtml(metric.name)}</dt><dd>${escapeHtml(formatDimensionMetric(metric.name, means[metric.name]))}</dd></div>
5789
+ <p>${escapeHtml(metric.higherIsBetter ? "Higher is better" : "Lower is better")} \xB7 No pass threshold is recorded in the result.</p>`).join("")}
5790
+ </dl>`;
5791
+ return ` <article class="dimension">
5792
+ <p class="eyebrow">${escapeHtml(dimension.category)}</p>
5793
+ <h3>${escapeHtml(dimension.question)}</h3>
5794
+ ${body}
5795
+ </article>`;
5796
+ }).join("\n");
5797
+ return ` <section aria-labelledby="dimensions-heading">
5798
+ <div class="section-heading">
5799
+ <div><p class="eyebrow">Memory eval dimensions</p><h2 id="dimensions-heading">What this run can\u2014and cannot\u2014claim</h2></div>
5800
+ <p>Dimensions map only to matching <code>MEMORY_EVAL_DIMENSIONS</code> metrics.</p>
5801
+ </div>
5802
+ <div class="dimension-grid">
5803
+ ${cards}
5804
+ </div>
5805
+ </section>`;
5806
+ }
5807
+ function renderAggregateMetrics(result) {
5808
+ const names = Object.keys(result.results.aggregates).sort();
5809
+ if (names.length === 0) {
5810
+ return ` <section aria-labelledby="aggregate-heading">
5811
+ <div class="section-heading"><div><p class="eyebrow">Raw record</p><h2 id="aggregate-heading">Aggregate Metrics</h2></div></div>
5812
+ <p class="empty-state">No aggregate metrics were recorded. The report does not replace them with zeros.</p>
5813
+ </section>`;
5814
+ }
5815
+ const rows = names.map((name) => {
5816
+ const aggregate = result.results.aggregates[name];
5817
+ return ` <tr><th scope="row">${escapeHtml(name)}</th><td>${formatNumber(aggregate.mean)}</td><td>${formatNumber(aggregate.median)}</td><td>${formatNumber(aggregate.stdDev)}</td><td>${formatNumber(aggregate.min)}</td><td>${formatNumber(aggregate.max)}</td></tr>`;
5818
+ }).join("\n");
5819
+ return ` <section aria-labelledby="aggregate-heading">
5820
+ <div class="section-heading"><div><p class="eyebrow">Raw record</p><h2 id="aggregate-heading">Aggregate Metrics</h2></div><p>Untransformed values from the stored result.</p></div>
5821
+ <div class="table-wrap"><table><thead><tr><th>Metric</th><th>Mean</th><th>Median</th><th>Std dev</th><th>Min</th><th>Max</th></tr></thead><tbody>
5822
+ ${rows}
5823
+ </tbody></table></div>
5824
+ </section>`;
5825
+ }
5826
+ function optionString(result, names) {
5827
+ const options = result.config.benchmarkOptions;
5828
+ if (!isRecord(options)) return void 0;
5829
+ for (const name of names) {
5830
+ const value = options[name];
5831
+ if (typeof value === "string" && value.trim().length > 0) return value;
5832
+ }
5833
+ return void 0;
5834
+ }
5835
+ function providerLabel(provider) {
5836
+ return provider ? `${provider.provider} / ${provider.model}` : "Not recorded";
5837
+ }
5838
+ function renderProvenance(result, provenance) {
5839
+ const rubricVersion = result.config.judgeProvider?.rubricVersion ?? optionString(result, ["rubricVersion", "judgeRubricVersion", "rubric_version"]);
5840
+ const remnicConfigKeyCount = Object.keys(result.config.remnicConfig ?? {}).length;
5841
+ const machine = [result.environment.os, result.environment.nodeVersion, result.environment.hardware].filter((value) => typeof value === "string" && value.length > 0).join(" \xB7 ");
5842
+ const entries = [
5843
+ ["Result ID", result.meta.id],
5844
+ ["Task Count", String(result.results.tasks.length)],
5845
+ ["Git SHA", result.meta.gitSha],
5846
+ ["Remnic / benchmark", `${result.meta.remnicVersion} / ${result.meta.version}`],
5847
+ ["Judge", providerLabel(result.config.judgeProvider)],
5848
+ ["Rubric version", rubricVersion ?? "Not recorded in stored result"],
5849
+ ["Dataset hash", result.meta.datasetHash ?? "Not recorded"],
5850
+ ["Sealed qrels hash", result.meta.qrelsSealedHash ?? "Not recorded"],
5851
+ ["Judge prompt hash", result.meta.judgePromptHash ?? "Not recorded"],
5852
+ ["Seeds", result.meta.seeds?.join(", ") || "Not recorded"],
5853
+ ["Manifest reference", provenance.manifestReference ?? "Not available"],
5854
+ ["Manifest artifact hash", provenance.artifactHash ?? "Not available"],
5855
+ ["Machine fingerprint", machine || "Not recorded"],
5856
+ [
5857
+ "Remnic config",
5858
+ remnicConfigKeyCount === 0 ? "No keys recorded" : `[redacted ${remnicConfigKeyCount} key${remnicConfigKeyCount === 1 ? "" : "s"}]`
5859
+ ]
5860
+ ];
5861
+ return ` <footer aria-labelledby="provenance-heading">
5862
+ <div class="section-heading"><div><p class="eyebrow">Receipts</p><h2 id="provenance-heading">Provenance</h2></div><p>Result fields are persisted with the run. Manifest receipts appear only when the caller supplies a verified adjacent manifest.</p></div>
5863
+ <dl class="receipts">
5864
+ ${entries.map(([label, value]) => ` <div><dt>${escapeHtml(label)}</dt><dd>${escapeHtml(value)}</dd></div>`).join("\n")}
5865
+ </dl>
5866
+ </footer>`;
5867
+ }
5868
+ function renderMemoryReportCard(result, provenance = {}) {
5869
+ const status = resultStatus(result);
5870
+ const statusText = statusCopy(status);
5871
+ const system = result.config.systemProvider ? `${result.config.systemProvider.provider} / ${result.config.systemProvider.model}` : result.config.adapterMode;
5872
+ const failure = result.meta.failureReason ? `<p class="failure"><strong>Run stopped:</strong> ${escapeHtml(result.meta.failureReason)}</p>` : "";
5873
+ return `<!doctype html>
5874
+ <html lang="en">
5875
+ <head>
5876
+ <meta charset="utf-8">
5877
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5878
+ <title>Remnic Bench Report: ${escapeHtml(result.meta.benchmark)}</title>
5879
+ <style>
5880
+ :root { color-scheme: light; --ink:#142523; --muted:#5d6e69; --mist:#e9f0ed; --paper:#f8fbf9; --line:#cbd8d3; --teal:#0b7168; --teal-soft:#d8ebe6; --ember:#c9563f; --ember-soft:#f8e1db; --shadow:0 18px 48px rgba(20,37,35,.09); font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; }
5881
+ * { box-sizing:border-box; }
5882
+ body { margin:0; background:var(--mist); color:var(--ink); }
5883
+ main { width:min(1180px,calc(100% - 32px)); margin:0 auto; padding:32px 0 64px; }
5884
+ h1,h2,h3,p,dl,dd { margin:0; }
5885
+ h1,h2 { font-family:Georgia,"Times New Roman",serif; font-weight:500; letter-spacing:-.025em; }
5886
+ h1 { max-width:780px; font-size:clamp(2.5rem,7vw,5.8rem); line-height:.94; }
5887
+ h2 { font-size:clamp(1.7rem,3vw,2.5rem); line-height:1.05; }
5888
+ h3 { font-size:1rem; line-height:1.25; }
5889
+ p { line-height:1.55; }
5890
+ code,.eyebrow,.score__value,.receipts dd,table { font-family:"SFMono-Regular",Consolas,"Liberation Mono",monospace; }
5891
+ .eyebrow { margin-bottom:10px; color:var(--teal); font-size:.72rem; font-weight:700; letter-spacing:.12em; text-transform:uppercase; }
5892
+ .hero { position:relative; overflow:hidden; padding:clamp(28px,5vw,60px); background:var(--ink); color:var(--paper); border-radius:24px; box-shadow:var(--shadow); }
5893
+ .hero::after { content:""; position:absolute; width:320px; height:320px; right:-120px; top:-160px; border:56px solid var(--teal); border-radius:50%; opacity:.62; }
5894
+ .hero__meta { position:relative; z-index:1; display:flex; flex-wrap:wrap; gap:10px; margin-bottom:40px; }
5895
+ .pill { padding:7px 11px; border:1px solid rgba(248,251,249,.28); border-radius:999px; font-size:.78rem; }
5896
+ .hero__grid { position:relative; z-index:1; display:grid; grid-template-columns:minmax(0,1fr) minmax(220px,330px); gap:36px; align-items:end; }
5897
+ .hero__lede { max-width:650px; margin-top:22px; color:#bdd0ca; font-size:1.05rem; }
5898
+ .status { display:inline-flex; align-items:center; gap:8px; margin-top:24px; font-weight:700; }
5899
+ .status::before { content:""; width:9px; height:9px; background:var(--teal); border-radius:50%; box-shadow:0 0 0 5px rgba(11,113,104,.25); }
5900
+ .status--partial::before,.status--backend-unusable::before { background:var(--ember); box-shadow:0 0 0 5px rgba(201,86,63,.24); }
5901
+ .status__detail { display:block; margin-top:8px; color:#bdd0ca; font-size:.88rem; }
5902
+ .score { padding:24px; background:rgba(248,251,249,.08); border:1px solid rgba(248,251,249,.18); border-radius:18px; }
5903
+ .score__value { display:block; color:#75d2c4; font-size:clamp(2.8rem,6vw,4.8rem); line-height:1; }
5904
+ .score--na .score__value { color:#f2a18f; }
5905
+ .score__label { display:block; margin-top:10px; font-weight:750; }
5906
+ .score__note { display:block; margin-top:8px; color:#bdd0ca; font-size:.78rem; line-height:1.45; }
5907
+ .failure { position:relative; z-index:1; margin-top:24px; padding:14px 16px; background:rgba(201,86,63,.18); border-left:3px solid #f08d76; border-radius:8px; color:#ffe9e4; }
5908
+ section,footer { margin-top:22px; padding:clamp(22px,4vw,38px); background:var(--paper); border:1px solid var(--line); border-radius:20px; }
5909
+ .spotlight { margin-top:-1px; border-top-left-radius:0; border-top-right-radius:0; border-top:6px solid var(--teal); }
5910
+ .section-heading { display:flex; justify-content:space-between; gap:28px; align-items:end; margin-bottom:24px; }
5911
+ .section-heading>p { max-width:430px; color:var(--muted); font-size:.9rem; }
5912
+ .ledger { display:grid; grid-template-columns:repeat(3,1fr); gap:1px; overflow:hidden; border:1px solid var(--line); border-radius:16px; background:var(--line); }
5913
+ .ledger-card { min-height:220px; padding:24px; background:white; }
5914
+ .ledger-card--not-measured { background:#f0f3f1; color:var(--muted); }
5915
+ .ledger-card__value { margin:22px 0 8px; color:var(--teal); font-family:Georgia,"Times New Roman",serif; font-size:2.4rem; }
5916
+ .ledger-card--not-measured .ledger-card__value { color:var(--muted); font-size:1.4rem; }
5917
+ .ledger-card h3 { margin-bottom:8px; }
5918
+ .ledger-card p:last-child { color:var(--muted); font-size:.87rem; }
5919
+ .ledger-summary { margin-top:18px; padding:14px 16px; background:var(--teal-soft); border-radius:10px; }
5920
+ .ledger-summary--empty { background:#eef1ef; color:var(--muted); }
5921
+ .dimension-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
5922
+ .dimension { padding:20px; border:1px solid var(--line); border-radius:14px; background:white; }
5923
+ .dimension h3 { min-height:2.5em; margin-bottom:18px; }
5924
+ .dimension dl>div { display:flex; justify-content:space-between; gap:16px; padding-top:10px; border-top:1px solid var(--line); }
5925
+ .dimension dl p,.dimension__empty { margin-top:8px; color:var(--muted); font-size:.78rem; }
5926
+ .dimension dt { overflow-wrap:anywhere; }
5927
+ .dimension dd { color:var(--teal); font-weight:700; }
5928
+ .table-wrap { overflow-x:auto; border:1px solid var(--line); border-radius:12px; }
5929
+ table { width:100%; border-collapse:collapse; font-size:.8rem; }
5930
+ th,td { padding:12px 14px; text-align:left; border-top:1px solid var(--line); white-space:nowrap; }
5931
+ thead th { border-top:0; background:#edf3f0; color:var(--muted); font-size:.7rem; letter-spacing:.05em; text-transform:uppercase; }
5932
+ tbody th { color:var(--ink); }
5933
+ .empty-state { padding:22px; background:#eef1ef; border-radius:12px; color:var(--muted); }
5934
+ footer { background:#dfe9e5; }
5935
+ .receipts { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:1px; overflow:hidden; border:1px solid #bccbc5; border-radius:12px; background:#bccbc5; }
5936
+ .receipts>div { min-width:0; padding:14px 16px; background:var(--paper); }
5937
+ .receipts dt { margin-bottom:6px; color:var(--muted); font-size:.72rem; text-transform:uppercase; letter-spacing:.06em; }
5938
+ .receipts dd { overflow-wrap:anywhere; font-size:.82rem; }
5939
+ @media (max-width:760px) { main{width:min(100% - 20px,1180px);padding-top:10px}.hero{border-radius:16px}.hero__grid,.ledger,.dimension-grid,.receipts{grid-template-columns:1fr}.section-heading{display:block}.section-heading>p{margin-top:12px}.spotlight{border-radius:0 0 16px 16px}.ledger-card{min-height:0} }
5940
+ @media (prefers-reduced-motion:reduce) { *,*::before,*::after { scroll-behavior:auto!important; } }
5941
+ @media print { body{background:white}main{width:100%;padding:0}.hero,section,footer{box-shadow:none;break-inside:avoid}.hero{border-radius:0} }
5942
+ </style>
5943
+ </head>
5944
+ <body>
5945
+ <main>
5946
+ <header class="hero">
5947
+ <div class="hero__meta"><span class="pill">${escapeHtml(result.meta.benchmark)}</span><span class="pill">${escapeHtml(result.meta.mode)} mode</span><span class="pill">${result.results.tasks.length} task${result.results.tasks.length === 1 ? "" : "s"}</span><span class="pill">${escapeHtml(result.meta.timestamp)}</span></div>
5948
+ <div class="hero__grid">
5949
+ <div>
5950
+ <p class="eyebrow">Remnic memory report card</p>
5951
+ <h1>${escapeHtml(system)}</h1>
5952
+ <p class="hero__lede">Adapter: ${escapeHtml(result.config.adapterMode)} \xB7 Run ${escapeHtml(result.meta.id)} \xB7 Git ${escapeHtml(result.meta.gitSha)}</p>
5953
+ <p class="status status--${status}">${escapeHtml(statusText.label)}</p>
5954
+ <span class="status__detail">${escapeHtml(statusText.detail)}</span>
5955
+ </div>
5956
+ ${renderScore(result, status)}
5957
+ </div>
5958
+ ${failure}
5959
+ </header>
5960
+ ${renderCorrectionSpotlight(result)}
5961
+ ${renderDimensions(result)}
5962
+ ${renderScenarioDrilldown(result)}
5963
+ ${renderAggregateMetrics(result)}
5964
+ ${renderProvenance(result, provenance)}
5965
+ </main>
5966
+ </body>
5967
+ </html>
5968
+ `;
5969
+ }
5970
+
4652
5971
  // src/results-store.ts
4653
5972
  var BASELINE_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
5973
+ var REPRO_MANIFEST_FILENAME = "MANIFEST.json";
4654
5974
  function defaultBenchmarkBaselineDir() {
4655
5975
  const homeDir = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
4656
5976
  return path3.join(homeDir, ".remnic", "bench", "baselines");
@@ -4683,6 +6003,25 @@ function isObjectRecord(value) {
4683
6003
  function isFiniteNumber(value) {
4684
6004
  return typeof value === "number" && Number.isFinite(value);
4685
6005
  }
6006
+ async function loadBenchmarkReportCardProvenance(outputDir, resultId) {
6007
+ const manifestPath = path3.join(outputDir, REPRO_MANIFEST_FILENAME);
6008
+ let parsed;
6009
+ try {
6010
+ parsed = JSON.parse(await readFile3(manifestPath, "utf8"));
6011
+ } catch {
6012
+ return {};
6013
+ }
6014
+ if (!isObjectRecord(parsed) || !Array.isArray(parsed.results)) return {};
6015
+ const coversResult = parsed.results.some(
6016
+ (entry) => isObjectRecord(entry) && entry.resultId === resultId
6017
+ );
6018
+ if (!coversResult) return {};
6019
+ const artifactHash = typeof parsed.artifactHash === "string" && parsed.artifactHash.length > 0 ? parsed.artifactHash : void 0;
6020
+ return {
6021
+ manifestReference: REPRO_MANIFEST_FILENAME,
6022
+ ...artifactHash ? { artifactHash } : {}
6023
+ };
6024
+ }
4686
6025
  function isProviderConfigLike(value) {
4687
6026
  if (value === null) {
4688
6027
  return true;
@@ -4964,7 +6303,7 @@ function classifyPublishCandidate(result, target, contaminationManifest) {
4964
6303
  }
4965
6304
  return null;
4966
6305
  }
4967
- function toPublishedBenchmarkFeedEntry(result) {
6306
+ function toPublishedBenchmarkFeedEntry(result, provenance) {
4968
6307
  if (!integrityMetaIsComplete(result.meta)) {
4969
6308
  throw new Error(
4970
6309
  "toPublishedBenchmarkFeedEntry called with a result missing integrity metadata; call assertPublishableIntegrity first."
@@ -4982,6 +6321,7 @@ function toPublishedBenchmarkFeedEntry(result) {
4982
6321
  aggregateMetrics: result.results.aggregates,
4983
6322
  cost: result.cost,
4984
6323
  environment: result.environment,
6324
+ reportCardHtml: renderMemoryReportCard(result, provenance),
4985
6325
  integrity: {
4986
6326
  splitType: result.meta.splitType,
4987
6327
  qrelsSealedHash: result.meta.qrelsSealedHash,
@@ -5009,9 +6349,13 @@ async function buildBenchmarkPublishFeed(outputDir, target, options = {}) {
5009
6349
  skipped.push({ ...skip, path: summary.path });
5010
6350
  continue;
5011
6351
  }
6352
+ const provenance = await loadBenchmarkReportCardProvenance(
6353
+ outputDir,
6354
+ result.meta.id
6355
+ );
5012
6356
  latestByBenchmark.set(
5013
6357
  summary.benchmark,
5014
- toPublishedBenchmarkFeedEntry(result)
6358
+ toPublishedBenchmarkFeedEntry(result, provenance)
5015
6359
  );
5016
6360
  }
5017
6361
  return {
@@ -5034,197 +6378,13 @@ function csvEscape(value) {
5034
6378
  }
5035
6379
  return text;
5036
6380
  }
5037
- function escapeHtml(value) {
5038
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll(`"`, "&quot;").replaceAll("'", "&#39;");
5039
- }
5040
- function renderHtmlKeyValueRows(entries) {
5041
- return entries.map(([label, value]) => ` <tr><th>${escapeHtml(label)}</th><td>${escapeHtml(String(value))}</td></tr>`).join("\n");
5042
- }
5043
- function renderBenchmarkResultHtml(result) {
5044
- const seeds = Array.isArray(result.meta.seeds) ? result.meta.seeds.join(", ") : "Unknown";
5045
- const aggregateRows = Object.keys(result.results.aggregates).sort().map((metric) => {
5046
- const aggregate = result.results.aggregates[metric];
5047
- return ` <tr><th>${escapeHtml(metric)}</th><td>${escapeHtml(String(aggregate.mean))}</td><td>${escapeHtml(String(aggregate.median))}</td><td>${escapeHtml(String(aggregate.stdDev))}</td><td>${escapeHtml(String(aggregate.min))}</td><td>${escapeHtml(String(aggregate.max))}</td></tr>`;
5048
- }).join("\n");
5049
- const statisticsBlock = result.results.statistics ? `
5050
- <section>
5051
- <h2>Statistics</h2>
5052
- <pre>${escapeHtml(JSON.stringify(result.results.statistics, null, 2))}</pre>
5053
- </section>` : "";
5054
- const remnicConfigKeyCount = Object.keys(result.config.remnicConfig ?? {}).length;
5055
- const renderedConfig = {
5056
- systemProvider: result.config.systemProvider,
5057
- judgeProvider: result.config.judgeProvider,
5058
- internalProvider: result.config.internalProvider,
5059
- remnicConfig: remnicConfigKeyCount === 0 ? "[empty]" : `[redacted ${remnicConfigKeyCount} key${remnicConfigKeyCount === 1 ? "" : "s"}]`
5060
- };
5061
- return `<!doctype html>
5062
- <html lang="en">
5063
- <head>
5064
- <meta charset="utf-8">
5065
- <meta name="viewport" content="width=device-width, initial-scale=1">
5066
- <title>Remnic Bench Report: ${escapeHtml(result.meta.benchmark)}</title>
5067
- <style>
5068
- :root {
5069
- color-scheme: light;
5070
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
5071
- }
5072
- body {
5073
- margin: 0;
5074
- background: #f5f7fb;
5075
- color: #18202a;
5076
- }
5077
- main {
5078
- max-width: 1080px;
5079
- margin: 0 auto;
5080
- padding: 32px 20px 48px;
5081
- }
5082
- header {
5083
- margin-bottom: 24px;
5084
- }
5085
- h1, h2 {
5086
- margin: 0 0 12px;
5087
- }
5088
- p {
5089
- margin: 0;
5090
- line-height: 1.5;
5091
- }
5092
- section {
5093
- background: #ffffff;
5094
- border: 1px solid #d8dee8;
5095
- border-radius: 12px;
5096
- padding: 20px;
5097
- margin-top: 16px;
5098
- box-shadow: 0 8px 24px rgba(15, 23, 42, 0.05);
5099
- }
5100
- table {
5101
- width: 100%;
5102
- border-collapse: collapse;
5103
- }
5104
- th, td {
5105
- text-align: left;
5106
- padding: 10px 12px;
5107
- border-top: 1px solid #e5eaf1;
5108
- vertical-align: top;
5109
- }
5110
- thead th {
5111
- border-top: none;
5112
- font-size: 0.85rem;
5113
- letter-spacing: 0.02em;
5114
- text-transform: uppercase;
5115
- color: #526173;
5116
- }
5117
- tbody th {
5118
- width: 30%;
5119
- }
5120
- pre {
5121
- margin: 0;
5122
- overflow-x: auto;
5123
- white-space: pre-wrap;
5124
- word-break: break-word;
5125
- background: #f5f7fb;
5126
- border-radius: 10px;
5127
- padding: 14px;
5128
- border: 1px solid #e5eaf1;
5129
- }
5130
- .muted {
5131
- color: #526173;
5132
- margin-top: 6px;
5133
- }
5134
- .empty {
5135
- color: #526173;
5136
- font-style: italic;
5137
- }
5138
- </style>
5139
- </head>
5140
- <body>
5141
- <main>
5142
- <header>
5143
- <h1>Remnic Bench Report</h1>
5144
- <p>${escapeHtml(result.meta.benchmark)} \xB7 ${escapeHtml(result.meta.id)}</p>
5145
- <p class="muted">Generated from a stored benchmark result export.</p>
5146
- </header>
5147
- <section>
5148
- <h2>Run Summary</h2>
5149
- <table>
5150
- <tbody>
5151
- ${renderHtmlKeyValueRows([
5152
- ["Result ID", result.meta.id],
5153
- ["Benchmark", result.meta.benchmark],
5154
- ["Benchmark Tier", result.meta.benchmarkTier],
5155
- ["Timestamp", result.meta.timestamp],
5156
- ["Mode", result.meta.mode],
5157
- ["Run Count", result.meta.runCount],
5158
- ["Task Count", result.results.tasks.length],
5159
- ["Remnic Version", result.meta.remnicVersion],
5160
- ["Benchmark Version", result.meta.version],
5161
- ["Git SHA", result.meta.gitSha],
5162
- ["Seeds", seeds]
5163
- ])}
5164
- </tbody>
5165
- </table>
5166
- </section>
5167
- <section>
5168
- <h2>Aggregate Metrics</h2>
5169
- ${aggregateRows.length > 0 ? ` <table>
5170
- <thead>
5171
- <tr><th>Metric</th><th>Mean</th><th>Median</th><th>Std Dev</th><th>Min</th><th>Max</th></tr>
5172
- </thead>
5173
- <tbody>
5174
- ${aggregateRows}
5175
- </tbody>
5176
- </table>` : ' <p class="empty">No aggregate metrics recorded for this run.</p>'}
5177
- </section>
5178
- <section>
5179
- <h2>Cost</h2>
5180
- <table>
5181
- <tbody>
5182
- ${renderHtmlKeyValueRows([
5183
- ["Total Tokens", result.cost.totalTokens],
5184
- ["Input Tokens", result.cost.inputTokens],
5185
- ["Output Tokens", result.cost.outputTokens],
5186
- ["Estimated Cost (USD)", result.cost.estimatedCostUsd],
5187
- ["Total Latency (ms)", result.cost.totalLatencyMs],
5188
- ["Mean Query Latency (ms)", result.cost.meanQueryLatencyMs]
5189
- ])}
5190
- </tbody>
5191
- </table>
5192
- </section>
5193
- <section>
5194
- <h2>Environment</h2>
5195
- <table>
5196
- <tbody>
5197
- ${renderHtmlKeyValueRows([
5198
- ["OS", result.environment.os],
5199
- ["Node Version", result.environment.nodeVersion],
5200
- ["Hardware", result.environment.hardware ?? "Unknown"]
5201
- ])}
5202
- </tbody>
5203
- </table>
5204
- </section>
5205
- <section>
5206
- <h2>Configuration</h2>
5207
- <table>
5208
- <tbody>
5209
- ${renderHtmlKeyValueRows([
5210
- ["Adapter Mode", result.config.adapterMode]
5211
- ])}
5212
- </tbody>
5213
- </table>
5214
- <pre>${escapeHtml(JSON.stringify(renderedConfig, null, 2))}</pre>
5215
- </section>${statisticsBlock}
5216
- </main>
5217
- </body>
5218
- </html>
5219
- `;
5220
- }
5221
- function renderBenchmarkResultExport(result, format) {
6381
+ function renderBenchmarkResultExport(result, format, options = {}) {
5222
6382
  if (format === "json") {
5223
6383
  return `${JSON.stringify(result, null, 2)}
5224
6384
  `;
5225
6385
  }
5226
6386
  if (format === "html") {
5227
- return renderBenchmarkResultHtml(result);
6387
+ return renderMemoryReportCard(result, options.reportCardProvenance);
5228
6388
  }
5229
6389
  const rows = [
5230
6390
  [
@@ -6162,7 +7322,12 @@ async function buildResultManifest(resultsDir, resultPath, result) {
6162
7322
  runCount: result.meta.runCount,
6163
7323
  seeds: [...result.meta.seeds],
6164
7324
  taskCount: result.results.tasks.length,
6165
- configHash: sha256String(stableStringify(result.config))
7325
+ configHash: sha256String(stableStringify(result.config)),
7326
+ judge: result.config.judgeProvider ? {
7327
+ provider: result.config.judgeProvider.provider,
7328
+ model: result.config.judgeProvider.model,
7329
+ rubricVersion: result.config.judgeProvider.rubricVersion ?? null
7330
+ } : null
6166
7331
  };
6167
7332
  }
6168
7333
  async function resolveResultPaths(resultsDir, explicitPaths) {
@@ -6474,7 +7639,35 @@ function readJudgeCalibrationFromBenchmarkOptions(value) {
6474
7639
  if (typeof kappa !== "number" || !Number.isFinite(kappa) || typeof sampleSize !== "number" || !Number.isFinite(sampleSize) || typeof threshold !== "number" || !Number.isFinite(threshold) || typeof warning !== "boolean") {
6475
7640
  return void 0;
6476
7641
  }
6477
- return { kappa, sampleSize, threshold, warning };
7642
+ const confidenceInterval = readCalibrationConfidenceInterval(record.confidenceInterval);
7643
+ const bootstrapSamples = record.bootstrapSamples;
7644
+ const answerSetHash = record.answerSetHash;
7645
+ const sourceResultId = record.sourceResultId;
7646
+ const sliceQuestionIds = readCalibrationQuestionIds(record.sliceQuestionIds);
7647
+ const hasCompleteProvenance = typeof answerSetHash === "string" && /^[0-9a-f]{64}$/.test(answerSetHash) && typeof sourceResultId === "string" && sourceResultId.length > 0 && sliceQuestionIds !== void 0 && sliceQuestionIds.length === sampleSize;
7648
+ return {
7649
+ kappa,
7650
+ sampleSize,
7651
+ threshold,
7652
+ warning,
7653
+ ...confidenceInterval ? { confidenceInterval } : {},
7654
+ ...typeof bootstrapSamples === "number" && Number.isInteger(bootstrapSamples) && bootstrapSamples > 0 ? { bootstrapSamples } : {},
7655
+ ...hasCompleteProvenance ? { answerSetHash, sourceResultId, sliceQuestionIds } : {}
7656
+ };
7657
+ }
7658
+ function readCalibrationConfidenceInterval(value) {
7659
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
7660
+ const interval = value;
7661
+ if (typeof interval.lower !== "number" || !Number.isFinite(interval.lower) || typeof interval.upper !== "number" || !Number.isFinite(interval.upper) || typeof interval.level !== "number" || !Number.isFinite(interval.level) || interval.lower > interval.upper || interval.level <= 0 || interval.level >= 1) {
7662
+ return void 0;
7663
+ }
7664
+ return { lower: interval.lower, upper: interval.upper, level: interval.level };
7665
+ }
7666
+ function readCalibrationQuestionIds(value) {
7667
+ if (!Array.isArray(value) || value.length === 0 || value.length > 200 || !value.every((id) => typeof id === "string" && id.length > 0) || new Set(value).size !== value.length) {
7668
+ return void 0;
7669
+ }
7670
+ return value;
6478
7671
  }
6479
7672
  function buildBenchmarkArtifactFilename(artifact) {
6480
7673
  const date = sanitizeSegment(artifact.startedAt.slice(0, 10));
@@ -6561,6 +7754,25 @@ function parseBenchmarkArtifact(raw) {
6561
7754
  if (typeof warning !== "boolean") {
6562
7755
  throw new Error(`BenchmarkArtifact judgeCalibration.warning must be a boolean; got ${String(warning)}.`);
6563
7756
  }
7757
+ if (calibration.confidenceInterval !== void 0 && !readCalibrationConfidenceInterval(calibration.confidenceInterval)) {
7758
+ throw new Error("BenchmarkArtifact judgeCalibration.confidenceInterval must contain finite ordered lower/upper bounds and a level between 0 and 1.");
7759
+ }
7760
+ if (calibration.bootstrapSamples !== void 0 && (typeof calibration.bootstrapSamples !== "number" || !Number.isInteger(calibration.bootstrapSamples) || calibration.bootstrapSamples <= 0)) {
7761
+ throw new Error("BenchmarkArtifact judgeCalibration.bootstrapSamples must be a positive integer when provided.");
7762
+ }
7763
+ if (calibration.answerSetHash !== void 0 && (typeof calibration.answerSetHash !== "string" || !/^[0-9a-f]{64}$/.test(calibration.answerSetHash))) {
7764
+ throw new Error("BenchmarkArtifact judgeCalibration.answerSetHash must be a lowercase SHA-256 hex digest when provided.");
7765
+ }
7766
+ if (calibration.sourceResultId !== void 0 && (typeof calibration.sourceResultId !== "string" || calibration.sourceResultId.length === 0)) {
7767
+ throw new Error("BenchmarkArtifact judgeCalibration.sourceResultId must be a non-empty string when provided.");
7768
+ }
7769
+ if (calibration.sliceQuestionIds !== void 0 && !readCalibrationQuestionIds(calibration.sliceQuestionIds)) {
7770
+ throw new Error("BenchmarkArtifact judgeCalibration.sliceQuestionIds must contain 1 to 200 unique non-empty strings when provided.");
7771
+ }
7772
+ const hasAnyPinnedProvenance = calibration.answerSetHash !== void 0 || calibration.sourceResultId !== void 0 || calibration.sliceQuestionIds !== void 0;
7773
+ if (hasAnyPinnedProvenance && (typeof calibration.answerSetHash !== "string" || !/^[0-9a-f]{64}$/.test(calibration.answerSetHash) || typeof calibration.sourceResultId !== "string" || calibration.sourceResultId.length === 0 || !readCalibrationQuestionIds(calibration.sliceQuestionIds) || calibration.sliceQuestionIds.length !== calibration.sampleSize)) {
7774
+ throw new Error("BenchmarkArtifact judgeCalibration pinned provenance requires a sourceResultId, answerSetHash, and unique sliceQuestionIds matching sampleSize.");
7775
+ }
6564
7776
  }
6565
7777
  const metrics = requireObject(record, "metrics");
6566
7778
  for (const [key, value] of Object.entries(metrics)) {
@@ -6678,6 +7890,14 @@ function isPublishedBenchmarkArtifactId(value) {
6678
7890
  }
6679
7891
 
6680
7892
  // src/providers/retry-fetch.ts
7893
+ var RetryFetchHttpError = class extends Error {
7894
+ status;
7895
+ constructor(status, message) {
7896
+ super(message);
7897
+ this.name = "RetryFetchHttpError";
7898
+ this.status = status;
7899
+ }
7900
+ };
6681
7901
  var DEFAULTS = {
6682
7902
  maxAttempts: 3,
6683
7903
  baseBackoffMs: 1e3,
@@ -6839,7 +8059,8 @@ async function retryFetch(url, init, options) {
6839
8059
  const bodyPreview = await readBodyPreview(response, 512);
6840
8060
  if (attempt >= opts.maxAttempts && remainingExtendedBudgetMs() <= 0) {
6841
8061
  callerSignal?.removeEventListener("abort", onCallerAbort);
6842
- throw new Error(
8062
+ throw new RetryFetchHttpError(
8063
+ response.status,
6843
8064
  `HTTP ${response.status} ${response.statusText} (attempt ${attempt}/${opts.maxAttempts}): ${bodyPreview}`
6844
8065
  );
6845
8066
  }
@@ -9183,7 +10404,10 @@ function readGitSha() {
9183
10404
  return explicitSha.trim().slice(0, 40);
9184
10405
  }
9185
10406
  try {
9186
- return execSync("git rev-parse --short HEAD", { encoding: "utf8" }).trim();
10407
+ return execSync("git rev-parse --short HEAD", {
10408
+ encoding: "utf8",
10409
+ stdio: ["ignore", "pipe", "ignore"]
10410
+ }).trim();
9187
10411
  } catch {
9188
10412
  return "unknown";
9189
10413
  }
@@ -9766,11 +10990,12 @@ async function answerBenchmarkQuestion(options) {
9766
10990
  }
9767
10991
  const answerMode = options.answerMode ?? "default";
9768
10992
  const answerFormat = options.answerFormat === "auto" || options.answerFormat === void 0 ? inferAnswerFormat(options.question) : options.answerFormat;
9769
- const question = answerMode === "strict" ? buildStrictBenchmarkQuestion(options.question, answerFormat) : answerMode === "agentic-memory" ? buildAgenticMemoryBenchmarkQuestion(
10993
+ const baseQuestion = answerMode === "strict" ? buildStrictBenchmarkQuestion(options.question, answerFormat) : answerMode === "agentic-memory" ? buildAgenticMemoryBenchmarkQuestion(
9770
10994
  options.question,
9771
10995
  answerFormat,
9772
10996
  options.questionContext
9773
10997
  ) : options.question;
10998
+ const question = shouldInstructRecallAbstention(options.recallSupport) ? buildRecallAbstentionQuestion(baseQuestion, options.recallSupport) : baseQuestion;
9774
10999
  const response = await options.responder.respond(
9775
11000
  question,
9776
11001
  options.recalledText
@@ -9793,6 +11018,19 @@ async function answerBenchmarkQuestion(options) {
9793
11018
  model: finalResponse.model ?? response.model
9794
11019
  };
9795
11020
  }
11021
+ function shouldInstructRecallAbstention(support) {
11022
+ return support?.status === "empty" || support?.status === "weak";
11023
+ }
11024
+ function buildRecallAbstentionQuestion(question, support) {
11025
+ const description = support.status === "empty" ? "the successful recall returned no evidence" : "the recall adapter explicitly classified the supplied evidence as weak";
11026
+ return [
11027
+ question,
11028
+ "",
11029
+ "Recall support gate:",
11030
+ `- ${description}.`,
11031
+ '- Do not infer, guess, or synthesize an answer. Answer exactly "unknown".'
11032
+ ].join("\n");
11033
+ }
9796
11034
  function buildAgenticMemoryBenchmarkQuestion(question, answerFormat = "auto", context) {
9797
11035
  const prompt = buildStrictBenchmarkQuestion(question, answerFormat);
9798
11036
  return [
@@ -10402,6 +11640,592 @@ function asStringArray(value) {
10402
11640
 
10403
11641
  // src/responders.ts
10404
11642
  import { FallbackLlmClient } from "@remnic/core";
11643
+
11644
+ // src/judges/memcorrect-rubrics.ts
11645
+ var OPENAI_RESPONSES_JUDGE_RUBRIC_VERSION = "openai-responses-bench-v1";
11646
+ var MEMCORRECT_CORRECTION_ACCEPTANCE_RUBRIC_VERSION = "memcorrect-correction-acceptance-v1";
11647
+ var MEMCORRECT_STALE_HARM_RUBRIC_VERSION = "memcorrect-stale-memory-harm-v1";
11648
+ var GENERAL_ANSWER_JUDGE_RUBRIC = [
11649
+ "Grade the predicted benchmark answer against the reference answer.",
11650
+ "A score of 1 means fully correct, 0 means fully incorrect, and values between 0 and 1 mean partially correct.",
11651
+ "Do not reward unsupported claims. Ignore harmless wording and formatting differences."
11652
+ ].join(" ");
11653
+ var MEMCORRECT_CORRECTION_ACCEPTANCE_RUBRIC = [
11654
+ "Decide whether the memory system accepted the user's correction.",
11655
+ "Pass only when the corrected fact is reflected and the retired fact is not asserted as current.",
11656
+ "A quotation or correction-history record that clearly marks the retired fact as obsolete is not stale recall.",
11657
+ "Use a score of 1 for accepted, 0 for not accepted, and a fractional score only when the evidence is genuinely ambiguous."
11658
+ ].join(" ");
11659
+ var MEMCORRECT_STALE_HARM_RUBRIC = [
11660
+ "Decide whether the memory system avoided stale-memory harm after a correction.",
11661
+ "Pass only when the answer does not present the retired fact as current and does not act on it.",
11662
+ "Correction provenance may mention the retired fact only when it is explicitly identified as obsolete.",
11663
+ "Use a score of 1 when no stale harm is present, 0 when stale information affected the answer, and a fractional score only for genuinely ambiguous evidence."
11664
+ ].join(" ");
11665
+
11666
+ // src/providers/openai-responses.ts
11667
+ var DEFAULT_OPENAI_RESPONSES_JUDGE_MODEL = "gpt-5.6";
11668
+ var VERDICT_JSON_SCHEMA = {
11669
+ type: "object",
11670
+ additionalProperties: false,
11671
+ required: ["score", "decision", "reason"],
11672
+ properties: {
11673
+ score: { type: "number", minimum: 0, maximum: 1 },
11674
+ decision: { type: "string", enum: ["pass", "partial", "fail"] },
11675
+ reason: { type: "string" }
11676
+ }
11677
+ };
11678
+ var ASSISTANT_RUBRIC_JSON_SCHEMA = {
11679
+ type: "object",
11680
+ additionalProperties: false,
11681
+ required: ["identity_accuracy", "stance_coherence", "novelty", "calibration", "notes"],
11682
+ properties: {
11683
+ identity_accuracy: { type: "number", minimum: 0, maximum: 5 },
11684
+ stance_coherence: { type: "number", minimum: 0, maximum: 5 },
11685
+ novelty: { type: "number", minimum: 0, maximum: 5 },
11686
+ calibration: { type: "number", minimum: 0, maximum: 5 },
11687
+ notes: { type: "string" }
11688
+ }
11689
+ };
11690
+ var OpenAiResponsesJudgeError = class extends Error {
11691
+ code;
11692
+ retryable;
11693
+ httpStatus;
11694
+ telemetry;
11695
+ constructor(failure) {
11696
+ super(failure.error.message);
11697
+ this.name = "OpenAiResponsesJudgeError";
11698
+ this.code = failure.error.code;
11699
+ this.retryable = failure.error.retryable;
11700
+ this.httpStatus = failure.error.httpStatus;
11701
+ this.telemetry = failure.telemetry;
11702
+ }
11703
+ };
11704
+ var OpenAiResponsesProvider = class {
11705
+ provider = "openai";
11706
+ id;
11707
+ name;
11708
+ rubricVersion;
11709
+ config;
11710
+ usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
11711
+ telemetryEvents = [];
11712
+ constructor(config = {}) {
11713
+ const model = normalizeModel(config.model);
11714
+ this.config = { ...config, model };
11715
+ this.id = `openai-responses:${model}`;
11716
+ this.name = model;
11717
+ this.rubricVersion = config.rubricVersion ?? OPENAI_RESPONSES_JUDGE_RUBRIC_VERSION;
11718
+ }
11719
+ async complete(prompt, opts = {}) {
11720
+ const startedAt = performance.now();
11721
+ let response;
11722
+ try {
11723
+ response = await retryFetch(
11724
+ this.responsesUrl(),
11725
+ {
11726
+ method: "POST",
11727
+ headers: this.headers(opts.headers),
11728
+ signal: opts.signal,
11729
+ body: JSON.stringify({
11730
+ model: this.config.model,
11731
+ ...opts.systemPrompt ? { instructions: opts.systemPrompt } : {},
11732
+ input: prompt,
11733
+ temperature: opts.temperature ?? this.config.temperature,
11734
+ ...this.config.seed !== void 0 ? { seed: this.config.seed } : {},
11735
+ ...opts.maxTokens !== void 0 ? { max_output_tokens: opts.maxTokens } : {},
11736
+ store: false
11737
+ })
11738
+ },
11739
+ this.config.retryOptions
11740
+ );
11741
+ } catch (error) {
11742
+ throw this.asTransportError(error, startedAt, this.rubricVersion);
11743
+ }
11744
+ const parsed = await this.parseResponse(response, startedAt, this.rubricVersion);
11745
+ this.recordUsage(parsed.telemetry.inputTokens, parsed.telemetry.outputTokens);
11746
+ if (!parsed.ok) {
11747
+ this.recordTelemetry(parsed.telemetry);
11748
+ throw new OpenAiResponsesJudgeError(parsed);
11749
+ }
11750
+ const text = parsed.text;
11751
+ if (text === null) {
11752
+ const failure = this.failure(
11753
+ "malformed_response",
11754
+ "OpenAI Responses API returned no text output.",
11755
+ startedAt,
11756
+ {
11757
+ response,
11758
+ payload: parsed.payload,
11759
+ rubricVersion: this.rubricVersion
11760
+ }
11761
+ );
11762
+ this.recordTelemetry(failure.telemetry);
11763
+ throw new OpenAiResponsesJudgeError(failure);
11764
+ }
11765
+ this.recordTelemetry(parsed.telemetry);
11766
+ return {
11767
+ text,
11768
+ tokens: {
11769
+ input: parsed.telemetry.inputTokens,
11770
+ output: parsed.telemetry.outputTokens
11771
+ },
11772
+ latencyMs: parsed.telemetry.latencyMs,
11773
+ model: parsed.telemetry.model
11774
+ };
11775
+ }
11776
+ async judge(request) {
11777
+ const startedAt = performance.now();
11778
+ let response;
11779
+ try {
11780
+ response = await retryFetch(
11781
+ this.responsesUrl(),
11782
+ {
11783
+ method: "POST",
11784
+ headers: this.headers(),
11785
+ signal: request.signal,
11786
+ body: JSON.stringify({
11787
+ model: this.config.model,
11788
+ instructions: request.rubric,
11789
+ input: request.input,
11790
+ temperature: this.config.temperature,
11791
+ ...this.config.seed !== void 0 ? { seed: this.config.seed } : {},
11792
+ text: {
11793
+ format: {
11794
+ type: "json_schema",
11795
+ name: "benchmark_verdict",
11796
+ description: "A normalized benchmark grading verdict.",
11797
+ strict: true,
11798
+ schema: VERDICT_JSON_SCHEMA
11799
+ }
11800
+ },
11801
+ max_output_tokens: request.maxTokens ?? 256,
11802
+ store: false
11803
+ })
11804
+ },
11805
+ this.config.retryOptions
11806
+ );
11807
+ } catch (error) {
11808
+ const failure = this.transportFailure(error, startedAt, request.rubricVersion);
11809
+ this.recordTelemetry(failure.telemetry);
11810
+ return failure;
11811
+ }
11812
+ const parsed = await this.parseResponse(response, startedAt, request.rubricVersion);
11813
+ this.recordUsage(parsed.telemetry.inputTokens, parsed.telemetry.outputTokens);
11814
+ if (!parsed.ok) {
11815
+ this.recordTelemetry(parsed.telemetry);
11816
+ return parsed;
11817
+ }
11818
+ if (parsed.text === null) {
11819
+ const failure = this.failure(
11820
+ "malformed_response",
11821
+ "OpenAI Responses API returned no structured verdict text.",
11822
+ startedAt,
11823
+ { response, payload: parsed.payload, rubricVersion: request.rubricVersion }
11824
+ );
11825
+ this.recordTelemetry(failure.telemetry);
11826
+ return failure;
11827
+ }
11828
+ const verdict = parseVerdict(parsed.text);
11829
+ if (!verdict) {
11830
+ const failure = this.failure(
11831
+ "malformed_verdict",
11832
+ "OpenAI Responses API returned a verdict that failed schema validation.",
11833
+ startedAt,
11834
+ { response, payload: parsed.payload, rubricVersion: request.rubricVersion }
11835
+ );
11836
+ this.recordTelemetry(failure.telemetry);
11837
+ return failure;
11838
+ }
11839
+ this.recordTelemetry(parsed.telemetry);
11840
+ return { ok: true, verdict, telemetry: parsed.telemetry };
11841
+ }
11842
+ async evaluateAssistantRubric(request) {
11843
+ const rubricVersion = `sealed:${request.rubricId}`;
11844
+ const startedAt = performance.now();
11845
+ let response;
11846
+ try {
11847
+ response = await retryFetch(
11848
+ this.responsesUrl(),
11849
+ {
11850
+ method: "POST",
11851
+ headers: this.headers(),
11852
+ body: JSON.stringify({
11853
+ model: this.config.model,
11854
+ instructions: request.system,
11855
+ input: request.user,
11856
+ temperature: this.config.temperature,
11857
+ ...this.config.seed !== void 0 ? { seed: this.config.seed } : {},
11858
+ text: {
11859
+ format: {
11860
+ type: "json_schema",
11861
+ name: "sealed_assistant_rubric",
11862
+ description: "Scores for every sealed assistant-rubric dimension.",
11863
+ strict: true,
11864
+ schema: ASSISTANT_RUBRIC_JSON_SCHEMA
11865
+ }
11866
+ },
11867
+ max_output_tokens: 512,
11868
+ store: false
11869
+ })
11870
+ },
11871
+ this.config.retryOptions
11872
+ );
11873
+ } catch (error) {
11874
+ throw this.asTransportError(error, startedAt, rubricVersion);
11875
+ }
11876
+ const parsed = await this.parseResponse(response, startedAt, rubricVersion);
11877
+ this.recordUsage(parsed.telemetry.inputTokens, parsed.telemetry.outputTokens);
11878
+ if (!parsed.ok) {
11879
+ this.recordTelemetry(parsed.telemetry);
11880
+ throw new OpenAiResponsesJudgeError(parsed);
11881
+ }
11882
+ if (parsed.text === null || !parseAssistantRubric(parsed.text)) {
11883
+ const failure = this.failure(
11884
+ "malformed_verdict",
11885
+ "OpenAI Responses API returned an invalid sealed assistant-rubric verdict.",
11886
+ startedAt,
11887
+ { response, payload: parsed.payload, rubricVersion }
11888
+ );
11889
+ this.recordTelemetry(failure.telemetry);
11890
+ throw new OpenAiResponsesJudgeError(failure);
11891
+ }
11892
+ this.recordTelemetry(parsed.telemetry);
11893
+ return parsed.text;
11894
+ }
11895
+ getUsage() {
11896
+ return { ...this.usage };
11897
+ }
11898
+ resetUsage() {
11899
+ this.usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
11900
+ }
11901
+ getTelemetryEvents() {
11902
+ return this.telemetryEvents.map((event) => ({ ...event }));
11903
+ }
11904
+ async parseResponse(response, startedAt, rubricVersion) {
11905
+ let payload;
11906
+ try {
11907
+ payload = await response.json();
11908
+ } catch {
11909
+ if (!response.ok) {
11910
+ return this.failure(
11911
+ response.status === 429 ? "rate_limited" : "api_error",
11912
+ `OpenAI Responses API request failed with HTTP ${response.status}.`,
11913
+ startedAt,
11914
+ { response, rubricVersion }
11915
+ );
11916
+ }
11917
+ return this.failure(
11918
+ "malformed_response",
11919
+ "OpenAI Responses API returned non-JSON data.",
11920
+ startedAt,
11921
+ { response, rubricVersion }
11922
+ );
11923
+ }
11924
+ if (!response.ok) {
11925
+ const code = response.status === 429 ? "rate_limited" : "api_error";
11926
+ return this.failure(
11927
+ code,
11928
+ `OpenAI Responses API request failed with HTTP ${response.status}.`,
11929
+ startedAt,
11930
+ { response, payload, rubricVersion }
11931
+ );
11932
+ }
11933
+ if (payload.error || payload.status === "failed" || payload.status === "cancelled") {
11934
+ return this.failure(
11935
+ "api_error",
11936
+ `OpenAI Responses API reported ${payload.error?.code ?? payload.status ?? "an error"}.`,
11937
+ startedAt,
11938
+ { response, payload, rubricVersion }
11939
+ );
11940
+ }
11941
+ if (payload.status === "incomplete") {
11942
+ return this.failure(
11943
+ "incomplete_response",
11944
+ `OpenAI Responses API response was incomplete (${payload.incomplete_details?.reason ?? "unknown reason"}).`,
11945
+ startedAt,
11946
+ { response, payload, rubricVersion }
11947
+ );
11948
+ }
11949
+ const refusal = readRefusal(payload);
11950
+ if (refusal !== null) {
11951
+ return this.failure(
11952
+ "refusal",
11953
+ "OpenAI Responses API refused the benchmark grading request.",
11954
+ startedAt,
11955
+ { response, payload, rubricVersion }
11956
+ );
11957
+ }
11958
+ return {
11959
+ ok: true,
11960
+ payload,
11961
+ text: readOutputText(payload),
11962
+ telemetry: this.telemetry(payload, startedAt, rubricVersion)
11963
+ };
11964
+ }
11965
+ failure(code, message, startedAt, context) {
11966
+ const telemetry = this.telemetry(
11967
+ context.payload,
11968
+ startedAt,
11969
+ context.rubricVersion,
11970
+ code,
11971
+ context.response?.status
11972
+ );
11973
+ return {
11974
+ ok: false,
11975
+ error: {
11976
+ code,
11977
+ message,
11978
+ retryable: code === "rate_limited" || code === "transport_error",
11979
+ ...context.response ? { httpStatus: context.response.status } : {}
11980
+ },
11981
+ telemetry
11982
+ };
11983
+ }
11984
+ transportFailure(error, startedAt, rubricVersion) {
11985
+ if (isAbortError2(error)) {
11986
+ return this.failure(
11987
+ "aborted",
11988
+ "OpenAI Responses API request was aborted by the caller.",
11989
+ startedAt,
11990
+ { rubricVersion }
11991
+ );
11992
+ }
11993
+ if (error instanceof RetryFetchHttpError) {
11994
+ return this.failure(
11995
+ error.status === 429 ? "rate_limited" : "api_error",
11996
+ `OpenAI Responses API request failed with HTTP ${error.status} after retries.`,
11997
+ startedAt,
11998
+ {
11999
+ response: new Response(null, { status: error.status }),
12000
+ rubricVersion
12001
+ }
12002
+ );
12003
+ }
12004
+ return this.failure(
12005
+ "transport_error",
12006
+ `OpenAI Responses API transport failed (${safeErrorName(error)}).`,
12007
+ startedAt,
12008
+ { rubricVersion }
12009
+ );
12010
+ }
12011
+ asTransportError(error, startedAt, rubricVersion) {
12012
+ const failure = this.transportFailure(error, startedAt, rubricVersion);
12013
+ this.recordTelemetry(failure.telemetry);
12014
+ return new OpenAiResponsesJudgeError(failure);
12015
+ }
12016
+ telemetry(payload, startedAt, rubricVersion, errorCode, httpStatus) {
12017
+ const tokens = readTokens(payload);
12018
+ return {
12019
+ model: payload?.model ?? this.config.model,
12020
+ rubricVersion,
12021
+ inputTokens: tokens.input,
12022
+ outputTokens: tokens.output,
12023
+ latencyMs: Math.round(performance.now() - startedAt),
12024
+ ...errorCode ? { errorCode } : {},
12025
+ ...httpStatus !== void 0 ? { httpStatus } : {}
12026
+ };
12027
+ }
12028
+ recordTelemetry(event) {
12029
+ this.telemetryEvents.push({ ...event });
12030
+ }
12031
+ recordUsage(input, output) {
12032
+ this.usage = {
12033
+ inputTokens: this.usage.inputTokens + input,
12034
+ outputTokens: this.usage.outputTokens + output,
12035
+ totalTokens: this.usage.totalTokens + input + output
12036
+ };
12037
+ }
12038
+ responsesUrl() {
12039
+ const baseUrl = trimTrailingSlashes(this.config.baseUrl ?? "https://api.openai.com/v1");
12040
+ return `${baseUrl}/responses`;
12041
+ }
12042
+ headers(extra = {}) {
12043
+ return {
12044
+ "content-type": "application/json",
12045
+ ...this.config.apiKey ? { authorization: `Bearer ${this.config.apiKey}` } : {},
12046
+ ...this.config.headers ?? {},
12047
+ ...extra
12048
+ };
12049
+ }
12050
+ };
12051
+ function createOpenAiResponsesProvider(config = {}) {
12052
+ return new OpenAiResponsesProvider(config);
12053
+ }
12054
+ function createOpenAiResponsesBenchJudge(config = {}, provider = createOpenAiResponsesProvider(config)) {
12055
+ const scoreWithMetrics = async (question, predicted, expected, control) => {
12056
+ const result = await provider.judge({
12057
+ rubric: GENERAL_ANSWER_JUDGE_RUBRIC,
12058
+ rubricVersion: config.rubricVersion ?? OPENAI_RESPONSES_JUDGE_RUBRIC_VERSION,
12059
+ input: [
12060
+ `QUESTION: ${question}`,
12061
+ `REFERENCE_ANSWER: ${expected}`,
12062
+ `PREDICTED_ANSWER: ${predicted}`
12063
+ ].join("\n\n"),
12064
+ signal: control?.signal
12065
+ });
12066
+ if (!result.ok) throw new OpenAiResponsesJudgeError(result);
12067
+ return toBenchJudgeResult(result);
12068
+ };
12069
+ const scoreBinaryPrompt = async (prompt, control) => {
12070
+ const result = await provider.judge({
12071
+ rubric: `${GENERAL_ANSWER_JUDGE_RUBRIC} This evaluator is binary: score must be exactly 0 or 1.`,
12072
+ rubricVersion: config.rubricVersion ?? OPENAI_RESPONSES_JUDGE_RUBRIC_VERSION,
12073
+ input: prompt,
12074
+ signal: control?.signal
12075
+ });
12076
+ if (!result.ok) throw new OpenAiResponsesJudgeError(result);
12077
+ if (result.verdict.score !== 0 && result.verdict.score !== 1) {
12078
+ const failure = {
12079
+ ok: false,
12080
+ error: {
12081
+ code: "malformed_verdict",
12082
+ message: "OpenAI Responses API returned a non-binary verdict for a binary rubric.",
12083
+ retryable: false
12084
+ },
12085
+ telemetry: { ...result.telemetry, errorCode: "malformed_verdict" }
12086
+ };
12087
+ throw new OpenAiResponsesJudgeError(failure);
12088
+ }
12089
+ return toBenchJudgeResult(result);
12090
+ };
12091
+ const judgeSpecialized = async (request, rubric, control) => {
12092
+ const result = rubric === "correction" ? await judgeMemCorrectCorrectionAcceptance(provider, serializeMemCorrectJudgeRequest(request), control?.signal) : await judgeMemCorrectStaleMemoryHarm(provider, serializeMemCorrectJudgeRequest(request), control?.signal);
12093
+ if (!result.ok) throw new OpenAiResponsesJudgeError(result);
12094
+ return {
12095
+ ...toBenchJudgeResult(result),
12096
+ decision: result.verdict.decision,
12097
+ reason: result.verdict.reason,
12098
+ rubricVersion: result.telemetry.rubricVersion
12099
+ };
12100
+ };
12101
+ return {
12102
+ async score(question, predicted, expected, control) {
12103
+ return (await scoreWithMetrics(question, predicted, expected, control)).score;
12104
+ },
12105
+ scoreWithMetrics,
12106
+ scoreBinaryPrompt,
12107
+ judgeMemCorrectCorrectionAcceptance: (request, control) => judgeSpecialized(request, "correction", control),
12108
+ judgeMemCorrectStaleMemoryHarm: (request, control) => judgeSpecialized(request, "stale_harm", control)
12109
+ };
12110
+ }
12111
+ async function judgeMemCorrectCorrectionAcceptance(provider, input, signal) {
12112
+ return provider.judge({
12113
+ rubric: MEMCORRECT_CORRECTION_ACCEPTANCE_RUBRIC,
12114
+ rubricVersion: MEMCORRECT_CORRECTION_ACCEPTANCE_RUBRIC_VERSION,
12115
+ input,
12116
+ signal
12117
+ });
12118
+ }
12119
+ async function judgeMemCorrectStaleMemoryHarm(provider, input, signal) {
12120
+ return provider.judge({
12121
+ rubric: MEMCORRECT_STALE_HARM_RUBRIC,
12122
+ rubricVersion: MEMCORRECT_STALE_HARM_RUBRIC_VERSION,
12123
+ input,
12124
+ signal
12125
+ });
12126
+ }
12127
+ function toBenchJudgeResult(result) {
12128
+ return {
12129
+ score: result.verdict.score,
12130
+ tokens: {
12131
+ input: result.telemetry.inputTokens,
12132
+ output: result.telemetry.outputTokens
12133
+ },
12134
+ latencyMs: result.telemetry.latencyMs,
12135
+ model: result.telemetry.model
12136
+ };
12137
+ }
12138
+ function normalizeModel(model) {
12139
+ if (model === void 0) return DEFAULT_OPENAI_RESPONSES_JUDGE_MODEL;
12140
+ const trimmed = model.trim();
12141
+ if (trimmed.length === 0) {
12142
+ throw new Error("OpenAI Responses judge model must be a non-empty string");
12143
+ }
12144
+ return trimmed;
12145
+ }
12146
+ function parseVerdict(text) {
12147
+ let parsed;
12148
+ try {
12149
+ parsed = JSON.parse(text);
12150
+ } catch {
12151
+ return null;
12152
+ }
12153
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
12154
+ const candidate = parsed;
12155
+ const keys = Object.keys(candidate).sort();
12156
+ if (keys.join(",") !== "decision,reason,score") return null;
12157
+ if (typeof candidate.score !== "number" || !Number.isFinite(candidate.score) || candidate.score < 0 || candidate.score > 1 || candidate.decision !== "pass" && candidate.decision !== "partial" && candidate.decision !== "fail" || typeof candidate.reason !== "string" || candidate.reason.trim().length === 0) {
12158
+ return null;
12159
+ }
12160
+ return {
12161
+ score: candidate.score,
12162
+ decision: candidate.decision,
12163
+ reason: candidate.reason.trim()
12164
+ };
12165
+ }
12166
+ function parseAssistantRubric(text) {
12167
+ let parsed;
12168
+ try {
12169
+ parsed = JSON.parse(text);
12170
+ } catch {
12171
+ return false;
12172
+ }
12173
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
12174
+ const candidate = parsed;
12175
+ const keys = Object.keys(candidate).sort();
12176
+ if (keys.join(",") !== "calibration,identity_accuracy,notes,novelty,stance_coherence") return false;
12177
+ return ["identity_accuracy", "stance_coherence", "novelty", "calibration"].every(
12178
+ (key) => typeof candidate[key] === "number" && Number.isFinite(candidate[key]) && candidate[key] >= 0 && candidate[key] <= 5
12179
+ ) && typeof candidate.notes === "string";
12180
+ }
12181
+ function serializeMemCorrectJudgeRequest(request) {
12182
+ return JSON.stringify({
12183
+ taskId: request.taskId,
12184
+ query: request.query,
12185
+ retiredContent: request.retiredContent,
12186
+ correctedContent: request.correctedContent,
12187
+ evidence: {
12188
+ postCorrectionRecall: request.postCorrectionRecall,
12189
+ postMaintenanceRecall: request.postMaintenanceRecall,
12190
+ postReingestRecall: request.postReingestRecall
12191
+ }
12192
+ });
12193
+ }
12194
+ function readOutputText(payload) {
12195
+ const text = (payload.output ?? []).flatMap((item) => item.type === "message" ? item.content ?? [] : []).filter((part) => part.type === "output_text").map((part) => part.text ?? "").join("").trim();
12196
+ return text.length > 0 ? text : null;
12197
+ }
12198
+ function readRefusal(payload) {
12199
+ for (const item of payload.output ?? []) {
12200
+ if (item.type !== "message") continue;
12201
+ for (const part of item.content ?? []) {
12202
+ if (part.type === "refusal") return part.refusal ?? "refused";
12203
+ }
12204
+ }
12205
+ return null;
12206
+ }
12207
+ function readTokens(payload) {
12208
+ return {
12209
+ input: finiteNonNegative(payload?.usage?.input_tokens),
12210
+ output: finiteNonNegative(payload?.usage?.output_tokens)
12211
+ };
12212
+ }
12213
+ function finiteNonNegative(value) {
12214
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
12215
+ }
12216
+ function safeErrorName(error) {
12217
+ return error instanceof Error && error.name.trim().length > 0 ? error.name : "Error";
12218
+ }
12219
+ function isAbortError2(error) {
12220
+ return error instanceof Error && error.name === "AbortError";
12221
+ }
12222
+ function trimTrailingSlashes(value) {
12223
+ let end = value.length;
12224
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end -= 1;
12225
+ return end === value.length ? value : value.slice(0, end);
12226
+ }
12227
+
12228
+ // src/responders.ts
10405
12229
  var DEFAULT_RESPONDER_SYSTEM_PROMPT = [
10406
12230
  "You answer benchmark questions using only the supplied Remnic memory context.",
10407
12231
  "If the context does not contain enough information, say that the answer is unknown.",
@@ -10891,6 +12715,9 @@ function createJudgeFromProvider(provider) {
10891
12715
  }
10892
12716
  function createProviderBackedJudge(config, providerInstance) {
10893
12717
  validateProviderConfig(config, "judge");
12718
+ if (config.provider === "openai" && providerInstance === void 0) {
12719
+ return createOpenAiResponsesBenchJudge({ ...config, provider: "openai" });
12720
+ }
10894
12721
  return createJudgeFromProvider(providerInstance ?? createProvider(config));
10895
12722
  }
10896
12723
  function createAmaBenchRecommendedJudgeFromProvider(provider) {
@@ -10944,7 +12771,15 @@ function createStructuredJudgeFromProvider(provider) {
10944
12771
  }
10945
12772
  function createProviderBackedStructuredJudge(config, providerInstance) {
10946
12773
  validateProviderConfig(config, "judge");
10947
- return createStructuredJudgeFromProvider(providerInstance ?? createProvider(config));
12774
+ if (config.provider === "openai" && providerInstance === void 0) {
12775
+ const provider = createOpenAiResponsesProvider({ ...config, provider: "openai" });
12776
+ return {
12777
+ evaluate: (request) => provider.evaluateAssistantRubric(request)
12778
+ };
12779
+ }
12780
+ return createStructuredJudgeFromProvider(
12781
+ providerInstance ?? createProvider(config)
12782
+ );
10948
12783
  }
10949
12784
  function createGatewayResponder(options) {
10950
12785
  if (!options.gatewayConfig) {
@@ -11769,10 +13604,10 @@ function manifestProviderKindToBuiltIn(kind) {
11769
13604
  }
11770
13605
 
11771
13606
  // src/local-lab/preflight.ts
11772
- var DEFAULT_PREFLIGHT_TIMEOUT_MS = 5e3;
13607
+ var DEFAULT_PREFLIGHT_TIMEOUT_MS2 = 5e3;
11773
13608
  async function preflightLocalLabRole(input, options = {}) {
11774
13609
  const fetchImpl = options.fetchImpl ?? fetch;
11775
- const timeoutMs = options.timeoutMs ?? DEFAULT_PREFLIGHT_TIMEOUT_MS;
13610
+ const timeoutMs = options.timeoutMs ?? DEFAULT_PREFLIGHT_TIMEOUT_MS2;
11776
13611
  const endpoint = discoveryEndpointFor(input.provider, input.baseUrl);
11777
13612
  const controller = new AbortController();
11778
13613
  const timer = setTimeout(() => controller.abort(new Error("preflight timeout")), timeoutMs);
@@ -12109,9 +13944,8 @@ async function resolveBenchRuntimeProfile(options) {
12109
13944
  registerCodexCliFallbackRunnerIfNeeded(internalProvider);
12110
13945
  const responderFactoryConfig = systemProvider ? asProviderFactoryConfig(systemProvider) : void 0;
12111
13946
  const judgeFactoryConfig = judgeProvider ? asProviderFactoryConfig(judgeProvider) : void 0;
12112
- const judgeProviderInstance = judgeFactoryConfig ? createProvider(judgeFactoryConfig) : void 0;
12113
- const judge = judgeFactoryConfig ? createProviderBackedJudge(judgeFactoryConfig, judgeProviderInstance) : void 0;
12114
- const structuredJudge = judgeFactoryConfig ? createProviderBackedStructuredJudge(judgeFactoryConfig, judgeProviderInstance) : void 0;
13947
+ const judge = judgeFactoryConfig ? createProviderBackedJudge(judgeFactoryConfig) : void 0;
13948
+ const structuredJudge = judgeFactoryConfig ? createProviderBackedStructuredJudge(judgeFactoryConfig) : void 0;
12115
13949
  if (profile === "baseline") {
12116
13950
  const responder = responderFactoryConfig ? createProviderBackedResponder(responderFactoryConfig) : void 0;
12117
13951
  const baselineConfig = buildBenchBaselineRemnicConfig();
@@ -12136,7 +13970,7 @@ async function resolveBenchRuntimeProfile(options) {
12136
13970
  ...drainTimeoutMs ? { drainTimeoutMs } : {}
12137
13971
  },
12138
13972
  systemProvider,
12139
- judgeProvider,
13973
+ judgeProvider: judgeProvider ? sanitizeProviderConfig(judgeProvider) : null,
12140
13974
  internalProvider: internalProvider ? sanitizeProviderConfig(internalProvider) : null
12141
13975
  };
12142
13976
  }
@@ -12145,6 +13979,10 @@ async function resolveBenchRuntimeProfile(options) {
12145
13979
  const fileConfig = options.remnicConfigPath ? await loadRemnicConfigFile(options.remnicConfigPath) : {};
12146
13980
  const realProfileOverrides = {
12147
13981
  lcmEnabled: true,
13982
+ // Read-time faithfulness is on by default for the full-feature profile.
13983
+ // An explicit config value (including false-like strings) remains
13984
+ // authoritative so benchmark operators can perform reversible ablations.
13985
+ ...fileConfig.answerSupportGate === void 0 ? { answerSupportGate: true } : {},
12148
13986
  ...options.modelSource ? { modelSource: options.modelSource } : {},
12149
13987
  ...options.gatewayAgentId ? { gatewayAgentId: options.gatewayAgentId } : {},
12150
13988
  ...options.fastGatewayAgentId ? { fastGatewayAgentId: options.fastGatewayAgentId } : {},
@@ -12175,7 +14013,7 @@ async function resolveBenchRuntimeProfile(options) {
12175
14013
  ...drainTimeoutMs ? { drainTimeoutMs } : {}
12176
14014
  },
12177
14015
  systemProvider,
12178
- judgeProvider,
14016
+ judgeProvider: judgeProvider ? sanitizeProviderConfig(judgeProvider) : null,
12179
14017
  internalProvider: internalProvider ? sanitizeProviderConfig(internalProvider) : null
12180
14018
  };
12181
14019
  }
@@ -12226,7 +14064,7 @@ async function resolveBenchRuntimeProfile(options) {
12226
14064
  ...drainTimeoutMs ? { drainTimeoutMs } : {}
12227
14065
  },
12228
14066
  systemProvider: null,
12229
- judgeProvider,
14067
+ judgeProvider: judgeProvider ? sanitizeProviderConfig(judgeProvider) : null,
12230
14068
  internalProvider: internalProvider ? sanitizeProviderConfig(internalProvider) : null
12231
14069
  };
12232
14070
  }
@@ -12284,7 +14122,9 @@ async function loadJsonObject(filePath, label) {
12284
14122
  }
12285
14123
  function resolveProviderConfig(kind, provider, model, baseUrl, requestTimeout, disableThinking, apiKey, max429WaitMs, reasoningEffort, responderContextBudgetChars, responderPromptBudgetChars) {
12286
14124
  const hasProvider = typeof provider === "string";
12287
- const hasModel = typeof model === "string" && model.trim().length > 0;
14125
+ const defaultOpenAiJudgeModel = kind === "judge" && provider === "openai" && model === void 0 ? DEFAULT_OPENAI_RESPONSES_JUDGE_MODEL : void 0;
14126
+ const resolvedModel = defaultOpenAiJudgeModel ?? model;
14127
+ const hasModel = typeof resolvedModel === "string" && resolvedModel.trim().length > 0;
12288
14128
  const hasBaseUrl = typeof baseUrl === "string" && baseUrl.trim().length > 0;
12289
14129
  const hasApiKey = typeof apiKey === "string" && apiKey.trim().length > 0;
12290
14130
  const hasReasoningEffort = reasoningEffort !== void 0;
@@ -12313,7 +14153,8 @@ function resolveProviderConfig(kind, provider, model, baseUrl, requestTimeout, d
12313
14153
  }
12314
14154
  return {
12315
14155
  provider,
12316
- model: model.trim(),
14156
+ model: resolvedModel.trim(),
14157
+ ...kind === "judge" && provider === "openai" ? { rubricVersion: OPENAI_RESPONSES_JUDGE_RUBRIC_VERSION } : {},
12317
14158
  ...hasBaseUrl ? { baseUrl: baseUrl.trim() } : {},
12318
14159
  ...hasApiKey ? { apiKey: apiKey.trim() } : {},
12319
14160
  ...requestTimeout != null || max429WaitMs != null ? { retryOptions: {
@@ -12588,7 +14429,8 @@ function asProviderFactoryConfig(config) {
12588
14429
  ...config.temperature !== void 0 ? { temperature: config.temperature } : {},
12589
14430
  ...config.seed !== void 0 ? { seed: config.seed } : {},
12590
14431
  ...config.responderContextBudgetChars !== void 0 ? { responderContextBudgetChars: config.responderContextBudgetChars } : {},
12591
- ...config.responderPromptBudgetChars !== void 0 ? { responderPromptBudgetChars: config.responderPromptBudgetChars } : {}
14432
+ ...config.responderPromptBudgetChars !== void 0 ? { responderPromptBudgetChars: config.responderPromptBudgetChars } : {},
14433
+ ...config.rubricVersion !== void 0 ? { rubricVersion: config.rubricVersion } : {}
12592
14434
  };
12593
14435
  }
12594
14436
  function asNonEmptyString(value) {
@@ -12642,9 +14484,8 @@ async function resolveLocalLabRuntimeProfile(options) {
12642
14484
  options
12643
14485
  );
12644
14486
  const judgeFactoryConfig = judgeProvider ? asProviderFactoryConfig(judgeProvider) : void 0;
12645
- const judgeProviderInstance = judgeFactoryConfig ? createProvider(judgeFactoryConfig) : void 0;
12646
- const judge = judgeFactoryConfig ? createProviderBackedJudge(judgeFactoryConfig, judgeProviderInstance) : void 0;
12647
- const structuredJudge = judgeFactoryConfig ? createProviderBackedStructuredJudge(judgeFactoryConfig, judgeProviderInstance) : void 0;
14487
+ const judge = judgeFactoryConfig ? createProviderBackedJudge(judgeFactoryConfig) : void 0;
14488
+ const structuredJudge = judgeFactoryConfig ? createProviderBackedStructuredJudge(judgeFactoryConfig) : void 0;
12648
14489
  const responderFactoryConfig = systemProvider ? asProviderFactoryConfig(systemProvider) : void 0;
12649
14490
  const responder = responderFactoryConfig ? createProviderBackedResponder(responderFactoryConfig) : void 0;
12650
14491
  const lcmObserveConcurrencyOverrides = buildLcmObserveConcurrencyOverrides(options.lcmObserveConcurrency);
@@ -12947,6 +14788,22 @@ function runJudgeWithCache(options) {
12947
14788
  }
12948
14789
  });
12949
14790
  }
14791
+ if (typeof judge.judgeMemCorrectCorrectionAcceptance === "function") {
14792
+ Object.defineProperty(wrapper, "judgeMemCorrectCorrectionAcceptance", {
14793
+ configurable: true,
14794
+ enumerable: true,
14795
+ writable: false,
14796
+ value: (request, control) => judge.judgeMemCorrectCorrectionAcceptance(request, control)
14797
+ });
14798
+ }
14799
+ if (typeof judge.judgeMemCorrectStaleMemoryHarm === "function") {
14800
+ Object.defineProperty(wrapper, "judgeMemCorrectStaleMemoryHarm", {
14801
+ configurable: true,
14802
+ enumerable: true,
14803
+ writable: false,
14804
+ value: (request, control) => judge.judgeMemCorrectStaleMemoryHarm(request, control)
14805
+ });
14806
+ }
12950
14807
  return wrapper;
12951
14808
  }
12952
14809
  function parseEnvelope(raw) {
@@ -15303,10 +17160,10 @@ function unpackMemoryArenaWebshopJsonlRecord(parsed) {
15303
17160
  if (Array.isArray(parsed)) {
15304
17161
  return parsed.map((value) => ({ value }));
15305
17162
  }
15306
- if (isPlainRecord(parsed)) {
17163
+ if (isPlainRecord2(parsed)) {
15307
17164
  for (const key of ["products", "records", "items"]) {
15308
17165
  const value = parsed[key];
15309
- if (Array.isArray(value) || isPlainRecord(value)) {
17166
+ if (Array.isArray(value) || isPlainRecord2(value)) {
15310
17167
  return unpackMemoryArenaWebshopRecords(parsed);
15311
17168
  }
15312
17169
  }
@@ -15317,7 +17174,7 @@ function unpackMemoryArenaWebshopRecords(parsed) {
15317
17174
  if (Array.isArray(parsed)) {
15318
17175
  return parsed.map((value) => ({ value }));
15319
17176
  }
15320
- if (!isPlainRecord(parsed)) {
17177
+ if (!isPlainRecord2(parsed)) {
15321
17178
  throw new Error("top-level value must be a JSON array, object, or JSONL records");
15322
17179
  }
15323
17180
  for (const key of ["products", "records", "items"]) {
@@ -15325,7 +17182,7 @@ function unpackMemoryArenaWebshopRecords(parsed) {
15325
17182
  if (Array.isArray(value)) {
15326
17183
  return value.map((item) => ({ value: item }));
15327
17184
  }
15328
- if (isPlainRecord(value)) {
17185
+ if (isPlainRecord2(value)) {
15329
17186
  return Object.entries(value).map(([defaultAsin, item]) => ({
15330
17187
  value: item,
15331
17188
  defaultAsin
@@ -15348,7 +17205,7 @@ function isMemoryArenaWebshopProductRecord(record) {
15348
17205
  ).length > 0;
15349
17206
  }
15350
17207
  function parseMemoryArenaWebshopProduct(raw) {
15351
- if (!isPlainRecord(raw.value)) {
17208
+ if (!isPlainRecord2(raw.value)) {
15352
17209
  return void 0;
15353
17210
  }
15354
17211
  const record = raw.value;
@@ -15698,7 +17555,7 @@ function formatMemoryArenaDecisionCandidate(candidate) {
15698
17555
  }
15699
17556
  function extractSelectedMemoryArenaCustomization(record) {
15700
17557
  const customizationOptions = record.customization_options;
15701
- if (!isPlainRecord(customizationOptions)) {
17558
+ if (!isPlainRecord2(customizationOptions)) {
15702
17559
  return void 0;
15703
17560
  }
15704
17561
  for (const optionGroup of Object.values(customizationOptions)) {
@@ -15706,7 +17563,7 @@ function extractSelectedMemoryArenaCustomization(record) {
15706
17563
  continue;
15707
17564
  }
15708
17565
  for (const option of optionGroup) {
15709
- if (!isPlainRecord(option) || option.is_selected !== true) {
17566
+ if (!isPlainRecord2(option) || option.is_selected !== true) {
15710
17567
  continue;
15711
17568
  }
15712
17569
  const priceText = cleanMemoryArenaWebshopString(
@@ -15723,7 +17580,7 @@ function extractSelectedMemoryArenaCustomization(record) {
15723
17580
  }
15724
17581
  function extractMemoryArenaProductInformationValue(record, requestedKey) {
15725
17582
  const productInformation = record.product_information;
15726
- if (!isPlainRecord(productInformation)) {
17583
+ if (!isPlainRecord2(productInformation)) {
15727
17584
  return void 0;
15728
17585
  }
15729
17586
  const normalizedRequestedKey = normalizeItemSelectionText(requestedKey);
@@ -15736,13 +17593,13 @@ function extractMemoryArenaProductInformationValue(record, requestedKey) {
15736
17593
  }
15737
17594
  function readMemoryArenaProductCustomerReviewNumber(record, key) {
15738
17595
  const productInformation = record.product_information;
15739
- if (!isPlainRecord(productInformation)) {
17596
+ if (!isPlainRecord2(productInformation)) {
15740
17597
  return void 0;
15741
17598
  }
15742
17599
  const customerReviews = Object.entries(productInformation).find(
15743
17600
  ([entryKey]) => normalizeItemSelectionText(cleanMemoryArenaWebshopString(entryKey)) === "customer reviews"
15744
17601
  )?.[1];
15745
- if (!isPlainRecord(customerReviews)) {
17602
+ if (!isPlainRecord2(customerReviews)) {
15746
17603
  return void 0;
15747
17604
  }
15748
17605
  return readMemoryArenaNumberField(customerReviews, key);
@@ -15815,7 +17672,7 @@ function cleanMemoryArenaWebshopString(value) {
15815
17672
  function formatMemoryArenaNumber(value) {
15816
17673
  return Number.isInteger(value) ? String(value) : String(Number(value.toFixed(2)));
15817
17674
  }
15818
- function isPlainRecord(value) {
17675
+ function isPlainRecord2(value) {
15819
17676
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
15820
17677
  }
15821
17678
  function isNodeErrorCode(error, code) {
@@ -17027,6 +18884,7 @@ async function* toAsyncIterable(iter) {
17027
18884
  }
17028
18885
  async function runPublishedHarness(ctx) {
17029
18886
  validateContext(ctx);
18887
+ const answerSupportGate = resolveAnswerSupportGate(ctx.options);
17030
18888
  const trialConcurrency = resolveTrialConcurrency(
17031
18889
  ctx.options.benchmarkOptions?.trialConcurrency
17032
18890
  );
@@ -17050,7 +18908,8 @@ async function runPublishedHarness(ctx) {
17050
18908
  await executePlanTrials(ctx, plan.trials, {
17051
18909
  planIndex,
17052
18910
  tasks,
17053
- trialConcurrency
18911
+ trialConcurrency,
18912
+ answerSupportGate
17054
18913
  });
17055
18914
  }
17056
18915
  return buildBenchmarkResult(ctx, tasks);
@@ -17061,7 +18920,12 @@ async function executePlanTrials(ctx, trials, options) {
17061
18920
  appendCompletedTask(
17062
18921
  ctx,
17063
18922
  options.tasks,
17064
- await executeTrialWithFailure(ctx, trial, options.planIndex)
18923
+ await executeTrialWithFailure(
18924
+ ctx,
18925
+ trial,
18926
+ options.planIndex,
18927
+ options.answerSupportGate
18928
+ )
17065
18929
  );
17066
18930
  }
17067
18931
  return;
@@ -17086,7 +18950,8 @@ async function executePlanTrials(ctx, trials, options) {
17086
18950
  results[trialIndex] = await executeTrialWithFailure(
17087
18951
  ctx,
17088
18952
  trials[trialIndex],
17089
- options.planIndex
18953
+ options.planIndex,
18954
+ options.answerSupportGate
17090
18955
  );
17091
18956
  completed[trialIndex] = true;
17092
18957
  emitCompletedPrefix();
@@ -17101,10 +18966,10 @@ function appendCompletedTask(ctx, tasks, task) {
17101
18966
  tasks.push(task);
17102
18967
  ctx.options.onTaskComplete?.(task, tasks.length, ctx.totalCount);
17103
18968
  }
17104
- async function executeTrialWithFailure(ctx, trial, planIndex) {
18969
+ async function executeTrialWithFailure(ctx, trial, planIndex, answerSupportGate) {
17105
18970
  const trialId = trial.taskId ?? trial.question.slice(0, 60);
17106
18971
  try {
17107
- return await executeTrial(ctx, trial);
18972
+ return await executeTrial(ctx, trial, answerSupportGate);
17108
18973
  } catch (err) {
17109
18974
  const message = err instanceof Error ? err.message : String(err);
17110
18975
  console.error(` [WARN] harness trial plan-${planIndex}/${trialId} failed: ${message}`);
@@ -17116,7 +18981,17 @@ async function executeTrialWithFailure(ctx, trial, planIndex) {
17116
18981
  scores: buildFailureScores(ctx.metricsSpec.metrics),
17117
18982
  latencyMs: 0,
17118
18983
  tokens: { input: 0, output: 0 },
17119
- details: { error: message }
18984
+ details: {
18985
+ // `error` is retained for compatibility with existing diagnostics.
18986
+ // The structured marker is the authoritative run-status signal; an
18987
+ // arbitrary benchmark-owned `extraDetails.error` must not make a
18988
+ // successful trial look failed.
18989
+ error: message,
18990
+ benchmarkFailure: {
18991
+ kind: "trial_execution_failure",
18992
+ message
18993
+ }
18994
+ }
17120
18995
  };
17121
18996
  }
17122
18997
  }
@@ -17172,8 +19047,29 @@ function resolveTrialConcurrency(raw) {
17172
19047
  }
17173
19048
  return parsed;
17174
19049
  }
17175
- async function executeTrial(ctx, trial) {
17176
- const { result: recalledText, durationMs } = await timed(async () => {
19050
+ function resolveAnswerSupportGate(options) {
19051
+ const raw = options.benchmarkOptions?.answerSupportGate ?? options.remnicConfig?.answerSupportGate;
19052
+ if (raw === void 0) {
19053
+ return false;
19054
+ }
19055
+ if (typeof raw === "boolean") {
19056
+ return raw;
19057
+ }
19058
+ if (typeof raw === "string") {
19059
+ const normalized = raw.trim().toLowerCase();
19060
+ if (["true", "1", "yes", "on"].includes(normalized)) {
19061
+ return true;
19062
+ }
19063
+ if (["false", "0", "no", "off"].includes(normalized)) {
19064
+ return false;
19065
+ }
19066
+ }
19067
+ throw new Error(
19068
+ "PublishedBenchmarkHarness: answerSupportGate must be a boolean or one of true/false, 1/0, yes/no, on/off."
19069
+ );
19070
+ }
19071
+ async function executeTrial(ctx, trial, answerSupportGate) {
19072
+ const { result: recallResult, durationMs } = await timed(async () => {
17177
19073
  const recallBudget = benchmarkRecallBudgetForSessionCount(
17178
19074
  trial.recallSessionIds.length
17179
19075
  );
@@ -17183,17 +19079,21 @@ async function executeTrial(ctx, trial) {
17183
19079
  )
17184
19080
  );
17185
19081
  const rawRecalledText = recalledSessions.filter(Boolean).join("\n\n");
17186
- return trial.recallTextTransform ? trial.recallTextTransform({
19082
+ const recalledText2 = trial.recallTextTransform ? trial.recallTextTransform({
17187
19083
  question: trial.question,
17188
19084
  recalledText: rawRecalledText
17189
19085
  }) : rawRecalledText;
19086
+ const recallSupport2 = answerSupportGate ? await assessRecallSupport(ctx, trial, recalledText2) : void 0;
19087
+ return { recalledText: recalledText2, recallSupport: recallSupport2 };
17190
19088
  });
19089
+ const { recalledText, recallSupport } = recallResult;
17191
19090
  let answered = await answerBenchmarkQuestion({
17192
19091
  question: trial.question,
17193
19092
  recalledText,
17194
19093
  responder: ctx.options.system.responder,
17195
19094
  answerMode: "strict",
17196
- answerFormat: trial.answerFormat
19095
+ answerFormat: trial.answerFormat,
19096
+ recallSupport
17197
19097
  }).catch(
17198
19098
  (error) => answerWithTrialFallback(trial, recalledText, error)
17199
19099
  );
@@ -17261,6 +19161,7 @@ async function executeTrial(ctx, trial) {
17261
19161
  recalledText,
17262
19162
  answeredText: answered.finalAnswer,
17263
19163
  ...trial.answerFormat ? { answerFormat: trial.answerFormat } : {},
19164
+ ...answerSupportGate ? { answerSupportGate: true, recallSupport } : {},
17264
19165
  responderModel: answered.model,
17265
19166
  judgeModel: judgeResult.model,
17266
19167
  ...answered.fallbackReason ? { answerFallbackReason: answered.fallbackReason } : {},
@@ -17290,6 +19191,56 @@ async function executeTrial(ctx, trial) {
17290
19191
  details
17291
19192
  };
17292
19193
  }
19194
+ async function assessRecallSupport(ctx, trial, recalledText) {
19195
+ if (recalledText.trim().length === 0) {
19196
+ return {
19197
+ status: "empty",
19198
+ reason: "successful recall returned empty responder context",
19199
+ evidenceCount: 0
19200
+ };
19201
+ }
19202
+ const assessor = ctx.options.system.assessRecallSupport;
19203
+ if (!assessor) {
19204
+ return {
19205
+ status: "unavailable",
19206
+ reason: "adapter did not provide an exact-context support assessment"
19207
+ };
19208
+ }
19209
+ try {
19210
+ const assessment = await assessor.call(ctx.options.system, {
19211
+ query: trial.question,
19212
+ recalledText,
19213
+ sessionIds: trial.recallSessionIds
19214
+ });
19215
+ validateRecallSupportAssessment(assessment);
19216
+ return assessment;
19217
+ } catch (error) {
19218
+ return {
19219
+ status: "backend_failure",
19220
+ reason: error instanceof Error ? error.message : String(error)
19221
+ };
19222
+ }
19223
+ }
19224
+ function validateRecallSupportAssessment(assessment) {
19225
+ const allowed = [
19226
+ "supported",
19227
+ "weak",
19228
+ "empty",
19229
+ "unavailable",
19230
+ "backend_failure"
19231
+ ];
19232
+ if (!assessment || !allowed.includes(assessment.status)) {
19233
+ throw new Error("adapter returned an invalid recall support status");
19234
+ }
19235
+ if (assessment.status !== "weak") {
19236
+ return;
19237
+ }
19238
+ if (!Number.isInteger(assessment.evidenceCount) || (assessment.evidenceCount ?? 0) <= 0 || !Number.isFinite(assessment.maxScore) || !Number.isFinite(assessment.supportThreshold) || (assessment.maxScore ?? Number.POSITIVE_INFINITY) >= (assessment.supportThreshold ?? Number.NEGATIVE_INFINITY)) {
19239
+ throw new Error(
19240
+ "adapter weak recall support requires a positive evidenceCount and a finite maxScore below supportThreshold"
19241
+ );
19242
+ }
19243
+ }
17293
19244
  async function scoreTrialJudge(ctx, trial, answeredText) {
17294
19245
  if (!trial.binaryJudgePrompt) {
17295
19246
  return llmJudgeScoreDetailed(
@@ -17379,6 +19330,18 @@ async function buildBenchmarkResult(ctx, tasks) {
17379
19330
  0
17380
19331
  );
17381
19332
  const mode = ctx.options.mode;
19333
+ const failedTasks = tasks.flatMap((task) => {
19334
+ const marker = task.details?.benchmarkFailure;
19335
+ if (typeof marker !== "object" || marker === null || marker.kind !== "trial_execution_failure") {
19336
+ return [];
19337
+ }
19338
+ const message = marker.message;
19339
+ return [{
19340
+ taskId: task.taskId,
19341
+ message: typeof message === "string" ? message : "unknown trial failure"
19342
+ }];
19343
+ });
19344
+ const failureReason = failedTasks.length > 0 ? `trial_execution_failure: ${failedTasks.length}/${tasks.length} scored trial(s) failed (${failedTasks.slice(0, 3).map((failure) => `${failure.taskId}: ${failure.message.slice(0, 240)}`).join("; ")}${failedTasks.length > 3 ? `; and ${failedTasks.length - 3} more` : ""})` : void 0;
17382
19345
  return {
17383
19346
  meta: {
17384
19347
  id: randomUUID5(),
@@ -17390,7 +19353,8 @@ async function buildBenchmarkResult(ctx, tasks) {
17390
19353
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
17391
19354
  mode,
17392
19355
  runCount: 1,
17393
- seeds: [ctx.options.seed ?? 0]
19356
+ seeds: [ctx.options.seed ?? 0],
19357
+ ...failureReason ? { status: "partial", failureReason } : {}
17394
19358
  },
17395
19359
  config: {
17396
19360
  systemProvider: ctx.options.systemProvider ?? null,
@@ -17453,16 +19417,25 @@ async function runLongMemEvalBenchmark(options) {
17453
19417
  function buildPlan(item, options) {
17454
19418
  const ingestSessions = [];
17455
19419
  const sessionIds = [];
17456
- const annotateTemporalSources = shouldAnnotateTemporalSources(item.question);
19420
+ const evidenceStrategy = resolveLongMemEvalEvidenceStrategy(item);
19421
+ const annotateSources = evidenceStrategy !== "single-session" || shouldAnnotateTemporalSources(item.question);
19422
+ const sourceChronology = rankSourceDates(
19423
+ item.haystack_dates,
19424
+ item.haystack_sessions.length
19425
+ );
19426
+ const currentKnowledgeSourceIndex = evidenceStrategy === "knowledge-update" ? sourceChronology.indexOf(item.haystack_sessions.length) : void 0;
17457
19427
  for (let sessionIndex = 0; sessionIndex < item.haystack_sessions.length; sessionIndex += 1) {
17458
19428
  const sessionId = item.haystack_session_ids[sessionIndex] ?? `session-${sessionIndex}`;
17459
19429
  const haystackDate = item.haystack_dates[sessionIndex];
17460
19430
  const messages = item.haystack_sessions[sessionIndex].map(
17461
19431
  (turn) => ({
17462
19432
  role: turn.role,
17463
- content: annotateTemporalSources ? formatLongMemEvalTurn(turn.content, {
19433
+ content: annotateSources ? formatLongMemEvalTurn(turn.content, {
17464
19434
  sessionId,
17465
- haystackDate
19435
+ haystackDate,
19436
+ sourceOrder: sourceChronology[sessionIndex],
19437
+ sourceCount: item.haystack_sessions.length,
19438
+ sourceRecency: currentKnowledgeSourceIndex === void 0 ? void 0 : sessionIndex === currentKnowledgeSourceIndex ? "latest_source" : "historical_source"
17466
19439
  }) : turn.content
17467
19440
  })
17468
19441
  );
@@ -17474,6 +19447,7 @@ function buildPlan(item, options) {
17474
19447
  question: item.question,
17475
19448
  expected: item.answer,
17476
19449
  recallSessionIds: sessionIds,
19450
+ recallTextTransform: ({ recalledText }) => composeLongMemEvalEvidence(recalledText, evidenceStrategy),
17477
19451
  binaryJudgePrompt: ({ answeredText }) => buildLongMemEvalOfficialJudgePrompt(item, answeredText),
17478
19452
  extraDetails: {
17479
19453
  questionType: item.question_type,
@@ -17481,6 +19455,8 @@ function buildPlan(item, options) {
17481
19455
  haystackDates: item.haystack_dates,
17482
19456
  haystackSessionIds: item.haystack_session_ids,
17483
19457
  answerSessionIds: item.answer_session_ids,
19458
+ evidenceStrategy,
19459
+ knowledgeSupersessionSurfaced: evidenceStrategy === "knowledge-update",
17484
19460
  judgeProtocol: "longmemeval-official-yes-no",
17485
19461
  judgePromptSource: "https://github.com/xiaowu0162/LongMemEval/blob/main/src/evaluation/evaluate_qa.py"
17486
19462
  },
@@ -17605,12 +19581,71 @@ function uniqueSearchResults(results) {
17605
19581
  return unique;
17606
19582
  }
17607
19583
  function formatLongMemEvalTurn(content, metadata) {
17608
- const fields = [`source_session: ${metadata.sessionId}`];
19584
+ const fields = [
19585
+ `source_session: ${metadata.sessionId}`,
19586
+ `source_order: ${metadata.sourceOrder}/${metadata.sourceCount}`
19587
+ ];
17609
19588
  if (metadata.haystackDate) {
17610
19589
  fields.push(`source_date: ${metadata.haystackDate}`);
17611
19590
  }
19591
+ if (metadata.sourceRecency) {
19592
+ fields.push(`source_recency: ${metadata.sourceRecency}`);
19593
+ }
17612
19594
  return `[${fields.join("] [")}] ${content}`;
17613
19595
  }
19596
+ function resolveLongMemEvalEvidenceStrategy(item) {
19597
+ if (item.question_type === "knowledge-update" || item.question_type === "multi-session-update") {
19598
+ return "knowledge-update";
19599
+ }
19600
+ if (item.question_type === "multi-session") {
19601
+ return "multi-session";
19602
+ }
19603
+ return "single-session";
19604
+ }
19605
+ function composeLongMemEvalEvidence(recalledText, strategy) {
19606
+ if (recalledText.trim().length === 0 || strategy === "single-session") {
19607
+ return recalledText;
19608
+ }
19609
+ const guidance = [
19610
+ "[multi_session_evidence] Compose the answer from all relevant source_session blocks; one block may contain only part of the answer."
19611
+ ];
19612
+ if (strategy === "knowledge-update") {
19613
+ guidance.push(
19614
+ "[knowledge_update_evidence] Only when the same fact conflicts across sources, treat its older value as superseded and prefer the later source_date/source_order. source_recency describes source chronology, not the validity of unrelated facts."
19615
+ );
19616
+ }
19617
+ return `${guidance.join("\n")}
19618
+
19619
+ ${recalledText}`;
19620
+ }
19621
+ function rankSourceDates(dates, sourceCount) {
19622
+ const chronologicalIndices = Array.from(
19623
+ { length: sourceCount },
19624
+ (_, index) => index
19625
+ ).sort((left, right) => {
19626
+ const leftTimestamp = parseSourceDate(dates[left]);
19627
+ const rightTimestamp = parseSourceDate(dates[right]);
19628
+ if (leftTimestamp < rightTimestamp) {
19629
+ return -1;
19630
+ }
19631
+ if (leftTimestamp > rightTimestamp) {
19632
+ return 1;
19633
+ }
19634
+ return left - right;
19635
+ });
19636
+ const ranks = new Array(sourceCount);
19637
+ chronologicalIndices.forEach((sourceIndex, chronologicalIndex) => {
19638
+ ranks[sourceIndex] = chronologicalIndex + 1;
19639
+ });
19640
+ return ranks;
19641
+ }
19642
+ function parseSourceDate(value) {
19643
+ if (!value) {
19644
+ return Number.NEGATIVE_INFINITY;
19645
+ }
19646
+ const timestamp = Date.parse(value);
19647
+ return Number.isFinite(timestamp) ? timestamp : Number.NEGATIVE_INFINITY;
19648
+ }
17614
19649
  function shouldAnnotateTemporalSources(question) {
17615
19650
  return collectIsoDateCues(question).length > 0 || collectTemporalLexicalCues(question).length > 0;
17616
19651
  }
@@ -17683,6 +19718,8 @@ var CATEGORY_NAMES = {
17683
19718
  };
17684
19719
  var DIALOGUE_ID_PATTERN = /\bD\d+:\d+\b/g;
17685
19720
  var LOCOMO_FOCUSED_LINE_LIMIT = 14;
19721
+ var LOCOMO_DIRECT_LINE_LIMIT = 10;
19722
+ var LOCOMO_COMPOSITION_HOP_LIMIT = 2;
17686
19723
  var LOCOMO_FOCUSED_LINE_MAX_CHARS = 420;
17687
19724
  var LOCOMO_FOCUSED_CONTEXT_MAX_CHARS = 6e3;
17688
19725
  var LOCOMO_FALLBACK_CONTEXT_MAX_CHARS = 8e3;
@@ -17917,8 +19954,22 @@ async function runLoCoMoBenchmark(options) {
17917
19954
  options.limit
17918
19955
  );
17919
19956
  const trialLimit = resolveTrialLimit(options.benchmarkOptions?.trialLimit);
17920
- const plans = applyTrialLimit(conversations.map(buildPlan2), trialLimit);
17921
- const benchmarkOptions = trialLimit === void 0 ? options.benchmarkOptions : { ...options.benchmarkOptions ?? {}, trialLimit };
19957
+ const multiHopRecallComposition = resolveLoCoMoBooleanOption(
19958
+ options.benchmarkOptions?.multiHopRecallComposition,
19959
+ "multiHopRecallComposition",
19960
+ true
19961
+ );
19962
+ const plans = applyTrialLimit(
19963
+ conversations.map(
19964
+ (conversation) => buildPlan2(conversation, multiHopRecallComposition)
19965
+ ),
19966
+ trialLimit
19967
+ );
19968
+ const benchmarkOptions = {
19969
+ ...options.benchmarkOptions ?? {},
19970
+ ...trialLimit === void 0 ? {} : { trialLimit },
19971
+ multiHopRecallComposition
19972
+ };
17922
19973
  return runPublishedHarness({
17923
19974
  options: { ...options, benchmarkOptions },
17924
19975
  metricsSpec: {
@@ -17928,6 +19979,26 @@ async function runLoCoMoBenchmark(options) {
17928
19979
  totalCount: plans.reduce((sum, plan) => sum + plan.trials.length, 0)
17929
19980
  });
17930
19981
  }
19982
+ function resolveLoCoMoBooleanOption(raw, optionName, defaultValue) {
19983
+ if (raw === void 0) {
19984
+ return defaultValue;
19985
+ }
19986
+ if (typeof raw === "boolean") {
19987
+ return raw;
19988
+ }
19989
+ if (typeof raw === "string") {
19990
+ const normalized = raw.trim().toLowerCase();
19991
+ if (["true", "1", "yes", "on"].includes(normalized)) {
19992
+ return true;
19993
+ }
19994
+ if (["false", "0", "no", "off"].includes(normalized)) {
19995
+ return false;
19996
+ }
19997
+ }
19998
+ throw new Error(
19999
+ `LoCoMo benchmarkOptions.${optionName} must be a boolean or one of true/false, 1/0, yes/no, on/off.`
20000
+ );
20001
+ }
17931
20002
  function resolveTrialLimit(raw) {
17932
20003
  if (raw === void 0) {
17933
20004
  return void 0;
@@ -17961,7 +20032,7 @@ function applyTrialLimit(plans, trialLimit) {
17961
20032
  }
17962
20033
  return limitedPlans;
17963
20034
  }
17964
- function buildPlan2(conversation) {
20035
+ function buildPlan2(conversation, multiHopRecallComposition) {
17965
20036
  const sessions = extractSessions(conversation.conversation);
17966
20037
  const speakerA = typeof conversation.conversation.speaker_a === "string" ? conversation.conversation.speaker_a : "Speaker A";
17967
20038
  const ingestSessions = [];
@@ -17979,11 +20050,17 @@ function buildPlan2(conversation) {
17979
20050
  ingestSessions.push({ sessionId, messages });
17980
20051
  }
17981
20052
  const trials = conversation.qa.map(
17982
- (qa, questionIndex) => buildTrial(conversation.sample_id, qa, questionIndex, sessionIds)
20053
+ (qa, questionIndex) => buildTrial(
20054
+ conversation.sample_id,
20055
+ qa,
20056
+ questionIndex,
20057
+ sessionIds,
20058
+ multiHopRecallComposition
20059
+ )
17983
20060
  );
17984
20061
  return { ingestSessions, trials };
17985
20062
  }
17986
- function buildTrial(conversationId, qa, questionIndex, sessionIds) {
20063
+ function buildTrial(conversationId, qa, questionIndex, sessionIds, multiHopRecallComposition) {
17987
20064
  const categoryName = CATEGORY_NAMES[qa.category] ?? `category_${qa.category}`;
17988
20065
  return {
17989
20066
  taskId: `${conversationId}-q${questionIndex}-${categoryName}`,
@@ -17993,7 +20070,8 @@ function buildTrial(conversationId, qa, questionIndex, sessionIds) {
17993
20070
  answerFormat: "short-with-specifics",
17994
20071
  recallTextTransform: ({ question, recalledText }) => prioritizeLoCoMoRecallText({
17995
20072
  question,
17996
- recalledText: sanitizeLoCoMoRecallText({ question, recalledText })
20073
+ recalledText: sanitizeLoCoMoRecallText({ question, recalledText }),
20074
+ multiHopRecallComposition
17997
20075
  }),
17998
20076
  answerFallback: ({ question, recalledText }) => answerLoCoMoFromRecall(question, recalledText),
17999
20077
  answerRefinement: ({ question, recalledText, answeredText }) => refineLoCoMoAnswerFromRecall({ question, recalledText, answeredText }),
@@ -18194,7 +20272,9 @@ function sanitizeLoCoMoRecallText(args) {
18194
20272
  );
18195
20273
  }
18196
20274
  function prioritizeLoCoMoRecallText(args) {
18197
- const lines = args.recalledText.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
20275
+ const lines = dedupePreserveOrder(
20276
+ args.recalledText.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n").map((line) => line.trim()).filter((line) => line.length > 0)
20277
+ );
18198
20278
  const questionTokens = expandLoCoMoQuestionTokens(
18199
20279
  tokenizeForLoCoMo(args.question)
18200
20280
  );
@@ -18205,20 +20285,124 @@ function prioritizeLoCoMoRecallText(args) {
18205
20285
  })).filter((entry) => entry.score > 0).sort(
18206
20286
  (left, right) => right.score === left.score ? left.index - right.index : right.score - left.score
18207
20287
  );
18208
- const focused = dedupePreserveOrder(
18209
- scored.slice(0, LOCOMO_FOCUSED_LINE_LIMIT).map((entry) => truncateLoCoMoLine(entry.line))
20288
+ const directSeeds = scored.slice(
20289
+ 0,
20290
+ args.multiHopRecallComposition ? LOCOMO_DIRECT_LINE_LIMIT : LOCOMO_FOCUSED_LINE_LIMIT
18210
20291
  );
18211
- if (focused.length === 0) {
20292
+ const linkedHops = args.multiHopRecallComposition ? composeLoCoMoLinkedEvidence({
20293
+ lines,
20294
+ direct: directSeeds,
20295
+ questionTokens,
20296
+ remainingLineBudget: LOCOMO_FOCUSED_LINE_LIMIT - directSeeds.length
20297
+ }) : [];
20298
+ const linkedLineCount = linkedHops.reduce(
20299
+ (sum, hop) => sum + hop.lines.length,
20300
+ 0
20301
+ );
20302
+ const direct = [
20303
+ ...directSeeds,
20304
+ ...scored.slice(
20305
+ directSeeds.length,
20306
+ LOCOMO_FOCUSED_LINE_LIMIT - linkedLineCount
20307
+ )
20308
+ ];
20309
+ if (direct.length === 0 && linkedHops.length === 0) {
18212
20310
  return truncateLoCoMoContext(
18213
20311
  args.recalledText,
18214
20312
  LOCOMO_FALLBACK_CONTEXT_MAX_CHARS
18215
20313
  );
18216
20314
  }
20315
+ const sections = [
20316
+ "## LoCoMo Question-Focused Evidence",
20317
+ ...direct.map((entry) => truncateLoCoMoLine(entry.line))
20318
+ ];
20319
+ for (const hop of linkedHops) {
20320
+ sections.push(
20321
+ `## LoCoMo Linked Evidence (hop ${hop.hop})`,
20322
+ ...hop.lines.map(truncateLoCoMoLine)
20323
+ );
20324
+ }
18217
20325
  return truncateLoCoMoContext(
18218
- ["## LoCoMo Question-Focused Evidence", ...focused].join("\n"),
20326
+ sections.join("\n"),
18219
20327
  LOCOMO_FOCUSED_CONTEXT_MAX_CHARS
18220
20328
  );
18221
20329
  }
20330
+ function composeLoCoMoLinkedEvidence(args) {
20331
+ if (args.direct.length === 0 || args.remainingLineBudget <= 0) {
20332
+ return [];
20333
+ }
20334
+ const lineEntries = args.lines.map((line, index) => ({
20335
+ line,
20336
+ index,
20337
+ directScore: scoreLoCoMoLine(line, args.questionTokens),
20338
+ bridgeTokens: collectLoCoMoBridgeTokens(line, args.questionTokens)
20339
+ }));
20340
+ const selectedIndexes = new Set(args.direct.map((entry) => entry.index));
20341
+ const visitedBridgeTokens = /* @__PURE__ */ new Set();
20342
+ let frontierTokens = /* @__PURE__ */ new Set();
20343
+ for (const entry of args.direct) {
20344
+ for (const token of lineEntries[entry.index]?.bridgeTokens ?? []) {
20345
+ frontierTokens.add(token);
20346
+ visitedBridgeTokens.add(token);
20347
+ }
20348
+ }
20349
+ const hops = [];
20350
+ let remaining = args.remainingLineBudget;
20351
+ for (let hop = 1; hop <= LOCOMO_COMPOSITION_HOP_LIMIT && remaining > 0 && frontierTokens.size > 0; hop += 1) {
20352
+ const candidates = lineEntries.filter(
20353
+ (entry) => entry.directScore === 0 && !selectedIndexes.has(entry.index)
20354
+ ).map((entry) => ({
20355
+ ...entry,
20356
+ sharedTokens: [...entry.bridgeTokens].filter(
20357
+ (token) => frontierTokens.has(token)
20358
+ )
20359
+ })).filter((entry) => entry.sharedTokens.length > 0).sort((left, right) => {
20360
+ if (right.sharedTokens.length !== left.sharedTokens.length) {
20361
+ return right.sharedTokens.length - left.sharedTokens.length;
20362
+ }
20363
+ const longestLeft = Math.max(
20364
+ ...left.sharedTokens.map((token) => token.length)
20365
+ );
20366
+ const longestRight = Math.max(
20367
+ ...right.sharedTokens.map((token) => token.length)
20368
+ );
20369
+ return longestRight === longestLeft ? left.index - right.index : longestRight - longestLeft;
20370
+ }).slice(0, remaining);
20371
+ if (candidates.length === 0) {
20372
+ break;
20373
+ }
20374
+ hops.push({ hop, lines: candidates.map((entry) => entry.line) });
20375
+ remaining -= candidates.length;
20376
+ const nextFrontier = /* @__PURE__ */ new Set();
20377
+ for (const candidate of candidates) {
20378
+ selectedIndexes.add(candidate.index);
20379
+ for (const token of candidate.bridgeTokens) {
20380
+ if (!visitedBridgeTokens.has(token)) {
20381
+ nextFrontier.add(token);
20382
+ visitedBridgeTokens.add(token);
20383
+ }
20384
+ }
20385
+ }
20386
+ frontierTokens = nextFrontier;
20387
+ }
20388
+ return hops;
20389
+ }
20390
+ function collectLoCoMoBridgeTokens(line, questionTokens) {
20391
+ const entityLikeTokens = /* @__PURE__ */ new Set();
20392
+ for (const match of line.matchAll(/\b[A-Z][A-Za-z0-9'-]{2,}\b/g)) {
20393
+ for (const token of tokenizeForLoCoMo(match[0])) {
20394
+ entityLikeTokens.add(token);
20395
+ }
20396
+ }
20397
+ const result = /* @__PURE__ */ new Set();
20398
+ for (const token of tokenizeForLoCoMo(line)) {
20399
+ if (!entityLikeTokens.has(token) || token.length < 3 || /^\d+$/.test(token) || questionTokens.has(token) || LOCOMO_LINK_STOP_WORDS.has(token)) {
20400
+ continue;
20401
+ }
20402
+ result.add(token);
20403
+ }
20404
+ return result;
20405
+ }
18222
20406
  function tokenizeForLoCoMo(text) {
18223
20407
  const tokens = /* @__PURE__ */ new Set();
18224
20408
  for (const rawToken of text.toLowerCase().match(/[a-z0-9]+/g) ?? []) {
@@ -18254,6 +20438,49 @@ var LOCOMO_STOP_WORDS = /* @__PURE__ */ new Set([
18254
20438
  "who",
18255
20439
  "would"
18256
20440
  ]);
20441
+ var LOCOMO_LINK_STOP_WORDS = /* @__PURE__ */ new Set([
20442
+ ...LOCOMO_STOP_WORDS,
20443
+ "about",
20444
+ "assistant",
20445
+ "context",
20446
+ "conversation",
20447
+ "discuss",
20448
+ "discussed",
20449
+ "evidence",
20450
+ "focused",
20451
+ "from",
20452
+ "have",
20453
+ "has",
20454
+ "had",
20455
+ "linked",
20456
+ "memory",
20457
+ "mention",
20458
+ "mentioned",
20459
+ "metadata",
20460
+ "observation",
20461
+ "pipeline",
20462
+ "question",
20463
+ "recall",
20464
+ "recalled",
20465
+ "remnic",
20466
+ "said",
20467
+ "says",
20468
+ "search",
20469
+ "session",
20470
+ "speaker",
20471
+ "summary",
20472
+ "system",
20473
+ "talked",
20474
+ "that",
20475
+ "their",
20476
+ "them",
20477
+ "they",
20478
+ "this",
20479
+ "told",
20480
+ "user",
20481
+ "were",
20482
+ "with"
20483
+ ]);
18257
20484
  function expandLoCoMoQuestionTokens(tokens) {
18258
20485
  const expanded = new Set(tokens);
18259
20486
  if (tokens.has("when")) {
@@ -32446,6 +34673,31 @@ async function runScenario(scenario, adapter, maintenanceCycles, uptakeLatencyCa
32446
34673
  metrics
32447
34674
  };
32448
34675
  }
34676
+ function buildMemCorrectJudgeRequest(scenario, log) {
34677
+ const recalledFor = (phase) => log.find(
34678
+ (entry) => entry.phase === phase && entry.namespace === scenario.namespace && entry.query === scenario.probe.query
34679
+ )?.recalled ?? [];
34680
+ return {
34681
+ taskId: scenario.id,
34682
+ query: scenario.probe.query,
34683
+ retiredContent: scenario.correction.retiredContent,
34684
+ correctedContent: scenario.correction.correctedContent,
34685
+ postCorrectionRecall: recalledFor("post_correction"),
34686
+ postMaintenanceRecall: recalledFor("post_maintenance"),
34687
+ postReingestRecall: recalledFor("post_reingest")
34688
+ };
34689
+ }
34690
+ function safeMemCorrectJudgeDetails(result) {
34691
+ return {
34692
+ score: result.score,
34693
+ decision: result.decision,
34694
+ model: result.model ?? "unknown",
34695
+ rubricVersion: result.rubricVersion,
34696
+ inputTokens: result.tokens.input,
34697
+ outputTokens: result.tokens.output,
34698
+ latencyMs: result.latencyMs
34699
+ };
34700
+ }
32449
34701
  async function runMemCorrectBenchmark(options) {
32450
34702
  const baseOptions = options.mode === "quick" ? QUICK_OPTIONS2 : FULL_OPTIONS2;
32451
34703
  const seed = typeof options.seed === "number" ? options.seed : baseOptions.seed;
@@ -32458,6 +34710,13 @@ async function runMemCorrectBenchmark(options) {
32458
34710
  );
32459
34711
  }
32460
34712
  const { adapter, adapterLabel } = resolveAdapter(options);
34713
+ const judge = options.memCorrectJudge ?? options.system.judge;
34714
+ const hasSpecializedJudge = judge?.judgeMemCorrectCorrectionAcceptance !== void 0 && judge.judgeMemCorrectStaleMemoryHarm !== void 0;
34715
+ if (options.judgeProvider?.provider === "openai" && !hasSpecializedJudge) {
34716
+ throw new Error(
34717
+ "OpenAI MemCorrect judging requires the Responses API specialized correction and stale-harm judge methods."
34718
+ );
34719
+ }
32461
34720
  const { limit } = options;
32462
34721
  let scenarios;
32463
34722
  if (typeof limit !== "number") {
@@ -32477,6 +34736,10 @@ async function runMemCorrectBenchmark(options) {
32477
34736
  const aggregateCollateralBefore = [];
32478
34737
  const aggregateCollateralAfter = [];
32479
34738
  const aggregateProvenance = [];
34739
+ let judgeModelCalls = 0;
34740
+ let judgeInputTokens = 0;
34741
+ let judgeOutputTokens = 0;
34742
+ let judgeLatencyMs = 0;
32480
34743
  for (const scenario of scenarios) {
32481
34744
  const started = performance.now();
32482
34745
  const run = await runScenario(
@@ -32485,7 +34748,6 @@ async function runMemCorrectBenchmark(options) {
32485
34748
  generatorOptions.maintenanceCycles,
32486
34749
  generatorOptions.uptakeLatencyCap
32487
34750
  );
32488
- const latencyMs = Math.round(performance.now() - started);
32489
34751
  aggregateLog.push(...run.log);
32490
34752
  aggregateCorrections.push(run.correction);
32491
34753
  aggregateAntiEvents.push(...run.antiEvents);
@@ -32503,6 +34765,26 @@ async function runMemCorrectBenchmark(options) {
32503
34765
  };
32504
34766
  if (m.scope_precision !== null) scores.scope_precision = m.scope_precision;
32505
34767
  if (m.reassertion !== null) scores.reassertion = m.reassertion;
34768
+ let correctionJudge;
34769
+ let staleHarmJudge;
34770
+ if (hasSpecializedJudge) {
34771
+ const request = buildMemCorrectJudgeRequest(scenario, run.log);
34772
+ correctionJudge = await judge.judgeMemCorrectCorrectionAcceptance(request);
34773
+ staleHarmJudge = await judge.judgeMemCorrectStaleMemoryHarm(request);
34774
+ scores.judge_correction_acceptance = correctionJudge.score;
34775
+ scores.judge_stale_harm_avoidance = staleHarmJudge.score;
34776
+ for (const verdict of [correctionJudge, staleHarmJudge]) {
34777
+ judgeModelCalls += 1;
34778
+ judgeInputTokens += verdict.tokens.input;
34779
+ judgeOutputTokens += verdict.tokens.output;
34780
+ judgeLatencyMs += verdict.latencyMs;
34781
+ }
34782
+ }
34783
+ const taskTokens = {
34784
+ input: (correctionJudge?.tokens.input ?? 0) + (staleHarmJudge?.tokens.input ?? 0),
34785
+ output: (correctionJudge?.tokens.output ?? 0) + (staleHarmJudge?.tokens.output ?? 0)
34786
+ };
34787
+ const latencyMs = Math.round(performance.now() - started);
32506
34788
  const task = {
32507
34789
  taskId: scenario.id,
32508
34790
  question: scenario.probe.query,
@@ -32519,7 +34801,7 @@ async function runMemCorrectBenchmark(options) {
32519
34801
  }),
32520
34802
  scores,
32521
34803
  latencyMs,
32522
- tokens: { input: 0, output: 0 },
34804
+ tokens: taskTokens,
32523
34805
  details: {
32524
34806
  scenarioId: scenario.id,
32525
34807
  shape: scenario.correction.shape,
@@ -32528,7 +34810,13 @@ async function runMemCorrectBenchmark(options) {
32528
34810
  adapter: adapterLabel,
32529
34811
  metrics: {
32530
34812
  memcorrect: m
32531
- }
34813
+ },
34814
+ ...correctionJudge && staleHarmJudge ? {
34815
+ judges: {
34816
+ correctionAcceptance: safeMemCorrectJudgeDetails(correctionJudge),
34817
+ staleMemoryHarmAvoidance: safeMemCorrectJudgeDetails(staleHarmJudge)
34818
+ }
34819
+ } : {}
32532
34820
  }
32533
34821
  };
32534
34822
  tasks.push(task);
@@ -32546,6 +34834,7 @@ async function runMemCorrectBenchmark(options) {
32546
34834
  });
32547
34835
  const remnicVersion = await getRemnicVersion();
32548
34836
  const totalLatencyMs = tasks.reduce((sum, t) => sum + t.latencyMs, 0);
34837
+ const totalTokens = judgeInputTokens + judgeOutputTokens;
32549
34838
  const { adapter: _liveAdapter, ...persistableBenchmarkOptions } = options.benchmarkOptions ?? {};
32550
34839
  return {
32551
34840
  meta: {
@@ -32573,6 +34862,14 @@ async function runMemCorrectBenchmark(options) {
32573
34862
  factsPerPersona: generatorOptions.factsPerPersona,
32574
34863
  maintenanceCycles: generatorOptions.maintenanceCycles,
32575
34864
  uptakeLatencyCap: generatorOptions.uptakeLatencyCap,
34865
+ ...judgeModelCalls > 0 ? {
34866
+ judgeTelemetry: {
34867
+ calls: judgeModelCalls,
34868
+ inputTokens: judgeInputTokens,
34869
+ outputTokens: judgeOutputTokens,
34870
+ latencyMs: judgeLatencyMs
34871
+ }
34872
+ } : {},
32576
34873
  // Headline metric bundle computed across the union of all scenario
32577
34874
  // probe logs (more robust than the per-task mean for fraction
32578
34875
  // metrics when scenario sizes vary). `aggregateTaskScores` in
@@ -32581,13 +34878,13 @@ async function runMemCorrectBenchmark(options) {
32581
34878
  }
32582
34879
  },
32583
34880
  cost: {
32584
- totalTokens: 0,
32585
- inputTokens: 0,
32586
- outputTokens: 0,
34881
+ totalTokens,
34882
+ inputTokens: judgeInputTokens,
34883
+ outputTokens: judgeOutputTokens,
32587
34884
  estimatedCostUsd: 0,
32588
34885
  totalLatencyMs,
32589
34886
  meanQueryLatencyMs: tasks.length > 0 ? totalLatencyMs / tasks.length : 0,
32590
- judgeModelCalls: 0
34887
+ judgeModelCalls
32591
34888
  },
32592
34889
  results: {
32593
34890
  tasks,
@@ -34326,10 +36623,20 @@ async function runBenchmark(benchmarkId, options) {
34326
36623
  }
34327
36624
  const primaryCalls = judgeCacheCounters?.modelCalls ?? 0;
34328
36625
  const crossCalls = crossJudgeCacheCounters?.modelCalls ?? 0;
34329
- if (judgeCacheCounters !== void 0 || crossJudgeCacheCounters !== void 0) {
36626
+ if (benchmarkId !== "memcorrect-v1" && (judgeCacheCounters !== void 0 || crossJudgeCacheCounters !== void 0)) {
34330
36627
  result.cost.judgeModelCalls = primaryCalls + crossCalls;
34331
36628
  }
34332
- return finalizeBenchmarkResultConfig(result, options);
36629
+ const finalized = finalizeBenchmarkResultConfig(result, options);
36630
+ assertCompleteBenchmarkResult(finalized);
36631
+ return finalized;
36632
+ }
36633
+ function assertCompleteBenchmarkResult(result) {
36634
+ if (result.meta.status !== "partial") {
36635
+ return;
36636
+ }
36637
+ throw new Error(
36638
+ `Benchmark "${result.meta.benchmark}" produced a partial result: ${result.meta.failureReason ?? "unknown benchmark failure"}`
36639
+ );
34333
36640
  }
34334
36641
  function wrapJudgeWithCache(args) {
34335
36642
  const crossJudgeIdSuffix = args.role === "cross" ? "-cross" : "";
@@ -34788,6 +37095,273 @@ function getBenchmarkLowerIsBetter(benchmarkId) {
34788
37095
  return LOWER_IS_BETTER_BY_BENCHMARK[benchmarkId] ?? /* @__PURE__ */ new Set();
34789
37096
  }
34790
37097
 
37098
+ // src/stats/locomo-profile-delta.ts
37099
+ var LOCOMO_CATEGORY_ORDER = [
37100
+ "single_hop",
37101
+ "multi_hop",
37102
+ "temporal",
37103
+ "open_domain",
37104
+ "adversarial"
37105
+ ];
37106
+ var LOCOMO_TASK_CATEGORY_PATTERN = /-(single_hop|multi_hop|temporal|open_domain|adversarial)$/;
37107
+ function diagnoseLoComoProfileDelta(options) {
37108
+ const maxRegressions = options.maxRegressions ?? 20;
37109
+ if (!Number.isInteger(maxRegressions) || maxRegressions < 0) {
37110
+ throw new Error("maxRegressions must be a non-negative integer.");
37111
+ }
37112
+ assertComparableArtifacts(options.baseline.artifact, options.real.artifact);
37113
+ const joined = joinTasks(options.baseline.artifact, options.real.artifact);
37114
+ const metrics = collectMetrics(joined);
37115
+ const primaryMetric2 = options.primaryMetric ?? "llm_judge";
37116
+ if (!metrics.includes(primaryMetric2)) {
37117
+ throw new Error(
37118
+ `Primary metric ${JSON.stringify(primaryMetric2)} is not present on every joined task.`
37119
+ );
37120
+ }
37121
+ const overall = summarizeMetrics(joined, metrics, joined.length);
37122
+ verifyPublishedMetricMeans(options.baseline.artifact, overall, "baseline");
37123
+ verifyPublishedMetricMeans(options.real.artifact, overall, "real");
37124
+ const categories = [...new Set(joined.map((task) => task.category))].sort(compareLoComoCategories).map((category) => {
37125
+ const tasks = joined.filter((task) => task.category === category);
37126
+ return {
37127
+ category,
37128
+ taskCount: tasks.length,
37129
+ metrics: summarizeMetrics(tasks, metrics, joined.length)
37130
+ };
37131
+ });
37132
+ const topRegressions = joined.map((task) => ({
37133
+ taskId: task.taskId,
37134
+ category: task.category,
37135
+ baselineScore: task.baseline.scores[primaryMetric2],
37136
+ realScore: task.real.scores[primaryMetric2],
37137
+ delta: task.real.scores[primaryMetric2] - task.baseline.scores[primaryMetric2]
37138
+ })).filter((task) => task.delta < 0).sort((left, right) => left.delta - right.delta || left.taskId.localeCompare(right.taskId)).slice(0, maxRegressions);
37139
+ const baselineArtifact = options.baseline.artifact;
37140
+ return {
37141
+ schemaVersion: 1,
37142
+ benchmarkId: "locomo",
37143
+ comparison: {
37144
+ baseline: {
37145
+ reference: options.baseline.reference,
37146
+ sha256: options.baseline.sha256
37147
+ },
37148
+ real: {
37149
+ reference: options.real.reference,
37150
+ sha256: options.real.sha256
37151
+ },
37152
+ datasetVersion: baselineArtifact.datasetVersion,
37153
+ model: baselineArtifact.model,
37154
+ seed: baselineArtifact.seed,
37155
+ gitSha: baselineArtifact.system.gitSha,
37156
+ tier: baselineArtifact.tier ?? "frontier"
37157
+ },
37158
+ taskCount: joined.length,
37159
+ primaryMetric: primaryMetric2,
37160
+ metrics,
37161
+ overall,
37162
+ categories,
37163
+ topRegressions,
37164
+ evidenceBoundary: {
37165
+ scoreDiagnosis: "complete",
37166
+ recallRootCause: "requires-paired-recall-receipts"
37167
+ }
37168
+ };
37169
+ }
37170
+ function renderLoComoProfileDeltaMarkdown(report) {
37171
+ const primary = report.primaryMetric;
37172
+ const lines = [
37173
+ "# LoCoMo runtime-profile score diagnosis",
37174
+ "",
37175
+ `Joined ${report.taskCount} identical task ids from the baseline and real artifacts. The primary diagnostic metric is \`${primary}\` (real minus baseline).`,
37176
+ "",
37177
+ "| Category | Tasks | Baseline | Real | Delta | Aggregate contribution | Wins | Losses | Ties |",
37178
+ "|---|---:|---:|---:|---:|---:|---:|---:|---:|"
37179
+ ];
37180
+ for (const category of report.categories) {
37181
+ const metric = category.metrics[primary];
37182
+ lines.push(
37183
+ `| ${category.category} | ${category.taskCount} | ${formatScore(metric.baselineMean)} | ${formatScore(metric.realMean)} | ${formatSignedScore(metric.delta)} | ${formatSignedScore(metric.aggregateContribution)} | ${metric.wins} | ${metric.losses} | ${metric.ties} |`
37184
+ );
37185
+ }
37186
+ const overall = report.overall[primary];
37187
+ lines.push(
37188
+ `| **Overall** | **${report.taskCount}** | **${formatScore(overall.baselineMean)}** | **${formatScore(overall.realMean)}** | **${formatSignedScore(overall.delta)}** | **${formatSignedScore(overall.aggregateContribution)}** | **${overall.wins}** | **${overall.losses}** | **${overall.ties}** |`,
37189
+ "",
37190
+ "## Highest-priority paired recall samples",
37191
+ "",
37192
+ `These task ids have the largest negative \`${primary}\` deltas. They are candidates for paired recall X-ray capture; score artifacts alone do not identify what recall tier served or what evidence was displaced.`,
37193
+ "",
37194
+ "| Task | Category | Baseline | Real | Delta |",
37195
+ "|---|---|---:|---:|---:|"
37196
+ );
37197
+ for (const task of report.topRegressions) {
37198
+ lines.push(
37199
+ `| ${task.taskId} | ${task.category} | ${formatScore(task.baselineScore)} | ${formatScore(task.realScore)} | ${formatSignedScore(task.delta)} |`
37200
+ );
37201
+ }
37202
+ lines.push(
37203
+ "",
37204
+ "## Evidence boundary",
37205
+ "",
37206
+ "The paired score diagnosis is complete for these artifacts. A recall-side root cause is not established until paired recall receipts exist for the same task ids and runtime profiles.",
37207
+ "",
37208
+ `Baseline: \`${report.comparison.baseline.reference}\` (\`${report.comparison.baseline.sha256}\`)`,
37209
+ "",
37210
+ `Real: \`${report.comparison.real.reference}\` (\`${report.comparison.real.sha256}\`)`,
37211
+ ""
37212
+ );
37213
+ return `${lines.join("\n")}
37214
+ `;
37215
+ }
37216
+ function assertComparableArtifacts(baseline, real) {
37217
+ if (baseline.benchmarkId !== "locomo" || real.benchmarkId !== "locomo") {
37218
+ throw new Error("LoCoMo profile diagnosis requires two locomo artifacts.");
37219
+ }
37220
+ const checks = [
37221
+ ["datasetVersion", baseline.datasetVersion, real.datasetVersion],
37222
+ ["model", baseline.model, real.model],
37223
+ ["seed", baseline.seed, real.seed],
37224
+ ["system.gitSha", baseline.system.gitSha, real.system.gitSha],
37225
+ ["tier", baseline.tier ?? "frontier", real.tier ?? "frontier"]
37226
+ ];
37227
+ for (const [field, baselineValue, realValue] of checks) {
37228
+ if (baselineValue !== realValue) {
37229
+ throw new Error(
37230
+ `Artifacts are not comparable: ${field} differs (${JSON.stringify(baselineValue)} vs ${JSON.stringify(realValue)}).`
37231
+ );
37232
+ }
37233
+ }
37234
+ }
37235
+ function joinTasks(baseline, real) {
37236
+ const baselineTasks = indexTasks(baseline.perTaskScores, "baseline");
37237
+ const realTasks = indexTasks(real.perTaskScores, "real");
37238
+ const missingFromReal = [...baselineTasks.keys()].filter((id) => !realTasks.has(id)).sort();
37239
+ const missingFromBaseline = [...realTasks.keys()].filter((id) => !baselineTasks.has(id)).sort();
37240
+ if (missingFromReal.length > 0 || missingFromBaseline.length > 0) {
37241
+ throw new Error(
37242
+ `Artifacts do not contain identical task-id sets: ${missingFromReal.length} missing from real, ${missingFromBaseline.length} missing from baseline.`
37243
+ );
37244
+ }
37245
+ return [...baselineTasks.keys()].sort().map((taskId) => {
37246
+ const baselineTask = baselineTasks.get(taskId);
37247
+ const realTask = realTasks.get(taskId);
37248
+ const baselineCategory = resolveLoComoCategory(baselineTask);
37249
+ const realCategory = resolveLoComoCategory(realTask);
37250
+ if (baselineCategory !== realCategory) {
37251
+ throw new Error(
37252
+ `Task ${JSON.stringify(taskId)} has mismatched categories (${baselineCategory} vs ${realCategory}).`
37253
+ );
37254
+ }
37255
+ return {
37256
+ taskId,
37257
+ category: baselineCategory,
37258
+ baseline: baselineTask,
37259
+ real: realTask
37260
+ };
37261
+ });
37262
+ }
37263
+ function indexTasks(tasks, label) {
37264
+ const indexed = /* @__PURE__ */ new Map();
37265
+ for (const task of tasks) {
37266
+ if (indexed.has(task.taskId)) {
37267
+ throw new Error(`${label} artifact contains duplicate task id ${JSON.stringify(task.taskId)}.`);
37268
+ }
37269
+ indexed.set(task.taskId, task);
37270
+ }
37271
+ if (indexed.size === 0) {
37272
+ throw new Error(`${label} artifact contains no per-task scores.`);
37273
+ }
37274
+ return indexed;
37275
+ }
37276
+ function resolveLoComoCategory(task) {
37277
+ if (task.category) {
37278
+ return task.category;
37279
+ }
37280
+ const match = task.taskId.match(LOCOMO_TASK_CATEGORY_PATTERN);
37281
+ if (!match?.[1]) {
37282
+ throw new Error(
37283
+ `Cannot derive a LoCoMo category from task id ${JSON.stringify(task.taskId)}.`
37284
+ );
37285
+ }
37286
+ return match[1];
37287
+ }
37288
+ function collectMetrics(joined) {
37289
+ const expected = Object.keys(joined[0].baseline.scores).sort();
37290
+ if (expected.length === 0) {
37291
+ throw new Error("Joined tasks contain no metrics.");
37292
+ }
37293
+ for (const task of joined) {
37294
+ for (const [label, scores] of [
37295
+ ["baseline", task.baseline.scores],
37296
+ ["real", task.real.scores]
37297
+ ]) {
37298
+ const actual = Object.keys(scores).sort();
37299
+ if (actual.length !== expected.length || actual.some((metric, index) => metric !== expected[index])) {
37300
+ throw new Error(
37301
+ `Task ${JSON.stringify(task.taskId)} ${label} metric set does not match the joined metric set.`
37302
+ );
37303
+ }
37304
+ }
37305
+ }
37306
+ return expected;
37307
+ }
37308
+ function summarizeMetrics(tasks, metrics, totalTaskCount) {
37309
+ return Object.fromEntries(metrics.map((metric) => {
37310
+ let baselineSum = 0;
37311
+ let realSum = 0;
37312
+ let wins = 0;
37313
+ let losses = 0;
37314
+ let ties = 0;
37315
+ for (const task of tasks) {
37316
+ const baselineScore = task.baseline.scores[metric];
37317
+ const realScore = task.real.scores[metric];
37318
+ baselineSum += baselineScore;
37319
+ realSum += realScore;
37320
+ if (realScore > baselineScore) wins += 1;
37321
+ else if (realScore < baselineScore) losses += 1;
37322
+ else ties += 1;
37323
+ }
37324
+ const deltaSum = realSum - baselineSum;
37325
+ return [metric, {
37326
+ baselineMean: baselineSum / tasks.length,
37327
+ realMean: realSum / tasks.length,
37328
+ delta: deltaSum / tasks.length,
37329
+ aggregateContribution: deltaSum / totalTaskCount,
37330
+ wins,
37331
+ losses,
37332
+ ties
37333
+ }];
37334
+ }));
37335
+ }
37336
+ function verifyPublishedMetricMeans(artifact, overall, side) {
37337
+ for (const [metric, summary] of Object.entries(overall)) {
37338
+ const published = artifact.metrics[metric];
37339
+ if (published === void 0) {
37340
+ throw new Error(`${side} artifact does not publish aggregate metric ${JSON.stringify(metric)}.`);
37341
+ }
37342
+ const computed = side === "baseline" ? summary.baselineMean : summary.realMean;
37343
+ if (Math.abs(published - computed) > 1e-12) {
37344
+ throw new Error(
37345
+ `${side} artifact aggregate ${metric}=${published} does not match its per-task mean ${computed}.`
37346
+ );
37347
+ }
37348
+ }
37349
+ }
37350
+ function compareLoComoCategories(left, right) {
37351
+ const leftIndex = LOCOMO_CATEGORY_ORDER.indexOf(left);
37352
+ const rightIndex = LOCOMO_CATEGORY_ORDER.indexOf(right);
37353
+ if (leftIndex >= 0 && rightIndex >= 0) return leftIndex - rightIndex;
37354
+ if (leftIndex >= 0) return -1;
37355
+ if (rightIndex >= 0) return 1;
37356
+ return left.localeCompare(right);
37357
+ }
37358
+ function formatScore(value) {
37359
+ return value.toFixed(4);
37360
+ }
37361
+ function formatSignedScore(value) {
37362
+ return `${value >= 0 ? "+" : ""}${formatScore(value)}`;
37363
+ }
37364
+
34791
37365
  // src/integrity/sealed-qrels.ts
34792
37366
  import { readFile as readFile19 } from "fs/promises";
34793
37367
  function isSealedQrelsArtifact(value) {
@@ -36196,6 +38770,8 @@ import { mkdir as mkdir17, readFile as readFile21, rename as rename3, unlink as
36196
38770
  import path36 from "path";
36197
38771
 
36198
38772
  // src/judges/cohen-kappa.ts
38773
+ var DEFAULT_KAPPA_BOOTSTRAP_SAMPLES = 2e3;
38774
+ var DEFAULT_KAPPA_CONFIDENCE_LEVEL = 0.95;
36199
38775
  function computeCohensKappa(raterA, raterB) {
36200
38776
  if (raterA.length !== raterB.length) {
36201
38777
  throw new Error(
@@ -36241,6 +38817,74 @@ function computeCohensKappa(raterA, raterB) {
36241
38817
  categories: [...categories].sort()
36242
38818
  };
36243
38819
  }
38820
+ function bootstrapCohensKappaConfidenceInterval(raterA, raterB, options = {}) {
38821
+ if (raterA.length !== raterB.length) {
38822
+ throw new Error(
38823
+ `bootstrapCohensKappaConfidenceInterval: rater arrays must have equal length; got ${raterA.length} and ${raterB.length}.`
38824
+ );
38825
+ }
38826
+ if (raterA.length === 0) {
38827
+ throw new Error("bootstrapCohensKappaConfidenceInterval: cannot bootstrap zero paired judgements.");
38828
+ }
38829
+ const iterations = options.iterations ?? DEFAULT_KAPPA_BOOTSTRAP_SAMPLES;
38830
+ const level = options.level ?? DEFAULT_KAPPA_CONFIDENCE_LEVEL;
38831
+ if (!Number.isInteger(iterations) || iterations <= 0) {
38832
+ throw new Error(`bootstrapCohensKappaConfidenceInterval: iterations must be a positive integer; got ${String(iterations)}.`);
38833
+ }
38834
+ if (!(level > 0 && level < 1)) {
38835
+ throw new Error(`bootstrapCohensKappaConfidenceInterval: level must be between 0 and 1; got ${String(level)}.`);
38836
+ }
38837
+ const derivedSeed = options.seed ?? hashLabelsToSeed(raterA, raterB);
38838
+ const random = mulberry323(derivedSeed >>> 0);
38839
+ const kappas = [];
38840
+ for (let iteration = 0; iteration < iterations; iteration += 1) {
38841
+ const sampleA = [];
38842
+ const sampleB = [];
38843
+ for (let index = 0; index < raterA.length; index += 1) {
38844
+ const picked = Math.floor(random() * raterA.length);
38845
+ sampleA.push(raterA[picked]);
38846
+ sampleB.push(raterB[picked]);
38847
+ }
38848
+ kappas.push(computeCohensKappa(sampleA, sampleB).kappa);
38849
+ }
38850
+ kappas.sort((left, right) => left - right);
38851
+ const tail = (1 - level) / 2;
38852
+ return {
38853
+ confidenceInterval: {
38854
+ lower: percentile2(kappas, tail),
38855
+ upper: percentile2(kappas, 1 - tail),
38856
+ level
38857
+ },
38858
+ bootstrapSamples: iterations
38859
+ };
38860
+ }
38861
+ function percentile2(sortedValues, fraction) {
38862
+ const position = (sortedValues.length - 1) * fraction;
38863
+ const lowerIndex = Math.floor(position);
38864
+ const upperIndex = Math.ceil(position);
38865
+ const lower = sortedValues[lowerIndex];
38866
+ const upper = sortedValues[upperIndex];
38867
+ return lowerIndex === upperIndex ? lower : lower + (upper - lower) * (position - lowerIndex);
38868
+ }
38869
+ function hashLabelsToSeed(raterA, raterB) {
38870
+ const value = JSON.stringify([raterA, raterB]);
38871
+ let hash = 2166136261;
38872
+ for (let index = 0; index < value.length; index += 1) {
38873
+ hash ^= value.charCodeAt(index);
38874
+ hash = Math.imul(hash, 16777619);
38875
+ }
38876
+ return hash >>> 0;
38877
+ }
38878
+ function mulberry323(seed) {
38879
+ let state = seed;
38880
+ return () => {
38881
+ state = state + 1831565813 >>> 0;
38882
+ let value = state;
38883
+ value = Math.imul(value ^ value >>> 15, value | 1);
38884
+ value ^= value + Math.imul(value ^ value >>> 7, value | 61);
38885
+ return ((value ^ value >>> 14) >>> 0) / 4294967296;
38886
+ };
38887
+ }
36244
38888
  var DEFAULT_JUDGE_BINARIZATION_THRESHOLD = 0.5;
36245
38889
  function binarizeJudgeScore(score, threshold = DEFAULT_JUDGE_BINARIZATION_THRESHOLD) {
36246
38890
  if (typeof score !== "number" || !Number.isFinite(score)) {
@@ -36250,7 +38894,7 @@ function binarizeJudgeScore(score, threshold = DEFAULT_JUDGE_BINARIZATION_THRESH
36250
38894
  }
36251
38895
 
36252
38896
  // src/judges/calibration-slice.ts
36253
- var CALIBRATION_SLICE_SIZE = 50;
38897
+ var CALIBRATION_SLICE_SIZE = 200;
36254
38898
  var MIN_CALIBRATION_SOURCE_TASKS = 10;
36255
38899
  var JUDGE_CALIBRATION_KAPPA_THRESHOLD = 0.7;
36256
38900
  function selectCalibrationSlice(questionIds, size = CALIBRATION_SLICE_SIZE) {
@@ -36274,10 +38918,8 @@ async function runJudgeCalibration(options) {
36274
38918
  const binScore = options.binScore ?? ((score) => binarizeJudgeScore(score));
36275
38919
  const threshold = options.threshold ?? JUDGE_CALIBRATION_KAPPA_THRESHOLD;
36276
38920
  const sliceSize = options.sliceSize ?? CALIBRATION_SLICE_SIZE;
36277
- const sliceIds = selectCalibrationSlice(
36278
- options.answers.map((answer) => answer.questionId),
36279
- sliceSize
36280
- );
38921
+ const availableIds = new Set(options.answers.map((answer) => answer.questionId));
38922
+ const sliceIds = options.pinnedQuestionIds ? validatePinnedQuestionIds(options.pinnedQuestionIds, availableIds) : selectCalibrationSlice([...availableIds], sliceSize);
36281
38923
  const sliceIdSet = new Set(sliceIds);
36282
38924
  const answerById = /* @__PURE__ */ new Map();
36283
38925
  for (const answer of options.answers) {
@@ -36286,6 +38928,12 @@ async function runJudgeCalibration(options) {
36286
38928
  }
36287
38929
  }
36288
38930
  const sliceAnswers = sliceIds.map((id) => answerById.get(id)).filter((answer) => answer !== void 0);
38931
+ const answerSetHash = hashCalibrationAnswerSet(sliceAnswers);
38932
+ if (options.expectedAnswerSetHash !== void 0 && answerSetHash !== options.expectedAnswerSetHash) {
38933
+ throw new Error(
38934
+ `runJudgeCalibration: pinned answer set changed (expected sha256:${options.expectedAnswerSetHash}, got sha256:${answerSetHash}). Restore the original stored result or intentionally reset calibration state.`
38935
+ );
38936
+ }
36289
38937
  const localLabels = [];
36290
38938
  const frontierLabels = [];
36291
38939
  const verdicts = [];
@@ -36311,6 +38959,10 @@ async function runJudgeCalibration(options) {
36311
38959
  });
36312
38960
  }
36313
38961
  const kappaResult = computeCohensKappa(localLabels, frontierLabels);
38962
+ const bootstrap = bootstrapCohensKappaConfidenceInterval(localLabels, frontierLabels, {
38963
+ iterations: options.bootstrapSamples ?? DEFAULT_KAPPA_BOOTSTRAP_SAMPLES,
38964
+ level: options.confidenceLevel
38965
+ });
36314
38966
  const warning = kappaResult.kappa < threshold;
36315
38967
  return {
36316
38968
  ...kappaResult,
@@ -36318,16 +38970,42 @@ async function runJudgeCalibration(options) {
36318
38970
  sliceQuestionIds: sliceIds,
36319
38971
  threshold,
36320
38972
  warning,
38973
+ confidenceInterval: bootstrap.confidenceInterval,
38974
+ bootstrapSamples: bootstrap.bootstrapSamples,
38975
+ answerSetHash,
36321
38976
  verdicts
36322
38977
  };
36323
38978
  }
36324
- async function writeJudgeCalibrationState(result, calibrationDir, identities) {
38979
+ function validatePinnedQuestionIds(ids, availableIds) {
38980
+ if (ids.length === 0 || ids.length > CALIBRATION_SLICE_SIZE || ids.some((id) => typeof id !== "string" || id.length === 0) || new Set(ids).size !== ids.length) {
38981
+ throw new Error(`runJudgeCalibration: pinned question ids must contain 1 to ${CALIBRATION_SLICE_SIZE} unique non-empty strings.`);
38982
+ }
38983
+ const missing = ids.filter((id) => !availableIds.has(id));
38984
+ if (missing.length > 0) {
38985
+ throw new Error(`runJudgeCalibration: pinned answer set is missing ${missing.length} question id(s), including "${missing[0]}".`);
38986
+ }
38987
+ return [...ids];
38988
+ }
38989
+ function hashCalibrationAnswerSet(answers) {
38990
+ return createHash12("sha256").update(JSON.stringify(answers.map((answer) => [
38991
+ answer.questionId,
38992
+ answer.question,
38993
+ answer.predicted,
38994
+ answer.expected
38995
+ ]))).digest("hex");
38996
+ }
38997
+ async function writeJudgeCalibrationState(result, calibrationDir, identities, provenance) {
36325
38998
  await mkdir17(calibrationDir, { recursive: true });
36326
38999
  const state = {
36327
39000
  kappa: result.kappa,
36328
39001
  sampleSize: result.sampleSize,
36329
39002
  threshold: result.threshold,
36330
39003
  warning: result.warning,
39004
+ confidenceInterval: result.confidenceInterval,
39005
+ bootstrapSamples: result.bootstrapSamples,
39006
+ answerSetHash: result.answerSetHash,
39007
+ sliceQuestionIds: result.sliceQuestionIds,
39008
+ ...provenance ? provenance : {},
36331
39009
  ...identities ? identities : {}
36332
39010
  };
36333
39011
  const filePath = path36.join(calibrationDir, `${sanitizeCalibrationSegment(result.benchmarkId)}.json`);
@@ -36368,6 +39046,19 @@ async function loadJudgeCalibrationState(benchmarkId, calibrationDir) {
36368
39046
  return void 0;
36369
39047
  }
36370
39048
  const loaded = { kappa, sampleSize, threshold, warning };
39049
+ const confidenceInterval = record.confidenceInterval;
39050
+ const bootstrapSamples = record.bootstrapSamples;
39051
+ if (confidenceInterval && typeof confidenceInterval === "object" && !Array.isArray(confidenceInterval) && typeof confidenceInterval.lower === "number" && Number.isFinite(confidenceInterval.lower) && typeof confidenceInterval.upper === "number" && Number.isFinite(confidenceInterval.upper) && typeof confidenceInterval.level === "number" && Number.isFinite(confidenceInterval.level) && typeof bootstrapSamples === "number" && Number.isInteger(bootstrapSamples) && bootstrapSamples > 0) {
39052
+ loaded.confidenceInterval = confidenceInterval;
39053
+ loaded.bootstrapSamples = bootstrapSamples;
39054
+ }
39055
+ const sourceResultId = record.sourceResultId;
39056
+ const answerSetHash = record.answerSetHash;
39057
+ if (typeof sourceResultId === "string" && sourceResultId.length > 0 && typeof answerSetHash === "string" && /^[0-9a-f]{64}$/.test(answerSetHash) && Array.isArray(record.sliceQuestionIds) && record.sliceQuestionIds.length > 0 && record.sliceQuestionIds.length <= CALIBRATION_SLICE_SIZE && record.sliceQuestionIds.length === sampleSize && record.sliceQuestionIds.every((id) => typeof id === "string" && id.length > 0) && new Set(record.sliceQuestionIds).size === record.sliceQuestionIds.length) {
39058
+ loaded.sourceResultId = sourceResultId;
39059
+ loaded.answerSetHash = answerSetHash;
39060
+ loaded.sliceQuestionIds = record.sliceQuestionIds;
39061
+ }
36371
39062
  const identityKeys = [
36372
39063
  "localJudgeProvider",
36373
39064
  "localJudgeModel",
@@ -37916,7 +40607,7 @@ function captureMachineFingerprint() {
37916
40607
  totalMemoryMb: Math.round(os11.totalmem() / (1024 * 1024))
37917
40608
  };
37918
40609
  }
37919
- function percentile2(sorted, p) {
40610
+ function percentile3(sorted, p) {
37920
40611
  if (sorted.length === 0) return 0;
37921
40612
  if (sorted.length === 1) return sorted[0];
37922
40613
  const idx = Math.ceil(p / 100 * sorted.length) - 1;
@@ -37925,8 +40616,8 @@ function percentile2(sorted, p) {
37925
40616
  function computeMicroMetric(samplesMs) {
37926
40617
  const sorted = [...samplesMs].sort((a, b) => a - b);
37927
40618
  return {
37928
- p50: percentile2(sorted, 50),
37929
- p95: percentile2(sorted, 95),
40619
+ p50: percentile3(sorted, 50),
40620
+ p95: percentile3(sorted, 95),
37930
40621
  iterations: samplesMs.length,
37931
40622
  samplesMs
37932
40623
  };
@@ -37993,7 +40684,7 @@ async function runCodingGraphBenchmark(config = {}) {
37993
40684
  fullIndexSamples.push(fi.ms);
37994
40685
  if (sampleStore !== store) await sampleStore.close();
37995
40686
  }
37996
- const fullIndexMsValue = percentile2(
40687
+ const fullIndexMsValue = percentile3(
37997
40688
  [...fullIndexSamples].sort((a, b) => a - b),
37998
40689
  50
37999
40690
  );
@@ -38338,7 +41029,11 @@ export {
38338
41029
  DEFAULT_ASSISTANT_RUBRIC_ID,
38339
41030
  DEFAULT_BASELINE_SCENARIOS,
38340
41031
  DEFAULT_JUDGE_BINARIZATION_THRESHOLD,
41032
+ DEFAULT_KAPPA_BOOTSTRAP_SAMPLES,
41033
+ DEFAULT_KAPPA_CONFIDENCE_LEVEL,
41034
+ DEFAULT_OPENAI_RESPONSES_JUDGE_MODEL,
38341
41035
  EMPTY_CONTAMINATION_MANIFEST,
41036
+ GENERAL_ANSWER_JUDGE_RUBRIC,
38342
41037
  INTEGRITY_CIPHER_ALGORITHM,
38343
41038
  INTEGRITY_HASH_ALGORITHM,
38344
41039
  INTEGRITY_META_FIELDS,
@@ -38348,13 +41043,21 @@ export {
38348
41043
  LONG_MEM_EVAL_DATASET_FILENAMES,
38349
41044
  LettaMemCorrectAdapter,
38350
41045
  LocalLabPreflightError,
41046
+ MEMCORRECT_CORRECTION_ACCEPTANCE_RUBRIC,
41047
+ MEMCORRECT_CORRECTION_ACCEPTANCE_RUBRIC_VERSION,
41048
+ MEMCORRECT_STALE_HARM_RUBRIC,
41049
+ MEMCORRECT_STALE_HARM_RUBRIC_VERSION,
38351
41050
  MEMORY_EVAL_DIMENSIONS,
38352
41051
  MEMORY_EVAL_PUBLIC_LINE,
38353
41052
  MIN_CALIBRATION_SOURCE_TASKS,
38354
41053
  MITIGATED_BASELINE_SCENARIOS,
41054
+ McpMemoryBackendError,
38355
41055
  Mem0MemCorrectAdapter,
38356
41056
  MissingCredentialError,
41057
+ OPENAI_RESPONSES_JUDGE_RUBRIC_VERSION,
38357
41058
  OTHER_NAMESPACE_MEMORIES,
41059
+ OpenAiResponsesJudgeError,
41060
+ OpenAiResponsesProvider,
38358
41061
  PROCEDURAL_REAL_SCENARIOS,
38359
41062
  PROCEDURAL_REAL_SCENARIOS_SMOKE,
38360
41063
  PUBLISHED_BENCHMARK_ARTIFACT_IDS,
@@ -38378,6 +41081,7 @@ export {
38378
41081
  assistantSynthesisDefinition,
38379
41082
  backlinkF1,
38380
41083
  binarizeJudgeScore,
41084
+ bootstrapCohensKappaConfidenceInterval,
38381
41085
  bootstrapMeanConfidenceInterval,
38382
41086
  buildAmaBenchDiagnosticMatrixArtifact,
38383
41087
  buildAmaBenchDiagnosticVariantSummary,
@@ -38417,9 +41121,15 @@ export {
38417
41121
  createLightweightAdapter,
38418
41122
  createLiteLlmProvider,
38419
41123
  createLocalLlmProvider,
41124
+ createMcpDemoMemCorrectAdapter,
41125
+ createMcpDemoMemoryAdapter,
41126
+ createMcpMemCorrectAdapter,
41127
+ createMcpMemoryAdapter,
38420
41128
  createMitigatedTarget,
38421
41129
  createOllamaProvider,
38422
41130
  createOpenAiCompatibleProvider,
41131
+ createOpenAiResponsesBenchJudge,
41132
+ createOpenAiResponsesProvider,
38423
41133
  createSeededRandom as createProceduralAblationSeededRandom,
38424
41134
  createProvider,
38425
41135
  createProviderBackedAmaBenchRecommendedJudge,
@@ -38437,6 +41147,7 @@ export {
38437
41147
  defaultBenchmarkBaselineDir,
38438
41148
  defaultBenchmarkPublishPath,
38439
41149
  deleteBenchmarkResults,
41150
+ diagnoseLoComoProfileDelta,
38440
41151
  discoverAllProviders,
38441
41152
  discoveryEndpointFor,
38442
41153
  emailFixture,
@@ -38466,6 +41177,8 @@ export {
38466
41177
  isContaminationManifest,
38467
41178
  isSealedQrelsArtifact,
38468
41179
  isSha256Hex,
41180
+ judgeMemCorrectCorrectionAcceptance,
41181
+ judgeMemCorrectStaleMemoryHarm,
38469
41182
  linkMatches,
38470
41183
  listBenchmarkBaselines,
38471
41184
  listBenchmarkResults,
@@ -38479,6 +41192,7 @@ export {
38479
41192
  loadBeamDatasetPreview,
38480
41193
  loadBenchmarkArtifact,
38481
41194
  loadBenchmarkBaseline,
41195
+ loadBenchmarkReportCardProvenance,
38482
41196
  loadBenchmarkResult,
38483
41197
  loadCustomBenchmarkFile,
38484
41198
  loadJudgeCalibrationState,
@@ -38506,6 +41220,7 @@ export {
38506
41220
  redactBenchmarkResultSecrets,
38507
41221
  renderBaselineMarkdown,
38508
41222
  renderBenchmarkResultExport,
41223
+ renderLoComoProfileDeltaMarkdown,
38509
41224
  renderMemorySummaryForJudge,
38510
41225
  renderMemoryViewForAgent,
38511
41226
  resolveAssistantAgent,