musubi-sdd 3.5.1 → 3.6.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/README.md +25 -3
- package/bin/musubi-orchestrate.js +309 -0
- package/package.json +1 -1
- package/src/llm-providers/anthropic-provider.js +175 -0
- package/src/llm-providers/base-provider.js +221 -0
- package/src/llm-providers/copilot-provider.js +262 -0
- package/src/llm-providers/index.js +214 -0
- package/src/llm-providers/openai-provider.js +205 -0
- package/src/orchestration/index.js +25 -0
- package/src/orchestration/patterns/swarm.js +111 -4
- package/src/orchestration/replanning/adaptive-goal-modifier.js +1150 -0
- package/src/orchestration/replanning/alternative-generator.js +508 -0
- package/src/orchestration/replanning/config.js +378 -0
- package/src/orchestration/replanning/goal-progress-tracker.js +727 -0
- package/src/orchestration/replanning/index.js +82 -0
- package/src/orchestration/replanning/plan-evaluator.js +455 -0
- package/src/orchestration/replanning/plan-monitor.js +379 -0
- package/src/orchestration/replanning/proactive-path-optimizer.js +972 -0
- package/src/orchestration/replanning/replan-history.js +402 -0
- package/src/orchestration/replanning/replanning-engine.js +706 -0
- package/src/templates/agents/claude-code/CLAUDE.md +45 -0
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +20 -0
- package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +89 -0
- package/src/templates/agents/codex/AGENTS.md +13 -0
- package/src/templates/agents/cursor/AGENTS.md +13 -0
- package/src/templates/agents/gemini-cli/GEMINI.md +13 -0
- package/src/templates/agents/github-copilot/AGENTS.md +13 -0
- package/src/templates/agents/qwen-code/QWEN.md +13 -0
- package/src/templates/agents/windsurf/AGENTS.md +13 -0
|
@@ -0,0 +1,972 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Proactive Path Optimizer for MUSUBI Replanning Engine
|
|
3
|
+
* Continuously evaluates and optimizes execution paths, even on success
|
|
4
|
+
* @module orchestration/replanning/proactive-path-optimizer
|
|
5
|
+
* @version 1.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const EventEmitter = require('events');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default configuration for ProactivePathOptimizer
|
|
14
|
+
*/
|
|
15
|
+
const DEFAULT_CONFIG = {
|
|
16
|
+
// Enable proactive optimization
|
|
17
|
+
enabled: true,
|
|
18
|
+
|
|
19
|
+
// Evaluation frequency (every N successful tasks)
|
|
20
|
+
evaluateEvery: 3,
|
|
21
|
+
|
|
22
|
+
// Minimum improvement threshold to trigger re-routing (percentage)
|
|
23
|
+
minImprovementThreshold: 0.15,
|
|
24
|
+
|
|
25
|
+
// Maximum time to spend on optimization (ms)
|
|
26
|
+
optimizationTimeout: 5000,
|
|
27
|
+
|
|
28
|
+
// Consider parallel execution opportunities
|
|
29
|
+
considerParallelization: true,
|
|
30
|
+
|
|
31
|
+
// Consider task merging opportunities
|
|
32
|
+
considerMerging: true,
|
|
33
|
+
|
|
34
|
+
// Consider task reordering for better dependency resolution
|
|
35
|
+
considerReordering: true,
|
|
36
|
+
|
|
37
|
+
// Consider skipping optional tasks when ahead of schedule
|
|
38
|
+
considerSkipping: false,
|
|
39
|
+
|
|
40
|
+
// Learning from past executions
|
|
41
|
+
learningEnabled: true,
|
|
42
|
+
|
|
43
|
+
// Maximum optimization history to keep
|
|
44
|
+
maxHistorySize: 100
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Path metrics for comparison
|
|
49
|
+
*/
|
|
50
|
+
class PathMetrics {
|
|
51
|
+
constructor(data = {}) {
|
|
52
|
+
this.estimatedTime = data.estimatedTime || 0;
|
|
53
|
+
this.estimatedCost = data.estimatedCost || 0;
|
|
54
|
+
this.parallelizationFactor = data.parallelizationFactor || 1.0;
|
|
55
|
+
this.riskScore = data.riskScore || 0;
|
|
56
|
+
this.dependencyComplexity = data.dependencyComplexity || 0;
|
|
57
|
+
this.resourceUtilization = data.resourceUtilization || 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Calculate overall score (lower is better)
|
|
62
|
+
* @returns {number} Overall score
|
|
63
|
+
*/
|
|
64
|
+
getScore() {
|
|
65
|
+
return (
|
|
66
|
+
this.estimatedTime * 0.4 +
|
|
67
|
+
this.estimatedCost * 0.2 +
|
|
68
|
+
(1 - this.parallelizationFactor) * 0.15 +
|
|
69
|
+
this.riskScore * 0.15 +
|
|
70
|
+
this.dependencyComplexity * 0.1
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Compare with another metrics object
|
|
76
|
+
* @param {PathMetrics} other - Other metrics
|
|
77
|
+
* @returns {number} Improvement ratio (positive = better)
|
|
78
|
+
*/
|
|
79
|
+
compareWith(other) {
|
|
80
|
+
const thisScore = this.getScore();
|
|
81
|
+
const otherScore = other.getScore();
|
|
82
|
+
|
|
83
|
+
if (otherScore === 0) return 0;
|
|
84
|
+
return (otherScore - thisScore) / otherScore;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Optimization opportunity
|
|
90
|
+
*/
|
|
91
|
+
class OptimizationOpportunity {
|
|
92
|
+
constructor(data = {}) {
|
|
93
|
+
this.type = data.type; // 'parallelize', 'merge', 'reorder', 'skip', 'substitute'
|
|
94
|
+
this.description = data.description || '';
|
|
95
|
+
this.affectedTasks = data.affectedTasks || [];
|
|
96
|
+
this.estimatedImprovement = data.estimatedImprovement || 0;
|
|
97
|
+
this.confidence = data.confidence || 0.5;
|
|
98
|
+
this.newPath = data.newPath || null;
|
|
99
|
+
this.reasoning = data.reasoning || '';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get weighted score for ranking
|
|
104
|
+
* @returns {number} Weighted score
|
|
105
|
+
*/
|
|
106
|
+
getWeightedScore() {
|
|
107
|
+
return this.estimatedImprovement * this.confidence;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Proactive Path Optimizer
|
|
113
|
+
* Continuously analyzes and optimizes execution paths
|
|
114
|
+
*/
|
|
115
|
+
class ProactivePathOptimizer extends EventEmitter {
|
|
116
|
+
/**
|
|
117
|
+
* Create a proactive path optimizer
|
|
118
|
+
* @param {Object} llmProvider - LLM provider for intelligent optimization
|
|
119
|
+
* @param {Object} [options={}] - Optimizer options
|
|
120
|
+
*/
|
|
121
|
+
constructor(llmProvider, options = {}) {
|
|
122
|
+
super();
|
|
123
|
+
|
|
124
|
+
this.llm = llmProvider;
|
|
125
|
+
this.config = { ...DEFAULT_CONFIG, ...options.config };
|
|
126
|
+
|
|
127
|
+
// State
|
|
128
|
+
this.successCount = 0;
|
|
129
|
+
this.optimizationHistory = [];
|
|
130
|
+
this.learningData = new Map(); // Task patterns -> performance data
|
|
131
|
+
this.currentMetrics = null;
|
|
132
|
+
|
|
133
|
+
// Analyzers
|
|
134
|
+
this.parallelizationAnalyzer = new ParallelizationAnalyzer();
|
|
135
|
+
this.mergingAnalyzer = new MergingAnalyzer();
|
|
136
|
+
this.reorderingAnalyzer = new ReorderingAnalyzer();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Notify optimizer of successful task completion
|
|
141
|
+
* @param {Object} task - Completed task
|
|
142
|
+
* @param {Object} context - Execution context
|
|
143
|
+
* @param {Object} result - Task result
|
|
144
|
+
* @returns {Promise<OptimizationResult|null>} Optimization result if triggered
|
|
145
|
+
*/
|
|
146
|
+
async onTaskSuccess(task, context, result) {
|
|
147
|
+
if (!this.config.enabled) return null;
|
|
148
|
+
|
|
149
|
+
this.successCount++;
|
|
150
|
+
|
|
151
|
+
// Record performance data for learning
|
|
152
|
+
if (this.config.learningEnabled) {
|
|
153
|
+
this.recordPerformance(task, result);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check if we should evaluate
|
|
157
|
+
if (this.successCount % this.config.evaluateEvery !== 0) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Perform proactive optimization
|
|
162
|
+
return this.optimize(context);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Perform proactive path optimization
|
|
167
|
+
* @param {Object} context - Execution context
|
|
168
|
+
* @returns {Promise<OptimizationResult>} Optimization result
|
|
169
|
+
*/
|
|
170
|
+
async optimize(context) {
|
|
171
|
+
const startTime = Date.now();
|
|
172
|
+
|
|
173
|
+
// Calculate current path metrics
|
|
174
|
+
this.currentMetrics = this.calculatePathMetrics(context);
|
|
175
|
+
|
|
176
|
+
// Find optimization opportunities
|
|
177
|
+
const opportunities = await this.findOpportunities(context);
|
|
178
|
+
|
|
179
|
+
if (opportunities.length === 0) {
|
|
180
|
+
return {
|
|
181
|
+
optimized: false,
|
|
182
|
+
reason: 'No optimization opportunities found',
|
|
183
|
+
currentMetrics: this.currentMetrics
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Rank opportunities
|
|
188
|
+
const ranked = this.rankOpportunities(opportunities);
|
|
189
|
+
const best = ranked[0];
|
|
190
|
+
|
|
191
|
+
// Check if improvement meets threshold
|
|
192
|
+
if (best.estimatedImprovement < this.config.minImprovementThreshold) {
|
|
193
|
+
return {
|
|
194
|
+
optimized: false,
|
|
195
|
+
reason: `Best improvement (${(best.estimatedImprovement * 100).toFixed(1)}%) below threshold`,
|
|
196
|
+
opportunities: ranked.slice(0, 3),
|
|
197
|
+
currentMetrics: this.currentMetrics
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Validate the optimization
|
|
202
|
+
const validation = await this.validateOptimization(best, context);
|
|
203
|
+
|
|
204
|
+
if (!validation.valid) {
|
|
205
|
+
return {
|
|
206
|
+
optimized: false,
|
|
207
|
+
reason: validation.reason,
|
|
208
|
+
opportunities: ranked.slice(0, 3),
|
|
209
|
+
currentMetrics: this.currentMetrics
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Record optimization
|
|
214
|
+
this.recordOptimization(best, context);
|
|
215
|
+
|
|
216
|
+
// Emit optimization event
|
|
217
|
+
this.emit('optimization', {
|
|
218
|
+
type: best.type,
|
|
219
|
+
improvement: best.estimatedImprovement,
|
|
220
|
+
affectedTasks: best.affectedTasks,
|
|
221
|
+
newPath: best.newPath
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
optimized: true,
|
|
226
|
+
optimization: best,
|
|
227
|
+
newPath: best.newPath,
|
|
228
|
+
estimatedImprovement: best.estimatedImprovement,
|
|
229
|
+
newMetrics: this.calculatePathMetrics({ ...context, pending: best.newPath }),
|
|
230
|
+
duration: Date.now() - startTime
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Calculate metrics for current execution path
|
|
236
|
+
* @param {Object} context - Execution context
|
|
237
|
+
* @returns {PathMetrics} Path metrics
|
|
238
|
+
*/
|
|
239
|
+
calculatePathMetrics(context) {
|
|
240
|
+
const pending = context.pending || [];
|
|
241
|
+
|
|
242
|
+
// Estimate total time
|
|
243
|
+
let estimatedTime = 0;
|
|
244
|
+
pending.forEach(task => {
|
|
245
|
+
const historical = this.getHistoricalDuration(task);
|
|
246
|
+
estimatedTime += historical || task.estimatedDuration || 30000;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Calculate parallelization factor
|
|
250
|
+
const parallelizable = this.countParallelizable(pending);
|
|
251
|
+
const parallelizationFactor = pending.length > 0
|
|
252
|
+
? parallelizable / pending.length
|
|
253
|
+
: 1.0;
|
|
254
|
+
|
|
255
|
+
// Calculate risk score
|
|
256
|
+
const riskScore = this.calculateRiskScore(pending, context);
|
|
257
|
+
|
|
258
|
+
// Calculate dependency complexity
|
|
259
|
+
const dependencyComplexity = this.calculateDependencyComplexity(pending);
|
|
260
|
+
|
|
261
|
+
return new PathMetrics({
|
|
262
|
+
estimatedTime,
|
|
263
|
+
parallelizationFactor,
|
|
264
|
+
riskScore,
|
|
265
|
+
dependencyComplexity
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Find optimization opportunities
|
|
271
|
+
* @param {Object} context - Execution context
|
|
272
|
+
* @returns {Promise<OptimizationOpportunity[]>} Found opportunities
|
|
273
|
+
*/
|
|
274
|
+
async findOpportunities(context) {
|
|
275
|
+
const opportunities = [];
|
|
276
|
+
const pending = context.pending || [];
|
|
277
|
+
|
|
278
|
+
if (pending.length < 2) return opportunities;
|
|
279
|
+
|
|
280
|
+
// Check parallelization opportunities
|
|
281
|
+
if (this.config.considerParallelization) {
|
|
282
|
+
const parallelOps = this.parallelizationAnalyzer.analyze(pending, context);
|
|
283
|
+
opportunities.push(...parallelOps);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check merging opportunities
|
|
287
|
+
if (this.config.considerMerging) {
|
|
288
|
+
const mergeOps = this.mergingAnalyzer.analyze(pending, context);
|
|
289
|
+
opportunities.push(...mergeOps);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check reordering opportunities
|
|
293
|
+
if (this.config.considerReordering) {
|
|
294
|
+
const reorderOps = this.reorderingAnalyzer.analyze(pending, context);
|
|
295
|
+
opportunities.push(...reorderOps);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Use LLM for additional insights if available
|
|
299
|
+
if (this.llm && pending.length >= 3) {
|
|
300
|
+
const llmOps = await this.getLLMOptimizations(pending, context);
|
|
301
|
+
opportunities.push(...llmOps);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return opportunities;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Get LLM-based optimization suggestions
|
|
309
|
+
* @param {Object[]} pending - Pending tasks
|
|
310
|
+
* @param {Object} context - Execution context
|
|
311
|
+
* @returns {Promise<OptimizationOpportunity[]>} LLM suggestions
|
|
312
|
+
*/
|
|
313
|
+
async getLLMOptimizations(pending, context) {
|
|
314
|
+
try {
|
|
315
|
+
const prompt = this.buildOptimizationPrompt(pending, context);
|
|
316
|
+
|
|
317
|
+
const response = await Promise.race([
|
|
318
|
+
this.llm.completeJSON(prompt, this.getOptimizationSchema()),
|
|
319
|
+
new Promise((_, reject) =>
|
|
320
|
+
setTimeout(() => reject(new Error('Timeout')), this.config.optimizationTimeout)
|
|
321
|
+
)
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
return this.processLLMResponse(response, pending);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// LLM optimization is optional, don't fail on error
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Build optimization prompt for LLM
|
|
333
|
+
* @param {Object[]} pending - Pending tasks
|
|
334
|
+
* @param {Object} context - Execution context
|
|
335
|
+
* @returns {string} Prompt
|
|
336
|
+
*/
|
|
337
|
+
buildOptimizationPrompt(pending, context) {
|
|
338
|
+
const completedSummary = (context.completed || []).map(t => ({
|
|
339
|
+
name: t.name || t.skill,
|
|
340
|
+
duration: t.duration
|
|
341
|
+
}));
|
|
342
|
+
|
|
343
|
+
const pendingSummary = pending.map(t => ({
|
|
344
|
+
id: t.id,
|
|
345
|
+
name: t.name || t.skill,
|
|
346
|
+
dependencies: t.dependencies || [],
|
|
347
|
+
estimatedDuration: t.estimatedDuration
|
|
348
|
+
}));
|
|
349
|
+
|
|
350
|
+
return `Analyze this execution plan and suggest optimizations:
|
|
351
|
+
|
|
352
|
+
COMPLETED TASKS:
|
|
353
|
+
${JSON.stringify(completedSummary, null, 2)}
|
|
354
|
+
|
|
355
|
+
PENDING TASKS:
|
|
356
|
+
${JSON.stringify(pendingSummary, null, 2)}
|
|
357
|
+
|
|
358
|
+
Consider:
|
|
359
|
+
1. Tasks that can run in parallel (no dependencies between them)
|
|
360
|
+
2. Tasks that could be merged into one
|
|
361
|
+
3. Better ordering based on dependencies
|
|
362
|
+
4. Tasks that could be skipped or simplified
|
|
363
|
+
|
|
364
|
+
Return optimization suggestions with estimated improvement percentages.`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Get JSON schema for LLM response
|
|
369
|
+
* @returns {Object} JSON schema
|
|
370
|
+
*/
|
|
371
|
+
getOptimizationSchema() {
|
|
372
|
+
return {
|
|
373
|
+
type: 'object',
|
|
374
|
+
properties: {
|
|
375
|
+
optimizations: {
|
|
376
|
+
type: 'array',
|
|
377
|
+
items: {
|
|
378
|
+
type: 'object',
|
|
379
|
+
properties: {
|
|
380
|
+
type: { type: 'string', enum: ['parallelize', 'merge', 'reorder', 'skip', 'substitute'] },
|
|
381
|
+
description: { type: 'string' },
|
|
382
|
+
affectedTaskIds: { type: 'array', items: { type: 'string' } },
|
|
383
|
+
estimatedImprovement: { type: 'number' },
|
|
384
|
+
newOrder: { type: 'array', items: { type: 'string' } },
|
|
385
|
+
reasoning: { type: 'string' }
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Process LLM response into optimization opportunities
|
|
395
|
+
* @param {Object} response - LLM response
|
|
396
|
+
* @param {Object[]} pending - Pending tasks
|
|
397
|
+
* @returns {OptimizationOpportunity[]} Processed opportunities
|
|
398
|
+
*/
|
|
399
|
+
processLLMResponse(response, pending) {
|
|
400
|
+
if (!response || !response.optimizations) return [];
|
|
401
|
+
|
|
402
|
+
return response.optimizations.map(opt => {
|
|
403
|
+
const affectedTasks = (opt.affectedTaskIds || [])
|
|
404
|
+
.map(id => pending.find(t => t.id === id))
|
|
405
|
+
.filter(Boolean);
|
|
406
|
+
|
|
407
|
+
let newPath = null;
|
|
408
|
+
if (opt.newOrder) {
|
|
409
|
+
newPath = opt.newOrder
|
|
410
|
+
.map(id => pending.find(t => t.id === id))
|
|
411
|
+
.filter(Boolean);
|
|
412
|
+
|
|
413
|
+
// Add any tasks not in newOrder at the end
|
|
414
|
+
const inOrder = new Set(opt.newOrder);
|
|
415
|
+
pending.forEach(t => {
|
|
416
|
+
if (!inOrder.has(t.id)) {
|
|
417
|
+
newPath.push(t);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return new OptimizationOpportunity({
|
|
423
|
+
type: opt.type,
|
|
424
|
+
description: opt.description,
|
|
425
|
+
affectedTasks,
|
|
426
|
+
estimatedImprovement: opt.estimatedImprovement || 0.1,
|
|
427
|
+
confidence: 0.6, // LLM suggestions get moderate confidence
|
|
428
|
+
newPath,
|
|
429
|
+
reasoning: opt.reasoning
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Rank opportunities by weighted score
|
|
436
|
+
* @param {OptimizationOpportunity[]} opportunities - Opportunities
|
|
437
|
+
* @returns {OptimizationOpportunity[]} Ranked opportunities
|
|
438
|
+
*/
|
|
439
|
+
rankOpportunities(opportunities) {
|
|
440
|
+
return [...opportunities].sort((a, b) =>
|
|
441
|
+
b.getWeightedScore() - a.getWeightedScore()
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Validate an optimization before applying
|
|
447
|
+
* @param {OptimizationOpportunity} optimization - Optimization to validate
|
|
448
|
+
* @param {Object} context - Execution context
|
|
449
|
+
* @returns {Promise<{valid: boolean, reason?: string}>} Validation result
|
|
450
|
+
*/
|
|
451
|
+
async validateOptimization(optimization, context) {
|
|
452
|
+
// Check dependency constraints
|
|
453
|
+
if (optimization.newPath) {
|
|
454
|
+
const valid = this.validateDependencies(optimization.newPath);
|
|
455
|
+
if (!valid) {
|
|
456
|
+
return { valid: false, reason: 'New path violates dependency constraints' };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Check resource constraints
|
|
461
|
+
if (optimization.type === 'parallelize') {
|
|
462
|
+
const canParallelize = this.checkResourceCapacity(
|
|
463
|
+
optimization.affectedTasks.length
|
|
464
|
+
);
|
|
465
|
+
if (!canParallelize) {
|
|
466
|
+
return { valid: false, reason: 'Insufficient resources for parallelization' };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return { valid: true };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Validate dependencies in a path
|
|
475
|
+
* @param {Object[]} path - Task path
|
|
476
|
+
* @returns {boolean} Whether dependencies are satisfied
|
|
477
|
+
*/
|
|
478
|
+
validateDependencies(path) {
|
|
479
|
+
const completed = new Set();
|
|
480
|
+
|
|
481
|
+
for (const task of path) {
|
|
482
|
+
const deps = task.dependencies || [];
|
|
483
|
+
for (const dep of deps) {
|
|
484
|
+
if (!completed.has(dep)) {
|
|
485
|
+
// Check if dependency is in remaining path
|
|
486
|
+
const depIndex = path.findIndex(t => t.id === dep);
|
|
487
|
+
const taskIndex = path.indexOf(task);
|
|
488
|
+
if (depIndex === -1 || depIndex > taskIndex) {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
completed.add(task.id);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Check if resources allow parallelization
|
|
501
|
+
* @param {number} parallelCount - Number of parallel tasks
|
|
502
|
+
* @returns {boolean} Whether resources are available
|
|
503
|
+
*/
|
|
504
|
+
checkResourceCapacity(parallelCount) {
|
|
505
|
+
// Default implementation assumes we can handle up to 4 parallel tasks
|
|
506
|
+
return parallelCount <= 4;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Record performance data for learning
|
|
511
|
+
* @param {Object} task - Completed task
|
|
512
|
+
* @param {Object} result - Task result
|
|
513
|
+
*/
|
|
514
|
+
recordPerformance(task, result) {
|
|
515
|
+
const key = task.skill || task.name;
|
|
516
|
+
|
|
517
|
+
if (!this.learningData.has(key)) {
|
|
518
|
+
this.learningData.set(key, {
|
|
519
|
+
durations: [],
|
|
520
|
+
successRate: 0,
|
|
521
|
+
totalCount: 0
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const data = this.learningData.get(key);
|
|
526
|
+
data.durations.push(result.duration || 0);
|
|
527
|
+
data.totalCount++;
|
|
528
|
+
data.successRate = (data.successRate * (data.totalCount - 1) + 1) / data.totalCount;
|
|
529
|
+
|
|
530
|
+
// Keep only last 50 durations
|
|
531
|
+
if (data.durations.length > 50) {
|
|
532
|
+
data.durations.shift();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Get historical duration for a task type
|
|
538
|
+
* @param {Object} task - Task
|
|
539
|
+
* @returns {number|null} Historical average duration or null
|
|
540
|
+
*/
|
|
541
|
+
getHistoricalDuration(task) {
|
|
542
|
+
const key = task.skill || task.name;
|
|
543
|
+
const data = this.learningData.get(key);
|
|
544
|
+
|
|
545
|
+
if (!data || data.durations.length === 0) return null;
|
|
546
|
+
|
|
547
|
+
return data.durations.reduce((a, b) => a + b, 0) / data.durations.length;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Count parallelizable tasks
|
|
552
|
+
* @param {Object[]} tasks - Tasks
|
|
553
|
+
* @returns {number} Count of parallelizable tasks
|
|
554
|
+
*/
|
|
555
|
+
countParallelizable(tasks) {
|
|
556
|
+
let count = 0;
|
|
557
|
+
const completed = new Set();
|
|
558
|
+
|
|
559
|
+
for (const task of tasks) {
|
|
560
|
+
const deps = task.dependencies || [];
|
|
561
|
+
if (deps.length === 0 || deps.every(d => completed.has(d))) {
|
|
562
|
+
count++;
|
|
563
|
+
}
|
|
564
|
+
completed.add(task.id);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return count;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Calculate risk score for pending tasks
|
|
572
|
+
* @param {Object[]} pending - Pending tasks
|
|
573
|
+
* @param {Object} context - Execution context
|
|
574
|
+
* @returns {number} Risk score (0-1)
|
|
575
|
+
*/
|
|
576
|
+
calculateRiskScore(pending, context) {
|
|
577
|
+
if (pending.length === 0) return 0;
|
|
578
|
+
|
|
579
|
+
let riskSum = 0;
|
|
580
|
+
|
|
581
|
+
for (const task of pending) {
|
|
582
|
+
const key = task.skill || task.name;
|
|
583
|
+
const data = this.learningData.get(key);
|
|
584
|
+
|
|
585
|
+
if (data) {
|
|
586
|
+
// Use failure rate as risk indicator
|
|
587
|
+
riskSum += (1 - data.successRate);
|
|
588
|
+
} else {
|
|
589
|
+
// Unknown tasks get moderate risk
|
|
590
|
+
riskSum += 0.3;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return riskSum / pending.length;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Calculate dependency complexity
|
|
599
|
+
* @param {Object[]} tasks - Tasks
|
|
600
|
+
* @returns {number} Complexity score (0-1)
|
|
601
|
+
*/
|
|
602
|
+
calculateDependencyComplexity(tasks) {
|
|
603
|
+
if (tasks.length === 0) return 0;
|
|
604
|
+
|
|
605
|
+
let totalDeps = 0;
|
|
606
|
+
let maxDeps = 0;
|
|
607
|
+
|
|
608
|
+
for (const task of tasks) {
|
|
609
|
+
const deps = (task.dependencies || []).length;
|
|
610
|
+
totalDeps += deps;
|
|
611
|
+
maxDeps = Math.max(maxDeps, deps);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Normalize: average deps / max possible deps
|
|
615
|
+
const avgDeps = totalDeps / tasks.length;
|
|
616
|
+
const normalized = Math.min(avgDeps / 5, 1); // Assume 5+ deps is max complexity
|
|
617
|
+
|
|
618
|
+
return normalized;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Record optimization for history
|
|
623
|
+
* @param {OptimizationOpportunity} optimization - Applied optimization
|
|
624
|
+
* @param {Object} context - Execution context
|
|
625
|
+
*/
|
|
626
|
+
recordOptimization(optimization, context) {
|
|
627
|
+
this.optimizationHistory.push({
|
|
628
|
+
timestamp: Date.now(),
|
|
629
|
+
type: optimization.type,
|
|
630
|
+
improvement: optimization.estimatedImprovement,
|
|
631
|
+
tasksAffected: optimization.affectedTasks.length,
|
|
632
|
+
contextSnapshot: {
|
|
633
|
+
completedCount: context.completed?.length || 0,
|
|
634
|
+
pendingCount: context.pending?.length || 0
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Limit history size
|
|
639
|
+
while (this.optimizationHistory.length > this.config.maxHistorySize) {
|
|
640
|
+
this.optimizationHistory.shift();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Get optimization statistics
|
|
646
|
+
* @returns {Object} Statistics
|
|
647
|
+
*/
|
|
648
|
+
getStatistics() {
|
|
649
|
+
const history = this.optimizationHistory;
|
|
650
|
+
|
|
651
|
+
if (history.length === 0) {
|
|
652
|
+
return {
|
|
653
|
+
totalOptimizations: 0,
|
|
654
|
+
averageImprovement: 0,
|
|
655
|
+
byType: {}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const byType = {};
|
|
660
|
+
let totalImprovement = 0;
|
|
661
|
+
|
|
662
|
+
for (const opt of history) {
|
|
663
|
+
byType[opt.type] = (byType[opt.type] || 0) + 1;
|
|
664
|
+
totalImprovement += opt.improvement;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
return {
|
|
668
|
+
totalOptimizations: history.length,
|
|
669
|
+
averageImprovement: totalImprovement / history.length,
|
|
670
|
+
byType,
|
|
671
|
+
learningDataSize: this.learningData.size
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Reset optimizer state
|
|
677
|
+
*/
|
|
678
|
+
reset() {
|
|
679
|
+
this.successCount = 0;
|
|
680
|
+
this.currentMetrics = null;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Clear all learning data
|
|
685
|
+
*/
|
|
686
|
+
clearLearningData() {
|
|
687
|
+
this.learningData.clear();
|
|
688
|
+
this.optimizationHistory = [];
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Parallelization Analyzer
|
|
694
|
+
* Finds opportunities to run tasks in parallel
|
|
695
|
+
*/
|
|
696
|
+
class ParallelizationAnalyzer {
|
|
697
|
+
/**
|
|
698
|
+
* Analyze tasks for parallelization opportunities
|
|
699
|
+
* @param {Object[]} tasks - Tasks
|
|
700
|
+
* @param {Object} context - Context
|
|
701
|
+
* @returns {OptimizationOpportunity[]} Opportunities
|
|
702
|
+
*/
|
|
703
|
+
analyze(tasks, context) {
|
|
704
|
+
const opportunities = [];
|
|
705
|
+
const groups = this.findParallelGroups(tasks);
|
|
706
|
+
|
|
707
|
+
for (const group of groups) {
|
|
708
|
+
if (group.length >= 2) {
|
|
709
|
+
const sequentialTime = group.reduce((sum, t) =>
|
|
710
|
+
sum + (t.estimatedDuration || 30000), 0
|
|
711
|
+
);
|
|
712
|
+
const parallelTime = Math.max(...group.map(t =>
|
|
713
|
+
t.estimatedDuration || 30000
|
|
714
|
+
));
|
|
715
|
+
const improvement = (sequentialTime - parallelTime) / sequentialTime;
|
|
716
|
+
|
|
717
|
+
if (improvement > 0.1) {
|
|
718
|
+
opportunities.push(new OptimizationOpportunity({
|
|
719
|
+
type: 'parallelize',
|
|
720
|
+
description: `Run ${group.length} tasks in parallel`,
|
|
721
|
+
affectedTasks: group,
|
|
722
|
+
estimatedImprovement: improvement,
|
|
723
|
+
confidence: 0.8,
|
|
724
|
+
reasoning: 'Tasks have no interdependencies'
|
|
725
|
+
}));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return opportunities;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Find groups of tasks that can run in parallel
|
|
735
|
+
* @param {Object[]} tasks - Tasks
|
|
736
|
+
* @returns {Object[][]} Groups
|
|
737
|
+
*/
|
|
738
|
+
findParallelGroups(tasks) {
|
|
739
|
+
const groups = [];
|
|
740
|
+
const used = new Set();
|
|
741
|
+
const completed = new Set();
|
|
742
|
+
|
|
743
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
744
|
+
if (used.has(i)) continue;
|
|
745
|
+
|
|
746
|
+
const task = tasks[i];
|
|
747
|
+
const deps = task.dependencies || [];
|
|
748
|
+
|
|
749
|
+
// Check if dependencies are satisfied
|
|
750
|
+
if (!deps.every(d => completed.has(d))) continue;
|
|
751
|
+
|
|
752
|
+
// Find other tasks that can run in parallel
|
|
753
|
+
const group = [task];
|
|
754
|
+
used.add(i);
|
|
755
|
+
|
|
756
|
+
for (let j = i + 1; j < tasks.length; j++) {
|
|
757
|
+
if (used.has(j)) continue;
|
|
758
|
+
|
|
759
|
+
const other = tasks[j];
|
|
760
|
+
const otherDeps = other.dependencies || [];
|
|
761
|
+
|
|
762
|
+
// Can run in parallel if:
|
|
763
|
+
// 1. Its dependencies are satisfied
|
|
764
|
+
// 2. It doesn't depend on any task in the current group
|
|
765
|
+
if (otherDeps.every(d => completed.has(d)) &&
|
|
766
|
+
!otherDeps.some(d => group.some(g => g.id === d))) {
|
|
767
|
+
group.push(other);
|
|
768
|
+
used.add(j);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
groups.push(group);
|
|
773
|
+
group.forEach(t => completed.add(t.id));
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return groups;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Merging Analyzer
|
|
782
|
+
* Finds opportunities to merge similar tasks
|
|
783
|
+
*/
|
|
784
|
+
class MergingAnalyzer {
|
|
785
|
+
/**
|
|
786
|
+
* Analyze tasks for merging opportunities
|
|
787
|
+
* @param {Object[]} tasks - Tasks
|
|
788
|
+
* @param {Object} context - Context
|
|
789
|
+
* @returns {OptimizationOpportunity[]} Opportunities
|
|
790
|
+
*/
|
|
791
|
+
analyze(tasks, context) {
|
|
792
|
+
const opportunities = [];
|
|
793
|
+
const groups = this.findMergeableGroups(tasks);
|
|
794
|
+
|
|
795
|
+
for (const group of groups) {
|
|
796
|
+
if (group.length >= 2) {
|
|
797
|
+
const overhead = 5000; // Assumed overhead per task
|
|
798
|
+
const savingsTime = overhead * (group.length - 1);
|
|
799
|
+
const totalTime = group.reduce((sum, t) =>
|
|
800
|
+
sum + (t.estimatedDuration || 30000), 0
|
|
801
|
+
);
|
|
802
|
+
const improvement = savingsTime / totalTime;
|
|
803
|
+
|
|
804
|
+
if (improvement > 0.05) {
|
|
805
|
+
opportunities.push(new OptimizationOpportunity({
|
|
806
|
+
type: 'merge',
|
|
807
|
+
description: `Merge ${group.length} similar ${group[0].skill || group[0].name} tasks`,
|
|
808
|
+
affectedTasks: group,
|
|
809
|
+
estimatedImprovement: improvement,
|
|
810
|
+
confidence: 0.7,
|
|
811
|
+
reasoning: 'Tasks operate on similar targets'
|
|
812
|
+
}));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return opportunities;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Find groups of tasks that could be merged
|
|
822
|
+
* @param {Object[]} tasks - Tasks
|
|
823
|
+
* @returns {Object[][]} Mergeable groups
|
|
824
|
+
*/
|
|
825
|
+
findMergeableGroups(tasks) {
|
|
826
|
+
const bySkill = new Map();
|
|
827
|
+
|
|
828
|
+
for (const task of tasks) {
|
|
829
|
+
const skill = task.skill || task.name;
|
|
830
|
+
if (!bySkill.has(skill)) {
|
|
831
|
+
bySkill.set(skill, []);
|
|
832
|
+
}
|
|
833
|
+
bySkill.get(skill).push(task);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Return groups with 2+ tasks of the same skill
|
|
837
|
+
return Array.from(bySkill.values()).filter(g => g.length >= 2);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Reordering Analyzer
|
|
843
|
+
* Finds better task orderings based on dependencies
|
|
844
|
+
*/
|
|
845
|
+
class ReorderingAnalyzer {
|
|
846
|
+
/**
|
|
847
|
+
* Analyze tasks for reordering opportunities
|
|
848
|
+
* @param {Object[]} tasks - Tasks
|
|
849
|
+
* @param {Object} context - Context
|
|
850
|
+
* @returns {OptimizationOpportunity[]} Opportunities
|
|
851
|
+
*/
|
|
852
|
+
analyze(tasks, context) {
|
|
853
|
+
const opportunities = [];
|
|
854
|
+
|
|
855
|
+
// Check for dependency-based improvements
|
|
856
|
+
const optimalOrder = this.topologicalSort(tasks);
|
|
857
|
+
|
|
858
|
+
if (!this.arraysEqual(tasks, optimalOrder)) {
|
|
859
|
+
const improvement = this.estimateReorderImprovement(tasks, optimalOrder);
|
|
860
|
+
|
|
861
|
+
if (improvement > 0.05) {
|
|
862
|
+
opportunities.push(new OptimizationOpportunity({
|
|
863
|
+
type: 'reorder',
|
|
864
|
+
description: 'Optimize task order for better dependency resolution',
|
|
865
|
+
affectedTasks: tasks,
|
|
866
|
+
estimatedImprovement: improvement,
|
|
867
|
+
confidence: 0.9,
|
|
868
|
+
newPath: optimalOrder,
|
|
869
|
+
reasoning: 'Current order causes unnecessary waiting'
|
|
870
|
+
}));
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return opportunities;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Topological sort of tasks based on dependencies
|
|
879
|
+
* @param {Object[]} tasks - Tasks
|
|
880
|
+
* @returns {Object[]} Sorted tasks
|
|
881
|
+
*/
|
|
882
|
+
topologicalSort(tasks) {
|
|
883
|
+
const taskMap = new Map(tasks.map(t => [t.id, t]));
|
|
884
|
+
const inDegree = new Map();
|
|
885
|
+
const result = [];
|
|
886
|
+
|
|
887
|
+
// Initialize in-degrees
|
|
888
|
+
for (const task of tasks) {
|
|
889
|
+
inDegree.set(task.id, 0);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Calculate in-degrees
|
|
893
|
+
for (const task of tasks) {
|
|
894
|
+
for (const dep of (task.dependencies || [])) {
|
|
895
|
+
if (taskMap.has(dep)) {
|
|
896
|
+
inDegree.set(task.id, inDegree.get(task.id) + 1);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Queue tasks with no dependencies
|
|
902
|
+
const queue = tasks.filter(t => inDegree.get(t.id) === 0);
|
|
903
|
+
|
|
904
|
+
while (queue.length > 0) {
|
|
905
|
+
const task = queue.shift();
|
|
906
|
+
result.push(task);
|
|
907
|
+
|
|
908
|
+
// Reduce in-degree for dependent tasks
|
|
909
|
+
for (const other of tasks) {
|
|
910
|
+
if ((other.dependencies || []).includes(task.id)) {
|
|
911
|
+
const newDegree = inDegree.get(other.id) - 1;
|
|
912
|
+
inDegree.set(other.id, newDegree);
|
|
913
|
+
if (newDegree === 0) {
|
|
914
|
+
queue.push(other);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Add remaining tasks (circular dependencies)
|
|
921
|
+
for (const task of tasks) {
|
|
922
|
+
if (!result.includes(task)) {
|
|
923
|
+
result.push(task);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
return result;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Check if two arrays have same elements in same order
|
|
932
|
+
* @param {Object[]} a - First array
|
|
933
|
+
* @param {Object[]} b - Second array
|
|
934
|
+
* @returns {boolean} Whether equal
|
|
935
|
+
*/
|
|
936
|
+
arraysEqual(a, b) {
|
|
937
|
+
if (a.length !== b.length) return false;
|
|
938
|
+
for (let i = 0; i < a.length; i++) {
|
|
939
|
+
if (a[i].id !== b[i].id) return false;
|
|
940
|
+
}
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Estimate improvement from reordering
|
|
946
|
+
* @param {Object[]} current - Current order
|
|
947
|
+
* @param {Object[]} optimal - Optimal order
|
|
948
|
+
* @returns {number} Estimated improvement (0-1)
|
|
949
|
+
*/
|
|
950
|
+
estimateReorderImprovement(current, optimal) {
|
|
951
|
+
// Simple heuristic: count how many tasks are out of place
|
|
952
|
+
let outOfPlace = 0;
|
|
953
|
+
for (let i = 0; i < current.length; i++) {
|
|
954
|
+
if (current[i].id !== optimal[i].id) {
|
|
955
|
+
outOfPlace++;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// More out of place = more potential improvement
|
|
960
|
+
return (outOfPlace / current.length) * 0.3;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
module.exports = {
|
|
965
|
+
ProactivePathOptimizer,
|
|
966
|
+
PathMetrics,
|
|
967
|
+
OptimizationOpportunity,
|
|
968
|
+
ParallelizationAnalyzer,
|
|
969
|
+
MergingAnalyzer,
|
|
970
|
+
ReorderingAnalyzer,
|
|
971
|
+
DEFAULT_CONFIG
|
|
972
|
+
};
|