obol-ai 0.3.8 → 0.3.10
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/CHANGELOG.md +4 -0
- package/bin/obol.js +16 -0
- package/package.json +5 -1
- package/src/analysis.js +11 -7
- package/src/cli/curiosity.js +62 -0
- package/src/cli/evolve.js +72 -0
- package/src/curiosity/index.js +15 -1
- package/src/evolve/backup.js +5 -5
- package/src/evolve/data.js +6 -3
- package/src/evolve/evolve.js +35 -0
package/CHANGELOG.md
CHANGED
package/bin/obol.js
CHANGED
|
@@ -94,4 +94,20 @@ program
|
|
|
94
94
|
await reauth();
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
program
|
|
98
|
+
.command('evolve [userId]')
|
|
99
|
+
.description('Trigger an evolution cycle manually')
|
|
100
|
+
.action(async (userId) => {
|
|
101
|
+
const { evolve } = require('../src/cli/evolve');
|
|
102
|
+
await evolve({ userId: userId ? parseInt(userId) : undefined });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
program
|
|
106
|
+
.command('curiosity [userId]')
|
|
107
|
+
.description('Trigger a curiosity cycle manually')
|
|
108
|
+
.action(async (userId) => {
|
|
109
|
+
const { curiosity } = require('../src/cli/curiosity');
|
|
110
|
+
await curiosity({ userId: userId ? parseInt(userId) : undefined });
|
|
111
|
+
});
|
|
112
|
+
|
|
97
113
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"start": "node src/index.js",
|
|
11
11
|
"test": "vitest run",
|
|
12
12
|
"test:watch": "vitest",
|
|
13
|
+
"evolve": "node bin/obol.js evolve",
|
|
14
|
+
"curiosity": "node bin/obol.js curiosity",
|
|
15
|
+
"bench": "node src/cli/prompt-bench.js",
|
|
16
|
+
"debug": "node src/cli/prompt-debug.js",
|
|
13
17
|
"prepublishOnly": "node src/cli/changelog.js",
|
|
14
18
|
"postinstall": "pip3 install faster-whisper 2>/dev/null || pip install faster-whisper 2>/dev/null || echo 'Note: faster-whisper not installed (Python/pip unavailable). Voice transcription will be disabled.'"
|
|
15
19
|
},
|
package/src/analysis.js
CHANGED
|
@@ -124,10 +124,10 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
|
|
|
124
124
|
items: {
|
|
125
125
|
type: 'object',
|
|
126
126
|
properties: {
|
|
127
|
-
key: { type: 'string', description: 'Stable identifier e.g. "timing.active_hours"' },
|
|
127
|
+
key: { type: 'string', description: 'Stable dot-notation identifier for this pattern, e.g. "timing.active_hours", "mood.stress_signals", "humor.style"' },
|
|
128
128
|
dimension: { type: 'string', enum: ['timing', 'mood', 'humor', 'engagement', 'communication', 'topics'] },
|
|
129
|
-
summary: { type: 'string', description: '
|
|
130
|
-
data: { type: 'object', description: '
|
|
129
|
+
summary: { type: 'string', description: 'Factual observation about the user, e.g. "Most active between 7-10pm on weekdays", "Uses sarcasm and dry humor when relaxed"' },
|
|
130
|
+
data: { type: 'object', description: 'Factual evidence only. Examples: {"peak_hours":["19:00-22:00"],"peak_days":["mon","wed","fri"]} or {"preferred_topics":["crypto","music"]} or {"avg_message_length":"short","uses_caps":false}. Never put notes, commentary, or meta-analysis here.' },
|
|
131
131
|
confidence: { type: 'number', description: '0-1' },
|
|
132
132
|
},
|
|
133
133
|
required: ['key', 'dimension', 'summary', 'confidence'],
|
|
@@ -139,9 +139,11 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
|
|
|
139
139
|
}];
|
|
140
140
|
|
|
141
141
|
try {
|
|
142
|
+
const patternGuidance = `Extract behavioral patterns about this user from the report. Each pattern must be a factual observation about the user's behavior — not notes about your analysis process. If you see the same pattern in the existing list, reuse its exact key and update the summary/confidence. Skip patterns already at confidence >0.8 unless new evidence contradicts them.`;
|
|
143
|
+
|
|
142
144
|
const system = formattedPatterns
|
|
143
|
-
? `Existing behavioral patterns for this user:\n${formattedPatterns}\n\n---\n\
|
|
144
|
-
:
|
|
145
|
+
? `Existing behavioral patterns for this user:\n${formattedPatterns}\n\n---\n\n${patternGuidance}`
|
|
146
|
+
: `Convert this analytical report into structured data using the save_analysis tool. ${patternGuidance}`;
|
|
145
147
|
|
|
146
148
|
const response = await client.messages.create({
|
|
147
149
|
model: 'claude-sonnet-4-6',
|
|
@@ -169,8 +171,10 @@ async function structureReport(client, report, scheduler, patterns, chatId, time
|
|
|
169
171
|
|
|
170
172
|
for (const p of patternList) {
|
|
171
173
|
if (!p.key || !p.dimension || !p.summary) continue;
|
|
172
|
-
await patterns.
|
|
173
|
-
|
|
174
|
+
const existing = await patterns.get(p.key).catch(() => null);
|
|
175
|
+
const save = existing ? patterns.incrementObservation : patterns.upsert;
|
|
176
|
+
await save(p.key, p.dimension, p.summary, p.data || {}, p.confidence || 0.5).catch(e =>
|
|
177
|
+
console.error('[analysis] Failed to save pattern:', e.message)
|
|
174
178
|
);
|
|
175
179
|
}
|
|
176
180
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const { loadConfig, ensureUserDir } = require('../config');
|
|
2
|
+
const { createMemory } = require('../memory');
|
|
3
|
+
const { createSelfMemory } = require('../memory/self');
|
|
4
|
+
const { createPatterns } = require('../soul/patterns');
|
|
5
|
+
const { createAnthropicClient } = require('../claude/client');
|
|
6
|
+
const { runCuriosity } = require('../curiosity');
|
|
7
|
+
|
|
8
|
+
async function runCuriosityCli({ userId: userIdArg } = {}) {
|
|
9
|
+
process.env.OBOL_VERBOSE = '1';
|
|
10
|
+
|
|
11
|
+
const config = loadConfig({ resolve: false });
|
|
12
|
+
|
|
13
|
+
if (!config?.anthropic) {
|
|
14
|
+
console.error('Anthropic not configured. Run: obol init');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!config?.supabase) {
|
|
19
|
+
console.error('Supabase not configured. Run: obol init');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const allowedUsers = config.telegram?.allowedUsers || [];
|
|
24
|
+
const userId = userIdArg || allowedUsers[0];
|
|
25
|
+
|
|
26
|
+
if (!userId) {
|
|
27
|
+
console.error('No user ID found. Pass <userId> or configure telegram.allowedUsers');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const userDir = ensureUserDir(userId);
|
|
32
|
+
|
|
33
|
+
console.log(`Starting curiosity cycle for user ${userId}...`);
|
|
34
|
+
|
|
35
|
+
const client = createAnthropicClient(config.anthropic);
|
|
36
|
+
const selfMemory = await createSelfMemory(config.supabase, 0);
|
|
37
|
+
const memory = await createMemory(config.supabase, userId).catch(() => null);
|
|
38
|
+
const patterns = await createPatterns(config.supabase, userId).catch(() => null);
|
|
39
|
+
|
|
40
|
+
const parts = [];
|
|
41
|
+
if (memory) {
|
|
42
|
+
const recent = await memory.recent({ limit: 3 }).catch(() => []);
|
|
43
|
+
if (recent.length) parts.push(recent.map(m => `- ${m.content}`).join('\n'));
|
|
44
|
+
}
|
|
45
|
+
if (patterns) {
|
|
46
|
+
const fmt = await patterns.format().catch(() => null);
|
|
47
|
+
if (fmt) parts.push(fmt);
|
|
48
|
+
}
|
|
49
|
+
const peopleContext = parts.join('\n\n') || undefined;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const result = await runCuriosity(client, selfMemory, userId, { memory, patterns, peopleContext, userDir });
|
|
53
|
+
console.log(`\nCuriosity cycle complete — stored ${result.count} things`);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`Curiosity cycle failed: ${e.message}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
} finally {
|
|
58
|
+
delete process.env.OBOL_VERBOSE;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = { curiosity: runCuriosityCli };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { loadConfig, ensureUserDir } = require('../config');
|
|
2
|
+
const { createMemory } = require('../memory');
|
|
3
|
+
const { createSelfMemory } = require('../memory/self');
|
|
4
|
+
const { createMessageLog } = require('../messages');
|
|
5
|
+
const { createAnthropicClient } = require('../claude/client');
|
|
6
|
+
|
|
7
|
+
async function runEvolve({ userId: userIdArg } = {}) {
|
|
8
|
+
process.env.OBOL_VERBOSE = '1';
|
|
9
|
+
|
|
10
|
+
const config = loadConfig({ resolve: false });
|
|
11
|
+
|
|
12
|
+
if (!config?.anthropic) {
|
|
13
|
+
console.error('Anthropic not configured. Run: obol init');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!config?.supabase) {
|
|
18
|
+
console.error('Supabase not configured. Run: obol init');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const allowedUsers = config.telegram?.allowedUsers || [];
|
|
23
|
+
const userId = userIdArg || allowedUsers[0];
|
|
24
|
+
|
|
25
|
+
if (!userId) {
|
|
26
|
+
console.error('No user ID found. Pass <userId> or configure telegram.allowedUsers');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const userDir = ensureUserDir(userId);
|
|
31
|
+
|
|
32
|
+
console.log(`Starting evolution for user ${userId}...`);
|
|
33
|
+
|
|
34
|
+
const client = createAnthropicClient(config.anthropic);
|
|
35
|
+
const memory = await createMemory(config.supabase, userId);
|
|
36
|
+
const selfMemory = await createSelfMemory(config.supabase, 0).catch(() => null);
|
|
37
|
+
const messageLog = createMessageLog(config.supabase, memory, config.anthropic, userId, userDir);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const { evolve } = require('../evolve');
|
|
41
|
+
const result = await evolve(client, messageLog, memory, userDir, config.supabase, selfMemory);
|
|
42
|
+
|
|
43
|
+
console.log(`\nEvolution #${result.evolutionNumber} complete`);
|
|
44
|
+
console.log(` Soul: ${result.previousLength} → ${result.newLength} chars`);
|
|
45
|
+
|
|
46
|
+
if (result.scriptsFixed) console.log(' Scripts: fixed after test regression');
|
|
47
|
+
else if (result.scriptsRolledBack) console.log(' Scripts: rolled back (tests failed)');
|
|
48
|
+
|
|
49
|
+
if (result.upgrades?.length > 0) {
|
|
50
|
+
console.log(' New capabilities:');
|
|
51
|
+
for (const u of result.upgrades) {
|
|
52
|
+
console.log(` - ${u.name}: ${u.description}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (result.deployedApps?.length > 0) {
|
|
57
|
+
console.log(' Deployed:');
|
|
58
|
+
for (const app of result.deployedApps) {
|
|
59
|
+
console.log(` - ${app.name}${app.url ? ` → ${app.url}` : ` (failed: ${app.error})`}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (result.changelog) console.log(`\n ${result.changelog}`);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(`Evolution failed: ${e.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
} finally {
|
|
68
|
+
delete process.env.OBOL_VERBOSE;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { evolve: runEvolve };
|
package/src/curiosity/index.js
CHANGED
|
@@ -107,10 +107,12 @@ You can search your own memory to see what you already know before looking thing
|
|
|
107
107
|
context ? `What you have access to:\n\n${context}` : null,
|
|
108
108
|
].filter(Boolean).join('\n\n');
|
|
109
109
|
|
|
110
|
+
const log = process.env.OBOL_VERBOSE ? (msg) => console.log(`[curiosity] ${msg}`) : () => {};
|
|
110
111
|
const messages = [{ role: 'user', content: `What are you curious about right now?` }];
|
|
111
112
|
let stored = 0;
|
|
112
113
|
|
|
113
114
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
115
|
+
log(`Iteration ${i + 1}/${MAX_ITERATIONS}...`);
|
|
114
116
|
const response = await client.messages.create({
|
|
115
117
|
model: RESEARCH_MODEL,
|
|
116
118
|
max_tokens: 2000,
|
|
@@ -121,12 +123,24 @@ You can search your own memory to see what you already know before looking thing
|
|
|
121
123
|
|
|
122
124
|
messages.push({ role: 'assistant', content: response.content });
|
|
123
125
|
|
|
124
|
-
|
|
126
|
+
const textBlocks = response.content.filter(b => b.type === 'text').map(b => b.text).join(' ');
|
|
127
|
+
if (textBlocks) log(` Text: ${textBlocks.substring(0, 200)}`);
|
|
128
|
+
log(` Stop reason: ${response.stop_reason}`);
|
|
129
|
+
|
|
130
|
+
if (response.stop_reason === 'end_turn') {
|
|
131
|
+
if (stored === 0 && i < 3) {
|
|
132
|
+
log(' No saves yet — nudging to continue...');
|
|
133
|
+
messages.push({ role: 'user', content: 'Keep going — search the web, explore that thread, and save what you find with the remember tool. Don\'t just reflect, actually look things up.' });
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
125
138
|
if (response.stop_reason !== 'tool_use') break;
|
|
126
139
|
|
|
127
140
|
const toolResults = [];
|
|
128
141
|
for (const block of response.content) {
|
|
129
142
|
if (block.type !== 'tool_use') continue;
|
|
143
|
+
log(` Tool: ${block.name}${block.name === 'remember' ? ` — ${block.input.content?.substring(0, 100)}` : block.name === 'web_search' ? ` — "${block.input.query}"` : block.name === 'knowledge_search' ? ` — "${block.input.query}"` : ''}`);
|
|
130
144
|
|
|
131
145
|
if (block.name === 'remember') {
|
|
132
146
|
try {
|
package/src/evolve/backup.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
async function backupSnapshot(message, userDir) {
|
|
2
2
|
try {
|
|
3
3
|
const { loadConfig } = require('../config');
|
|
4
|
-
const cfg = loadConfig();
|
|
5
|
-
if (cfg?.github)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
const cfg = loadConfig({ resolve: false });
|
|
5
|
+
if (!cfg?.github?.token || !cfg?.github?.username || !cfg?.github?.repo) return;
|
|
6
|
+
const resolved = loadConfig();
|
|
7
|
+
const { runBackup } = require('../backup');
|
|
8
|
+
await runBackup(resolved.github, message, userDir);
|
|
9
9
|
} catch {}
|
|
10
10
|
}
|
|
11
11
|
|
package/src/evolve/data.js
CHANGED
|
@@ -45,7 +45,8 @@ async function fetchRecentMessages(messageLog, state) {
|
|
|
45
45
|
`${messageLog.url}/rest/v1/obol_messages?order=created_at.asc&limit=500&select=role,content,created_at${userFilter}${sinceFilter}`,
|
|
46
46
|
{ headers: messageLog.headers }
|
|
47
47
|
);
|
|
48
|
-
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
return Array.isArray(data) ? data : [];
|
|
49
50
|
} catch (e) {
|
|
50
51
|
console.error('[evolve] Failed to fetch recent messages:', e.message);
|
|
51
52
|
return [];
|
|
@@ -73,7 +74,8 @@ async function fetchMemories(memory, messageLog, state) {
|
|
|
73
74
|
`${url}/rest/v1/obol_memory?select=content,category,importance&order=importance.desc,accessed_at.desc&limit=20${memUserFilter}`,
|
|
74
75
|
{ headers }
|
|
75
76
|
);
|
|
76
|
-
|
|
77
|
+
const coreData = await res.json();
|
|
78
|
+
coreMemories = Array.isArray(coreData) ? coreData : [];
|
|
77
79
|
} catch (e) {
|
|
78
80
|
console.error('[evolve] Failed to fetch core memories:', e.message);
|
|
79
81
|
}
|
|
@@ -85,7 +87,8 @@ async function fetchMemories(memory, messageLog, state) {
|
|
|
85
87
|
`${url}/rest/v1/obol_memory?select=content,category,importance,tags,created_at,source&order=created_at.asc&limit=100${memUserFilter}${sinceFilter}`,
|
|
86
88
|
{ headers }
|
|
87
89
|
);
|
|
88
|
-
|
|
90
|
+
const recentData = await res.json();
|
|
91
|
+
recentMemories = Array.isArray(recentData) ? recentData : [];
|
|
89
92
|
} catch (e) {
|
|
90
93
|
console.error('[evolve] Failed to fetch recent memories:', e.message);
|
|
91
94
|
}
|
package/src/evolve/evolve.js
CHANGED
|
@@ -22,6 +22,8 @@ const MODELS = {
|
|
|
22
22
|
const MAX_FIX_ATTEMPTS = 1;
|
|
23
23
|
|
|
24
24
|
async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig = null, selfMemory = null) {
|
|
25
|
+
const log = process.env.OBOL_VERBOSE ? (msg) => console.log(`[evolve] ${msg}`) : () => {};
|
|
26
|
+
|
|
25
27
|
const { PERSONALITY_DIR } = require('../soul');
|
|
26
28
|
const baseDir = userDir || OBOL_DIR;
|
|
27
29
|
const state = loadEvolutionState(userDir);
|
|
@@ -33,16 +35,20 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
|
|
|
33
35
|
const testsDir = path.join(baseDir, 'tests');
|
|
34
36
|
const commandsDir = path.join(baseDir, 'commands');
|
|
35
37
|
|
|
38
|
+
log('Loading current personality files...');
|
|
36
39
|
const currentSoul = fs.existsSync(soulPath) ? fs.readFileSync(soulPath, 'utf-8') : '';
|
|
37
40
|
const currentUser = fs.existsSync(userPath) ? fs.readFileSync(userPath, 'utf-8') : '';
|
|
38
41
|
const currentAgents = fs.existsSync(agentsPath) ? fs.readFileSync(agentsPath, 'utf-8') : '';
|
|
39
42
|
const currentScripts = readDir(scriptsDir);
|
|
40
43
|
const currentTests = readDir(testsDir);
|
|
41
44
|
const currentCommands = readDir(commandsDir);
|
|
45
|
+
log(` Soul: ${currentSoul.length} chars, Scripts: ${Object.keys(currentScripts).length}, Tests: ${Object.keys(currentTests).length}, Commands: ${Object.keys(currentCommands).length}`);
|
|
42
46
|
|
|
47
|
+
log('Fetching messages and memories...');
|
|
43
48
|
const recentMessages = await fetchRecentMessages(messageLog, state);
|
|
44
49
|
const { coreMemories, recentMemories } = await fetchMemories(memory, messageLog, state);
|
|
45
50
|
const selfMemories = await fetchSelfMemories(selfMemory);
|
|
51
|
+
log(` Messages: ${recentMessages.length}, Core memories: ${coreMemories.length}, Recent memories: ${recentMemories.length}, Self memories: ${selfMemories.length}`);
|
|
46
52
|
|
|
47
53
|
let previousSoul = '';
|
|
48
54
|
const archiveDir = path.join(PERSONALITY_DIR, 'evolution');
|
|
@@ -53,6 +59,7 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
|
|
|
53
59
|
.sort();
|
|
54
60
|
if (archives.length > 0) {
|
|
55
61
|
previousSoul = fs.readFileSync(path.join(archiveDir, archives[archives.length - 1]), 'utf-8');
|
|
62
|
+
log(` Previous soul: ${archives[archives.length - 1]} (${previousSoul.length} chars)`);
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
65
|
} catch {}
|
|
@@ -78,10 +85,13 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
|
|
|
78
85
|
.join('\n\n') || '(no commands)';
|
|
79
86
|
|
|
80
87
|
const evolutionNumber = state.evolutionCount + 1;
|
|
88
|
+
log(`Evolution #${evolutionNumber} (last: ${state.lastEvolution || 'never'})`);
|
|
81
89
|
|
|
90
|
+
log('Pre-evolution backup...');
|
|
82
91
|
await backupSnapshot(`pre-evolution #${evolutionNumber}`, userDir);
|
|
83
92
|
|
|
84
93
|
if (memory && recentMessages.length >= 4) {
|
|
94
|
+
log('Deep consolidating memory...');
|
|
85
95
|
await deepConsolidateMemory(claudeClient, memory, recentMessages, evolutionNumber, MODELS.personality).catch(e =>
|
|
86
96
|
console.error('[evolve] Deep consolidation failed:', e.message)
|
|
87
97
|
);
|
|
@@ -90,6 +100,7 @@ async function evolve(claudeClient, messageLog, memory, userDir, supabaseConfig
|
|
|
90
100
|
const isFirstEvolution = !currentSoul;
|
|
91
101
|
let growthReport = '';
|
|
92
102
|
if (!isFirstEvolution && (recentMemories.length > 0 || recentMessages.length > 0 || selfMemories.length > 0)) {
|
|
103
|
+
log('Running growth analysis...');
|
|
93
104
|
try {
|
|
94
105
|
const growthResponse = await claudeClient.messages.create({
|
|
95
106
|
model: MODELS.personality,
|
|
@@ -122,12 +133,15 @@ ${transcript.substring(0, 30000)}`,
|
|
|
122
133
|
}],
|
|
123
134
|
});
|
|
124
135
|
growthReport = growthResponse.content.filter(b => b.type === 'text').map(b => b.text).join('\n');
|
|
136
|
+
log(` Growth report: ${growthReport.length} chars`);
|
|
125
137
|
} catch (e) {
|
|
126
138
|
console.error('[evolve] Growth analysis failed:', e.message);
|
|
127
139
|
}
|
|
128
140
|
}
|
|
129
141
|
|
|
142
|
+
log('Running baseline tests...');
|
|
130
143
|
const baselineResults = runTests(testsDir);
|
|
144
|
+
log(` Baseline: ${baselineResults.passed} passed, ${baselineResults.failed} failed`);
|
|
131
145
|
|
|
132
146
|
const firstEvolutionPreamble = isFirstEvolution ? `
|
|
133
147
|
## FIRST EVOLUTION — IMPORTANT
|
|
@@ -152,6 +166,7 @@ A pre-evolution analysis has been conducted comparing your previous state agains
|
|
|
152
166
|
baselineResults,
|
|
153
167
|
});
|
|
154
168
|
|
|
169
|
+
log('Running main evolution (this takes a while)...');
|
|
155
170
|
const response = await claudeClient.messages.create({
|
|
156
171
|
model: MODELS.personality,
|
|
157
172
|
max_tokens: 16384,
|
|
@@ -191,6 +206,7 @@ ${transcript || '(no conversations yet)'}
|
|
|
191
206
|
Evolve. Rewrite everything that needs rewriting. Write tests for every script. Keep what works. Fix what doesn't.${growthReport ? ' Use the growth report to guide personality continuity and trait adjustments.' : ''}`
|
|
192
207
|
}],
|
|
193
208
|
});
|
|
209
|
+
log(' Evolution response received');
|
|
194
210
|
|
|
195
211
|
const responseText = response.content.filter(b => b.type === 'text').map(b => b.text).join('\n');
|
|
196
212
|
|
|
@@ -211,11 +227,14 @@ Evolve. Rewrite everything that needs rewriting. Write tests for every script. K
|
|
|
211
227
|
throw new Error('Evolution produced empty or too-short SOUL.md');
|
|
212
228
|
}
|
|
213
229
|
|
|
230
|
+
log(` New soul: ${result.soul.length} chars`);
|
|
231
|
+
|
|
214
232
|
let scriptsRolledBack = false;
|
|
215
233
|
const hasNewTests = result.tests && typeof result.tests === 'object' && Object.keys(result.tests).length > 0;
|
|
216
234
|
const hasNewScripts = result.scripts && typeof result.scripts === 'object' && Object.keys(result.scripts).length > 0;
|
|
217
235
|
|
|
218
236
|
if (hasNewTests) {
|
|
237
|
+
log(`Writing ${Object.keys(result.tests).length} new tests...`);
|
|
219
238
|
syncDir(testsDir, result.tests);
|
|
220
239
|
for (const f of Object.keys(result.tests)) {
|
|
221
240
|
try { fs.chmodSync(path.join(testsDir, f), 0o755); } catch {}
|
|
@@ -225,6 +244,7 @@ Evolve. Rewrite everything that needs rewriting. Write tests for every script. K
|
|
|
225
244
|
const preRefactorResults = hasNewTests ? runTests(testsDir) : baselineResults;
|
|
226
245
|
|
|
227
246
|
if (hasNewScripts) {
|
|
247
|
+
log(`Writing ${Object.keys(result.scripts).length} new scripts...`);
|
|
228
248
|
syncDir(scriptsDir, result.scripts);
|
|
229
249
|
for (const f of Object.keys(result.scripts)) {
|
|
230
250
|
try { fs.chmodSync(path.join(scriptsDir, f), 0o755); } catch {}
|
|
@@ -234,11 +254,14 @@ Evolve. Rewrite everything that needs rewriting. Write tests for every script. K
|
|
|
234
254
|
let scriptsFixed = false;
|
|
235
255
|
|
|
236
256
|
if (hasNewTests || hasNewScripts) {
|
|
257
|
+
log('Running post-refactor tests...');
|
|
237
258
|
let postRefactorResults = runTests(testsDir);
|
|
259
|
+
log(` Post-refactor: ${postRefactorResults.passed} passed, ${postRefactorResults.failed} failed`);
|
|
238
260
|
|
|
239
261
|
let fixAttempt = 0;
|
|
240
262
|
while (postRefactorResults.failed > preRefactorResults.failed && fixAttempt < MAX_FIX_ATTEMPTS) {
|
|
241
263
|
fixAttempt++;
|
|
264
|
+
log(` Fix attempt ${fixAttempt}/${MAX_FIX_ATTEMPTS}...`);
|
|
242
265
|
|
|
243
266
|
try {
|
|
244
267
|
const fixResponse = await claudeClient.messages.create({
|
|
@@ -288,6 +311,7 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
288
311
|
|
|
289
312
|
if (postRefactorResults.failed <= preRefactorResults.failed) {
|
|
290
313
|
scriptsFixed = true;
|
|
314
|
+
log(' Scripts fixed');
|
|
291
315
|
}
|
|
292
316
|
}
|
|
293
317
|
}
|
|
@@ -297,6 +321,7 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
297
321
|
}
|
|
298
322
|
|
|
299
323
|
if (postRefactorResults.failed > preRefactorResults.failed) {
|
|
324
|
+
log(' Rolling back scripts...');
|
|
300
325
|
syncDir(scriptsDir, currentScripts);
|
|
301
326
|
for (const f of Object.keys(currentScripts)) {
|
|
302
327
|
try { fs.chmodSync(path.join(scriptsDir, f), 0o755); } catch {}
|
|
@@ -312,6 +337,7 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
312
337
|
}
|
|
313
338
|
}
|
|
314
339
|
|
|
340
|
+
log('Archiving previous soul...');
|
|
315
341
|
fs.mkdirSync(archiveDir, { recursive: true });
|
|
316
342
|
if (currentSoul) {
|
|
317
343
|
const timestamp = new Date().toISOString().slice(0, 10);
|
|
@@ -321,6 +347,7 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
321
347
|
);
|
|
322
348
|
}
|
|
323
349
|
|
|
350
|
+
log('Writing new soul...');
|
|
324
351
|
fs.writeFileSync(soulPath, result.soul);
|
|
325
352
|
if (supabaseConfig) {
|
|
326
353
|
const { backup } = require('../soul');
|
|
@@ -330,22 +357,27 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
330
357
|
}
|
|
331
358
|
|
|
332
359
|
if (result.user && result.user.length > 50) {
|
|
360
|
+
log('Writing USER.md...');
|
|
333
361
|
fs.writeFileSync(userPath, result.user);
|
|
334
362
|
}
|
|
335
363
|
|
|
336
364
|
if (result.agents && result.agents.length > 50) {
|
|
365
|
+
log('Writing AGENTS.md...');
|
|
337
366
|
fs.writeFileSync(agentsPath, result.agents);
|
|
338
367
|
}
|
|
339
368
|
|
|
340
369
|
if (result.commands && typeof result.commands === 'object') {
|
|
341
370
|
if (Object.keys(result.commands).length > 0 || Object.keys(currentCommands).length > 0) {
|
|
371
|
+
log(`Writing ${Object.keys(result.commands).length} commands...`);
|
|
342
372
|
syncDir(commandsDir, result.commands);
|
|
343
373
|
}
|
|
344
374
|
}
|
|
345
375
|
|
|
376
|
+
log('Building and deploying apps...');
|
|
346
377
|
const deployedApps = await buildAndDeployApps(result, baseDir);
|
|
347
378
|
|
|
348
379
|
if (result.dependencies && Array.isArray(result.dependencies) && result.dependencies.length > 0) {
|
|
380
|
+
log(`Installing dependencies: ${result.dependencies.join(', ')}...`);
|
|
349
381
|
try {
|
|
350
382
|
const validDeps = result.dependencies.filter(isValidNpmPackage);
|
|
351
383
|
if (validDeps.length === 0) throw new Error('No valid package names found');
|
|
@@ -370,6 +402,7 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
370
402
|
saveEvolutionState(state, userDir);
|
|
371
403
|
|
|
372
404
|
if (memory) {
|
|
405
|
+
log('Saving evolution memory...');
|
|
373
406
|
const changelog = result.changelog || `Evolution #${evolutionNumber} completed.`;
|
|
374
407
|
const rollbackNote = scriptsRolledBack ? ' Scripts rolled back due to test regression.' : scriptsFixed ? ' Scripts fixed after test regression.' : '';
|
|
375
408
|
await memory.add(
|
|
@@ -385,8 +418,10 @@ Fix the scripts. Tests define correct behavior.`
|
|
|
385
418
|
}
|
|
386
419
|
}
|
|
387
420
|
|
|
421
|
+
log('Post-evolution backup...');
|
|
388
422
|
await backupSnapshot(`post-evolution #${evolutionNumber}`, userDir);
|
|
389
423
|
|
|
424
|
+
log('Done');
|
|
390
425
|
return {
|
|
391
426
|
evolutionNumber,
|
|
392
427
|
previousLength: currentSoul.length,
|