swarm-engine 1.3.0 → 1.41.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.
- package/README.md +45 -7
- package/dist/cli/commands/memory.js +117 -0
- package/dist/cli/commands/memory.js.map +1 -1
- package/dist/core/patterns.d.ts +7 -1
- package/dist/core/patterns.d.ts.map +1 -1
- package/dist/core/patterns.js +23 -0
- package/dist/core/patterns.js.map +1 -1
- package/dist/core/types.d.ts +39 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/index.d.ts +37 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +91 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/runtime/adaptive.d.ts +1 -0
- package/dist/runtime/adaptive.d.ts.map +1 -1
- package/dist/runtime/adaptive.js +18 -4
- package/dist/runtime/adaptive.js.map +1 -1
- package/dist/runtime/agent-runner.d.ts +18 -0
- package/dist/runtime/agent-runner.d.ts.map +1 -1
- package/dist/runtime/agent-runner.js +147 -4
- package/dist/runtime/agent-runner.js.map +1 -1
- package/dist/runtime/backends/claude-session.d.ts +49 -0
- package/dist/runtime/backends/claude-session.d.ts.map +1 -0
- package/dist/runtime/backends/claude-session.js +237 -0
- package/dist/runtime/backends/claude-session.js.map +1 -0
- package/dist/runtime/backends/claude.d.ts.map +1 -1
- package/dist/runtime/backends/claude.js +90 -4
- package/dist/runtime/backends/claude.js.map +1 -1
- package/dist/runtime/backends/types.d.ts +31 -1
- package/dist/runtime/backends/types.d.ts.map +1 -1
- package/dist/runtime/compaction.d.ts +7 -1
- package/dist/runtime/compaction.d.ts.map +1 -1
- package/dist/runtime/compaction.js +12 -2
- package/dist/runtime/compaction.js.map +1 -1
- package/dist/runtime/distiller.d.ts +1 -0
- package/dist/runtime/distiller.d.ts.map +1 -1
- package/dist/runtime/distiller.js +8 -2
- package/dist/runtime/distiller.js.map +1 -1
- package/dist/runtime/engine.d.ts +48 -2
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +600 -8
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/execution-graph.d.ts +86 -0
- package/dist/runtime/execution-graph.d.ts.map +1 -0
- package/dist/runtime/execution-graph.js +441 -0
- package/dist/runtime/execution-graph.js.map +1 -0
- package/dist/runtime/executor.d.ts +7 -1
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +20 -0
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/graph-adversarial.d.ts +88 -0
- package/dist/runtime/graph-adversarial.d.ts.map +1 -0
- package/dist/runtime/graph-adversarial.js +378 -0
- package/dist/runtime/graph-adversarial.js.map +1 -0
- package/dist/runtime/graph-analyzer.d.ts +106 -0
- package/dist/runtime/graph-analyzer.d.ts.map +1 -0
- package/dist/runtime/graph-analyzer.js +321 -0
- package/dist/runtime/graph-analyzer.js.map +1 -0
- package/dist/runtime/graph-causal.d.ts +91 -0
- package/dist/runtime/graph-causal.d.ts.map +1 -0
- package/dist/runtime/graph-causal.js +292 -0
- package/dist/runtime/graph-causal.js.map +1 -0
- package/dist/runtime/graph-context-router.d.ts +73 -0
- package/dist/runtime/graph-context-router.d.ts.map +1 -0
- package/dist/runtime/graph-context-router.js +162 -0
- package/dist/runtime/graph-context-router.js.map +1 -0
- package/dist/runtime/graph-discovery.d.ts +71 -0
- package/dist/runtime/graph-discovery.d.ts.map +1 -0
- package/dist/runtime/graph-discovery.js +364 -0
- package/dist/runtime/graph-discovery.js.map +1 -0
- package/dist/runtime/graph-dropout.d.ts +59 -0
- package/dist/runtime/graph-dropout.d.ts.map +1 -0
- package/dist/runtime/graph-dropout.js +204 -0
- package/dist/runtime/graph-dropout.js.map +1 -0
- package/dist/runtime/graph-embeddings.d.ts +58 -0
- package/dist/runtime/graph-embeddings.d.ts.map +1 -0
- package/dist/runtime/graph-embeddings.js +299 -0
- package/dist/runtime/graph-embeddings.js.map +1 -0
- package/dist/runtime/graph-feedback.d.ts +30 -0
- package/dist/runtime/graph-feedback.d.ts.map +1 -0
- package/dist/runtime/graph-feedback.js +80 -0
- package/dist/runtime/graph-feedback.js.map +1 -0
- package/dist/runtime/graph-gnn.d.ts +120 -0
- package/dist/runtime/graph-gnn.d.ts.map +1 -0
- package/dist/runtime/graph-gnn.js +524 -0
- package/dist/runtime/graph-gnn.js.map +1 -0
- package/dist/runtime/graph-learner.d.ts +70 -0
- package/dist/runtime/graph-learner.d.ts.map +1 -0
- package/dist/runtime/graph-learner.js +265 -0
- package/dist/runtime/graph-learner.js.map +1 -0
- package/dist/runtime/graph-meta-adversarial.d.ts +113 -0
- package/dist/runtime/graph-meta-adversarial.d.ts.map +1 -0
- package/dist/runtime/graph-meta-adversarial.js +366 -0
- package/dist/runtime/graph-meta-adversarial.js.map +1 -0
- package/dist/runtime/graph-meta.d.ts +115 -0
- package/dist/runtime/graph-meta.d.ts.map +1 -0
- package/dist/runtime/graph-meta.js +465 -0
- package/dist/runtime/graph-meta.js.map +1 -0
- package/dist/runtime/graph-self-evolve.d.ts +92 -0
- package/dist/runtime/graph-self-evolve.d.ts.map +1 -0
- package/dist/runtime/graph-self-evolve.js +422 -0
- package/dist/runtime/graph-self-evolve.js.map +1 -0
- package/dist/runtime/graph-synthesis.d.ts +47 -0
- package/dist/runtime/graph-synthesis.d.ts.map +1 -0
- package/dist/runtime/graph-synthesis.js +232 -0
- package/dist/runtime/graph-synthesis.js.map +1 -0
- package/dist/runtime/graph-trajectory.d.ts +88 -0
- package/dist/runtime/graph-trajectory.d.ts.map +1 -0
- package/dist/runtime/graph-trajectory.js +334 -0
- package/dist/runtime/graph-trajectory.js.map +1 -0
- package/dist/runtime/learning-engine.d.ts +12 -0
- package/dist/runtime/learning-engine.d.ts.map +1 -1
- package/dist/runtime/learning-engine.js +70 -0
- package/dist/runtime/learning-engine.js.map +1 -1
- package/dist/runtime/prompt-compressor.d.ts +16 -0
- package/dist/runtime/prompt-compressor.d.ts.map +1 -0
- package/dist/runtime/prompt-compressor.js +68 -0
- package/dist/runtime/prompt-compressor.js.map +1 -0
- package/dist/runtime/repo-map.d.ts +40 -0
- package/dist/runtime/repo-map.d.ts.map +1 -0
- package/dist/runtime/repo-map.js +358 -0
- package/dist/runtime/repo-map.js.map +1 -0
- package/dist/runtime/sdk-mcp-server.d.ts +44 -0
- package/dist/runtime/sdk-mcp-server.d.ts.map +1 -0
- package/dist/runtime/sdk-mcp-server.js +133 -0
- package/dist/runtime/sdk-mcp-server.js.map +1 -0
- package/dist/runtime/structured-handoff.d.ts +41 -0
- package/dist/runtime/structured-handoff.d.ts.map +1 -0
- package/dist/runtime/structured-handoff.js +279 -0
- package/dist/runtime/structured-handoff.js.map +1 -0
- package/dist/runtime/token-analytics.d.ts +38 -0
- package/dist/runtime/token-analytics.d.ts.map +1 -0
- package/dist/runtime/token-analytics.js +59 -0
- package/dist/runtime/token-analytics.js.map +1 -0
- package/dist/runtime/verifier.d.ts +10 -0
- package/dist/runtime/verifier.d.ts.map +1 -1
- package/dist/runtime/verifier.js +97 -1
- package/dist/runtime/verifier.js.map +1 -1
- package/package.json +2 -2
- package/skills/swarm-output-style/SKILL.md +71 -33
package/dist/runtime/engine.js
CHANGED
|
@@ -23,9 +23,32 @@ 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';
|
|
31
|
+
import { ExecutionGraph } from './execution-graph.js';
|
|
32
|
+
import { GraphLearner } from './graph-learner.js';
|
|
33
|
+
import { GraphAnalyzer } from './graph-analyzer.js';
|
|
34
|
+
import { GraphContextRouter } from './graph-context-router.js';
|
|
35
|
+
import { ReviewFeedbackRecorder } from './graph-feedback.js';
|
|
36
|
+
import { parseReviewOutput } from './review-schema.js';
|
|
28
37
|
import { closeDatabase } from './database.js';
|
|
38
|
+
import { CausalGraphEngine } from './graph-causal.js';
|
|
39
|
+
import { FailurePropagationPredictor } from './graph-gnn.js';
|
|
40
|
+
import { AdversarialEvolver } from './graph-adversarial.js';
|
|
41
|
+
import { MetaPatternSelector } from './graph-meta.js';
|
|
42
|
+
import { PredictiveDropout } from './graph-dropout.js';
|
|
43
|
+
import { PatternSynthesizer } from './graph-synthesis.js';
|
|
44
|
+
import { TrajectoryPredictor } from './graph-trajectory.js';
|
|
45
|
+
import { MetaAdversarialTester } from './graph-meta-adversarial.js';
|
|
46
|
+
import { RuleEvolver } from './graph-self-evolve.js';
|
|
47
|
+
import { TaskDiscovery } from './graph-discovery.js';
|
|
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';
|
|
29
52
|
/**
|
|
30
53
|
* The Swarm Orchestration Engine.
|
|
31
54
|
*
|
|
@@ -61,6 +84,137 @@ export class SwarmEngine {
|
|
|
61
84
|
livingSpec = null;
|
|
62
85
|
agentRunner;
|
|
63
86
|
learningEngine;
|
|
87
|
+
executionGraph = null;
|
|
88
|
+
graphAnalyzer = null;
|
|
89
|
+
graphLearner = null;
|
|
90
|
+
// Lazy-initialized ML modules (only constructed on first access)
|
|
91
|
+
_reviewFeedback;
|
|
92
|
+
_causalEngine;
|
|
93
|
+
_gnnPredictor;
|
|
94
|
+
_adversarialEvolver;
|
|
95
|
+
_metaSelector;
|
|
96
|
+
_predictiveDropout;
|
|
97
|
+
_gnnWeights;
|
|
98
|
+
_patternSynthesizer;
|
|
99
|
+
_trajectoryPredictor;
|
|
100
|
+
_metaAdversarial;
|
|
101
|
+
_ruleEvolver;
|
|
102
|
+
_taskDiscovery;
|
|
103
|
+
_orchestrationEmbedder;
|
|
104
|
+
swarmMcpServer = null;
|
|
105
|
+
decisions = new Map();
|
|
106
|
+
get reviewFeedback() {
|
|
107
|
+
if (this._reviewFeedback === undefined) {
|
|
108
|
+
this._reviewFeedback = this.executionGraph
|
|
109
|
+
? this.tryInit(() => new ReviewFeedbackRecorder(this.executionGraph), 'ReviewFeedbackRecorder')
|
|
110
|
+
: null;
|
|
111
|
+
}
|
|
112
|
+
return this._reviewFeedback;
|
|
113
|
+
}
|
|
114
|
+
get causalEngine() {
|
|
115
|
+
if (this._causalEngine === undefined) {
|
|
116
|
+
this._causalEngine = this.executionGraph
|
|
117
|
+
? this.tryInit(() => new CausalGraphEngine(this.executionGraph), 'CausalGraphEngine')
|
|
118
|
+
: null;
|
|
119
|
+
}
|
|
120
|
+
return this._causalEngine;
|
|
121
|
+
}
|
|
122
|
+
get gnnPredictor() {
|
|
123
|
+
if (this._gnnPredictor === undefined) {
|
|
124
|
+
this._gnnPredictor = this.executionGraph
|
|
125
|
+
? this.tryInit(() => new FailurePropagationPredictor(this.executionGraph), 'FailurePropagationPredictor')
|
|
126
|
+
: null;
|
|
127
|
+
}
|
|
128
|
+
return this._gnnPredictor;
|
|
129
|
+
}
|
|
130
|
+
get adversarialEvolver() {
|
|
131
|
+
if (this._adversarialEvolver === undefined) {
|
|
132
|
+
this._adversarialEvolver = this.executionGraph
|
|
133
|
+
? this.tryInit(() => new AdversarialEvolver(this.executionGraph), 'AdversarialEvolver')
|
|
134
|
+
: null;
|
|
135
|
+
}
|
|
136
|
+
return this._adversarialEvolver;
|
|
137
|
+
}
|
|
138
|
+
get metaSelector() {
|
|
139
|
+
if (this._metaSelector === undefined) {
|
|
140
|
+
this._metaSelector = this.executionGraph
|
|
141
|
+
? this.tryInit(() => new MetaPatternSelector(this.executionGraph), 'MetaPatternSelector')
|
|
142
|
+
: null;
|
|
143
|
+
}
|
|
144
|
+
return this._metaSelector;
|
|
145
|
+
}
|
|
146
|
+
get predictiveDropout() {
|
|
147
|
+
if (this._predictiveDropout === undefined) {
|
|
148
|
+
this._predictiveDropout = this.executionGraph
|
|
149
|
+
? this.tryInit(() => new PredictiveDropout(this.executionGraph), 'PredictiveDropout')
|
|
150
|
+
: null;
|
|
151
|
+
}
|
|
152
|
+
return this._predictiveDropout;
|
|
153
|
+
}
|
|
154
|
+
get gnnWeights() {
|
|
155
|
+
if (this._gnnWeights === undefined) {
|
|
156
|
+
if (this.gnnPredictor) {
|
|
157
|
+
try {
|
|
158
|
+
this._gnnWeights = this.gnnPredictor.initWeights();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
this._gnnWeights = null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
this._gnnWeights = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return this._gnnWeights;
|
|
169
|
+
}
|
|
170
|
+
get patternSynthesizer() {
|
|
171
|
+
if (this._patternSynthesizer === undefined) {
|
|
172
|
+
this._patternSynthesizer = this.executionGraph
|
|
173
|
+
? this.tryInit(() => new PatternSynthesizer(this.executionGraph), 'PatternSynthesizer')
|
|
174
|
+
: null;
|
|
175
|
+
}
|
|
176
|
+
return this._patternSynthesizer;
|
|
177
|
+
}
|
|
178
|
+
get trajectoryPredictor() {
|
|
179
|
+
if (this._trajectoryPredictor === undefined) {
|
|
180
|
+
this._trajectoryPredictor = this.executionGraph
|
|
181
|
+
? this.tryInit(() => new TrajectoryPredictor(this.executionGraph), 'TrajectoryPredictor')
|
|
182
|
+
: null;
|
|
183
|
+
}
|
|
184
|
+
return this._trajectoryPredictor;
|
|
185
|
+
}
|
|
186
|
+
get metaAdversarial() {
|
|
187
|
+
if (this._metaAdversarial === undefined) {
|
|
188
|
+
this._metaAdversarial = this.executionGraph
|
|
189
|
+
? this.tryInit(() => new MetaAdversarialTester(this.executionGraph), 'MetaAdversarialTester')
|
|
190
|
+
: null;
|
|
191
|
+
}
|
|
192
|
+
return this._metaAdversarial;
|
|
193
|
+
}
|
|
194
|
+
get ruleEvolver() {
|
|
195
|
+
if (this._ruleEvolver === undefined) {
|
|
196
|
+
this._ruleEvolver = this.executionGraph
|
|
197
|
+
? this.tryInit(() => new RuleEvolver(this.executionGraph), 'RuleEvolver')
|
|
198
|
+
: null;
|
|
199
|
+
}
|
|
200
|
+
return this._ruleEvolver;
|
|
201
|
+
}
|
|
202
|
+
get taskDiscovery() {
|
|
203
|
+
if (this._taskDiscovery === undefined) {
|
|
204
|
+
this._taskDiscovery = this.executionGraph
|
|
205
|
+
? this.tryInit(() => new TaskDiscovery(this.executionGraph), 'TaskDiscovery')
|
|
206
|
+
: null;
|
|
207
|
+
}
|
|
208
|
+
return this._taskDiscovery;
|
|
209
|
+
}
|
|
210
|
+
get orchestrationEmbedder() {
|
|
211
|
+
if (this._orchestrationEmbedder === undefined) {
|
|
212
|
+
this._orchestrationEmbedder = this.executionGraph
|
|
213
|
+
? this.tryInit(() => new OrchestrationEmbedder(this.executionGraph), 'OrchestrationEmbedder')
|
|
214
|
+
: null;
|
|
215
|
+
}
|
|
216
|
+
return this._orchestrationEmbedder;
|
|
217
|
+
}
|
|
64
218
|
constructor(options) {
|
|
65
219
|
this.options = options;
|
|
66
220
|
this.registry = options.registry;
|
|
@@ -92,6 +246,51 @@ export class SwarmEngine {
|
|
|
92
246
|
this.modelRouter = this.tryInit(() => new ModelRouter(), 'ModelRouter');
|
|
93
247
|
this.autonomyTracker = this.tryInit(() => new AutonomyTracker(), 'AutonomyTracker');
|
|
94
248
|
this.compounder = this.tryInit(() => new KnowledgeCompounder(), 'KnowledgeCompounder');
|
|
249
|
+
this.executionGraph = this.tryInit(() => new ExecutionGraph(), 'ExecutionGraph');
|
|
250
|
+
this.executionGraph?.attachToEventBus(this.bus);
|
|
251
|
+
// Create SwarmMcpServer for SDK tool injection
|
|
252
|
+
try {
|
|
253
|
+
this.swarmMcpServer = new SwarmMcpServer({
|
|
254
|
+
phaseOutputs: this.phaseOutputs,
|
|
255
|
+
graph: this.executionGraph,
|
|
256
|
+
decisions: this.decisions,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
this.swarmMcpServer = null;
|
|
261
|
+
}
|
|
262
|
+
// Create graph analysis deps (reused by AgentRunner + LearningEngine)
|
|
263
|
+
this.graphLearner = this.executionGraph
|
|
264
|
+
? this.tryInit(() => new GraphLearner(this.executionGraph), 'GraphLearner')
|
|
265
|
+
: null;
|
|
266
|
+
this.graphAnalyzer = this.executionGraph
|
|
267
|
+
? this.tryInit(() => new GraphAnalyzer(this.executionGraph), 'GraphAnalyzer')
|
|
268
|
+
: null;
|
|
269
|
+
// ML modules (reviewFeedback, causalEngine, gnnPredictor, adversarialEvolver,
|
|
270
|
+
// metaSelector, predictiveDropout, gnnWeights, patternSynthesizer, trajectoryPredictor,
|
|
271
|
+
// metaAdversarial, ruleEvolver, taskDiscovery, orchestrationEmbedder) are lazy-initialized
|
|
272
|
+
// via property getters — they are only constructed on first access.
|
|
273
|
+
// Prune stale graph data on init (Feature 6: graph decay)
|
|
274
|
+
if (this.executionGraph) {
|
|
275
|
+
try {
|
|
276
|
+
this.executionGraph.pruneOlderThan(90);
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
/* silent */
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Rule evolution proposal on startup (skip in mock mode and when no historical data)
|
|
283
|
+
if (this.ruleEvolver && !this.options.mock) {
|
|
284
|
+
try {
|
|
285
|
+
const proposals = this.ruleEvolver.evolveRules(50);
|
|
286
|
+
if (proposals.length > 0) {
|
|
287
|
+
this.log.info(`Rule evolver proposes ${proposals.length} rule updates (top: ${proposals[0].rule.name}, +${Math.round(proposals[0].backtestedImprovement * 100)}%)`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
this.log.debug(`Rule evolution failed: ${e}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
95
294
|
if (options.worktreeEnabled) {
|
|
96
295
|
try {
|
|
97
296
|
this.worktreeManager = new WorktreeManager(options.cwd);
|
|
@@ -107,6 +306,9 @@ export class SwarmEngine {
|
|
|
107
306
|
logger: options.logger,
|
|
108
307
|
compaction: this.compaction,
|
|
109
308
|
worktreeManager: this.worktreeManager,
|
|
309
|
+
contextRouter: this.executionGraph ? new GraphContextRouter(this.executionGraph) : undefined,
|
|
310
|
+
graphLearner: this.graphLearner,
|
|
311
|
+
graphAnalyzer: this.graphAnalyzer,
|
|
110
312
|
});
|
|
111
313
|
this.learningEngine = new LearningEngine({
|
|
112
314
|
traceStore: this.traceStore,
|
|
@@ -114,6 +316,10 @@ export class SwarmEngine {
|
|
|
114
316
|
modelRouter: this.modelRouter,
|
|
115
317
|
autonomyTracker: this.autonomyTracker,
|
|
116
318
|
compounder: this.compounder,
|
|
319
|
+
graphLearner: this.graphLearner,
|
|
320
|
+
metaSelector: this.metaSelector,
|
|
321
|
+
predictiveDropout: this.predictiveDropout,
|
|
322
|
+
orchestrationEmbedder: this.orchestrationEmbedder,
|
|
117
323
|
log: options.logger,
|
|
118
324
|
});
|
|
119
325
|
if (options.pluginLoader) {
|
|
@@ -124,10 +330,35 @@ export class SwarmEngine {
|
|
|
124
330
|
/**
|
|
125
331
|
* Execute an orchestration from config.
|
|
126
332
|
*/
|
|
127
|
-
async execute(config) {
|
|
333
|
+
async execute(config, executeOptions) {
|
|
128
334
|
// Re-use existing instance if present (e.g., when resuming from checkpoint)
|
|
129
335
|
const orchestration = this.orchestrations.get(config.id) ?? this.createInstance(config);
|
|
130
336
|
this.orchestrations.set(config.id, orchestration);
|
|
337
|
+
// Dry run mode: execute only the first phase in 'plan' mode, then return
|
|
338
|
+
if (executeOptions?.dryRun) {
|
|
339
|
+
this.log.info('Dry run mode: running first phase in plan mode');
|
|
340
|
+
const firstPhase = orchestration.phases[0];
|
|
341
|
+
if (firstPhase) {
|
|
342
|
+
this.executor.setPermissionMode('plan');
|
|
343
|
+
try {
|
|
344
|
+
this.phaseOutputs.clear();
|
|
345
|
+
orchestration.status = 'running';
|
|
346
|
+
orchestration.startedAt = new Date();
|
|
347
|
+
const outputs = await this.executePhase(orchestration, firstPhase);
|
|
348
|
+
this.phaseOutputs.set(firstPhase.config.name, outputs);
|
|
349
|
+
orchestration.status = 'completed';
|
|
350
|
+
}
|
|
351
|
+
catch (error) {
|
|
352
|
+
orchestration.status = 'failed';
|
|
353
|
+
this.log.error('Dry run failed', { error: error instanceof Error ? error.message : String(error) });
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
this.executor.setPermissionMode(this.options.permissionMode ?? 'default');
|
|
357
|
+
orchestration.completedAt = new Date();
|
|
358
|
+
}
|
|
359
|
+
return orchestration;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
131
362
|
this.log.info(`Starting orchestration: ${config.name}`, {
|
|
132
363
|
pattern: config.pattern,
|
|
133
364
|
phases: config.phases.length,
|
|
@@ -138,9 +369,45 @@ export class SwarmEngine {
|
|
|
138
369
|
pattern: config.pattern,
|
|
139
370
|
phaseCount: config.phases.length,
|
|
140
371
|
}, 'engine');
|
|
372
|
+
// Single-agent fast path for simple tasks
|
|
373
|
+
if (config.phases?.length === 1 && config.phases[0].agents?.length === 1) {
|
|
374
|
+
try {
|
|
375
|
+
const complexity = classifyTaskComplexity(config.description || '');
|
|
376
|
+
if (complexity === 'simple') {
|
|
377
|
+
this.log.info('Simple task detected — using single-agent fast path');
|
|
378
|
+
this.bus.emit('system:warning', { type: 'single-agent-bypass', task: config.description }, 'engine');
|
|
379
|
+
try {
|
|
380
|
+
const phase = config.phases[0];
|
|
381
|
+
const agent = phase.agents[0];
|
|
382
|
+
const runContext = {
|
|
383
|
+
phaseOutputs: new Map(),
|
|
384
|
+
intraPhaseOutputs: new Map(),
|
|
385
|
+
livingSpec: null,
|
|
386
|
+
sharedContextFiles: [],
|
|
387
|
+
};
|
|
388
|
+
const instance = await this.agentRunner.executeAgentWithRetry(agent, orchestration.phases[0], orchestration, 0, runContext);
|
|
389
|
+
orchestration.status = instance.result?.output ? 'completed' : 'failed';
|
|
390
|
+
orchestration.startedAt = new Date();
|
|
391
|
+
orchestration.completedAt = new Date();
|
|
392
|
+
orchestration.phases[0].status = orchestration.status;
|
|
393
|
+
if (instance.result)
|
|
394
|
+
orchestration.phases[0].agents = [instance];
|
|
395
|
+
orchestration.usage = instance.usage;
|
|
396
|
+
return orchestration;
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
this.log.debug(`Single-agent bypass failed, falling through to normal execution: ${e}`);
|
|
400
|
+
// Fall through to normal execution
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
/* classification failed — fall through */
|
|
406
|
+
}
|
|
407
|
+
}
|
|
141
408
|
orchestration.status = 'running';
|
|
142
409
|
orchestration.startedAt = new Date();
|
|
143
|
-
this.phaseOutputs
|
|
410
|
+
this.phaseOutputs.clear();
|
|
144
411
|
// TODO: Checkpoint doesn't store phase outputs — resumed phases lose inter-phase context
|
|
145
412
|
this.reflections = [];
|
|
146
413
|
this.sharedContextFiles = [];
|
|
@@ -158,6 +425,20 @@ export class SwarmEngine {
|
|
|
158
425
|
completed.add(phase.config.name);
|
|
159
426
|
}
|
|
160
427
|
}
|
|
428
|
+
// GNN pre-execution risk prediction (Feature 2: GNN Failure Propagation)
|
|
429
|
+
if (this.gnnPredictor && this.gnnWeights) {
|
|
430
|
+
try {
|
|
431
|
+
const risks = this.gnnPredictor.predict(orchestration.config.id, this.gnnWeights);
|
|
432
|
+
const highRisk = risks.filter((r) => r.riskScore > 0.7);
|
|
433
|
+
if (highRisk.length > 0) {
|
|
434
|
+
this.log.warn(`GNN predicts high failure risk for ${highRisk.length} nodes`);
|
|
435
|
+
this.bus.emit('system:warning', { type: 'gnn-risk', risks: highRisk }, 'engine');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
this.log.debug(`GNN prediction failed: ${e}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
161
442
|
while (completed.size < orchestration.phases.length) {
|
|
162
443
|
// Check if abort was requested
|
|
163
444
|
if (this.abortController.signal.aborted) {
|
|
@@ -226,11 +507,84 @@ export class SwarmEngine {
|
|
|
226
507
|
}
|
|
227
508
|
if (phase.status === 'completed') {
|
|
228
509
|
completed.add(phase.config.name);
|
|
510
|
+
// Confidence-gated phase transition (Feature 3)
|
|
511
|
+
if (this.graphAnalyzer) {
|
|
512
|
+
try {
|
|
513
|
+
const reflection = this.reflectionEngine.reflect(phase);
|
|
514
|
+
const patternStats = this.graphLearner?.getPatternStats(orchestration.config.pattern)?.[0];
|
|
515
|
+
const gateOutputs = this.phaseOutputs.get(phase.config.name) || [];
|
|
516
|
+
const gate = this.graphAnalyzer.evaluatePhaseConfidence({
|
|
517
|
+
kind: phase.config.kind ?? 'implement',
|
|
518
|
+
agentCount: phase.agents.length,
|
|
519
|
+
outputs: gateOutputs,
|
|
520
|
+
usage: phase.usage,
|
|
521
|
+
}, { confidence: reflection.confidence }, patternStats
|
|
522
|
+
? { successRate: patternStats.successRate, avgDurationMs: patternStats.avgDurationMs }
|
|
523
|
+
: undefined);
|
|
524
|
+
if (gate.result === 'halt') {
|
|
525
|
+
this.log.warn(`Phase gate HALT: ${gate.reason}`);
|
|
526
|
+
orchestration.status = 'failed';
|
|
527
|
+
}
|
|
528
|
+
if (gate.result === 'warn') {
|
|
529
|
+
this.log.warn(`Phase gate WARNING: ${gate.reason}`);
|
|
530
|
+
this.bus.emit('system:warning', {
|
|
531
|
+
type: 'gate-warning',
|
|
532
|
+
orchestrationId: orchestration.config.id,
|
|
533
|
+
phase: phase.config.name,
|
|
534
|
+
reason: gate.reason,
|
|
535
|
+
}, 'engine');
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
/* Graph gate evaluation failed — continue */
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
// Trajectory prediction after each completed phase
|
|
543
|
+
if (this.trajectoryPredictor) {
|
|
544
|
+
try {
|
|
545
|
+
const features = this.trajectoryPredictor.extractFeatures(orchestration, phase);
|
|
546
|
+
const pred = this.trajectoryPredictor.predict(features);
|
|
547
|
+
if (pred.successProbability < 0.3 && pred.confidence !== 'low') {
|
|
548
|
+
this.log.warn(`Trajectory predictor: ${(pred.successProbability * 100).toFixed(0)}% success probability (${pred.trajectory})`);
|
|
549
|
+
this.bus.emit('system:warning', { type: 'trajectory-risk', ...pred }, 'engine');
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
catch (e) {
|
|
553
|
+
this.log.debug(`Trajectory prediction failed: ${e}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
229
556
|
}
|
|
230
557
|
else if (phase.status === 'failed') {
|
|
231
558
|
orchestration.status = 'failed';
|
|
232
559
|
}
|
|
233
560
|
}
|
|
561
|
+
// Trajectory forecast over remaining phases
|
|
562
|
+
if (this.trajectoryPredictor) {
|
|
563
|
+
try {
|
|
564
|
+
const forecastCompleted = orchestration.phases
|
|
565
|
+
.filter((p) => p.status === 'completed')
|
|
566
|
+
.map((p) => ({
|
|
567
|
+
name: p.config.name,
|
|
568
|
+
status: p.status,
|
|
569
|
+
tokens: p.usage?.totalTokens || 0,
|
|
570
|
+
durationMs: p.usage?.durationMs || 0,
|
|
571
|
+
confidence: 'medium',
|
|
572
|
+
}));
|
|
573
|
+
const forecastRemaining = orchestration.phases
|
|
574
|
+
.filter((p) => p.status === 'pending')
|
|
575
|
+
.map((p) => p.config.name);
|
|
576
|
+
if (forecastRemaining.length > 0) {
|
|
577
|
+
const forecast = this.trajectoryPredictor.predictTrajectory(orchestration.config.id, forecastCompleted, forecastRemaining, orchestration.config.pattern);
|
|
578
|
+
if (forecast.overallSuccessProb < 0.3) {
|
|
579
|
+
this.log.warn(`Trajectory forecast: ${Math.round(forecast.overallSuccessProb * 100)}% success probability`);
|
|
580
|
+
this.bus.emit('system:warning', { type: 'trajectory-forecast', forecast }, 'engine');
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
this.log.debug(`Trajectory forecast failed: ${e}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
234
588
|
if (orchestration.status === 'failed')
|
|
235
589
|
break;
|
|
236
590
|
// Check cost budget after each batch of phases
|
|
@@ -264,6 +618,50 @@ export class SwarmEngine {
|
|
|
264
618
|
error: error instanceof Error ? error.message : String(error),
|
|
265
619
|
});
|
|
266
620
|
}
|
|
621
|
+
// Post-failure causal analysis (Feature 1: Causal Inference)
|
|
622
|
+
// Note: suggestIntervention scans all historical orchestrations — skip when graph is large
|
|
623
|
+
if (orchestration.status === 'failed' && this.causalEngine && this.executionGraph) {
|
|
624
|
+
try {
|
|
625
|
+
const orchCount = this.executionGraph.getNodesByType('orchestration').length;
|
|
626
|
+
if (orchCount <= 200) {
|
|
627
|
+
const suggestions = this.causalEngine.suggestIntervention(orchestration.config.id);
|
|
628
|
+
if (suggestions.length > 0) {
|
|
629
|
+
this.log.info(`Causal analysis suggests: ${suggestions[0].reasoning}`);
|
|
630
|
+
this.bus.emit('system:warning', { type: 'causal-suggestion', suggestions }, 'engine');
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
catch (e) {
|
|
635
|
+
this.log.debug(`Causal suggestion failed: ${e}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
// Self-audit on failure: red-team the engine's own ML subsystems
|
|
639
|
+
// Skipped in mock mode and when insufficient historical data to avoid test overhead
|
|
640
|
+
if (orchestration.status === 'failed' && this.metaAdversarial && !this.options.mock) {
|
|
641
|
+
try {
|
|
642
|
+
const orchCount = this.executionGraph?.getNodesByType('orchestration').length ?? 0;
|
|
643
|
+
if (orchCount >= 5) {
|
|
644
|
+
const report = this.metaAdversarial.runFullAudit({
|
|
645
|
+
patternSelector: this.metaSelector ?? undefined,
|
|
646
|
+
dropout: this.predictiveDropout ?? undefined,
|
|
647
|
+
causal: this.causalEngine
|
|
648
|
+
? {
|
|
649
|
+
estimateCausalEffect: (t, tv, cv) =>
|
|
650
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
651
|
+
this.causalEngine.estimateCausalEffect(t, tv, cv),
|
|
652
|
+
}
|
|
653
|
+
: undefined,
|
|
654
|
+
});
|
|
655
|
+
if (report.overallRisk !== 'low') {
|
|
656
|
+
this.log.warn(`Self-audit found ${report.vulnerabilities.length} ML vulnerabilities (risk: ${report.overallRisk})`);
|
|
657
|
+
this.bus.emit('system:warning', { type: 'self-audit', overallRisk: report.overallRisk, recommendations: report.recommendations }, 'engine');
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
catch (e) {
|
|
662
|
+
this.log.debug(`Meta-adversarial audit failed: ${e}`);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
267
665
|
orchestration.completedAt = new Date();
|
|
268
666
|
continuousVerifier.stop();
|
|
269
667
|
// Clean up shared context files
|
|
@@ -276,6 +674,15 @@ export class SwarmEngine {
|
|
|
276
674
|
this.sharedContextFiles = [];
|
|
277
675
|
// Record learning outcomes (traces, heuristics, model routing, autonomy)
|
|
278
676
|
await this.learningEngine.recordOutcome(orchestration, config, this.reflections);
|
|
677
|
+
// Train trajectory predictor on final outcome
|
|
678
|
+
if (this.trajectoryPredictor) {
|
|
679
|
+
try {
|
|
680
|
+
this.trajectoryPredictor.recordOutcome(orchestration, orchestration.status === 'completed');
|
|
681
|
+
}
|
|
682
|
+
catch (e) {
|
|
683
|
+
this.log.debug(`Trajectory training failed: ${e}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
279
686
|
// Compute total usage
|
|
280
687
|
orchestration.usage = orchestration.phases.reduce((total, phase) => mergeUsageStats(total, phase.usage), emptyUsageStats());
|
|
281
688
|
this.bus.emit(orchestration.status === 'completed' ? 'orchestration:complete' : 'orchestration:failed', {
|
|
@@ -382,6 +789,56 @@ export class SwarmEngine {
|
|
|
382
789
|
// Inject learned heuristics and past solutions into the plan
|
|
383
790
|
this.learningEngine.injectHeuristics(rewrittenPlan);
|
|
384
791
|
this.learningEngine.injectSolutions(rewrittenPlan);
|
|
792
|
+
// Meta-pattern recommendation (Feature 4: Meta-Pattern Selector)
|
|
793
|
+
if (this.metaSelector) {
|
|
794
|
+
try {
|
|
795
|
+
const recommendation = this.metaSelector.selectPattern(task);
|
|
796
|
+
if (recommendation.confidence > 0.6) {
|
|
797
|
+
this.log.info(`Meta-selector recommends: ${recommendation.pattern} (${Math.round(recommendation.confidence * 100)}% confidence)`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
catch (e) {
|
|
801
|
+
this.log.debug(`Meta-pattern selection failed: ${e}`);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
// Task discovery: log discovered backlog items from historical failure patterns (skip in mock mode)
|
|
805
|
+
if (this.taskDiscovery && !this.options.mock) {
|
|
806
|
+
try {
|
|
807
|
+
const backlog = this.taskDiscovery.generateBacklog(3);
|
|
808
|
+
if (backlog.length > 0) {
|
|
809
|
+
this.log.info(`Task discovery: ${backlog.length} actionable items found (top: ${backlog[0]?.title})`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
catch (e) {
|
|
813
|
+
this.log.debug(`Task discovery failed: ${e}`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// Pattern synthesis: log insights from topology analysis (skip in mock mode)
|
|
817
|
+
if (this.patternSynthesizer && !this.options.mock) {
|
|
818
|
+
try {
|
|
819
|
+
const insights = this.patternSynthesizer.analyzeTopologyDiffs(3);
|
|
820
|
+
if (insights.length > 0) {
|
|
821
|
+
const top = insights[0];
|
|
822
|
+
this.log.info(`Pattern synthesis top insight: "${top.feature}" (correlation: ${top.successCorrelation.toFixed(2)}, n=${top.sampleSize})`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
catch (e) {
|
|
826
|
+
this.log.debug(`Pattern synthesis failed: ${e}`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
// Pattern synthesis: suggest novel pattern from historical success topology
|
|
830
|
+
if (this.patternSynthesizer && !this.options.mock) {
|
|
831
|
+
try {
|
|
832
|
+
const synthesized = this.patternSynthesizer.synthesizePattern(task);
|
|
833
|
+
if (synthesized && synthesized.confidence > 0.5) {
|
|
834
|
+
this.log.info(`Pattern synthesis suggests: ${synthesized.name} (${Math.round(synthesized.confidence * 100)}% confidence)`);
|
|
835
|
+
this.bus.emit('system:warning', { type: 'pattern-synthesis', pattern: synthesized }, 'engine');
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
catch (e) {
|
|
839
|
+
this.log.debug(`Pattern synthesis suggestion failed: ${e}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
385
842
|
// Search over candidate plans to find the best one
|
|
386
843
|
const searcher = new PlanSearcher(this.costModel, this.traceStore ?? undefined);
|
|
387
844
|
const candidates = searcher.search(rewrittenPlan, {
|
|
@@ -400,6 +857,24 @@ export class SwarmEngine {
|
|
|
400
857
|
quality: c.cost.qualityScore,
|
|
401
858
|
}));
|
|
402
859
|
}
|
|
860
|
+
// Emit plan preview event so listeners can inspect before execution
|
|
861
|
+
try {
|
|
862
|
+
this.bus.emit('system:warning', {
|
|
863
|
+
type: 'plan-preview',
|
|
864
|
+
pattern: patternName,
|
|
865
|
+
phases: bestPlan.phases.map((p) => ({
|
|
866
|
+
name: p.name,
|
|
867
|
+
kind: p.kind,
|
|
868
|
+
agentCount: p.agents?.length || 0,
|
|
869
|
+
agents: p.agents?.map((a) => ({ type: a.type, model: a.model })) || [],
|
|
870
|
+
})),
|
|
871
|
+
estimatedCost: bestPlan.estimates,
|
|
872
|
+
task: task.slice(0, 200),
|
|
873
|
+
}, 'engine');
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
/* plan preview emit failed — non-critical */
|
|
877
|
+
}
|
|
403
878
|
return bestPlan;
|
|
404
879
|
}
|
|
405
880
|
/**
|
|
@@ -417,9 +892,89 @@ export class SwarmEngine {
|
|
|
417
892
|
this.bus.emit('system:warning', { type: 'agent-dropout', ...dropout }, 'adaptive');
|
|
418
893
|
}
|
|
419
894
|
}
|
|
895
|
+
// Predictive dropout from ML model (Feature 5: Predictive Dropout)
|
|
896
|
+
if (this.predictiveDropout && phase.config.agents.length > 1) {
|
|
897
|
+
try {
|
|
898
|
+
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 || '');
|
|
899
|
+
const toDrop = recommendations.filter((r) => r.shouldDropOut);
|
|
900
|
+
if (toDrop.length > 0) {
|
|
901
|
+
this.log.info(`Predictive dropout: skipping ${toDrop.map((d) => d.agentType).join(', ')}`);
|
|
902
|
+
phase.config.agents = phase.config.agents.filter((a) => !toDrop.some((d) => d.agentType === a.type));
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch (e) {
|
|
906
|
+
this.log.debug(`Predictive dropout failed: ${e}`);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
// Feature 5: Diff-based review context — inject git diff before review phases
|
|
910
|
+
if (phase.config.kind === 'review' && !this.options.mock) {
|
|
911
|
+
try {
|
|
912
|
+
const cwd = this.options.cwd || process.cwd();
|
|
913
|
+
const { stdout: diff } = await execFileAsync('git', ['diff', '--stat', 'HEAD'], {
|
|
914
|
+
cwd,
|
|
915
|
+
timeout: 10000,
|
|
916
|
+
encoding: 'utf-8',
|
|
917
|
+
});
|
|
918
|
+
const trimmedDiff = diff.slice(0, 3000);
|
|
919
|
+
if (trimmedDiff.trim()) {
|
|
920
|
+
const { stdout: diffDetail } = await execFileAsync('git', ['diff', 'HEAD'], {
|
|
921
|
+
cwd,
|
|
922
|
+
timeout: 10000,
|
|
923
|
+
encoding: 'utf-8',
|
|
924
|
+
});
|
|
925
|
+
const existing = this.phaseOutputs.get(phase.config.name) || [];
|
|
926
|
+
existing.unshift(`## Git Diff Summary\n\`\`\`\n${trimmedDiff}\n\`\`\`\n\n## Detailed Changes\n\`\`\`diff\n${diffDetail.slice(0, 5000)}\n\`\`\``);
|
|
927
|
+
this.phaseOutputs.set(phase.config.name, existing);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
catch (e) {
|
|
931
|
+
this.log.debug(`Diff context failed: ${e}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
// Feature 6: Pre-review lint wiring — run lint/typecheck before review phases
|
|
935
|
+
if (phase.config.kind === 'review' && this.verifier && !this.options.mock) {
|
|
936
|
+
try {
|
|
937
|
+
const lintResults = await this.verifier.verify();
|
|
938
|
+
const failures = lintResults.filter((r) => !r.passed);
|
|
939
|
+
if (failures.length > 0) {
|
|
940
|
+
const lintOutput = failures.map((f) => `${f.step}: ${f.output?.slice(0, 500)}`).join('\n');
|
|
941
|
+
const existing = this.phaseOutputs.get(phase.config.name) || [];
|
|
942
|
+
existing.unshift(`## Pre-Review Lint/Typecheck Results\n${lintOutput}`);
|
|
943
|
+
this.phaseOutputs.set(phase.config.name, existing);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
catch (e) {
|
|
947
|
+
this.log.debug(`Pre-review checks failed: ${e}`);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
420
950
|
const maxRetries = phase.config.maxRetries ?? 2;
|
|
951
|
+
// Smart phase output routing: filter prior outputs by relevance instead of passing everything
|
|
952
|
+
let filteredOutputs = this.phaseOutputs;
|
|
953
|
+
if (phase.config.kind === 'review') {
|
|
954
|
+
// Review phase only needs implement/integrate/test outputs, not research
|
|
955
|
+
const reviewFiltered = new Map();
|
|
956
|
+
for (const [phaseName, outputs] of this.phaseOutputs) {
|
|
957
|
+
if (phaseName.includes('implement') || phaseName.includes('integrate') || phaseName.includes('test')) {
|
|
958
|
+
reviewFiltered.set(phaseName, outputs);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
// Fall back to all outputs if nothing matched
|
|
962
|
+
if (reviewFiltered.size > 0)
|
|
963
|
+
filteredOutputs = reviewFiltered;
|
|
964
|
+
}
|
|
965
|
+
if (phase.config.kind === 'test') {
|
|
966
|
+
// Test phase only needs implement outputs
|
|
967
|
+
const testFiltered = new Map();
|
|
968
|
+
for (const [phaseName, outputs] of this.phaseOutputs) {
|
|
969
|
+
if (phaseName.includes('implement')) {
|
|
970
|
+
testFiltered.set(phaseName, outputs);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (testFiltered.size > 0)
|
|
974
|
+
filteredOutputs = testFiltered;
|
|
975
|
+
}
|
|
421
976
|
const runContext = {
|
|
422
|
-
phaseOutputs:
|
|
977
|
+
phaseOutputs: filteredOutputs,
|
|
423
978
|
livingSpec: this.livingSpec,
|
|
424
979
|
sharedContextFiles: this.sharedContextFiles,
|
|
425
980
|
cwd: this.options.cwd,
|
|
@@ -467,9 +1022,16 @@ export class SwarmEngine {
|
|
|
467
1022
|
}
|
|
468
1023
|
}
|
|
469
1024
|
else {
|
|
470
|
-
// Run agents sequentially (with retry)
|
|
1025
|
+
// Run agents sequentially (with retry) — accumulate intra-phase outputs for live re-routing (Feature 1)
|
|
1026
|
+
const intraPhaseOutputs = new Map();
|
|
471
1027
|
for (const assignment of phase.config.agents) {
|
|
472
|
-
await this.agentRunner.executeAgentWithRetry(assignment, phase, orchestration, maxRetries, runContext);
|
|
1028
|
+
const agentInstance = await this.agentRunner.executeAgentWithRetry(assignment, phase, orchestration, maxRetries, { ...runContext, intraPhaseOutputs });
|
|
1029
|
+
// After agent completes, add its output to intra-phase buffer for next sequential agent
|
|
1030
|
+
if (agentInstance?.result?.output) {
|
|
1031
|
+
const existing = intraPhaseOutputs.get(phase.config.name) || [];
|
|
1032
|
+
existing.push(agentInstance.result.output);
|
|
1033
|
+
intraPhaseOutputs.set(phase.config.name, existing);
|
|
1034
|
+
}
|
|
473
1035
|
// Check phase token budget after each agent
|
|
474
1036
|
if (phase.config.tokenBudget) {
|
|
475
1037
|
const phaseTokens = phase.agents.reduce((sum, a) => sum + a.usage.totalTokens, 0);
|
|
@@ -490,8 +1052,23 @@ export class SwarmEngine {
|
|
|
490
1052
|
}
|
|
491
1053
|
phase.completedAt = new Date();
|
|
492
1054
|
phase.usage = phase.agents.reduce((total, agent) => mergeUsageStats(total, agent.usage), emptyUsageStats());
|
|
1055
|
+
// Record review feedback into the knowledge graph (Feature 7)
|
|
1056
|
+
if (phase.config.kind === 'review' && this.reviewFeedback) {
|
|
1057
|
+
for (const agent of phase.agents) {
|
|
1058
|
+
if (agent.result?.output) {
|
|
1059
|
+
try {
|
|
1060
|
+
this.reviewFeedback.recordFindings(orchestration.config.id, phase.config.name, agent.result.output, agent.config.name);
|
|
1061
|
+
const findings = parseReviewOutput(agent.result.output, agent.config.name);
|
|
1062
|
+
this.reviewFeedback.adjustContextWeights(orchestration.config.id, findings);
|
|
1063
|
+
}
|
|
1064
|
+
catch (e) {
|
|
1065
|
+
this.log.debug(`Review feedback recording failed: ${e}`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
493
1070
|
// Run verification after implementation phases
|
|
494
|
-
if (phase.config.kind === 'implement' || phase.config.kind === 'integrate') {
|
|
1071
|
+
if ((phase.config.kind === 'implement' || phase.config.kind === 'integrate') && !this.options.mock) {
|
|
495
1072
|
const results = await this.verifier.verify();
|
|
496
1073
|
const summary = Verifier.summarize(results);
|
|
497
1074
|
this.log.info(`Verification: ${summary}`, { phase: phase.config.name });
|
|
@@ -520,10 +1097,18 @@ export class SwarmEngine {
|
|
|
520
1097
|
agents: phase.agents.length,
|
|
521
1098
|
usage: phase.usage,
|
|
522
1099
|
});
|
|
523
|
-
// Collect agent outputs for inter-phase context
|
|
1100
|
+
// Collect agent outputs for inter-phase context (structured handoff)
|
|
524
1101
|
const outputs = phase.agents
|
|
525
1102
|
.filter((a) => a.result?.output)
|
|
526
|
-
.map((a) =>
|
|
1103
|
+
.map((a) => {
|
|
1104
|
+
try {
|
|
1105
|
+
const handoff = HandoffParser.parseAgentOutput(a.result.output);
|
|
1106
|
+
return HandoffParser.serializeForNextPhase([{ agentName: a.config.name, handoff }], 4000);
|
|
1107
|
+
}
|
|
1108
|
+
catch {
|
|
1109
|
+
return `[${a.config.name}]: ${a.result.output.slice(0, 2000)}`;
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
527
1112
|
// Update living spec with phase outputs
|
|
528
1113
|
if (this.livingSpec) {
|
|
529
1114
|
this.livingSpec = updateSpecFromPhase(this.livingSpec, phase.config.name, outputs);
|
|
@@ -635,6 +1220,10 @@ export class SwarmEngine {
|
|
|
635
1220
|
}
|
|
636
1221
|
catch { }
|
|
637
1222
|
this.learningEngine.close();
|
|
1223
|
+
try {
|
|
1224
|
+
this.executionGraph?.close();
|
|
1225
|
+
}
|
|
1226
|
+
catch { }
|
|
638
1227
|
closeDatabase();
|
|
639
1228
|
this.removeSignalHandlers();
|
|
640
1229
|
}
|
|
@@ -660,6 +1249,9 @@ export class SwarmEngine {
|
|
|
660
1249
|
get patternRegistry() {
|
|
661
1250
|
return this.patterns;
|
|
662
1251
|
}
|
|
1252
|
+
get graph() {
|
|
1253
|
+
return this.executionGraph;
|
|
1254
|
+
}
|
|
663
1255
|
// ─── Helpers ───────────────────────────────────────────────────
|
|
664
1256
|
tryInit(factory, name) {
|
|
665
1257
|
try {
|