@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.22
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/assets/gep/genes.jsonl +5 -5
- package/dist/algo/bans.d.ts +10 -1
- package/dist/algo/bans.js +56 -6
- package/dist/algo/candidateAssembly.d.ts +25 -2
- package/dist/algo/candidateAssembly.js +171 -26
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/conversationSniffer.js +25 -8
- package/dist/algo/cycleEngine.d.ts +63 -5
- package/dist/algo/cycleEngine.js +320 -42
- package/dist/algo/cycleFailureClassifier.d.ts +1 -1
- package/dist/algo/evolutionEvent.d.ts +26 -0
- package/dist/algo/evolutionEvent.js +32 -0
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +36 -3
- package/dist/algo/geneHealth.js +47 -4
- package/dist/algo/geneIntake.d.ts +46 -1
- package/dist/algo/geneIntake.js +121 -10
- package/dist/algo/geneSelection.d.ts +126 -6
- package/dist/algo/geneSelection.js +479 -36
- package/dist/algo/index.d.ts +6 -1
- package/dist/algo/index.js +6 -1
- package/dist/algo/kautoProjection.d.ts +41 -0
- package/dist/algo/kautoProjection.js +95 -0
- package/dist/algo/kautoValidator.d.ts +68 -0
- package/dist/algo/kautoValidator.js +256 -0
- package/dist/algo/memoryGraph.d.ts +62 -0
- package/dist/algo/memoryGraph.js +86 -0
- package/dist/algo/orchestrator.d.ts +17 -1
- package/dist/algo/orchestrator.js +30 -4
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/algo/solidify.d.ts +11 -2
- package/dist/algo/solidify.js +37 -7
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetrepair/hubRejection.d.ts +12 -0
- package/dist/assetrepair/hubRejection.js +109 -0
- package/dist/assetrepair/index.d.ts +2 -0
- package/dist/assetrepair/index.js +2 -0
- package/dist/assetrepair/repair.d.ts +33 -0
- package/dist/assetrepair/repair.js +155 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
- package/dist/assetstore/assetSidecarRecords.js +384 -0
- package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
- package/dist/assetstore/assetSidecarRecovery.js +288 -0
- package/dist/assetstore/assetStoreHealth.d.ts +81 -0
- package/dist/assetstore/assetStoreHealth.js +319 -0
- package/dist/assetstore/assetStoreLayout.d.ts +2 -0
- package/dist/assetstore/assetStoreLayout.js +6 -0
- package/dist/assetstore/assetStoreStorage.d.ts +42 -0
- package/dist/assetstore/assetStoreStorage.js +336 -0
- package/dist/assetstore/assetSyncLedger.d.ts +91 -1
- package/dist/assetstore/assetSyncLedger.js +718 -59
- package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
- package/dist/assetstore/foreignJsonlSource.js +150 -0
- package/dist/assetstore/index.d.ts +5 -0
- package/dist/assetstore/index.js +5 -0
- package/dist/assetstore/learningHistory.js +3 -3
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +12 -1
- package/dist/assetstore/localJsonl.js +251 -42
- package/dist/assetstore/provenance.d.ts +93 -4
- package/dist/assetstore/provenance.js +368 -84
- package/dist/assetstore/provider.d.ts +63 -0
- package/dist/assetstore/provider.js +97 -6
- package/dist/assetstore/reviewFilter.d.ts +19 -1
- package/dist/assetstore/reviewFilter.js +39 -1
- package/dist/assetstore/reviewLedger.d.ts +8 -2
- package/dist/assetstore/reviewLedger.js +71 -45
- package/dist/assetstore/unionReadStore.d.ts +25 -0
- package/dist/assetstore/unionReadStore.js +119 -0
- 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 +3 -1
- package/dist/benchmark/index.js +3 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/benchmark/triggerShift.d.ts +62 -0
- package/dist/benchmark/triggerShift.js +106 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +3 -1
- package/dist/bootstrap/index.js +3 -1
- package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
- package/dist/bootstrap/lifecycleBootstrap.js +433 -0
- package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
- package/dist/bootstrap/v1EnvCompat.js +303 -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 +2 -1
- package/dist/events/ingest.js +15 -0
- package/dist/events/paths.d.ts +12 -10
- package/dist/events/paths.js +24 -20
- 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 +68 -4
- package/dist/exec/autoExec.js +392 -33
- package/dist/exec/autonomousCycle.d.ts +31 -4
- package/dist/exec/autonomousCycle.js +71 -13
- package/dist/exec/claudeBridge.d.ts +80 -15
- package/dist/exec/claudeBridge.js +845 -76
- package/dist/exec/executionBinding.d.ts +414 -0
- package/dist/exec/executionBinding.js +588 -0
- package/dist/exec/index.d.ts +1 -0
- package/dist/exec/index.js +1 -0
- package/dist/exec/openPrRegistry.d.ts +8 -2
- package/dist/exec/openPrRegistry.js +32 -22
- package/dist/exec/prompt.js +14 -1
- package/dist/exec/proofOfWork.d.ts +1 -1
- package/dist/exec/proofOfWork.js +2 -2
- package/dist/exec/runnerRegistry.d.ts +153 -36
- package/dist/exec/runnerRegistry.js +848 -65
- 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 +29 -8
- package/dist/hub/capability.d.ts +130 -4
- package/dist/hub/conversationDistiller.d.ts +19 -0
- package/dist/hub/conversationDistiller.js +115 -37
- package/dist/hub/fake.d.ts +3 -2
- package/dist/hub/fake.js +2 -1
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/hub/recipeCompose.d.ts +27 -0
- package/dist/hub/recipeCompose.js +90 -0
- package/dist/hub/sanitize.js +122 -7
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -1
- package/dist/issueReporter/index.d.ts +156 -0
- package/dist/issueReporter/index.js +1688 -0
- 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 +12 -2
- package/dist/mailbox/ipcServer.js +183 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- package/dist/modelCompatibility.d.ts +164 -0
- package/dist/modelCompatibility.js +309 -0
- package/dist/observers/valueDigestObserver.d.ts +9 -0
- package/dist/observers/valueDigestObserver.js +35 -2
- package/dist/ops/cleanup.js +1 -1
- 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/savingsCore.js +1 -2
- package/dist/ops/selfUpdate.d.ts +18 -1
- package/dist/ops/selfUpdate.js +88 -23
- package/dist/ops/valueOutreach.d.ts +3 -1
- package/dist/ops/valueOutreach.js +5 -1
- package/dist/personality/schema.d.ts +24 -24
- 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/proofOfWork.d.ts +125 -6
- package/dist/schema/proofOfWork.js +102 -4
- package/dist/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- package/dist/shadow/shadowHub.js +1 -0
- 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 +4 -1
- package/dist/signals/index.js +4 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -0
- package/dist/signals/scopeVocabulary.d.ts +75 -0
- package/dist/signals/scopeVocabulary.js +91 -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.d.ts +64 -0
- package/dist/strategy/constraintAblation.js +2820 -0
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/strategy/index.d.ts +2 -1
- package/dist/strategy/index.js +2 -1
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +216 -0
- package/dist/trace/learningTrace.js +298 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/trace/trajectory.d.ts +8 -0
- package/dist/trace/trajectory.js +14 -2
- package/dist/util/fetchPort.d.ts +1 -0
- package/dist/util/fetchPort.js +11 -0
- package/dist/util/fileLock.d.ts +113 -7
- package/dist/util/fileLock.js +1035 -80
- package/dist/util/index.d.ts +3 -1
- package/dist/util/index.js +2 -1
- package/dist/verify/index.d.ts +2 -1
- package/dist/verify/index.js +1 -1
- package/dist/verify/sandboxRunner.d.ts +30 -0
- package/dist/verify/sandboxRunner.js +370 -27
- package/dist/verify/sandboxedValidation.d.ts +16 -2
- package/dist/verify/sandboxedValidation.js +200 -22
- package/dist/verify/validation.d.ts +35 -4
- package/dist/verify/validation.js +164 -16
- package/dist/wire/geneHints.d.ts +88 -16
- package/dist/wire/geneHints.js +124 -15
- package/dist/wire/index.d.ts +8 -3
- package/dist/wire/index.js +2 -2
- package/dist/wire/schemaGate.d.ts +21 -0
- package/dist/wire/schemaGate.js +119 -10
- package/dist/workflow/dsl.d.ts +24 -3
- package/dist/workflow/dsl.js +4 -0
- package/dist/workflow/engine.d.ts +5 -1
- package/dist/workflow/engine.js +3 -0
- package/dist/workflow/index.d.ts +3 -1
- package/dist/workflow/index.js +3 -1
- package/dist/workflow/runtime.d.ts +110 -0
- package/dist/workflow/runtime.js +1298 -0
- package/dist/workflow/stateStore.d.ts +172 -0
- package/dist/workflow/stateStore.js +1044 -0
- package/package.json +12 -4
|
@@ -1,19 +1,33 @@
|
|
|
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) {
|
|
10
|
+
const selected = decision.selectedGeneId
|
|
11
|
+
? decision.candidates.find((candidate) => (candidate.geneId === decision.selectedGeneId
|
|
12
|
+
|| (decision.selectedAssetId !== undefined && candidate.assetId === decision.selectedAssetId)))
|
|
13
|
+
: undefined;
|
|
14
|
+
const enriched = {
|
|
15
|
+
...decision,
|
|
16
|
+
...(selected ? { selectedReason: selected.reasons.join('; ') } : {}),
|
|
17
|
+
...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: [...input.memoryEvidence] } : {}),
|
|
18
|
+
};
|
|
7
19
|
return input.antiWarnings && input.antiWarnings.length > 0
|
|
8
|
-
? { ...
|
|
9
|
-
:
|
|
20
|
+
? { ...enriched, antiWarnings: [...input.antiWarnings] }
|
|
21
|
+
: enriched;
|
|
10
22
|
}
|
|
11
23
|
function signalMatchScore(signals, match) {
|
|
12
|
-
|
|
24
|
+
const genericSignals = withoutTaskDomainSignals(signals);
|
|
25
|
+
const genericMatch = withoutTaskDomainSignals(match);
|
|
26
|
+
if (genericSignals.length === 0 || genericMatch.length === 0)
|
|
13
27
|
return 0;
|
|
14
|
-
const set = new Set(
|
|
15
|
-
const hit =
|
|
16
|
-
return hit /
|
|
28
|
+
const set = new Set(genericSignals);
|
|
29
|
+
const hit = genericMatch.filter((m) => set.has(m)).length;
|
|
30
|
+
return hit / genericMatch.length; // 覆盖率
|
|
17
31
|
}
|
|
18
32
|
// Weight of the bag-of-words cosine path when it fills the recall gap left by literal + tag overlap.
|
|
19
33
|
const SEMANTIC_WEIGHT = 0.5;
|
|
@@ -29,12 +43,90 @@ export const CONFIDENCE_WEIGHT = 0.15;
|
|
|
29
43
|
* ±REUSE_WEIGHT and can never dominate health/signal-match.
|
|
30
44
|
*/
|
|
31
45
|
export const REUSE_WEIGHT = 0.1;
|
|
46
|
+
/** Weight of scoped local MemoryGraph outcome evidence. */
|
|
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
|
+
/**
|
|
53
|
+
* Soft boost for strict K_auto members. Smaller than CONFIDENCE_WEIGHT: membership is a writer-side property,
|
|
54
|
+
* not verified cycle history, so it only breaks near-ties between already-eligible candidates.
|
|
55
|
+
*/
|
|
56
|
+
export const KAUTO_WEIGHT = 0.05;
|
|
57
|
+
/** λ is intentionally NOT inlined into SELECTION_WEIGHTS_VERSION text:
|
|
58
|
+
* decision.gene_selected root events already sit within ~2B of the 4096B line budget
|
|
59
|
+
* under UCB1/plateau fixtures; a `,kauto=0.05` suffix drops selectionPolicy. */
|
|
60
|
+
const SELECTION_GUARD_MATCH_EPS = 1e-6;
|
|
32
61
|
/**
|
|
33
62
|
* Version of the full engine-health weight vector (health 0.6 + signal-match 0.4 − epigenetic penalty
|
|
34
|
-
* + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment
|
|
35
|
-
* weight snapshots track the change. Composed from the
|
|
63
|
+
* + CONFIDENCE_WEIGHT × confidence + REUSE_WEIGHT × reuse-sentiment + KAUTO_WEIGHT × kauto-member).
|
|
64
|
+
* Bumped whenever a factor is added so golden weight snapshots track the change. Composed from the
|
|
65
|
+
* health-weights version so a change to either layer shows.
|
|
36
66
|
*/
|
|
37
|
-
export const
|
|
67
|
+
export const LEGACY_SELECTION_WEIGHTS_VERSION = `sel-6-domain(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT},domain=${TASK_DOMAIN_WEIGHT})`;
|
|
68
|
+
export const SELECTION_WEIGHTS_VERSION = `sel-7-idf-domain(${HEALTH_WEIGHTS_VERSION},conf=${CONFIDENCE_WEIGHT},memory=${MEMORY_GRAPH_WEIGHT},reuse=${REUSE_WEIGHT},domain=${TASK_DOMAIN_WEIGHT})`;
|
|
69
|
+
const SEMANTIC_CORPUS_LIMIT = 1_000;
|
|
70
|
+
const SEMANTIC_SIGNAL_LIMIT = 16;
|
|
71
|
+
const SEMANTIC_SIGNAL_CHARS = 96;
|
|
72
|
+
const SEMANTIC_GENE_ID_CHARS = 128;
|
|
73
|
+
const SEMANTIC_CATEGORY_CHARS = 96;
|
|
74
|
+
const SEMANTIC_SUMMARY_CHARS = 1_024;
|
|
75
|
+
const SEMANTIC_IDENTITY_CHARS = 256;
|
|
76
|
+
function legacyCandidateSemanticText(candidate) {
|
|
77
|
+
return [
|
|
78
|
+
...withoutTaskDomainSignals(candidate.signalsMatch),
|
|
79
|
+
candidate.category ?? '',
|
|
80
|
+
candidate.summary ?? '',
|
|
81
|
+
].join(' ');
|
|
82
|
+
}
|
|
83
|
+
function boundedSignals(signals) {
|
|
84
|
+
const out = [];
|
|
85
|
+
const genericSignals = withoutTaskDomainSignals(signals);
|
|
86
|
+
const limit = Math.min(genericSignals.length, SEMANTIC_SIGNAL_LIMIT);
|
|
87
|
+
for (let index = 0; index < limit; index += 1) {
|
|
88
|
+
const signal = genericSignals[index];
|
|
89
|
+
if (typeof signal === 'string')
|
|
90
|
+
out.push(signal.slice(0, SEMANTIC_SIGNAL_CHARS));
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
function semanticCandidateProjection(candidate) {
|
|
95
|
+
const signalsMatch = boundedSignals(candidate.signalsMatch);
|
|
96
|
+
const geneId = candidate.geneId.slice(0, SEMANTIC_GENE_ID_CHARS);
|
|
97
|
+
const category = candidate.category?.slice(0, SEMANTIC_CATEGORY_CHARS);
|
|
98
|
+
const summary = candidate.summary?.slice(0, SEMANTIC_SUMMARY_CHARS);
|
|
99
|
+
return {
|
|
100
|
+
tagInput: {
|
|
101
|
+
signalsMatch,
|
|
102
|
+
geneId,
|
|
103
|
+
...(category ? { category } : {}),
|
|
104
|
+
...(summary ? { summary } : {}),
|
|
105
|
+
},
|
|
106
|
+
text: [...signalsMatch, category ?? '', summary ?? ''].join(' '),
|
|
107
|
+
hasContent: signalsMatch.some((signal) => signal.trim().length > 0)
|
|
108
|
+
|| Boolean(category?.trim())
|
|
109
|
+
|| Boolean(summary?.trim()),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function buildSelectionSemanticProfile(corpus) {
|
|
113
|
+
const documents = [];
|
|
114
|
+
const identities = new Set();
|
|
115
|
+
for (const candidate of corpus) {
|
|
116
|
+
if (documents.length >= SEMANTIC_CORPUS_LIMIT)
|
|
117
|
+
break;
|
|
118
|
+
const identityValue = candidate.assetId ?? candidate.geneId;
|
|
119
|
+
const identity = `${candidate.assetId ? 'asset' : 'gene'}:${identityValue.slice(0, SEMANTIC_IDENTITY_CHARS)}`;
|
|
120
|
+
if (identities.has(identity))
|
|
121
|
+
continue;
|
|
122
|
+
identities.add(identity);
|
|
123
|
+
const projection = semanticCandidateProjection(candidate);
|
|
124
|
+
if (!projection.hasContent)
|
|
125
|
+
continue;
|
|
126
|
+
documents.push({ tags: geneTags(projection.tagInput), text: projection.text });
|
|
127
|
+
}
|
|
128
|
+
return buildSemanticIdfProfile(documents);
|
|
129
|
+
}
|
|
38
130
|
/**
|
|
39
131
|
* Match score with semantic signal expansion (ported from v1): literal coverage first, then fill the
|
|
40
132
|
* recall gap with semantic tag overlap, then with bag-of-words cosine similarity. A perfect literal match
|
|
@@ -42,22 +134,43 @@ export const SELECTION_WEIGHTS_VERSION = `sel-3(${HEALTH_WEIGHTS_VERSION},conf=$
|
|
|
42
134
|
* for 'repair'), and cosine catches lexical similarity the curated tags miss (e.g. a 'timeout_slow' signal
|
|
43
135
|
* vs a gene summarized "fix the slow timeout"). Each layer only fills the gap the previous one left.
|
|
44
136
|
*/
|
|
45
|
-
function expandedMatchScore(signals, c) {
|
|
137
|
+
function expandedMatchScore(signals, c, semanticProfile) {
|
|
46
138
|
const literal = signalMatchScore(signals, c.signalsMatch);
|
|
47
139
|
if (literal >= 1)
|
|
48
140
|
return { score: 1, literal, tag: 0, cos: 0 };
|
|
49
|
-
const
|
|
141
|
+
const projection = semanticProfile ? semanticCandidateProjection(c) : undefined;
|
|
142
|
+
const semanticSignals = semanticProfile ? boundedSignals(signals) : withoutTaskDomainSignals(signals);
|
|
143
|
+
const tag = semanticProfile
|
|
144
|
+
? idfTagOverlapScore(semanticSignals, projection.tagInput, semanticProfile)
|
|
145
|
+
: tagOverlapScore(semanticSignals, {
|
|
146
|
+
signalsMatch: withoutTaskDomainSignals(c.signalsMatch),
|
|
147
|
+
geneId: c.geneId,
|
|
148
|
+
category: c.category,
|
|
149
|
+
summary: c.summary,
|
|
150
|
+
});
|
|
50
151
|
const base = Math.min(1, literal + (1 - literal) * tag);
|
|
51
152
|
if (base >= 1)
|
|
52
153
|
return { score: 1, literal, tag, cos: 0 };
|
|
53
|
-
const
|
|
54
|
-
|
|
154
|
+
const cos = semanticProfile
|
|
155
|
+
? idfBagCosine(semanticSignals.join(' '), projection.text, semanticProfile)
|
|
156
|
+
: bagCosine(semanticSignals.join(' '), legacyCandidateSemanticText(c));
|
|
55
157
|
return { score: Math.min(1, base + (1 - base) * cos * SEMANTIC_WEIGHT), literal, tag, cos };
|
|
56
158
|
}
|
|
57
|
-
|
|
58
|
-
|
|
159
|
+
function taskDomainEvidence(queryDomain, candidateSignals) {
|
|
160
|
+
if (queryDomain.status !== 'resolved')
|
|
161
|
+
return 0;
|
|
162
|
+
const candidateDomain = resolveTaskDomainSignals(candidateSignals);
|
|
163
|
+
return candidateDomain.status === 'resolved' && candidateDomain.slug === queryDomain.slug
|
|
164
|
+
? TASK_DOMAIN_SIGNAL_EVIDENCE
|
|
165
|
+
: 0;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Score one candidate under the engine-health weight vector, including bounded task-domain evidence.
|
|
169
|
+
* `kautoWeight` defaults to production KAUTO_WEIGHT; ablation passes λ ∈ {0,0.01,0.05,0.1}.
|
|
170
|
+
*/
|
|
171
|
+
function scoreCandidate(signals, c, semanticProfile, queryDomain, kautoWeight = KAUTO_WEIGHT) {
|
|
59
172
|
const health = geneHealthScore(c.view, { reuseCount: c.reuseCount, antiPatternCount: c.antiPatternCount });
|
|
60
|
-
const m = expandedMatchScore(signals, c);
|
|
173
|
+
const m = expandedMatchScore(signals, c, semanticProfile);
|
|
61
174
|
const epi = c.epigeneticPenalty ?? 0;
|
|
62
175
|
// Confidence is the fourth (positive) factor: a [0,1] preferred-gene edge for this signal fingerprint.
|
|
63
176
|
// Clamped defensively so an upstream bug cannot turn the soft nudge into a dominant term.
|
|
@@ -65,16 +178,139 @@ function scoreCandidate(signals, c) {
|
|
|
65
178
|
// Cross-runtime reuse sentiment (#268 phase 1): clamped to [-1,1] so an upstream bug cannot turn the soft nudge
|
|
66
179
|
// into a dominant (or unbounded) term. Absent → 0 → no effect (default-off until a caller injects it).
|
|
67
180
|
const reuse = Math.max(-1, Math.min(1, c.reuseAdjust ?? 0));
|
|
68
|
-
const
|
|
181
|
+
const memory = Math.max(-1, Math.min(1, c.memoryBoost ?? 0));
|
|
182
|
+
const domainEvidence = taskDomainEvidence(queryDomain, c.signalsMatch);
|
|
183
|
+
const domainContribution = TASK_DOMAIN_WEIGHT * domainEvidence;
|
|
184
|
+
// Soft K_auto membership preference: binary, already-admitted candidates only.
|
|
185
|
+
const kauto = c.kautoMember === true ? 1 : 0;
|
|
186
|
+
const kautoContribution = kautoWeight * kauto;
|
|
187
|
+
// score_base is everything except the K_auto soft term — used by λ ablation.
|
|
188
|
+
const scoreBase = 0.6 * health.score + 0.4 * m.score - epi + CONFIDENCE_WEIGHT * conf
|
|
189
|
+
+ MEMORY_GRAPH_WEIGHT * memory + REUSE_WEIGHT * reuse + domainContribution;
|
|
190
|
+
const score = scoreBase + kautoContribution;
|
|
69
191
|
const breakdown = m.tag > 0 || m.cos > 0 ? `(literal=${m.literal.toFixed(2)}+tag=${m.tag.toFixed(2)}+cos=${m.cos.toFixed(2)})` : '';
|
|
70
192
|
const reasons = [`health=${health.score.toFixed(3)}(succ=${health.successRate.toFixed(2)},reuse=${health.reuseCount})`, `信号匹配=${m.score.toFixed(2)}${breakdown}`];
|
|
71
193
|
if (epi > 0)
|
|
72
194
|
reasons.push(`epigenetic 环境抑制 -${epi.toFixed(2)}`);
|
|
73
195
|
if (conf > 0)
|
|
74
196
|
reasons.push(`preferred-gene confidence +${(CONFIDENCE_WEIGHT * conf).toFixed(3)} (conf=${conf.toFixed(2)})`);
|
|
197
|
+
if (memory !== 0)
|
|
198
|
+
reasons.push(`scoped memory-graph outcome ${memory >= 0 ? '+' : ''}${(MEMORY_GRAPH_WEIGHT * memory).toFixed(3)} (boost=${memory.toFixed(2)})`);
|
|
75
199
|
if (reuse !== 0)
|
|
76
200
|
reasons.push(`cross-runtime reuse ${reuse >= 0 ? '+' : ''}${(REUSE_WEIGHT * reuse).toFixed(3)} (sentiment=${reuse.toFixed(2)})`);
|
|
77
|
-
|
|
201
|
+
if (domainContribution > 0)
|
|
202
|
+
reasons.push(`task_domain match +${domainContribution.toFixed(3)} (evidence=${domainEvidence.toFixed(2)})`);
|
|
203
|
+
if (kautoContribution > 0)
|
|
204
|
+
reasons.push(`K_auto member +${kautoContribution.toFixed(3)}`);
|
|
205
|
+
// Keep scoreBase/kautoContribution off ScoredCandidate so decision.gene_selected
|
|
206
|
+
// root-event candidate payloads retain the pre-K_auto 4096B line budget.
|
|
207
|
+
return {
|
|
208
|
+
geneId: c.geneId,
|
|
209
|
+
...(c.assetId ? { assetId: c.assetId } : {}),
|
|
210
|
+
score,
|
|
211
|
+
matchScore: m.score,
|
|
212
|
+
health,
|
|
213
|
+
reasons,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function candidateIdentity(c) {
|
|
217
|
+
return c.assetId ? `asset:${c.assetId}` : `gene:${c.geneId}`;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Offline λ ablation over an already-admitted candidate pool.
|
|
221
|
+
* Does not run hard gates / force / distilled fallback — those stay outside the soft preference.
|
|
222
|
+
* Pure ranking sensitivity for score_T2 = score_base + λ · 1[k_a ∈ K_auto].
|
|
223
|
+
*/
|
|
224
|
+
export function ablateKautoLambda(input, lambdas = [0, 0.01, 0.05, 0.1], opts = {}) {
|
|
225
|
+
const topK = opts.topK ?? 3;
|
|
226
|
+
const floor = opts.floor ?? 0;
|
|
227
|
+
const semanticProfile = input.disableSemanticIdf || input.semanticCorpus === undefined
|
|
228
|
+
? undefined
|
|
229
|
+
: buildSelectionSemanticProfile(input.semanticCorpus);
|
|
230
|
+
const queryDomain = resolveTaskDomainSignals(input.signals);
|
|
231
|
+
const pools = lambdas.map((lambda) => {
|
|
232
|
+
const scored = input.candidates
|
|
233
|
+
.map((c) => {
|
|
234
|
+
const s = scoreCandidate(input.signals, c, semanticProfile, queryDomain, lambda);
|
|
235
|
+
const kautoContribution = (c.kautoMember === true ? 1 : 0) * lambda;
|
|
236
|
+
return {
|
|
237
|
+
geneId: s.geneId,
|
|
238
|
+
...(s.assetId ? { assetId: s.assetId } : {}),
|
|
239
|
+
score: s.score,
|
|
240
|
+
scoreBase: s.score - kautoContribution,
|
|
241
|
+
kautoContribution,
|
|
242
|
+
kautoMember: c.kautoMember === true,
|
|
243
|
+
};
|
|
244
|
+
})
|
|
245
|
+
.sort((a, b) => b.score - a.score || (a.assetId ?? a.geneId).localeCompare(b.assetId ?? b.geneId));
|
|
246
|
+
const eligible = scored.filter((s) => s.score > floor);
|
|
247
|
+
const chosen = eligible[0];
|
|
248
|
+
const bases = scored.map((s) => s.scoreBase);
|
|
249
|
+
const scoreBaseMin = bases.length ? Math.min(...bases) : 0;
|
|
250
|
+
const scoreBaseMax = bases.length ? Math.max(...bases) : 0;
|
|
251
|
+
const scoreBaseMean = bases.length ? bases.reduce((a, b) => a + b, 0) / bases.length : 0;
|
|
252
|
+
return {
|
|
253
|
+
lambda,
|
|
254
|
+
ranking: scored.map((s, index) => ({
|
|
255
|
+
rank: index + 1,
|
|
256
|
+
geneId: s.geneId,
|
|
257
|
+
...(s.assetId ? { assetId: s.assetId } : {}),
|
|
258
|
+
score: s.score,
|
|
259
|
+
scoreBase: s.scoreBase,
|
|
260
|
+
kautoContribution: s.kautoContribution,
|
|
261
|
+
kautoMember: s.kautoMember,
|
|
262
|
+
})),
|
|
263
|
+
selectedGeneId: chosen?.geneId ?? null,
|
|
264
|
+
...(chosen?.assetId ? { selectedAssetId: chosen.assetId } : {}),
|
|
265
|
+
scoreBaseMin,
|
|
266
|
+
scoreBaseMax,
|
|
267
|
+
scoreBaseMean,
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
const baseline = pools.find((p) => p.lambda === 0) ?? pools[0];
|
|
271
|
+
const baselineRank = new Map(baseline?.ranking.map((r) => [candidateIdentity(r), r.rank]) ?? []);
|
|
272
|
+
const baselineTopK = new Set((baseline?.ranking.slice(0, topK) ?? []).map((r) => candidateIdentity(r)));
|
|
273
|
+
const baselineSelected = baseline
|
|
274
|
+
? candidateIdentity({
|
|
275
|
+
geneId: baseline.selectedGeneId ?? '',
|
|
276
|
+
...(baseline.selectedAssetId ? { assetId: baseline.selectedAssetId } : {}),
|
|
277
|
+
})
|
|
278
|
+
: '';
|
|
279
|
+
const rankChangesByLambda = {};
|
|
280
|
+
const selectedChangesByLambda = {};
|
|
281
|
+
const topKChangesByLambda = {};
|
|
282
|
+
for (const pool of pools) {
|
|
283
|
+
const key = String(pool.lambda);
|
|
284
|
+
let rankChanges = 0;
|
|
285
|
+
for (const row of pool.ranking) {
|
|
286
|
+
const id = candidateIdentity(row);
|
|
287
|
+
if ((baselineRank.get(id) ?? -1) !== row.rank)
|
|
288
|
+
rankChanges += 1;
|
|
289
|
+
}
|
|
290
|
+
rankChangesByLambda[key] = rankChanges;
|
|
291
|
+
const selectedId = candidateIdentity({
|
|
292
|
+
geneId: pool.selectedGeneId ?? '',
|
|
293
|
+
...(pool.selectedAssetId ? { assetId: pool.selectedAssetId } : {}),
|
|
294
|
+
});
|
|
295
|
+
selectedChangesByLambda[key] = selectedId !== baselineSelected;
|
|
296
|
+
const topSet = new Set(pool.ranking.slice(0, topK).map((r) => candidateIdentity(r)));
|
|
297
|
+
let topDiff = 0;
|
|
298
|
+
for (const id of topSet)
|
|
299
|
+
if (!baselineTopK.has(id))
|
|
300
|
+
topDiff += 1;
|
|
301
|
+
for (const id of baselineTopK)
|
|
302
|
+
if (!topSet.has(id))
|
|
303
|
+
topDiff += 1;
|
|
304
|
+
topKChangesByLambda[key] = topDiff;
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
lambdas,
|
|
308
|
+
pools,
|
|
309
|
+
rankChangesByLambda,
|
|
310
|
+
selectedChangesByLambda,
|
|
311
|
+
topKChangesByLambda,
|
|
312
|
+
topK,
|
|
313
|
+
};
|
|
78
314
|
}
|
|
79
315
|
function matchesForcedGeneId(c, forcedGeneId) {
|
|
80
316
|
return c.geneId === forcedGeneId || c.assetId === forcedGeneId;
|
|
@@ -88,6 +324,23 @@ function matchesScoredIdentity(a, b) {
|
|
|
88
324
|
function withoutCandidateIdentity(scored, c) {
|
|
89
325
|
return scored.filter((s) => !matchesCandidateIdentity(s, c));
|
|
90
326
|
}
|
|
327
|
+
function candidateForScore(input, scored) {
|
|
328
|
+
// Eligibility is attached to one assembled candidate, not to every logical alias of the same content.
|
|
329
|
+
// Match the exact scored row so an untrusted alias cannot borrow a trusted candidate's UCB1 admission bit.
|
|
330
|
+
return input.candidates.find((candidate) => (candidate.geneId === scored.geneId && candidate.assetId === scored.assetId));
|
|
331
|
+
}
|
|
332
|
+
function compactMetric(value) {
|
|
333
|
+
return Math.round(value * 1_000_000) / 1_000_000;
|
|
334
|
+
}
|
|
335
|
+
function compactChoice(choice) {
|
|
336
|
+
return {
|
|
337
|
+
...choice,
|
|
338
|
+
armId: choice.armId.slice(0, 160),
|
|
339
|
+
meanReward: compactMetric(choice.meanReward),
|
|
340
|
+
...(choice.bonus !== null ? { bonus: compactMetric(choice.bonus) } : { bonus: null }),
|
|
341
|
+
...(choice.index !== null ? { index: compactMetric(choice.index) } : { index: null }),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
91
344
|
function isReusableGenerationSource(source) {
|
|
92
345
|
return source === 'distilled' || source === 'evolved';
|
|
93
346
|
}
|
|
@@ -133,20 +386,150 @@ function forcedSelection(input, scored, scoreForcedFallback, floor) {
|
|
|
133
386
|
forcedFallback.reasons.push(`forced gene selected despite floor ${floor}`);
|
|
134
387
|
return { selectedGeneId: fallback.geneId, ...(fallback.assetId ? { selectedAssetId: fallback.assetId } : {}), scoredForChoice: scoredCandidatesOnly };
|
|
135
388
|
}
|
|
389
|
+
/** Refs #626: identify selections whose relevance is absent or cannot discriminate during a plateau. */
|
|
390
|
+
export function assessSelectionGuard(scored, plateauActive) {
|
|
391
|
+
if (scored.length === 0) {
|
|
392
|
+
return { wouldAbstain: true, reason: 'no_match', maxMatch: 0, matchSpread: 0 };
|
|
393
|
+
}
|
|
394
|
+
const matches = scored.map((candidate) => candidate.matchScore ?? 0);
|
|
395
|
+
const maxMatch = Math.max(...matches);
|
|
396
|
+
const minMatch = Math.min(...matches);
|
|
397
|
+
const matchSpread = maxMatch - minMatch;
|
|
398
|
+
const metrics = {
|
|
399
|
+
maxMatch: compactMetric(maxMatch),
|
|
400
|
+
matchSpread: compactMetric(matchSpread),
|
|
401
|
+
};
|
|
402
|
+
if (maxMatch <= SELECTION_GUARD_MATCH_EPS) {
|
|
403
|
+
return { wouldAbstain: true, reason: 'no_match', ...metrics };
|
|
404
|
+
}
|
|
405
|
+
if (plateauActive && scored.length > 1 && matchSpread <= SELECTION_GUARD_MATCH_EPS) {
|
|
406
|
+
return { wouldAbstain: true, reason: 'plateau_flat_match', ...metrics };
|
|
407
|
+
}
|
|
408
|
+
return { wouldAbstain: false, ...metrics };
|
|
409
|
+
}
|
|
136
410
|
/** 实现1: engine 健康分主导(health 0.6 + 信号匹配 0.4). */
|
|
137
411
|
export const engineHealthSelection = {
|
|
138
412
|
name: 'engine-health',
|
|
139
413
|
version: '1',
|
|
140
414
|
run(input, ctx) {
|
|
141
|
-
const
|
|
415
|
+
const semanticProfile = input.disableSemanticIdf || input.semanticCorpus === undefined
|
|
416
|
+
? undefined
|
|
417
|
+
: buildSelectionSemanticProfile(input.semanticCorpus);
|
|
418
|
+
const weightsVersion = semanticProfile ? SELECTION_WEIGHTS_VERSION : LEGACY_SELECTION_WEIGHTS_VERSION;
|
|
419
|
+
const semanticMetadata = semanticProfile
|
|
420
|
+
? {
|
|
421
|
+
semanticProfileVersion: semanticProfile.version,
|
|
422
|
+
semanticDocumentCount: semanticProfile.documentCount,
|
|
423
|
+
}
|
|
424
|
+
: {};
|
|
425
|
+
const queryDomain = resolveTaskDomainSignals(input.signals);
|
|
426
|
+
const scoreForSelection = (candidate) => (scoreCandidate(input.signals, candidate, semanticProfile, queryDomain));
|
|
427
|
+
const scored = input.candidates.map(scoreForSelection).sort((a, b) => b.score - a.score);
|
|
142
428
|
const floor = input.floor ?? 0;
|
|
143
|
-
const forced = forcedSelection(input, scored,
|
|
429
|
+
const forced = forcedSelection(input, scored, scoreForSelection, floor);
|
|
144
430
|
const eligible = forced.scoredForChoice.filter((s) => s.score > floor);
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
431
|
+
const guardMode = input.selectionGuard ?? 'legacy';
|
|
432
|
+
const plateauActive = input.exploration?.plateau?.active === true;
|
|
433
|
+
let guardAssessment = assessSelectionGuard(eligible, plateauActive);
|
|
434
|
+
if (eligible.length === 0) {
|
|
435
|
+
const fullAssessment = assessSelectionGuard(forced.scoredForChoice, plateauActive);
|
|
436
|
+
if (fullAssessment.reason !== 'no_match') {
|
|
437
|
+
guardAssessment = {
|
|
438
|
+
wouldAbstain: false,
|
|
439
|
+
...(fullAssessment.maxMatch !== undefined ? { maxMatch: fullAssessment.maxMatch } : {}),
|
|
440
|
+
...(fullAssessment.matchSpread !== undefined ? { matchSpread: fullAssessment.matchSpread } : {}),
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const policyEligible = guardMode === 'enforce' && guardAssessment.reason === 'no_match'
|
|
445
|
+
? []
|
|
446
|
+
: eligible;
|
|
447
|
+
// Deterministic top by default. Plateau exploration either preserves legacy random drift or, when explicitly
|
|
448
|
+
// requested, evaluates UCB1 over the same adaptive top-N window. Forced selection always wins below.
|
|
449
|
+
const requestedPolicy = input.exploration?.policy ?? 'engine-health';
|
|
450
|
+
const ucb1Active = input.exploration?.plateau?.active === true;
|
|
451
|
+
let drift = { driftMode: 'deterministic', index: 0, intensity: 0 };
|
|
452
|
+
let chosen;
|
|
453
|
+
let selectionPolicy;
|
|
454
|
+
if (forced.selectedGeneId === undefined && ucb1Active && requestedPolicy !== 'engine-health') {
|
|
455
|
+
// Canonicalize ties before slicing the UCB window. The legacy/default ranked list intentionally keeps its
|
|
456
|
+
// stable input-order tie behavior; UCB1 must produce the same arm for any permutation of the same pool.
|
|
457
|
+
const ucbRanked = [...policyEligible].sort((left, right) => {
|
|
458
|
+
const leftScore = Number.isFinite(left.score) ? left.score : Number.NEGATIVE_INFINITY;
|
|
459
|
+
const rightScore = Number.isFinite(right.score) ? right.score : Number.NEGATIVE_INFINITY;
|
|
460
|
+
return rightScore !== leftScore
|
|
461
|
+
? rightScore - leftScore
|
|
462
|
+
: (left.assetId ?? left.geneId).localeCompare(right.assetId ?? right.geneId);
|
|
463
|
+
});
|
|
464
|
+
const window = ucbRanked.slice(0, explorationWindowSize(ucbRanked.length, input.exploration));
|
|
465
|
+
const logicalIdCounts = new Map();
|
|
466
|
+
for (const scored of window) {
|
|
467
|
+
logicalIdCounts.set(scored.geneId, (logicalIdCounts.get(scored.geneId) ?? 0) + 1);
|
|
468
|
+
}
|
|
469
|
+
const history = input.exploration?.ucb1History;
|
|
470
|
+
const result = history
|
|
471
|
+
? chooseUcb1Arm(window.map((scored) => {
|
|
472
|
+
const candidate = candidateForScore(input, scored);
|
|
473
|
+
const armId = scored.assetId ?? scored.geneId;
|
|
474
|
+
return {
|
|
475
|
+
armId,
|
|
476
|
+
baseScore: scored.score,
|
|
477
|
+
explorationEligible: candidate?.explorationEligible === true,
|
|
478
|
+
stats: candidate
|
|
479
|
+
? ucb1StatsForCandidate(history, candidate.geneId, candidate.assetId, (logicalIdCounts.get(candidate.geneId) ?? 0) === 1)
|
|
480
|
+
: { armId, pulls: 0, completedPulls: 0, rewardSum: 0, meanReward: 0 },
|
|
481
|
+
};
|
|
482
|
+
}), history.totalPulls)
|
|
483
|
+
: { choice: null, fallbackReason: 'missing_history' };
|
|
484
|
+
const ucbChosen = result.choice
|
|
485
|
+
? window.find((candidate) => (candidate.assetId ?? candidate.geneId) === result.choice.armId)
|
|
486
|
+
: undefined;
|
|
487
|
+
if (requestedPolicy === 'ucb1' && ucbChosen) {
|
|
488
|
+
chosen = ucbChosen;
|
|
489
|
+
const choice = compactChoice(result.choice);
|
|
490
|
+
chosen.reasons.push('ucb1(index=' + (choice.index ?? 'cold') + ',mean=' + choice.meanReward.toFixed(3) + ',pulls=' + choice.pulls + ')');
|
|
491
|
+
selectionPolicy = {
|
|
492
|
+
requested: requestedPolicy,
|
|
493
|
+
effective: 'ucb1',
|
|
494
|
+
selectionPolicyVersion: UCB1_SELECTION_POLICY_VERSION,
|
|
495
|
+
rewardPolicyVersion: UCB1_REWARD_POLICY_VERSION,
|
|
496
|
+
arm: choice,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
|
|
501
|
+
chosen = eligible[drift.index] ?? eligible[0];
|
|
502
|
+
selectionPolicy = {
|
|
503
|
+
requested: requestedPolicy,
|
|
504
|
+
effective: 'engine-health',
|
|
505
|
+
selectionPolicyVersion: UCB1_SELECTION_POLICY_VERSION,
|
|
506
|
+
rewardPolicyVersion: UCB1_REWARD_POLICY_VERSION,
|
|
507
|
+
...(result.choice ? {
|
|
508
|
+
arm: compactChoice(result.choice),
|
|
509
|
+
shadowArmId: result.choice.armId.slice(0, 160),
|
|
510
|
+
shadowDisagrees: Boolean(chosen && (chosen.assetId ?? chosen.geneId) !== result.choice.armId),
|
|
511
|
+
} : {}),
|
|
512
|
+
...(result.fallbackReason ? { fallbackReason: result.fallbackReason } : {}),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
drift = driftSelect(eligible.length, input.exploration, ctx?.rng ?? Math.random);
|
|
518
|
+
chosen = eligible[drift.index] ?? eligible[0];
|
|
519
|
+
}
|
|
520
|
+
const forcedSelected = forced.selectedGeneId !== undefined;
|
|
521
|
+
const ucb1ResolvedPlateau = guardAssessment.reason === 'plateau_flat_match'
|
|
522
|
+
&& selectionPolicy?.effective === 'ucb1';
|
|
523
|
+
const guardApplied = guardMode === 'enforce'
|
|
524
|
+
&& !forcedSelected
|
|
525
|
+
&& guardAssessment.wouldAbstain
|
|
526
|
+
&& !ucb1ResolvedPlateau;
|
|
527
|
+
if (guardApplied) {
|
|
528
|
+
chosen = undefined;
|
|
529
|
+
}
|
|
148
530
|
let selectedGeneId = forced.selectedGeneId ?? (chosen ? chosen.geneId : null);
|
|
149
531
|
let selectedAssetId = forced.selectedAssetId ?? chosen?.assetId;
|
|
532
|
+
let fallbackSelected = false;
|
|
150
533
|
if (!selectedGeneId) {
|
|
151
534
|
// Issue #97 (ported from v1 selector.js): no candidate matched any live signal (every gene scored <= 0). Rather
|
|
152
535
|
// than fall straight through to a blind innovate, reuse a *distilled* (or evolved) gene if one is available.
|
|
@@ -163,25 +546,64 @@ export const engineHealthSelection = {
|
|
|
163
546
|
// scored above zero, NOT merely when nothing cleared a positive `floor`. Otherwise a real but weak signal match
|
|
164
547
|
// (0 < score <= floor) would be preempted by an unrelated zero-evidence distilled gene; V1 (which has no floor)
|
|
165
548
|
// keeps such a match. When candidates scored > 0 but below floor, defer to V2's floor policy (innovate).
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
549
|
+
const normalPoolHasMatch = forced.scoredForChoice.some((candidate) => (candidate.matchScore ?? 0) > SELECTION_GUARD_MATCH_EPS);
|
|
550
|
+
const noSignalMatch = !forced.forceRejected
|
|
551
|
+
&& ((guardApplied && guardAssessment.reason === 'no_match' && !normalPoolHasMatch)
|
|
552
|
+
|| forced.scoredForChoice.every((candidate) => candidate.score <= 0));
|
|
553
|
+
const fallback = noSignalMatch
|
|
554
|
+
? (input.distilledFallback ?? [])
|
|
555
|
+
.filter((candidate) => isReusableFallbackCandidate(candidate) && (candidate.epigeneticPenalty ?? 0) === 0)
|
|
556
|
+
.map((candidate) => ({ candidate, scored: scoreForSelection(candidate) }))
|
|
557
|
+
.sort((left, right) => right.scored.score - left.scored.score || left.candidate.geneId.localeCompare(right.candidate.geneId))[0]
|
|
169
558
|
: undefined;
|
|
170
|
-
if (
|
|
171
|
-
const fbScored =
|
|
559
|
+
if (fallback) {
|
|
560
|
+
const { candidate: fb, scored: fbScored } = fallback;
|
|
172
561
|
fbScored.reasons.push('distilled_fallback(#97): 无信号匹配, 低置信度复用蒸馏 gene');
|
|
173
562
|
scored.push(fbScored);
|
|
174
563
|
selectedGeneId = fb.geneId;
|
|
175
564
|
selectedAssetId = fb.assetId;
|
|
565
|
+
fallbackSelected = true;
|
|
176
566
|
}
|
|
177
|
-
else {
|
|
178
|
-
forced.scoredForChoice.forEach((
|
|
567
|
+
else if (!guardApplied) {
|
|
568
|
+
forced.scoredForChoice.forEach((candidate) => candidate.reasons.push(`< floor ${floor} → innovate`));
|
|
179
569
|
}
|
|
180
570
|
}
|
|
181
|
-
else if (forced.selectedGeneId === undefined && drift.driftMode !== 'deterministic') {
|
|
571
|
+
else if (forced.selectedGeneId === undefined && selectionPolicy?.effective !== 'ucb1' && drift.driftMode !== 'deterministic') {
|
|
182
572
|
scored.find((s) => s.geneId === selectedGeneId)?.reasons.push(`drift(${drift.driftMode},intensity=${drift.intensity.toFixed(2)})`);
|
|
183
573
|
}
|
|
184
|
-
|
|
574
|
+
if (selectionPolicy?.requested === 'ucb1-shadow' && selectionPolicy.shadowArmId) {
|
|
575
|
+
selectionPolicy.shadowDisagrees = (selectedAssetId ?? selectedGeneId) !== selectionPolicy.shadowArmId;
|
|
576
|
+
}
|
|
577
|
+
const selectionGuard = guardMode === 'legacy'
|
|
578
|
+
? undefined
|
|
579
|
+
: {
|
|
580
|
+
mode: guardMode,
|
|
581
|
+
version: SELECTION_GUARD_VERSION,
|
|
582
|
+
status: forcedSelected
|
|
583
|
+
? 'forced'
|
|
584
|
+
: ucb1ResolvedPlateau
|
|
585
|
+
? 'ucb1'
|
|
586
|
+
: fallbackSelected
|
|
587
|
+
? 'fallback'
|
|
588
|
+
: guardApplied
|
|
589
|
+
? 'innovate'
|
|
590
|
+
: guardAssessment.wouldAbstain
|
|
591
|
+
? 'shadow'
|
|
592
|
+
: 'allowed',
|
|
593
|
+
...(guardAssessment.wouldAbstain && guardAssessment.reason ? { reason: guardAssessment.reason } : {}),
|
|
594
|
+
...(guardAssessment.maxMatch !== undefined ? { maxMatch: guardAssessment.maxMatch } : {}),
|
|
595
|
+
...(guardAssessment.matchSpread !== undefined ? { matchSpread: guardAssessment.matchSpread } : {}),
|
|
596
|
+
};
|
|
597
|
+
return decisionWithWarnings(input, {
|
|
598
|
+
selectedGeneId,
|
|
599
|
+
...(selectedAssetId ? { selectedAssetId } : {}),
|
|
600
|
+
candidates: scored,
|
|
601
|
+
weightsVersion,
|
|
602
|
+
...semanticMetadata,
|
|
603
|
+
strategyName: 'engine-health',
|
|
604
|
+
...(selectionPolicy ? { selectionPolicy } : {}),
|
|
605
|
+
...(selectionGuard ? { selectionGuard } : {}),
|
|
606
|
+
});
|
|
185
607
|
},
|
|
186
608
|
};
|
|
187
609
|
/** 实现2: 纯信号匹配采样(忽略 health, 对照基线 — 经验主义要可对比). */
|
|
@@ -213,14 +635,35 @@ export function agentLedSelection(pick) {
|
|
|
213
635
|
run(input, ctx) {
|
|
214
636
|
const baseDecision = engineHealthSelection.run(input, ctx);
|
|
215
637
|
const base = baseDecision.candidates;
|
|
638
|
+
const decisionMetadata = {
|
|
639
|
+
weightsVersion: baseDecision.weightsVersion,
|
|
640
|
+
...(baseDecision.semanticProfileVersion
|
|
641
|
+
? { semanticProfileVersion: baseDecision.semanticProfileVersion }
|
|
642
|
+
: {}),
|
|
643
|
+
...(baseDecision.semanticDocumentCount !== undefined
|
|
644
|
+
? { semanticDocumentCount: baseDecision.semanticDocumentCount }
|
|
645
|
+
: {}),
|
|
646
|
+
};
|
|
216
647
|
const forced = input.forcedGeneId ? base.find((s) => s.reasons.some((r) => r.includes('forced gene'))) : undefined;
|
|
217
648
|
if (forced?.reasons.some((r) => r.includes('forced gene selected'))) {
|
|
218
|
-
return decisionWithWarnings(input, {
|
|
649
|
+
return decisionWithWarnings(input, {
|
|
650
|
+
selectedGeneId: forced.geneId,
|
|
651
|
+
...(forced.assetId ? { selectedAssetId: forced.assetId } : {}),
|
|
652
|
+
candidates: base,
|
|
653
|
+
...decisionMetadata,
|
|
654
|
+
strategyName: 'agent-led',
|
|
655
|
+
});
|
|
219
656
|
}
|
|
220
657
|
const candidatesForPick = forced?.reasons.some((r) => r.includes('forced gene rejected')) ? base.filter((s) => !matchesScoredIdentity(s, forced)) : base;
|
|
221
658
|
const choice = pick(candidatesForPick, input);
|
|
222
659
|
const selected = candidatesForPick.find((s) => s.geneId === choice) ?? base.find((s) => s.geneId === choice);
|
|
223
|
-
return decisionWithWarnings(input, {
|
|
660
|
+
return decisionWithWarnings(input, {
|
|
661
|
+
selectedGeneId: choice,
|
|
662
|
+
...(selected?.assetId ? { selectedAssetId: selected.assetId } : {}),
|
|
663
|
+
candidates: base,
|
|
664
|
+
...decisionMetadata,
|
|
665
|
+
strategyName: 'agent-led',
|
|
666
|
+
});
|
|
224
667
|
},
|
|
225
668
|
};
|
|
226
669
|
}
|
package/dist/algo/index.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
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';
|
|
6
|
+
export * from './memoryGraph.js';
|
|
5
7
|
export * from './capabilityCandidates.js';
|
|
6
8
|
export * from './conversationSniffer.js';
|
|
7
9
|
export * from './geneIntake.js';
|
|
8
10
|
export * from './candidateAssembly.js';
|
|
9
11
|
export * from './genePromotion.js';
|
|
12
|
+
export * from './publishEligibility.js';
|
|
10
13
|
export * from './bans.js';
|
|
11
14
|
export * from './antiDistill.js';
|
|
12
15
|
export * from './orchestrator.js';
|
|
@@ -16,4 +19,6 @@ export * from './solidify.js';
|
|
|
16
19
|
export * from './evolutionEvent.js';
|
|
17
20
|
export * from './cycleEngine.js';
|
|
18
21
|
export * from './strategyPresets.js';
|
|
19
|
-
export * from './cycleFailureClassifier.js';
|
|
22
|
+
export * from './cycleFailureClassifier.js';
|
|
23
|
+
export * from './kautoValidator.js';
|
|
24
|
+
export * from './kautoProjection.js';
|