@evomap/evolver-core 2.0.0-beta.8 → 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 (147) 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/conversationSniffer.js +20 -7
  5. package/dist/algo/cycleEngine.d.ts +23 -2
  6. package/dist/algo/cycleEngine.js +236 -36
  7. package/dist/algo/exploration.d.ts +7 -0
  8. package/dist/algo/exploration.js +16 -3
  9. package/dist/algo/geneHealth.d.ts +2 -2
  10. package/dist/algo/geneHealth.js +5 -4
  11. package/dist/algo/geneSelection.d.ts +63 -5
  12. package/dist/algo/geneSelection.js +330 -30
  13. package/dist/algo/index.d.ts +2 -0
  14. package/dist/algo/index.js +2 -0
  15. package/dist/algo/orchestrator.d.ts +8 -1
  16. package/dist/algo/orchestrator.js +15 -3
  17. package/dist/algo/publishEligibility.d.ts +34 -0
  18. package/dist/algo/publishEligibility.js +52 -0
  19. package/dist/algo/ucb1.d.ts +53 -0
  20. package/dist/algo/ucb1.js +156 -0
  21. package/dist/assetstore/assetSidecarRecords.d.ts +7 -2
  22. package/dist/assetstore/assetSidecarRecords.js +238 -0
  23. package/dist/assetstore/assetSidecarRecovery.js +2 -2
  24. package/dist/assetstore/assetStoreHealth.d.ts +6 -0
  25. package/dist/assetstore/assetStoreHealth.js +56 -14
  26. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  27. package/dist/assetstore/assetStoreStorage.js +27 -9
  28. package/dist/assetstore/assetSyncLedger.d.ts +86 -0
  29. package/dist/assetstore/assetSyncLedger.js +683 -4
  30. package/dist/assetstore/index.d.ts +1 -0
  31. package/dist/assetstore/index.js +1 -0
  32. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  33. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  34. package/dist/assetstore/localJsonl.d.ts +4 -2
  35. package/dist/assetstore/localJsonl.js +91 -11
  36. package/dist/assetstore/provenance.d.ts +80 -4
  37. package/dist/assetstore/provenance.js +313 -5
  38. package/dist/assetstore/provider.d.ts +58 -1
  39. package/dist/assetstore/provider.js +97 -6
  40. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  41. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  42. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  43. package/dist/benchmark/antiGeneRollout.js +4 -3
  44. package/dist/benchmark/index.d.ts +2 -1
  45. package/dist/benchmark/index.js +2 -1
  46. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  47. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  48. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  49. package/dist/bootstrap/envFingerprint.js +5 -0
  50. package/dist/bootstrap/index.d.ts +2 -1
  51. package/dist/bootstrap/index.js +2 -1
  52. package/dist/bootstrap/v1EnvCompat.d.ts +111 -0
  53. package/dist/bootstrap/v1EnvCompat.js +280 -0
  54. package/dist/events/eventArchive.d.ts +2 -0
  55. package/dist/events/eventArchive.js +13 -3
  56. package/dist/events/eventSchema.d.ts +7 -7
  57. package/dist/events/eventStore.d.ts +2 -0
  58. package/dist/events/eventStore.js +5 -1
  59. package/dist/events/ingest.d.ts +1 -0
  60. package/dist/events/ingest.js +1 -0
  61. package/dist/events/paths.d.ts +3 -1
  62. package/dist/events/paths.js +4 -0
  63. package/dist/events/public.d.ts +2 -2
  64. package/dist/events/public.js +2 -2
  65. package/dist/events/reports.d.ts +2 -0
  66. package/dist/events/reports.js +4 -0
  67. package/dist/exec/autoExec.d.ts +43 -2
  68. package/dist/exec/autoExec.js +76 -9
  69. package/dist/exec/autonomousCycle.d.ts +22 -4
  70. package/dist/exec/autonomousCycle.js +64 -13
  71. package/dist/exec/claudeBridge.d.ts +32 -7
  72. package/dist/exec/claudeBridge.js +281 -29
  73. package/dist/exec/prompt.js +5 -1
  74. package/dist/exec/runnerRegistry.d.ts +68 -26
  75. package/dist/exec/runnerRegistry.js +307 -72
  76. package/dist/exec/selfPr.js +1 -7
  77. package/dist/feedback/envelope.d.ts +61 -0
  78. package/dist/feedback/envelope.js +168 -0
  79. package/dist/feedback/index.d.ts +1 -0
  80. package/dist/feedback/index.js +1 -0
  81. package/dist/hooks/hooks.js +1 -0
  82. package/dist/hub/assetCallLog.d.ts +35 -1
  83. package/dist/hub/assetCallLog.js +124 -1
  84. package/dist/hub/bindings.d.ts +8 -1
  85. package/dist/hub/bindings.js +17 -6
  86. package/dist/hub/capability.d.ts +90 -3
  87. package/dist/hub/fake.d.ts +2 -2
  88. package/dist/hub/fake.js +1 -1
  89. package/dist/hub/questionGenerator.d.ts +5 -1
  90. package/dist/hub/questionGenerator.js +8 -6
  91. package/dist/hub/sanitize.js +3 -2
  92. package/dist/index.d.ts +3 -1
  93. package/dist/index.js +4 -1
  94. package/dist/mailbox/dispatch.d.ts +1 -1
  95. package/dist/mailbox/dispatch.js +22 -6
  96. package/dist/mailbox/envelope.d.ts +7 -1
  97. package/dist/mailbox/envelope.js +9 -2
  98. package/dist/mailbox/ipcServer.d.ts +10 -2
  99. package/dist/mailbox/ipcServer.js +163 -13
  100. package/dist/mailbox/store.d.ts +89 -3
  101. package/dist/mailbox/store.js +895 -41
  102. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  103. package/dist/ops/evolutionGraphProjection.js +315 -0
  104. package/dist/ops/index.d.ts +2 -1
  105. package/dist/ops/index.js +2 -1
  106. package/dist/ops/selfUpdate.d.ts +8 -0
  107. package/dist/ops/selfUpdate.js +24 -8
  108. package/dist/personality/schema.d.ts +16 -16
  109. package/dist/schema/evolutionGraph.d.ts +784 -0
  110. package/dist/schema/evolutionGraph.js +187 -0
  111. package/dist/schema/index.d.ts +1 -0
  112. package/dist/schema/index.js +1 -0
  113. package/dist/schema/signal.d.ts +3 -3
  114. package/dist/schema/signal.js +1 -1
  115. package/dist/signals/curriculum.d.ts +55 -0
  116. package/dist/signals/curriculum.js +202 -0
  117. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  118. package/dist/signals/expand.d.ts +15 -1
  119. package/dist/signals/expand.js +169 -1
  120. package/dist/signals/extractor.d.ts +2 -2
  121. package/dist/signals/extractor.js +31 -6
  122. package/dist/signals/index.d.ts +3 -1
  123. package/dist/signals/index.js +3 -1
  124. package/dist/signals/metaSignals.d.ts +4 -0
  125. package/dist/signals/metaSignals.js +42 -0
  126. package/dist/signals/signalGate.js +1 -1
  127. package/dist/signals/taskDomain.d.ts +22 -0
  128. package/dist/signals/taskDomain.js +43 -0
  129. package/dist/strategy/constraintAblation.d.ts +64 -0
  130. package/dist/strategy/constraintAblation.js +2820 -0
  131. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  132. package/dist/strategy/constraintAblationPredicates.js +339 -0
  133. package/dist/strategy/index.d.ts +2 -1
  134. package/dist/strategy/index.js +2 -1
  135. package/dist/trace/index.d.ts +3 -1
  136. package/dist/trace/index.js +3 -1
  137. package/dist/trace/learningTrace.d.ts +209 -0
  138. package/dist/trace/learningTrace.js +276 -0
  139. package/dist/trace/proxyTurns.d.ts +31 -0
  140. package/dist/trace/proxyTurns.js +137 -0
  141. package/dist/verify/sandboxRunner.d.ts +28 -0
  142. package/dist/verify/sandboxRunner.js +218 -19
  143. package/dist/verify/sandboxedValidation.d.ts +9 -0
  144. package/dist/verify/sandboxedValidation.js +113 -13
  145. package/dist/verify/validation.d.ts +11 -1
  146. package/dist/verify/validation.js +31 -0
  147. package/package.json +5 -2
