@evomap/evolver 1.28.1
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/LICENSE +22 -0
- package/README.md +290 -0
- package/README.zh-CN.md +236 -0
- package/SKILL.md +132 -0
- package/assets/gep/capsules.json +79 -0
- package/assets/gep/events.jsonl +7 -0
- package/assets/gep/genes.json +108 -0
- package/index.js +530 -0
- package/package.json +38 -0
- package/src/canary.js +13 -0
- package/src/evolve.js +1704 -0
- package/src/gep/a2a.js +173 -0
- package/src/gep/a2aProtocol.js +736 -0
- package/src/gep/analyzer.js +35 -0
- package/src/gep/assetCallLog.js +130 -0
- package/src/gep/assetStore.js +297 -0
- package/src/gep/assets.js +36 -0
- package/src/gep/bridge.js +71 -0
- package/src/gep/candidates.js +142 -0
- package/src/gep/contentHash.js +65 -0
- package/src/gep/deviceId.js +209 -0
- package/src/gep/envFingerprint.js +83 -0
- package/src/gep/hubReview.js +206 -0
- package/src/gep/hubSearch.js +237 -0
- package/src/gep/issueReporter.js +262 -0
- package/src/gep/llmReview.js +92 -0
- package/src/gep/memoryGraph.js +771 -0
- package/src/gep/memoryGraphAdapter.js +203 -0
- package/src/gep/mutation.js +186 -0
- package/src/gep/narrativeMemory.js +108 -0
- package/src/gep/paths.js +113 -0
- package/src/gep/personality.js +355 -0
- package/src/gep/prompt.js +566 -0
- package/src/gep/questionGenerator.js +212 -0
- package/src/gep/reflection.js +127 -0
- package/src/gep/sanitize.js +67 -0
- package/src/gep/selector.js +250 -0
- package/src/gep/signals.js +417 -0
- package/src/gep/skillDistiller.js +499 -0
- package/src/gep/solidify.js +1681 -0
- package/src/gep/strategy.js +126 -0
- package/src/gep/taskReceiver.js +528 -0
- package/src/gep/validationReport.js +55 -0
- package/src/ops/cleanup.js +80 -0
- package/src/ops/commentary.js +60 -0
- package/src/ops/health_check.js +106 -0
- package/src/ops/index.js +11 -0
- package/src/ops/innovation.js +67 -0
- package/src/ops/lifecycle.js +168 -0
- package/src/ops/self_repair.js +72 -0
- package/src/ops/skills_monitor.js +143 -0
- package/src/ops/trigger.js +33 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getMemoryDir } = require('./paths');
|
|
4
|
+
const { normalizePersonalityState, isValidPersonalityState, personalityKey } = require('./personality');
|
|
5
|
+
const { isValidMutation, normalizeMutation } = require('./mutation');
|
|
6
|
+
|
|
7
|
+
function ensureDir(dir) {
|
|
8
|
+
try {
|
|
9
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
10
|
+
} catch (e) {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function stableHash(input) {
|
|
14
|
+
const s = String(input || '');
|
|
15
|
+
let h = 2166136261;
|
|
16
|
+
for (let i = 0; i < s.length; i++) {
|
|
17
|
+
h ^= s.charCodeAt(i);
|
|
18
|
+
h = Math.imul(h, 16777619);
|
|
19
|
+
}
|
|
20
|
+
return (h >>> 0).toString(16).padStart(8, '0');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function nowIso() {
|
|
24
|
+
return new Date().toISOString();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeErrorSignature(text) {
|
|
28
|
+
const s = String(text || '').trim();
|
|
29
|
+
if (!s) return null;
|
|
30
|
+
return (
|
|
31
|
+
s
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
// normalize Windows paths
|
|
34
|
+
.replace(/[a-z]:\\[^ \n\r\t]+/gi, '<path>')
|
|
35
|
+
// normalize Unix paths
|
|
36
|
+
.replace(/\/[^ \n\r\t]+/g, '<path>')
|
|
37
|
+
// normalize hex and numbers
|
|
38
|
+
.replace(/\b0x[0-9a-f]+\b/gi, '<hex>')
|
|
39
|
+
.replace(/\b\d+\b/g, '<n>')
|
|
40
|
+
// normalize whitespace
|
|
41
|
+
.replace(/\s+/g, ' ')
|
|
42
|
+
.slice(0, 220)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeSignalsForMatching(signals) {
|
|
47
|
+
const list = Array.isArray(signals) ? signals : [];
|
|
48
|
+
const out = [];
|
|
49
|
+
for (const s of list) {
|
|
50
|
+
const str = String(s || '').trim();
|
|
51
|
+
if (!str) continue;
|
|
52
|
+
if (str.startsWith('errsig:')) {
|
|
53
|
+
const norm = normalizeErrorSignature(str.slice('errsig:'.length));
|
|
54
|
+
if (norm) out.push(`errsig_norm:${stableHash(norm)}`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
out.push(str);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function computeSignalKey(signals) {
|
|
63
|
+
// Key must be stable across runs; normalize noisy signatures (paths, numbers).
|
|
64
|
+
const list = normalizeSignalsForMatching(signals);
|
|
65
|
+
const uniq = Array.from(new Set(list.filter(Boolean))).sort();
|
|
66
|
+
return uniq.join('|') || '(none)';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function extractErrorSignatureFromSignals(signals) {
|
|
70
|
+
// Convention: signals can include "errsig:<raw>" emitted by signals extractor.
|
|
71
|
+
const list = Array.isArray(signals) ? signals : [];
|
|
72
|
+
for (const s of list) {
|
|
73
|
+
const str = String(s || '');
|
|
74
|
+
if (str.startsWith('errsig:')) return normalizeErrorSignature(str.slice('errsig:'.length));
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function memoryGraphPath() {
|
|
80
|
+
const { getEvolutionDir } = require('./paths');
|
|
81
|
+
const evoDir = getEvolutionDir();
|
|
82
|
+
return process.env.MEMORY_GRAPH_PATH || path.join(evoDir, 'memory_graph.jsonl');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function memoryGraphStatePath() {
|
|
86
|
+
const { getEvolutionDir } = require('./paths');
|
|
87
|
+
return path.join(getEvolutionDir(), 'memory_graph_state.json');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function appendJsonl(filePath, obj) {
|
|
91
|
+
const dir = path.dirname(filePath);
|
|
92
|
+
ensureDir(dir);
|
|
93
|
+
fs.appendFileSync(filePath, JSON.stringify(obj) + '\n', 'utf8');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readJsonIfExists(filePath, fallback) {
|
|
97
|
+
try {
|
|
98
|
+
if (!fs.existsSync(filePath)) return fallback;
|
|
99
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
100
|
+
if (!raw.trim()) return fallback;
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return fallback;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function writeJsonAtomic(filePath, obj) {
|
|
108
|
+
const dir = path.dirname(filePath);
|
|
109
|
+
ensureDir(dir);
|
|
110
|
+
const tmp = `${filePath}.tmp`;
|
|
111
|
+
fs.writeFileSync(tmp, JSON.stringify(obj, null, 2) + '\n', 'utf8');
|
|
112
|
+
fs.renameSync(tmp, filePath);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function tryReadMemoryGraphEvents(limitLines = 2000) {
|
|
116
|
+
try {
|
|
117
|
+
const p = memoryGraphPath();
|
|
118
|
+
if (!fs.existsSync(p)) return [];
|
|
119
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
120
|
+
const lines = raw
|
|
121
|
+
.split('\n')
|
|
122
|
+
.map(l => l.trim())
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
const recent = lines.slice(Math.max(0, lines.length - limitLines));
|
|
125
|
+
return recent
|
|
126
|
+
.map(l => {
|
|
127
|
+
try {
|
|
128
|
+
return JSON.parse(l);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
.filter(Boolean);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function jaccard(aList, bList) {
|
|
140
|
+
const aNorm = normalizeSignalsForMatching(aList);
|
|
141
|
+
const bNorm = normalizeSignalsForMatching(bList);
|
|
142
|
+
const a = new Set((Array.isArray(aNorm) ? aNorm : []).map(String));
|
|
143
|
+
const b = new Set((Array.isArray(bNorm) ? bNorm : []).map(String));
|
|
144
|
+
if (a.size === 0 && b.size === 0) return 1;
|
|
145
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
146
|
+
let inter = 0;
|
|
147
|
+
for (const x of a) if (b.has(x)) inter++;
|
|
148
|
+
const union = a.size + b.size - inter;
|
|
149
|
+
return union === 0 ? 0 : inter / union;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function decayWeight(updatedAtIso, halfLifeDays) {
|
|
153
|
+
const hl = Number(halfLifeDays);
|
|
154
|
+
if (!Number.isFinite(hl) || hl <= 0) return 1;
|
|
155
|
+
const t = Date.parse(updatedAtIso);
|
|
156
|
+
if (!Number.isFinite(t)) return 1;
|
|
157
|
+
const ageDays = (Date.now() - t) / (1000 * 60 * 60 * 24);
|
|
158
|
+
if (!Number.isFinite(ageDays) || ageDays <= 0) return 1;
|
|
159
|
+
// Exponential half-life decay: weight = 0.5^(age/hl)
|
|
160
|
+
return Math.pow(0.5, ageDays / hl);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function aggregateEdges(events) {
|
|
164
|
+
// Aggregate by (signal_key, gene_id) from outcome events.
|
|
165
|
+
// Laplace smoothing to avoid 0/1 extremes.
|
|
166
|
+
const map = new Map();
|
|
167
|
+
for (const ev of events) {
|
|
168
|
+
if (!ev || ev.type !== 'MemoryGraphEvent') continue;
|
|
169
|
+
if (ev.kind !== 'outcome') continue;
|
|
170
|
+
const signalKey = ev.signal && ev.signal.key ? String(ev.signal.key) : '(none)';
|
|
171
|
+
const geneId = ev.gene && ev.gene.id ? String(ev.gene.id) : null;
|
|
172
|
+
if (!geneId) continue;
|
|
173
|
+
|
|
174
|
+
const k = `${signalKey}::${geneId}`;
|
|
175
|
+
const cur = map.get(k) || { signalKey, geneId, success: 0, fail: 0, last_ts: null, last_score: null };
|
|
176
|
+
const status = ev.outcome && ev.outcome.status ? String(ev.outcome.status) : 'unknown';
|
|
177
|
+
if (status === 'success') cur.success += 1;
|
|
178
|
+
else if (status === 'failed') cur.fail += 1;
|
|
179
|
+
|
|
180
|
+
const ts = ev.ts || ev.created_at || ev.at;
|
|
181
|
+
if (ts && (!cur.last_ts || Date.parse(ts) > Date.parse(cur.last_ts))) {
|
|
182
|
+
cur.last_ts = ts;
|
|
183
|
+
cur.last_score =
|
|
184
|
+
ev.outcome && Number.isFinite(Number(ev.outcome.score)) ? Number(ev.outcome.score) : cur.last_score;
|
|
185
|
+
}
|
|
186
|
+
map.set(k, cur);
|
|
187
|
+
}
|
|
188
|
+
return map;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function aggregateGeneOutcomes(events) {
|
|
192
|
+
// Aggregate by gene_id from outcome events (gene -> outcome success probability).
|
|
193
|
+
const map = new Map();
|
|
194
|
+
for (const ev of events) {
|
|
195
|
+
if (!ev || ev.type !== 'MemoryGraphEvent') continue;
|
|
196
|
+
if (ev.kind !== 'outcome') continue;
|
|
197
|
+
const geneId = ev.gene && ev.gene.id ? String(ev.gene.id) : null;
|
|
198
|
+
if (!geneId) continue;
|
|
199
|
+
const cur = map.get(geneId) || { geneId, success: 0, fail: 0, last_ts: null, last_score: null };
|
|
200
|
+
const status = ev.outcome && ev.outcome.status ? String(ev.outcome.status) : 'unknown';
|
|
201
|
+
if (status === 'success') cur.success += 1;
|
|
202
|
+
else if (status === 'failed') cur.fail += 1;
|
|
203
|
+
const ts = ev.ts || ev.created_at || ev.at;
|
|
204
|
+
if (ts && (!cur.last_ts || Date.parse(ts) > Date.parse(cur.last_ts))) {
|
|
205
|
+
cur.last_ts = ts;
|
|
206
|
+
cur.last_score =
|
|
207
|
+
ev.outcome && Number.isFinite(Number(ev.outcome.score)) ? Number(ev.outcome.score) : cur.last_score;
|
|
208
|
+
}
|
|
209
|
+
map.set(geneId, cur);
|
|
210
|
+
}
|
|
211
|
+
return map;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function edgeExpectedSuccess(edge, opts) {
|
|
215
|
+
const e = edge || { success: 0, fail: 0, last_ts: null };
|
|
216
|
+
const succ = Number(e.success) || 0;
|
|
217
|
+
const fail = Number(e.fail) || 0;
|
|
218
|
+
const total = succ + fail;
|
|
219
|
+
const p = (succ + 1) / (total + 2); // Laplace smoothing
|
|
220
|
+
const halfLifeDays = opts && Number.isFinite(Number(opts.half_life_days)) ? Number(opts.half_life_days) : 30;
|
|
221
|
+
const w = decayWeight(e.last_ts || '', halfLifeDays);
|
|
222
|
+
return { p, w, total, value: p * w };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function getMemoryAdvice({ signals, genes, driftEnabled }) {
|
|
226
|
+
const events = tryReadMemoryGraphEvents(2000);
|
|
227
|
+
const edges = aggregateEdges(events);
|
|
228
|
+
const geneOutcomes = aggregateGeneOutcomes(events);
|
|
229
|
+
const curSignals = Array.isArray(signals) ? signals : [];
|
|
230
|
+
const curKey = computeSignalKey(curSignals);
|
|
231
|
+
|
|
232
|
+
const bannedGeneIds = new Set();
|
|
233
|
+
const scoredGeneIds = [];
|
|
234
|
+
|
|
235
|
+
// Similarity: consider exact key first, then any key with overlap.
|
|
236
|
+
const seenKeys = new Set();
|
|
237
|
+
const candidateKeys = [];
|
|
238
|
+
candidateKeys.push({ key: curKey, sim: 1 });
|
|
239
|
+
seenKeys.add(curKey);
|
|
240
|
+
|
|
241
|
+
for (const ev of events) {
|
|
242
|
+
if (!ev || ev.type !== 'MemoryGraphEvent') continue;
|
|
243
|
+
const k = ev.signal && ev.signal.key ? String(ev.signal.key) : '(none)';
|
|
244
|
+
if (seenKeys.has(k)) continue;
|
|
245
|
+
const sigs = ev.signal && Array.isArray(ev.signal.signals) ? ev.signal.signals : [];
|
|
246
|
+
const sim = jaccard(curSignals, sigs);
|
|
247
|
+
if (sim >= 0.34) {
|
|
248
|
+
candidateKeys.push({ key: k, sim });
|
|
249
|
+
seenKeys.add(k);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const byGene = new Map();
|
|
254
|
+
for (const ck of candidateKeys) {
|
|
255
|
+
for (const g of Array.isArray(genes) ? genes : []) {
|
|
256
|
+
if (!g || g.type !== 'Gene' || !g.id) continue;
|
|
257
|
+
const k = `${ck.key}::${g.id}`;
|
|
258
|
+
const edge = edges.get(k);
|
|
259
|
+
const cur = byGene.get(g.id) || { geneId: g.id, best: 0, attempts: 0, prior: 0, prior_attempts: 0 };
|
|
260
|
+
|
|
261
|
+
// Signal->Gene edge score (if available)
|
|
262
|
+
if (edge) {
|
|
263
|
+
const ex = edgeExpectedSuccess(edge, { half_life_days: 30 });
|
|
264
|
+
const weighted = ex.value * ck.sim;
|
|
265
|
+
if (weighted > cur.best) cur.best = weighted;
|
|
266
|
+
cur.attempts = Math.max(cur.attempts, ex.total);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Gene->Outcome prior (independent of signal): stabilizer when signal edges are sparse.
|
|
270
|
+
const gEdge = geneOutcomes.get(String(g.id));
|
|
271
|
+
if (gEdge) {
|
|
272
|
+
const gx = edgeExpectedSuccess(gEdge, { half_life_days: 45 });
|
|
273
|
+
cur.prior = Math.max(cur.prior, gx.value);
|
|
274
|
+
cur.prior_attempts = Math.max(cur.prior_attempts, gx.total);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
byGene.set(g.id, cur);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
for (const [geneId, info] of byGene.entries()) {
|
|
282
|
+
const combined = info.best > 0 ? info.best + info.prior * 0.12 : info.prior * 0.4;
|
|
283
|
+
scoredGeneIds.push({ geneId, score: combined, attempts: info.attempts, prior: info.prior });
|
|
284
|
+
// Low-efficiency path suppression (unless drift is explicit).
|
|
285
|
+
if (!driftEnabled && info.attempts >= 2 && info.best < 0.18) {
|
|
286
|
+
bannedGeneIds.add(geneId);
|
|
287
|
+
}
|
|
288
|
+
// Also suppress genes with consistently poor global outcomes when signal edges are sparse.
|
|
289
|
+
if (!driftEnabled && info.attempts < 2 && info.prior_attempts >= 3 && info.prior < 0.12) {
|
|
290
|
+
bannedGeneIds.add(geneId);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
scoredGeneIds.sort((a, b) => b.score - a.score);
|
|
295
|
+
const preferredGeneId = scoredGeneIds.length ? scoredGeneIds[0].geneId : null;
|
|
296
|
+
|
|
297
|
+
const explanation = [];
|
|
298
|
+
if (preferredGeneId) explanation.push(`memory_prefer:${preferredGeneId}`);
|
|
299
|
+
if (bannedGeneIds.size) explanation.push(`memory_ban:${Array.from(bannedGeneIds).slice(0, 6).join(',')}`);
|
|
300
|
+
if (preferredGeneId) {
|
|
301
|
+
const top = scoredGeneIds.find(x => x && x.geneId === preferredGeneId);
|
|
302
|
+
if (top && Number.isFinite(Number(top.prior)) && top.prior > 0) explanation.push(`gene_prior:${top.prior.toFixed(3)}`);
|
|
303
|
+
}
|
|
304
|
+
if (driftEnabled) explanation.push('random_drift:enabled');
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
currentSignalKey: curKey,
|
|
308
|
+
preferredGeneId,
|
|
309
|
+
bannedGeneIds,
|
|
310
|
+
explanation,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function recordSignalSnapshot({ signals, observations }) {
|
|
315
|
+
const signalKey = computeSignalKey(signals);
|
|
316
|
+
const ts = nowIso();
|
|
317
|
+
const errsig = extractErrorSignatureFromSignals(signals);
|
|
318
|
+
const ev = {
|
|
319
|
+
type: 'MemoryGraphEvent',
|
|
320
|
+
kind: 'signal',
|
|
321
|
+
id: `mge_${Date.now()}_${stableHash(`${signalKey}|signal|${ts}`)}`,
|
|
322
|
+
ts,
|
|
323
|
+
signal: {
|
|
324
|
+
key: signalKey,
|
|
325
|
+
signals: Array.isArray(signals) ? signals : [],
|
|
326
|
+
error_signature: errsig || null,
|
|
327
|
+
},
|
|
328
|
+
observed: observations && typeof observations === 'object' ? observations : null,
|
|
329
|
+
};
|
|
330
|
+
appendJsonl(memoryGraphPath(), ev);
|
|
331
|
+
return ev;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function buildHypothesisText({ signalKey, signals, geneId, geneCategory, driftEnabled }) {
|
|
335
|
+
const sigCount = Array.isArray(signals) ? signals.length : 0;
|
|
336
|
+
const drift = driftEnabled ? 'drift' : 'directed';
|
|
337
|
+
const g = geneId ? `${geneId}${geneCategory ? `(${geneCategory})` : ''}` : '(none)';
|
|
338
|
+
return `Given signal_key=${signalKey} with ${sigCount} signals, selecting gene=${g} under mode=${drift} is expected to reduce repeated errors and improve stability.`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function recordHypothesis({
|
|
342
|
+
signals,
|
|
343
|
+
mutation,
|
|
344
|
+
personality_state,
|
|
345
|
+
selectedGene,
|
|
346
|
+
selector,
|
|
347
|
+
driftEnabled,
|
|
348
|
+
selectedBy,
|
|
349
|
+
capsulesUsed,
|
|
350
|
+
observations,
|
|
351
|
+
}) {
|
|
352
|
+
const signalKey = computeSignalKey(signals);
|
|
353
|
+
const geneId = selectedGene && selectedGene.id ? String(selectedGene.id) : null;
|
|
354
|
+
const geneCategory = selectedGene && selectedGene.category ? String(selectedGene.category) : null;
|
|
355
|
+
const ts = nowIso();
|
|
356
|
+
const errsig = extractErrorSignatureFromSignals(signals);
|
|
357
|
+
const hypothesisId = `hyp_${Date.now()}_${stableHash(`${signalKey}|${geneId || 'none'}|${ts}`)}`;
|
|
358
|
+
const personalityState = personality_state || null;
|
|
359
|
+
const mutNorm = mutation && isValidMutation(mutation) ? normalizeMutation(mutation) : null;
|
|
360
|
+
const psNorm = personalityState && isValidPersonalityState(personalityState) ? normalizePersonalityState(personalityState) : null;
|
|
361
|
+
const ev = {
|
|
362
|
+
type: 'MemoryGraphEvent',
|
|
363
|
+
kind: 'hypothesis',
|
|
364
|
+
id: `mge_${Date.now()}_${stableHash(`${hypothesisId}|${ts}`)}`,
|
|
365
|
+
ts,
|
|
366
|
+
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [], error_signature: errsig || null },
|
|
367
|
+
hypothesis: {
|
|
368
|
+
id: hypothesisId,
|
|
369
|
+
text: buildHypothesisText({ signalKey, signals, geneId, geneCategory, driftEnabled }),
|
|
370
|
+
predicted_outcome: { status: null, score: null },
|
|
371
|
+
},
|
|
372
|
+
mutation: mutNorm
|
|
373
|
+
? {
|
|
374
|
+
id: mutNorm.id,
|
|
375
|
+
category: mutNorm.category,
|
|
376
|
+
trigger_signals: mutNorm.trigger_signals,
|
|
377
|
+
target: mutNorm.target,
|
|
378
|
+
expected_effect: mutNorm.expected_effect,
|
|
379
|
+
risk_level: mutNorm.risk_level,
|
|
380
|
+
}
|
|
381
|
+
: null,
|
|
382
|
+
personality: psNorm
|
|
383
|
+
? {
|
|
384
|
+
key: personalityKey(psNorm),
|
|
385
|
+
state: psNorm,
|
|
386
|
+
}
|
|
387
|
+
: null,
|
|
388
|
+
gene: { id: geneId, category: geneCategory },
|
|
389
|
+
action: {
|
|
390
|
+
drift: !!driftEnabled,
|
|
391
|
+
selected_by: selectedBy || 'selector',
|
|
392
|
+
selector: selector || null,
|
|
393
|
+
},
|
|
394
|
+
capsules: {
|
|
395
|
+
used: Array.isArray(capsulesUsed) ? capsulesUsed.map(String).filter(Boolean) : [],
|
|
396
|
+
},
|
|
397
|
+
observed: observations && typeof observations === 'object' ? observations : null,
|
|
398
|
+
};
|
|
399
|
+
appendJsonl(memoryGraphPath(), ev);
|
|
400
|
+
return { hypothesisId, signalKey };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function hasErrorSignal(signals) {
|
|
404
|
+
const list = Array.isArray(signals) ? signals : [];
|
|
405
|
+
return list.includes('log_error');
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function recordAttempt({
|
|
409
|
+
signals,
|
|
410
|
+
mutation,
|
|
411
|
+
personality_state,
|
|
412
|
+
selectedGene,
|
|
413
|
+
selector,
|
|
414
|
+
driftEnabled,
|
|
415
|
+
selectedBy,
|
|
416
|
+
hypothesisId,
|
|
417
|
+
capsulesUsed,
|
|
418
|
+
observations,
|
|
419
|
+
}) {
|
|
420
|
+
const signalKey = computeSignalKey(signals);
|
|
421
|
+
const geneId = selectedGene && selectedGene.id ? String(selectedGene.id) : null;
|
|
422
|
+
const geneCategory = selectedGene && selectedGene.category ? String(selectedGene.category) : null;
|
|
423
|
+
const ts = nowIso();
|
|
424
|
+
const errsig = extractErrorSignatureFromSignals(signals);
|
|
425
|
+
const actionId = `act_${Date.now()}_${stableHash(`${signalKey}|${geneId || 'none'}|${ts}`)}`;
|
|
426
|
+
const personalityState = personality_state || null;
|
|
427
|
+
const mutNorm = mutation && isValidMutation(mutation) ? normalizeMutation(mutation) : null;
|
|
428
|
+
const psNorm = personalityState && isValidPersonalityState(personalityState) ? normalizePersonalityState(personalityState) : null;
|
|
429
|
+
const ev = {
|
|
430
|
+
type: 'MemoryGraphEvent',
|
|
431
|
+
kind: 'attempt',
|
|
432
|
+
id: `mge_${Date.now()}_${stableHash(actionId)}`,
|
|
433
|
+
ts,
|
|
434
|
+
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [], error_signature: errsig || null },
|
|
435
|
+
mutation: mutNorm
|
|
436
|
+
? {
|
|
437
|
+
id: mutNorm.id,
|
|
438
|
+
category: mutNorm.category,
|
|
439
|
+
trigger_signals: mutNorm.trigger_signals,
|
|
440
|
+
target: mutNorm.target,
|
|
441
|
+
expected_effect: mutNorm.expected_effect,
|
|
442
|
+
risk_level: mutNorm.risk_level,
|
|
443
|
+
}
|
|
444
|
+
: null,
|
|
445
|
+
personality: psNorm
|
|
446
|
+
? {
|
|
447
|
+
key: personalityKey(psNorm),
|
|
448
|
+
state: psNorm,
|
|
449
|
+
}
|
|
450
|
+
: null,
|
|
451
|
+
gene: { id: geneId, category: geneCategory },
|
|
452
|
+
hypothesis: hypothesisId ? { id: String(hypothesisId) } : null,
|
|
453
|
+
action: {
|
|
454
|
+
id: actionId,
|
|
455
|
+
drift: !!driftEnabled,
|
|
456
|
+
selected_by: selectedBy || 'selector',
|
|
457
|
+
selector: selector || null,
|
|
458
|
+
},
|
|
459
|
+
capsules: {
|
|
460
|
+
used: Array.isArray(capsulesUsed) ? capsulesUsed.map(String).filter(Boolean) : [],
|
|
461
|
+
},
|
|
462
|
+
observed: observations && typeof observations === 'object' ? observations : null,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
appendJsonl(memoryGraphPath(), ev);
|
|
466
|
+
|
|
467
|
+
// State is mutable; graph is append-only.
|
|
468
|
+
const statePath = memoryGraphStatePath();
|
|
469
|
+
const state = readJsonIfExists(statePath, { last_action: null });
|
|
470
|
+
state.last_action = {
|
|
471
|
+
action_id: actionId,
|
|
472
|
+
signal_key: signalKey,
|
|
473
|
+
signals: Array.isArray(signals) ? signals : [],
|
|
474
|
+
mutation_id: mutNorm ? mutNorm.id : null,
|
|
475
|
+
mutation_category: mutNorm ? mutNorm.category : null,
|
|
476
|
+
mutation_risk_level: mutNorm ? mutNorm.risk_level : null,
|
|
477
|
+
personality_key: psNorm ? personalityKey(psNorm) : null,
|
|
478
|
+
personality_state: psNorm || null,
|
|
479
|
+
gene_id: geneId,
|
|
480
|
+
gene_category: geneCategory,
|
|
481
|
+
hypothesis_id: hypothesisId ? String(hypothesisId) : null,
|
|
482
|
+
capsules_used: Array.isArray(capsulesUsed) ? capsulesUsed.map(String).filter(Boolean) : [],
|
|
483
|
+
had_error: hasErrorSignal(signals),
|
|
484
|
+
created_at: ts,
|
|
485
|
+
outcome_recorded: false,
|
|
486
|
+
baseline_observed: observations && typeof observations === 'object' ? observations : null,
|
|
487
|
+
};
|
|
488
|
+
writeJsonAtomic(statePath, state);
|
|
489
|
+
|
|
490
|
+
return { actionId, signalKey };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function inferOutcomeFromSignals({ prevHadError, currentHasError }) {
|
|
494
|
+
if (prevHadError && !currentHasError) return { status: 'success', score: 0.85, note: 'error_cleared' };
|
|
495
|
+
if (prevHadError && currentHasError) return { status: 'failed', score: 0.2, note: 'error_persisted' };
|
|
496
|
+
if (!prevHadError && currentHasError) return { status: 'failed', score: 0.15, note: 'new_error_appeared' };
|
|
497
|
+
return { status: 'success', score: 0.6, note: 'stable_no_error' };
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function clamp01(x) {
|
|
501
|
+
const n = Number(x);
|
|
502
|
+
if (!Number.isFinite(n)) return 0;
|
|
503
|
+
return Math.max(0, Math.min(1, n));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function tryParseLastEvolutionEventOutcome(evidenceText) {
|
|
507
|
+
// Scan tail text for an EvolutionEvent JSON line and extract its outcome.
|
|
508
|
+
const s = String(evidenceText || '');
|
|
509
|
+
if (!s) return null;
|
|
510
|
+
const lines = s.split('\n').slice(-400);
|
|
511
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
512
|
+
const line = lines[i].trim();
|
|
513
|
+
if (!line) continue;
|
|
514
|
+
if (!line.includes('"type"') || !line.includes('EvolutionEvent')) continue;
|
|
515
|
+
try {
|
|
516
|
+
const obj = JSON.parse(line);
|
|
517
|
+
if (!obj || obj.type !== 'EvolutionEvent') continue;
|
|
518
|
+
const o = obj.outcome && typeof obj.outcome === 'object' ? obj.outcome : null;
|
|
519
|
+
if (!o) continue;
|
|
520
|
+
const status = o.status === 'success' || o.status === 'failed' ? o.status : null;
|
|
521
|
+
const score = Number.isFinite(Number(o.score)) ? clamp01(Number(o.score)) : null;
|
|
522
|
+
if (!status && score == null) continue;
|
|
523
|
+
return {
|
|
524
|
+
status: status || (score != null && score >= 0.5 ? 'success' : 'failed'),
|
|
525
|
+
score: score != null ? score : status === 'success' ? 0.75 : 0.25,
|
|
526
|
+
note: 'evolutionevent_observed',
|
|
527
|
+
};
|
|
528
|
+
} catch (e) {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function inferOutcomeEnhanced({ prevHadError, currentHasError, baselineObserved, currentObserved }) {
|
|
536
|
+
const evidence =
|
|
537
|
+
currentObserved &&
|
|
538
|
+
currentObserved.evidence &&
|
|
539
|
+
(currentObserved.evidence.recent_session_tail || currentObserved.evidence.today_log_tail)
|
|
540
|
+
? currentObserved.evidence
|
|
541
|
+
: null;
|
|
542
|
+
const combinedEvidence = evidence
|
|
543
|
+
? `${String(evidence.recent_session_tail || '')}\n${String(evidence.today_log_tail || '')}`
|
|
544
|
+
: '';
|
|
545
|
+
const observed = tryParseLastEvolutionEventOutcome(combinedEvidence);
|
|
546
|
+
if (observed) return observed;
|
|
547
|
+
|
|
548
|
+
const base = inferOutcomeFromSignals({ prevHadError, currentHasError });
|
|
549
|
+
|
|
550
|
+
const prevErrCount =
|
|
551
|
+
baselineObserved && Number.isFinite(Number(baselineObserved.recent_error_count))
|
|
552
|
+
? Number(baselineObserved.recent_error_count)
|
|
553
|
+
: null;
|
|
554
|
+
const curErrCount =
|
|
555
|
+
currentObserved && Number.isFinite(Number(currentObserved.recent_error_count))
|
|
556
|
+
? Number(currentObserved.recent_error_count)
|
|
557
|
+
: null;
|
|
558
|
+
|
|
559
|
+
let score = base.score;
|
|
560
|
+
if (prevErrCount != null && curErrCount != null) {
|
|
561
|
+
const delta = prevErrCount - curErrCount;
|
|
562
|
+
score += Math.max(-0.12, Math.min(0.12, delta / 50));
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const prevScan =
|
|
566
|
+
baselineObserved && Number.isFinite(Number(baselineObserved.scan_ms)) ? Number(baselineObserved.scan_ms) : null;
|
|
567
|
+
const curScan =
|
|
568
|
+
currentObserved && Number.isFinite(Number(currentObserved.scan_ms)) ? Number(currentObserved.scan_ms) : null;
|
|
569
|
+
if (prevScan != null && curScan != null && prevScan > 0) {
|
|
570
|
+
const ratio = (prevScan - curScan) / prevScan;
|
|
571
|
+
score += Math.max(-0.06, Math.min(0.06, ratio));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return { status: base.status, score: clamp01(score), note: `${base.note}|heuristic_delta` };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function buildConfidenceEdgeEvent({ signalKey, signals, geneId, geneCategory, outcomeEventId, halfLifeDays }) {
|
|
578
|
+
const events = tryReadMemoryGraphEvents(2000);
|
|
579
|
+
const edges = aggregateEdges(events);
|
|
580
|
+
const k = `${signalKey}::${geneId}`;
|
|
581
|
+
const edge = edges.get(k) || { success: 0, fail: 0, last_ts: null };
|
|
582
|
+
const ex = edgeExpectedSuccess(edge, { half_life_days: halfLifeDays });
|
|
583
|
+
const ts = nowIso();
|
|
584
|
+
return {
|
|
585
|
+
type: 'MemoryGraphEvent',
|
|
586
|
+
kind: 'confidence_edge',
|
|
587
|
+
id: `mge_${Date.now()}_${stableHash(`${signalKey}|${geneId}|confidence|${ts}`)}`,
|
|
588
|
+
ts,
|
|
589
|
+
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [] },
|
|
590
|
+
gene: { id: geneId, category: geneCategory || null },
|
|
591
|
+
edge: { signal_key: signalKey, gene_id: geneId },
|
|
592
|
+
stats: {
|
|
593
|
+
success: Number(edge.success) || 0,
|
|
594
|
+
fail: Number(edge.fail) || 0,
|
|
595
|
+
attempts: Number(ex.total) || 0,
|
|
596
|
+
p: ex.p,
|
|
597
|
+
decay_weight: ex.w,
|
|
598
|
+
value: ex.value,
|
|
599
|
+
half_life_days: halfLifeDays,
|
|
600
|
+
updated_at: ts,
|
|
601
|
+
},
|
|
602
|
+
derived_from: { outcome_event_id: outcomeEventId || null },
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function buildGeneOutcomeConfidenceEvent({ geneId, geneCategory, outcomeEventId, halfLifeDays }) {
|
|
607
|
+
const events = tryReadMemoryGraphEvents(2000);
|
|
608
|
+
const geneOutcomes = aggregateGeneOutcomes(events);
|
|
609
|
+
const edge = geneOutcomes.get(String(geneId)) || { success: 0, fail: 0, last_ts: null };
|
|
610
|
+
const ex = edgeExpectedSuccess(edge, { half_life_days: halfLifeDays });
|
|
611
|
+
const ts = nowIso();
|
|
612
|
+
return {
|
|
613
|
+
type: 'MemoryGraphEvent',
|
|
614
|
+
kind: 'confidence_gene_outcome',
|
|
615
|
+
id: `mge_${Date.now()}_${stableHash(`${geneId}|gene_outcome|confidence|${ts}`)}`,
|
|
616
|
+
ts,
|
|
617
|
+
gene: { id: String(geneId), category: geneCategory || null },
|
|
618
|
+
edge: { gene_id: String(geneId) },
|
|
619
|
+
stats: {
|
|
620
|
+
success: Number(edge.success) || 0,
|
|
621
|
+
fail: Number(edge.fail) || 0,
|
|
622
|
+
attempts: Number(ex.total) || 0,
|
|
623
|
+
p: ex.p,
|
|
624
|
+
decay_weight: ex.w,
|
|
625
|
+
value: ex.value,
|
|
626
|
+
half_life_days: halfLifeDays,
|
|
627
|
+
updated_at: ts,
|
|
628
|
+
},
|
|
629
|
+
derived_from: { outcome_event_id: outcomeEventId || null },
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function recordOutcomeFromState({ signals, observations }) {
|
|
634
|
+
const statePath = memoryGraphStatePath();
|
|
635
|
+
const state = readJsonIfExists(statePath, { last_action: null });
|
|
636
|
+
const last = state && state.last_action ? state.last_action : null;
|
|
637
|
+
if (!last || !last.action_id) return null;
|
|
638
|
+
if (last.outcome_recorded) return null;
|
|
639
|
+
|
|
640
|
+
const currentHasError = hasErrorSignal(signals);
|
|
641
|
+
const inferred = inferOutcomeEnhanced({
|
|
642
|
+
prevHadError: !!last.had_error,
|
|
643
|
+
currentHasError,
|
|
644
|
+
baselineObserved: last.baseline_observed || null,
|
|
645
|
+
currentObserved: observations || null,
|
|
646
|
+
});
|
|
647
|
+
const ts = nowIso();
|
|
648
|
+
const errsig = extractErrorSignatureFromSignals(signals);
|
|
649
|
+
const ev = {
|
|
650
|
+
type: 'MemoryGraphEvent',
|
|
651
|
+
kind: 'outcome',
|
|
652
|
+
id: `mge_${Date.now()}_${stableHash(`${last.action_id}|outcome|${ts}`)}`,
|
|
653
|
+
ts,
|
|
654
|
+
signal: {
|
|
655
|
+
key: String(last.signal_key || '(none)'),
|
|
656
|
+
signals: Array.isArray(last.signals) ? last.signals : [],
|
|
657
|
+
error_signature: errsig || null,
|
|
658
|
+
},
|
|
659
|
+
mutation:
|
|
660
|
+
last.mutation_id || last.mutation_category || last.mutation_risk_level
|
|
661
|
+
? {
|
|
662
|
+
id: last.mutation_id || null,
|
|
663
|
+
category: last.mutation_category || null,
|
|
664
|
+
risk_level: last.mutation_risk_level || null,
|
|
665
|
+
}
|
|
666
|
+
: null,
|
|
667
|
+
personality:
|
|
668
|
+
last.personality_key || last.personality_state
|
|
669
|
+
? {
|
|
670
|
+
key: last.personality_key || null,
|
|
671
|
+
state: last.personality_state || null,
|
|
672
|
+
}
|
|
673
|
+
: null,
|
|
674
|
+
gene: { id: last.gene_id || null, category: last.gene_category || null },
|
|
675
|
+
action: { id: String(last.action_id) },
|
|
676
|
+
hypothesis: last.hypothesis_id ? { id: String(last.hypothesis_id) } : null,
|
|
677
|
+
outcome: {
|
|
678
|
+
status: inferred.status,
|
|
679
|
+
score: inferred.score,
|
|
680
|
+
note: inferred.note,
|
|
681
|
+
observed: { current_signals: Array.isArray(signals) ? signals : [] },
|
|
682
|
+
},
|
|
683
|
+
confidence: {
|
|
684
|
+
// This is an interpretable, decayed success estimate derived from outcomes; aggregation is computed at read-time.
|
|
685
|
+
half_life_days: 30,
|
|
686
|
+
},
|
|
687
|
+
observed: observations && typeof observations === 'object' ? observations : null,
|
|
688
|
+
baseline: last.baseline_observed || null,
|
|
689
|
+
capsules: {
|
|
690
|
+
used: Array.isArray(last.capsules_used) ? last.capsules_used : [],
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
appendJsonl(memoryGraphPath(), ev);
|
|
695
|
+
|
|
696
|
+
// Persist explicit confidence snapshots (append-only) for auditability.
|
|
697
|
+
try {
|
|
698
|
+
if (last.gene_id) {
|
|
699
|
+
const edgeEv = buildConfidenceEdgeEvent({
|
|
700
|
+
signalKey: String(last.signal_key || '(none)'),
|
|
701
|
+
signals: Array.isArray(last.signals) ? last.signals : [],
|
|
702
|
+
geneId: String(last.gene_id),
|
|
703
|
+
geneCategory: last.gene_category || null,
|
|
704
|
+
outcomeEventId: ev.id,
|
|
705
|
+
halfLifeDays: 30,
|
|
706
|
+
});
|
|
707
|
+
appendJsonl(memoryGraphPath(), edgeEv);
|
|
708
|
+
|
|
709
|
+
const geneEv = buildGeneOutcomeConfidenceEvent({
|
|
710
|
+
geneId: String(last.gene_id),
|
|
711
|
+
geneCategory: last.gene_category || null,
|
|
712
|
+
outcomeEventId: ev.id,
|
|
713
|
+
halfLifeDays: 45,
|
|
714
|
+
});
|
|
715
|
+
appendJsonl(memoryGraphPath(), geneEv);
|
|
716
|
+
}
|
|
717
|
+
} catch (e) {}
|
|
718
|
+
|
|
719
|
+
last.outcome_recorded = true;
|
|
720
|
+
last.outcome_recorded_at = ts;
|
|
721
|
+
state.last_action = last;
|
|
722
|
+
writeJsonAtomic(statePath, state);
|
|
723
|
+
|
|
724
|
+
return ev;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function recordExternalCandidate({ asset, source, signals }) {
|
|
728
|
+
// Append-only annotation: external assets enter as candidates only.
|
|
729
|
+
// This does not affect outcome aggregation (which only uses kind === 'outcome').
|
|
730
|
+
const a = asset && typeof asset === 'object' ? asset : null;
|
|
731
|
+
const type = a && a.type ? String(a.type) : null;
|
|
732
|
+
const id = a && a.id ? String(a.id) : null;
|
|
733
|
+
if (!type || !id) return null;
|
|
734
|
+
|
|
735
|
+
const ts = nowIso();
|
|
736
|
+
const signalKey = computeSignalKey(signals);
|
|
737
|
+
const ev = {
|
|
738
|
+
type: 'MemoryGraphEvent',
|
|
739
|
+
kind: 'external_candidate',
|
|
740
|
+
id: `mge_${Date.now()}_${stableHash(`${type}|${id}|external|${ts}`)}`,
|
|
741
|
+
ts,
|
|
742
|
+
signal: { key: signalKey, signals: Array.isArray(signals) ? signals : [] },
|
|
743
|
+
external: {
|
|
744
|
+
source: source || 'external',
|
|
745
|
+
received_at: ts,
|
|
746
|
+
},
|
|
747
|
+
asset: { type, id },
|
|
748
|
+
candidate: {
|
|
749
|
+
// Minimal hints for later local triggering/validation.
|
|
750
|
+
trigger: type === 'Capsule' && Array.isArray(a.trigger) ? a.trigger : [],
|
|
751
|
+
gene: type === 'Capsule' && a.gene ? String(a.gene) : null,
|
|
752
|
+
confidence: type === 'Capsule' && Number.isFinite(Number(a.confidence)) ? Number(a.confidence) : null,
|
|
753
|
+
},
|
|
754
|
+
};
|
|
755
|
+
|
|
756
|
+
appendJsonl(memoryGraphPath(), ev);
|
|
757
|
+
return ev;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
module.exports = {
|
|
761
|
+
memoryGraphPath,
|
|
762
|
+
computeSignalKey,
|
|
763
|
+
tryReadMemoryGraphEvents,
|
|
764
|
+
getMemoryAdvice,
|
|
765
|
+
recordSignalSnapshot,
|
|
766
|
+
recordHypothesis,
|
|
767
|
+
recordAttempt,
|
|
768
|
+
recordOutcomeFromState,
|
|
769
|
+
recordExternalCandidate,
|
|
770
|
+
};
|
|
771
|
+
|