@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.
- package/dist/algo/candidateAssembly.d.ts +7 -2
- package/dist/algo/candidateAssembly.js +81 -13
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/cycleEngine.d.ts +23 -2
- package/dist/algo/cycleEngine.js +236 -36
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +2 -2
- package/dist/algo/geneHealth.js +5 -4
- package/dist/algo/geneSelection.d.ts +63 -5
- package/dist/algo/geneSelection.js +330 -30
- package/dist/algo/index.d.ts +2 -0
- package/dist/algo/index.js +2 -0
- package/dist/algo/orchestrator.d.ts +8 -1
- package/dist/algo/orchestrator.js +15 -3
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +7 -2
- package/dist/assetstore/assetSidecarRecords.js +238 -0
- package/dist/assetstore/assetSidecarRecovery.js +2 -2
- package/dist/assetstore/assetStoreHealth.d.ts +6 -0
- package/dist/assetstore/assetStoreHealth.js +56 -14
- package/dist/assetstore/assetStoreStorage.d.ts +1 -1
- package/dist/assetstore/assetStoreStorage.js +27 -9
- package/dist/assetstore/assetSyncLedger.d.ts +86 -0
- package/dist/assetstore/assetSyncLedger.js +683 -4
- package/dist/assetstore/index.d.ts +1 -0
- package/dist/assetstore/index.js +1 -0
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +4 -2
- package/dist/assetstore/localJsonl.js +89 -9
- package/dist/assetstore/provenance.d.ts +80 -4
- package/dist/assetstore/provenance.js +313 -5
- package/dist/assetstore/provider.d.ts +58 -1
- package/dist/assetstore/provider.js +97 -6
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +4 -3
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +4 -3
- package/dist/benchmark/index.d.ts +2 -1
- package/dist/benchmark/index.js +2 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +2 -1
- package/dist/bootstrap/index.js +2 -1
- package/dist/bootstrap/v1EnvCompat.d.ts +111 -0
- package/dist/bootstrap/v1EnvCompat.js +280 -0
- package/dist/events/eventArchive.d.ts +2 -0
- package/dist/events/eventArchive.js +13 -3
- package/dist/events/eventSchema.d.ts +7 -7
- package/dist/events/eventStore.d.ts +2 -0
- package/dist/events/eventStore.js +5 -1
- package/dist/events/ingest.d.ts +1 -0
- package/dist/events/ingest.js +1 -0
- package/dist/events/paths.d.ts +3 -1
- package/dist/events/paths.js +4 -0
- package/dist/events/public.d.ts +2 -2
- package/dist/events/public.js +2 -2
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +43 -2
- package/dist/exec/autoExec.js +76 -9
- package/dist/exec/autonomousCycle.d.ts +22 -4
- package/dist/exec/autonomousCycle.js +64 -13
- package/dist/exec/claudeBridge.d.ts +32 -7
- package/dist/exec/claudeBridge.js +281 -29
- package/dist/exec/prompt.js +5 -1
- package/dist/exec/runnerRegistry.d.ts +68 -26
- package/dist/exec/runnerRegistry.js +307 -72
- package/dist/exec/selfPr.js +1 -7
- package/dist/feedback/envelope.d.ts +61 -0
- package/dist/feedback/envelope.js +168 -0
- package/dist/feedback/index.d.ts +1 -0
- package/dist/feedback/index.js +1 -0
- package/dist/hooks/hooks.js +1 -0
- package/dist/hub/assetCallLog.d.ts +35 -1
- package/dist/hub/assetCallLog.js +124 -1
- package/dist/hub/bindings.d.ts +8 -1
- package/dist/hub/bindings.js +17 -6
- package/dist/hub/capability.d.ts +90 -3
- package/dist/hub/fake.d.ts +2 -2
- package/dist/hub/fake.js +1 -1
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -1
- package/dist/mailbox/dispatch.d.ts +1 -1
- package/dist/mailbox/dispatch.js +22 -6
- package/dist/mailbox/envelope.d.ts +7 -1
- package/dist/mailbox/envelope.js +9 -2
- package/dist/mailbox/ipcServer.d.ts +10 -2
- package/dist/mailbox/ipcServer.js +163 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- package/dist/ops/evolutionGraphProjection.d.ts +20 -0
- package/dist/ops/evolutionGraphProjection.js +315 -0
- package/dist/ops/index.d.ts +2 -1
- package/dist/ops/index.js +2 -1
- package/dist/ops/selfUpdate.d.ts +8 -0
- package/dist/ops/selfUpdate.js +24 -8
- package/dist/personality/schema.d.ts +16 -16
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +187 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/cycleHistoryFromEvents.js +17 -8
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +169 -1
- package/dist/signals/extractor.d.ts +2 -2
- package/dist/signals/extractor.js +31 -6
- package/dist/signals/index.d.ts +3 -1
- package/dist/signals/index.js +3 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -0
- package/dist/signals/signalGate.js +1 -1
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/strategy/constraintAblation.js +115 -369
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +209 -0
- package/dist/trace/learningTrace.js +276 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/verify/sandboxRunner.d.ts +28 -0
- package/dist/verify/sandboxRunner.js +218 -19
- package/dist/verify/sandboxedValidation.d.ts +9 -0
- package/dist/verify/sandboxedValidation.js +113 -13
- package/dist/verify/validation.d.ts +11 -1
- package/dist/verify/validation.js +31 -0
- 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
|
|
74
|
-
* through to a blind innovate, selection reuses a known
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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(
|
|
23
|
-
const hit =
|
|
24
|
-
return hit /
|
|
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
|
|
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
|
|
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
|
|
64
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
302
|
+
const forced = forcedSelection(input, scored, scoreForSelection, floor);
|
|
157
303
|
const eligible = forced.scoredForChoice.filter((s) => s.score > floor);
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
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
|
|
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:
|
|
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((
|
|
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
|
-
|
|
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, {
|
|
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, {
|
|
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
|
}
|
package/dist/algo/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/algo/index.js
CHANGED
|
@@ -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';
|