@@ -1,4 +1,5 @@
1
- import { canTransition } from '../cycle/stateMachine.js';
1
+ import { LineTooLargeError } from '../events/eventStore.js';
2
+ import { TERMINAL, canTransition, stageForEventType } from '../cycle/stateMachine.js';
2
3
  import {} from './geneSelection.js';
3
4
  import { detectPlateau } from './exploration.js';
4
5
  import { epigeneticPenaltyForIds } from './epigenetics.js';
@@ -11,10 +12,122 @@ import { applySelectForRun, applyStatsUpdate, applyForcePivot } from '../persona
11
12
  import { solidify } from './solidify.js';
12
13
  import { buildEvolutionEvent } from './evolutionEvent.js';
13
14
  import { cycleRecordsFromEvents } from '../signals/cycleHistoryFromEvents.js';
15
+ import { withoutTaskDomainSignals } from '../signals/taskDomain.js';
14
16
  import { computeMetaSignals, deriveCycleHistory } from '../signals/metaSignals.js';
17
+ import { capabilityGapsFromSignals, curriculumOutcomesFromEvents, generateCurriculumSignals, normalizeCapabilityGaps, } from '../signals/curriculum.js';
15
18
  import { resolveStrategy } from './strategyPresets.js';
16
19
  import { classifyCycleFailure, } from './cycleFailureClassifier.js';
17
20
  import { isDistilledGeneId } from './geneIntake.js';
