@evomap/evolver-core 2.0.0-beta.17 → 2.0.0-beta.19

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 (71) hide show
  1. package/dist/algo/candidateAssembly.js +21 -2
  2. package/dist/algo/cycleEngine.d.ts +12 -0
  3. package/dist/algo/cycleEngine.js +36 -4
  4. package/dist/algo/geneHealth.d.ts +2 -2
  5. package/dist/algo/geneHealth.js +5 -4
  6. package/dist/algo/geneSelection.d.ts +1 -1
  7. package/dist/algo/orchestrator.js +9 -2
  8. package/dist/assetstore/assetSidecarRecords.js +4 -0
  9. package/dist/assetstore/assetStoreHealth.js +41 -24
  10. package/dist/assetstore/assetStoreStorage.d.ts +1 -1
  11. package/dist/assetstore/assetStoreStorage.js +16 -7
  12. package/dist/assetstore/localJsonl.d.ts +2 -1
  13. package/dist/assetstore/localJsonl.js +54 -10
  14. package/dist/assetstore/provenance.d.ts +24 -0
  15. package/dist/assetstore/provenance.js +219 -12
  16. package/dist/assetstore/provider.d.ts +20 -1
  17. package/dist/assetstore/provider.js +34 -1
  18. package/dist/bootstrap/index.d.ts +2 -1
  19. package/dist/bootstrap/index.js +2 -1
  20. package/dist/bootstrap/v1EnvCompat.d.ts +110 -0
  21. package/dist/bootstrap/v1EnvCompat.js +256 -0
  22. package/dist/events/public.d.ts +1 -1
  23. package/dist/events/public.js +1 -1
  24. package/dist/events/reports.d.ts +2 -0
  25. package/dist/events/reports.js +4 -0
  26. package/dist/exec/autoExec.d.ts +18 -1
  27. package/dist/exec/autoExec.js +24 -9
  28. package/dist/exec/autonomousCycle.d.ts +19 -4
  29. package/dist/exec/autonomousCycle.js +63 -13
  30. package/dist/exec/claudeBridge.d.ts +25 -7
  31. package/dist/exec/claudeBridge.js +264 -29
  32. package/dist/exec/prompt.js +5 -1
  33. package/dist/exec/runnerRegistry.d.ts +68 -26
  34. package/dist/exec/runnerRegistry.js +307 -72
  35. package/dist/exec/selfPr.js +1 -7
  36. package/dist/feedback/envelope.d.ts +61 -0
  37. package/dist/feedback/envelope.js +168 -0
  38. package/dist/feedback/index.d.ts +1 -0
  39. package/dist/feedback/index.js +1 -0
  40. package/dist/hub/assetCallLog.d.ts +35 -1
  41. package/dist/hub/assetCallLog.js +124 -1
  42. package/dist/hub/bindings.d.ts +8 -1
  43. package/dist/hub/bindings.js +17 -6
  44. package/dist/hub/capability.d.ts +11 -1
  45. package/dist/hub/fake.d.ts +2 -2
  46. package/dist/hub/fake.js +1 -1
  47. package/dist/index.d.ts +3 -1
  48. package/dist/index.js +4 -1
  49. package/dist/mailbox/dispatch.d.ts +1 -1
  50. package/dist/mailbox/dispatch.js +22 -6
  51. package/dist/mailbox/envelope.d.ts +7 -1
  52. package/dist/mailbox/envelope.js +9 -2
  53. package/dist/mailbox/ipcServer.d.ts +10 -2
  54. package/dist/mailbox/ipcServer.js +163 -13
  55. package/dist/mailbox/store.d.ts +38 -2
  56. package/dist/mailbox/store.js +416 -27
  57. package/dist/signals/curriculum.d.ts +55 -0
  58. package/dist/signals/curriculum.js +202 -0
  59. package/dist/signals/expand.js +17 -6
  60. package/dist/signals/index.d.ts +2 -1
  61. package/dist/signals/index.js +2 -1
  62. package/dist/strategy/constraintAblation.js +115 -369
  63. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  64. package/dist/strategy/constraintAblationPredicates.js +339 -0
  65. package/dist/trace/index.d.ts +2 -1
  66. package/dist/trace/index.js +2 -1
  67. package/dist/trace/proxyTurns.d.ts +31 -0
  68. package/dist/trace/proxyTurns.js +137 -0
  69. package/dist/verify/validation.d.ts +11 -1
  70. package/dist/verify/validation.js +31 -0
  71. package/package.json +4 -1
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Progressive curriculum signals, adapted from V1's curriculum producer.
3
+ *
4
+ * V2 keeps the policy pure and derives its history from the append-only root event log. This avoids V1's
5
+ * separate mutable curriculum_state.json sidecar while preserving the behavior that matters to selection:
6
+ * classify recent outcomes and add at most one capability-gap target plus one frontier target.
7
+ */
8
+ const MASTERY_THRESHOLD = 0.8;
9
+ const MASTERY_MIN_ATTEMPTS = 3;
10
+ const FAILURE_THRESHOLD = 0.3;
11
+ const MIN_CLASSIFICATION_ATTEMPTS = 2;
12
+ const MAX_CURRICULUM_SIGNALS = 2;
13
+ const MAX_TARGET_CHARS = 60;
14
+ const DEFAULT_OUTCOME_WINDOW = 200;
15
+ const CAPABILITY_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,59}$/;
16
+ export const MAX_CAPABILITY_GAPS = 16;
17
+ export const CAPABILITY_GAPS_STATE_KEY = 'curriculum:capability_gaps';
18
+ const CAPABILITY_GAPS_STATE_VERSION = 1;
19
+ function record(value) {
20
+ return value && typeof value === 'object' && !Array.isArray(value)
21
+ ? value
22
+ : undefined;
23
+ }
24
+ function stringArray(value) {
25
+ if (!Array.isArray(value))
26
+ return undefined;
27
+ return value.filter((entry) => typeof entry === 'string');
28
+ }
29
+ function boundedWindow(value) {
30
+ if (!Number.isFinite(value))
31
+ return DEFAULT_OUTCOME_WINDOW;
32
+ return Math.max(1, Math.min(10_000, Math.floor(value)));
33
+ }
34
+ /** Normalize untrusted capability names before they cross adapter, persistence, or selection boundaries. */
35
+ export function normalizeCapabilityGaps(value) {
36
+ if (!Array.isArray(value))
37
+ return [];
38
+ const gaps = [];
39
+ for (const entry of value) {
40
+ if (typeof entry !== 'string')
41
+ continue;
42
+ const normalized = entry.trim().toLowerCase();
43
+ if (!CAPABILITY_NAME.test(normalized) || gaps.includes(normalized))
44
+ continue;
45
+ gaps.push(normalized);
46
+ if (gaps.length >= MAX_CAPABILITY_GAPS)
47
+ break;
48
+ }
49
+ return gaps;
50
+ }
51
+ /** Versioned, bounded snapshot stored atomically in the lifecycle mailbox KV table. */
52
+ export function serializeCapabilityGapsState(capabilityGaps, observedAt) {
53
+ const safeObservedAt = Number.isFinite(observedAt) && observedAt >= 0 ? Math.floor(observedAt) : 0;
54
+ return JSON.stringify({
55
+ version: CAPABILITY_GAPS_STATE_VERSION,
56
+ capabilityGaps: normalizeCapabilityGaps(capabilityGaps),
57
+ observedAt: safeObservedAt,
58
+ });
59
+ }
60
+ /** Parse only the current bounded snapshot format. Unknown versions fail open without influencing selection. */
61
+ export function capabilityGapsFromState(value) {
62
+ if (typeof value !== 'string' || value.length === 0)
63
+ return [];
64
+ try {
65
+ const parsed = record(JSON.parse(value));
66
+ if (!parsed || parsed['version'] !== CAPABILITY_GAPS_STATE_VERSION)
67
+ return [];
68
+ const observedAt = parsed['observedAt'];
69
+ if (typeof observedAt !== 'number' || !Number.isFinite(observedAt) || observedAt < 0)
70
+ return [];
71
+ return normalizeCapabilityGaps(parsed['capabilityGaps']);
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ /** Stable V2 equivalent of V1 computeSignalKey, excluding previously generated curriculum targets. */
78
+ export function curriculumSignalKey(signals) {
79
+ return [...new Set(signals
80
+ .map((signal) => signal.trim())
81
+ .filter((signal) => signal.length > 0 && !signal.startsWith('curriculum_target:')))]
82
+ .sort()
83
+ .join('|');
84
+ }
85
+ /** Classify signal-key outcomes using V1's thresholds. */
86
+ export function classifyCurriculumOutcomes(outcomes) {
87
+ const aggregates = new Map();
88
+ for (const outcome of outcomes) {
89
+ const key = typeof outcome?.key === 'string' ? outcome.key.trim() : '';
90
+ if (!key || (outcome.status !== 'success' && outcome.status !== 'failed'))
91
+ continue;
92
+ const aggregate = aggregates.get(key) ?? { success: 0, failed: 0 };
93
+ if (outcome.status === 'success')
94
+ aggregate.success += 1;
95
+ else
96
+ aggregate.failed += 1;
97
+ aggregates.set(key, aggregate);
98
+ }
99
+ const mastered = [];
100
+ const failing = [];
101
+ const frontier = [];
102
+ for (const [key, aggregate] of aggregates) {
103
+ const total = aggregate.success + aggregate.failed;
104
+ if (total < MIN_CLASSIFICATION_ATTEMPTS)
105
+ continue;
106
+ const rate = aggregate.success / total;
107
+ const bucket = { key, success: aggregate.success, failed: aggregate.failed, total, rate };
108
+ if (rate >= MASTERY_THRESHOLD && total >= MASTERY_MIN_ATTEMPTS)
109
+ mastered.push(bucket);
110
+ else if (rate <= FAILURE_THRESHOLD)
111
+ failing.push(bucket);
112
+ else
113
+ frontier.push(bucket);
114
+ }
115
+ frontier.sort((left, right) => Math.abs(left.rate - 0.5) - Math.abs(right.rate - 0.5));
116
+ return { mastered, failing, frontier };
117
+ }
118
+ /** Generate no more than the two target families V1 produced: gap first, then closest frontier. */
119
+ export function generateCurriculumSignals(input) {
120
+ const analysis = classifyCurriculumOutcomes(input.outcomes);
121
+ const generated = [];
122
+ const gap = (input.capabilityGaps ?? []).find((entry) => typeof entry === 'string' && entry.trim().length > 0)?.trim();
123
+ if (gap) {
124
+ const normalizedGap = gap.toLowerCase();
125
+ const alreadyMastered = analysis.mastered.some((entry) => entry.key.toLowerCase().includes(normalizedGap));
126
+ if (!alreadyMastered)
127
+ generated.push(`curriculum_target:gap:${gap.slice(0, MAX_TARGET_CHARS)}`);
128
+ }
129
+ const best = analysis.frontier[0];
130
+ if (generated.length < MAX_CURRICULUM_SIGNALS && best) {
131
+ const alreadyTargeted = generated.some((signal) => signal.includes(best.key));
132
+ if (!alreadyTargeted)
133
+ generated.push(`curriculum_target:frontier:${best.key.slice(0, MAX_TARGET_CHARS)}`);
134
+ }
135
+ return generated.slice(0, MAX_CURRICULUM_SIGNALS);
136
+ }
137
+ /**
138
+ * Map V2's capability signal conventions to concrete curriculum gaps. A bare cap:* tag is not sufficient by
139
+ * itself: it becomes a curriculum gap only when the same signal set explicitly declares capability_gap.
140
+ */
141
+ export function capabilityGapsFromSignals(signals) {
142
+ const hasGapMarker = signals.some((signal) => signal === 'capability_gap' || signal.startsWith('capability_gap:'));
143
+ if (!hasGapMarker)
144
+ return [];
145
+ const gaps = [];
146
+ const add = (raw) => {
147
+ const normalized = raw.trim().toLowerCase();
148
+ if (!CAPABILITY_NAME.test(normalized) || gaps.includes(normalized))
149
+ return;
150
+ gaps.push(normalized);
151
+ };
152
+ for (const signal of signals)
153
+ if (signal.startsWith('cap:'))
154
+ add(signal.slice('cap:'.length));
155
+ for (const signal of signals)
156
+ if (signal.startsWith('capability_gap:'))
157
+ add(signal.slice('capability_gap:'.length));
158
+ return normalizeCapabilityGaps(gaps);
159
+ }
160
+ /**
161
+ * Join cycle.signals_collected to terminal cycle events and retain the latest outcome window. `baseSignals`
162
+ * wins over `signals`, so history-derived meta-signals do not become a self-reinforcing curriculum key.
163
+ */
164
+ export function curriculumOutcomesFromEvents(events, maxOutcomes = DEFAULT_OUTCOME_WINDOW) {
165
+ const limit = boundedWindow(maxOutcomes);
166
+ const selectedNewestFirst = [];
167
+ const terminalCycles = new Set();
168
+ // Pick the latest unique terminal cycles first. EventStore.readAll already owns the input array, while every
169
+ // auxiliary collection in this adapter remains bounded by the configured outcome window.
170
+ for (let index = events.length - 1; index >= 0 && selectedNewestFirst.length < limit; index -= 1) {
171
+ const event = events[index];
172
+ if (event.type !== 'cycle.solidified' && event.type !== 'cycle.failed')
173
+ continue;
174
+ const payload = record(event.payload);
175
+ if (!payload || typeof payload['cycleId'] !== 'string' || terminalCycles.has(payload['cycleId']))
176
+ continue;
177
+ const cycleId = payload['cycleId'];
178
+ terminalCycles.add(cycleId);
179
+ selectedNewestFirst.push({
180
+ cycleId,
181
+ status: event.type === 'cycle.failed' || payload['producedValue'] === false ? 'failed' : 'success',
182
+ });
183
+ }
184
+ const signalsByCycle = new Map();
185
+ // Build the signal lookup independently of terminal ordering. Normal logs collect signals first, but a
186
+ // recovered/imported history can be reordered without making otherwise valid outcomes disappear.
187
+ for (const event of events) {
188
+ const payload = record(event.payload);
189
+ if (!payload)
190
+ continue;
191
+ const cycleId = typeof payload['cycleId'] === 'string' ? payload['cycleId'] : undefined;
192
+ if (!cycleId || !terminalCycles.has(cycleId) || event.type !== 'cycle.signals_collected')
193
+ continue;
194
+ const baseSignals = stringArray(payload['baseSignals']);
195
+ const fallbackSignals = stringArray(payload['signals']);
196
+ signalsByCycle.set(cycleId, baseSignals ?? fallbackSignals ?? []);
197
+ }
198
+ return selectedNewestFirst.reverse().flatMap(({ cycleId, status }) => {
199
+ const key = curriculumSignalKey(signalsByCycle.get(cycleId) ?? []);
200
+ return key ? [{ key, status }] : [];
201
+ });
202
+ }
@@ -8,6 +8,18 @@
8
8
  // reach an English-keyworded gene through tagOverlapScore. This is the v2-native port of v1 #99's
9
9
  // multilingual signals_match aliases: v1 attached `|`-pipe synonyms to shipped seed genes, but v2 has
10
10
  // no seed-gene catalog and no pipe-alias parsing — its expansion rules ARE the shared signal vocabulary.
11
+ const SUCCESS_OUTCOME_TAGS = ['signal:success', 'action:optimize', 'action:innovate'];
12
+ const EXPLICIT_SUCCESS_SIGNALS = new Set([
13
+ 'issue_already_resolved',
14
+ 'issue_resolved',
15
+ 'openclaw_self_healed',
16
+ 'resolved',
17
+ 'self_healed',
18
+ 'stable_success_plateau',
19
+ 'success_prose',
20
+ 'verified-success',
21
+ 'verified_success',
22
+ ]);
11
23
  const EXPANSION_RULES = [
12
24
  { re: /(error|exception|failed|unstable|log_error|runtime|429|错误|异常|エラー|오류|例外|예외|失败|失敗|실패|不稳定|不安定|불안정)/, tags: ['problem:reliability', 'action:repair'] },
13
25
  // Korean 감사 alone is a homograph (audit == "thank you"), so it is matched only via compounds / audit-context
@@ -23,12 +35,6 @@ const EXPANSION_RULES = [
23
35
  // SUCCESS signal (#578), not a stagnation signal. Without (?<!success_) the stagnation rule would fire on it,
24
36
  // producing contradictory tags (problem:stagnation + signal:success) in a single expandSignals pass.
25
37
  { re: /(stagnation|(?<!success_)plateau|steady_state|saturation|empty_cycle_loop|loop_detected|recurring)/, tags: ['problem:stagnation', 'action:innovate'] },
26
- // Success signals (v2-native, #578): a stable success plateau or resolved issue expands to
27
- // signal:success + action:optimize/action:innovate so a gene tagged for optimization/innovation can be selected
28
- // when the loop is succeeding — enabling "learn from what works" rather than only "fix what's broken".
29
- // `verified[-_]success` matches both the underscore form (meta-signal naming) and the hyphen form
30
- // (distillPrimitives signalTokens token) so success genes distilled from sessions are also expanded.
31
- { re: /(stable_success_plateau|issue_already_resolved|openclaw_self_healed|self_healed|resolved|verified[-_]success|success_prose)/, tags: ['signal:success', 'action:optimize', 'action:innovate'] },
32
38
  { re: /(task|worker|heartbeat|hub|commitment|assignment|orchestration)/, tags: ['area:orchestration'] },
33
39
  // Tool-integrity (ported from v1 #99 gene_tool_integrity): bypassing a registered tool or looping on raw
34
40
  // shell is an orchestration-discipline / validation risk. CN/JA/KO aliases included for recall parity.
@@ -50,6 +56,11 @@ export function expandSignals(signals, extraText = '') {
50
56
  if (base && base !== str)
51
57
  tags.add(base); // namespace prefix (e.g. 'auth:token' → 'auth')
52
58
  }
59
+ // #578 success markers are structured signals; prose inference accepts negated forms too easily.
60
+ if (signals.some((signal) => EXPLICIT_SUCCESS_SIGNALS.has(String(signal).trim().toLowerCase().normalize('NFKC')))) {
61
+ for (const tag of SUCCESS_OUTCOME_TAGS)
62
+ tags.add(tag);
63
+ }
53
64
  const text = (signals.join(' ') + ' ' + extraText).toLowerCase().normalize('NFKC');
54
65
  for (const rule of EXPANSION_RULES) {
55
66
  if (rule.re.test(text))
@@ -3,4 +3,5 @@ export * from './signalGate.js';
3
3
  export * from './expand.js';
4
4
  export * from './traceSignals.js';
5
5
  export * from './metaSignals.js';
6
- export * from './cycleHistoryFromEvents.js';
6
+ export * from './cycleHistoryFromEvents.js';
7
+ export * from './curriculum.js';
@@ -3,4 +3,5 @@ export * from './signalGate.js';
3
3
  export * from './expand.js';
4
4
  export * from './traceSignals.js';
5
5
  export * from './metaSignals.js';
6
- export * from './cycleHistoryFromEvents.js';
6
+ export * from './cycleHistoryFromEvents.js';
7
+ export * from './curriculum.js';