@evomap/evolver-core 2.0.0-beta.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/algo/candidateAssembly.d.ts +7 -2
  2. package/dist/algo/candidateAssembly.js +81 -13
  3. package/dist/algo/capabilityCandidates.js +10 -0
  4. package/dist/algo/cycleEngine.d.ts +23 -2
  5. package/dist/algo/cycleEngine.js +236 -36
  6. package/dist/algo/exploration.d.ts +7 -0
  7. package/dist/algo/exploration.js +16 -3
  8. package/dist/algo/geneHealth.d.ts +2 -2
  9. package/dist/algo/geneHealth.js +5 -4
  10. package/dist/algo/geneSelection.d.ts +63 -5
  11. package/dist/algo/geneSelection.js +330 -30
  12. package/dist/algo/index.d.ts +2 -0
  13. package/dist/algo/index.js +2 -0
  14. package/dist/algo/orchestrator.d.ts +8 -1
  15. package/dist/algo/orchestrator.js +15 -3
  16. package/dist/algo/publishEligibility.d.ts +34 -0
  17. package/dist/algo/publishEligibility.js +52 -0
  18. package/dist/algo/ucb1.d.ts +53 -0
  19. package/dist/algo/ucb1.js +156 -0
  20. package/dist/assetstore/assetSidecarRecords.d.ts +7 -2
  21. package/dist/assetstore/assetSidecarRecords.js +238 -0
  22. package/dist/assetstore/assetSidecarRecovery.js +2 -2
  23. package/dist/assetstore/assetStoreHealth.d.ts +6 -0
  24. package/dist/assetstore/assetStoreHealth.js +56 -14
  25. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  26. package/dist/assetstore/assetStoreStorage.js +27 -9
  27. package/dist/assetstore/assetSyncLedger.d.ts +86 -0
  28. package/dist/assetstore/assetSyncLedger.js +683 -4
  29. package/dist/assetstore/index.d.ts +1 -0
  30. package/dist/assetstore/index.js +1 -0
  31. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  32. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  33. package/dist/assetstore/localJsonl.d.ts +4 -2
  34. package/dist/assetstore/localJsonl.js +89 -9
  35. package/dist/assetstore/provenance.d.ts +80 -4
  36. package/dist/assetstore/provenance.js +313 -5
  37. package/dist/assetstore/provider.d.ts +58 -1
  38. package/dist/assetstore/provider.js +97 -6
  39. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  40. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  41. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  42. package/dist/benchmark/antiGeneRollout.js +4 -3
  43. package/dist/benchmark/index.d.ts +2 -1
  44. package/dist/benchmark/index.js +2 -1
  45. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  46. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  47. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  48. package/dist/bootstrap/envFingerprint.js +5 -0
  49. package/dist/bootstrap/index.d.ts +2 -1
  50. package/dist/bootstrap/index.js +2 -1
  51. package/dist/bootstrap/v1EnvCompat.d.ts +111 -0
  52. package/dist/bootstrap/v1EnvCompat.js +280 -0
  53. package/dist/events/eventArchive.d.ts +2 -0
  54. package/dist/events/eventArchive.js +13 -3
  55. package/dist/events/eventSchema.d.ts +7 -7
  56. package/dist/events/eventStore.d.ts +2 -0
  57. package/dist/events/eventStore.js +5 -1
  58. package/dist/events/ingest.d.ts +1 -0
  59. package/dist/events/ingest.js +1 -0
  60. package/dist/events/paths.d.ts +3 -1
  61. package/dist/events/paths.js +4 -0
  62. package/dist/events/public.d.ts +2 -2
  63. package/dist/events/public.js +2 -2
  64. package/dist/events/reports.d.ts +2 -0
  65. package/dist/events/reports.js +4 -0
  66. package/dist/exec/autoExec.d.ts +43 -2
  67. package/dist/exec/autoExec.js +76 -9
  68. package/dist/exec/autonomousCycle.d.ts +22 -4
  69. package/dist/exec/autonomousCycle.js +64 -13
  70. package/dist/exec/claudeBridge.d.ts +32 -7
  71. package/dist/exec/claudeBridge.js +281 -29
  72. package/dist/exec/prompt.js +5 -1
  73. package/dist/exec/runnerRegistry.d.ts +68 -26
  74. package/dist/exec/runnerRegistry.js +307 -72
  75. package/dist/exec/selfPr.js +1 -7
  76. package/dist/feedback/envelope.d.ts +61 -0
  77. package/dist/feedback/envelope.js +168 -0
  78. package/dist/feedback/index.d.ts +1 -0
  79. package/dist/feedback/index.js +1 -0
  80. package/dist/hooks/hooks.js +1 -0
  81. package/dist/hub/assetCallLog.d.ts +35 -1
  82. package/dist/hub/assetCallLog.js +124 -1
  83. package/dist/hub/bindings.d.ts +8 -1
  84. package/dist/hub/bindings.js +17 -6
  85. package/dist/hub/capability.d.ts +90 -3
  86. package/dist/hub/fake.d.ts +2 -2
  87. package/dist/hub/fake.js +1 -1
  88. package/dist/hub/questionGenerator.d.ts +5 -1
  89. package/dist/hub/questionGenerator.js +8 -6
  90. package/dist/index.d.ts +3 -1
  91. package/dist/index.js +4 -1
  92. package/dist/mailbox/dispatch.d.ts +1 -1
  93. package/dist/mailbox/dispatch.js +22 -6
  94. package/dist/mailbox/envelope.d.ts +7 -1
  95. package/dist/mailbox/envelope.js +9 -2
  96. package/dist/mailbox/ipcServer.d.ts +10 -2
  97. package/dist/mailbox/ipcServer.js +163 -13
  98. package/dist/mailbox/store.d.ts +89 -3
  99. package/dist/mailbox/store.js +895 -41
  100. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  101. package/dist/ops/evolutionGraphProjection.js +315 -0
  102. package/dist/ops/index.d.ts +2 -1
  103. package/dist/ops/index.js +2 -1
  104. package/dist/ops/selfUpdate.d.ts +8 -0
  105. package/dist/ops/selfUpdate.js +24 -8
  106. package/dist/personality/schema.d.ts +16 -16
  107. package/dist/schema/evolutionGraph.d.ts +784 -0
  108. package/dist/schema/evolutionGraph.js +187 -0
  109. package/dist/schema/index.d.ts +1 -0
  110. package/dist/schema/index.js +1 -0
  111. package/dist/schema/signal.d.ts +3 -3
  112. package/dist/schema/signal.js +1 -1
  113. package/dist/signals/curriculum.d.ts +55 -0
  114. package/dist/signals/curriculum.js +202 -0
  115. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  116. package/dist/signals/expand.d.ts +15 -1
  117. package/dist/signals/expand.js +169 -1
  118. package/dist/signals/extractor.d.ts +2 -2
  119. package/dist/signals/extractor.js +31 -6
  120. package/dist/signals/index.d.ts +3 -1
  121. package/dist/signals/index.js +3 -1
  122. package/dist/signals/metaSignals.d.ts +4 -0
  123. package/dist/signals/metaSignals.js +42 -0
  124. package/dist/signals/signalGate.js +1 -1
  125. package/dist/signals/taskDomain.d.ts +22 -0
  126. package/dist/signals/taskDomain.js +43 -0
  127. package/dist/strategy/constraintAblation.js +115 -369
  128. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  129. package/dist/strategy/constraintAblationPredicates.js +339 -0
  130. package/dist/trace/index.d.ts +3 -1
  131. package/dist/trace/index.js +3 -1
  132. package/dist/trace/learningTrace.d.ts +209 -0
  133. package/dist/trace/learningTrace.js +276 -0
  134. package/dist/trace/proxyTurns.d.ts +31 -0
  135. package/dist/trace/proxyTurns.js +137 -0
  136. package/dist/verify/sandboxRunner.d.ts +28 -0
  137. package/dist/verify/sandboxRunner.js +218 -19
  138. package/dist/verify/sandboxedValidation.d.ts +9 -0
  139. package/dist/verify/sandboxedValidation.js +113 -13
  140. package/dist/verify/validation.d.ts +11 -1
  141. package/dist/verify/validation.js +31 -0
  142. package/package.json +5 -2