21
+ import { deriveUcb1History } from './ucb1.js';
22
+ function persistedTerminalAfterLatestSelection(ingestor, cycleId) {
23
+ const events = ingestor.readAll();
24
+ let selectedSeq = -1;
25
+ for (let index = events.length - 1; index >= 0; index -= 1) {
26
+ const event = events[index];
27
+ if (event.type === 'decision.gene_selected' && event.payload['cycleId'] === cycleId) {
28
+ selectedSeq = event.seq;
29
+ break;
30
+ }
31
+ }
32
+ if (selectedSeq < 0)
33
+ return null;
34
+ for (let index = events.length - 1; index >= 0; index -= 1) {
35
+ const event = events[index];
36
+ if (event.seq <= selectedSeq)
37
+ break;
38
+ if (event.payload['cycleId'] !== cycleId)
39
+ continue;
40
+ const terminal = stageForEventType(event.type);
41
+ if (terminal && TERMINAL.has(terminal))
42
+ return terminal;
43
+ }
44
+ return null;
45
+ }
46
+ function selectionPolicyEventProjection(trace) {
47
+ return {
48
+ requested: trace.requested,
49
+ effective: trace.effective,
50
+ version: trace.selectionPolicyVersion,
51
+ rewardVersion: trace.rewardPolicyVersion,
52
+ ...(trace.arm ? {
53
+ arm: {
54
+ id: trace.arm.armId,
55
+ pulls: trace.arm.pulls,
56
+ completed: trace.arm.completedPulls,
57
+ total: trace.arm.totalPulls,
58
+ mean: trace.arm.meanReward,
59
+ index: trace.arm.index,
60
+ },
61
+ } : {}),
62
+ ...(trace.shadowDisagrees !== undefined ? { disagrees: trace.shadowDisagrees } : {}),
63
+ ...(trace.fallbackReason ? { fallback: trace.fallbackReason } : {}),
64
+ };
65
+ }
66
+ function minimalSelectionPolicyEventProjection(trace) {
67
+ return {
68
+ requested: trace.requested,
69
+ effective: trace.effective,
70
+ version: trace.selectionPolicyVersion,
71
+ rewardVersion: trace.rewardPolicyVersion,
72
+ };
73
+ }
74
+ function selectionGuardEventProjection(trace) {
75
+ return {
76
+ mode: trace.mode,
77
+ version: trace.version,
78
+ status: trace.status,
79
+ ...(trace.reason ? { reason: trace.reason } : {}),
80
+ ...(trace.maxMatch !== undefined ? { maxMatch: trace.maxMatch } : {}),
81
+ ...(trace.matchSpread !== undefined ? { spread: trace.matchSpread } : {}),
82
+ };
83
+ }
84
+ function minimalSelectionGuardEventProjection(trace) {
85
+ return {
86
+ mode: trace.mode,
87
+ version: trace.version,
88
+ status: trace.status,
89
+ ...(trace.reason ? { reason: trace.reason } : {}),
90
+ };
91
+ }
92
+ function selectionCandidateEventProjection(candidate) {
93
+ const { matchScore: _matchScore, ...persisted } = candidate;
94
+ return persisted;
95
+ }
96
+ async function ingestGeneSelectedWithTraceBudget(ingestor, event, policyTrace, guardTrace) {
97
+ const guard = guardTrace ? selectionGuardEventProjection(guardTrace) : undefined;
98
+ const minimalGuard = guardTrace ? minimalSelectionGuardEventProjection(guardTrace) : undefined;
99
+ const projections = [];
100
+ if (policyTrace) {
101
+ projections.push({ selectionPolicy: selectionPolicyEventProjection(policyTrace), ...(guard ? { selectionGuard: guard } : {}) });
102
+ projections.push({ selectionPolicy: minimalSelectionPolicyEventProjection(policyTrace), ...(guard ? { selectionGuard: guard } : {}) });
103
+ if (minimalGuard) {
104
+ projections.push({ selectionPolicy: minimalSelectionPolicyEventProjection(policyTrace), selectionGuard: minimalGuard });
105
+ projections.push({ selectionGuard: minimalGuard });
106
+ }
107
+ projections.push({ selectionPolicy: minimalSelectionPolicyEventProjection(policyTrace) });
108
+ }
109
+ else if (guard) {
110
+ projections.push({ selectionGuard: guard });
111
+ if (minimalGuard)
112
+ projections.push({ selectionGuard: minimalGuard });
113
+ }
114
+ projections.push({});
115
+ for (let index = 0; index < projections.length; index += 1) {
116
+ const projection = projections[index];
117
+ try {
118
+ await ingestor.ingest({
119
+ type: event.type,
120
+ human: event.human,
121
+ payload: { ...event.payload, ...projection },
122
+ });
123
+ return;
124
+ }
125
+ catch (error) {
126
+ if (!(error instanceof LineTooLargeError) || index === projections.length - 1)
127
+ throw error;
128
+ }
129
+ }
130
+ }
18
131
  /** Read the trailing run of cycle outcomes (newest last) from the event log, for plateau detection. */
