@cogitator-ai/self-modifying 17.0.8 → 17.0.11

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 (91) hide show
  1. package/README.md +42 -1
  2. package/dist/architecture-evolution/capability-analyzer.d.ts +1 -1
  3. package/dist/architecture-evolution/capability-analyzer.d.ts.map +1 -1
  4. package/dist/architecture-evolution/capability-analyzer.js +4 -3
  5. package/dist/architecture-evolution/capability-analyzer.js.map +1 -1
  6. package/dist/architecture-evolution/evolution-strategy.d.ts.map +1 -1
  7. package/dist/architecture-evolution/evolution-strategy.js +3 -2
  8. package/dist/architecture-evolution/evolution-strategy.js.map +1 -1
  9. package/dist/architecture-evolution/parameter-optimizer.d.ts +1 -0
  10. package/dist/architecture-evolution/parameter-optimizer.d.ts.map +1 -1
  11. package/dist/architecture-evolution/parameter-optimizer.js +7 -4
  12. package/dist/architecture-evolution/parameter-optimizer.js.map +1 -1
  13. package/dist/architecture-evolution/prompts.d.ts.map +1 -1
  14. package/dist/architecture-evolution/prompts.js +7 -6
  15. package/dist/architecture-evolution/prompts.js.map +1 -1
  16. package/dist/constraints/index.d.ts +1 -1
  17. package/dist/constraints/index.d.ts.map +1 -1
  18. package/dist/constraints/index.js +1 -1
  19. package/dist/constraints/index.js.map +1 -1
  20. package/dist/constraints/modification-validator.d.ts.map +1 -1
  21. package/dist/constraints/modification-validator.js +12 -21
  22. package/dist/constraints/modification-validator.js.map +1 -1
  23. package/dist/constraints/safety-constraints.d.ts +2 -1
  24. package/dist/constraints/safety-constraints.d.ts.map +1 -1
  25. package/dist/constraints/safety-constraints.js +11 -1
  26. package/dist/constraints/safety-constraints.js.map +1 -1
  27. package/dist/events/event-emitter.d.ts.map +1 -1
  28. package/dist/events/event-emitter.js +8 -1
  29. package/dist/events/event-emitter.js.map +1 -1
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/meta-reasoning/meta-reasoner.d.ts.map +1 -1
  35. package/dist/meta-reasoning/meta-reasoner.js +6 -0
  36. package/dist/meta-reasoning/meta-reasoner.js.map +1 -1
  37. package/dist/meta-reasoning/prompts.d.ts.map +1 -1
  38. package/dist/meta-reasoning/prompts.js +37 -3
  39. package/dist/meta-reasoning/prompts.js.map +1 -1
  40. package/dist/self-modifying-agent.d.ts +2 -1
  41. package/dist/self-modifying-agent.d.ts.map +1 -1
  42. package/dist/self-modifying-agent.js +122 -13
  43. package/dist/self-modifying-agent.js.map +1 -1
  44. package/dist/tool-generation/generated-tool-store.d.ts.map +1 -1
  45. package/dist/tool-generation/generated-tool-store.js +2 -4
  46. package/dist/tool-generation/generated-tool-store.js.map +1 -1
  47. package/dist/tool-generation/prompts.d.ts +1 -1
  48. package/dist/tool-generation/prompts.d.ts.map +1 -1
  49. package/dist/tool-generation/prompts.js +26 -21
  50. package/dist/tool-generation/prompts.js.map +1 -1
  51. package/dist/tool-generation/tool-generator.d.ts +1 -1
  52. package/dist/tool-generation/tool-generator.d.ts.map +1 -1
  53. package/dist/tool-generation/tool-generator.js +12 -13
  54. package/dist/tool-generation/tool-generator.js.map +1 -1
  55. package/dist/tool-generation/tool-sandbox.d.ts.map +1 -1
  56. package/dist/tool-generation/tool-sandbox.js +6 -2
  57. package/dist/tool-generation/tool-sandbox.js.map +1 -1
  58. package/dist/tool-generation/tool-validator.d.ts.map +1 -1
  59. package/dist/tool-generation/tool-validator.js +0 -3
  60. package/dist/tool-generation/tool-validator.js.map +1 -1
  61. package/dist/utils/index.d.ts +1 -0
  62. package/dist/utils/index.d.ts.map +1 -1
  63. package/dist/utils/index.js +34 -0
  64. package/dist/utils/index.js.map +1 -1
  65. package/dist/utils/llm-helper.d.ts +1 -0
  66. package/dist/utils/llm-helper.d.ts.map +1 -1
  67. package/dist/utils/llm-helper.js +1 -1
  68. package/dist/utils/llm-helper.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/__tests__/events.test.ts +112 -0
  71. package/src/__tests__/self-modifying-agent.test.ts +197 -0
  72. package/src/__tests__/utils.test.ts +100 -0
  73. package/src/architecture-evolution/capability-analyzer.ts +5 -3
  74. package/src/architecture-evolution/evolution-strategy.ts +3 -2
  75. package/src/architecture-evolution/parameter-optimizer.ts +8 -7
  76. package/src/architecture-evolution/prompts.ts +7 -6
  77. package/src/constraints/index.ts +1 -0
  78. package/src/constraints/modification-validator.ts +13 -22
  79. package/src/constraints/safety-constraints.ts +16 -1
  80. package/src/events/event-emitter.ts +9 -1
  81. package/src/index.ts +3 -0
  82. package/src/meta-reasoning/meta-reasoner.ts +9 -0
  83. package/src/meta-reasoning/prompts.ts +43 -3
  84. package/src/self-modifying-agent.ts +132 -13
  85. package/src/tool-generation/generated-tool-store.ts +2 -4
  86. package/src/tool-generation/prompts.ts +26 -21
  87. package/src/tool-generation/tool-generator.ts +13 -15
  88. package/src/tool-generation/tool-sandbox.ts +12 -2
  89. package/src/tool-generation/tool-validator.ts +0 -5
  90. package/src/utils/index.ts +40 -0
  91. package/src/utils/llm-helper.ts +2 -2