@@ -6,6 +6,7 @@ import type { AssetRecord } from '../assetstore/provider.js';
6
6
  import { type ExplorationInput } from './exploration.js';
7
7
  import type { GenerationSource } from '../wire/index.js';
8
8
  import type { MemoryGraphGeneEvidence } from './memoryGraph.js';
9
+ import { UCB1_REWARD_POLICY_VERSION, UCB1_SELECTION_POLICY_VERSION, type SelectionPolicy, type Ucb1Choice, type Ucb1FallbackReason } from './ucb1.js';
9
10
  /** 一个候选 gene 的选择期素材. */
10
11
  export interface GeneCandidateInput {
11
12
  geneId: string;
@@ -52,12 +53,23 @@ export interface GeneCandidateInput {
52
53
  * fallback only for old candidates that do not carry this field.
53
54
  */
54
55
  generationSource?: GenerationSource;
56
+ /**
57
+ * Assembly-owned UCB1 eligibility. Only trusted approved/legacy-local candidates may set true. Injected callers
58
+ * must not self-assert this bit; candidateAssembly overwrites Hub candidates to false.
59
+ */
60
+ explorationEligible?: boolean;
55
61
  }
56
62
  export interface SelectionInput {
57
63
  signals: readonly string[];
58
64
  candidates: readonly GeneCandidateInput[];
65
+ /** Trust-filtered library corpus captured before relevance admission. Omit for direct strategy callers. */
66
+ semanticCorpus?: readonly GeneCandidateInput[];
67
+ /** Emergency rollback: restore the pre-IDF semantic scorer and skip profile construction. */
68
+ disableSemanticIdf?: boolean;
59
69
  /** 低于此分则不选(→ 走 innovate 新基因), 默认 0. */
60
70
  floor?: number;
71
+ /** Relevance guard rollout. Omit for legacy selector behavior in direct/core callers. */
72
+ selectionGuard?: SelectionGuardMode;
61
73
  /**
62
74
  * Explicit gene requested by GEP / an external runtime. This is a hard selection only within the already
63
75
  * assembled candidate/fallback pools: it cannot resurrect a gene filtered by trust/review/ban upstream, and it is
@@ -70,8 +82,9 @@ export interface SelectionInput {
70
82
  /**
71
83
  * Distilled-gene fallback pool (ported from v1 #97): broadly-applicable distilled genes that do NOT match the
72
84
  * live signals, supplied by the assembly layer (already trust/review/ban-filtered) so they never compete in the
73
- * normal scored set. Used ONLY as a last resort when no candidate clears the floor: instead of falling straight
74
- * through to a blind innovate, selection reuses a known distilled strategy. Epigenetically-suppressed entries
85
+ * normal scored set. Used ONLY after normal selection has no reusable positive choice (the legacy non-positive
86
+ * pool or an enforced no-match guard): instead of falling through to a blind innovate, selection reuses a known
87
+ * distilled strategy. Epigenetically-suppressed entries
75
88
  * (epigeneticPenalty > 0) are skipped — v2's event-log-derived epigeneticPenalty is the analog of v1's asset-mark
76
89
  * hard suppression (a related band, not the identical predicate).
77
90
  */
@@ -87,7 +100,8 @@ export interface SelectionInput {
87
100
  export interface ScoredCandidate {
88
101
  geneId: string;
89
102
  assetId?: string;
90
- score: number;
103
+ score: number; /** Internal expanded match in [0,1]; omitted from root-event candidate payloads. */
104
+ matchScore?: number;
91
105
  reasons: string[];
92
106
  health?: GeneHealth;
93
107
  }
@@ -114,6 +128,36 @@ export interface GeneDecision {
114
128
  selectedReason?: string;
115
129
  /** Bounded structured outcome evidence for prompt enrichment. */
116
130
  memoryEvidence?: MemoryGraphGeneEvidence[];
131
+ /** Deterministic identity of the bounded IDF profile used for this decision. */
132
+ semanticProfileVersion?: string;
133
+ /** Number of trusted semantic documents represented by the profile. */
134
+ semanticDocumentCount?: number;
135
+ /** Compact policy trace; omitted for the default engine-health behavior. */
136
+ selectionPolicy?: SelectionPolicyTrace;
137
+ /** Versioned relevance guard trace; omitted only for explicit legacy rollback. */
138
+ selectionGuard?: SelectionGuardTrace;
139
+ }
140
+ export interface SelectionPolicyTrace {
141
+ requested: Exclude<SelectionPolicy, 'engine-health'>;
142
+ effective: 'engine-health' | 'ucb1';
143
+ selectionPolicyVersion: typeof UCB1_SELECTION_POLICY_VERSION;
144
+ rewardPolicyVersion: typeof UCB1_REWARD_POLICY_VERSION;
145
+ arm?: Ucb1Choice;
146
+ shadowArmId?: string;
147
+ shadowDisagrees?: boolean;
148
+ fallbackReason?: Ucb1FallbackReason;
149
+ }
150
+ export type SelectionGuardMode = 'legacy' | 'shadow' | 'enforce';
151
+ export declare const SELECTION_GUARD_VERSION = "relevance-guard-v1";
152
+ export type SelectionGuardReason = 'no_match' | 'plateau_flat_match';
153
+ export type SelectionGuardStatus = 'allowed' | 'shadow' | 'forced' | 'ucb1' | 'fallback' | 'innovate';
154
+ export interface SelectionGuardTrace {
155
+ mode: Exclude<SelectionGuardMode, 'legacy'>;
156
+ version: typeof SELECTION_GUARD_VERSION;
157
+ status: SelectionGuardStatus;
158
+ reason?: SelectionGuardReason;
159
+ maxMatch?: number;
160
+ matchSpread?: number;
117
161
  }
118
162
  /**
119
163
  * Weight of the preferred-gene confidence factor (fourth factor, positive cross-cycle learning). Kept small so
@@ -129,12 +173,25 @@ export declare const CONFIDENCE_WEIGHT = 0.15;
129
173
  export declare const REUSE_WEIGHT = 0.1;
130
174
  /** Weight of scoped local MemoryGraph outcome evidence. */
131
175
  export declare const MEMORY_GRAPH_WEIGHT = 0.12;
176
+ /** Bounded weight for a canonical task-domain signal match (#628). */
177
+ export declare const TASK_DOMAIN_WEIGHT = 0.08;
178
+ /** signals_match is weak domain evidence; its maximum score contribution is 0.08 * 0.5 = 0.04. */
179
+ export declare const TASK_DOMAIN_SIGNAL_EVIDENCE = 0.5;
132
180
  /**
133
181
  * Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
134
182
  * + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment). Bumped whenever a factor is added so golden
135
183
  * weight snapshots track the change. Composed from the health-weights version so a change to either layer shows.
136
184
  */
137
- export declare const SELECTION_WEIGHTS_VERSION = "sel-4(gh-1,conf=0.15,memory=0.12,reuse=0.1)";
185
+ export declare const LEGACY_SELECTION_WEIGHTS_VERSION = "sel-5-domain(gh-2,conf=0.15,memory=0.12,reuse=0.1,domain=0.08)";
186
+ export declare const SELECTION_WEIGHTS_VERSION = "sel-6-idf-domain(gh-2,conf=0.15,memory=0.12,reuse=0.1,domain=0.08)";
187
+ interface SelectionGuardAssessment {
188
+ wouldAbstain: boolean;
189
+ reason?: SelectionGuardReason;
190
+ maxMatch?: number;
191
+ matchSpread?: number;
192
+ }
193
+ /** Refs #626: identify selections whose relevance is absent or cannot discriminate during a plateau. */
194
+ export declare function assessSelectionGuard(scored: readonly ScoredCandidate[], plateauActive: boolean): SelectionGuardAssessment;
138
195
  /** 实现1: engine 健康分主导(health 0.6 + 信号匹配 0.4). */
139
196
  export declare const engineHealthSelection: Strategy<SelectionInput, GeneDecision>;
140
197
  /** 实现2: 纯信号匹配采样(忽略 health, 对照基线 — 经验主义要可对比). */
@@ -142,4 +199,5 @@ export declare const signalMatchSelection: Strategy<SelectionInput, GeneDecision
142
199
  /** 实现3: agent 主导(注入决策回调; engine 只给候选+分, agent 拍板, D26 agent 一等公民). */
143
200
  export declare function agentLedSelection(pick: (scored: ScoredCandidate[], input: SelectionInput) => string | null): Strategy<SelectionInput, GeneDecision>;
144
201
  /** 选 gene StrategyPoint: 默认 engine-health, 备选 signal-match(+ 可注册 agent-led). */
145
- export declare function makeGeneSelectionPoint(): StrategyPoint<SelectionInput, GeneDecision>;
202
+ export declare function makeGeneSelectionPoint(): StrategyPoint<SelectionInput, GeneDecision>;
203
+ export {};
@@ -1,11 +1,15 @@
1
1
  import { StrategyPoint } from '../strategy/strategyPoint.js';
2
2
  import { geneHealthScore, HEALTH_WEIGHTS_VERSION } from './geneHealth.js';
3
- import { tagOverlapScore, bagCosine } from '../signals/expand.js';
4
- import { driftSelect } from './exploration.js';
3
+ import { bagCosine, buildSemanticIdfProfile, geneTags, idfBagCosine, idfTagOverlapScore, tagOverlapScore, } from '../signals/expand.js';
4
+ import { driftSelect, explorationWindowSize } from './exploration.js';
5
5
  import { isDistilledGeneId } from './geneIntake.js';
6
+ import { chooseUcb1Arm, ucb1StatsForCandidate, UCB1_REWARD_POLICY_VERSION, UCB1_SELECTION_POLICY_VERSION, } from './ucb1.js';
7
+ import { resolveTaskDomainSignals, withoutTaskDomainSignals, } from '../signals/taskDomain.js';
8
+ export const SELECTION_GUARD_VERSION = 'relevance-guard-v1';
6
9
  function decisionWithWarnings(input, decision) {
7
10
  const selected = decision.selectedGeneId
8
- ? decision.candidates.find((candidate) => candidate.geneId === decision.selectedGeneId || candidate.assetId === decision.selectedAssetId)
11
+ ? decision.candidates.find((candidate) => (candidate.geneId === decision.selectedGeneId
12
+ || (decision.selectedAssetId !== undefined && candidate.assetId === decision.selectedAssetId)))
9
13
  : undefined;
10
14
  const enriched = {
11
15
  ...decision,
@@ -17,11 +21,13 @@ function decisionWithWarnings(input, decision) {
17
21
  : enriched;
18
22
  }
19
23
  function signalMatchScore(signals, match) {
20
- if (signals.length === 0 || match.length === 0)
24
+ const genericSignals = withoutTaskDomainSignals(signals);
25
+ const genericMatch = withoutTaskDomainSignals(match);
26
+ if (genericSignals.length === 0 || genericMatch.length === 0)
21
27
  return 0;
22
- const set = new Set(signals);
23
- const hit = match.filter((m) => set.has(m)).length;
24
- return hit / match.length; // 覆盖率
28
+ const set = new Set(genericSignals);
29
+ const hit = genericMatch.filter((m) => set.has(m)).length;
30
+ return hit / genericMatch.length; // 覆盖率
25
31
  }
26
32
  // Weight of the bag-of-words cosine path when it fills the recall gap left by literal + tag overlap.
27
33
  const SEMANTIC_WEIGHT = 0.5;
@@ -39,12 +45,79 @@ export const CONFIDENCE_WEIGHT = 0.15;
39
45
  export const REUSE_WEIGHT = 0.1;
40
46
  /** Weight of scoped local MemoryGraph outcome evidence. */
41
47
  export const MEMORY_GRAPH_WEIGHT = 0.12;
48
+ /** Bounded weight for a canonical task-domain signal match (#628). */
49
+ export const TASK_DOMAIN_WEIGHT = 0.08;
50
+ /** signals_match is weak domain evidence; its maximum score contribution is 0.08 * 0.5 = 0.04. */
51
+ export const TASK_DOMAIN_SIGNAL_EVIDENCE = 0.5;
52
+ const SELECTION_GUARD_MATCH_EPS = 1e-6;
42
53
  /**
43
54
  * Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
44
55
  * + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment). Bumped whenever a factor is added so golden
45
56
  * weight snapshots track the change. Composed from the health-weights version so a change to either layer shows.
46
57
  */
47
- export const SELECTION_WEIGHTS_VERSION = `sel-4(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT})`;
58
+ export const LEGACY_SELECTION_WEIGHTS_VERSION = `sel-5-domain(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT},domain=${TASK_DOMAIN_WEIGHT})`;
59
+ export const SELECTION_WEIGHTS_VERSION = `sel-6-idf-domain(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT},domain=${TASK_DOMAIN_WEIGHT})`;
60
+ const SEMANTIC_CORPUS_LIMIT = 1_000;
61
+ const SEMANTIC_SIGNAL_LIMIT = 16;
62
+ const SEMANTIC_SIGNAL_CHARS = 96;
63
+ const SEMANTIC_GENE_ID_CHARS = 128;
64
+ const SEMANTIC_CATEGORY_CHARS = 96;
65
+ const SEMANTIC_SUMMARY_CHARS = 1_024;
66
+ const SEMANTIC_IDENTITY_CHARS = 256;
67
+ function legacyCandidateSemanticText(candidate) {
68
+ return [
69
+ ...withoutTaskDomainSignals(candidate.signalsMatch),
70
+ candidate.category ?? '',
71
+ candidate.summary ?? '',
72
+ ].join(' ');
73
+ }
74
+ function boundedSignals(signals) {
75
+ const out = [];
76
+ const genericSignals = withoutTaskDomainSignals(signals);
77
+ const limit = Math.min(genericSignals.length, SEMANTIC_SIGNAL_LIMIT);
78
+ for (let index = 0; index < limit; index += 1) {
79
+ const signal = genericSignals[index];
80
+ if (typeof signal === 'string')
81
+ out.push(signal.slice(0, SEMANTIC_SIGNAL_CHARS));
82
+ }
83
+ return out;
84
+ }
85
+ function semanticCandidateProjection(candidate) {
86
+ const signalsMatch = boundedSignals(candidate.signalsMatch);
87
+ const geneId = candidate.geneId.slice(0, SEMANTIC_GENE_ID_CHARS);
88
+ const category = candidate.category?.slice(0, SEMANTIC_CATEGORY_CHARS);
89
+ const summary = candidate.summary?.slice(0, SEMANTIC_SUMMARY_CHARS);
90
+ return {
91
+ tagInput: {
92
+ signalsMatch,
93
+ geneId,
94
+ ...(category ? { category } : {}),
95
+ ...(summary ? { summary } : {}),
96
+ },
97
+ text: [...signalsMatch, category ?? '', summary ?? ''].join(' '),
98
+ hasContent: signalsMatch.some((signal) => signal.trim().length > 0)
99
+ || Boolean(category?.trim())
100
+ || Boolean(summary?.trim()),
101
+ };
102
+ }
103
+ function buildSelectionSemanticProfile(corpus) {
104
+ const documents = [];
105
+ const identities = new Set();
106
+ for (const candidate of corpus) {
107
+ if (documents.length >= SEMANTIC_CORPUS_LIMIT)
108
+ break;
109
+ const identityValue = candidate.assetId ?? candidate.geneId;
110
+ const identity = `${candidate.assetId ? 'asset' : 'gene'}:${identityValue.slice(0, SEMANTIC_IDENTITY_CHARS)}`;
111
+ if (identities.has(identity))
112
+ continue;
113
+ identities.add(identity);
114
+ const projection = semanticCandidateProjection(candidate);
115
+ if (!projection.hasContent)
116
+ continue;
117
+ documents.push({ tags: geneTags(projection.tagInput), text: projection.text });
118
+ }
119
+ return buildSemanticIdfProfile(documents);
120
+ }
48
121
  /**
49
122
  * Match score with semantic signal expansion (ported from v1): literal coverage first, then fill the
50
123
  * recall gap with semantic tag overlap, then with bag-of-words cosine similarity. A perfect literal match
@@ -52,22 +125,40 @@ export const SELECTION_WEIGHTS_VERSION = `sel-4(${HEALTH_WEIGHTS_VERSION},conf=$
52
125
  * for 'repair'), and cosine catches lexical similarity the curated tags miss (e.g. a 'timeout_slow' signal
53
126
  * vs a gene summarized "fix the slow timeout"). Each layer only fills the gap the previous one left.
54
127
  */
55
- function expandedMatchScore(signals, c) {
128
+ function expandedMatchScore(signals, c, semanticProfile) {
56
129
  const literal = signalMatchScore(signals, c.signalsMatch);
57
130
  if (literal >= 1)
58
131
  return { score: 1, literal, tag: 0, cos: 0 };
59
- const tag = tagOverlapScore(signals, { signalsMatch: c.signalsMatch, geneId: c.geneId, category: c.category, summary: c.summary });
132
+ const projection = semanticProfile ? semanticCandidateProjection(c) : undefined;
133
+ const semanticSignals = semanticProfile ? boundedSignals(signals) : withoutTaskDomainSignals(signals);
134
+ const tag = semanticProfile
135
+ ? idfTagOverlapScore(semanticSignals, projection.tagInput, semanticProfile)
136
+ : tagOverlapScore(semanticSignals, {
137
+ signalsMatch: withoutTaskDomainSignals(c.signalsMatch),
138
+ geneId: c.geneId,
139
+ category: c.category,
140
+ summary: c.summary,
141
+ });
60
142
  const base = Math.min(1, literal + (1 - literal) * tag);
61
143
  if (base >= 1)
62
144
  return { score: 1, literal, tag, cos: 0 };
63
- const geneText = [...c.signalsMatch, c.category ?? '', c.summary ?? ''].join(' ');
64
- const cos = bagCosine(signals.join(' '), geneText);
145
+ const cos = semanticProfile
146
+ ? idfBagCosine(semanticSignals.join(' '), projection.text, semanticProfile)
147
+ : bagCosine(semanticSignals.join(' '), legacyCandidateSemanticText(c));
65
148
  return { score: Math.min(1, base + (1 - base) * cos * SEMANTIC_WEIGHT), literal, tag, cos };
66
149
  }
67
- /** Score one candidate under the engine-health weight vector (health 0.6 + 信号匹配 0.4 − epigenetic + confidence). */
68
- function scoreCandidate(signals, c) {
150
+ function taskDomainEvidence(queryDomain, candidateSignals) {
151
+ if (queryDomain.status !== 'resolved')
152
+ return 0;
153
+ const candidateDomain = resolveTaskDomainSignals(candidateSignals);
154
+ return candidateDomain.status === 'resolved' && candidateDomain.slug === queryDomain.slug
155
+ ? TASK_DOMAIN_SIGNAL_EVIDENCE
156
+ : 0;
157
+ }
158
+ /** Score one candidate under the engine-health weight vector, including bounded task-domain evidence. */
159
+ function scoreCandidate(signals, c, semanticProfile, queryDomain) {
69
160
  const health = geneHealthScore(c.view, { reuseCount: c.reuseCount, antiPatternCount: c.antiPatternCount });
70
- const m = expandedMatchScore(signals, c);
161
+ const m = expandedMatchScore(signals, c, semanticProfile);
71
162
  const epi = c.epigeneticPenalty ?? 0;
72
163
  // Confidence is the fourth (positive) factor: a [0,1] preferred-gene edge for this signal fingerprint.
73
164
  // Clamped defensively so an upstream bug cannot turn the soft nudge into a dominant term.
@@ -76,7 +167,10 @@ function scoreCandidate(signals, c) {
76
167
  // into a dominant (or unbounded) term. Absent → 0 → no effect (default-off until a caller injects it).
77
168
  const reuse = Math.max(-1, Math.min(1, c.reuseAdjust ?? 0));
78
169
  const memory = Math.max(-1, Math.min(1, c.memoryBoost ?? 0));
79
- const score = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf + MEMORY_GRAPH_WEIGHT * memory + REUSE_WEIGHT * reuse;
170
+ const domainEvidence = taskDomainEvidence(queryDomain, c.signalsMatch);
171
+ const domainContribution = TASK_DOMAIN_WEIGHT * domainEvidence;
172
+ const score = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf
173
+ + MEMORY_GRAPH_WEIGHT * memory + REUSE_WEIGHT * reuse + domainContribution;
80
174
  const breakdown = m.tag > 0 || m.cos > 0 ? `(literal=${m.literal.toFixed(2)}+tag=${m.tag.toFixed(2)}+cos=${m.cos.toFixed(2)})` : '';
81
175
  const reasons = [`health=${health.score.toFixed(3)}(succ=${health.successRate.toFixed(2)},reuse=${health.reuseCount})`, `信号匹配=${m.score.toFixed(2)}${breakdown}`];
82
176
  if (epi > 0)
@@ -87,7 +181,9 @@ function scoreCandidate(signals, c) {
87
181
  reasons.push(`scoped memory-graph outcome ${memory >= 0 ? '+' : ''}${(MEMORY_GRAPH_WEIGHT * memory).toFixed(3)} (boost=${memory.toFixed(2)})`);
88
182
  if (reuse !== 0)
89
183
  reasons.push(`cross-runtime reuse ${reuse >= 0 ? '+' : ''}${(REUSE_WEIGHT * reuse).toFixed(3)} (sentiment=${reuse.toFixed(2)})`);
90
- return { geneId: c.geneId, ...(c.assetId ? { assetId: c.assetId } : {}), score, health, reasons };
184
+ if (domainContribution > 0)
185
+ reasons.push(`task_domain match +${domainContribution.toFixed(3)} (evidence=${domainEvidence.toFixed(2)})`);
186
+ return { geneId: c.geneId, ...(c.assetId ? { assetId: c.assetId } : {}), score, matchScore: m.score, health, reasons };
91
187
  }
92
188
  function matchesForcedGeneId(c, forcedGeneId) {
93
189
  return c.geneId === forcedGeneId || c.assetId === forcedGeneId;
@@ -101,6 +197,23 @@ function matchesScoredIdentity(a, b) {
101
197
  function withoutCandidateIdentity(scored, c) {
102
198
  return scored.filter((s) => !matchesCandidateIdentity(s, c));
103
199
  }
200
+ function candidateForScore(input, scored) {
201
+ // Eligibility is attached to one assembled candidate, not to every logical alias of the same content.
202
+ // Match the exact scored row so an untrusted alias cannot borrow a trusted candidate's UCB1 admission bit.
203
+ return input.candidates.find((candidate) => (candidate.geneId === scored.geneId && candidate.assetId === scored.assetId));
204
+ }
205
+ function compactMetric(value) {
206
+ return Math.round(value * 1_000_000) / 1_000_000;
207
+ }
208
+ function compactChoice(choice) {
209
+ return {
210
+ ...choice,
211
+ armId: choice.armId.slice(0, 160),
212
+ meanReward: compactMetric(choice.meanReward),
213
+ ...(choice.bonus !== null ? { bonus: compactMetric(choice.bonus) } : { bonus: null }),
214
+ ...(choice.index !== null ? { index: compactMetric(choice.index) } : { index: null }),
215
+ };
216
+ }
104
217
  function isReusableGenerationSource(source) {
105
218
  return source === 'distilled' || source === 'evolved';
106
219
  }
@@ -146,20 +259,150 @@ function forcedSelection(input, scored, scoreForcedFallback, floor) {
146
259
  forcedFallback.reasons.push(`forced gene selected despite floor ${floor}`);
147
260
  return { selectedGeneId: fallback.geneId, ...(fallback.assetId ? { selectedAssetId: fallback.assetId } : {}), scoredForChoice: scoredCandidatesOnly };
148
261
  }
262
+ /** Refs #626: identify selections whose relevance is absent or cannot discriminate during a plateau. */
263
+ export function assessSelectionGuard(scored, plateauActive) {
264
+ if (scored.length === 0) {
265
+ return { wouldAbstain: true, reason: 'no_match', maxMatch: 0, matchSpread: 0 };
266
+ }
267
+ const matches = scored.map((candidate) => candidate.matchScore ?? 0);
268
+ const maxMatch = Math.max(...matches);
269
+ const minMatch = Math.min(...matches);
270
+ const matchSpread = maxMatch - minMatch;
271
+ const metrics = {
272
+ maxMatch: compactMetric(maxMatch),
273
+ matchSpread: compactMetric(matchSpread),
274
+ };
275
+ if (maxMatch <= SELECTION_GUARD_MATCH_EPS) {
276
+ return { wouldAbstain: true, reason: 'no_match', ...metrics };
277
+ }
278
+ if (plateauActive && scored.length > 1 && matchSpread <= SELECTION_GUARD_MATCH_EPS) {
279
+ return { wouldAbstain: true, reason: 'plateau_flat_match', ...metrics };
280
+ }
281
+ return { wouldAbstain: false, ...metrics };
282
+ }
149
283
  /** 实现1: engine 健康分主导(health 0.6 + 信号匹配 0.4). */
150
284
  export const engineHealthSelection = {
151
285
  name: 'engine-health',
152
286
  version: '1',
153
287
  run(input, ctx) {
154
- const scored = input.candidates.map((c) => scoreCandidate(input.signals, c)).sort((a, b) => b.score - a.score);
288
+ const semanticProfile = input.disableSemanticIdf || input.semanticCorpus === undefined
289
+ ? undefined
290
+ : buildSelectionSemanticProfile(input.semanticCorpus);
291
+ const weightsVersion = semanticProfile ? SELECTION_WEIGHTS_VERSION : LEGACY_SELECTION_WEIGHTS_VERSION;
292
+ const semanticMetadata = semanticProfile
293
+ ? {
294
+ semanticProfileVersion: semanticProfile.version,
295
+ semanticDocumentCount: semanticProfile.documentCount,
296
+ }
297
+ : {};
298
+ const queryDomain = resolveTaskDomainSignals(input.signals);
299
+ const scoreForSelection = (candidate) => (scoreCandidate(input.signals, candidate, semanticProfile, queryDomain));
300
+ const scored = input.candidates.map(scoreForSelection).sort((a, b) => b.score - a.score);
155
301
  const floor = input.floor ?? 0;
156
- const forced = forcedSelection(input, scored, (c) => scoreCandidate(input.signals, c), floor);
302
+ const forced = forcedSelection(input, scored, scoreForSelection, floor);
157
303
  const eligible = forced.scoredForChoice.filter((s) => s.score > floor);
158
- // Deterministic top by default; exploration drift picks from the top-N to escape local optima.
159
- const drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
160
- const chosen = eligible[drift.index] ?? eligible[0];
304
+ const guardMode = input.selectionGuard ?? 'legacy';
305
+ const plateauActive = input.exploration?.plateau?.active === true;
306
+ let guardAssessment = assessSelectionGuard(eligible, plateauActive);
307
+ if (eligible.length === 0) {
308
+ const fullAssessment = assessSelectionGuard(forced.scoredForChoice, plateauActive);
309
+ if (fullAssessment.reason !== 'no_match') {
310
+ guardAssessment = {
311
+ wouldAbstain: false,
312
+ ...(fullAssessment.maxMatch !== undefined ? { maxMatch: fullAssessment.maxMatch } : {}),
313
+ ...(fullAssessment.matchSpread !== undefined ? { matchSpread: fullAssessment.matchSpread } : {}),
314
+ };
315
+ }
316
+ }
317
+ const policyEligible = guardMode === 'enforce' && guardAssessment.reason === 'no_match'
318
+ ? []
319
+ : eligible;
320
+ // Deterministic top by default. Plateau exploration either preserves legacy random drift or, when explicitly
321
+ // requested, evaluates UCB1 over the same adaptive top-N window. Forced selection always wins below.
322
+ const requestedPolicy = input.exploration?.policy ?? 'engine-health';
323
+ const ucb1Active = input.exploration?.plateau?.active === true;
324
+ let drift = { driftMode: 'deterministic', index: 0, intensity: 0 };
325
+ let chosen;
326
+ let selectionPolicy;
327
+ if (forced.selectedGeneId === undefined && ucb1Active && requestedPolicy !== 'engine-health') {
328
+ // Canonicalize ties before slicing the UCB window. The legacy/default ranked list intentionally keeps its
329
+ // stable input-order tie behavior; UCB1 must produce the same arm for any permutation of the same pool.
330
+ const ucbRanked = [...policyEligible].sort((left, right) => {
331
+ const leftScore = Number.isFinite(left.score) ? left.score : Number.NEGATIVE_INFINITY;
332
+ const rightScore = Number.isFinite(right.score) ? right.score : Number.NEGATIVE_INFINITY;
333
+ return rightScore !== leftScore
334
+ ? rightScore - leftScore
335
+ : (left.assetId ?? left.geneId).localeCompare(right.assetId ?? right.geneId);
336
+ });
337
+ const window = ucbRanked.slice(0, explorationWindowSize(ucbRanked.length, input.exploration));
338
+ const logicalIdCounts = new Map();
339
+ for (const scored of window) {
340
+ logicalIdCounts.set(scored.geneId, (logicalIdCounts.get(scored.geneId) ?? 0) + 1);
341
+ }
342
+ const history = input.exploration?.ucb1History;
343
+ const result = history
344
+ ? chooseUcb1Arm(window.map((scored) => {
345
+ const candidate = candidateForScore(input, scored);
346
+ const armId = scored.assetId ?? scored.geneId;
347
+ return {
348
+ armId,
349
+ baseScore: scored.score,
350
+ explorationEligible: candidate?.explorationEligible === true,
351
+ stats: candidate
352
+ ? ucb1StatsForCandidate(history, candidate.geneId, candidate.assetId, (logicalIdCounts.get(candidate.geneId) ?? 0) === 1)
353
+ : { armId, pulls: 0, completedPulls: 0, rewardSum: 0, meanReward: 0 },
354
+ };
355
+ }), history.totalPulls)
356
+ : { choice: null, fallbackReason: 'missing_history' };
357
+ const ucbChosen = result.choice
358
+ ? window.find((candidate) => (candidate.assetId ?? candidate.geneId) === result.choice.armId)
359
+ : undefined;
360
+ if (requestedPolicy === 'ucb1' && ucbChosen) {
361
+ chosen = ucbChosen;
362
+ const choice = compactChoice(result.choice);
363
+ chosen.reasons.push('ucb1(index=' + (choice.index ?? 'cold') + ',mean=' + choice.meanReward.toFixed(3) + ',pulls=' + choice.pulls + ')');
364
+ selectionPolicy = {
365
+ requested: requestedPolicy,
366
+ effective: 'ucb1',
367
+ selectionPolicyVersion: UCB1_SELECTION_POLICY_VERSION,
368
+ rewardPolicyVersion: UCB1_REWARD_POLICY_VERSION,
369
+ arm: choice,
370
+ };
371
+ }
372
+ else {
373
+ drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
374
+ chosen = eligible[drift.index] ?? eligible[0];
375
+ selectionPolicy = {
376
+ requested: requestedPolicy,
377
+ effective: 'engine-health',
378
+ selectionPolicyVersion: UCB1_SELECTION_POLICY_VERSION,
379
+ rewardPolicyVersion: UCB1_REWARD_POLICY_VERSION,
380
+ ...(result.choice ? {
381
+ arm: compactChoice(result.choice),
382
+ shadowArmId: result.choice.armId.slice(0, 160),
383
+ shadowDisagrees: Boolean(chosen && (chosen.assetId ?? chosen.geneId) !== result.choice.armId),
384
+ } : {}),
385
+ ...(result.fallbackReason ? { fallbackReason: result.fallbackReason } : {}),
386
+ };
387
+ }
388
+ }
389
+ else {
390
+ drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
391
+ chosen = eligible[drift.index] ?? eligible[0];
392
+ }
393
+ const forcedSelected = forced.selectedGeneId !== undefined;
394
+ const ucb1ResolvedPlateau = guardAssessment.reason === 'plateau_flat_match'
395
+ && selectionPolicy?.effective === 'ucb1';
396
+ const guardApplied = guardMode === 'enforce'
397
+ && !forcedSelected
398
+ && guardAssessment.wouldAbstain
399
+ && !ucb1ResolvedPlateau;
400
+ if (guardApplied) {
401
+ chosen = undefined;
402
+ }
161
403
  let selectedGeneId = forced.selectedGeneId ?? (chosen ? chosen.geneId : null);
162
404
  let selectedAssetId = forced.selectedAssetId ?? chosen?.assetId;
405
+ let fallbackSelected = false;
163
406
  if (!selectedGeneId) {
164
407
  // Issue #97 (ported from v1 selector.js): no candidate matched any live signal (every gene scored <= 0). Rather
165
408
  // than fall straight through to a blind innovate, reuse a *distilled* (or evolved) gene if one is available.
@@ -176,11 +419,14 @@ export const engineHealthSelection = {
176
419
  // scored above zero, NOT merely when nothing cleared a positive `floor`. Otherwise a real but weak signal match
177
420
  // (0 < score <= floor) would be preempted by an unrelated zero-evidence distilled gene; V1 (which has no floor)
178
421
  // keeps such a match. When candidates scored > 0 but below floor, defer to V2's floor policy (innovate).
179
- const noSignalMatch = !forced.forceRejected && forced.scoredForChoice.every((s) => s.score <= 0);
422
+ const normalPoolHasMatch = forced.scoredForChoice.some((candidate) => (candidate.matchScore ?? 0) > SELECTION_GUARD_MATCH_EPS);
423
+ const noSignalMatch = !forced.forceRejected
424
+ && ((guardApplied && guardAssessment.reason === 'no_match' && !normalPoolHasMatch)
425
+ || forced.scoredForChoice.every((candidate) => candidate.score <= 0));
180
426
  const fallback = noSignalMatch
181
427
  ? (input.distilledFallback ?? [])
182
428
  .filter((candidate) => isReusableFallbackCandidate(candidate) && (candidate.epigeneticPenalty ?? 0) === 0)
183
- .map((candidate) => ({ candidate, scored: scoreCandidate(input.signals, candidate) }))
429
+ .map((candidate) => ({ candidate, scored: scoreForSelection(candidate) }))
184
430
  .sort((left, right) => right.scored.score - left.scored.score || left.candidate.geneId.localeCompare(right.candidate.geneId))[0]
185
431
  : undefined;
186
432
  if (fallback) {
@@ -189,15 +435,48 @@ export const engineHealthSelection = {
189
435
  scored.push(fbScored);
190
436
  selectedGeneId = fb.geneId;
191
437
  selectedAssetId = fb.assetId;
438
+ fallbackSelected = true;
192
439
  }
193
- else {
194
- forced.scoredForChoice.forEach((s) => s.reasons.push(`< floor ${floor} → innovate`));
440
+ else if (!guardApplied) {
441
+ forced.scoredForChoice.forEach((candidate) => candidate.reasons.push(`< floor ${floor} → innovate`));
195
442
  }
196
443
  }
197
- else if (forced.selectedGeneId === undefined && drift.driftMode !== 'deterministic') {
444
+ else if (forced.selectedGeneId === undefined && selectionPolicy?.effective !== 'ucb1' && drift.driftMode !== 'deterministic') {
198
445
  scored.find((s) => s.geneId === selectedGeneId)?.reasons.push(`drift(${drift.driftMode},intensity=${drift.intensity.toFixed(2)})`);
199
446
  }
200
- return decisionWithWarnings(input, { selectedGeneId, ...(selectedAssetId ? { selectedAssetId } : {}), candidates: scored, weightsVersion: SELECTION_WEIGHTS_VERSION, strategyName: 'engine-health' });
447
+ if (selectionPolicy?.requested === 'ucb1-shadow' && selectionPolicy.shadowArmId) {
448
+ selectionPolicy.shadowDisagrees = (selectedAssetId ?? selectedGeneId) !== selectionPolicy.shadowArmId;
449
+ }
450
+ const selectionGuard = guardMode === 'legacy'
451
+ ? undefined
452
+ : {
453
+ mode: guardMode,
454
+ version: SELECTION_GUARD_VERSION,
455
+ status: forcedSelected
456
+ ? 'forced'
457
+ : ucb1ResolvedPlateau
458
+ ? 'ucb1'
459
+ : fallbackSelected
460
+ ? 'fallback'
461
+ : guardApplied
462
+ ? 'innovate'
463
+ : guardAssessment.wouldAbstain
464
+ ? 'shadow'
465
+ : 'allowed',
466
+ ...(guardAssessment.wouldAbstain && guardAssessment.reason ? { reason: guardAssessment.reason } : {}),
467
+ ...(guardAssessment.maxMatch !== undefined ? { maxMatch: guardAssessment.maxMatch } : {}),
468
+ ...(guardAssessment.matchSpread !== undefined ? { matchSpread: guardAssessment.matchSpread } : {}),
469
+ };
470
+ return decisionWithWarnings(input, {
471
+ selectedGeneId,
472
+ ...(selectedAssetId ? { selectedAssetId } : {}),
473
+ candidates: scored,
474
+ weightsVersion,
475
+ ...semanticMetadata,
476
+ strategyName: 'engine-health',
477
+ ...(selectionPolicy ? { selectionPolicy } : {}),
478
+ ...(selectionGuard ? { selectionGuard } : {}),
479
+ });
201
480
  },
202
481
  };
203
482
  /** 实现2: 纯信号匹配采样(忽略 health, 对照基线 — 经验主义要可对比). */
@@ -229,14 +508,35 @@ export function agentLedSelection(pick) {
229
508
  run(input, ctx) {
230
509
  const baseDecision = engineHealthSelection.run(input, ctx);
231
510
  const base = baseDecision.candidates;
511
+ const decisionMetadata = {
512
+ weightsVersion: baseDecision.weightsVersion,
513
+ ...(baseDecision.semanticProfileVersion
514
+ ? { semanticProfileVersion: baseDecision.semanticProfileVersion }
515
+ : {}),
516
+ ...(baseDecision.semanticDocumentCount !== undefined
517
+ ? { semanticDocumentCount: baseDecision.semanticDocumentCount }
518
+ : {}),
519
+ };
232
520
  const forced = input.forcedGeneId ? base.find((s) => s.reasons.some((r) => r.includes('forced gene'))) : undefined;
233
521
  if (forced?.reasons.some((r) => r.includes('forced gene selected'))) {
234
- return decisionWithWarnings(input, { selectedGeneId: forced.geneId, ...(forced.assetId ? { selectedAssetId: forced.assetId } : {}), candidates: base, weightsVersion: SELECTION_WEIGHTS_VERSION, strategyName: 'agent-led' });
522
+ return decisionWithWarnings(input, {
523
+ selectedGeneId: forced.geneId,
524
+ ...(forced.assetId ? { selectedAssetId: forced.assetId } : {}),
525
+ candidates: base,
526
+ ...decisionMetadata,
527
+ strategyName: 'agent-led',
528
+ });
235
529
  }
236
530
  const candidatesForPick = forced?.reasons.some((r) => r.includes('forced gene rejected')) ? base.filter((s) => !matchesScoredIdentity(s, forced)) : base;
237
531
  const choice = pick(candidatesForPick, input);
238
532
  const selected = candidatesForPick.find((s) => s.geneId === choice) ?? base.find((s) => s.geneId === choice);
239
- return decisionWithWarnings(input, { selectedGeneId: choice, ...(selected?.assetId ? { selectedAssetId: selected.assetId } : {}), candidates: base, weightsVersion: SELECTION_WEIGHTS_VERSION, strategyName: 'agent-led' });
533
+ return decisionWithWarnings(input, {
534
+ selectedGeneId: choice,
535
+ ...(selected?.assetId ? { selectedAssetId: selected.assetId } : {}),
536
+ candidates: base,
537
+ ...decisionMetadata,
538
+ strategyName: 'agent-led',
539
+ });
240
540
  },
241
541
  };
242
542
  }
@@ -1,5 +1,6 @@
1
1
  export * from './geneHealth.js';
2
2
  export * from './exploration.js';
3
+ export * from './ucb1.js';
3
4
  export * from './epigenetics.js';
4
5
  export * from './confidence.js';
5
6
  export * from './memoryGraph.js';
@@ -8,6 +9,7 @@ export * from './conversationSniffer.js';
8
9
  export * from './geneIntake.js';
9
10
  export * from './candidateAssembly.js';
10
11
  export * from './genePromotion.js';
12
+ export * from './publishEligibility.js';
11
13
  export * from './bans.js';
12
14
  export * from './antiDistill.js';
13
15
  export * from './orchestrator.js';
@@ -1,5 +1,6 @@
1
1
  export * from './geneHealth.js';
2
2
  export * from './exploration.js';
3
+ export * from './ucb1.js';
3
4
  export * from './epigenetics.js';
4
5
  export * from './confidence.js';
5
6
  export * from './memoryGraph.js';
@@ -8,6 +9,7 @@ export * from './conversationSniffer.js';
8
9
  export * from './geneIntake.js';
9
10
  export * from './candidateAssembly.js';
10
11
  export * from './genePromotion.js';
12
+ export * from './publishEligibility.js';
11
13
  export * from './bans.js';
12
14
  export * from './antiDistill.js';
13
15
  export * from './orchestrator.js';