swarm-engine 1.38.0 → 1.43.0

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 (127) hide show
  1. package/dist/core/patterns.d.ts +7 -1
  2. package/dist/core/patterns.d.ts.map +1 -1
  3. package/dist/core/patterns.js +23 -0
  4. package/dist/core/patterns.js.map +1 -1
  5. package/dist/core/types.d.ts +11 -0
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/core/types.js.map +1 -1
  8. package/dist/index.d.ts +26 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +18 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/memory/index.d.ts.map +1 -1
  13. package/dist/memory/index.js +3 -9
  14. package/dist/memory/index.js.map +1 -1
  15. package/dist/runtime/acon.d.ts +61 -0
  16. package/dist/runtime/acon.d.ts.map +1 -0
  17. package/dist/runtime/acon.js +266 -0
  18. package/dist/runtime/acon.js.map +1 -0
  19. package/dist/runtime/adaptive.d.ts +1 -0
  20. package/dist/runtime/adaptive.d.ts.map +1 -1
  21. package/dist/runtime/adaptive.js +18 -4
  22. package/dist/runtime/adaptive.js.map +1 -1
  23. package/dist/runtime/agent-runner.d.ts +8 -0
  24. package/dist/runtime/agent-runner.d.ts.map +1 -1
  25. package/dist/runtime/agent-runner.js +79 -4
  26. package/dist/runtime/agent-runner.js.map +1 -1
  27. package/dist/runtime/backends/claude-session.d.ts +49 -0
  28. package/dist/runtime/backends/claude-session.d.ts.map +1 -0
  29. package/dist/runtime/backends/claude-session.js +237 -0
  30. package/dist/runtime/backends/claude-session.js.map +1 -0
  31. package/dist/runtime/backends/claude.d.ts.map +1 -1
  32. package/dist/runtime/backends/claude.js +90 -4
  33. package/dist/runtime/backends/claude.js.map +1 -1
  34. package/dist/runtime/backends/types.d.ts +31 -1
  35. package/dist/runtime/backends/types.d.ts.map +1 -1
  36. package/dist/runtime/benefits.d.ts +170 -0
  37. package/dist/runtime/benefits.d.ts.map +1 -0
  38. package/dist/runtime/benefits.js +588 -0
  39. package/dist/runtime/benefits.js.map +1 -0
  40. package/dist/runtime/compaction.d.ts +7 -1
  41. package/dist/runtime/compaction.d.ts.map +1 -1
  42. package/dist/runtime/compaction.js +12 -2
  43. package/dist/runtime/compaction.js.map +1 -1
  44. package/dist/runtime/compactor.d.ts +90 -0
  45. package/dist/runtime/compactor.d.ts.map +1 -0
  46. package/dist/runtime/compactor.js +418 -0
  47. package/dist/runtime/compactor.js.map +1 -0
  48. package/dist/runtime/context-decay.d.ts +45 -0
  49. package/dist/runtime/context-decay.d.ts.map +1 -0
  50. package/dist/runtime/context-decay.js +149 -0
  51. package/dist/runtime/context-decay.js.map +1 -0
  52. package/dist/runtime/distiller.d.ts +1 -0
  53. package/dist/runtime/distiller.d.ts.map +1 -1
  54. package/dist/runtime/distiller.js +8 -2
  55. package/dist/runtime/distiller.js.map +1 -1
  56. package/dist/runtime/engine.d.ts +44 -15
  57. package/dist/runtime/engine.d.ts.map +1 -1
  58. package/dist/runtime/engine.js +406 -68
  59. package/dist/runtime/engine.js.map +1 -1
  60. package/dist/runtime/execution-graph.d.ts.map +1 -1
  61. package/dist/runtime/execution-graph.js.map +1 -1
  62. package/dist/runtime/executor.d.ts +7 -1
  63. package/dist/runtime/executor.d.ts.map +1 -1
  64. package/dist/runtime/executor.js +20 -0
  65. package/dist/runtime/executor.js.map +1 -1
  66. package/dist/runtime/graph-adversarial.d.ts.map +1 -1
  67. package/dist/runtime/graph-adversarial.js.map +1 -1
  68. package/dist/runtime/graph-analyzer.d.ts.map +1 -1
  69. package/dist/runtime/graph-analyzer.js.map +1 -1
  70. package/dist/runtime/graph-discovery.d.ts.map +1 -1
  71. package/dist/runtime/graph-discovery.js +1 -4
  72. package/dist/runtime/graph-discovery.js.map +1 -1
  73. package/dist/runtime/graph-dropout.d.ts.map +1 -1
  74. package/dist/runtime/graph-dropout.js +10 -2
  75. package/dist/runtime/graph-dropout.js.map +1 -1
  76. package/dist/runtime/graph-embeddings.d.ts.map +1 -1
  77. package/dist/runtime/graph-embeddings.js +1 -3
  78. package/dist/runtime/graph-embeddings.js.map +1 -1
  79. package/dist/runtime/graph-feedback.d.ts.map +1 -1
  80. package/dist/runtime/graph-feedback.js +1 -3
  81. package/dist/runtime/graph-feedback.js.map +1 -1
  82. package/dist/runtime/graph-gnn.d.ts.map +1 -1
  83. package/dist/runtime/graph-gnn.js.map +1 -1
  84. package/dist/runtime/graph-meta-adversarial.d.ts.map +1 -1
  85. package/dist/runtime/graph-meta-adversarial.js.map +1 -1
  86. package/dist/runtime/graph-meta.d.ts.map +1 -1
  87. package/dist/runtime/graph-meta.js +48 -20
  88. package/dist/runtime/graph-meta.js.map +1 -1
  89. package/dist/runtime/graph-self-evolve.d.ts.map +1 -1
  90. package/dist/runtime/graph-self-evolve.js.map +1 -1
  91. package/dist/runtime/graph-synthesis.d.ts.map +1 -1
  92. package/dist/runtime/graph-synthesis.js.map +1 -1
  93. package/dist/runtime/graph-trajectory.d.ts.map +1 -1
  94. package/dist/runtime/graph-trajectory.js +3 -8
  95. package/dist/runtime/graph-trajectory.js.map +1 -1
  96. package/dist/runtime/learning-engine.d.ts.map +1 -1
  97. package/dist/runtime/learning-engine.js +10 -4
  98. package/dist/runtime/learning-engine.js.map +1 -1
  99. package/dist/runtime/output-schemas.d.ts +21 -0
  100. package/dist/runtime/output-schemas.d.ts.map +1 -0
  101. package/dist/runtime/output-schemas.js +252 -0
  102. package/dist/runtime/output-schemas.js.map +1 -0
  103. package/dist/runtime/prompt-compressor.d.ts +16 -0
  104. package/dist/runtime/prompt-compressor.d.ts.map +1 -0
  105. package/dist/runtime/prompt-compressor.js +68 -0
  106. package/dist/runtime/prompt-compressor.js.map +1 -0
  107. package/dist/runtime/repo-map.d.ts +40 -0
  108. package/dist/runtime/repo-map.d.ts.map +1 -0
  109. package/dist/runtime/repo-map.js +358 -0
  110. package/dist/runtime/repo-map.js.map +1 -0
  111. package/dist/runtime/sdk-mcp-server.d.ts +44 -0
  112. package/dist/runtime/sdk-mcp-server.d.ts.map +1 -0
  113. package/dist/runtime/sdk-mcp-server.js +133 -0
  114. package/dist/runtime/sdk-mcp-server.js.map +1 -0
  115. package/dist/runtime/structured-handoff.d.ts +41 -0
  116. package/dist/runtime/structured-handoff.d.ts.map +1 -0
  117. package/dist/runtime/structured-handoff.js +279 -0
  118. package/dist/runtime/structured-handoff.js.map +1 -0
  119. package/dist/runtime/token-analytics.d.ts +38 -0
  120. package/dist/runtime/token-analytics.d.ts.map +1 -0
  121. package/dist/runtime/token-analytics.js +59 -0
  122. package/dist/runtime/token-analytics.js.map +1 -0
  123. package/dist/runtime/verifier.d.ts +10 -0
  124. package/dist/runtime/verifier.d.ts.map +1 -1
  125. package/dist/runtime/verifier.js +97 -1
  126. package/dist/runtime/verifier.js.map +1 -1
  127. package/package.json +1 -1