19
132
  function recentCycleOutcomes(ingestor, window) {
20
133
  let events;
@@ -106,7 +219,7 @@ function categoryForStrategy(category, strategy) {
106
219
  * Reading from the same append-only log makes this fully replayable. ad-hoc (innovate) cycles have no gene to
107
220
  * credit and are skipped.
108
221
  */
109
- function recentConfidenceObservations(ingestor, window) {
222
+ function recentConfidenceObservations(ingestor, window, opts = {}) {
110
223
  let events;
111
224
  try {
112
225
  events = ingestor.tail(window);
@@ -146,8 +259,9 @@ function recentConfidenceObservations(ingestor, window) {
146
259
  // Older events predate the producedValue tag; when it is absent we treat the cycle as a real success, so we
147
260
  // never retroactively reclassify history into a surprise ban.
148
261
  const status = e.type === 'cycle.failed' ? 'failed' : p?.producedValue === false ? 'inert' : 'success';
262
+ const fingerprintSignals = opts.ignoreTaskDomain ? withoutTaskDomainSignals(signals) : signals;
149
263
  out.push({
150
- signalFingerprint: signalFingerprint(signals),
264
+ signalFingerprint: signalFingerprint(fingerprintSignals),
151
265
  geneId: gene,
152
266
  status,
153
267
  at: Number.isFinite(at) ? at : 0,
@@ -164,14 +278,44 @@ export class CycleEngine {
164
278
  constructor(deps) {
165
279
  this.deps = deps;
166
280
  }
281
+ /**
282
+ * Add V1-compatible curriculum targets from V2's replayable event history. Public so the orchestrator can run
283
+ * it before candidate assembly; runCycle calls it again for direct callers. Set merging makes that idempotent.
284
+ */
285
+ prepareCurriculumSignals(signals) {
286
+ let externalCapabilityGaps = [];
287
+ try {
288
+ externalCapabilityGaps = normalizeCapabilityGaps(this.deps.capabilityGaps?.() ?? []);
289
+ }
290
+ catch {
291
+ // An optional lifecycle-state reader must never suppress local curriculum generation.
292
+ }
293
+ try {
294
+ const localCapabilityGaps = capabilityGapsFromSignals(signals);
295
+ const curriculumSignals = generateCurriculumSignals({
296
+ outcomes: curriculumOutcomesFromEvents(this.deps.ingestor.readAll()),
297
+ // Preserve existing explicit local-signal precedence; Hub gaps fill the otherwise-missing V1 source.
298
+ capabilityGaps: normalizeCapabilityGaps([...localCapabilityGaps, ...externalCapabilityGaps]),
299
+ });
300
+ return { signals: mergeSignals(signals, curriculumSignals), curriculumSignals };
301
+ }
302
+ catch {
303
+ return { signals: [...signals], curriculumSignals: [] };
304
+ }
305
+ }
167
306
  async runCycle(input) {
168
307
  const { ingestor, store } = this.deps;
169
308
  const now = this.deps.now();
170
309
  const envKey = envFingerprintKey((this.deps.envFingerprint ?? captureEnvFingerprint)());
171
310
  const reasons = [];
172
- const baseSignals = input.signals;
311
+ const baseSignals = [...input.signals];
312
+ const preparedSignals = input.curriculumSignals === undefined
313
+ ? this.prepareCurriculumSignals(input.signals)
314
+ : { signals: mergeSignals(input.signals, input.curriculumSignals), curriculumSignals: [...input.curriculumSignals] };
315
+ const selectionSignals = preparedSignals.signals;
316
+ const curriculumSignals = preparedSignals.curriculumSignals;
173
317
  const metaSignals = historyMetaSignals(ingestor, 100);
174
- const cycleSignals = mergeSignals(baseSignals, metaSignals);
318
+ const cycleSignals = mergeSignals(selectionSignals, metaSignals);
175
319
  // PORT v1 #279 issue-reporter: classify cycle.failed payloads when the caller supplies host context. The
176
320
  // post-solidify failed path adds V2-local context (current gene + blast radius) so hard-filtered local no-op
177
321
  // genes are reachable without re-emitting the old ban_gene soft signal.
@@ -221,6 +365,7 @@ export class CycleEngine {
221
365
  cycleId: input.cycleId,
222
366
  signals: cycleSignals,
223
367
  baseSignals,
368
+ ...(curriculumSignals.length > 0 ? { curriculumSignals } : {}),
224
369
  ...(metaSignals.length > 0 ? { metaSignals, strategy: strategy.name } : {}),
225
370
  },
226
371
  });
@@ -250,7 +395,7 @@ export class CycleEngine {
250
395
  // recentEvents 由事件日志的近期 outcome 派生, 让"失败连击触发变异"在环内真正生效.
251
396
  const sel = await applySelectForRun(personalityDeps, {
252
397
  driftEnabled: plateau.active,
253
- signals: baseSignals,
398
+ signals: selectionSignals,
254
399
  recentEvents: recentOutcomes.map((status) => ({ outcome: { status } })),
255
400
  });
256
401
  personalityForRun = sel.state;
@@ -263,8 +408,24 @@ export class CycleEngine {
263
408
  return;
264
409
  await applyStatsUpdate({ store: this.deps.personality, ingestor, cycleId: input.cycleId }, { personality: personalityForRun, outcome, score });
265
410
  };
411
+ let ucb1History;
412
+ if (plateau.active && input.selectionPolicy && input.selectionPolicy !== 'engine-health') {
413
+ try {
414
+ ucb1History = deriveUcb1History(ingestor.readAllStrict());
415
+ }
416
+ catch {
417
+ // Active UCB1 fails safe to legacy drift; shadow records missing_history in its compact policy trace.
418
+ ucb1History = undefined;
419
+ }
420
+ }
266
421
  const exploration = plateau.active
267
- ? { plateau, driftEnabled: true, totalAttempts: input.candidates.reduce((s, c) => s + (c.view?.total ?? 0), 0) }
422
+ ? {
423
+ plateau,
424
+ driftEnabled: true,
425
+ totalAttempts: input.candidates.reduce((s, c) => s + (c.view?.total ?? 0), 0),
426
+ ...(input.selectionPolicy ? { policy: input.selectionPolicy } : {}),
427
+ ...(ucb1History ? { ucb1History } : {}),
428
+ }
268
429
  : undefined;
269
430
  // Epigenetic: penalize candidates that fail in THIS environment class (derived from per-env outcome history).
270
431
  const geneOutcomes = recentGeneOutcomes(ingestor, 200);
@@ -272,7 +433,7 @@ export class CycleEngine {
272
433
  // signal fingerprint. Derived from the same event log, applied as a soft fourth factor in selection.
273
434
  const confidenceObs = recentConfidenceObservations(ingestor, 200);
274
435
  const confidenceEdges = deriveConfidenceEdges(confidenceObs);
275
- const currentFingerprint = signalFingerprint(baseSignals);
436
+ const currentConfidenceFingerprint = signalFingerprint(baseSignals);
276
437
  // #195: drop genes stuck producing only inert (zero-work) cycles on THIS signal. A sole-matching do-nothing
277
438
  // gene is otherwise re-selected every cycle (drift can't diversify a single candidate) and the failure-streak
278
439
  // ban never fires because nothing "fails" — so it dominates --loop forever while producing no artifacts.
@@ -282,46 +443,85 @@ export class CycleEngine {
282
443
  // cycles evict its inert run (or the earlier real success that exempts it) and the ban would flap. Recomputing
283
444
  // it over INERT_BAN_OBSERVATION_WINDOW events (mirroring v1's memory-graph read horizon) keeps the trailing run
284
445
  // and the successCount exemption alive across intervening cycles. tail() reads the full log either way (no I/O cost).
285
- const inertBanObs = recentConfidenceObservations(ingestor, INERT_BAN_OBSERVATION_WINDOW);
286
- const inertBanned = inertBannedGeneIds(deriveConfidenceEdges(inertBanObs), inertBanObs, currentFingerprint);
446
+ const inertBanObs = recentConfidenceObservations(ingestor, INERT_BAN_OBSERVATION_WINDOW, { ignoreTaskDomain: true });
447
+ const inertBanFingerprint = signalFingerprint(withoutTaskDomainSignals(baseSignals));
448
+ const inertBanned = inertBannedGeneIds(deriveConfidenceEdges(inertBanObs), inertBanObs, inertBanFingerprint);
287
449
  const candidates = input.candidates
288
450
  .filter((c) => !isCandidateInSet(c, inertBanned))
289
451
  .map((c) => {
290
452
  const epi = epigeneticPenaltyForIds(candidateIds(c), envKey, geneOutcomes);
291
- const conf = confidenceFor(confidenceEdges, currentFingerprint, c.geneId, now);
453
+ const conf = confidenceFor(confidenceEdges, currentConfidenceFingerprint, c.geneId, now);
292
454
  const withEpi = epi > 0 ? { ...c, epigeneticPenalty: epi } : c;
293
455
  return conf > 0 ? { ...withEpi, confidence: conf } : withEpi;
294
456
  });
295
457
  // #97 distilled-gene fallback pool: attach the env-scoped epigenetic penalty (so selection can hard-skip a
296
458
  // suppressed fallback, v1 parity) and drop any inert-banned id (composes with #195). Selection uses these only
297
- // when no normal candidate clears the floor.
459
+ // when normal selection has no reusable positive choice.
298
460
  const distilledFallback = (input.distilledFallback ?? [])
299
461
  .filter((c) => !isCandidateInSet(c, inertBanned))
300
462
  .map((c) => {
301
463
  const epi = epigeneticPenaltyForIds(candidateIds(c), envKey, geneOutcomes);
302
464
  return epi > 0 ? { ...c, epigeneticPenalty: epi } : c;
303
465
  });
304
- const decision = (await this.deps.selection.run({ signals: baseSignals, candidates, floor: input.selectionFloor, ...(input.forcedGeneId !== undefined ? { forcedGeneId: input.forcedGeneId } : {}), ...(exploration ? { exploration } : {}), ...(distilledFallback.length > 0 ? { distilledFallback } : {}), ...(input.antiWarnings && input.antiWarnings.length > 0 ? { antiWarnings: input.antiWarnings } : {}), ...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: input.memoryEvidence } : {}) }, { now, cycleId: input.cycleId, ...(this.deps.rng ? { rng: this.deps.rng } : {}) }));
305
- await ingestor.ingest({
466
+ const semanticCorpus = input.semanticCorpus?.filter((c) => !isCandidateInSet(c, inertBanned));
467
+ const decision = (await this.deps.selection.run({ signals: selectionSignals, candidates, ...(semanticCorpus ? { semanticCorpus } : {}), ...(input.disableSemanticIdf ? { disableSemanticIdf: true } : {}), floor: input.selectionFloor, ...(input.selectionGuard ? { selectionGuard: input.selectionGuard } : {}), ...(input.forcedGeneId !== undefined ? { forcedGeneId: input.forcedGeneId } : {}), ...(exploration ? { exploration } : {}), ...(distilledFallback.length > 0 ? { distilledFallback } : {}), ...(input.antiWarnings && input.antiWarnings.length > 0 ? { antiWarnings: input.antiWarnings } : {}), ...(input.memoryEvidence && input.memoryEvidence.length > 0 ? { memoryEvidence: input.memoryEvidence } : {}) }, { now, cycleId: input.cycleId, ...(this.deps.rng ? { rng: this.deps.rng } : {}) }));
468
+ const geneId = decision.selectedGeneId ?? 'ad-hoc';
469
+ const runPostSelectionStep = async (phase, step) => {
470
+ try {
471
+ return await step();
472
+ }
473
+ catch (error) {
474
+ let persistedTerminal = null;
475
+ try {
476
+ persistedTerminal = persistedTerminalAfterLatestSelection(ingestor, input.cycleId);
477
+ }
478
+ catch {
479
+ // Readback is best-effort; compensation below remains the fallback when the log is unavailable.
480
+ }
481
+ if (persistedTerminal && !TERMINAL.has(stage) && canTransition(stage, persistedTerminal)) {
482
+ advance(persistedTerminal);
483
+ }
484
+ if (!persistedTerminal && !TERMINAL.has(stage)) {
485
+ try {
486
+ await ingestor.ingest({
487
+ type: 'cycle.failed',
488
+ human: { title: `cycle ${input.cycleId} selection 后异常` },
489
+ payload: { cycleId: input.cycleId, reason: 'post_selection_error', phase, gene: geneId, env: envKey },
490
+ });
491
+ advance('failed');
492
+ try {
493
+ await recordPersonalityOutcome('failed', null);
494
+ }
495
+ catch {
496
+ // Personality is soft state; never let its writeback replace the original failure.
497
+ }
498
+ }
499
+ catch {
500
+ // The event store itself may be unavailable. Preserve and rethrow the original step failure.
501
+ }
502
+ }
503
+ throw error;
504
+ }
505
+ };
506
+ await runPostSelectionStep('decision_event', () => ingestGeneSelectedWithTraceBudget(ingestor, {
306
507
  type: 'decision.gene_selected',
307
508
  human: { title: `选 gene ${decision.selectedGeneId ?? '(innovate)'}`, why: decision.candidates.map((c) => `${c.geneId}:${c.score.toFixed(3)}`).join(', ') || '无候选→innovate' },
308
- payload: { cycleId: input.cycleId, selectedGeneId: decision.selectedGeneId, ...(decision.selectedAssetId ? { selectedAssetId: decision.selectedAssetId } : {}), ...(decision.selectedReason ? { selectedReason: decision.selectedReason } : {}), candidates: decision.candidates, ...(decision.antiWarnings && decision.antiWarnings.length > 0 ? { antiWarnings: decision.antiWarnings } : {}), ...(decision.memoryEvidence && decision.memoryEvidence.length > 0 ? { memoryEvidence: decision.memoryEvidence } : {}), weightsVersion: decision.weightsVersion, strategy: decision.strategyName, strategyPreset: strategy.name, ...(plateau.active ? { plateau } : {}), ...(inertBanned.size > 0 ? { inertBanned: [...inertBanned] } : {}) },
309
- });
509
+ payload: { cycleId: input.cycleId, selectedGeneId: decision.selectedGeneId, ...(decision.selectedAssetId ? { selectedAssetId: decision.selectedAssetId } : {}), ...(decision.selectedReason ? { selectedReason: decision.selectedReason } : {}), candidates: decision.candidates.map(selectionCandidateEventProjection), ...(decision.antiWarnings && decision.antiWarnings.length > 0 ? { antiWarnings: decision.antiWarnings } : {}), ...(decision.memoryEvidence && decision.memoryEvidence.length > 0 ? { memoryEvidence: decision.memoryEvidence } : {}), weightsVersion: decision.weightsVersion, ...(decision.semanticProfileVersion ? { semanticProfileVersion: decision.semanticProfileVersion } : {}), ...(decision.semanticDocumentCount !== undefined ? { semanticDocumentCount: decision.semanticDocumentCount } : {}), strategy: decision.strategyName, strategyPreset: strategy.name, ...(plateau.active ? { plateau } : {}), ...(inertBanned.size > 0 ? { inertBanned: [...inertBanned] } : {}) },
510
+ }, decision.selectionPolicy, decision.selectionGuard));
310
511
  advance('gene_selected');
311
- const geneId = decision.selectedGeneId ?? 'ad-hoc';
312
512
  // 变异
313
- const builtMutation = buildMutation({ decision, category: cycleCategory, signals: cycleSignals, target: input.target, expectedEffect: input.expectedEffect });
513
+ const builtMutation = await runPostSelectionStep('mutation_build', () => buildMutation({ decision, category: cycleCategory, signals: cycleSignals, target: input.target, expectedEffect: input.expectedEffect }));
314
514
  // 人格风险闸(用途②): 高危人格禁 innovate / 未达阈值的 high-risk 降级. 未注入人格 ⇒ 原样放行.
315
515
  // 用本轮 applySelectForRun 选出的状态(personalityForRun), 而非再读一次 store.currentState() ——
316
516
  // 保证"喂 prompt 的风格 / 过风险闸 / 回写统计"三者用的是同一个当轮人格.
317
517
  let mutation = builtMutation;
318
518
  if (this.deps.personality && personalityForRun) {
319
- await emitPersonalitySelected(ingestor, { cycleId: input.cycleId, personality: personalityForRun, known: personalityKnown });
320
- const gate = gatePersonalityRisk(builtMutation, personalityForRun);
519
+ await runPostSelectionStep('personality_selected', () => emitPersonalitySelected(ingestor, { cycleId: input.cycleId, personality: personalityForRun, known: personalityKnown }));
520
+ const gate = await runPostSelectionStep('personality_risk_gate', () => gatePersonalityRisk(builtMutation, personalityForRun));
321
521
  mutation = gate.mutation;
322
- await emitPersonalityRiskGated(ingestor, { cycleId: input.cycleId, personality: personalityForRun, gate });
522
+ await runPostSelectionStep('personality_risk_gate', () => emitPersonalityRiskGated(ingestor, { cycleId: input.cycleId, personality: personalityForRun, gate }));
323
523
  }
324
- await ingestor.ingest({ type: 'mutation.built', human: { title: `变异 ${mutation.category} → ${mutation.target}` }, payload: { cycleId: input.cycleId, mutationId: mutation.id, risk: mutation.risk_level, category: mutation.category, strategyPreset: strategy.name } });
524
+ await runPostSelectionStep('mutation_event', () => ingestor.ingest({ type: 'mutation.built', human: { title: `变异 ${mutation.category} → ${mutation.target}` }, payload: { cycleId: input.cycleId, mutationId: mutation.id, risk: mutation.risk_level, category: mutation.category, strategyPreset: strategy.name } }));
325
525
  advance('mutation_built');
326
526
  // 执行(agent/runtime)
327
527
  let exec;
@@ -332,7 +532,7 @@ export class CycleEngine {
332
532
  const msg = e instanceof Error ? e.message : String(e);
333
533
  // execute threw before returning a transcript; the error text IS the host context (a provider error that
334
534
  // surfaces as a thrown exception still classifies as host_provider_error). Pass it as the sessionLog.
335
- await ingestor.ingest({ type: 'cycle.failed', human: { title: `cycle ${input.cycleId} 执行抛错` }, payload: { cycleId: input.cycleId, error: msg, gene: geneId, env: envKey, ...failureClassPayload({ geneId }, msg) } });
535
+ await runPostSelectionStep('terminal_event', () => ingestor.ingest({ type: 'cycle.failed', human: { title: `cycle ${input.cycleId} 执行抛错` }, payload: { cycleId: input.cycleId, error: msg, gene: geneId, env: envKey, ...failureClassPayload({ geneId }, msg) } }));
336
536
  advance('failed');
337
537
  await recordPersonalityOutcome('failed', null);
338
538
  return { cycleId: input.cycleId, triggered: true, finalStage: stage, producedValue: false, decision, mutation, reasons: [...reasons, `执行异常: ${msg}`] };
@@ -356,7 +556,7 @@ export class CycleEngine {
356
556
  }
357
557
  if (!permit.ok) {
358
558
  const reason = `solidify permit denied: ${permit.reason}`;
359
- await ingestor.ingest({
559
+ await runPostSelectionStep('terminal_event', () => ingestor.ingest({
360
560
  type: 'cycle.failed',
361
561
  human: { title: `cycle ${input.cycleId} permit denied` },
362
562
  payload: {
@@ -367,30 +567,30 @@ export class CycleEngine {
367
567
  ...executionMetadata(exec),
368
568
  ...failureClassPayload({ geneId }),
369
569
  },
370
- });
570
+ }));
371
571
  advance('failed');
372
572
  await recordPersonalityOutcome('failed', null);
373
573
  return { cycleId: input.cycleId, triggered: true, finalStage: stage, producedValue: false, decision, mutation, ...executionMetadata(exec), reasons: [...reasons, reason] };
374
574
  }
375
575
  }
376
576
  // solidify → Capsule
377
- const sol = solidify({
577
+ const sol = await runPostSelectionStep('solidify', () => solidify({
378
578
  geneId, trigger: cycleSignals, summary: input.summary, confidence: input.confidence,
379
579
  outcome: exec.outcome, proofOfWork: exec.proofOfWork, strongEvidence: exec.strongEvidence,
380
- });
580
+ }));
381
581
  reasons.push(...sol.reasons);
382
- await store.put(sol.capsule);
383
- await ingestor.ingest({ type: 'capsule.produced', human: { title: `Capsule ${sol.resolutionStatus}` }, payload: { cycleId: input.cycleId, capsuleId: sol.capsule.asset_id, resolutionStatus: sol.resolutionStatus, producedValue: sol.producedValue, blastRadius: sol.capsule.blast_radius } });
582
+ await runPostSelectionStep('capsule_store', () => store.put(sol.capsule));
583
+ await runPostSelectionStep('capsule_event', () => ingestor.ingest({ type: 'capsule.produced', human: { title: `Capsule ${sol.resolutionStatus}` }, payload: { cycleId: input.cycleId, capsuleId: sol.capsule.asset_id, resolutionStatus: sol.resolutionStatus, producedValue: sol.producedValue, blastRadius: sol.capsule.blast_radius } }));
384
584
  // EvolutionEvent(capsule_id 引 Capsule; outcome 真值在此)
385
- const event = buildEvolutionEvent({
585
+ const event = await runPostSelectionStep('evolution_event_build', () => buildEvolutionEvent({
386
586
  intent: cycleCategory, signals: cycleSignals, genesUsed: decision.selectedGeneId ? [decision.selectedGeneId] : [],
387
587
  mutationId: mutation.id, blastRadius: sol.capsule.blast_radius, outcome: exec.outcome,
388
588
  capsuleId: sol.capsule.asset_id, sourceType: decision.selectedGeneId ? 'reused' : 'generated',
389
- });
390
- await store.put(event);
391
- await ingestor.ingest({ type: 'evolution_event.projected', human: { title: `世代记录 ${exec.outcome.status}` }, payload: { cycleId: input.cycleId, eventId: event.asset_id, capsuleId: sol.capsule.asset_id, outcome: exec.outcome } });
589
+ }));
590
+ await runPostSelectionStep('evolution_event_store', () => store.put(event));
591
+ await runPostSelectionStep('evolution_event_projection', () => ingestor.ingest({ type: 'evolution_event.projected', human: { title: `世代记录 ${exec.outcome.status}` }, payload: { cycleId: input.cycleId, eventId: event.asset_id, capsuleId: sol.capsule.asset_id, outcome: exec.outcome } }));
392
592
  if (exec.outcome.status === 'failed') {
393
- await ingestor.ingest({
593
+ await runPostSelectionStep('terminal_event', () => ingestor.ingest({
394
594
  type: 'cycle.failed',
395
595
  human: { title: `cycle ${input.cycleId} 失败` },
396
596
  payload: {
@@ -411,11 +611,11 @@ export class CycleEngine {
411
611
  : {}),
412
612
  }, exec.sessionLog),
413
613
  },
414
- });
614
+ }));
415
615
  advance('failed');
416
616
  }
417
617
  else {
418
- await ingestor.ingest({ type: 'cycle.solidified', human: { title: `cycle ${input.cycleId} 固化` }, payload: { cycleId: input.cycleId, capsuleId: sol.capsule.asset_id, outcome: exec.outcome, gene: geneId, env: envKey, producedValue: sol.producedValue } });
618
+ await runPostSelectionStep('terminal_event', () => ingestor.ingest({ type: 'cycle.solidified', human: { title: `cycle ${input.cycleId} 固化` }, payload: { cycleId: input.cycleId, capsuleId: sol.capsule.asset_id, outcome: exec.outcome, gene: geneId, env: envKey, producedValue: sol.producedValue } }));
419
619
  advance('solidified');
420
620
  }
421
621
  // 人格统计回写(用途②): 用本轮真实 outcome/score 回写当轮人格桶, 供下一轮自然选择靠拢. 未注入 ⇒ no-op.
@@ -1,3 +1,4 @@
1
+ import type { SelectionPolicy, Ucb1History } from './ucb1.js';
1
2
  export type PlateauSeverity = 'suggested' | 'required';
2
3
  export interface PlateauState {
3
4
  active: boolean;
@@ -6,12 +7,16 @@ export interface PlateauState {
6
7
  }
7
8
  /** Exploration control fed into selection. Absent → pure deterministic top-score selection. */
8
9
  export interface ExplorationInput {
10
+ /** Selection policy requested by the composition layer. Default preserves legacy engine-health + drift. */
11
+ policy?: SelectionPolicy;
9
12
  /** Force drift on even without a plateau (e.g. an explicit explore policy). */
10
13
  driftEnabled?: boolean;
11
14
  /** Plateau detected upstream; an active plateau forces drift intensity up. */
12
15
  plateau?: PlateauState;
13
16
  /** Total attempts across the candidate pool (drives the maturity decay of the drift offset). */
14
17
  totalAttempts?: number;
18
+ /** Event-log-derived state for UCB1. Undefined means fail-safe to legacy drift. */
19
+ ucb1History?: Ucb1History;
15
20
  }
16
21
  /**
17
22
  * Adaptive drift intensity (ported v1): 1/sqrt(Ne) + an offset that decays from 0.3 → 0.02 as the pool
@@ -25,6 +30,8 @@ export interface DriftDecision {
25
30
  index: number;
26
31
  intensity: number;
27
32
  }
33
+ /** Deterministic top-N window shared by legacy random drift and UCB1. */
34
+ export declare function explorationWindowSize(eligibleCount: number, exp: ExplorationInput | undefined): number;
28
35
  /**
29
36
  * Pick which ranked candidate to take among `eligibleCount` above-floor candidates.
30
37
  * No exploration → deterministic top (index 0). With exploration → compute intensity (an active plateau
@@ -15,8 +15,9 @@ const PLATEAU_FORCE = 10;
15
15
  export function computeDriftIntensity(ne, totalAttempts = 0) {
16
16
  if (!Number.isFinite(ne) || ne <= 1)
17
17
  return 0.7;
18
+ const attempts = Number.isFinite(totalAttempts) ? Math.max(0, totalAttempts) : 0;
18
19
  const maturityThreshold = ne * MATURITY_ATTEMPTS_PER_GENE;
19
- const maturity = maturityThreshold > 0 ? Math.min(1, totalAttempts / maturityThreshold) : 0;
20
+ const maturity = maturityThreshold > 0 ? Math.min(1, attempts / maturityThreshold) : 0;
20
21
  const offset = DRIFT_OFFSET_MAX - (DRIFT_OFFSET_MAX - DRIFT_OFFSET_MIN) * maturity;
21
22
  return Math.min(1, 1 / Math.sqrt(ne) + offset);
22
23
  }
@@ -34,6 +35,18 @@ export function detectPlateau(recentOutcomes) {
34
35
  return { active: true, severity: 'suggested', count };
35
36
  return { active: false, severity: 'suggested', count };
36
37
  }
38
+ function explorationActive(exp) {
39
+ return Boolean(exp && (exp.driftEnabled || exp.plateau?.active));
40
+ }
41
+ /** Deterministic top-N window shared by legacy random drift and UCB1. */
42
+ export function explorationWindowSize(eligibleCount, exp) {
43
+ if (!explorationActive(exp) || eligibleCount <= 1)
44
+ return Math.min(1, Math.max(0, eligibleCount));
45
+ let intensity = computeDriftIntensity(eligibleCount, exp.totalAttempts ?? 0);
46
+ if (exp.plateau?.active)
47
+ intensity = Math.max(intensity, exp.plateau.severity === 'required' ? 1.0 : 0.7);
48
+ return Math.min(eligibleCount, Math.max(2, Math.ceil(eligibleCount * intensity)));
49
+ }
37
50
  /**
38
51
  * Pick which ranked candidate to take among `eligibleCount` above-floor candidates.
39
52
  * No exploration → deterministic top (index 0). With exploration → compute intensity (an active plateau
@@ -41,7 +54,7 @@ export function detectPlateau(recentOutcomes) {
41
54
  * with intensity). rng() is called at most twice; pass a seeded rng for deterministic tests.
42
55
  */
43
56
  export function driftSelect(eligibleCount, exp, rng) {
44
- if (!exp || (!exp.driftEnabled && !exp.plateau?.active) || eligibleCount <= 1) {
57
+ if (!explorationActive(exp) || eligibleCount <= 1) {
45
58
  return { driftMode: 'deterministic', index: 0, intensity: 0 };
46
59
  }
47
60
  let intensity = computeDriftIntensity(eligibleCount, exp.totalAttempts ?? 0);
@@ -49,7 +62,7 @@ export function driftSelect(eligibleCount, exp, rng) {
49
62
  intensity = Math.max(intensity, exp.plateau.severity === 'required' ? 1.0 : 0.7);
50
63
  if (rng() >= intensity)
51
64
  return { driftMode: 'deterministic', index: 0, intensity };
52
- const topN = Math.min(eligibleCount, Math.max(2, Math.ceil(eligibleCount * intensity)));
65
+ const topN = explorationWindowSize(eligibleCount, exp);
53
66
  const index = Math.min(topN - 1, Math.max(0, Math.floor(rng() * topN)));
54
67
  return { driftMode: exp.plateau?.active ? 'plateau_drift' : 'drift', index, intensity };
55
68
  }
@@ -12,10 +12,10 @@ export interface GeneHealthWeights {
12
12
  reuse: number;
13
13
  antiPattern: number;
14
14
  }
15
- export declare const HEALTH_WEIGHTS_VERSION = "gh-1";
15
+ export declare const HEALTH_WEIGHTS_VERSION = "gh-2";
16
16
  export declare const DEFAULT_HEALTH_WEIGHTS: GeneHealthWeights;
17
17
  /**
18
- * gene 健康分 = w1·successRate + w2·reuse归一 − w3·antiPattern密度.
18
+ * gene 健康分 = successRate·(w1 + w2·reuse归一) − w3·antiPattern密度.
19
19
  * 输入 = M3-6 聚合视图(不内联 learning_history) + anti_patterns 数 + 复用计数.
20
20
  */
21
21
  export declare function geneHealthScore(view: GeneLearningView, opts?: {
@@ -1,16 +1,17 @@
1
- export const HEALTH_WEIGHTS_VERSION = 'gh-1';
2
- export const DEFAULT_HEALTH_WEIGHTS = { successRate: 0.6, reuse: 0.3, antiPattern: 0.4 };
1
+ export const HEALTH_WEIGHTS_VERSION = 'gh-2';
2
+ export const DEFAULT_HEALTH_WEIGHTS = { successRate: 0.6, reuse: 0.1, antiPattern: 0.4 };
3
3
  /** reuseCount 归一(对数压缩, 复用越多分越高但边际递减). */
4
4
  function reuseScore(count) {
5
5
  return count <= 0 ? 0 : Math.min(1, Math.log10(count + 1) / 2); // 100 次≈封顶
6
6
  }
7
7
  /**
8
- * gene 健康分 = w1·successRate + w2·reuse归一 − w3·antiPattern密度.
8
+ * gene 健康分 = successRate·(w1 + w2·reuse归一) − w3·antiPattern密度.
9
9
  * 输入 = M3-6 聚合视图(不内联 learning_history) + anti_patterns 数 + 复用计数.
10
10
  */
11
11
  export function geneHealthScore(view, opts = {}, w = DEFAULT_HEALTH_WEIGHTS) {
12
12
  const reuseCount = opts.reuseCount ?? view.total;
13
13
  const antiPatternPenalty = Math.min(1, (opts.antiPatternCount ?? 0) / 5); // 5+ anti-pattern 封顶惩罚
14
- const score = w.successRate * view.successRate + w.reuse * reuseScore(reuseCount) - w.antiPattern * antiPatternPenalty;
14
+ const confidenceAdjustedSuccess = view.successRate * (w.successRate + w.reuse * reuseScore(reuseCount));
15
+ const score = confidenceAdjustedSuccess - w.antiPattern * antiPatternPenalty;
15
16
  return { geneId: view.geneId, successRate: view.successRate, reuseCount, antiPatternPenalty, score };
16
17
  }