@tangle-network/agent-knowledge 4.1.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -64,6 +64,7 @@ import {
64
64
  agentMemorySequenceJudge,
65
65
  applyRetrievalHoldout,
66
66
  applySessionStickyRetrievalHoldout,
67
+ assertImmutableRef,
67
68
  buildAgentMemorySequenceScenarios,
68
69
  buildAgentMemorySequencesFromBenchmarkCases,
69
70
  createAgentMemoryBranch,
@@ -75,14 +76,18 @@ import {
75
76
  emitRetrievalHoldoutBypass,
76
77
  forkAgentMemoryBranchSnapshot,
77
78
  graphitiMemoryAdapterIdentity,
79
+ jsonCandidateCodec,
80
+ jsonObjectCandidateCodec,
78
81
  mem0MemoryAdapterIdentity,
79
82
  renderMemoryContext,
80
83
  resetRetrievalHoldoutRegistry,
81
84
  retrievalHoldoutConfigHash,
82
85
  runAgentMemoryExperiment,
83
86
  runAgentMemoryImprovement,
87
+ runSerializedKnowledgeOptimization,
88
+ scenarioContentFingerprint,
84
89
  toOffPolicyTrajectory
85
- } from "./chunk-CPMLJYA3.js";
90
+ } from "./chunk-LMR53POQ.js";
86
91
  import {
87
92
  IRS_DIMENSION_HINTS,
88
93
  MAX_RESPONSE_BYTES,
@@ -112,7 +117,6 @@ import {
112
117
  buildKnowledgeBenchmarkScenarios,
113
118
  buildRetrievalBenchmarkCasesFromQrels,
114
119
  buildRetrievalEvalDispatch,
115
- buildRetrievalParameterCandidates,
116
120
  createInMemoryBenchmarkAdapter,
117
121
  createMemoryExecutionPool,
118
122
  createNoopMemoryBenchmarkAdapter,
@@ -123,24 +127,23 @@ import {
123
127
  memoryWriteResultToSourceRecord,
124
128
  parseKnowledgeBenchmarkJsonl,
125
129
  parseKnowledgeBenchmarkQrels,
130
+ partitionRetrievalScenarios,
126
131
  renderKnowledgeBenchmarkReportMarkdown,
127
132
  resolveMemoryCleanupTimeoutMs,
128
133
  respondToIndustryMemoryBenchmarkSmokeCase,
129
134
  respondToIndustryRagBenchmarkSmokeCase,
130
135
  retrievalConfigFromSurface,
131
136
  retrievalConfigSurface,
132
- retrievalParameterSweepProposer,
133
137
  retrievalRecallJudge,
134
138
  runBoundedMemoryLifecycle,
135
139
  runKnowledgeBenchmarkSuite,
136
140
  runMemoryAdapterBenchmark,
137
- runRetrievalImprovementLoop,
138
141
  scoreKnowledgeBenchmarkArtifact,
139
142
  scoreMemoryBenchmarkArtifact,
140
143
  scoreRetrievalArtifact,
141
144
  sleepForMemoryRecovery,
142
145
  summarizeKnowledgeBenchmarkCampaign
143
- } from "./chunk-BBCIB4UL.js";
146
+ } from "./chunk-EYIA5PLQ.js";
144
147
  import {
145
148
  reciprocalRankFusion,
146
149
  searchKnowledge,
@@ -691,6 +694,7 @@ import {
691
694
  } from "@tangle-network/agent-interface";
692
695
  import { z } from "zod";
693
696
  var digestSchema = z.string().regex(/^[a-f0-9]{64}$/);
697
+ var immutableRefSchema = z.string().regex(/^(?:sha256:[a-f0-9]{64}|git:[a-f0-9]{40})$/);
694
698
  var runIdSchema = z.string().min(1).max(2048);
695
699
  var safePathSegmentSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/);
696
700
  var knowledgeImprovementMutationReceiptSchema = z.object({
@@ -770,6 +774,7 @@ var candidateRecordSchema = z.object({
770
774
  candidateHash: digestSchema.optional(),
771
775
  evidenceHash: digestSchema.optional(),
772
776
  promotionPlanHash: digestSchema.optional(),
777
+ finalEvaluationStartedAt: z.iso.datetime().optional(),
773
778
  status: improvementStatusSchema,
774
779
  createdAt: z.iso.datetime(),
775
780
  updatedAt: z.iso.datetime()
@@ -778,6 +783,7 @@ var KnowledgeImprovementRunStateSchema = z.object({
778
783
  runId: runIdSchema,
779
784
  root: z.string().min(1),
780
785
  goal: z.string().min(1),
786
+ implementationRef: immutableRefSchema,
781
787
  status: improvementStatusSchema,
782
788
  baseHash: digestSchema,
783
789
  createdAt: z.iso.datetime(),
@@ -874,6 +880,7 @@ var KnowledgeImprovementEvidenceSchema = z.object({
874
880
  candidateId: safePathSegmentSchema,
875
881
  iteration: z.number().int().positive(),
876
882
  goalHash: digestSchema,
883
+ implementationRef: immutableRefSchema,
877
884
  baseHash: digestSchema,
878
885
  candidateHash: digestSchema,
879
886
  promotionPlanHash: digestSchema,
@@ -899,6 +906,7 @@ var UPDATE_PHASES = [
899
906
  "knowledge-update"
900
907
  ];
901
908
  var EVALUATION_PHASES = [
909
+ "rag-optimization",
902
910
  "retrieval-tuning",
903
911
  "gap-diagnosis",
904
912
  "answer-quality",
@@ -1279,7 +1287,60 @@ function immutableJsonValue(value) {
1279
1287
 
1280
1288
  // src/kb-improvement/evaluation.ts
1281
1289
  import { join as join3 } from "path";
1282
- import { contentHash as contentHash5 } from "@tangle-network/agent-eval";
1290
+ import { contentHash as contentHash6 } from "@tangle-network/agent-eval";
1291
+
1292
+ // src/rag-answer-evidence.ts
1293
+ function assertRagAnswerEvidence(result) {
1294
+ if (!result || typeof result !== "object") {
1295
+ throw new Error("answer-quality evidence must be an object");
1296
+ }
1297
+ if (typeof result.passed !== "boolean") {
1298
+ throw new Error("answer-quality evidence requires a boolean passed verdict");
1299
+ }
1300
+ assertImmutableRef(result.datasetRef, "answer-quality datasetRef");
1301
+ assertImmutableRef(result.evaluatorRef, "answer-quality evaluatorRef");
1302
+ if (!Array.isArray(result.finalScenarioIds) || result.finalScenarioIds.length < 2 || new Set(result.finalScenarioIds).size !== result.finalScenarioIds.length || result.finalScenarioIds.some((id) => typeof id !== "string" || id.trim().length === 0)) {
1303
+ throw new Error("answer-quality evidence requires at least 2 unique final scenario IDs");
1304
+ }
1305
+ if (!result.metrics || typeof result.metrics !== "object" || Array.isArray(result.metrics)) {
1306
+ throw new Error("answer-quality evidence requires non-empty finite metrics");
1307
+ }
1308
+ const metrics = Object.entries(result.metrics);
1309
+ if (metrics.length === 0 || metrics.some(([key, value]) => key.trim().length === 0 || !Number.isFinite(value))) {
1310
+ throw new Error("answer-quality evidence requires non-empty finite metrics");
1311
+ }
1312
+ if (!result.cost || typeof result.cost !== "object") {
1313
+ throw new Error("answer-quality evidence requires cost accounting");
1314
+ }
1315
+ if (!Number.isFinite(result.cost.totalCostUsd) || result.cost.totalCostUsd < 0 || typeof result.cost.accountingComplete !== "boolean" || !Array.isArray(result.cost.incompleteReasons) || result.cost.incompleteReasons.some((reason) => typeof reason !== "string" || !reason.trim())) {
1316
+ throw new Error("answer-quality evidence has invalid cost accounting");
1317
+ }
1318
+ if (result.cost.accountingComplete && result.cost.incompleteReasons.length > 0) {
1319
+ throw new Error("complete answer-quality cost accounting cannot include incomplete reasons");
1320
+ }
1321
+ }
1322
+ function ragAnswerEvidenceRejectionReasons(evidence, costCeiling) {
1323
+ const reasons = [];
1324
+ if (!evidence.passed) reasons.push("answer-quality evaluation failed");
1325
+ if (!evidence.cost.accountingComplete) {
1326
+ reasons.push("answer-quality final comparison has incomplete cost accounting");
1327
+ }
1328
+ if (costCeiling === void 0) {
1329
+ reasons.push("answer-quality promotion requires answerQualityCostCeiling");
1330
+ } else if (exceedsCostCeiling(evidence.cost.totalCostUsd, costCeiling)) {
1331
+ reasons.push(
1332
+ `answer-quality final comparison cost ${evidence.cost.totalCostUsd} exceeds ${costCeiling}`
1333
+ );
1334
+ }
1335
+ return reasons;
1336
+ }
1337
+ function exceedsCostCeiling(totalCostUsd, costCeiling) {
1338
+ const tolerance = Number.EPSILON * Math.max(1, Math.abs(totalCostUsd), Math.abs(costCeiling)) * 8;
1339
+ return totalCostUsd - costCeiling > tolerance;
1340
+ }
1341
+
1342
+ // src/rag-eval/calibration.ts
1343
+ import { contentHash as contentHash3 } from "@tangle-network/agent-eval";
1283
1344
 
1284
1345
  // src/claim-grounding.ts
1285
1346
  var citedClaimKey = "citedClaim";
@@ -1911,6 +1972,12 @@ function below(metric, threshold) {
1911
1972
 
1912
1973
  // src/rag-eval/calibration.ts
1913
1974
  function createRagAnswerQualityHook(options) {
1975
+ assertImmutableRef(options.evaluatorRef, "RAG answer evaluatorRef");
1976
+ const finalScenarioIds = options.scenarios.map((scenario) => scenario.id);
1977
+ if (finalScenarioIds.length < 2 || new Set(finalScenarioIds).size !== finalScenarioIds.length || finalScenarioIds.some((id) => !id.trim())) {
1978
+ throw new Error("RAG answer quality requires at least 2 unique final scenarios");
1979
+ }
1980
+ const datasetRef = `sha256:${contentHash3(options.scenarios)}`;
1914
1981
  return async () => {
1915
1982
  const summaries = [];
1916
1983
  const findings = [];
@@ -1932,9 +1999,14 @@ function createRagAnswerQualityHook(options) {
1932
1999
  findings.push(...summary.findings);
1933
2000
  }
1934
2001
  const metrics = aggregateRagAnswerMetrics(summaries);
2002
+ const cost = typeof options.cost === "function" ? await options.cost() : structuredClone(options.cost);
1935
2003
  return {
1936
2004
  passed: findings.length === 0,
1937
2005
  metrics,
2006
+ finalScenarioIds,
2007
+ datasetRef,
2008
+ evaluatorRef: options.evaluatorRef,
2009
+ cost,
1938
2010
  findings,
1939
2011
  metadata: { scenarioCount: options.scenarios.length }
1940
2012
  };
@@ -2034,6 +2106,46 @@ function sourceRefs(text) {
2034
2106
  return refs;
2035
2107
  }
2036
2108
 
2109
+ // src/rag-optimization.ts
2110
+ async function runRagOptimization(options) {
2111
+ const {
2112
+ baseline,
2113
+ method,
2114
+ trainScenarios,
2115
+ selectionScenarios,
2116
+ finalScenarios,
2117
+ run,
2118
+ judges,
2119
+ ...runOptions
2120
+ } = options;
2121
+ const result = await runSerializedKnowledgeOptimization({
2122
+ ...runOptions,
2123
+ baseline,
2124
+ method,
2125
+ trainScenarios,
2126
+ selectionScenarios,
2127
+ finalScenarios,
2128
+ codec: jsonObjectCandidateCodec(),
2129
+ judges: [...judges ?? [ragAnswerQualityJudge()]],
2130
+ scenarioFingerprint: scenarioContentFingerprint,
2131
+ dispatchCandidate: ({ candidate, candidateSurface, candidateSurfaceHash, scenario, context }) => run({
2132
+ config: candidate,
2133
+ configSurface: candidateSurface,
2134
+ configSurfaceHash: candidateSurfaceHash,
2135
+ scenario,
2136
+ context
2137
+ })
2138
+ });
2139
+ return {
2140
+ ...result,
2141
+ baselineConfig: result.baseline.value,
2142
+ winnerConfig: result.winner.value,
2143
+ trainScenarios: [...trainScenarios],
2144
+ selectionScenarios: [...selectionScenarios],
2145
+ finalScenarios: [...finalScenarios]
2146
+ };
2147
+ }
2148
+
2037
2149
  // src/research-loop.ts
2038
2150
  import {
2039
2151
  blockingKnowledgeEval,
@@ -2347,33 +2459,74 @@ async function applyKnowledgeResearchDecision(options, decision, iteration = 1)
2347
2459
  };
2348
2460
  }
2349
2461
 
2462
+ // src/retrieval-optimization.ts
2463
+ import { canonicalJson as canonicalJson3 } from "@tangle-network/agent-eval";
2464
+ import {
2465
+ surfaceHash
2466
+ } from "@tangle-network/agent-eval/campaign";
2467
+ async function runRetrievalImprovementLoop(options) {
2468
+ const dispatch = buildRetrievalEvalDispatch({
2469
+ index: options.index,
2470
+ defaultK: options.defaultK,
2471
+ retrieve: options.retrieve
2472
+ });
2473
+ const {
2474
+ baseline,
2475
+ trainScenarios,
2476
+ selectionScenarios,
2477
+ finalScenarios,
2478
+ method,
2479
+ index: _index,
2480
+ defaultK: _defaultK,
2481
+ retrieve: _retrieve,
2482
+ judges,
2483
+ metricWeights,
2484
+ ...runOptions
2485
+ } = options;
2486
+ const result = await runSerializedKnowledgeOptimization({
2487
+ ...runOptions,
2488
+ baseline,
2489
+ method,
2490
+ trainScenarios,
2491
+ selectionScenarios,
2492
+ finalScenarios,
2493
+ codec: jsonObjectCandidateCodec(),
2494
+ judges: [...judges ?? [retrievalRecallJudge({ weights: metricWeights })]],
2495
+ scenarioFingerprint: retrievalScenarioFingerprint,
2496
+ dispatchCandidate: ({ candidateSurface, scenario, context }) => dispatch(candidateSurface, scenario, context)
2497
+ });
2498
+ return {
2499
+ ...result,
2500
+ baselineConfig: result.baseline.value,
2501
+ winnerConfig: result.winner.value,
2502
+ trainScenarios: [...trainScenarios],
2503
+ selectionScenarios: [...selectionScenarios],
2504
+ finalScenarios: [...finalScenarios]
2505
+ };
2506
+ }
2507
+ function retrievalScenarioFingerprint(scenario) {
2508
+ return surfaceHash(
2509
+ canonicalJson3({
2510
+ query: scenario.query,
2511
+ expected: scenario.expected,
2512
+ k: scenario.k ?? null
2513
+ })
2514
+ );
2515
+ }
2516
+
2350
2517
  // src/rag-improvement-loop.ts
2351
2518
  async function runRagKnowledgeImprovementLoop(options) {
2352
2519
  assertConfiguredRequiredPhases(options);
2520
+ assertOptionalCostCeiling(options.answerQualityCostCeiling, "answerQualityCostCeiling");
2353
2521
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
2354
2522
  const phases = [];
2523
+ let optimization;
2355
2524
  let retrieval;
2356
2525
  let findings = [];
2357
2526
  let acquisition;
2358
2527
  let knowledgeUpdate;
2359
2528
  let answerQuality;
2360
2529
  let promotion;
2361
- if (phaseEnabled(options, "retrieval-tuning")) {
2362
- if (options.retrieval) {
2363
- retrieval = await runPhase(
2364
- phases,
2365
- now,
2366
- "retrieval-tuning",
2367
- async () => {
2368
- assertNotAborted(options.signal);
2369
- return runRetrievalImprovementLoop(options.retrieval);
2370
- },
2371
- summarizeRetrievalResult
2372
- );
2373
- } else {
2374
- skipPhase(phases, now, "retrieval-tuning", "no retrieval options provided");
2375
- }
2376
- }
2377
2530
  if (phaseEnabled(options, "gap-diagnosis")) {
2378
2531
  if (options.diagnose) {
2379
2532
  findings = [
@@ -2386,8 +2539,9 @@ async function runRagKnowledgeImprovementLoop(options) {
2386
2539
  return options.diagnose({
2387
2540
  goal: options.goal,
2388
2541
  phases,
2542
+ optimization: selectRagOptimization(optimization),
2389
2543
  signal: options.signal,
2390
- retrieval
2544
+ retrieval: selectRetrievalOptimization(retrieval)
2391
2545
  });
2392
2546
  },
2393
2547
  (diagnosed) => `${diagnosed.length} finding(s)`
@@ -2408,8 +2562,9 @@ async function runRagKnowledgeImprovementLoop(options) {
2408
2562
  return options.acquireKnowledge({
2409
2563
  goal: options.goal,
2410
2564
  phases,
2565
+ optimization: selectRagOptimization(optimization),
2411
2566
  signal: options.signal,
2412
- retrieval,
2567
+ retrieval: selectRetrievalOptimization(retrieval),
2413
2568
  findings
2414
2569
  });
2415
2570
  },
@@ -2430,8 +2585,9 @@ async function runRagKnowledgeImprovementLoop(options) {
2430
2585
  return options.updateKnowledge({
2431
2586
  goal: options.goal,
2432
2587
  phases,
2588
+ optimization: selectRagOptimization(optimization),
2433
2589
  signal: options.signal,
2434
- retrieval,
2590
+ retrieval: selectRetrievalOptimization(retrieval),
2435
2591
  findings,
2436
2592
  acquisition
2437
2593
  });
@@ -2453,6 +2609,38 @@ async function runRagKnowledgeImprovementLoop(options) {
2453
2609
  skipPhase(phases, now, "knowledge-update", "no update hook or research loop provided");
2454
2610
  }
2455
2611
  }
2612
+ if (phaseEnabled(options, "rag-optimization") && (options.optimization || options.enabledPhases?.includes("rag-optimization") || options.requiredPhases?.includes("rag-optimization"))) {
2613
+ if (options.optimization) {
2614
+ optimization = await runPhase(
2615
+ phases,
2616
+ now,
2617
+ "rag-optimization",
2618
+ async () => {
2619
+ assertNotAborted(options.signal);
2620
+ return runRagOptimization(options.optimization);
2621
+ },
2622
+ summarizeRagOptimization
2623
+ );
2624
+ } else {
2625
+ skipPhase(phases, now, "rag-optimization", "no full RAG optimization options provided");
2626
+ }
2627
+ }
2628
+ if (phaseEnabled(options, "retrieval-tuning")) {
2629
+ if (options.retrieval) {
2630
+ retrieval = await runPhase(
2631
+ phases,
2632
+ now,
2633
+ "retrieval-tuning",
2634
+ async () => {
2635
+ assertNotAborted(options.signal);
2636
+ return runRetrievalImprovementLoop(options.retrieval);
2637
+ },
2638
+ summarizeRetrievalResult
2639
+ );
2640
+ } else {
2641
+ skipPhase(phases, now, "retrieval-tuning", "no retrieval options provided");
2642
+ }
2643
+ }
2456
2644
  if (phaseEnabled(options, "answer-quality")) {
2457
2645
  if (options.evaluateAnswers) {
2458
2646
  answerQuality = await runPhase(
@@ -2461,15 +2649,18 @@ async function runRagKnowledgeImprovementLoop(options) {
2461
2649
  "answer-quality",
2462
2650
  async () => {
2463
2651
  assertNotAborted(options.signal);
2464
- return options.evaluateAnswers({
2652
+ const result = await options.evaluateAnswers({
2465
2653
  goal: options.goal,
2466
2654
  phases,
2655
+ optimization: selectRagOptimization(optimization),
2467
2656
  signal: options.signal,
2468
- retrieval,
2657
+ retrieval: selectRetrievalOptimization(retrieval),
2469
2658
  findings,
2470
2659
  acquisition,
2471
2660
  knowledgeUpdate
2472
2661
  });
2662
+ assertRagAnswerEvidence(result);
2663
+ return result;
2473
2664
  },
2474
2665
  summarizeAnswerQuality
2475
2666
  );
@@ -2479,18 +2670,30 @@ async function runRagKnowledgeImprovementLoop(options) {
2479
2670
  }
2480
2671
  }
2481
2672
  if (phaseEnabled(options, "promotion")) {
2482
- if (options.promote) {
2673
+ if (options.decidePromotion) {
2483
2674
  promotion = await runPhase(
2484
2675
  phases,
2485
2676
  now,
2486
2677
  "promotion",
2487
2678
  async () => {
2488
2679
  assertNotAborted(options.signal);
2489
- return options.promote({
2680
+ const evidenceRejection = rejectUnsafePromotionEvidence({
2681
+ optimization: optimization?.comparison,
2682
+ optimizationCostCeiling: options.optimization?.costCeiling,
2683
+ retrieval: retrieval?.comparison,
2684
+ retrievalCostCeiling: options.retrieval?.costCeiling,
2685
+ answerQuality,
2686
+ answerQualityCostCeiling: options.answerQualityCostCeiling
2687
+ });
2688
+ if (evidenceRejection) return evidenceRejection;
2689
+ return options.decidePromotion({
2490
2690
  goal: options.goal,
2491
2691
  phases,
2692
+ optimization: selectRagOptimization(optimization),
2693
+ optimizationComparison: optimization?.comparison,
2492
2694
  signal: options.signal,
2493
- retrieval,
2695
+ retrieval: selectRetrievalOptimization(retrieval),
2696
+ retrievalComparison: retrieval?.comparison,
2494
2697
  findings,
2495
2698
  acquisition,
2496
2699
  knowledgeUpdate,
@@ -2500,12 +2703,13 @@ async function runRagKnowledgeImprovementLoop(options) {
2500
2703
  (result) => `${result.promoted ? "promoted" : "held"}: ${result.reason}`
2501
2704
  );
2502
2705
  } else {
2503
- skipPhase(phases, now, "promotion", "no promotion hook provided");
2706
+ skipPhase(phases, now, "promotion", "no promotion decision hook provided");
2504
2707
  }
2505
2708
  }
2506
2709
  return {
2507
2710
  goal: options.goal,
2508
2711
  phases,
2712
+ optimization,
2509
2713
  retrieval,
2510
2714
  findings,
2511
2715
  acquisition,
@@ -2514,6 +2718,58 @@ async function runRagKnowledgeImprovementLoop(options) {
2514
2718
  promotion
2515
2719
  };
2516
2720
  }
2721
+ function rejectUnsafePromotionEvidence(evidence) {
2722
+ const reasons = [];
2723
+ if (!evidence.optimization && !evidence.retrieval && !evidence.answerQuality) {
2724
+ reasons.push("promotion requires final RAG, retrieval, or answer-quality evidence");
2725
+ }
2726
+ for (const [label, comparison, costCeiling] of [
2727
+ ["RAG", evidence.optimization, evidence.optimizationCostCeiling],
2728
+ ["retrieval", evidence.retrieval, evidence.retrievalCostCeiling]
2729
+ ]) {
2730
+ if (!comparison) continue;
2731
+ if (!comparison.totalCost.accountingComplete) {
2732
+ reasons.push(`${label} final comparison has incomplete cost accounting`);
2733
+ }
2734
+ const optimizerSource = comparison.best.provenance?.source;
2735
+ if (optimizerSource && optimizerSource.evidence !== "observed") {
2736
+ reasons.push(`${label} optimizer package identity was not observed`);
2737
+ }
2738
+ if (comparison.best.liftCi.low < 0) {
2739
+ reasons.push(`${label} final comparison does not rule out a regression`);
2740
+ }
2741
+ if (costCeiling !== void 0 && exceedsCostCeiling2(comparison.totalCost.totalCostUsd, costCeiling)) {
2742
+ reasons.push(
2743
+ `${label} final comparison cost ${comparison.totalCost.totalCostUsd} exceeds ${costCeiling}`
2744
+ );
2745
+ }
2746
+ }
2747
+ if (evidence.answerQuality) {
2748
+ reasons.push(
2749
+ ...ragAnswerEvidenceRejectionReasons(
2750
+ evidence.answerQuality,
2751
+ evidence.answerQualityCostCeiling
2752
+ )
2753
+ );
2754
+ }
2755
+ if (reasons.length === 0) return void 0;
2756
+ return {
2757
+ promoted: false,
2758
+ reason: reasons.join("; ")
2759
+ };
2760
+ }
2761
+ function assertOptionalCostCeiling(value, label) {
2762
+ if (value !== void 0 && (!Number.isFinite(value) || value < 0)) {
2763
+ throw new Error(`${label} must be a non-negative finite number`);
2764
+ }
2765
+ }
2766
+ function exceedsCostCeiling2(totalCostUsd, costCeiling) {
2767
+ const tolerance = Number.EPSILON * Math.max(1, Math.abs(totalCostUsd), Math.abs(costCeiling)) * 8;
2768
+ return totalCostUsd - costCeiling > tolerance;
2769
+ }
2770
+ function summarizeRagOptimization(result) {
2771
+ return `${result.methodName}; winner=${result.winner.surfaceHash}`;
2772
+ }
2517
2773
  async function runKnowledgeResearchUpdate(options, acquisition) {
2518
2774
  const research = options.knowledgeResearch;
2519
2775
  if (!research) {
@@ -2573,7 +2829,27 @@ function skipPhase(phases, now, phase, summary) {
2573
2829
  phases.push({ phase, status: "skipped", summary, startedAt: timestamp, finishedAt: timestamp });
2574
2830
  }
2575
2831
  function summarizeRetrievalResult(result) {
2576
- return `${result.candidates.length} candidate(s); winner=${JSON.stringify(result.winnerConfig)}`;
2832
+ return `${result.methodName}; winner=${result.winner.surfaceHash}`;
2833
+ }
2834
+ function selectRagOptimization(result) {
2835
+ if (!result) return void 0;
2836
+ return {
2837
+ methodName: result.methodName,
2838
+ baseline: structuredClone(result.baseline),
2839
+ winner: structuredClone(result.winner),
2840
+ baselineConfig: structuredClone(result.baselineConfig),
2841
+ winnerConfig: structuredClone(result.winnerConfig)
2842
+ };
2843
+ }
2844
+ function selectRetrievalOptimization(result) {
2845
+ if (!result) return void 0;
2846
+ return {
2847
+ methodName: result.methodName,
2848
+ baseline: structuredClone(result.baseline),
2849
+ winner: structuredClone(result.winner),
2850
+ baselineConfig: structuredClone(result.baselineConfig),
2851
+ winnerConfig: structuredClone(result.winnerConfig)
2852
+ };
2577
2853
  }
2578
2854
  function summarizeAcquisitionDecision(decision) {
2579
2855
  const sourcePathCount = decision.sourcePaths?.length ?? 0;
@@ -2603,6 +2879,8 @@ function assertConfiguredRequiredPhases(options) {
2603
2879
  }
2604
2880
  function phaseConfigured(options, phase) {
2605
2881
  switch (phase) {
2882
+ case "rag-optimization":
2883
+ return Boolean(options.optimization);
2606
2884
  case "retrieval-tuning":
2607
2885
  return Boolean(options.retrieval);
2608
2886
  case "gap-diagnosis":
@@ -2614,11 +2892,13 @@ function phaseConfigured(options, phase) {
2614
2892
  case "answer-quality":
2615
2893
  return Boolean(options.evaluateAnswers);
2616
2894
  case "promotion":
2617
- return Boolean(options.promote);
2895
+ return Boolean(options.decidePromotion);
2618
2896
  }
2619
2897
  }
2620
2898
  function requiredPhaseMessage(phase) {
2621
2899
  switch (phase) {
2900
+ case "rag-optimization":
2901
+ return "required phase rag-optimization requires optimization options";
2622
2902
  case "retrieval-tuning":
2623
2903
  return "required phase retrieval-tuning requires retrieval options";
2624
2904
  case "gap-diagnosis":
@@ -2630,7 +2910,7 @@ function requiredPhaseMessage(phase) {
2630
2910
  case "answer-quality":
2631
2911
  return "required phase answer-quality requires an evaluateAnswers hook";
2632
2912
  case "promotion":
2633
- return "required phase promotion requires a promote hook";
2913
+ return "required phase promotion requires a decidePromotion hook";
2634
2914
  }
2635
2915
  }
2636
2916
  function assertNotAborted(signal) {
@@ -2641,14 +2921,14 @@ function assertNotAborted(signal) {
2641
2921
 
2642
2922
  // src/kb-improvement/transition.ts
2643
2923
  import { createHash as createHash2 } from "crypto";
2644
- import { canonicalJson as canonicalJson4, contentHash as contentHash4 } from "@tangle-network/agent-eval";
2924
+ import { canonicalJson as canonicalJson5, contentHash as contentHash5 } from "@tangle-network/agent-eval";
2645
2925
 
2646
2926
  // src/kb-improvement/workspace.ts
2647
2927
  import { createHash } from "crypto";
2648
2928
  import { cp, lstat, mkdir, mkdtemp, rm } from "fs/promises";
2649
2929
  import { tmpdir } from "os";
2650
2930
  import { dirname, join as join2, relative as relative2, resolve as resolve2 } from "path";
2651
- import { canonicalJson as canonicalJson3, contentHash as contentHash3 } from "@tangle-network/agent-eval";
2931
+ import { canonicalJson as canonicalJson4, contentHash as contentHash4 } from "@tangle-network/agent-eval";
2652
2932
  function knowledgeImprovementCandidateRef(result) {
2653
2933
  if (!result.candidate) throw new Error("knowledge improvement result has no candidate");
2654
2934
  return candidateRefFor(result.runId, result.state, result.candidate);
@@ -2753,10 +3033,10 @@ async function withMeasuredCandidateSnapshot(liveRoot, runDir, state, candidateR
2753
3033
  throw new Error(`knowledge candidate '${candidateRef.candidateId}' does not exist`);
2754
3034
  }
2755
3035
  const expectedRef = candidateRefFor(candidateRef.runId, state, candidate);
2756
- if (canonicalJson3(expectedRef) !== canonicalJson3(candidateRef)) {
3036
+ if (canonicalJson4(expectedRef) !== canonicalJson4(candidateRef)) {
2757
3037
  throw new Error("knowledge candidate approval does not match the measured candidate");
2758
3038
  }
2759
- const evidence = await assertCandidateEvidence(runDir, candidateRef);
3039
+ const evidence = await assertCandidateEvidence(runDir, candidateRef, state.implementationRef);
2760
3040
  const relativePath = join2(
2761
3041
  "candidates",
2762
3042
  candidate.candidateId,
@@ -2791,7 +3071,7 @@ async function withIsolatedKnowledgeCopy(sourceRoot, expectedHash, target, use)
2791
3071
  await rm(isolationRoot, { recursive: true, force: true });
2792
3072
  }
2793
3073
  }
2794
- async function assertCandidateEvidence(runDir, candidate) {
3074
+ async function assertCandidateEvidence(runDir, candidate, expectedImplementationRef) {
2795
3075
  const evidence = KnowledgeImprovementEvidenceSchema.parse(
2796
3076
  JSON.parse(
2797
3077
  (await readRegularFileWithinRoot(
@@ -2800,13 +3080,13 @@ async function assertCandidateEvidence(runDir, candidate) {
2800
3080
  )).bytes.toString("utf8")
2801
3081
  )
2802
3082
  );
2803
- const actualHash = contentHash3(evidence);
3083
+ const actualHash = contentHash4(evidence);
2804
3084
  if (actualHash !== candidate.evidenceHash) {
2805
3085
  throw new Error(
2806
3086
  `knowledge candidate evidence changed after approval: expected ${candidate.evidenceHash}, got ${actualHash}`
2807
3087
  );
2808
3088
  }
2809
- if (evidence.runId !== candidate.runId || evidence.candidateId !== candidate.candidateId || evidence.goalHash !== candidate.goalHash || evidence.baseHash !== candidate.baseHash || evidence.candidateHash !== candidate.candidateHash || evidence.promotionPlanHash !== candidate.promotionPlanHash || evidence.evaluation.passed !== true) {
3089
+ if (evidence.runId !== candidate.runId || evidence.candidateId !== candidate.candidateId || evidence.goalHash !== candidate.goalHash || evidence.implementationRef !== expectedImplementationRef || evidence.baseHash !== candidate.baseHash || evidence.candidateHash !== candidate.candidateHash || evidence.promotionPlanHash !== candidate.promotionPlanHash || evidence.evaluation.passed !== true) {
2810
3090
  throw new Error("knowledge candidate evidence does not match the approved candidate");
2811
3091
  }
2812
3092
  return evidence;
@@ -3048,12 +3328,12 @@ async function applyKnowledgeCandidateTarget(input, target) {
3048
3328
  const { candidateRef, runDir, state } = input;
3049
3329
  assertStateIdentity(input.root, candidateRef, state);
3050
3330
  const candidate = state.candidates.find((entry) => entry.candidateId === candidateRef.candidateId);
3051
- if (!candidate || canonicalJson4(candidateIdentityFor(candidateRef.runId, state, candidate)) !== canonicalJson4(candidateRef)) {
3331
+ if (!candidate || canonicalJson5(candidateIdentityFor(candidateRef.runId, state, candidate)) !== canonicalJson5(candidateRef)) {
3052
3332
  throw new Error("knowledge candidate approval does not match the measured candidate");
3053
3333
  }
3054
3334
  const action = target === "candidate" ? "promotion" : "restore";
3055
3335
  const desiredHash = target === "candidate" ? candidateRef.candidateHash : candidateRef.baseHash;
3056
- const purpose = knowledgeCandidateTransitionPurpose(candidateRef, target);
3336
+ const purpose = knowledgeCandidateTransitionPurpose(candidateRef, state.implementationRef, target);
3057
3337
  const recoveryOwner = input.activation ? `knowledge-improvement-activation:${input.activation.activation.digest}` : "knowledge-improvement-candidate-transition";
3058
3338
  return withKnowledgeMutation(
3059
3339
  input.root,
@@ -3065,6 +3345,9 @@ async function applyKnowledgeCandidateTarget(input, target) {
3065
3345
  assertOwned();
3066
3346
  const transactionRoot = mutationLock.transactionRoot;
3067
3347
  const recovery = mutationLock.recovery?.purpose === purpose ? mutationLock.recovery : void 0;
3348
+ if (!recovery) {
3349
+ await assertCandidateEvidence(runDir, candidateRef, state.implementationRef);
3350
+ }
3068
3351
  const existingActivation = input.activation ? await loadKnowledgeActivationRecord(
3069
3352
  runDir,
3070
3353
  candidateRef,
@@ -3366,9 +3649,9 @@ function candidateTransitionResult(input, candidate, promoted, blocked, mutation
3366
3649
  blocked
3367
3650
  };
3368
3651
  }
3369
- function knowledgeCandidateTransitionPurpose(candidate, target) {
3652
+ function knowledgeCandidateTransitionPurpose(candidate, implementationRef, target) {
3370
3653
  const action = target === "candidate" ? "promotion" : "restore";
3371
- return `knowledge-${action}:${contentHash4(candidate)}`;
3654
+ return `knowledge-${action}:${contentHash5({ candidate, implementationRef })}`;
3372
3655
  }
3373
3656
  async function knowledgeFilePlanEntries(sourceRoot, targetRoot) {
3374
3657
  const [before, after] = await Promise.all([
@@ -3453,6 +3736,24 @@ async function hasCandidateTransitionEvent(runDir, candidateRef, target) {
3453
3736
 
3454
3737
  // src/kb-improvement/evaluation.ts
3455
3738
  function assertKnowledgeImprovementOptions(options) {
3739
+ assertImmutableRef(options.implementationRef, "knowledge improvement implementationRef");
3740
+ if (options.answerQualityCostCeiling !== void 0 && (!Number.isFinite(options.answerQualityCostCeiling) || options.answerQualityCostCeiling < 0)) {
3741
+ throw new Error(
3742
+ "knowledge improvement answerQualityCostCeiling must be a non-negative finite number"
3743
+ );
3744
+ }
3745
+ if (options.ragOptimization) {
3746
+ assertImmutableRef(
3747
+ options.ragOptimization.executionRef,
3748
+ "knowledge improvement RAG executionRef"
3749
+ );
3750
+ }
3751
+ if (options.retrieval) {
3752
+ assertImmutableRef(
3753
+ options.retrieval.executionRef,
3754
+ "knowledge improvement retrieval executionRef"
3755
+ );
3756
+ }
3456
3757
  if (options.step && options.knowledgeResearch?.step) {
3457
3758
  throw new Error("improveKnowledgeBase accepts either step or knowledgeResearch.step, not both");
3458
3759
  }
@@ -3472,9 +3773,15 @@ async function measureCandidate(runId, runDir, state, candidate, options, now) {
3472
3773
  if (candidate.status === "candidate-ready" && candidate.candidateHash === currentCandidateHash && candidate.evidenceHash !== void 0 && candidate.promotionPlanHash !== void 0) {
3473
3774
  const evidence = await assertCandidateEvidence(
3474
3775
  runDir,
3475
- candidateRefFor(runId, state, candidate)
3776
+ candidateRefFor(runId, state, candidate),
3777
+ state.implementationRef
3476
3778
  );
3477
- return { candidate, evaluation: evidence.evaluation };
3779
+ return {
3780
+ candidate,
3781
+ evaluation: evidence.evaluation,
3782
+ ...evidence.lifecycle === null ? {} : { lifecycle: evidence.lifecycle },
3783
+ finalEvaluated: shouldRunEvaluationStage(options)
3784
+ };
3478
3785
  }
3479
3786
  clearCandidateMeasurement(candidate);
3480
3787
  const lifecycles = [];
@@ -3489,15 +3796,44 @@ async function measureCandidate(runId, runDir, state, candidate, options, now) {
3489
3796
  if (updateLifecycle) lifecycles.push(updateLifecycle);
3490
3797
  }
3491
3798
  return withFrozenCandidateWorkspace(runDir, candidate, candidateRoot, async (snapshot) => {
3799
+ let lifecycle = mergeLifecycleResults(options.goal, lifecycles);
3800
+ const development = await evaluateCandidate(
3801
+ runDir,
3802
+ state,
3803
+ candidate,
3804
+ snapshot,
3805
+ lifecycle,
3806
+ options,
3807
+ now,
3808
+ false
3809
+ );
3810
+ if (!development.evaluation.passed || !shouldRunEvaluationStage(options)) {
3811
+ return {
3812
+ ...development,
3813
+ ...lifecycle ? { lifecycle } : {},
3814
+ finalEvaluated: false
3815
+ };
3816
+ }
3817
+ candidate.finalEvaluationStartedAt = now().toISOString();
3818
+ candidate.updatedAt = candidate.finalEvaluationStartedAt;
3819
+ state.updatedAt = candidate.finalEvaluationStartedAt;
3820
+ await saveState(runDir, state, options.onState);
3821
+ await appendLedger(runDir, {
3822
+ type: "candidate.final-evaluation-started",
3823
+ runId: state.runId,
3824
+ candidateId: candidate.candidateId
3825
+ });
3492
3826
  const evaluationLifecycle = await runCandidateEvaluationLifecycle(
3827
+ runId,
3493
3828
  runDir,
3494
3829
  candidate,
3495
3830
  snapshot.root,
3831
+ snapshot.hash,
3496
3832
  options,
3497
3833
  now
3498
3834
  );
3499
3835
  if (evaluationLifecycle) lifecycles.push(evaluationLifecycle);
3500
- const lifecycle = mergeLifecycleResults(options.goal, lifecycles);
3836
+ lifecycle = mergeLifecycleResults(options.goal, lifecycles);
3501
3837
  const measured = await evaluateCandidate(
3502
3838
  runDir,
3503
3839
  state,
@@ -3505,9 +3841,14 @@ async function measureCandidate(runId, runDir, state, candidate, options, now) {
3505
3841
  snapshot,
3506
3842
  lifecycle,
3507
3843
  options,
3508
- now
3844
+ now,
3845
+ true
3509
3846
  );
3510
- return { ...measured, ...lifecycle ? { lifecycle } : {} };
3847
+ return {
3848
+ ...measured,
3849
+ ...lifecycle ? { lifecycle } : {},
3850
+ finalEvaluated: true
3851
+ };
3511
3852
  });
3512
3853
  });
3513
3854
  }
@@ -3525,25 +3866,53 @@ async function runCandidateUpdateLifecycle(runId, candidate, candidateRoot, opti
3525
3866
  });
3526
3867
  return lifecycle;
3527
3868
  }
3528
- async function runCandidateEvaluationLifecycle(runDir, candidate, candidateRoot, options, now) {
3869
+ async function runCandidateEvaluationLifecycle(runId, runDir, candidate, candidateRoot, candidateHash, options, now) {
3529
3870
  if (!shouldRunEvaluationStage(options)) return void 0;
3530
3871
  const candidateIndex = await buildKnowledgeIndex(candidateRoot);
3531
- const lifecycle = await runRagKnowledgeImprovementLoop({
3532
- goal: options.goal,
3533
- retrieval: options.retrieval ? {
3534
- ...options.retrieval,
3535
- index: candidateIndex,
3536
- runDir: options.retrieval.runDir ?? join3(runDir, "retrieval", candidate.candidateId)
3537
- } : void 0,
3538
- diagnose: options.diagnose,
3539
- evaluateAnswers: options.evaluateAnswers,
3540
- promote: options.decidePromotion,
3541
- enabledPhases: selectedStagePhases(options, EVALUATION_PHASES),
3542
- requiredPhases: selectedStageRequiredPhases(options, EVALUATION_PHASES),
3543
- signal: options.signal,
3544
- now
3545
- });
3546
- return lifecycle;
3872
+ return withBaselineSnapshot(
3873
+ runDir,
3874
+ candidate.baseHash,
3875
+ (baselineRoot) => runRagKnowledgeImprovementLoop({
3876
+ goal: options.goal,
3877
+ optimization: options.ragOptimization ? {
3878
+ ...options.ragOptimization,
3879
+ executionRef: candidateExecutionRef(
3880
+ options.ragOptimization.executionRef,
3881
+ candidateHash
3882
+ ),
3883
+ runDir: options.ragOptimization.runDir ?? join3(runDir, "rag-optimization", candidate.candidateId),
3884
+ run: (input) => options.ragOptimization.run({
3885
+ ...input,
3886
+ runId,
3887
+ iteration: candidate.iteration,
3888
+ candidateId: candidate.candidateId,
3889
+ root: candidateRoot,
3890
+ baselineRoot,
3891
+ candidateRoot,
3892
+ candidateIndex,
3893
+ baseHash: candidate.baseHash
3894
+ })
3895
+ } : void 0,
3896
+ retrieval: options.retrieval ? {
3897
+ ...options.retrieval,
3898
+ executionRef: candidateExecutionRef(options.retrieval.executionRef, candidateHash),
3899
+ index: candidateIndex,
3900
+ runDir: options.retrieval.runDir ?? join3(runDir, "retrieval", candidate.candidateId)
3901
+ } : void 0,
3902
+ diagnose: options.diagnose,
3903
+ evaluateAnswers: options.evaluateAnswers,
3904
+ answerQualityCostCeiling: options.answerQualityCostCeiling,
3905
+ decidePromotion: options.decidePromotion,
3906
+ enabledPhases: selectedStagePhases(options, EVALUATION_PHASES),
3907
+ requiredPhases: selectedStageRequiredPhases(options, EVALUATION_PHASES),
3908
+ signal: options.signal,
3909
+ now
3910
+ })
3911
+ );
3912
+ }
3913
+ function candidateExecutionRef(executionRef, candidateHash) {
3914
+ return `sha256:${sha256(`${executionRef}
3915
+ ${candidateHash}`)}`;
3547
3916
  }
3548
3917
  function candidateKnowledgeResearchOptions(candidateRoot, options) {
3549
3918
  if (!options.step && !options.knowledgeResearch) return void 0;
@@ -3584,7 +3953,7 @@ function shouldRunEvaluationStage(options) {
3584
3953
  const phases = selectedStagePhases(options, EVALUATION_PHASES);
3585
3954
  if (phases.length === 0) return false;
3586
3955
  return Boolean(
3587
- options.retrieval || options.diagnose || options.evaluateAnswers || options.decidePromotion || selectedStageRequiredPhases(options, EVALUATION_PHASES).length > 0
3956
+ options.ragOptimization || options.retrieval || options.diagnose || options.evaluateAnswers || options.decidePromotion || options.evaluate || selectedStageRequiredPhases(options, EVALUATION_PHASES).length > 0
3588
3957
  );
3589
3958
  }
3590
3959
  function selectedStagePhases(options, stagePhases) {
@@ -3599,6 +3968,7 @@ function mergeLifecycleResults(goal, lifecycles) {
3599
3968
  return {
3600
3969
  goal,
3601
3970
  phases: lifecycles.flatMap((lifecycle) => lifecycle.phases),
3971
+ optimization: lastDefined(lifecycles.map((lifecycle) => lifecycle.optimization)),
3602
3972
  retrieval: lastDefined(lifecycles.map((lifecycle) => lifecycle.retrieval)),
3603
3973
  findings: lifecycles.flatMap((lifecycle) => lifecycle.findings),
3604
3974
  acquisition: lastDefined(lifecycles.map((lifecycle) => lifecycle.acquisition)),
@@ -3613,7 +3983,7 @@ function lastDefined(values) {
3613
3983
  }
3614
3984
  return void 0;
3615
3985
  }
3616
- async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle, options, now) {
3986
+ async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle, options, now, useConfiguredEvaluator) {
3617
3987
  return withBaselineSnapshot(runDir, state.baseHash, async (baselineRoot) => {
3618
3988
  const [baselineIndex, candidateIndex] = await Promise.all([
3619
3989
  buildKnowledgeIndex(baselineRoot),
@@ -3626,7 +3996,8 @@ async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle,
3626
3996
  ...options.kbQuality
3627
3997
  });
3628
3998
  const candidateHash = snapshot.hash;
3629
- const metric = options.evaluate?.({
3999
+ const evaluator = useConfiguredEvaluator ? options.evaluate : options.evaluateDevelopment;
4000
+ const configuredMetric = evaluator ? evaluator({
3630
4001
  runId: state.runId,
3631
4002
  iteration: candidate.iteration,
3632
4003
  root: options.root,
@@ -3641,14 +4012,19 @@ async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle,
3641
4012
  kbQuality,
3642
4013
  lifecycle,
3643
4014
  signal: options.signal
3644
- }) ?? defaultKnowledgeImprovementMetric(
4015
+ }) : void 0;
4016
+ const metric = configuredMetric ?? defaultKnowledgeImprovementMetric(
3645
4017
  validation,
3646
4018
  readiness,
3647
4019
  options.readinessSpecs,
3648
4020
  kbQuality,
3649
4021
  lifecycle
3650
4022
  );
3651
- const evaluation = applyLifecycleFailures(normalizeMetric(await metric), lifecycle);
4023
+ const evaluation = applyLifecycleFailures(
4024
+ normalizeMetric(await metric),
4025
+ lifecycle,
4026
+ options.answerQualityCostCeiling
4027
+ );
3652
4028
  const measuredHash = await hashKnowledgeBase(snapshot.root);
3653
4029
  if (measuredHash !== candidateHash) {
3654
4030
  throw new Error(
@@ -3667,6 +4043,7 @@ async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle,
3667
4043
  candidateId: candidate.candidateId,
3668
4044
  iteration: candidate.iteration,
3669
4045
  goalHash: sha256(state.goal),
4046
+ implementationRef: state.implementationRef,
3670
4047
  baseHash: candidate.baseHash,
3671
4048
  candidateHash,
3672
4049
  promotionPlanHash: candidate.promotionPlanHash,
@@ -3678,7 +4055,7 @@ async function evaluateCandidate(runDir, state, candidate, snapshot, lifecycle,
3678
4055
  })
3679
4056
  )
3680
4057
  );
3681
- candidate.evidenceHash = contentHash5(evidence);
4058
+ candidate.evidenceHash = contentHash6(evidence);
3682
4059
  candidate.updatedAt = now().toISOString();
3683
4060
  await writeJsonDurableWithinRoot(
3684
4061
  runDir,
@@ -3725,13 +4102,14 @@ function defaultKnowledgeImprovementMetric(validation, readiness, readinessSpecs
3725
4102
  }
3726
4103
  };
3727
4104
  }
3728
- function applyLifecycleFailures(metric, lifecycle) {
4105
+ function applyLifecycleFailures(metric, lifecycle, answerQualityCostCeiling) {
4106
+ const answerQualityFailures = lifecycle?.answerQuality ? ragAnswerEvidenceRejectionReasons(lifecycle.answerQuality, answerQualityCostCeiling) : [];
3729
4107
  const reasons = [
3730
4108
  metric.notes,
3731
- lifecycle?.answerQuality && !lifecycle.answerQuality.passed ? "answer quality failed" : void 0,
4109
+ ...answerQualityFailures,
3732
4110
  lifecycle?.promotion && !lifecycle.promotion.promoted ? `promotion decision held: ${lifecycle.promotion.reason}` : void 0
3733
4111
  ].filter((reason) => Boolean(reason));
3734
- const forcedFailure = Boolean(lifecycle?.answerQuality && !lifecycle.answerQuality.passed) || Boolean(lifecycle?.promotion && !lifecycle.promotion.promoted);
4112
+ const forcedFailure = answerQualityFailures.length > 0 || Boolean(lifecycle?.promotion && !lifecycle.promotion.promoted);
3735
4113
  return {
3736
4114
  ...metric,
3737
4115
  passed: metric.passed && !forcedFailure,
@@ -3780,6 +4158,7 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
3780
4158
  runId,
3781
4159
  root: options.root,
3782
4160
  goal: options.goal,
4161
+ implementationRef: options.implementationRef,
3783
4162
  status: "running",
3784
4163
  baseHash,
3785
4164
  createdAt: now().toISOString(),
@@ -3793,6 +4172,9 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
3793
4172
  if (state.goal !== options.goal) {
3794
4173
  throw new Error("knowledge improvement state does not match the requested goal");
3795
4174
  }
4175
+ if (state.implementationRef !== options.implementationRef) {
4176
+ throw new Error("knowledge improvement state does not match the requested implementationRef");
4177
+ }
3796
4178
  const promotedCandidateId = state.promotedCandidateId;
3797
4179
  const promotedCandidate = state.status === "promoted" ? state.candidates.find((candidate2) => candidate2.candidateId === promotedCandidateId) : void 0;
3798
4180
  if (state.status === "promoted" && !promotedCandidate) {
@@ -3810,7 +4192,8 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
3810
4192
  }
3811
4193
  const evidence2 = await assertCandidateEvidence(
3812
4194
  runDir,
3813
- candidateRefFor(runId, promotedState, promoted)
4195
+ candidateRefFor(runId, promotedState, promoted),
4196
+ promotedState.implementationRef
3814
4197
  );
3815
4198
  return {
3816
4199
  runId,
@@ -3833,6 +4216,22 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
3833
4216
  let lastRejectedEvaluation;
3834
4217
  let lifecycle;
3835
4218
  while (candidate || state.candidates.length < maxCandidates) {
4219
+ if (candidate?.status === "running" && candidate.finalEvaluationStartedAt) {
4220
+ state = await blockRun(
4221
+ runDir,
4222
+ state,
4223
+ `candidate '${candidate.candidateId}' was interrupted after final evaluation started; refusing to reuse final cases`,
4224
+ options.onState,
4225
+ now
4226
+ );
4227
+ return {
4228
+ runId,
4229
+ state,
4230
+ candidate,
4231
+ promoted: false,
4232
+ blocked: true
4233
+ };
4234
+ }
3836
4235
  if (!candidate) {
3837
4236
  const currentHash = await hashKnowledgeBase(options.root);
3838
4237
  if (currentHash !== state.baseHash) {
@@ -3891,6 +4290,7 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
3891
4290
  lastRejectedCandidate = candidate;
3892
4291
  lastRejectedEvaluation = evaluation;
3893
4292
  candidate = void 0;
4293
+ if (measured.finalEvaluated) break;
3894
4294
  }
3895
4295
  if (!candidate) {
3896
4296
  state.status = "rejected";
@@ -3906,7 +4306,11 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
3906
4306
  blocked: false
3907
4307
  };
3908
4308
  }
3909
- const evidence = await assertCandidateEvidence(runDir, candidateRefFor(runId, state, candidate));
4309
+ const evidence = await assertCandidateEvidence(
4310
+ runDir,
4311
+ candidateRefFor(runId, state, candidate),
4312
+ state.implementationRef
4313
+ );
3910
4314
  return {
3911
4315
  runId,
3912
4316
  state,
@@ -3921,6 +4325,93 @@ async function improveKnowledgeBaseInRun(options, runId, runDir, now) {
3921
4325
  }
3922
4326
  }
3923
4327
 
4328
+ // src/kb-improvement/optimization.ts
4329
+ async function optimizeKnowledgeBasePolicy(options) {
4330
+ const {
4331
+ root,
4332
+ goal,
4333
+ baselinePolicy,
4334
+ method,
4335
+ trainScenarios,
4336
+ selectionScenarios,
4337
+ finalScenarios,
4338
+ policyApplicationRef,
4339
+ candidateRunLabel,
4340
+ candidate,
4341
+ applyPolicy,
4342
+ ...optimizationOptions
4343
+ } = options;
4344
+ if (typeof root !== "string" || !root.trim()) {
4345
+ throw new Error("optimizeKnowledgeBasePolicy root must be non-empty");
4346
+ }
4347
+ if (typeof goal !== "string" || !goal.trim()) {
4348
+ throw new Error("optimizeKnowledgeBasePolicy goal must be non-empty");
4349
+ }
4350
+ assertImmutableRef(policyApplicationRef, "optimizeKnowledgeBasePolicy policyApplicationRef");
4351
+ if (candidateRunLabel !== void 0 && (typeof candidateRunLabel !== "string" || !candidateRunLabel.trim())) {
4352
+ throw new Error("optimizeKnowledgeBasePolicy candidateRunLabel must be non-empty");
4353
+ }
4354
+ const baseHash = await hashKnowledgeBase(root);
4355
+ const optimization = await runSerializedKnowledgeOptimization({
4356
+ ...optimizationOptions,
4357
+ executionRef: policyApplicationRef,
4358
+ baseline: baselinePolicy,
4359
+ method,
4360
+ trainScenarios,
4361
+ selectionScenarios,
4362
+ finalScenarios
4363
+ });
4364
+ const winner = optimization.winner;
4365
+ const currentBaseHash = await hashKnowledgeBase(root);
4366
+ if (currentBaseHash !== baseHash) {
4367
+ throw new Error(
4368
+ `knowledge base changed during policy optimization: expected ${baseHash}, got ${currentBaseHash}`
4369
+ );
4370
+ }
4371
+ const runId = stableId(
4372
+ "kbpolicy",
4373
+ `${candidateRunLabel ?? "default"}:${goal}:${optimization.methodName}:${winner.surfaceHash}:${policyApplicationRef}:${baseHash}`
4374
+ );
4375
+ const improvement = await improveKnowledgeBase({
4376
+ ...candidate ?? {},
4377
+ root,
4378
+ goal,
4379
+ implementationRef: `sha256:${sha256(
4380
+ `${policyApplicationRef}
4381
+ ${winner.surfaceHash}
4382
+ ${optimization.methodName}`
4383
+ )}`,
4384
+ runId,
4385
+ maxCandidates: 1,
4386
+ updateKnowledge: async (input) => {
4387
+ if (input.baseHash !== baseHash) {
4388
+ throw new Error(
4389
+ `knowledge base changed before policy materialization: expected ${baseHash}, got ${input.baseHash}`
4390
+ );
4391
+ }
4392
+ const result = await applyPolicy({
4393
+ ...input,
4394
+ policy: structuredClone(winner.value),
4395
+ policySurface: winner.surface,
4396
+ policySurfaceHash: winner.surfaceHash,
4397
+ optimizationMethod: optimization.methodName
4398
+ });
4399
+ return {
4400
+ ...result,
4401
+ metadata: {
4402
+ ...result.metadata ?? {},
4403
+ optimization: {
4404
+ method: optimization.methodName,
4405
+ policySurfaceHash: winner.surfaceHash,
4406
+ policyApplicationRef
4407
+ }
4408
+ }
4409
+ };
4410
+ }
4411
+ });
4412
+ return { optimization, improvement };
4413
+ }
4414
+
3924
4415
  // src/agent-candidate.ts
3925
4416
  function toAgentCandidateKnowledgeRef(candidate) {
3926
4417
  const parsed = KnowledgeImprovementCandidateRefSchema.parse(candidate);
@@ -4302,6 +4793,60 @@ function positiveInteger(value, name) {
4302
4793
  return value;
4303
4794
  }
4304
4795
 
4796
+ // src/filesystem-search-provider.ts
4797
+ var FileSystemSearchProvider = class {
4798
+ root;
4799
+ index;
4800
+ defaultLimit;
4801
+ refreshMode;
4802
+ constructor(options) {
4803
+ this.root = options.root;
4804
+ this.index = options.index;
4805
+ this.defaultLimit = options.defaultLimit ?? 10;
4806
+ this.refreshMode = options.refresh ?? "manual";
4807
+ }
4808
+ async getIndex(options = {}) {
4809
+ if (this.refreshMode === "always" || options.refresh || !this.index) {
4810
+ this.index = await buildKnowledgeIndex(this.root);
4811
+ }
4812
+ return this.index;
4813
+ }
4814
+ async search(query, options = {}) {
4815
+ const index = await this.getIndex(options);
4816
+ return searchKnowledge(index, query, options.limit ?? this.defaultLimit);
4817
+ }
4818
+ async retrieve(query, options = {}) {
4819
+ const hits = await this.search(query, options);
4820
+ return hits.map(hitFromSearchResult);
4821
+ }
4822
+ asRetrievalEvalRetriever() {
4823
+ return async ({ config, scenario, k }) => ({
4824
+ hits: await this.retrieve(scenario.query, {
4825
+ limit: k,
4826
+ refresh: config.refresh === true
4827
+ })
4828
+ });
4829
+ }
4830
+ invalidate() {
4831
+ this.index = void 0;
4832
+ }
4833
+ };
4834
+ function createFileSystemSearchProvider(options) {
4835
+ return new FileSystemSearchProvider(options);
4836
+ }
4837
+ function hitFromSearchResult(result) {
4838
+ return {
4839
+ pageId: result.page.id,
4840
+ path: result.page.path,
4841
+ title: result.page.title,
4842
+ rank: result.rank,
4843
+ score: result.score,
4844
+ normalizedScore: result.normalizedScore,
4845
+ sourceIds: result.page.sourceIds,
4846
+ snippet: result.snippet
4847
+ };
4848
+ }
4849
+
4305
4850
  // src/freshness.ts
4306
4851
  import { readFile } from "fs/promises";
4307
4852
  import { join as join4 } from "path";
@@ -6275,6 +6820,7 @@ export {
6275
6820
  AgentMemoryWriteInputSchema,
6276
6821
  DEFAULT_MEMORY_CLEANUP_TIMEOUT_MS,
6277
6822
  FileSystemKbStore,
6823
+ FileSystemSearchProvider,
6278
6824
  INDUSTRY_MEMORY_BENCHMARKS,
6279
6825
  INDUSTRY_RAG_BENCHMARKS,
6280
6826
  IRS_DIMENSION_HINTS,
@@ -6318,7 +6864,6 @@ export {
6318
6864
  buildKnowledgeIndex,
6319
6865
  buildRetrievalBenchmarkCasesFromQrels,
6320
6866
  buildRetrievalEvalDispatch,
6321
- buildRetrievalParameterCandidates,
6322
6867
  calibrateRagAnswerJudge,
6323
6868
  canonicalizeUrl,
6324
6869
  chunkMarkdown,
@@ -6333,6 +6878,7 @@ export {
6333
6878
  createCornellLiiSource,
6334
6879
  createD1FreshnessStoreStub,
6335
6880
  createFileSystemFreshnessStore,
6881
+ createFileSystemSearchProvider,
6336
6882
  createGraphitiMemoryAdapter,
6337
6883
  createInMemoryBenchmarkAdapter,
6338
6884
  createIrsPublicationsSource,
@@ -6378,6 +6924,8 @@ export {
6378
6924
  isKnowledgeMemoryBenchmarkCase,
6379
6925
  isSafeKnowledgePath,
6380
6926
  isScaffoldPath,
6927
+ jsonCandidateCodec,
6928
+ jsonObjectCandidateCodec,
6381
6929
  kbIndexToText,
6382
6930
  knowledgeBenchmarkJudge,
6383
6931
  knowledgeImprovementCandidateRef,
@@ -6402,10 +6950,12 @@ export {
6402
6950
  memoryWriteResultToSourceRecord,
6403
6951
  normalizeExternalRagScores,
6404
6952
  normalizeLinkTarget,
6953
+ optimizeKnowledgeBasePolicy,
6405
6954
  parseFrontmatter,
6406
6955
  parseKnowledgeBenchmarkJsonl,
6407
6956
  parseKnowledgeBenchmarkQrels,
6408
6957
  parseKnowledgeWriteBlocks,
6958
+ partitionRetrievalScenarios,
6409
6959
  politeFetch,
6410
6960
  promoteKnowledgeCandidate,
6411
6961
  proposeFromFinding,
@@ -6423,7 +6973,6 @@ export {
6423
6973
  retrievalConfigFromSurface,
6424
6974
  retrievalConfigSurface,
6425
6975
  retrievalHoldoutConfigHash,
6426
- retrievalParameterSweepProposer,
6427
6976
  retrievalRecallJudge,
6428
6977
  runAgentMemoryExperiment,
6429
6978
  runAgentMemoryImprovement,
@@ -6434,9 +6983,12 @@ export {
6434
6983
  runKnowledgeResearchLoop,
6435
6984
  runMemoryAdapterBenchmark,
6436
6985
  runRagKnowledgeImprovementLoop,
6986
+ runRagOptimization,
6437
6987
  runRetrievalImprovementLoop,
6988
+ runSerializedKnowledgeOptimization,
6438
6989
  runTwoAgentResearchLoop,
6439
6990
  runVerifiedResearchLoop,
6991
+ scenarioContentFingerprint,
6440
6992
  scoreKnowledgeBaseIndex,
6441
6993
  scoreKnowledgeBenchmarkArtifact,
6442
6994
  scoreMemoryBenchmarkArtifact,