@@ -23,6 +23,9 @@ import { estimateCacheSavings } from './cache-optimizer.js';
23
23
  import { createLivingSpec, updateSpecFromPhase } from './living-spec.js';
24
24
  import { estimateTokens } from '../utils/tokens.js';
25
25
  import { unlinkSync } from 'fs';
26
+ import { execFile } from 'child_process';
27
+ import { promisify } from 'util';
28
+ const execFileAsync = promisify(execFile);
26
29
  import { AgentRunner } from './agent-runner.js';
27
30
  import { LearningEngine } from './learning-engine.js';
28
31
  import { ExecutionGraph } from './execution-graph.js';
@@ -43,6 +46,10 @@ import { MetaAdversarialTester } from './graph-meta-adversarial.js';
43
46
  import { RuleEvolver } from './graph-self-evolve.js';
44
47
  import { TaskDiscovery } from './graph-discovery.js';
45
48
  import { OrchestrationEmbedder } from './graph-embeddings.js';
49
+ import { HandoffParser } from './structured-handoff.js';
50
+ import { classifyTaskComplexity } from './cascade.js';
51
+ import { SwarmMcpServer } from './sdk-mcp-server.js';
52
+ import { BenefitsCollector } from './benefits.js';
46
53
  /**
47
54
  * The Swarm Orchestration Engine.
48
55
  *
@@ -81,19 +88,135 @@ export class SwarmEngine {
81
88
  executionGraph = null;
82
89
  graphAnalyzer = null;
83
90
  graphLearner = null;
84
- reviewFeedback = null;
85
- causalEngine = null;
86
- gnnPredictor = null;
87
- adversarialEvolver = null;
88
- metaSelector = null;
89
- predictiveDropout = null;
90
- gnnWeights = null;
91
- patternSynthesizer = null;
92
- trajectoryPredictor = null;
93
- metaAdversarial = null;
94
- ruleEvolver = null;
95
- taskDiscovery = null;
96
- orchestrationEmbedder = null;
91
+ // Lazy-initialized ML modules (only constructed on first access)
92
+ _reviewFeedback;
93
+ _causalEngine;
94
+ _gnnPredictor;
95
+ _adversarialEvolver;
96
+ _metaSelector;
97
+ _predictiveDropout;
98
+ _gnnWeights;
99
+ _patternSynthesizer;
100
+ _trajectoryPredictor;
101
+ _metaAdversarial;
102
+ _ruleEvolver;
103
+ _taskDiscovery;
104
+ _orchestrationEmbedder;
105
+ swarmMcpServer = null;
106
+ decisions = new Map();
107
+ benefitsCollector = new BenefitsCollector();
108
+ get reviewFeedback() {
109
+ if (this._reviewFeedback === undefined) {
110
+ this._reviewFeedback = this.executionGraph
111
+ ? this.tryInit(() => new ReviewFeedbackRecorder(this.executionGraph), 'ReviewFeedbackRecorder')
112
+ : null;
113
+ }
114
+ return this._reviewFeedback;
115
+ }
116
+ get causalEngine() {
117
+ if (this._causalEngine === undefined) {
118
+ this._causalEngine = this.executionGraph
119
+ ? this.tryInit(() => new CausalGraphEngine(this.executionGraph), 'CausalGraphEngine')
120
+ : null;
121
+ }
122
+ return this._causalEngine;
123
+ }
124
+ get gnnPredictor() {
125
+ if (this._gnnPredictor === undefined) {
126
+ this._gnnPredictor = this.executionGraph
127
+ ? this.tryInit(() => new FailurePropagationPredictor(this.executionGraph), 'FailurePropagationPredictor')
128
+ : null;
129
+ }
130
+ return this._gnnPredictor;
131
+ }
132
+ get adversarialEvolver() {
133
+ if (this._adversarialEvolver === undefined) {
134
+ this._adversarialEvolver = this.executionGraph
135
+ ? this.tryInit(() => new AdversarialEvolver(this.executionGraph), 'AdversarialEvolver')
136
+ : null;
137
+ }
138
+ return this._adversarialEvolver;
139
+ }
140
+ get metaSelector() {
141
+ if (this._metaSelector === undefined) {
142
+ this._metaSelector = this.executionGraph
143
+ ? this.tryInit(() => new MetaPatternSelector(this.executionGraph), 'MetaPatternSelector')
144
+ : null;
145
+ }
146
+ return this._metaSelector;
147
+ }
148
+ get predictiveDropout() {
149
+ if (this._predictiveDropout === undefined) {
150
+ this._predictiveDropout = this.executionGraph
151
+ ? this.tryInit(() => new PredictiveDropout(this.executionGraph), 'PredictiveDropout')
152
+ : null;
153
+ }
154
+ return this._predictiveDropout;
155
+ }
156
+ get gnnWeights() {
157
+ if (this._gnnWeights === undefined) {
158
+ if (this.gnnPredictor) {
159
+ try {
160
+ this._gnnWeights = this.gnnPredictor.initWeights();
161
+ }
162
+ catch {
163
+ this._gnnWeights = null;
164
+ }
165
+ }
166
+ else {
167
+ this._gnnWeights = null;
168
+ }
169
+ }
170
+ return this._gnnWeights;
171
+ }
172
+ get patternSynthesizer() {
173
+ if (this._patternSynthesizer === undefined) {
174
+ this._patternSynthesizer = this.executionGraph
175
+ ? this.tryInit(() => new PatternSynthesizer(this.executionGraph), 'PatternSynthesizer')
176
+ : null;
177
+ }
178
+ return this._patternSynthesizer;
179
+ }
180
+ get trajectoryPredictor() {
181
+ if (this._trajectoryPredictor === undefined) {
182
+ this._trajectoryPredictor = this.executionGraph
183
+ ? this.tryInit(() => new TrajectoryPredictor(this.executionGraph), 'TrajectoryPredictor')
184
+ : null;
185
+ }
186
+ return this._trajectoryPredictor;
187
+ }
188
+ get metaAdversarial() {
189
+ if (this._metaAdversarial === undefined) {
190
+ this._metaAdversarial = this.executionGraph
191
+ ? this.tryInit(() => new MetaAdversarialTester(this.executionGraph), 'MetaAdversarialTester')
192
+ : null;
193
+ }
194
+ return this._metaAdversarial;
195
+ }
196
+ get ruleEvolver() {
197
+ if (this._ruleEvolver === undefined) {
198
+ this._ruleEvolver = this.executionGraph
199
+ ? this.tryInit(() => new RuleEvolver(this.executionGraph), 'RuleEvolver')
200
+ : null;
201
+ }
202
+ return this._ruleEvolver;
203
+ }
204
+ get taskDiscovery() {
205
+ if (this._taskDiscovery === undefined) {
206
+ this._taskDiscovery = this.executionGraph
207
+ ? this.tryInit(() => new TaskDiscovery(this.executionGraph), 'TaskDiscovery')
208
+ : null;
209
+ }
210
+ return this._taskDiscovery;
211
+ }
212
+ get orchestrationEmbedder() {
213
+ if (this._orchestrationEmbedder === undefined) {
214
+ this._orchestrationEmbedder = this.executionGraph
215
+ ? this.tryInit(() => new OrchestrationEmbedder(this.executionGraph), 'OrchestrationEmbedder')
216
+ : null;
217
+ }
218
+ return this._orchestrationEmbedder;
219
+ }
97
220
  constructor(options) {
98
221
  this.options = options;
99
222
  this.registry = options.registry;
@@ -127,46 +250,36 @@ export class SwarmEngine {
127
250
  this.compounder = this.tryInit(() => new KnowledgeCompounder(), 'KnowledgeCompounder');
128
251
  this.executionGraph = this.tryInit(() => new ExecutionGraph(), 'ExecutionGraph');
129
252
  this.executionGraph?.attachToEventBus(this.bus);
253
+ // Create SwarmMcpServer for SDK tool injection
254
+ try {
255
+ this.swarmMcpServer = new SwarmMcpServer({
256
+ phaseOutputs: this.phaseOutputs,
257
+ graph: this.executionGraph,
258
+ decisions: this.decisions,
259
+ });
260
+ }
261
+ catch {
262
+ this.swarmMcpServer = null;
263
+ }
130
264
  // Create graph analysis deps (reused by AgentRunner + LearningEngine)
131
- this.graphLearner = this.executionGraph ? this.tryInit(() => new GraphLearner(this.executionGraph), 'GraphLearner') : null;
132
- this.graphAnalyzer = this.executionGraph ? this.tryInit(() => new GraphAnalyzer(this.executionGraph), 'GraphAnalyzer') : null;
133
- this.reviewFeedback = this.executionGraph ? this.tryInit(() => new ReviewFeedbackRecorder(this.executionGraph), 'ReviewFeedbackRecorder') : null;
134
- // Cutting-edge ML features
135
- this.causalEngine = this.executionGraph
136
- ? this.tryInit(() => new CausalGraphEngine(this.executionGraph), 'CausalGraphEngine')
137
- : null;
138
- this.gnnPredictor = this.executionGraph
139
- ? this.tryInit(() => new FailurePropagationPredictor(this.executionGraph), 'FailurePropagationPredictor')
140
- : null;
141
- this.adversarialEvolver = this.executionGraph
142
- ? this.tryInit(() => new AdversarialEvolver(this.executionGraph), 'AdversarialEvolver')
265
+ this.graphLearner = this.executionGraph
266
+ ? this.tryInit(() => new GraphLearner(this.executionGraph), 'GraphLearner')
143
267
  : null;
144
- this.metaSelector = this.executionGraph
145
- ? this.tryInit(() => new MetaPatternSelector(this.executionGraph), 'MetaPatternSelector')
268
+ this.graphAnalyzer = this.executionGraph
269
+ ? this.tryInit(() => new GraphAnalyzer(this.executionGraph), 'GraphAnalyzer')
146
270
  : null;
147
- this.predictiveDropout = this.executionGraph
148
- ? this.tryInit(() => new PredictiveDropout(this.executionGraph), 'PredictiveDropout')
149
- : null;
150
- // Initialize GNN weights (training happens lazily via background scheduling)
151
- if (this.gnnPredictor) {
152
- try {
153
- this.gnnWeights = this.gnnPredictor.initWeights();
154
- }
155
- catch { /* silent */ }
156
- }
157
- // Self-aware engine features
158
- this.patternSynthesizer = this.executionGraph ? this.tryInit(() => new PatternSynthesizer(this.executionGraph), 'PatternSynthesizer') : null;
159
- this.trajectoryPredictor = this.executionGraph ? this.tryInit(() => new TrajectoryPredictor(this.executionGraph), 'TrajectoryPredictor') : null;
160
- this.metaAdversarial = this.executionGraph ? this.tryInit(() => new MetaAdversarialTester(this.executionGraph), 'MetaAdversarialTester') : null;
161
- this.ruleEvolver = this.executionGraph ? this.tryInit(() => new RuleEvolver(this.executionGraph), 'RuleEvolver') : null;
162
- this.taskDiscovery = this.executionGraph ? this.tryInit(() => new TaskDiscovery(this.executionGraph), 'TaskDiscovery') : null;
163
- this.orchestrationEmbedder = this.executionGraph ? this.tryInit(() => new OrchestrationEmbedder(this.executionGraph), 'OrchestrationEmbedder') : null;
271
+ // ML modules (reviewFeedback, causalEngine, gnnPredictor, adversarialEvolver,
272
+ // metaSelector, predictiveDropout, gnnWeights, patternSynthesizer, trajectoryPredictor,
273
+ // metaAdversarial, ruleEvolver, taskDiscovery, orchestrationEmbedder) are lazy-initialized
274
+ // via property getters they are only constructed on first access.
164
275
  // Prune stale graph data on init (Feature 6: graph decay)