@@ -0,0 +1,112 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { SelfModifyingEventEmitter } from '../events';
3
+ import type { SelfModifyingEvent } from '@cogitator-ai/types';
4
+
5
+ function makeEvent(type: string): SelfModifyingEvent {
6
+ return { type, runId: 'test', timestamp: new Date(), data: {} } as SelfModifyingEvent;
7
+ }
8
+
9
+ describe('SelfModifyingEventEmitter', () => {
10
+ it('emits to specific type handlers', async () => {
11
+ const emitter = new SelfModifyingEventEmitter();
12
+ const handler = vi.fn();
13
+
14
+ emitter.on('run_started', handler);
15
+ await emitter.emit(makeEvent('run_started'));
16
+
17
+ expect(handler).toHaveBeenCalledOnce();
18
+ });
19
+
20
+ it('emits to wildcard handlers', async () => {
21
+ const emitter = new SelfModifyingEventEmitter();
22
+ const handler = vi.fn();
23
+
24
+ emitter.on('*', handler);
25
+ await emitter.emit(makeEvent('run_started'));
26
+ await emitter.emit(makeEvent('run_completed'));
27
+
28
+ expect(handler).toHaveBeenCalledTimes(2);
29
+ });
30
+
31
+ it('does not call handlers for different event types', async () => {
32
+ const emitter = new SelfModifyingEventEmitter();
33
+ const handler = vi.fn();
34
+
35
+ emitter.on('run_started', handler);
36
+ await emitter.emit(makeEvent('run_completed'));
37
+
38
+ expect(handler).not.toHaveBeenCalled();
39
+ });
40
+
41
+ it('removes handler with off()', async () => {
42
+ const emitter = new SelfModifyingEventEmitter();
43
+ const handler = vi.fn();
44
+
45
+ emitter.on('run_started', handler);
46
+ emitter.off('run_started', handler);
47
+ await emitter.emit(makeEvent('run_started'));
48
+
49
+ expect(handler).not.toHaveBeenCalled();
50
+ });
51
+
52
+ it('returns unsubscribe function from on()', async () => {
53
+ const emitter = new SelfModifyingEventEmitter();
54
+ const handler = vi.fn();
55
+
56
+ const unsub = emitter.on('run_started', handler);
57
+ unsub();
58
+ await emitter.emit(makeEvent('run_started'));
59
+
60
+ expect(handler).not.toHaveBeenCalled();
61
+ });
62
+
63
+ it('survives handler errors with Promise.allSettled', async () => {
64
+ const emitter = new SelfModifyingEventEmitter();
65
+ const goodHandler = vi.fn();
66
+ const badHandler = vi.fn(() => {
67
+ throw new Error('boom');
68
+ });
69
+
70
+ emitter.on('run_started', badHandler);
71
+ emitter.on('run_started', goodHandler);
72
+
73
+ await emitter.emit(makeEvent('run_started'));
74
+
75
+ expect(badHandler).toHaveBeenCalled();
76
+ expect(goodHandler).toHaveBeenCalled();
77
+ });
78
+
79
+ it('removeAllListeners clears specific event', () => {
80
+ const emitter = new SelfModifyingEventEmitter();
81
+ emitter.on('run_started', vi.fn());
82
+ emitter.on('run_completed', vi.fn());
83
+
84
+ emitter.removeAllListeners('run_started');
85
+
86
+ expect(emitter.listenerCount('run_started')).toBe(0);
87
+ expect(emitter.listenerCount('run_completed')).toBe(1);
88
+ });
89
+
90
+ it('removeAllListeners clears all events', () => {
91
+ const emitter = new SelfModifyingEventEmitter();
92
+ emitter.on('run_started', vi.fn());
93
+ emitter.on('run_completed', vi.fn());
94
+ emitter.on('*', vi.fn());
95
+
96
+ emitter.removeAllListeners();
97
+
98
+ expect(emitter.listenerCount('run_started')).toBe(0);
99
+ expect(emitter.listenerCount('run_completed')).toBe(0);
100
+ expect(emitter.listenerCount('*')).toBe(0);
101
+ });
102
+
103
+ it('listenerCount returns correct count', () => {
104
+ const emitter = new SelfModifyingEventEmitter();
105
+ expect(emitter.listenerCount('run_started')).toBe(0);
106
+
107
+ emitter.on('run_started', vi.fn());
108
+ emitter.on('run_started', vi.fn());
109
+
110
+ expect(emitter.listenerCount('run_started')).toBe(2);
111
+ });
112
+ });
@@ -0,0 +1,197 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { SelfModifyingAgent } from '../self-modifying-agent';
3
+ import type { Agent, LLMBackend, Tool, ChatResponse, ToolCall } from '@cogitator-ai/types';
4
+ import { z } from 'zod';
5
+
6
+ function createMockAgent(tools: Tool[] = []): Agent {
7
+ return {
8
+ name: 'test-agent',
9
+ model: 'test-model',
10
+ instructions: 'You are a test assistant.',
11
+ tools,
12
+ config: { temperature: 0.5 },
13
+ } as Agent;
14
+ }
15
+
16
+ function createMockTool(name: string, result: unknown): Tool {
17
+ return {
18
+ name,
19
+ description: `Mock tool: ${name}`,
20
+ parameters: z.object({ input: z.string() }),
21
+ execute: vi.fn().mockResolvedValue(result),
22
+ toJSON: () => ({
23
+ name,
24
+ description: `Mock tool: ${name}`,
25
+ parameters: { type: 'object' as const, properties: { input: { type: 'string' } } },
26
+ }),
27
+ };
28
+ }
29
+
30
+ function chatResponse(content: string, toolCalls?: ToolCall[]): ChatResponse {
31
+ return {
32
+ content,
33
+ toolCalls,
34
+ finishReason: toolCalls?.length ? 'tool_calls' : 'stop',
35
+ usage: { inputTokens: 10, outputTokens: 10, totalTokens: 20 },
36
+ };
37
+ }
38
+
39
+ describe('SelfModifyingAgent tool execution', () => {
40
+ it('passes tools to LLM and returns response', async () => {
41
+ const calculator = createMockTool('calculator', { result: 42 });
42
+
43
+ const mockLLM: Partial<LLMBackend> = {
44
+ chat: vi.fn()
45
+ .mockResolvedValueOnce(chatResponse('', [
46
+ { id: 'call_1', name: 'calculator', arguments: { input: '6*7' } },
47
+ ]))
48
+ .mockResolvedValueOnce(chatResponse('The answer is 42.')),
49
+ };
50
+
51
+ const agent = createMockAgent([calculator]);
52
+ const selfMod = new SelfModifyingAgent({
53
+ agent,
54
+ llm: mockLLM as LLMBackend,
55
+ config: {
56
+ toolGeneration: { enabled: false },
57
+ metaReasoning: { enabled: false },
58
+ architectureEvolution: { enabled: false },
59
+ constraints: { enabled: false },
60
+ },
61
+ });
62
+
63
+ const result = await selfMod.run('What is 6 times 7?');
64
+
65
+ expect(result.output).toBe('The answer is 42.');
66
+ expect(calculator.execute).toHaveBeenCalledOnce();
67
+ expect(calculator.execute).toHaveBeenCalledWith(
68
+ { input: '6*7' },
69
+ expect.objectContaining({ agentId: 'test-agent' }),
70
+ );
71
+
72
+ const chatCalls = vi.mocked(mockLLM.chat!).mock.calls;
73
+ expect(chatCalls[0][0].tools).toBeDefined();
74
+ expect(chatCalls[0][0].tools).toHaveLength(1);
75
+ expect(chatCalls[0][0].tools![0].name).toBe('calculator');
76
+ });
77
+
78
+ it('handles multiple sequential tool calls', async () => {
79
+ const search = createMockTool('search', { results: ['found it'] });
80
+ const format = createMockTool('format', { text: 'formatted result' });
81
+
82
+ const mockLLM: Partial<LLMBackend> = {
83
+ chat: vi.fn()
84
+ .mockResolvedValueOnce(chatResponse('', [
85
+ { id: 'call_1', name: 'search', arguments: { input: 'test' } },
86
+ ]))
87
+ .mockResolvedValueOnce(chatResponse('', [
88
+ { id: 'call_2', name: 'format', arguments: { input: 'found it' } },
89
+ ]))
90
+ .mockResolvedValueOnce(chatResponse('Here is your formatted result.')),
91
+ };
92
+
93
+ const agent = createMockAgent([search, format]);
94
+ const selfMod = new SelfModifyingAgent({
95
+ agent,
96
+ llm: mockLLM as LLMBackend,
97
+ config: {
98
+ toolGeneration: { enabled: false },
99
+ metaReasoning: { enabled: false },
100
+ architectureEvolution: { enabled: false },
101
+ constraints: { enabled: false },
102
+ },
103
+ });
104
+
105
+ const result = await selfMod.run('Search and format');
106
+
107
+ expect(result.output).toBe('Here is your formatted result.');
108
+ expect(search.execute).toHaveBeenCalledOnce();
109
+ expect(format.execute).toHaveBeenCalledOnce();
110
+ expect(vi.mocked(mockLLM.chat!)).toHaveBeenCalledTimes(3);
111
+ });
112
+
113
+ it('handles tool execution errors gracefully', async () => {
114
+ const failTool = createMockTool('broken', null);
115
+ (failTool.execute as ReturnType<typeof vi.fn>).mockRejectedValue(new Error('tool crashed'));
116
+
117
+ const mockLLM: Partial<LLMBackend> = {
118
+ chat: vi.fn()
119
+ .mockResolvedValueOnce(chatResponse('', [
120
+ { id: 'call_1', name: 'broken', arguments: { input: 'test' } },
121
+ ]))
122
+ .mockResolvedValueOnce(chatResponse('The tool failed but I can help anyway.')),
123
+ };
124
+
125
+ const agent = createMockAgent([failTool]);
126
+ const selfMod = new SelfModifyingAgent({
127
+ agent,
128
+ llm: mockLLM as LLMBackend,
129
+ config: {
130
+ toolGeneration: { enabled: false },
131
+ metaReasoning: { enabled: false },
132
+ architectureEvolution: { enabled: false },
133
+ constraints: { enabled: false },
134
+ },
135
+ });
136
+
137
+ const result = await selfMod.run('Use the broken tool');
138
+
139
+ expect(result.output).toBe('The tool failed but I can help anyway.');
140
+ const secondCall = vi.mocked(mockLLM.chat!).mock.calls[1][0];
141
+ const toolMsg = secondCall.messages.find((m: { role: string }) => m.role === 'tool');
142
+ expect(toolMsg?.content).toContain('tool crashed');
143
+ });
144
+
145
+ it('handles unknown tool calls', async () => {
146
+ const mockLLM: Partial<LLMBackend> = {
147
+ chat: vi.fn()
148
+ .mockResolvedValueOnce(chatResponse('', [
149
+ { id: 'call_1', name: 'nonexistent', arguments: { input: 'x' } },
150
+ ]))
151
+ .mockResolvedValueOnce(chatResponse('I could not find that tool.')),
152
+ };
153
+
154
+ const agent = createMockAgent([]);
155
+ const selfMod = new SelfModifyingAgent({
156
+ agent,
157
+ llm: mockLLM as LLMBackend,
158
+ config: {
159
+ toolGeneration: { enabled: false },
160
+ metaReasoning: { enabled: false },
161
+ architectureEvolution: { enabled: false },
162
+ constraints: { enabled: false },
163
+ },
164
+ });
165
+
166
+ const result = await selfMod.run('Call a tool');
167
+
168
+ expect(result.output).toBe('I could not find that tool.');
169
+ const secondCall = vi.mocked(mockLLM.chat!).mock.calls[1][0];
170
+ const toolMsg = secondCall.messages.find((m: { role: string }) => m.role === 'tool');
171
+ expect(toolMsg?.content).toContain('not found');
172
+ });
173
+
174
+ it('works without any tools (no tools passed to LLM)', async () => {
175
+ const mockLLM: Partial<LLMBackend> = {
176
+ chat: vi.fn().mockResolvedValueOnce(chatResponse('Hello world')),
177
+ };
178
+
179
+ const agent = createMockAgent([]);
180
+ const selfMod = new SelfModifyingAgent({
181
+ agent,
182
+ llm: mockLLM as LLMBackend,
183
+ config: {
184
+ toolGeneration: { enabled: false },
185
+ metaReasoning: { enabled: false },
186
+ architectureEvolution: { enabled: false },
187
+ constraints: { enabled: false },
188
+ },
189
+ });
190
+
191
+ const result = await selfMod.run('Just say hi');
192
+
193
+ expect(result.output).toBe('Hello world');
194
+ const chatCall = vi.mocked(mockLLM.chat!).mock.calls[0][0];
195
+ expect(chatCall.tools).toBeUndefined();
196
+ });
197
+ });
@@ -0,0 +1,100 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { extractJson, llmChat } from '../utils';
3
+
4
+ describe('extractJson', () => {
5
+ it('extracts simple JSON object', () => {
6
+ const result = extractJson('{"key": "value"}');
7
+ expect(result).toBe('{"key": "value"}');
8
+ });
9
+
10
+ it('extracts JSON from surrounding text', () => {
11
+ const result = extractJson('Here is the result: {"onTrack": true, "confidence": 0.8} and that is it.');
12
+ expect(result).toBe('{"onTrack": true, "confidence": 0.8}');
13
+ });
14
+
15
+ it('handles nested braces', () => {
16
+ const result = extractJson('{"a": {"b": {"c": 1}}}');
17
+ expect(JSON.parse(result!)).toEqual({ a: { b: { c: 1 } } });
18
+ });
19
+
20
+ it('ignores braces inside strings', () => {
21
+ const result = extractJson('{"text": "hello {world}"}');
22
+ expect(JSON.parse(result!)).toEqual({ text: 'hello {world}' });
23
+ });
24
+
25
+ it('handles escaped quotes in strings', () => {
26
+ const result = extractJson('{"text": "she said \\"hi\\""}');
27
+ expect(result).toBe('{"text": "she said \\"hi\\""}');
28
+ expect(JSON.parse(result!)).toEqual({ text: 'she said "hi"' });
29
+ });
30
+
31
+ it('returns null for no JSON', () => {
32
+ expect(extractJson('no json here')).toBeNull();
33
+ });
34
+
35
+ it('returns null for empty input', () => {
36
+ expect(extractJson('')).toBeNull();
37
+ });
38
+
39
+ it('does not greedily match across multiple JSON objects', () => {
40
+ const input = 'Note: {not valid} But here is: {"valid": true}';
41
+ const result = extractJson(input);
42
+ expect(result).toBe('{not valid}');
43
+ });
44
+
45
+ it('handles first valid JSON from LLM response with preamble containing braces', () => {
46
+ const input = `Here's my analysis:
47
+ {"hasGap": true, "gaps": []}`;
48
+ const result = extractJson(input);
49
+ expect(JSON.parse(result!)).toEqual({ hasGap: true, gaps: [] });
50
+ });
51
+
52
+ it('handles arrays inside objects', () => {
53
+ const result = extractJson('{"items": [1, 2, {"nested": true}]}');
54
+ expect(JSON.parse(result!)).toEqual({ items: [1, 2, { nested: true }] });
55
+ });
56
+ });
57
+
58
+ describe('llmChat', () => {
59
+ it('uses complete when available', async () => {
60
+ const llm = {
61
+ complete: async (_opts: { messages: unknown[] }) => ({
62
+ content: `response from complete`,
63
+ usage: { inputTokens: 10, outputTokens: 20 },
64
+ }),
65
+ chat: async () => ({
66
+ content: 'should not be called',
67
+ usage: { inputTokens: 10, outputTokens: 20 },
68
+ }),
69
+ };
70
+
71
+ const result = await llmChat(llm as never, [{ role: 'user', content: 'hi' }]);
72
+ expect(result).toBe('response from complete');
73
+ });
74
+
75
+ it('falls back to chat when complete is unavailable', async () => {
76
+ const llm = {
77
+ chat: async (opts: { model: string }) => ({
78
+ content: `response from chat with model ${opts.model}`,
79
+ usage: { inputTokens: 10, outputTokens: 20 },
80
+ }),
81
+ };
82
+
83
+ const result = await llmChat(llm as never, [{ role: 'user', content: 'hi' }], {
84
+ model: 'gpt-4',
85
+ });
86
+ expect(result).toBe('response from chat with model gpt-4');
87
+ });
88
+
89
+ it('uses default model when none specified', async () => {
90
+ const llm = {
91
+ chat: async (opts: { model: string }) => ({
92
+ content: opts.model,
93
+ usage: { inputTokens: 10, outputTokens: 20 },
94
+ }),
95
+ };
96
+
97
+ const result = await llmChat(llm as never, [{ role: 'user', content: 'hi' }]);
98
+ expect(result).toBe('default');
99
+ });
100
+ });
@@ -151,7 +151,7 @@ export class CapabilityAnalyzer {
151
151
  let profile: TaskProfile;
152
152
 
153
153
  if (this.llm && this.enableLLMAnalysis) {
154
- profile = await this.analyzWithLLM(taskDescription, context);
154
+ profile = await this.analyzeWithLLM(taskDescription, context);
155
155
  } else {
156
156
  profile = this.analyzeHeuristically(taskDescription, context?.availableTools);
157
157
  }
@@ -167,7 +167,7 @@ export class CapabilityAnalyzer {
167
167
  return profile;
168
168
  }
169
169
 
170
- private async analyzWithLLM(
170
+ private async analyzeWithLLM(
171
171
  taskDescription: string,
172
172
  context?: {
173
173
  availableTools?: Tool[];
@@ -266,9 +266,11 @@ export class CapabilityAnalyzer {
266
266
  }
267
267
 
268
268
  private detectComplexity(task: string, wordCount: number): TaskProfile['complexity'] {
269
+ const taskWords = new Set(task.split(/\s+/));
270
+
269
271
  for (const [level, indicators] of Object.entries(COMPLEXITY_INDICATORS).reverse()) {
270
272
  for (const indicator of indicators) {
271
- if (task.includes(indicator)) {
273
+ if (taskWords.has(indicator)) {
272
274
  return level as TaskProfile['complexity'];
273
275
  }
274
276
  }
@@ -176,17 +176,18 @@ export class EvolutionStrategy {
176
176
  }
177
177
 
178
178
  private sampleNormal(): number {
179
- const u1 = Math.random();
179
+ const u1 = Math.random() || Number.MIN_VALUE;
180
180
  const u2 = Math.random();
181
181
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
182
182
  }
183
183
 
184
184
  updateCandidate(candidate: EvolutionCandidate, reward: number): void {
185
+ const clampedReward = Math.max(0, Math.min(1, reward));
185
186
  const oldScore = candidate.score;
186
187
  const oldCount = candidate.evaluationCount;
187
188
 
188
189
  candidate.evaluationCount++;
189
- candidate.score = (oldScore * oldCount + reward) / candidate.evaluationCount;
190
+ candidate.score = (oldScore * oldCount + clampedReward) / candidate.evaluationCount;
190
191
  }
191
192
 
192
193
  getExplorationRate(): number {
@@ -7,7 +7,7 @@ import type {
7
7
  EvolutionMetrics,
8
8
  } from '@cogitator-ai/types';
9
9
  import { CapabilityAnalyzer } from './capability-analyzer';
10
- import { EvolutionStrategy, type SelectionResult as _SelectionResult } from './evolution-strategy';
10
+ import { EvolutionStrategy } from './evolution-strategy';
11
11
  import {
12
12
  buildCandidateGenerationPrompt,
13
13
  buildPerformanceAnalysisPrompt,
@@ -48,6 +48,7 @@ export class ParameterOptimizer {
48
48
  private candidates: EvolutionCandidate[] = [];
49
49
  private history: HistoricalRecord[] = [];
50
50
  private currentGeneration = 0;
51
+ private lastEvolutionTimestamp = 0;
51
52
  private readonly maxHistorySize = 100;
52
53
 
53
54
  constructor(options: ParameterOptimizerOptions) {
@@ -173,6 +174,7 @@ export class ParameterOptimizer {
173
174
 
174
175
  private async evolve(currentProfile: TaskProfile): Promise<void> {
175
176
  this.currentGeneration++;
177
+ this.lastEvolutionTimestamp = Date.now();
176
178
 
177
179
  const topCandidates = [...this.candidates]
178
180
  .filter((c) => c.evaluationCount > 0)
@@ -325,10 +327,7 @@ export class ParameterOptimizer {
325
327
  }
326
328
 
327
329
  private getLastEvolutionTime(): number {
328
- const latestGenCandidate = this.candidates.find(
329
- (c) => c.generation === this.currentGeneration && c.evaluationCount === 0
330
- );
331
- return latestGenCandidate ? Date.now() - 300000 : 0;
330
+ return this.lastEvolutionTimestamp;
332
331
  }
333
332
 
334
333
  private getRelevantHistory(profile: TaskProfile): HistoricalRecord[] {
@@ -410,7 +409,7 @@ export class ParameterOptimizer {
410
409
  fields++;
411
410
  }
412
411
 
413
- if (a.model !== b.model) {
412
+ if (a.model !== undefined && b.model !== undefined && a.model !== b.model) {
414
413
  distance += 1;
415
414
  fields++;
416
415
  }
@@ -447,8 +446,9 @@ export class ParameterOptimizer {
447
446
  if (evaluatedCandidates.length === 0) return null;
448
447
 
449
448
  const results = evaluatedCandidates.map((c) => {
449
+ const configKey = JSON.stringify(c.config, Object.keys(c.config).sort());
450
450
  const records = this.history.filter(
451
- (h) => JSON.stringify(h.config) === JSON.stringify(c.config)
451
+ (h) => JSON.stringify(h.config, Object.keys(h.config).sort()) === configKey
452
452
  );
453
453
  const avgMetrics =
454
454
  records.length > 0
@@ -503,6 +503,7 @@ export class ParameterOptimizer {
503
503
  this.candidates = [];
504
504
  this.history = [];
505
505
  this.currentGeneration = 0;
506
+ this.lastEvolutionTimestamp = 0;
506
507
  this.evolutionStrategy.reset();
507
508
  }
508
509
  }
@@ -1,4 +1,5 @@
1
1
  import type { TaskProfile, ArchitectureConfig, EvolutionCandidate } from '@cogitator-ai/types';
2
+ import { extractJson } from '../utils';
2
3
 
3
4
  export const ARCHITECTURE_ANALYSIS_SYSTEM_PROMPT = `You are an expert in AI agent architecture optimization.
4
5
  Your task is to analyze tasks and recommend optimal configurations.
@@ -140,11 +141,11 @@ Respond with:
140
141
  }
141
142
 
142
143
  export function parseTaskProfileResponse(response: string): TaskProfile | null {
143
- const jsonMatch = /\{[\s\S]*\}/.exec(response);
144
- if (!jsonMatch) return null;
144
+ const json = extractJson(response);
145
+ if (!json) return null;
145
146
 
146
147
  try {
147
- const parsed = JSON.parse(jsonMatch[0]);
148
+ const parsed = JSON.parse(json);
148
149
 
149
150
  return {
150
151
  complexity: parsed.complexity || 'moderate',
@@ -197,11 +198,11 @@ export function parsePerformanceAnalysisResponse(response: string): {
197
198
  shouldAdopt: boolean;
198
199
  analysis: string;
199
200
  } | null {
200
- const jsonMatch = /\{[\s\S]*\}/.exec(response);
201
- if (!jsonMatch) return null;
201
+ const json = extractJson(response);
202
+ if (!json) return null;
202
203
 
203
204
  try {
204
- const parsed = JSON.parse(jsonMatch[0]);
205
+ const parsed = JSON.parse(json);
205
206
 
206
207
  return {
207
208
  recommendation: String(parsed.recommendation || ''),
@@ -6,6 +6,7 @@ export {
6
6
  mergeSafetyConstraints,
7
7
  mergeCapabilityConstraints,
8
8
  mergeResourceConstraints,
9
+ mergeCustomConstraints,
9
10
  mergeConstraints,
10
11
  } from './safety-constraints';
11
12
 
@@ -92,23 +92,7 @@ export class ModificationValidator {
92
92
  return this.evaluateExpression(rule, request.payload as Record<string, unknown>);
93
93
  }
94
94
 
95
- const payload = request.payload as Record<string, unknown>;
96
-
97
- switch (rule.type) {
98
- case 'invariant':
99
- return this.evaluateExpression(rule.expression ?? '', payload);
100
- case 'precondition':
101
- return this.evaluateExpression(rule.expression ?? '', payload);
102
- case 'postcondition':
103
- return true;
104
- case 'temporal':
105
- if (rule.pattern?.source === 'never') {
106
- return !this.evaluateExpression(rule.expression ?? '', payload);
107
- }
108
- return true;
109
- default:
110
- return true;
111
- }
95
+ return this.evaluateExpression(rule.expression ?? '', request.payload as Record<string, unknown>);
112
96
  }
113
97
 
114
98
  private evaluateExpression(expression: string, context: Record<string, unknown>): boolean {
@@ -127,16 +111,18 @@ export class ModificationValidator {
127
111
 
128
112
  if (conditions.length === 0) return true;
129
113
 
130
- let result = conditions[0];
114
+ const orGroups: boolean[][] = [[]];
115
+ orGroups[0].push(conditions[0]);
116
+
131
117
  for (let i = 0; i < operators.length; i++) {
132
118
  if (operators[i] === 'AND') {
133
- result = result && conditions[i + 1];
119
+ orGroups[orGroups.length - 1].push(conditions[i + 1]);
134
120
  } else {
135
- result = result || conditions[i + 1];
121
+ orGroups.push([conditions[i + 1]]);
136
122
  }
137
123
  }
138
124
 
139
- return result;
125
+ return orGroups.some((group) => group.every(Boolean));
140
126
  }
141
127
 
142
128
  private evaluateSimpleCondition(condition: string, context: Record<string, unknown>): boolean {
@@ -384,6 +370,11 @@ export class ModificationValidator {
384
370
  }
385
371
 
386
372
  getConstraints(): ModificationConstraints {
387
- return { ...this.constraints };
373
+ return {
374
+ safety: [...this.constraints.safety],
375
+ capability: [...this.constraints.capability],
376
+ resource: [...this.constraints.resource],
377
+ custom: this.constraints.custom ? [...this.constraints.custom] : [],
378
+ };
388
379
  }
389
380
  }
@@ -2,6 +2,7 @@ import type {
2
2
  SafetyConstraint,
3
3
  CapabilityConstraint,
4
4
  ResourceConstraint,
5
+ CustomConstraint,
5
6
  ModificationConstraints,
6
7
  } from '@cogitator-ai/types';
7
8
  import { DEFAULT_SAFETY_CONSTRAINTS } from '@cogitator-ai/types';
@@ -84,6 +85,20 @@ export function mergeResourceConstraints(
84
85
  return [...result.values()];
85
86
  }
86
87
 
88
+ export function mergeCustomConstraints(
89
+ base: CustomConstraint[],
90
+ additions: CustomConstraint[]
91
+ ): CustomConstraint[] {
92
+ const result = new Map<string, CustomConstraint>();
93
+ for (const c of base) {
94
+ result.set(c.id, c);
95
+ }
96
+ for (const c of additions) {
97
+ result.set(c.id, c);
98
+ }
99
+ return [...result.values()];
100
+ }
101
+
87
102
  export function mergeConstraints(
88
103
  base: ModificationConstraints,
89
104
  additions: Partial<ModificationConstraints>
@@ -96,6 +111,6 @@ export function mergeConstraints(
96
111
  resource: additions.resource
97
112
  ? mergeResourceConstraints(base.resource, additions.resource)
98
113
  : base.resource,
99
- custom: [...(base.custom ?? []), ...(additions.custom ?? [])],
114
+ custom: mergeCustomConstraints(base.custom ?? [], additions.custom ?? []),
100
115
  };
101
116
  }
@@ -30,7 +30,15 @@ export class SelfModifyingEventEmitter {
30
30
  ...(wildcardHandlers ? [...wildcardHandlers] : []),
31
31
  ];
32
32
 
33
- await Promise.all(allHandlers.map((h) => Promise.resolve(h(event))));
33
+ await Promise.allSettled(
34
+ allHandlers.map((h) => {
35
+ try {
36
+ return Promise.resolve(h(event));
37
+ } catch (e) {
38
+ return Promise.reject(e instanceof Error ? e : new Error(String(e)));
39
+ }
40
+ })
41
+ );
34
42
  }
35
43
 
36
44
  createEvent(