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.
Files changed (300) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +102 -25
  3. package/agents/accessibility-reviewer.md +2 -0
  4. package/agents/api-contract-reviewer.md +2 -0
  5. package/agents/concurrency-reviewer.md +2 -0
  6. package/agents/data-integrity-reviewer.md +1 -0
  7. package/agents/debugger.md +2 -0
  8. package/agents/dependency-reviewer.md +2 -0
  9. package/agents/devils-advocate.md +1 -0
  10. package/agents/documentation-reviewer.md +2 -0
  11. package/agents/documenter.md +13 -0
  12. package/agents/error-handling-reviewer.md +2 -0
  13. package/agents/grounding.md +3 -2
  14. package/agents/guardian.md +3 -0
  15. package/agents/implementer.md +2 -0
  16. package/agents/integrator.md +3 -0
  17. package/agents/judge.md +2 -0
  18. package/agents/librarian.md +4 -1
  19. package/agents/orchestrator.md +3 -0
  20. package/agents/performance-reviewer.md +2 -0
  21. package/agents/planner.md +2 -0
  22. package/agents/refactorer.md +3 -0
  23. package/agents/reviewer.md +1 -0
  24. package/agents/security-reviewer.md +1 -0
  25. package/agents/sentinel.md +3 -0
  26. package/agents/tester.md +2 -0
  27. package/agents/testing-reviewer.md +2 -0
  28. package/commands/diff-review.md +27 -15
  29. package/commands/discover.md +102 -0
  30. package/commands/dynamic.md +136 -0
  31. package/commands/fix-pr.md +30 -24
  32. package/commands/postmortem.md +106 -0
  33. package/commands/red-team.md +41 -26
  34. package/commands/research.md +22 -1
  35. package/commands/review-cycle.md +38 -20
  36. package/commands/spike.md +108 -0
  37. package/commands/swarm.md +68 -60
  38. package/commands/tdd.md +44 -24
  39. package/dist/cli/commands/acp.d.ts.map +1 -1
  40. package/dist/cli/commands/acp.js +12 -2
  41. package/dist/cli/commands/acp.js.map +1 -1
  42. package/dist/cli/commands/agents.d.ts.map +1 -1
  43. package/dist/cli/commands/agents.js +16 -13
  44. package/dist/cli/commands/agents.js.map +1 -1
  45. package/dist/cli/commands/completions.d.ts.map +1 -1
  46. package/dist/cli/commands/completions.js +21 -9
  47. package/dist/cli/commands/completions.js.map +1 -1
  48. package/dist/cli/commands/compound.d.ts.map +1 -1
  49. package/dist/cli/commands/compound.js +1 -2
  50. package/dist/cli/commands/compound.js.map +1 -1
  51. package/dist/cli/commands/configure.d.ts.map +1 -1
  52. package/dist/cli/commands/configure.js +24 -8
  53. package/dist/cli/commands/configure.js.map +1 -1
  54. package/dist/cli/commands/convert.d.ts +1 -1
  55. package/dist/cli/commands/convert.d.ts.map +1 -1
  56. package/dist/cli/commands/convert.js +22 -48
  57. package/dist/cli/commands/convert.js.map +1 -1
  58. package/dist/cli/commands/doctor.d.ts.map +1 -1
  59. package/dist/cli/commands/doctor.js +1 -3
  60. package/dist/cli/commands/doctor.js.map +1 -1
  61. package/dist/cli/commands/init.d.ts.map +1 -1
  62. package/dist/cli/commands/init.js +17 -7
  63. package/dist/cli/commands/init.js.map +1 -1
  64. package/dist/cli/commands/install.d.ts.map +1 -1
  65. package/dist/cli/commands/install.js +1 -1
  66. package/dist/cli/commands/install.js.map +1 -1
  67. package/dist/cli/commands/learn.js +6 -6
  68. package/dist/cli/commands/learn.js.map +1 -1
  69. package/dist/cli/commands/mcp.d.ts.map +1 -1
  70. package/dist/cli/commands/mcp.js +1 -2
  71. package/dist/cli/commands/mcp.js.map +1 -1
  72. package/dist/cli/commands/memory.d.ts.map +1 -1
  73. package/dist/cli/commands/memory.js +1 -2
  74. package/dist/cli/commands/memory.js.map +1 -1
  75. package/dist/cli/commands/orchestrate.d.ts.map +1 -1
  76. package/dist/cli/commands/orchestrate.js +20 -7
  77. package/dist/cli/commands/orchestrate.js.map +1 -1
  78. package/dist/cli/commands/plan.d.ts.map +1 -1
  79. package/dist/cli/commands/plan.js.map +1 -1
  80. package/dist/cli/commands/plugin.d.ts.map +1 -1
  81. package/dist/cli/commands/plugin.js +8 -5
  82. package/dist/cli/commands/plugin.js.map +1 -1
  83. package/dist/cli/commands/resume.js +1 -1
  84. package/dist/cli/commands/resume.js.map +1 -1
  85. package/dist/cli/commands/run.d.ts.map +1 -1
  86. package/dist/cli/commands/run.js +20 -6
  87. package/dist/cli/commands/run.js.map +1 -1
  88. package/dist/cli/commands/share.d.ts.map +1 -1
  89. package/dist/cli/commands/share.js +6 -1
  90. package/dist/cli/commands/share.js.map +1 -1
  91. package/dist/cli/commands/status.d.ts.map +1 -1
  92. package/dist/cli/commands/status.js +15 -7
  93. package/dist/cli/commands/status.js.map +1 -1
  94. package/dist/cli/commands/template.d.ts.map +1 -1
  95. package/dist/cli/commands/template.js +14 -6
  96. package/dist/cli/commands/template.js.map +1 -1
  97. package/dist/cli/commands/vault.d.ts.map +1 -1
  98. package/dist/cli/commands/vault.js +14 -9
  99. package/dist/cli/commands/vault.js.map +1 -1
  100. package/dist/cli/commands/verify.d.ts.map +1 -1
  101. package/dist/cli/commands/verify.js +2 -2
  102. package/dist/cli/commands/verify.js.map +1 -1
  103. package/dist/cli/commands/watch.js +1 -1
  104. package/dist/cli/commands/watch.js.map +1 -1
  105. package/dist/cli/index.js +14 -4
  106. package/dist/cli/index.js.map +1 -1
  107. package/dist/core/checkpoint.js +1 -1
  108. package/dist/core/checkpoint.js.map +1 -1
  109. package/dist/core/event-bus.d.ts.map +1 -1
  110. package/dist/core/event-bus.js +9 -3
  111. package/dist/core/event-bus.js.map +1 -1
  112. package/dist/core/lifecycle.js.map +1 -1
  113. package/dist/core/patterns.d.ts.map +1 -1
  114. package/dist/core/patterns.js +31 -8
  115. package/dist/core/patterns.js.map +1 -1
  116. package/dist/core/permissions.d.ts.map +1 -1
  117. package/dist/core/permissions.js +21 -10
  118. package/dist/core/permissions.js.map +1 -1
  119. package/dist/core/registry.d.ts.map +1 -1
  120. package/dist/core/registry.js +10 -6
  121. package/dist/core/registry.js.map +1 -1
  122. package/dist/core/snapshots.d.ts.map +1 -1
  123. package/dist/core/snapshots.js +17 -5
  124. package/dist/core/snapshots.js.map +1 -1
  125. package/dist/core/types.d.ts +3 -0
  126. package/dist/core/types.d.ts.map +1 -1
  127. package/dist/core/types.js.map +1 -1
  128. package/dist/hooks/index.js.map +1 -1
  129. package/dist/index.d.ts +68 -6
  130. package/dist/index.d.ts.map +1 -1
  131. package/dist/index.js +60 -4
  132. package/dist/index.js.map +1 -1
  133. package/dist/memory/index.d.ts +1 -0
  134. package/dist/memory/index.d.ts.map +1 -1
  135. package/dist/memory/index.js +39 -24
  136. package/dist/memory/index.js.map +1 -1
  137. package/dist/memory/schema.d.ts +1 -0
  138. package/dist/memory/schema.d.ts.map +1 -1
  139. package/dist/memory/schema.js +20 -19
  140. package/dist/memory/schema.js.map +1 -1
  141. package/dist/plugin/index.d.ts.map +1 -1
  142. package/dist/plugin/index.js.map +1 -1
  143. package/dist/runtime/acp.d.ts.map +1 -1
  144. package/dist/runtime/acp.js +71 -41
  145. package/dist/runtime/acp.js.map +1 -1
  146. package/dist/runtime/adaptive.d.ts.map +1 -1
  147. package/dist/runtime/adaptive.js +30 -31
  148. package/dist/runtime/adaptive.js.map +1 -1
  149. package/dist/runtime/agent-runner.d.ts +52 -0
  150. package/dist/runtime/agent-runner.d.ts.map +1 -0
  151. package/dist/runtime/agent-runner.js +156 -0
  152. package/dist/runtime/agent-runner.js.map +1 -0
  153. package/dist/runtime/autonomy.d.ts +1 -0
  154. package/dist/runtime/autonomy.d.ts.map +1 -1
  155. package/dist/runtime/autonomy.js +37 -19
  156. package/dist/runtime/autonomy.js.map +1 -1
  157. package/dist/runtime/backends/claude.d.ts.map +1 -1
  158. package/dist/runtime/backends/claude.js +2 -2
  159. package/dist/runtime/backends/claude.js.map +1 -1
  160. package/dist/runtime/backends/codex.d.ts.map +1 -1
  161. package/dist/runtime/backends/codex.js +8 -11
  162. package/dist/runtime/backends/codex.js.map +1 -1
  163. package/dist/runtime/backends/gemini.d.ts.map +1 -1
  164. package/dist/runtime/backends/gemini.js +11 -7
  165. package/dist/runtime/backends/gemini.js.map +1 -1
  166. package/dist/runtime/backends/index.js +1 -1
  167. package/dist/runtime/backends/index.js.map +1 -1
  168. package/dist/runtime/backends/mock.d.ts.map +1 -1
  169. package/dist/runtime/backends/mock.js +1 -1
  170. package/dist/runtime/backends/mock.js.map +1 -1
  171. package/dist/runtime/backends/vercel-ai.d.ts.map +1 -1
  172. package/dist/runtime/backends/vercel-ai.js +41 -9
  173. package/dist/runtime/backends/vercel-ai.js.map +1 -1
  174. package/dist/runtime/cache-optimizer.d.ts.map +1 -1
  175. package/dist/runtime/cache-optimizer.js +3 -9
  176. package/dist/runtime/cache-optimizer.js.map +1 -1
  177. package/dist/runtime/cascade.d.ts.map +1 -1
  178. package/dist/runtime/cascade.js +34 -7
  179. package/dist/runtime/cascade.js.map +1 -1
  180. package/dist/runtime/chunker.d.ts.map +1 -1
  181. package/dist/runtime/chunker.js +12 -6
  182. package/dist/runtime/chunker.js.map +1 -1
  183. package/dist/runtime/compounder.d.ts +1 -1
  184. package/dist/runtime/compounder.d.ts.map +1 -1
  185. package/dist/runtime/compounder.js +30 -11
  186. package/dist/runtime/compounder.js.map +1 -1
  187. package/dist/runtime/cost-model.d.ts.map +1 -1
  188. package/dist/runtime/cost-model.js +1 -1
  189. package/dist/runtime/cost-model.js.map +1 -1
  190. package/dist/runtime/database.d.ts +16 -0
  191. package/dist/runtime/database.d.ts.map +1 -0
  192. package/dist/runtime/database.js +39 -0
  193. package/dist/runtime/database.js.map +1 -0
  194. package/dist/runtime/distiller.d.ts.map +1 -1
  195. package/dist/runtime/distiller.js +6 -3
  196. package/dist/runtime/distiller.js.map +1 -1
  197. package/dist/runtime/engine.d.ts +7 -9
  198. package/dist/runtime/engine.d.ts.map +1 -1
  199. package/dist/runtime/engine.js +129 -394
  200. package/dist/runtime/engine.js.map +1 -1
  201. package/dist/runtime/executor.d.ts +1 -2
  202. package/dist/runtime/executor.d.ts.map +1 -1
  203. package/dist/runtime/executor.js +45 -14
  204. package/dist/runtime/executor.js.map +1 -1
  205. package/dist/runtime/heuristics.d.ts +1 -0
  206. package/dist/runtime/heuristics.d.ts.map +1 -1
  207. package/dist/runtime/heuristics.js +44 -22
  208. package/dist/runtime/heuristics.js.map +1 -1
  209. package/dist/runtime/learning-engine.d.ts +51 -0
  210. package/dist/runtime/learning-engine.d.ts.map +1 -0
  211. package/dist/runtime/learning-engine.js +209 -0
  212. package/dist/runtime/learning-engine.js.map +1 -0
  213. package/dist/runtime/living-spec.js +3 -3
  214. package/dist/runtime/living-spec.js.map +1 -1
  215. package/dist/runtime/lsp.d.ts.map +1 -1
  216. package/dist/runtime/lsp.js +41 -14
  217. package/dist/runtime/lsp.js.map +1 -1
  218. package/dist/runtime/mcp.d.ts.map +1 -1
  219. package/dist/runtime/mcp.js +56 -19
  220. package/dist/runtime/mcp.js.map +1 -1
  221. package/dist/runtime/model-router.d.ts +1 -0
  222. package/dist/runtime/model-router.d.ts.map +1 -1
  223. package/dist/runtime/model-router.js +37 -21
  224. package/dist/runtime/model-router.js.map +1 -1
  225. package/dist/runtime/panes.d.ts.map +1 -1
  226. package/dist/runtime/panes.js +50 -49
  227. package/dist/runtime/panes.js.map +1 -1
  228. package/dist/runtime/plan-search.js +2 -2
  229. package/dist/runtime/plan-search.js.map +1 -1
  230. package/dist/runtime/plugins.d.ts +1 -1
  231. package/dist/runtime/plugins.d.ts.map +1 -1
  232. package/dist/runtime/plugins.js +63 -47
  233. package/dist/runtime/plugins.js.map +1 -1
  234. package/dist/runtime/reflexion.d.ts.map +1 -1
  235. package/dist/runtime/reflexion.js +4 -8
  236. package/dist/runtime/reflexion.js.map +1 -1
  237. package/dist/runtime/review-schema.d.ts.map +1 -1
  238. package/dist/runtime/review-schema.js +12 -12
  239. package/dist/runtime/review-schema.js.map +1 -1
  240. package/dist/runtime/rewriter.d.ts.map +1 -1
  241. package/dist/runtime/rewriter.js +29 -9
  242. package/dist/runtime/rewriter.js.map +1 -1
  243. package/dist/runtime/sharing.d.ts +1 -1
  244. package/dist/runtime/sharing.d.ts.map +1 -1
  245. package/dist/runtime/sharing.js +55 -27
  246. package/dist/runtime/sharing.js.map +1 -1
  247. package/dist/runtime/stats.d.ts +1 -0
  248. package/dist/runtime/stats.d.ts.map +1 -1
  249. package/dist/runtime/stats.js +40 -24
  250. package/dist/runtime/stats.js.map +1 -1
  251. package/dist/runtime/templates.d.ts.map +1 -1
  252. package/dist/runtime/templates.js +2 -2
  253. package/dist/runtime/templates.js.map +1 -1
  254. package/dist/runtime/traces.d.ts +1 -0
  255. package/dist/runtime/traces.d.ts.map +1 -1
  256. package/dist/runtime/traces.js +50 -28
  257. package/dist/runtime/traces.js.map +1 -1
  258. package/dist/runtime/verifier.d.ts.map +1 -1
  259. package/dist/runtime/verifier.js +12 -6
  260. package/dist/runtime/verifier.js.map +1 -1
  261. package/dist/runtime/worktree.d.ts.map +1 -1
  262. package/dist/runtime/worktree.js +35 -18
  263. package/dist/runtime/worktree.js.map +1 -1
  264. package/dist/tui/dashboard.d.ts.map +1 -1
  265. package/dist/tui/dashboard.js +20 -16
  266. package/dist/tui/dashboard.js.map +1 -1
  267. package/dist/tui/progress.d.ts +2 -0
  268. package/dist/tui/progress.d.ts.map +1 -1
  269. package/dist/tui/progress.js +105 -33
  270. package/dist/tui/progress.js.map +1 -1
  271. package/dist/tui/renderer.d.ts.map +1 -1
  272. package/dist/tui/renderer.js.map +1 -1
  273. package/dist/utils/compact-format.js +1 -1
  274. package/dist/utils/compact-format.js.map +1 -1
  275. package/dist/utils/config.d.ts.map +1 -1
  276. package/dist/utils/config.js.map +1 -1
  277. package/dist/utils/env.d.ts.map +1 -1
  278. package/dist/utils/env.js +19 -5
  279. package/dist/utils/env.js.map +1 -1
  280. package/dist/utils/errors.d.ts.map +1 -1
  281. package/dist/utils/errors.js +3 -7
  282. package/dist/utils/errors.js.map +1 -1
  283. package/dist/utils/output.d.ts.map +1 -1
  284. package/dist/utils/output.js +6 -2
  285. package/dist/utils/output.js.map +1 -1
  286. package/dist/utils/project-config.d.ts +18 -0
  287. package/dist/utils/project-config.d.ts.map +1 -1
  288. package/dist/utils/project-config.js +14 -6
  289. package/dist/utils/project-config.js.map +1 -1
  290. package/dist/utils/schemas.d.ts.map +1 -1
  291. package/dist/utils/schemas.js +12 -12
  292. package/dist/utils/schemas.js.map +1 -1
  293. package/dist/utils/terminal.d.ts.map +1 -1
  294. package/dist/utils/terminal.js +18 -7
  295. package/dist/utils/terminal.js.map +1 -1
  296. package/dist/utils/tiers.d.ts.map +1 -1
  297. package/dist/utils/tiers.js +14 -6
  298. package/dist/utils/tiers.js.map +1 -1
  299. package/package.json +14 -3
  300. package/skills/swarm-output-style/SKILL.md +114 -46