165
276
  if (this.executionGraph) {
166
277
  try {
167
278
  this.executionGraph.pruneOlderThan(90);
168
279
  }
169
- catch { /* silent */ }
280
+ catch {
281
+ /* silent */
282
+ }
170
283
  }
171
284
  // Rule evolution proposal on startup (skip in mock mode and when no historical data)
172
285
  if (this.ruleEvolver && !this.options.mock) {
@@ -219,10 +332,35 @@ export class SwarmEngine {
219
332
  /**
220
333
  * Execute an orchestration from config.
221
334
  */
222
- async execute(config) {
335
+ async execute(config, executeOptions) {
223
336
  // Re-use existing instance if present (e.g., when resuming from checkpoint)
224
337
  const orchestration = this.orchestrations.get(config.id) ?? this.createInstance(config);
225
338
  this.orchestrations.set(config.id, orchestration);
339
+ // Dry run mode: execute only the first phase in 'plan' mode, then return
340
+ if (executeOptions?.dryRun) {
341
+ this.log.info('Dry run mode: running first phase in plan mode');
342
+ const firstPhase = orchestration.phases[0];
343
+ if (firstPhase) {
344
+ this.executor.setPermissionMode('plan');
345
+ try {
346
+ this.phaseOutputs.clear();
347
+ orchestration.status = 'running';
348
+ orchestration.startedAt = new Date();
349
+ const outputs = await this.executePhase(orchestration, firstPhase);
350
+ this.phaseOutputs.set(firstPhase.config.name, outputs);
351
+ orchestration.status = 'completed';
352
+ }
353
+ catch (error) {
354
+ orchestration.status = 'failed';
355
+ this.log.error('Dry run failed', { error: error instanceof Error ? error.message : String(error) });
356
+ }
357
+ finally {
358
+ this.executor.setPermissionMode(this.options.permissionMode ?? 'default');
359
+ orchestration.completedAt = new Date();
360
+ }
361
+ return orchestration;
362
+ }
363
+ }
226
364
  this.log.info(`Starting orchestration: ${config.name}`, {
227
365
  pattern: config.pattern,
228
366
  phases: config.phases.length,
@@ -233,9 +371,45 @@ export class SwarmEngine {
233
371
  pattern: config.pattern,
234
372
  phaseCount: config.phases.length,
235
373
  }, 'engine');
374
+ // Single-agent fast path for simple tasks
375
+ if (config.phases?.length === 1 && config.phases[0].agents?.length === 1) {
376
+ try {
377
+ const complexity = classifyTaskComplexity(config.description || '');
378
+ if (complexity === 'simple') {
379
+ this.log.info('Simple task detected — using single-agent fast path');
380
+ this.bus.emit('system:warning', { type: 'single-agent-bypass', task: config.description }, 'engine');
381
+ try {
382
+ const phase = config.phases[0];
383
+ const agent = phase.agents[0];
384
+ const runContext = {
385
+ phaseOutputs: new Map(),
386
+ intraPhaseOutputs: new Map(),
387
+ livingSpec: null,
388
+ sharedContextFiles: [],
389
+ };
390
+ const instance = await this.agentRunner.executeAgentWithRetry(agent, orchestration.phases[0], orchestration, 0, runContext);
391
+ orchestration.status = instance.result?.output ? 'completed' : 'failed';
392
+ orchestration.startedAt = new Date();
393
+ orchestration.completedAt = new Date();
394
+ orchestration.phases[0].status = orchestration.status;
395
+ if (instance.result)
396
+ orchestration.phases[0].agents = [instance];
397
+ orchestration.usage = instance.usage;
398
+ return orchestration;
399
+ }
400
+ catch (e) {
401
+ this.log.debug(`Single-agent bypass failed, falling through to normal execution: ${e}`);
402
+ // Fall through to normal execution
403
+ }
404
+ }
405
+ }
406
+ catch {
407
+ /* classification failed — fall through */
408
+ }
409
+ }
236
410
  orchestration.status = 'running';
237
411
  orchestration.startedAt = new Date();
238
- this.phaseOutputs = new Map();
412
+ this.phaseOutputs.clear();
239
413
  // TODO: Checkpoint doesn't store phase outputs — resumed phases lose inter-phase context
240
414
  this.reflections = [];
241
415
  this.sharedContextFiles = [];
@@ -257,7 +431,8 @@ export class SwarmEngine {
257
431
  if (this.gnnPredictor && this.gnnWeights) {
258
432
  try {
259
433
  const risks = this.gnnPredictor.predict(orchestration.config.id, this.gnnWeights);
260
- const highRisk = risks.filter(r => r.riskScore > 0.7);
434
+ const highRisk = risks.filter((r) => r.riskScore > 0.7);
435
+ this.benefitsCollector.addGnnPrediction(highRisk.length);
261
436
  if (highRisk.length > 0) {
262
437
  this.log.warn(`GNN predicts high failure risk for ${highRisk.length} nodes`);
263
438
  this.bus.emit('system:warning', { type: 'gnn-risk', risks: highRisk }, 'engine');
@@ -341,17 +516,32 @@ export class SwarmEngine {
341
516
  const reflection = this.reflectionEngine.reflect(phase);
342
517
  const patternStats = this.graphLearner?.getPatternStats(orchestration.config.pattern)?.[0];
343
518
  const gateOutputs = this.phaseOutputs.get(phase.config.name) || [];
344
- const gate = this.graphAnalyzer.evaluatePhaseConfidence({ kind: phase.config.kind ?? 'implement', agentCount: phase.agents.length, outputs: gateOutputs, usage: phase.usage }, { confidence: reflection.confidence }, patternStats ? { successRate: patternStats.successRate, avgDurationMs: patternStats.avgDurationMs } : undefined);
519
+ const gate = this.graphAnalyzer.evaluatePhaseConfidence({
520
+ kind: phase.config.kind ?? 'implement',
521
+ agentCount: phase.agents.length,
522
+ outputs: gateOutputs,
523
+ usage: phase.usage,
524
+ }, { confidence: reflection.confidence }, patternStats
525
+ ? { successRate: patternStats.successRate, avgDurationMs: patternStats.avgDurationMs }
526
+ : undefined);
527
+ this.benefitsCollector.addConfidenceGateEvaluated(gate.result !== 'pass');
345
528
  if (gate.result === 'halt') {
346
529
  this.log.warn(`Phase gate HALT: ${gate.reason}`);
347
530
  orchestration.status = 'failed';
348
531
  }
349
532
  if (gate.result === 'warn') {
350
533
  this.log.warn(`Phase gate WARNING: ${gate.reason}`);
351
- this.bus.emit('system:warning', { type: 'gate-warning', orchestrationId: orchestration.config.id, phase: phase.config.name, reason: gate.reason }, 'engine');
534
+ this.bus.emit('system:warning', {
535
+ type: 'gate-warning',
536
+ orchestrationId: orchestration.config.id,
537
+ phase: phase.config.name,
538
+ reason: gate.reason,
539
+ }, 'engine');
352
540
  }
353
541
  }
354
- catch { /* Graph gate evaluation failed — continue */ }
542
+ catch {
543
+ /* Graph gate evaluation failed — continue */
544
+ }
355
545
  }
356
546
  // Trajectory prediction after each completed phase
357
547
  if (this.trajectoryPredictor) {
@@ -376,9 +566,17 @@ export class SwarmEngine {
376
566
  if (this.trajectoryPredictor) {
377
567
  try {
378
568
  const forecastCompleted = orchestration.phases
379
- .filter(p => p.status === 'completed')
380
- .map(p => ({ name: p.config.name, status: p.status, tokens: p.usage?.totalTokens || 0, durationMs: p.usage?.durationMs || 0, confidence: 'medium' }));
381
- const forecastRemaining = orchestration.phases.filter(p => p.status === 'pending').map(p => p.config.name);
569
+ .filter((p) => p.status === 'completed')
570
+ .map((p) => ({
571
+ name: p.config.name,
572
+ status: p.status,
573
+ tokens: p.usage?.totalTokens || 0,
574
+ durationMs: p.usage?.durationMs || 0,
575
+ confidence: 'medium',
576
+ }));
577
+ const forecastRemaining = orchestration.phases
578
+ .filter((p) => p.status === 'pending')
579
+ .map((p) => p.config.name);
382
580
  if (forecastRemaining.length > 0) {
383
581
  const forecast = this.trajectoryPredictor.predictTrajectory(orchestration.config.id, forecastCompleted, forecastRemaining, orchestration.config.pattern);
384
582
  if (forecast.overallSuccessProb < 0.3) {
@@ -450,11 +648,13 @@ export class SwarmEngine {
450
648
  const report = this.metaAdversarial.runFullAudit({
451
649
  patternSelector: this.metaSelector ?? undefined,
452
650
  dropout: this.predictiveDropout ?? undefined,
453
- causal: this.causalEngine ? {
454
- estimateCausalEffect: (t, tv, cv) =>
455
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
456
- this.causalEngine.estimateCausalEffect(t, tv, cv),
457
- } : undefined,
651
+ causal: this.causalEngine
652
+ ? {
653
+ estimateCausalEffect: (t, tv, cv) =>
654
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
655
+ this.causalEngine.estimateCausalEffect(t, tv, cv),
656
+ }
657
+ : undefined,
458
658
  });
459
659
  if (report.overallRisk !== 'low') {
460
660
  this.log.warn(`Self-audit found ${report.vulnerabilities.length} ML vulnerabilities (risk: ${report.overallRisk})`);
@@ -489,12 +689,38 @@ export class SwarmEngine {
489
689
  }
490
690
  // Compute total usage
491
691
  orchestration.usage = orchestration.phases.reduce((total, phase) => mergeUsageStats(total, phase.usage), emptyUsageStats());
692
+ // Build benefits summary
693
+ let patternStats;
694
+ if (this.graphLearner) {
695
+ try {
696
+ const stats = this.graphLearner.getPatternStats(config.pattern)?.[0];
697
+ if (stats)
698
+ patternStats = { totalRuns: stats.totalRuns, successRate: stats.successRate, avgCostUsd: stats.avgCostUsd };
699
+ }
700
+ catch {
701
+ /* pattern stats unavailable */
702
+ }
703
+ }
704
+ const durationMs = orchestration.completedAt.getTime() - (orchestration.startedAt?.getTime() ?? 0);
705
+ const benefitsSummary = this.benefitsCollector.build({
706
+ actualTotalTokens: orchestration.usage.totalTokens,
707
+ actualCostUsd: orchestration.usage.costUsd,
708
+ durationMs,
709
+ agentsExecuted: orchestration.phases.reduce((s, p) => s + p.agents.length, 0),
710
+ phasesExecuted: orchestration.phases.filter((p) => p.status === 'completed').length,
711
+ historicalAvgCostUsd: patternStats?.avgCostUsd ?? null,
712
+ patternRunsRecorded: patternStats?.totalRuns ?? 0,
713
+ historicalSuccessRate: patternStats?.successRate ?? null,
714
+ });
715
+ orchestration.benefits = benefitsSummary;
716
+ this.benefitsCollector.reset();
492
717
  this.bus.emit(orchestration.status === 'completed' ? 'orchestration:complete' : 'orchestration:failed', {
493
718
  id: config.id,
494
719
  name: config.name,
495
720
  status: orchestration.status,
496
721
  usage: orchestration.usage,
497
- durationMs: orchestration.completedAt.getTime() - (orchestration.startedAt?.getTime() ?? 0),
722
+ benefits: benefitsSummary,
723
+ durationMs,
498
724
  }, 'engine');
499
725
  this.log.info(`Orchestration ${orchestration.status}: ${config.name}`, {
500
726
  usage: orchestration.usage,
@@ -661,6 +887,24 @@ export class SwarmEngine {
661
887
  quality: c.cost.qualityScore,
662
888
  }));
663
889
  }
890
+ // Emit plan preview event so listeners can inspect before execution
891
+ try {
892
+ this.bus.emit('system:warning', {
893
+ type: 'plan-preview',
894
+ pattern: patternName,
895
+ phases: bestPlan.phases.map((p) => ({
896
+ name: p.name,
897
+ kind: p.kind,
898
+ agentCount: p.agents?.length || 0,
899
+ agents: p.agents?.map((a) => ({ type: a.type, model: a.model })) || [],
900
+ })),
901
+ estimatedCost: bestPlan.estimates,
902
+ task: task.slice(0, 200),
903
+ }, 'engine');
904
+ }
905
+ catch {
906
+ /* plan preview emit failed — non-critical */
907
+ }
664
908
  return bestPlan;
665
909
  }
666
910
  /**
@@ -674,6 +918,9 @@ export class SwarmEngine {
674
918
  const dropout = this.replanner.evaluateDropout(orchestration, phase.config);
675
919
  if (dropout) {
676
920
  this.log.info(`Agent dropout: ${dropout.reason}`);
921
+ const droppedCount = phase.config.agents.length - dropout.agentsToKeep.length;
922
+ for (let i = 0; i < droppedCount; i++)
923
+ this.benefitsCollector.addAgentDroppedRules();
677
924
  phase.config.agents = phase.config.agents.filter((a) => dropout.agentsToKeep.includes(a.name));
678
925
  this.bus.emit('system:warning', { type: 'agent-dropout', ...dropout }, 'adaptive');
679
926
  }
@@ -681,20 +928,96 @@ export class SwarmEngine {
681
928
  // Predictive dropout from ML model (Feature 5: Predictive Dropout)
682
929
  if (this.predictiveDropout && phase.config.agents.length > 1) {
683
930
  try {
684
- const recommendations = this.predictiveDropout.predictRedundant(phase.config.agents.map(a => ({ id: a.name || a.type, agentType: a.type })), phase.config.kind ?? 'implement', 0, orchestration.config.pattern || '');
685
- const toDrop = recommendations.filter(r => r.shouldDropOut);
931
+ const recommendations = this.predictiveDropout.predictRedundant(phase.config.agents.map((a) => ({ id: a.name || a.type, agentType: a.type })), phase.config.kind ?? 'implement', 0, orchestration.config.pattern || '');
932
+ const toDrop = recommendations.filter((r) => r.shouldDropOut);
686
933
  if (toDrop.length > 0) {
687
- this.log.info(`Predictive dropout: skipping ${toDrop.map(d => d.agentType).join(', ')}`);
688
- phase.config.agents = phase.config.agents.filter(a => !toDrop.some(d => d.agentType === a.type));
934
+ this.log.info(`Predictive dropout: skipping ${toDrop.map((d) => d.agentType).join(', ')}`);
935
+ for (const d of toDrop)
936
+ this.benefitsCollector.addAgentDroppedML(5000); // ~5K tokens per dropped agent
937
+ phase.config.agents = phase.config.agents.filter((a) => !toDrop.some((d) => d.agentType === a.type));
689
938
  }
690
939
  }
691
940
  catch (e) {
692
941
  this.log.debug(`Predictive dropout failed: ${e}`);
693
942
  }
694
943
  }
944
+ // Feature 5: Diff-based review context — inject git diff before review phases
945
+ if (phase.config.kind === 'review' && !this.options.mock) {
946
+ try {
947
+ const cwd = this.options.cwd || process.cwd();
948
+ const { stdout: diff } = await execFileAsync('git', ['diff', '--stat', 'HEAD'], {
949
+ cwd,
950
+ timeout: 10000,
951
+ encoding: 'utf-8',
952
+ });
953
+ const trimmedDiff = diff.slice(0, 3000);
954
+ if (trimmedDiff.trim()) {
955
+ const { stdout: diffDetail } = await execFileAsync('git', ['diff', 'HEAD'], {
956
+ cwd,
957
+ timeout: 10000,
958
+ encoding: 'utf-8',
959
+ });
960
+ const existing = this.phaseOutputs.get(phase.config.name) || [];
961
+ existing.unshift(`## Git Diff Summary\n\`\`\`\n${trimmedDiff}\n\`\`\`\n\n## Detailed Changes\n\`\`\`diff\n${diffDetail.slice(0, 5000)}\n\`\`\``);
962
+ this.phaseOutputs.set(phase.config.name, existing);
963
+ }
964
+ }
965
+ catch (e) {
966
+ this.log.debug(`Diff context failed: ${e}`);
967
+ }
968
+ }
969
+ // Feature 6: Pre-review lint wiring — run lint/typecheck before review phases
970
+ if (phase.config.kind === 'review' && this.verifier && !this.options.mock) {
971
+ try {
972
+ const lintResults = await this.verifier.verify();
973
+ const failures = lintResults.filter((r) => !r.passed);
974
+ if (failures.length > 0) {
975
+ const lintOutput = failures.map((f) => `${f.step}: ${f.output?.slice(0, 500)}`).join('\n');
976
+ const existing = this.phaseOutputs.get(phase.config.name) || [];
977
+ existing.unshift(`## Pre-Review Lint/Typecheck Results\n${lintOutput}`);
978
+ this.phaseOutputs.set(phase.config.name, existing);
979
+ }
980
+ }
981
+ catch (e) {
982
+ this.log.debug(`Pre-review checks failed: ${e}`);
983
+ }
984
+ }
695
985
  const maxRetries = phase.config.maxRetries ?? 2;
986
+ // Smart phase output routing: filter prior outputs by relevance instead of passing everything
987
+ let filteredOutputs = this.phaseOutputs;
988
+ if (phase.config.kind === 'review') {
989
+ // Review phase only needs implement/integrate/test outputs, not research
990
+ const reviewFiltered = new Map();
991
+ for (const [phaseName, outputs] of this.phaseOutputs) {
992
+ if (phaseName.includes('implement') || phaseName.includes('integrate') || phaseName.includes('test')) {
993
+ reviewFiltered.set(phaseName, outputs);
994
+ }
995
+ }
996
+ // Fall back to all outputs if nothing matched
997
+ if (reviewFiltered.size > 0) {
998
+ const filteredTokens = [...reviewFiltered.values()].flat().reduce((s, o) => s + estimateTokens(o), 0);
999
+ const totalTokens = [...this.phaseOutputs.values()].flat().reduce((s, o) => s + estimateTokens(o), 0);
1000
+ this.benefitsCollector.addSmartRoutingSaved(Math.max(0, totalTokens - filteredTokens));
1001
+ filteredOutputs = reviewFiltered;
1002
+ }
1003
+ }
1004
+ if (phase.config.kind === 'test') {
1005
+ // Test phase only needs implement outputs
1006
+ const testFiltered = new Map();
1007
+ for (const [phaseName, outputs] of this.phaseOutputs) {
1008
+ if (phaseName.includes('implement')) {
1009
+ testFiltered.set(phaseName, outputs);
1010
+ }
1011
+ }
1012
+ if (testFiltered.size > 0) {
1013
+ const filteredTokens = [...testFiltered.values()].flat().reduce((s, o) => s + estimateTokens(o), 0);
1014
+ const totalTokens = [...this.phaseOutputs.values()].flat().reduce((s, o) => s + estimateTokens(o), 0);
1015
+ this.benefitsCollector.addSmartRoutingSaved(Math.max(0, totalTokens - filteredTokens));
1016
+ filteredOutputs = testFiltered;
1017
+ }
1018
+ }
696
1019
  const runContext = {
697
- phaseOutputs: this.phaseOutputs,
1020
+ phaseOutputs: filteredOutputs,
698
1021
  livingSpec: this.livingSpec,
699
1022
  sharedContextFiles: this.sharedContextFiles,
700
1023
  cwd: this.options.cwd,
@@ -719,6 +1042,7 @@ export class SwarmEngine {
719
1042
  const sharedTokens = estimateTokens(`## Orchestration: ${orchestration.config.name}\nPattern: ${orchestration.config.pattern}\n\n${phaseContext}`);
720
1043
  if (sharedTokens > 0) {
721
1044
  const savings = estimateCacheSavings(sharedTokens, phase.config.agents.length, 3.0);
1045
+ this.benefitsCollector.addCacheSavingsUsd(savings.savings ?? 0);
722
1046
  this.log.info(`Cache optimization: ${savings.savingsPercent}% estimated savings on shared prefix (${phase.config.agents.length} agents)`, savings);
723
1047
  }
724
1048
  }
@@ -778,6 +1102,7 @@ export class SwarmEngine {
778
1102
  if (agent.result?.output) {
779
1103
  try {
780
1104
  this.reviewFeedback.recordFindings(orchestration.config.id, phase.config.name, agent.result.output, agent.config.name);
1105
+ this.benefitsCollector.addReviewFindingRecorded();
781
1106
  const findings = parseReviewOutput(agent.result.output, agent.config.name);
782
1107
  this.reviewFeedback.adjustContextWeights(orchestration.config.id, findings);
783
1108
  }
@@ -788,7 +1113,7 @@ export class SwarmEngine {
788
1113
  }
789
1114
  }
790
1115
  // Run verification after implementation phases
791
- if (phase.config.kind === 'implement' || phase.config.kind === 'integrate') {
1116
+ if ((phase.config.kind === 'implement' || phase.config.kind === 'integrate') && !this.options.mock) {
792
1117
  const results = await this.verifier.verify();
793
1118
  const summary = Verifier.summarize(results);
794
1119
  this.log.info(`Verification: ${summary}`, { phase: phase.config.name });
@@ -798,6 +1123,10 @@ export class SwarmEngine {
798
1123
  const adaptation = this.replanner.analyze(orchestration, phase);
799
1124
  if (adaptation) {
800
1125
  this.replanner.apply(orchestration, adaptation);
1126
+ for (const _s of adaptation.skipPhases || [])
1127
+ this.benefitsCollector.addPhaseSkipped();
1128
+ for (const _o of Object.keys(adaptation.modelOverrides || {}))
1129
+ this.benefitsCollector.addModelDowngrade();
801
1130
  this.bus.emit('system:warning', {
802
1131
  type: 'adaptive-replan',
803
1132
  reason: adaptation.reason,
@@ -817,13 +1146,22 @@ export class SwarmEngine {
817
1146
  agents: phase.agents.length,
818
1147
  usage: phase.usage,
819
1148
  });
820
- // Collect agent outputs for inter-phase context
1149
+ // Collect agent outputs for inter-phase context (structured handoff)
821
1150
  const outputs = phase.agents
822
1151
  .filter((a) => a.result?.output)
823
- .map((a) => `[${a.config.name}]: ${a.result.output.slice(0, 2000)}`);
1152
+ .map((a) => {
1153
+ try {
1154
+ const handoff = HandoffParser.parseAgentOutput(a.result.output);
1155
+ return HandoffParser.serializeForNextPhase([{ agentName: a.config.name, handoff }], 4000);
1156
+ }
1157
+ catch {
1158
+ return `[${a.config.name}]: ${a.result.output.slice(0, 2000)}`;
1159
+ }
1160
+ });
824
1161
  // Update living spec with phase outputs
825
1162
  if (this.livingSpec) {
826
1163
  this.livingSpec = updateSpecFromPhase(this.livingSpec, phase.config.name, outputs);
1164
+ this.benefitsCollector.addLivingSpecUpdate();
827
1165
  }
828
1166
  // Generate reflection and inject into outputs for next phase
829
1167
  const reflection = this.reflectionEngine.reflect(phase);