@o-lang/olang 1.0.22 → 1.0.24

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 (3) hide show
  1. package/package.json +1 -1
  2. package/src/parser.js +206 -270
  3. package/src/runtime.js +74 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/olang",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "author": "Olalekan Ogundipe <info@workfily.com>",
5
5
  "description": "O-Lang: A governance language for user-directed, rule-enforced agent workflows",
6
6
  "main": "./src/index.js",
package/src/parser.js CHANGED
@@ -1,319 +1,255 @@
1
- // src/parser.js
2
- function parse(code, fileName = null) {
3
- if (fileName && !fileName.endsWith(".ol")) {
4
- throw new Error(`Expected .ol workflow, got: ${fileName}`);
1
+ const fs = require('fs');
2
+
3
+ function parse(content, filename = '<unknown>') {
4
+ if (typeof content === 'string') {
5
+ const lines = content.split('\n').map(line => line.replace(/\r$/, ''));
6
+ return parseLines(lines, filename);
7
+ } else if (typeof content === 'object' && content !== null) {
8
+ // Already parsed
9
+ return content;
10
+ } else {
11
+ throw new Error('parse() expects string content or pre-parsed object');
5
12
  }
6
- const rawLines = code.split(/\r?\n/);
7
- const lines = rawLines
8
- .map(l => l.trim())
9
- .filter(l => l && !l.startsWith('#') && !l.startsWith('//'));
13
+ }
14
+
15
+ function parseFromFile(filepath) {
16
+ const content = fs.readFileSync(filepath, 'utf8');
17
+ return parse(content, filepath);
18
+ }
10
19
 
20
+ function parseLines(lines, filename) {
21
+ // Remove evolution file parsing - evolution is now in-workflow
22
+ return parseWorkflowLines(lines, filename);
23
+ }
24
+
25
+ function parseWorkflowLines(lines, filename) {
11
26
  const workflow = {
12
- name: 'Unnamed Workflow',
27
+ type: 'workflow',
28
+ name: null,
13
29
  parameters: [],
14
30
  steps: [],
15
31
  returnValues: [],
16
32
  allowedResolvers: [],
17
- resolverPolicy: {
18
- declared: [],
19
- autoInjected: [],
20
- used: [],
21
- warnings: []
22
- },
33
+ maxGenerations: null, // ✅ Updated field name for Constraint: max_generations = X
23
34
  __warnings: [],
24
- __requiresMath: false
35
+ filename: filename
25
36
  };
26
-
37
+
27
38
  let i = 0;
39
+ let currentStep = null;
40
+ let inAllowResolvers = false;
41
+ let inIfBlock = false;
42
+ let ifCondition = null;
43
+ let ifBody = [];
44
+
28
45
  while (i < lines.length) {
29
- let line = lines[i];
30
-
31
- // FIXED: Match both "Allow resolvers" and "Allowed resolvers"
32
- const allowMatch = line.match(/^Allow resolvers\s*:\s*$/i);
33
- if (allowMatch) {
34
- i++;
35
- while (i < lines.length) {
36
- const nextLine = lines[i].trim();
37
- // Stop if line is empty or looks like a new top-level section
38
- if (nextLine === '' || /^[A-Z][a-z]/.test(nextLine)) {
39
- break;
40
- }
41
- // Strip optional YAML list marker: "- name" → "name"
42
- const cleanVal = nextLine.replace(/^\-\s*/, '').trim();
43
- if (cleanVal) {
44
- workflow.allowedResolvers.push(cleanVal);
45
- workflow.resolverPolicy.declared.push(cleanVal);
46
- }
47
- i++;
48
- }
46
+ let line = lines[i++].trim();
47
+
48
+ // Skip empty lines and comments
49
+ if (line === '' || line.startsWith('#')) {
49
50
  continue;
50
51
  }
51
-
52
- // ✅ NEW: Parse database Persist steps
53
- const dbPersistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+db:([^\s]+)$/i);
54
- if (dbPersistMatch) {
55
- workflow.steps.push({
56
- type: 'persist-db',
57
- source: dbPersistMatch[1].trim(),
58
- collection: dbPersistMatch[2].trim()
59
- });
60
- i++;
52
+
53
+ // Parse Workflow declaration
54
+ if (line.startsWith('Workflow ')) {
55
+ const match = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?$/i);
56
+ if (match) {
57
+ workflow.name = match[1];
58
+ if (match[2]) {
59
+ workflow.parameters = match[2].split(',').map(p => p.trim()).filter(p => p !== '');
60
+ }
61
+ } else {
62
+ workflow.__warnings.push(`Invalid Workflow syntax: ${line}`);
63
+ }
61
64
  continue;
62
65
  }
63
-
64
- // Parse file Persist steps
65
- const persistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]+)"$/i);
66
- if (persistMatch) {
67
- workflow.steps.push({
68
- type: 'persist',
69
- source: persistMatch[1].trim(),
70
- destination: persistMatch[2].trim()
71
- });
72
- i++;
66
+
67
+ // Parse Constraint: max_generations = X (✅ Updated syntax)
68
+ if (line.startsWith('Constraint: max_generations = ')) {
69
+ const match = line.match(/^Constraint:\s+max_generations\s*=\s*(\d+)$/i);
70
+ if (match) {
71
+ workflow.maxGenerations = parseInt(match[1], 10);
72
+ } else {
73
+ workflow.__warnings.push(`Invalid Constraint syntax: ${line}`);
74
+ }
73
75
  continue;
74
76
  }
75
-
76
- // ---- Math step patterns (standalone) ----
77
- let mathAdd = line.match(/^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
78
- if (mathAdd) {
79
- workflow.__requiresMath = true;
80
- workflow.steps.push({
81
- type: 'calculate',
82
- expression: `add({${mathAdd[1]}}, {${mathAdd[2]}})`,
83
- saveAs: mathAdd[3].trim()
84
- });
85
- i++;
77
+
78
+ // Parse Allow resolvers section
79
+ if (line === 'Allow resolvers:') {
80
+ inAllowResolvers = true;
86
81
  continue;
87
82
  }
88
-
89
- let mathSub = line.match(/^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
90
- if (mathSub) {
91
- workflow.__requiresMath = true;
92
- workflow.steps.push({
93
- type: 'calculate',
94
- expression: `subtract({${mathSub[2]}}, {${mathSub[1]}})`, // Fixed: was mathAdd
95
- saveAs: mathSub[3].trim()
96
- });
97
- i++;
83
+
84
+ if (inAllowResolvers) {
85
+ if (line.startsWith('- ')) {
86
+ const resolverName = line.substring(2).trim();
87
+ if (resolverName) {
88
+ workflow.allowedResolvers.push(resolverName);
89
+ }
90
+ } else if (line === '' || line.startsWith('#')) {
91
+ // Continue
92
+ } else {
93
+ // End of Allow resolvers section
94
+ inAllowResolvers = false;
95
+ i--; // Re-process this line
96
+ continue;
97
+ }
98
98
  continue;
99
99
  }
100
-
101
- let mathMul = line.match(/^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
102
- if (mathMul) {
103
- workflow.__requiresMath = true;
104
- workflow.steps.push({
105
- type: 'calculate',
106
- expression: `multiply({${mathMul[1]}}, {${mathMul[2]}})`,
107
- saveAs: mathMul[3].trim()
108
- });
109
- i++;
100
+
101
+ // Parse Step declarations
102
+ const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
103
+ if (stepMatch) {
104
+ // Save previous step if it exists
105
+ if (currentStep) {
106
+ workflow.steps.push(currentStep);
107
+ currentStep = null;
108
+ }
109
+
110
+ const stepNumber = parseInt(stepMatch[1], 10);
111
+ const stepContent = stepMatch[2];
112
+
113
+ currentStep = {
114
+ type: 'action',
115
+ stepNumber: stepNumber,
116
+ actionRaw: stepContent,
117
+ saveAs: null,
118
+ constraints: {}
119
+ };
110
120
  continue;
111
121
  }
112
-
113
- let mathDiv = line.match(/^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i);
114
- if (mathDiv) {
115
- workflow.__requiresMath = true;
116
- workflow.steps.push({
117
- type: 'calculate',
118
- expression: `divide({${mathDiv[1]}}, {${mathDiv[2]}})`,
119
- saveAs: mathDiv[3].trim()
120
- });
121
- i++;
122
+
123
+ // Parse Evolve steps ( NEW IN-WORKFLOW EVOLUTION)
124
+ const evolveMatch = line.match(/^Evolve\s+([^\s]+)\s+using\s+feedback:\s*"([^"]*)"$/i);
125
+ if (evolveMatch) {
126
+ if (currentStep) {
127
+ workflow.steps.push(currentStep);
128
+ currentStep = null;
129
+ }
130
+
131
+ currentStep = {
132
+ type: 'evolve',
133
+ stepNumber: workflow.steps.length + 1,
134
+ targetResolver: evolveMatch[1].trim(),
135
+ feedback: evolveMatch[2],
136
+ saveAs: null,
137
+ constraints: {}
138
+ };
122
139
  continue;
123
140
  }
124
-
125
- // ---- Workflow declaration ----
126
- const wfMatch = line.match(/^Workflow\s+"([^"]+)"(?:\s+with\s+(.+))?/i);
127
- if (wfMatch) {
128
- workflow.name = wfMatch[1];
129
- workflow.parameters = wfMatch[2]
130
- ? wfMatch[2].split(',').map(p => p.trim())
131
- : [];
132
- i++;
141
+
142
+ // Parse Save as
143
+ const saveMatch = line.match(/^Save as\s+(.+)$/i);
144
+ if (saveMatch && currentStep) {
145
+ currentStep.saveAs = saveMatch[1].trim();
133
146
  continue;
134
147
  }
135
-
136
- // ---- Return statement (updated: auto-detect math) ----
137
- const returnMatch = line.match(/^Return\s+(.+)$/i);
138
- if (returnMatch) {
139
- const returns = returnMatch[1].split(',').map(v => v.trim());
140
- workflow.returnValues = returns;
141
- // Check if any return vars come from math steps
142
- for (const retVar of returns) {
143
- const producedByMath = workflow.steps.some(
144
- s => s.saveAs === retVar && s.type === 'calculate'
145
- );
146
- if (producedByMath) workflow.__requiresMath = true;
147
- }
148
- i++;
148
+
149
+ // Parse If/When conditions
150
+ const ifMatch = line.match(/^(?:If|When)\s+(.+)$/i);
151
+ if (ifMatch) {
152
+ ifCondition = ifMatch[1].trim();
153
+ inIfBlock = true;
154
+ ifBody = [];
149
155
  continue;
150
156
  }
151
-
152
- // ---- Step parsing (with inline math detection) ----
153
- const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
154
- if (stepMatch) {
155
- const stepNum = parseInt(stepMatch[1], 10);
156
- const raw = stepMatch[2].trim();
157
-
158
- let mathDetected = null;
159
- let expr = '';
160
- let saveVar = null;
161
- const mathOps = [
162
- { re: /^Add\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'add' },
163
- { re: /^Subtract\s+\{(.+?)\}\s+from\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'subtract' },
164
- { re: /^Multiply\s+\{(.+?)\}\s+and\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'multiply' },
165
- { re: /^Divide\s+\{(.+?)\}\s+by\s+\{(.+?)\}\s+Save as\s+(.+)$/i, fn: 'divide' }
166
- ];
167
- for (const op of mathOps) {
168
- const m = raw.match(op.re);
169
- if (m) {
170
- mathDetected = op.fn;
171
- saveVar = m[3].trim();
172
- if (op.fn === 'subtract') {
173
- expr = `subtract({${m[2]}}, {${m[1]}})`;
174
- } else {
175
- expr = `${op.fn}({${m[1]}}, {${m[2]}})`;
176
- }
177
- break;
178
- }
157
+
158
+ const endIfMatch = line.match(/^End(?:If)?$/i);
159
+ if (endIfMatch && inIfBlock) {
160
+ if (currentStep) {
161
+ workflow.steps.push(currentStep);
162
+ currentStep = null;
179
163
  }
180
- if (mathDetected) workflow.__requiresMath = true;
164
+
181
165
  workflow.steps.push({
182
- type: mathDetected ? 'calculate' : 'action',
183
- stepNumber: stepNum,
184
- actionRaw: mathDetected ? null : raw,
185
- expression: mathDetected ? expr : undefined,
186
- saveAs: saveVar,
187
- constraints: {}
166
+ type: 'if',
167
+ condition: ifCondition,
168
+ body: ifBody,
169
+ stepNumber: workflow.steps.length + 1
188
170
  });
189
- i++;
171
+
172
+ inIfBlock = false;
173
+ ifCondition = null;
174
+ ifBody = [];
190
175
  continue;
191
176
  }
192
-
193
- // ---- Save as (for legacy or multi-line steps) ----
194
- const saveMatch = line.match(/^Save as\s+(.+)$/i);
195
- if (saveMatch && workflow.steps.length > 0) {
196
- const lastStep = workflow.steps[workflow.steps.length - 1];
197
- lastStep.saveAs = saveMatch[1].trim();
198
- if (lastStep.saveAs.match(/[A-Z][A-Za-z0-9_]*/)) {
199
- workflow.__requiresMath = true;
200
- }
201
- i++;
177
+
178
+ // Handle lines inside If block
179
+ if (inIfBlock) {
180
+ ifBody.push(line);
202
181
  continue;
203
182
  }
204
-
205
- // ---- Constraint parsing ----
206
- const constraintMatch = line.match(/^Constraint:\s*(.+)$/i);
207
- if (constraintMatch && workflow.steps.length > 0) {
208
- const lastStep = workflow.steps[workflow.steps.length - 1];
209
- if (!lastStep.constraints) lastStep.constraints = {};
210
- const eq = constraintMatch[1].match(/^([^=]+)=\s*(.+)$/);
211
- if (eq) {
212
- let key = eq[1].trim();
213
- let value = eq[2].trim();
214
- if (value.startsWith('[') && value.endsWith(']')) {
215
- value = value.slice(1, -1).split(',').map(v =>
216
- v.trim().replace(/^"/, '').replace(/"$/, '')
217
- );
218
- } else if (!isNaN(value)) {
219
- value = Number(value);
220
- } else if (value.startsWith('"') && value.endsWith('"')) {
221
- value = value.slice(1, -1);
222
- }
223
- lastStep.constraints[key] = value;
183
+
184
+ // Parse Return statement
185
+ const returnMatch = line.match(/^Return\s+(.+)$/i);
186
+ if (returnMatch) {
187
+ if (currentStep) {
188
+ workflow.steps.push(currentStep);
189
+ currentStep = null;
224
190
  }
225
- i++;
191
+ workflow.returnValues = returnMatch[1].split(',').map(r => r.trim()).filter(r => r !== '');
226
192
  continue;
227
193
  }
228
-
229
- i++;
194
+
195
+ // If we reach here and have unprocessed content, it's likely a workflow line without "Step X:"
196
+ // Try to handle it as a step
197
+ if (line.trim() !== '') {
198
+ if (!currentStep) {
199
+ currentStep = {
200
+ type: 'action',
201
+ stepNumber: workflow.steps.length + 1,
202
+ actionRaw: line,
203
+ saveAs: null,
204
+ constraints: {}
205
+ };
206
+ } else {
207
+ // Append to current step action (multi-line)
208
+ currentStep.actionRaw += ' ' + line;
209
+ }
210
+ }
230
211
  }
231
-
232
- // ============================
233
- // LINT & POLICY FINALIZATION
234
- // ============================
235
- if (workflow.__requiresMath) {
236
- workflow.resolverPolicy.used.push('builtInMathResolver');
237
- if (!workflow.resolverPolicy.declared.includes('builtInMathResolver')) {
238
- workflow.resolverPolicy.autoInjected.push('builtInMathResolver');
239
- workflow.allowedResolvers.unshift('builtInMathResolver');
240
- workflow.__warnings.push(
241
- 'Math operations detected. builtInMathResolver auto-injected.'
242
- );
212
+
213
+ // Don't forget the last step
214
+ if (currentStep) {
215
+ workflow.steps.push(currentStep);
216
+ }
217
+
218
+ // Post-process steps to extract Save as from actionRaw
219
+ workflow.steps.forEach(step => {
220
+ if (step.actionRaw && step.saveAs === null) {
221
+ const saveInAction = step.actionRaw.match(/(.+?)\s+Save as\s+(.+)$/i);
222
+ if (saveInAction) {
223
+ step.actionRaw = saveInAction[1].trim();
224
+ step.saveAs = saveInAction[2].trim();
225
+ }
243
226
  }
227
+ });
228
+
229
+ // Check for common issues
230
+ if (!workflow.name) {
231
+ workflow.__warnings.push('Workflow name not found');
244
232
  }
245
-
246
- if (workflow.resolverPolicy.declared.length === 0) {
247
- workflow.__warnings.push(
248
- 'No "Allow resolvers" section declared. Workflow will run in restricted mode.'
249
- );
233
+
234
+ if (workflow.steps.length === 0) {
235
+ workflow.__warnings.push('No steps found in workflow');
250
236
  }
251
-
252
- workflow.resolverPolicy.warnings = workflow.__warnings.slice();
237
+
238
+ if (workflow.returnValues.length === 0 && workflow.steps.length > 0) {
239
+ workflow.__warnings.push('No Return statement found');
240
+ }
241
+
253
242
  return workflow;
254
243
  }
255
244
 
256
- // ---------------------------
257
- // Parse nested blocks (updated)
258
- // ---------------------------
259
- function parseBlock(lines) {
260
- const steps = [];
261
- let current = null;
262
- for (const line of lines) {
263
- const stepMatch = line.match(/^Step\s+(\d+)\s*:\s*(.+)$/i);
264
- if (stepMatch) {
265
- current = {
266
- type: 'action',
267
- stepNumber: parseInt(stepMatch[1], 10),
268
- actionRaw: stepMatch[2].trim(),
269
- saveAs: null,
270
- constraints: {}
271
- };
272
- steps.push(current);
273
- continue;
274
- }
275
- const saveMatch = line.match(/^Save as\s+(.+)$/i);
276
- if (saveMatch && current) current.saveAs = saveMatch[1].trim();
277
- const debriefMatch = line.match(/^Debrief\s+(\w+)\s+with\s+"(.+)"$/i);
278
- if (debriefMatch) {
279
- steps.push({ type: 'debrief', agent: debriefMatch[1], message: debriefMatch[2] });
280
- }
281
- const evolveMatch = line.match(/^Evolve\s+(\w+)\s+using\s+feedback:\s+"(.+)"$/i);
282
- if (evolveMatch) {
283
- steps.push({ type: 'evolve', agent: evolveMatch[1], feedback: evolveMatch[2] });
284
- }
285
- const promptMatch = line.match(/^Prompt user to\s+"(.+)"$/i);
286
- if (promptMatch) {
287
- steps.push({ type: 'prompt', question: promptMatch[1], saveAs: null });
288
- }
289
- const useMatch = line.match(/^Use\s+(.+)$/i);
290
- if (useMatch) {
291
- steps.push({ type: 'use', tool: useMatch[1].trim(), saveAs: null, constraints: {} });
292
- }
293
- const askMatch = line.match(/^Ask\s+(.+)$/i);
294
- if (askMatch) {
295
- steps.push({ type: 'ask', target: askMatch[1].trim(), saveAs: null, constraints: {} });
296
- }
297
- // ✅ Parse file Persist in blocks
298
- const persistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+"([^"]+)"$/i);
299
- if (persistMatch) {
300
- steps.push({
301
- type: 'persist',
302
- source: persistMatch[1].trim(),
303
- destination: persistMatch[2].trim()
304
- });
305
- }
306
- // ✅ NEW: Parse database Persist in blocks
307
- const dbPersistMatch = line.match(/^Persist\s+([^\s]+)\s+to\s+db:([^\s]+)$/i);
308
- if (dbPersistMatch) {
309
- steps.push({
310
- type: 'persist-db',
311
- source: dbPersistMatch[1].trim(),
312
- collection: dbPersistMatch[2].trim()
313
- });
314
- }
245
+ function validate(workflow) {
246
+ const errors = [];
247
+
248
+ if (workflow.maxGenerations !== null && workflow.maxGenerations <= 0) {
249
+ errors.push('max_generations must be positive');
315
250
  }
316
- return steps;
251
+
252
+ return errors;
317
253
  }
318
254
 
319
- module.exports = { parse };
255
+ module.exports = { parse, parseFromFile, parseLines, validate };
package/src/runtime.js CHANGED
@@ -244,28 +244,43 @@ class RuntimeAPI {
244
244
  this.allowedResolvers.add('builtInMathResolver');
245
245
  }
246
246
 
247
+ // Handle different resolver input formats
248
+ let resolversToRun = [];
249
+
247
250
  if (agentResolver && Array.isArray(agentResolver._chain)) {
248
- for (let idx = 0; idx < agentResolver._chain.length; idx++) {
249
- const resolver = agentResolver._chain[idx];
250
- validateResolver(resolver);
251
-
252
- try {
253
- const out = await resolver(action, this.context);
254
- outputs.push(out);
255
- this.context[`__resolver_${idx}`] = out;
256
- } catch (e) {
257
- this.addWarning(`Resolver ${resolver?.name || idx} failed for action "${action}": ${e.message}`);
258
- outputs.push(null);
251
+ // Resolver chain mode
252
+ resolversToRun = agentResolver._chain;
253
+ } else if (Array.isArray(agentResolver)) {
254
+ // Array of resolvers mode (what npx olang passes with -r flags)
255
+ resolversToRun = agentResolver;
256
+ } else if (agentResolver) {
257
+ // Single resolver mode
258
+ resolversToRun = [agentResolver];
259
+ }
260
+
261
+ // ✅ Return the FIRST resolver that returns a non-undefined result
262
+ for (let idx = 0; idx < resolversToRun.length; idx++) {
263
+ const resolver = resolversToRun[idx];
264
+ validateResolver(resolver);
265
+
266
+ try {
267
+ const out = await resolver(action, this.context);
268
+ outputs.push(out);
269
+ this.context[`__resolver_${idx}`] = out;
270
+
271
+ // ✅ If resolver handled the action (returned non-undefined), use it immediately
272
+ if (out !== undefined) {
273
+ return out;
259
274
  }
275
+ } catch (e) {
276
+ this.addWarning(`Resolver ${resolver?.resolverName || resolver?.name || idx} failed for action "${action}": ${e.message}`);
277
+ outputs.push(null);
278
+ this.context[`__resolver_${idx}`] = null;
260
279
  }
261
- } else {
262
- validateResolver(agentResolver);
263
- const out = await agentResolver(action, this.context);
264
- outputs.push(out);
265
- this.context['__resolver_0'] = out;
266
280
  }
267
281
 
268
- return outputs[outputs.length - 1];
282
+ // If no resolver handled the action, return undefined
283
+ return undefined;
269
284
  };
270
285
 
271
286
  switch (stepType) {
@@ -313,6 +328,35 @@ class RuntimeAPI {
313
328
  break;
314
329
  }
315
330
 
331
+ case 'evolve': {
332
+ // ✅ Handle in-workflow Evolve steps
333
+ const { targetResolver, feedback } = step;
334
+
335
+ if (this.verbose) {
336
+ console.log(`🔄 Evolve step: ${targetResolver} with feedback: "${feedback}"`);
337
+ }
338
+
339
+ // Basic evolution: record the request (free tier)
340
+ const evolutionResult = {
341
+ resolver: targetResolver,
342
+ feedback: feedback,
343
+ status: 'evolution_requested',
344
+ timestamp: new Date().toISOString(),
345
+ workflow: this.context.workflow_name
346
+ };
347
+
348
+ // ✅ Check for Advanced Evolution Service (paid tier)
349
+ if (process.env.OLANG_EVOLUTION_API_KEY) {
350
+ evolutionResult.status = 'advanced_evolution_enabled';
351
+ evolution resultList.message = 'Advanced evolution service would process this request';
352
+ }
353
+
354
+ if (step.saveAs) {
355
+ this.context[step.saveAs] = evolutionResult;
356
+ }
357
+ break;
358
+ }
359
+
316
360
  case 'if': {
317
361
  if (this.evaluateCondition(step.condition, this.context)) {
318
362
  for (const s of step.body) await this.executeStep(s, agentResolver);
@@ -403,7 +447,7 @@ class RuntimeAPI {
403
447
  const db = this.dbClient.client.db(process.env.DB_NAME || 'olang');
404
448
  await db.collection(step.collection).insertOne({
405
449
  workflow_name: this.context.workflow_name || 'unknown',
406
- sourceValue,
450
+ data: sourceValue,
407
451
  created_at: new Date()
408
452
  });
409
453
  break;
@@ -437,11 +481,23 @@ class RuntimeAPI {
437
481
  }
438
482
 
439
483
  async executeWorkflow(workflow, inputs, agentResolver) {
484
+ // Handle regular workflows only (Evolve is a step type now)
485
+ if (workflow.type !== 'workflow') {
486
+ throw new Error(`Unknown workflow type: ${workflow.type}`);
487
+ }
488
+
440
489
  // ✅ Inject workflow name into context
441
490
  this.context = {
442
491
  ...inputs,
443
492
  workflow_name: workflow.name
444
493
  };
494
+
495
+ // ✅ Check generation constraint from Constraint: max_generations = X
496
+ const currentGeneration = inputs.__generation || 1;
497
+ if (workflow.maxGenerations !== null && currentGeneration > workflow.maxGenerations) {
498
+ throw new Error(`Workflow generation ${currentGeneration} exceeds Constraint: max_generations = ${workflow.maxGenerations}`);
499
+ }
500
+
445
501
  this.workflowSteps = workflow.steps;
446
502
  this.allowedResolvers = new Set(workflow.allowedResolvers || []);
447
503