swarm-engine 1.1.0 → 1.3.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/CLAUDE.md +1 -1
- package/README.md +102 -25
- package/agents/accessibility-reviewer.md +2 -0
- package/agents/api-contract-reviewer.md +2 -0
- package/agents/concurrency-reviewer.md +2 -0
- package/agents/data-integrity-reviewer.md +1 -0
- package/agents/debugger.md +2 -0
- package/agents/dependency-reviewer.md +2 -0
- package/agents/devils-advocate.md +1 -0
- package/agents/documentation-reviewer.md +2 -0
- package/agents/documenter.md +13 -0
- package/agents/error-handling-reviewer.md +2 -0
- package/agents/grounding.md +3 -2
- package/agents/guardian.md +3 -0
- package/agents/implementer.md +2 -0
- package/agents/integrator.md +3 -0
- package/agents/judge.md +2 -0
- package/agents/librarian.md +4 -1
- package/agents/orchestrator.md +3 -0
- package/agents/performance-reviewer.md +2 -0
- package/agents/planner.md +2 -0
- package/agents/refactorer.md +3 -0
- package/agents/reviewer.md +1 -0
- package/agents/security-reviewer.md +1 -0
- package/agents/sentinel.md +3 -0
- package/agents/tester.md +2 -0
- package/agents/testing-reviewer.md +2 -0
- package/commands/diff-review.md +27 -15
- package/commands/discover.md +102 -0
- package/commands/dynamic.md +136 -0
- package/commands/fix-pr.md +30 -24
- package/commands/postmortem.md +106 -0
- package/commands/red-team.md +41 -26
- package/commands/research.md +22 -1
- package/commands/review-cycle.md +38 -20
- package/commands/spike.md +108 -0
- package/commands/swarm.md +68 -60
- package/commands/tdd.md +44 -24
- package/dist/cli/commands/acp.d.ts.map +1 -1
- package/dist/cli/commands/acp.js +12 -2
- package/dist/cli/commands/acp.js.map +1 -1
- package/dist/cli/commands/agents.d.ts.map +1 -1
- package/dist/cli/commands/agents.js +16 -13
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/completions.d.ts.map +1 -1
- package/dist/cli/commands/completions.js +21 -9
- package/dist/cli/commands/completions.js.map +1 -1
- package/dist/cli/commands/compound.d.ts.map +1 -1
- package/dist/cli/commands/compound.js +1 -2
- package/dist/cli/commands/compound.js.map +1 -1
- package/dist/cli/commands/configure.d.ts.map +1 -1
- package/dist/cli/commands/configure.js +24 -8
- package/dist/cli/commands/configure.js.map +1 -1
- package/dist/cli/commands/convert.d.ts +1 -1
- package/dist/cli/commands/convert.d.ts.map +1 -1
- package/dist/cli/commands/convert.js +22 -48
- package/dist/cli/commands/convert.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +1 -3
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +17 -7
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +1 -1
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/learn.js +6 -6
- package/dist/cli/commands/learn.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +1 -2
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/memory.d.ts.map +1 -1
- package/dist/cli/commands/memory.js +1 -2
- package/dist/cli/commands/memory.js.map +1 -1
- package/dist/cli/commands/orchestrate.d.ts.map +1 -1
- package/dist/cli/commands/orchestrate.js +20 -7
- package/dist/cli/commands/orchestrate.js.map +1 -1
- package/dist/cli/commands/plan.d.ts.map +1 -1
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/cli/commands/plugin.d.ts.map +1 -1
- package/dist/cli/commands/plugin.js +8 -5
- package/dist/cli/commands/plugin.js.map +1 -1
- package/dist/cli/commands/resume.js +1 -1
- package/dist/cli/commands/resume.js.map +1 -1
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +20 -6
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/share.d.ts.map +1 -1
- package/dist/cli/commands/share.js +6 -1
- package/dist/cli/commands/share.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +15 -7
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/template.d.ts.map +1 -1
- package/dist/cli/commands/template.js +14 -6
- package/dist/cli/commands/template.js.map +1 -1
- package/dist/cli/commands/vault.d.ts.map +1 -1
- package/dist/cli/commands/vault.js +14 -9
- package/dist/cli/commands/vault.js.map +1 -1
- package/dist/cli/commands/verify.d.ts.map +1 -1
- package/dist/cli/commands/verify.js +2 -2
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/commands/watch.js +1 -1
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/cli/index.js +14 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/core/checkpoint.js +1 -1
- package/dist/core/checkpoint.js.map +1 -1
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +9 -3
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/patterns.d.ts.map +1 -1
- package/dist/core/patterns.js +31 -8
- package/dist/core/patterns.js.map +1 -1
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +21 -10
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +10 -6
- package/dist/core/registry.js.map +1 -1
- package/dist/core/snapshots.d.ts.map +1 -1
- package/dist/core/snapshots.js +17 -5
- package/dist/core/snapshots.js.map +1 -1
- package/dist/core/types.d.ts +3 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +68 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -4
- package/dist/index.js.map +1 -1
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +39 -24
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/schema.d.ts +1 -0
- package/dist/memory/schema.d.ts.map +1 -1
- package/dist/memory/schema.js +20 -19
- package/dist/memory/schema.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/runtime/acp.d.ts.map +1 -1
- package/dist/runtime/acp.js +71 -41
- package/dist/runtime/acp.js.map +1 -1
- package/dist/runtime/adaptive.d.ts.map +1 -1
- package/dist/runtime/adaptive.js +30 -31
- package/dist/runtime/adaptive.js.map +1 -1
- package/dist/runtime/agent-runner.d.ts +52 -0
- package/dist/runtime/agent-runner.d.ts.map +1 -0
- package/dist/runtime/agent-runner.js +156 -0
- package/dist/runtime/agent-runner.js.map +1 -0
- package/dist/runtime/autonomy.d.ts +1 -0
- package/dist/runtime/autonomy.d.ts.map +1 -1
- package/dist/runtime/autonomy.js +37 -19
- package/dist/runtime/autonomy.js.map +1 -1
- package/dist/runtime/backends/claude.d.ts.map +1 -1
- package/dist/runtime/backends/claude.js +2 -2
- package/dist/runtime/backends/claude.js.map +1 -1
- package/dist/runtime/backends/codex.d.ts.map +1 -1
- package/dist/runtime/backends/codex.js +8 -11
- package/dist/runtime/backends/codex.js.map +1 -1
- package/dist/runtime/backends/gemini.d.ts.map +1 -1
- package/dist/runtime/backends/gemini.js +11 -7
- package/dist/runtime/backends/gemini.js.map +1 -1
- package/dist/runtime/backends/index.js +1 -1
- package/dist/runtime/backends/index.js.map +1 -1
- package/dist/runtime/backends/mock.d.ts.map +1 -1
- package/dist/runtime/backends/mock.js +1 -1
- package/dist/runtime/backends/mock.js.map +1 -1
- package/dist/runtime/backends/vercel-ai.d.ts.map +1 -1
- package/dist/runtime/backends/vercel-ai.js +41 -9
- package/dist/runtime/backends/vercel-ai.js.map +1 -1
- package/dist/runtime/cache-optimizer.d.ts.map +1 -1
- package/dist/runtime/cache-optimizer.js +3 -9
- package/dist/runtime/cache-optimizer.js.map +1 -1
- package/dist/runtime/cascade.d.ts.map +1 -1
- package/dist/runtime/cascade.js +34 -7
- package/dist/runtime/cascade.js.map +1 -1
- package/dist/runtime/chunker.d.ts.map +1 -1
- package/dist/runtime/chunker.js +12 -6
- package/dist/runtime/chunker.js.map +1 -1
- package/dist/runtime/compounder.d.ts +1 -1
- package/dist/runtime/compounder.d.ts.map +1 -1
- package/dist/runtime/compounder.js +30 -11
- package/dist/runtime/compounder.js.map +1 -1
- package/dist/runtime/cost-model.d.ts.map +1 -1
- package/dist/runtime/cost-model.js +1 -1
- package/dist/runtime/cost-model.js.map +1 -1
- package/dist/runtime/database.d.ts +16 -0
- package/dist/runtime/database.d.ts.map +1 -0
- package/dist/runtime/database.js +39 -0
- package/dist/runtime/database.js.map +1 -0
- package/dist/runtime/distiller.d.ts.map +1 -1
- package/dist/runtime/distiller.js +6 -3
- package/dist/runtime/distiller.js.map +1 -1
- package/dist/runtime/engine.d.ts +7 -9
- package/dist/runtime/engine.d.ts.map +1 -1
- package/dist/runtime/engine.js +129 -394
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/executor.d.ts +1 -2
- package/dist/runtime/executor.d.ts.map +1 -1
- package/dist/runtime/executor.js +45 -14
- package/dist/runtime/executor.js.map +1 -1
- package/dist/runtime/heuristics.d.ts +1 -0
- package/dist/runtime/heuristics.d.ts.map +1 -1
- package/dist/runtime/heuristics.js +44 -22
- package/dist/runtime/heuristics.js.map +1 -1
- package/dist/runtime/learning-engine.d.ts +51 -0
- package/dist/runtime/learning-engine.d.ts.map +1 -0
- package/dist/runtime/learning-engine.js +209 -0
- package/dist/runtime/learning-engine.js.map +1 -0
- package/dist/runtime/living-spec.js +3 -3
- package/dist/runtime/living-spec.js.map +1 -1
- package/dist/runtime/lsp.d.ts.map +1 -1
- package/dist/runtime/lsp.js +41 -14
- package/dist/runtime/lsp.js.map +1 -1
- package/dist/runtime/mcp.d.ts.map +1 -1
- package/dist/runtime/mcp.js +56 -19
- package/dist/runtime/mcp.js.map +1 -1
- package/dist/runtime/model-router.d.ts +1 -0
- package/dist/runtime/model-router.d.ts.map +1 -1
- package/dist/runtime/model-router.js +37 -21
- package/dist/runtime/model-router.js.map +1 -1
- package/dist/runtime/panes.d.ts.map +1 -1
- package/dist/runtime/panes.js +50 -49
- package/dist/runtime/panes.js.map +1 -1
- package/dist/runtime/plan-search.js +2 -2
- package/dist/runtime/plan-search.js.map +1 -1
- package/dist/runtime/plugins.d.ts +1 -1
- package/dist/runtime/plugins.d.ts.map +1 -1
- package/dist/runtime/plugins.js +63 -47
- package/dist/runtime/plugins.js.map +1 -1
- package/dist/runtime/reflexion.d.ts.map +1 -1
- package/dist/runtime/reflexion.js +4 -8
- package/dist/runtime/reflexion.js.map +1 -1
- package/dist/runtime/review-schema.d.ts.map +1 -1
- package/dist/runtime/review-schema.js +12 -12
- package/dist/runtime/review-schema.js.map +1 -1
- package/dist/runtime/rewriter.d.ts.map +1 -1
- package/dist/runtime/rewriter.js +29 -9
- package/dist/runtime/rewriter.js.map +1 -1
- package/dist/runtime/sharing.d.ts +1 -1
- package/dist/runtime/sharing.d.ts.map +1 -1
- package/dist/runtime/sharing.js +55 -27
- package/dist/runtime/sharing.js.map +1 -1
- package/dist/runtime/stats.d.ts +1 -0
- package/dist/runtime/stats.d.ts.map +1 -1
- package/dist/runtime/stats.js +40 -24
- package/dist/runtime/stats.js.map +1 -1
- package/dist/runtime/templates.d.ts.map +1 -1
- package/dist/runtime/templates.js +2 -2
- package/dist/runtime/templates.js.map +1 -1
- package/dist/runtime/traces.d.ts +1 -0
- package/dist/runtime/traces.d.ts.map +1 -1
- package/dist/runtime/traces.js +50 -28
- package/dist/runtime/traces.js.map +1 -1
- package/dist/runtime/verifier.d.ts.map +1 -1
- package/dist/runtime/verifier.js +12 -6
- package/dist/runtime/verifier.js.map +1 -1
- package/dist/runtime/worktree.d.ts.map +1 -1
- package/dist/runtime/worktree.js +35 -18
- package/dist/runtime/worktree.js.map +1 -1
- package/dist/tui/dashboard.d.ts.map +1 -1
- package/dist/tui/dashboard.js +20 -16
- package/dist/tui/dashboard.js.map +1 -1
- package/dist/tui/progress.d.ts +2 -0
- package/dist/tui/progress.d.ts.map +1 -1
- package/dist/tui/progress.js +105 -33
- package/dist/tui/progress.js.map +1 -1
- package/dist/tui/renderer.d.ts.map +1 -1
- package/dist/tui/renderer.js.map +1 -1
- package/dist/utils/compact-format.js +1 -1
- package/dist/utils/compact-format.js.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/env.js +19 -5
- package/dist/utils/env.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +3 -7
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/output.d.ts.map +1 -1
- package/dist/utils/output.js +6 -2
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/project-config.d.ts +18 -0
- package/dist/utils/project-config.d.ts.map +1 -1
- package/dist/utils/project-config.js +14 -6
- package/dist/utils/project-config.js.map +1 -1
- package/dist/utils/schemas.d.ts.map +1 -1
- package/dist/utils/schemas.js +12 -12
- package/dist/utils/schemas.js.map +1 -1
- package/dist/utils/terminal.d.ts.map +1 -1
- package/dist/utils/terminal.js +18 -7
- package/dist/utils/terminal.js.map +1 -1
- package/dist/utils/tiers.d.ts.map +1 -1
- package/dist/utils/tiers.js +14 -6
- package/dist/utils/tiers.js.map +1 -1
- package/package.json +14 -3
- package/skills/swarm-output-style/SKILL.md +114 -46
package/dist/runtime/engine.js
CHANGED
|
@@ -19,14 +19,13 @@ import { KnowledgeCompounder } from './compounder.js';
|
|
|
19
19
|
import { ModelRouter } from './model-router.js';
|
|
20
20
|
import { AutonomyTracker } from './autonomy.js';
|
|
21
21
|
import { ContinuousVerifier } from './verifier.js';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { createLivingSpec, updateSpecFromPhase, formatLivingSpec } from './living-spec.js';
|
|
22
|
+
import { estimateCacheSavings } from './cache-optimizer.js';
|
|
23
|
+
import { createLivingSpec, updateSpecFromPhase } from './living-spec.js';
|
|
25
24
|
import { estimateTokens } from '../utils/tokens.js';
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
25
|
+
import { unlinkSync } from 'fs';
|
|
26
|
+
import { AgentRunner } from './agent-runner.js';
|
|
27
|
+
import { LearningEngine } from './learning-engine.js';
|
|
28
|
+
import { closeDatabase } from './database.js';
|
|
30
29
|
/**
|
|
31
30
|
* The Swarm Orchestration Engine.
|
|
32
31
|
*
|
|
@@ -60,6 +59,8 @@ export class SwarmEngine {
|
|
|
60
59
|
signalHandlers = [];
|
|
61
60
|
sharedContextFiles = [];
|
|
62
61
|
livingSpec = null;
|
|
62
|
+
agentRunner;
|
|
63
|
+
learningEngine;
|
|
63
64
|
constructor(options) {
|
|
64
65
|
this.options = options;
|
|
65
66
|
this.registry = options.registry;
|
|
@@ -85,43 +86,36 @@ export class SwarmEngine {
|
|
|
85
86
|
this.stats.attachToEventBus(this.bus, undefined);
|
|
86
87
|
this.costModel = new CostModel(this.stats);
|
|
87
88
|
this.reflectionEngine = new ReflectionEngine();
|
|
88
|
-
|
|
89
|
-
this.traceStore = new TraceStore();
|
|
90
|
-
}
|
|
91
|
-
catch (e) {
|
|
92
|
-
this.log.warn('TraceStore init failed', { error: e instanceof Error ? e.message : e });
|
|
93
|
-
}
|
|
89
|
+
this.traceStore = this.tryInit(() => new TraceStore(), 'TraceStore');
|
|
94
90
|
this.replanner = new AdaptiveReplanner(options.logger, this.traceStore ?? undefined);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.log.warn('HeuristicStore init failed', { error: e instanceof Error ? e.message : e });
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
this.modelRouter = new ModelRouter();
|
|
103
|
-
}
|
|
104
|
-
catch (e) {
|
|
105
|
-
this.log.warn('ModelRouter init failed', { error: e instanceof Error ? e.message : e });
|
|
106
|
-
}
|
|
107
|
-
try {
|
|
108
|
-
this.autonomyTracker = new AutonomyTracker();
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
this.log.warn('AutonomyTracker init failed', { error: e instanceof Error ? e.message : e });
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
this.compounder = new KnowledgeCompounder();
|
|
115
|
-
}
|
|
116
|
-
catch (e) {
|
|
117
|
-
this.log.warn('KnowledgeCompounder init failed', { error: e instanceof Error ? e.message : e });
|
|
118
|
-
}
|
|
91
|
+
this.heuristicStore = this.tryInit(() => new HeuristicStore(), 'HeuristicStore');
|
|
92
|
+
this.modelRouter = this.tryInit(() => new ModelRouter(), 'ModelRouter');
|
|
93
|
+
this.autonomyTracker = this.tryInit(() => new AutonomyTracker(), 'AutonomyTracker');
|
|
94
|
+
this.compounder = this.tryInit(() => new KnowledgeCompounder(), 'KnowledgeCompounder');
|
|
119
95
|
if (options.worktreeEnabled) {
|
|
120
96
|
try {
|
|
121
97
|
this.worktreeManager = new WorktreeManager(options.cwd);
|
|
122
98
|
}
|
|
123
|
-
catch {
|
|
99
|
+
catch {
|
|
100
|
+
/* not a git repo */
|
|
101
|
+
}
|
|
124
102
|
}
|
|
103
|
+
this.agentRunner = new AgentRunner({
|
|
104
|
+
registry: this.registry,
|
|
105
|
+
executor: this.executor,
|
|
106
|
+
bus: this.bus,
|
|
107
|
+
logger: options.logger,
|
|
108
|
+
compaction: this.compaction,
|
|
109
|
+
worktreeManager: this.worktreeManager,
|
|
110
|
+
});
|
|
111
|
+
this.learningEngine = new LearningEngine({
|
|
112
|
+
traceStore: this.traceStore,
|
|
113
|
+
heuristicStore: this.heuristicStore,
|
|
114
|
+
modelRouter: this.modelRouter,
|
|
115
|
+
autonomyTracker: this.autonomyTracker,
|
|
116
|
+
compounder: this.compounder,
|
|
117
|
+
log: options.logger,
|
|
118
|
+
});
|
|
125
119
|
if (options.pluginLoader) {
|
|
126
120
|
options.pluginLoader.wireHooks(this.bus);
|
|
127
121
|
}
|
|
@@ -147,16 +141,14 @@ export class SwarmEngine {
|
|
|
147
141
|
orchestration.status = 'running';
|
|
148
142
|
orchestration.startedAt = new Date();
|
|
149
143
|
this.phaseOutputs = new Map();
|
|
144
|
+
// TODO: Checkpoint doesn't store phase outputs — resumed phases lose inter-phase context
|
|
150
145
|
this.reflections = [];
|
|
151
146
|
this.sharedContextFiles = [];
|
|
152
147
|
this.abortController = new AbortController();
|
|
153
148
|
this.livingSpec = createLivingSpec(config.description);
|
|
154
149
|
// Start continuous verification
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
continuousVerifier = new ContinuousVerifier(this.verifier, this.bus);
|
|
158
|
-
continuousVerifier.start();
|
|
159
|
-
}
|
|
150
|
+
const continuousVerifier = new ContinuousVerifier(this.verifier, this.bus);
|
|
151
|
+
continuousVerifier.start();
|
|
160
152
|
try {
|
|
161
153
|
// Execute phases using work-queue approach (handles DAGs correctly)
|
|
162
154
|
// If resuming, seed completed phases so the DAG executor skips them
|
|
@@ -173,17 +165,19 @@ export class SwarmEngine {
|
|
|
173
165
|
break;
|
|
174
166
|
}
|
|
175
167
|
// Find all runnable phases (dependencies met, not yet completed)
|
|
176
|
-
const runnable = orchestration.phases.filter(p => {
|
|
168
|
+
const runnable = orchestration.phases.filter((p) => {
|
|
177
169
|
if (completed.has(p.config.name))
|
|
178
170
|
return false;
|
|
179
171
|
if (p.status === 'failed')
|
|
180
172
|
return false;
|
|
173
|
+
if (p.status === 'skipped')
|
|
174
|
+
return false;
|
|
181
175
|
const deps = p.config.dependsOn ?? [];
|
|
182
|
-
return deps.every(d => completed.has(d));
|
|
176
|
+
return deps.every((d) => completed.has(d));
|
|
183
177
|
});
|
|
184
178
|
if (runnable.length === 0) {
|
|
185
179
|
// No runnable phases — either all done or deadlocked
|
|
186
|
-
const incomplete = orchestration.phases.filter(p => !completed.has(p.config.name) && p.status !== 'failed');
|
|
180
|
+
const incomplete = orchestration.phases.filter((p) => !completed.has(p.config.name) && p.status !== 'failed');
|
|
187
181
|
if (incomplete.length > 0) {
|
|
188
182
|
// Deadlock — blocked phases with unmet dependencies
|
|
189
183
|
for (const p of incomplete) {
|
|
@@ -191,15 +185,14 @@ export class SwarmEngine {
|
|
|
191
185
|
}
|
|
192
186
|
orchestration.status = 'failed';
|
|
193
187
|
this.log.error('Orchestration deadlocked: blocked phases with unmet dependencies', {
|
|
194
|
-
blocked: incomplete.map(p => p.config.name),
|
|
188
|
+
blocked: incomplete.map((p) => p.config.name),
|
|
195
189
|
});
|
|
196
190
|
}
|
|
197
191
|
break;
|
|
198
192
|
}
|
|
199
|
-
//
|
|
193
|
+
// Handle approval gates sequentially (can't prompt in parallel)
|
|
194
|
+
const approved = [];
|
|
200
195
|
for (const phase of runnable) {
|
|
201
|
-
orchestration.currentPhase = orchestration.phases.indexOf(phase);
|
|
202
|
-
// Check for approval gate
|
|
203
196
|
if (phase.config.requiresApproval) {
|
|
204
197
|
this.log.info(`Phase requires approval: ${phase.config.name}`);
|
|
205
198
|
this.bus.emit('orchestration:paused', {
|
|
@@ -207,10 +200,9 @@ export class SwarmEngine {
|
|
|
207
200
|
phase: phase.config.name,
|
|
208
201
|
agentCount: phase.config.agents.length,
|
|
209
202
|
}, 'engine');
|
|
210
|
-
// In interactive mode, wait for approval
|
|
211
203
|
if (!this.options.nonInteractive) {
|
|
212
|
-
const
|
|
213
|
-
if (!
|
|
204
|
+
const ok = await this.requestApproval(phase.config.name, phase.config.agents);
|
|
205
|
+
if (!ok) {
|
|
214
206
|
phase.status = 'skipped';
|
|
215
207
|
this.log.info(`Phase skipped (not approved): ${phase.config.name}`);
|
|
216
208
|
completed.add(phase.config.name);
|
|
@@ -218,14 +210,25 @@ export class SwarmEngine {
|
|
|
218
210
|
}
|
|
219
211
|
}
|
|
220
212
|
}
|
|
213
|
+
approved.push(phase);
|
|
214
|
+
}
|
|
215
|
+
// Execute approved phases in parallel
|
|
216
|
+
orchestration.currentPhase = orchestration.phases.indexOf(approved[approved.length - 1] ?? runnable[0]);
|
|
217
|
+
const results = await Promise.allSettled(approved.map(async (phase) => {
|
|
221
218
|
const outputs = await this.executePhase(orchestration, phase);
|
|
222
219
|
this.phaseOutputs.set(phase.config.name, outputs);
|
|
220
|
+
}));
|
|
221
|
+
// Process results — mark completed or failed
|
|
222
|
+
for (let i = 0; i < results.length; i++) {
|
|
223
|
+
const phase = approved[i];
|
|
224
|
+
if (results[i].status === 'rejected') {
|
|
225
|
+
phase.status = 'failed';
|
|
226
|
+
}
|
|
223
227
|
if (phase.status === 'completed') {
|
|
224
228
|
completed.add(phase.config.name);
|
|
225
229
|
}
|
|
226
230
|
else if (phase.status === 'failed') {
|
|
227
231
|
orchestration.status = 'failed';
|
|
228
|
-
break;
|
|
229
232
|
}
|
|
230
233
|
}
|
|
231
234
|
if (orchestration.status === 'failed')
|
|
@@ -233,12 +236,13 @@ export class SwarmEngine {
|
|
|
233
236
|
// Check cost budget after each batch of phases
|
|
234
237
|
if (orchestration.config.costBudget) {
|
|
235
238
|
const spent = orchestration.phases
|
|
236
|
-
.filter(p => p.status === 'completed')
|
|
239
|
+
.filter((p) => p.status === 'completed')
|
|
237
240
|
.reduce((sum, p) => sum + p.usage.costUsd, 0);
|
|
238
241
|
if (spent >= orchestration.config.costBudget) {
|
|
239
242
|
orchestration.status = 'failed';
|
|
240
243
|
this.log.warn(`Cost budget exceeded: $${spent.toFixed(2)} >= $${orchestration.config.costBudget}`, {
|
|
241
|
-
spent,
|
|
244
|
+
spent,
|
|
245
|
+
budget: orchestration.config.costBudget,
|
|
242
246
|
});
|
|
243
247
|
this.bus.emit('system:warning', {
|
|
244
248
|
reason: 'budget-exceeded',
|
|
@@ -250,7 +254,7 @@ export class SwarmEngine {
|
|
|
250
254
|
}
|
|
251
255
|
}
|
|
252
256
|
}
|
|
253
|
-
if (orchestration.status !== 'failed') {
|
|
257
|
+
if (orchestration.status !== 'failed' && orchestration.status !== 'cancelled') {
|
|
254
258
|
orchestration.status = 'completed';
|
|
255
259
|
}
|
|
256
260
|
}
|
|
@@ -261,7 +265,7 @@ export class SwarmEngine {
|
|
|
261
265
|
});
|
|
262
266
|
}
|
|
263
267
|
orchestration.completedAt = new Date();
|
|
264
|
-
continuousVerifier
|
|
268
|
+
continuousVerifier.stop();
|
|
265
269
|
// Clean up shared context files
|
|
266
270
|
for (const f of this.sharedContextFiles) {
|
|
267
271
|
try {
|
|
@@ -270,91 +274,8 @@ export class SwarmEngine {
|
|
|
270
274
|
catch { }
|
|
271
275
|
}
|
|
272
276
|
this.sharedContextFiles = [];
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
name: p.config.name,
|
|
276
|
-
status: p.status,
|
|
277
|
-
agentCount: p.agents.length,
|
|
278
|
-
tokens: p.usage.totalTokens,
|
|
279
|
-
durationMs: p.usage.durationMs,
|
|
280
|
-
confidence: p.agents[0]?.result?.confidence ?? 'unknown',
|
|
281
|
-
}));
|
|
282
|
-
const traceData = {
|
|
283
|
-
orchestrationId: config.id,
|
|
284
|
-
task: config.description,
|
|
285
|
-
pattern: config.pattern,
|
|
286
|
-
status: orchestration.status,
|
|
287
|
-
totalTokens: orchestration.usage.totalTokens,
|
|
288
|
-
costUsd: orchestration.usage.costUsd,
|
|
289
|
-
durationMs: orchestration.usage.durationMs,
|
|
290
|
-
phaseCount: orchestration.phases.length,
|
|
291
|
-
agentCount: orchestration.phases.reduce((n, p) => n + p.agents.length, 0),
|
|
292
|
-
phases: phaseData,
|
|
293
|
-
reflections: this.reflections.map(r => redactSecrets(r)),
|
|
294
|
-
};
|
|
295
|
-
// Store execution trace for learning
|
|
296
|
-
if (this.traceStore) {
|
|
297
|
-
try {
|
|
298
|
-
this.traceStore.store(traceData);
|
|
299
|
-
}
|
|
300
|
-
catch { /* Don't fail orchestration if trace storage fails */ }
|
|
301
|
-
// Extract heuristics from the trace for future learning
|
|
302
|
-
if (this.heuristicStore) {
|
|
303
|
-
try {
|
|
304
|
-
this.heuristicStore.extractFromTrace(traceData);
|
|
305
|
-
}
|
|
306
|
-
catch { /* Don't fail orchestration if heuristic extraction fails */ }
|
|
307
|
-
}
|
|
308
|
-
// Auto-compound knowledge from completed orchestrations
|
|
309
|
-
if (orchestration.status === 'completed' && this.compounder) {
|
|
310
|
-
try {
|
|
311
|
-
const doc = this.compounder.extractFromTrace({ ...traceData, status: 'completed' }, this.reflections);
|
|
312
|
-
if (doc)
|
|
313
|
-
this.compounder.store(doc);
|
|
314
|
-
}
|
|
315
|
-
catch { /* Don't fail orchestration if compounding fails */ }
|
|
316
|
-
}
|
|
317
|
-
// Auto-capture outcomes to memory
|
|
318
|
-
try {
|
|
319
|
-
const { traceToMemoryEntries } = await import('./traces.js');
|
|
320
|
-
const memEntries = traceToMemoryEntries(traceData);
|
|
321
|
-
// Store in memory if SwarmMemory is available
|
|
322
|
-
let memory;
|
|
323
|
-
try {
|
|
324
|
-
const { SwarmMemory } = await import('../memory/index.js');
|
|
325
|
-
memory = new SwarmMemory();
|
|
326
|
-
for (const entry of memEntries) {
|
|
327
|
-
memory.store({ type: entry.type, title: entry.title, content: entry.content });
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
catch { /* Memory not available */ }
|
|
331
|
-
finally {
|
|
332
|
-
try {
|
|
333
|
-
memory?.close();
|
|
334
|
-
}
|
|
335
|
-
catch { }
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
catch { /* Don't fail orchestration */ }
|
|
339
|
-
}
|
|
340
|
-
// Record model outcomes for routing
|
|
341
|
-
if (this.modelRouter) {
|
|
342
|
-
for (const phase of orchestration.phases) {
|
|
343
|
-
for (const agent of phase.agents) {
|
|
344
|
-
const model = agent.config.model;
|
|
345
|
-
if (!model)
|
|
346
|
-
continue;
|
|
347
|
-
const success = agent.status === 'shutdown' || agent.status === 'idle';
|
|
348
|
-
this.modelRouter.record(model, agent.config.name, success, success ? 1.0 : 0.0);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
// Record autonomy outcome
|
|
353
|
-
if (this.autonomyTracker) {
|
|
354
|
-
const success = orchestration.status === 'completed';
|
|
355
|
-
const intervention = orchestration.phases.some(p => p.config.requiresApproval);
|
|
356
|
-
this.autonomyTracker.record(config.pattern, success, intervention);
|
|
357
|
-
}
|
|
277
|
+
// Record learning outcomes (traces, heuristics, model routing, autonomy)
|
|
278
|
+
await this.learningEngine.recordOutcome(orchestration, config, this.reflections);
|
|
358
279
|
// Compute total usage
|
|
359
280
|
orchestration.usage = orchestration.phases.reduce((total, phase) => mergeUsageStats(total, phase.usage), emptyUsageStats());
|
|
360
281
|
this.bus.emit(orchestration.status === 'completed' ? 'orchestration:complete' : 'orchestration:failed', {
|
|
@@ -377,7 +298,10 @@ export class SwarmEngine {
|
|
|
377
298
|
async runPattern(patternName, task, options) {
|
|
378
299
|
const pattern = this.patterns.get(patternName);
|
|
379
300
|
if (!pattern) {
|
|
380
|
-
throw new Error(`Unknown pattern: ${patternName}. Available: ${this.patterns
|
|
301
|
+
throw new Error(`Unknown pattern: ${patternName}. Available: ${this.patterns
|
|
302
|
+
.list()
|
|
303
|
+
.map((p) => p.name)
|
|
304
|
+
.join(', ')}`);
|
|
381
305
|
}
|
|
382
306
|
// Inject task into all agent prompts
|
|
383
307
|
const config = {
|
|
@@ -385,9 +309,9 @@ export class SwarmEngine {
|
|
|
385
309
|
name: `${patternName}: ${task.slice(0, 50)}`,
|
|
386
310
|
description: task,
|
|
387
311
|
pattern: patternName,
|
|
388
|
-
phases: pattern.phases.map(phase => ({
|
|
312
|
+
phases: pattern.phases.map((phase) => ({
|
|
389
313
|
...phase,
|
|
390
|
-
agents: phase.agents.map(a => ({
|
|
314
|
+
agents: phase.agents.map((a) => ({
|
|
391
315
|
...a,
|
|
392
316
|
prompt: `${task}\n\n${a.prompt ?? ''}`.trim(),
|
|
393
317
|
})),
|
|
@@ -396,17 +320,7 @@ export class SwarmEngine {
|
|
|
396
320
|
};
|
|
397
321
|
// Substitute pattern parameters into agent prompts
|
|
398
322
|
if (options?.params && pattern.parameters) {
|
|
399
|
-
|
|
400
|
-
for (const agent of phase.agents) {
|
|
401
|
-
if (agent.prompt) {
|
|
402
|
-
for (const [key, value] of Object.entries(options.params)) {
|
|
403
|
-
// Use string replacement instead of regex to avoid ReDoS from user-controlled keys
|
|
404
|
-
const placeholder = '${' + key + '}';
|
|
405
|
-
agent.prompt = agent.prompt.split(placeholder).join(String(value));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
323
|
+
this.substituteParams(config.phases, options.params);
|
|
410
324
|
}
|
|
411
325
|
return this.execute(config);
|
|
412
326
|
}
|
|
@@ -417,37 +331,24 @@ export class SwarmEngine {
|
|
|
417
331
|
plan(patternName, task, options) {
|
|
418
332
|
const pattern = this.patterns.get(patternName);
|
|
419
333
|
if (!pattern) {
|
|
420
|
-
throw new Error(`Unknown pattern: ${patternName}. Available: ${this.patterns
|
|
334
|
+
throw new Error(`Unknown pattern: ${patternName}. Available: ${this.patterns
|
|
335
|
+
.list()
|
|
336
|
+
.map((p) => p.name)
|
|
337
|
+
.join(', ')}`);
|
|
421
338
|
}
|
|
422
339
|
// Check for similar past traces to inform planning
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
if (similar.length > 0) {
|
|
427
|
-
let traceContext = '';
|
|
428
|
-
const best = similar.find(t => t.status === 'completed');
|
|
429
|
-
if (best) {
|
|
430
|
-
traceContext = `Past successful orchestration for similar task used pattern "${best.pattern}" with ${best.agentCount} agents (${best.totalTokens} tokens, ${Math.round(best.durationMs / 1000)}s).`;
|
|
431
|
-
}
|
|
432
|
-
const failed = similar.find(t => t.status === 'failed');
|
|
433
|
-
if (failed && failed.reflections.length > 0) {
|
|
434
|
-
traceContext += ` Previous attempt failed. Lessons: ${failed.reflections[0]?.slice(0, 200)}`;
|
|
435
|
-
}
|
|
436
|
-
if (traceContext) {
|
|
437
|
-
this.log.info('Found similar past traces', { context: traceContext.slice(0, 200) });
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
catch { /* Don't fail planning if trace lookup fails */ }
|
|
340
|
+
const traceContext = this.learningEngine.findSimilarTraces(task);
|
|
341
|
+
if (traceContext) {
|
|
342
|
+
this.log.info('Found similar past traces', { context: traceContext.slice(0, 200) });
|
|
442
343
|
}
|
|
443
344
|
const id = generateId();
|
|
444
|
-
const phases = pattern.phases.map(phase => ({
|
|
345
|
+
const phases = pattern.phases.map((phase) => ({
|
|
445
346
|
name: phase.name,
|
|
446
347
|
parallel: phase.parallel,
|
|
447
348
|
dependsOn: phase.dependsOn,
|
|
448
349
|
requiresApproval: phase.requiresApproval,
|
|
449
350
|
tokenBudget: phase.tokenBudget,
|
|
450
|
-
agents: phase.agents.map(a => {
|
|
351
|
+
agents: phase.agents.map((a) => {
|
|
451
352
|
const agentConfig = this.registry.getConfig(a.type);
|
|
452
353
|
return {
|
|
453
354
|
name: a.name,
|
|
@@ -462,14 +363,7 @@ export class SwarmEngine {
|
|
|
462
363
|
}));
|
|
463
364
|
// Substitute pattern parameters if provided
|
|
464
365
|
if (options?.params && pattern.parameters) {
|
|
465
|
-
|
|
466
|
-
for (const agent of phase.agents) {
|
|
467
|
-
for (const [key, value] of Object.entries(options.params)) {
|
|
468
|
-
const placeholder = '${' + key + '}';
|
|
469
|
-
agent.prompt = agent.prompt.split(placeholder).join(String(value));
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
366
|
+
this.substituteParams(phases, options.params);
|
|
473
367
|
}
|
|
474
368
|
const planResult = {
|
|
475
369
|
id,
|
|
@@ -485,34 +379,9 @@ export class SwarmEngine {
|
|
|
485
379
|
if (rewrites.length > 0) {
|
|
486
380
|
this.log.info('Plan rewritten', { rewrites });
|
|
487
381
|
}
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const heuristics = this.heuristicStore.retrieve(task);
|
|
492
|
-
if (heuristics.length > 0) {
|
|
493
|
-
const context = this.heuristicStore.formatForContext(heuristics);
|
|
494
|
-
for (const agent of rewrittenPlan.phases[0]?.agents ?? []) {
|
|
495
|
-
agent.prompt = `${context}\n\n${agent.prompt}`;
|
|
496
|
-
}
|
|
497
|
-
this.log.info('Injected heuristics into plan', { count: heuristics.length });
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
catch { /* Don't fail planning if heuristic retrieval fails */ }
|
|
501
|
-
}
|
|
502
|
-
// Inject relevant past solutions from knowledge compounder (singleton)
|
|
503
|
-
if (this.compounder) {
|
|
504
|
-
try {
|
|
505
|
-
const solutions = this.compounder.findRelevant(task, 3);
|
|
506
|
-
if (solutions.length > 0) {
|
|
507
|
-
const solutionContext = this.compounder.formatForContext(solutions);
|
|
508
|
-
for (const agent of rewrittenPlan.phases[0]?.agents ?? []) {
|
|
509
|
-
agent.prompt = `${solutionContext}\n\n${agent.prompt}`;
|
|
510
|
-
}
|
|
511
|
-
this.log.info('Injected past solutions into plan', { count: solutions.length });
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
catch { /* Don't fail planning if solution retrieval fails */ }
|
|
515
|
-
}
|
|
382
|
+
// Inject learned heuristics and past solutions into the plan
|
|
383
|
+
this.learningEngine.injectHeuristics(rewrittenPlan);
|
|
384
|
+
this.learningEngine.injectSolutions(rewrittenPlan);
|
|
516
385
|
// Search over candidate plans to find the best one
|
|
517
386
|
const searcher = new PlanSearcher(this.costModel, this.traceStore ?? undefined);
|
|
518
387
|
const candidates = searcher.search(rewrittenPlan, {
|
|
@@ -525,7 +394,7 @@ export class SwarmEngine {
|
|
|
525
394
|
bestPlan.estimates = this.costModel.estimate(bestPlan);
|
|
526
395
|
bestPlan.rewrites = rewrites;
|
|
527
396
|
if (candidates.length > 1) {
|
|
528
|
-
bestPlan._alternatives = candidates.slice(1).map(c => ({
|
|
397
|
+
bestPlan._alternatives = candidates.slice(1).map((c) => ({
|
|
529
398
|
source: c.source,
|
|
530
399
|
costUsd: c.cost.costUsd,
|
|
531
400
|
quality: c.cost.qualityScore,
|
|
@@ -544,11 +413,17 @@ export class SwarmEngine {
|
|
|
544
413
|
const dropout = this.replanner.evaluateDropout(orchestration, phase.config);
|
|
545
414
|
if (dropout) {
|
|
546
415
|
this.log.info(`Agent dropout: ${dropout.reason}`);
|
|
547
|
-
phase.config.agents = phase.config.agents.filter(a => dropout.agentsToKeep.includes(a.name));
|
|
416
|
+
phase.config.agents = phase.config.agents.filter((a) => dropout.agentsToKeep.includes(a.name));
|
|
548
417
|
this.bus.emit('system:warning', { type: 'agent-dropout', ...dropout }, 'adaptive');
|
|
549
418
|
}
|
|
550
419
|
}
|
|
551
420
|
const maxRetries = phase.config.maxRetries ?? 2;
|
|
421
|
+
const runContext = {
|
|
422
|
+
phaseOutputs: this.phaseOutputs,
|
|
423
|
+
livingSpec: this.livingSpec,
|
|
424
|
+
sharedContextFiles: this.sharedContextFiles,
|
|
425
|
+
cwd: this.options.cwd,
|
|
426
|
+
};
|
|
552
427
|
this.log.info(`Phase started: ${phase.config.name}`, {
|
|
553
428
|
agents: phase.config.agents.length,
|
|
554
429
|
parallel: phase.config.parallel,
|
|
@@ -562,7 +437,7 @@ export class SwarmEngine {
|
|
|
562
437
|
try {
|
|
563
438
|
if (phase.config.parallel) {
|
|
564
439
|
// Run all agents in parallel (with retry)
|
|
565
|
-
const results = await Promise.allSettled(phase.config.agents.map(assignment => this.executeAgentWithRetry(assignment, phase, orchestration, maxRetries)));
|
|
440
|
+
const results = await Promise.allSettled(phase.config.agents.map((assignment) => this.agentRunner.executeAgentWithRetry(assignment, phase, orchestration, maxRetries, runContext)));
|
|
566
441
|
// Log cache savings estimate for parallel phases
|
|
567
442
|
if (phase.config.agents.length > 1) {
|
|
568
443
|
const phaseContext = this.compaction.buildContextPrefix(this.phaseOutputs);
|
|
@@ -573,7 +448,7 @@ export class SwarmEngine {
|
|
|
573
448
|
}
|
|
574
449
|
}
|
|
575
450
|
// Check for failures
|
|
576
|
-
const failures = results.filter(r => r.status === 'rejected');
|
|
451
|
+
const failures = results.filter((r) => r.status === 'rejected');
|
|
577
452
|
if (failures.length > 0) {
|
|
578
453
|
const failureCount = failures.length;
|
|
579
454
|
this.log.warn(`Phase had ${failureCount} agent failures`, {
|
|
@@ -594,7 +469,7 @@ export class SwarmEngine {
|
|
|
594
469
|
else {
|
|
595
470
|
// Run agents sequentially (with retry)
|
|
596
471
|
for (const assignment of phase.config.agents) {
|
|
597
|
-
await this.executeAgentWithRetry(assignment, phase, orchestration, maxRetries);
|
|
472
|
+
await this.agentRunner.executeAgentWithRetry(assignment, phase, orchestration, maxRetries, runContext);
|
|
598
473
|
// Check phase token budget after each agent
|
|
599
474
|
if (phase.config.tokenBudget) {
|
|
600
475
|
const phaseTokens = phase.agents.reduce((sum, a) => sum + a.usage.totalTokens, 0);
|
|
@@ -616,7 +491,7 @@ export class SwarmEngine {
|
|
|
616
491
|
phase.completedAt = new Date();
|
|
617
492
|
phase.usage = phase.agents.reduce((total, agent) => mergeUsageStats(total, agent.usage), emptyUsageStats());
|
|
618
493
|
// Run verification after implementation phases
|
|
619
|
-
if (
|
|
494
|
+
if (phase.config.kind === 'implement' || phase.config.kind === 'integrate') {
|
|
620
495
|
const results = await this.verifier.verify();
|
|
621
496
|
const summary = Verifier.summarize(results);
|
|
622
497
|
this.log.info(`Verification: ${summary}`, { phase: phase.config.name });
|
|
@@ -647,8 +522,8 @@ export class SwarmEngine {
|
|
|
647
522
|
});
|
|
648
523
|
// Collect agent outputs for inter-phase context
|
|
649
524
|
const outputs = phase.agents
|
|
650
|
-
.filter(a => a.result?.output)
|
|
651
|
-
.map(a => `[${a.config.name}]: ${a.result.output.slice(0, 2000)}`);
|
|
525
|
+
.filter((a) => a.result?.output)
|
|
526
|
+
.map((a) => `[${a.config.name}]: ${a.result.output.slice(0, 2000)}`);
|
|
652
527
|
// Update living spec with phase outputs
|
|
653
528
|
if (this.livingSpec) {
|
|
654
529
|
this.livingSpec = updateSpecFromPhase(this.livingSpec, phase.config.name, outputs);
|
|
@@ -665,121 +540,6 @@ export class SwarmEngine {
|
|
|
665
540
|
}
|
|
666
541
|
return outputs;
|
|
667
542
|
}
|
|
668
|
-
/**
|
|
669
|
-
* Execute a single agent within a phase.
|
|
670
|
-
*/
|
|
671
|
-
async executeAgent(assignment, phase, orchestration) {
|
|
672
|
-
// Spawn from registry, passing all assignment overrides
|
|
673
|
-
const instance = this.registry.spawn(assignment.type, {
|
|
674
|
-
name: assignment.name,
|
|
675
|
-
model: assignment.model,
|
|
676
|
-
backend: assignment.backend,
|
|
677
|
-
backendModel: assignment.backendModel,
|
|
678
|
-
});
|
|
679
|
-
phase.agents.push(instance);
|
|
680
|
-
// Build task prompt with cache-friendly ordering:
|
|
681
|
-
// Shared prefix (orchestration + phase context) comes first for API cache hits,
|
|
682
|
-
// agent-specific content (definition + task) comes last.
|
|
683
|
-
const orchestrationContext = `## Orchestration: ${orchestration.config.name}\nPattern: ${orchestration.config.pattern}`;
|
|
684
|
-
const phaseContext = this.compaction.buildContextPrefix(this.phaseOutputs);
|
|
685
|
-
const agentPrompt = instance.config.prompt;
|
|
686
|
-
const baseTask = assignment.prompt || `Execute task as ${assignment.type}`;
|
|
687
|
-
const optimized = optimizeForCache(orchestrationContext, phaseContext, agentPrompt, baseTask);
|
|
688
|
-
// For parallel phases with large shared prefix, use shared context file
|
|
689
|
-
const sharedContextPath = phase.config.parallel
|
|
690
|
-
? this.writeSharedContext(optimized.sharedPrefix)
|
|
691
|
-
: null;
|
|
692
|
-
// Cascade model selection: if no explicit model override, pick starting tier by complexity
|
|
693
|
-
if (!assignment.model) {
|
|
694
|
-
const complexity = classifyTaskComplexity(baseTask);
|
|
695
|
-
const startTier = selectStartingTier(complexity);
|
|
696
|
-
if (startTier !== instance.config.model) {
|
|
697
|
-
instance.config.model = startTier;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
// Inject living spec into task context
|
|
701
|
-
const specPrefix = this.livingSpec ? formatLivingSpec(this.livingSpec) + '\n\n' : '';
|
|
702
|
-
const task = sharedContextPath
|
|
703
|
-
? `Read the shared context file at ${sharedContextPath} first, then:\n\n${specPrefix}${optimized.agentSuffix}`
|
|
704
|
-
: `${specPrefix}${optimized.full}`;
|
|
705
|
-
// Transition to running
|
|
706
|
-
this.registry.updateStatus(instance.id, 'running');
|
|
707
|
-
// Create worktree if enabled and assignment requests it
|
|
708
|
-
let worktreeId;
|
|
709
|
-
if (this.worktreeManager && assignment.worktree) {
|
|
710
|
-
try {
|
|
711
|
-
const wt = this.worktreeManager.create(instance.id, assignment.name);
|
|
712
|
-
worktreeId = wt.id;
|
|
713
|
-
this.bus.emit('worktree:created', { agentId: instance.id, worktreeId: wt.id, path: wt.path }, instance.id);
|
|
714
|
-
}
|
|
715
|
-
catch (err) {
|
|
716
|
-
this.log.warn(`Failed to create worktree for ${assignment.name}: ${err}`);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
// Execute with timeout
|
|
720
|
-
const timeoutMs = (instance.config.timeout ?? 300) * 1000; // default 5 min
|
|
721
|
-
let timer;
|
|
722
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
723
|
-
timer = setTimeout(() => reject(new Error(`Agent timeout after ${timeoutMs / 1000}s`)), timeoutMs);
|
|
724
|
-
});
|
|
725
|
-
try {
|
|
726
|
-
await Promise.race([
|
|
727
|
-
this.executor.execute(instance, task),
|
|
728
|
-
timeoutPromise,
|
|
729
|
-
]);
|
|
730
|
-
clearTimeout(timer);
|
|
731
|
-
// Merge worktree if created
|
|
732
|
-
if (worktreeId && this.worktreeManager) {
|
|
733
|
-
try {
|
|
734
|
-
this.worktreeManager.merge(worktreeId);
|
|
735
|
-
this.bus.emit('worktree:merged', { agentId: instance.id, worktreeId }, instance.id);
|
|
736
|
-
}
|
|
737
|
-
catch (err) {
|
|
738
|
-
this.log.warn(`Failed to merge worktree ${worktreeId}: ${err}`);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
// Transition to idle (completed successfully)
|
|
742
|
-
this.registry.updateStatus(instance.id, 'idle');
|
|
743
|
-
// Then shutdown
|
|
744
|
-
this.registry.updateStatus(instance.id, 'shutdown');
|
|
745
|
-
return instance;
|
|
746
|
-
}
|
|
747
|
-
catch (error) {
|
|
748
|
-
clearTimeout(timer);
|
|
749
|
-
if (String(error).includes('timeout')) {
|
|
750
|
-
this.registry.updateStatus(instance.id, 'timeout');
|
|
751
|
-
}
|
|
752
|
-
else {
|
|
753
|
-
this.registry.updateStatus(instance.id, 'error');
|
|
754
|
-
}
|
|
755
|
-
instance.error = error instanceof Error ? error.message : String(error);
|
|
756
|
-
throw error;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
async executeAgentWithRetry(assignment, phase, orchestration, maxRetries = 2) {
|
|
760
|
-
let lastError;
|
|
761
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
762
|
-
try {
|
|
763
|
-
return await this.executeAgent(assignment, phase, orchestration);
|
|
764
|
-
}
|
|
765
|
-
catch (error) {
|
|
766
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
767
|
-
// Remove the failed instance from phase agents to prevent duplicates on retry
|
|
768
|
-
const failedIndex = phase.agents.findIndex(a => a.config.name === assignment.name && (a.status === 'error' || a.status === 'timeout'));
|
|
769
|
-
if (failedIndex >= 0)
|
|
770
|
-
phase.agents.splice(failedIndex, 1);
|
|
771
|
-
if (attempt < maxRetries) {
|
|
772
|
-
const delayMs = Math.min(1000 * Math.pow(2, attempt) + Math.random() * 1000, 30000);
|
|
773
|
-
this.log.warn(`Agent failed, retrying in ${Math.round(delayMs / 1000)}s (attempt ${attempt + 1}/${maxRetries})`, {
|
|
774
|
-
agent: assignment.name,
|
|
775
|
-
error: lastError.message,
|
|
776
|
-
});
|
|
777
|
-
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
throw lastError;
|
|
782
|
-
}
|
|
783
543
|
// ─── Resume ────────────────────────────────────────────────────
|
|
784
544
|
/**
|
|
785
545
|
* Resume an orchestration from a checkpoint.
|
|
@@ -817,10 +577,8 @@ export class SwarmEngine {
|
|
|
817
577
|
const { createInterface } = await import('readline');
|
|
818
578
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
819
579
|
return new Promise((resolve) => {
|
|
820
|
-
const agentList = agents.map(a => ` ${a.name} (${a.type})`).join('\n');
|
|
821
|
-
rl.question(`\n⏸ Phase "${phaseName}" requires approval.\n` +
|
|
822
|
-
` Agents:\n${agentList}\n` +
|
|
823
|
-
` Proceed? [Y/n] `, (answer) => {
|
|
580
|
+
const agentList = agents.map((a) => ` ${a.name} (${a.type})`).join('\n');
|
|
581
|
+
rl.question(`\n⏸ Phase "${phaseName}" requires approval.\n` + ` Agents:\n${agentList}\n` + ` Proceed? [Y/n] `, (answer) => {
|
|
824
582
|
rl.close();
|
|
825
583
|
resolve(answer.toLowerCase() !== 'n');
|
|
826
584
|
});
|
|
@@ -856,26 +614,7 @@ export class SwarmEngine {
|
|
|
856
614
|
// Best effort — lifecycle may not allow transition
|
|
857
615
|
}
|
|
858
616
|
}
|
|
859
|
-
|
|
860
|
-
this.stats.close();
|
|
861
|
-
}
|
|
862
|
-
catch { /* best effort */ }
|
|
863
|
-
try {
|
|
864
|
-
this.traceStore?.close();
|
|
865
|
-
}
|
|
866
|
-
catch { /* best effort */ }
|
|
867
|
-
try {
|
|
868
|
-
this.heuristicStore?.close();
|
|
869
|
-
}
|
|
870
|
-
catch { /* best effort */ }
|
|
871
|
-
try {
|
|
872
|
-
this.modelRouter?.close();
|
|
873
|
-
}
|
|
874
|
-
catch { }
|
|
875
|
-
try {
|
|
876
|
-
this.autonomyTracker?.close();
|
|
877
|
-
}
|
|
878
|
-
catch { }
|
|
617
|
+
this.close();
|
|
879
618
|
this.bus.emit('system:warning', { message: 'Graceful shutdown complete' }, 'engine');
|
|
880
619
|
};
|
|
881
620
|
process.on('SIGINT', handler);
|
|
@@ -889,28 +628,14 @@ export class SwarmEngine {
|
|
|
889
628
|
}
|
|
890
629
|
this.signalHandlers = [];
|
|
891
630
|
}
|
|
892
|
-
/** Close all resources (stats, traces, heuristics, model router, autonomy tracker). */
|
|
631
|
+
/** Close all resources (stats, traces, heuristics, model router, autonomy tracker, database). */
|
|
893
632
|
close() {
|
|
894
633
|
try {
|
|
895
634
|
this.stats.close();
|
|
896
635
|
}
|
|
897
636
|
catch { }
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
catch { }
|
|
902
|
-
try {
|
|
903
|
-
this.heuristicStore?.close();
|
|
904
|
-
}
|
|
905
|
-
catch { }
|
|
906
|
-
try {
|
|
907
|
-
this.modelRouter?.close();
|
|
908
|
-
}
|
|
909
|
-
catch { }
|
|
910
|
-
try {
|
|
911
|
-
this.autonomyTracker?.close();
|
|
912
|
-
}
|
|
913
|
-
catch { }
|
|
637
|
+
this.learningEngine.close();
|
|
638
|
+
closeDatabase();
|
|
914
639
|
this.removeSignalHandlers();
|
|
915
640
|
}
|
|
916
641
|
// ─── Queries ───────────────────────────────────────────────────
|
|
@@ -930,33 +655,43 @@ export class SwarmEngine {
|
|
|
930
655
|
* Get active orchestrations.
|
|
931
656
|
*/
|
|
932
657
|
getActiveOrchestrations() {
|
|
933
|
-
return [...this.orchestrations.values()].filter(o => o.status === 'running' || o.status === 'paused');
|
|
658
|
+
return [...this.orchestrations.values()].filter((o) => o.status === 'running' || o.status === 'paused');
|
|
934
659
|
}
|
|
935
660
|
get patternRegistry() {
|
|
936
661
|
return this.patterns;
|
|
937
662
|
}
|
|
938
|
-
// ───
|
|
663
|
+
// ─── Helpers ───────────────────────────────────────────────────
|
|
664
|
+
tryInit(factory, name) {
|
|
665
|
+
try {
|
|
666
|
+
return factory();
|
|
667
|
+
}
|
|
668
|
+
catch (e) {
|
|
669
|
+
this.log.warn(`${name} init failed`, { error: e instanceof Error ? e.message : e });
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
939
673
|
/**
|
|
940
|
-
*
|
|
941
|
-
*
|
|
674
|
+
* Substitute ${key} placeholders in agent prompts.
|
|
675
|
+
* Uses string split/join instead of regex to avoid ReDoS from user-controlled keys.
|
|
942
676
|
*/
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
677
|
+
substituteParams(phases, params) {
|
|
678
|
+
for (const phase of phases) {
|
|
679
|
+
for (const agent of phase.agents) {
|
|
680
|
+
if (agent.prompt) {
|
|
681
|
+
for (const [key, value] of Object.entries(params)) {
|
|
682
|
+
const placeholder = '${' + key + '}';
|
|
683
|
+
agent.prompt = agent.prompt.split(placeholder).join(String(value));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
953
688
|
}
|
|
954
689
|
// ─── Factory ───────────────────────────────────────────────────
|
|
955
690
|
createInstance(config) {
|
|
956
691
|
return {
|
|
957
692
|
config,
|
|
958
693
|
status: 'pending',
|
|
959
|
-
phases: config.phases.map(phaseConfig => ({
|
|
694
|
+
phases: config.phases.map((phaseConfig) => ({
|
|
960
695
|
config: phaseConfig,
|
|
961
696
|
status: 'pending',
|
|
962
697
|
agents: [],
|