@@ -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 { optimizeForCache, estimateCacheSavings } from './cache-optimizer.js';
23
- import { classifyTaskComplexity, selectStartingTier } from './cascade.js';
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 { redactSecrets } from '../utils/redact.js';
27
- import { join } from 'path';
28
- import { existsSync, mkdirSync, writeFileSync, unlinkSync } from 'fs';
29
- import { randomUUID } from 'crypto';
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
- try {
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
- try {
96
- this.heuristicStore = new HeuristicStore();
97
- }
98
- catch (e) {
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 { /* not a git repo */ }
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
- let continuousVerifier = null;
156
- if (this.verifier) {
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
- // Execute runnable phases (could be parallel if multiple are ready)
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 approved = await this.requestApproval(phase.config.name, phase.config.agents);
213
- if (!approved) {
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, budget: orchestration.config.costBudget,
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?.stop();
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
- // Build phase data once for all consumers (avoids 4x redundant mapping)
274
- const phaseData = orchestration.phases.map(p => ({
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.list().map(p => p.name).join(', ')}`);
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
- for (const phase of config.phases) {
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.list().map(p => p.name).join(', ')}`);
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
- if (this.traceStore) {
424
- try {
425
- const similar = this.traceStore.findSimilar(task, 3);
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
- for (const phase of phases) {
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
- // Retrieve relevant heuristics and inject into first phase
489
- if (this.heuristicStore) {
490
- try {
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 (this.verifier && (phase.config.name.includes('implement') || phase.config.name.includes('integrate'))) {
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
- try {
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
- try {
899
- this.traceStore?.close();
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
- // ─── Shared Context ────────────────────────────────────────────
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
- * Write shared context to a file for parallel agents to read,
941
- * avoiding duplicating large context in each agent's prompt.
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
- writeSharedContext(context) {
944
- if (!context || context.length < 2000)
945
- return null; // Not worth sharing for small contexts
946
- const contextDir = join(this.options.cwd ?? process.cwd(), '.swarm');
947
- if (!existsSync(contextDir))
948
- mkdirSync(contextDir, { recursive: true });
949
- const contextPath = join(contextDir, `shared-context-${randomUUID().slice(0, 8)}.md`);
950
- writeFileSync(contextPath, redactSecrets(context), { mode: 0o600 });
951
- this.sharedContextFiles.push(contextPath);
952
- return contextPath;
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: [],