@soleri/core 9.11.0 → 9.13.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 (234) hide show
  1. package/dist/adapters/types.d.ts +2 -0
  2. package/dist/adapters/types.d.ts.map +1 -1
  3. package/dist/brain/brain.d.ts +5 -1
  4. package/dist/brain/brain.d.ts.map +1 -1
  5. package/dist/brain/brain.js +97 -10
  6. package/dist/brain/brain.js.map +1 -1
  7. package/dist/dream/cron-manager.d.ts +10 -0
  8. package/dist/dream/cron-manager.d.ts.map +1 -0
  9. package/dist/dream/cron-manager.js +122 -0
  10. package/dist/dream/cron-manager.js.map +1 -0
  11. package/dist/dream/dream-engine.d.ts +34 -0
  12. package/dist/dream/dream-engine.d.ts.map +1 -0
  13. package/dist/dream/dream-engine.js +88 -0
  14. package/dist/dream/dream-engine.js.map +1 -0
  15. package/dist/dream/dream-ops.d.ts +8 -0
  16. package/dist/dream/dream-ops.d.ts.map +1 -0
  17. package/dist/dream/dream-ops.js +49 -0
  18. package/dist/dream/dream-ops.js.map +1 -0
  19. package/dist/dream/index.d.ts +7 -0
  20. package/dist/dream/index.d.ts.map +1 -0
  21. package/dist/dream/index.js +5 -0
  22. package/dist/dream/index.js.map +1 -0
  23. package/dist/dream/schema.d.ts +3 -0
  24. package/dist/dream/schema.d.ts.map +1 -0
  25. package/dist/dream/schema.js +16 -0
  26. package/dist/dream/schema.js.map +1 -0
  27. package/dist/embeddings/index.d.ts +5 -0
  28. package/dist/embeddings/index.d.ts.map +1 -0
  29. package/dist/embeddings/index.js +3 -0
  30. package/dist/embeddings/index.js.map +1 -0
  31. package/dist/embeddings/openai-provider.d.ts +31 -0
  32. package/dist/embeddings/openai-provider.d.ts.map +1 -0
  33. package/dist/embeddings/openai-provider.js +120 -0
  34. package/dist/embeddings/openai-provider.js.map +1 -0
  35. package/dist/embeddings/pipeline.d.ts +36 -0
  36. package/dist/embeddings/pipeline.d.ts.map +1 -0
  37. package/dist/embeddings/pipeline.js +78 -0
  38. package/dist/embeddings/pipeline.js.map +1 -0
  39. package/dist/embeddings/types.d.ts +62 -0
  40. package/dist/embeddings/types.d.ts.map +1 -0
  41. package/dist/embeddings/types.js +3 -0
  42. package/dist/embeddings/types.js.map +1 -0
  43. package/dist/engine/bin/soleri-engine.js +4 -1
  44. package/dist/engine/bin/soleri-engine.js.map +1 -1
  45. package/dist/engine/module-manifest.d.ts.map +1 -1
  46. package/dist/engine/module-manifest.js +20 -0
  47. package/dist/engine/module-manifest.js.map +1 -1
  48. package/dist/engine/register-engine.d.ts.map +1 -1
  49. package/dist/engine/register-engine.js +12 -0
  50. package/dist/engine/register-engine.js.map +1 -1
  51. package/dist/flows/chain-types.d.ts +8 -8
  52. package/dist/flows/dispatch-registry.d.ts +15 -1
  53. package/dist/flows/dispatch-registry.d.ts.map +1 -1
  54. package/dist/flows/dispatch-registry.js +28 -1
  55. package/dist/flows/dispatch-registry.js.map +1 -1
  56. package/dist/flows/executor.d.ts +20 -2
  57. package/dist/flows/executor.d.ts.map +1 -1
  58. package/dist/flows/executor.js +79 -1
  59. package/dist/flows/executor.js.map +1 -1
  60. package/dist/flows/index.d.ts +2 -1
  61. package/dist/flows/index.d.ts.map +1 -1
  62. package/dist/flows/index.js.map +1 -1
  63. package/dist/flows/types.d.ts +43 -21
  64. package/dist/flows/types.d.ts.map +1 -1
  65. package/dist/index.d.ts +6 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +4 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/persona/defaults.d.ts +8 -0
  70. package/dist/persona/defaults.d.ts.map +1 -1
  71. package/dist/persona/defaults.js +49 -0
  72. package/dist/persona/defaults.js.map +1 -1
  73. package/dist/plugins/types.d.ts +31 -31
  74. package/dist/runtime/admin-ops.d.ts.map +1 -1
  75. package/dist/runtime/admin-ops.js +15 -0
  76. package/dist/runtime/admin-ops.js.map +1 -1
  77. package/dist/runtime/admin-setup-ops.js +2 -2
  78. package/dist/runtime/admin-setup-ops.js.map +1 -1
  79. package/dist/runtime/embedding-ops.d.ts +12 -0
  80. package/dist/runtime/embedding-ops.d.ts.map +1 -0
  81. package/dist/runtime/embedding-ops.js +96 -0
  82. package/dist/runtime/embedding-ops.js.map +1 -0
  83. package/dist/runtime/facades/embedding-facade.d.ts +7 -0
  84. package/dist/runtime/facades/embedding-facade.d.ts.map +1 -0
  85. package/dist/runtime/facades/embedding-facade.js +8 -0
  86. package/dist/runtime/facades/embedding-facade.js.map +1 -0
  87. package/dist/runtime/facades/index.d.ts.map +1 -1
  88. package/dist/runtime/facades/index.js +12 -0
  89. package/dist/runtime/facades/index.js.map +1 -1
  90. package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
  91. package/dist/runtime/facades/orchestrate-facade.js +120 -0
  92. package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
  93. package/dist/runtime/feature-flags.d.ts.map +1 -1
  94. package/dist/runtime/feature-flags.js +4 -0
  95. package/dist/runtime/feature-flags.js.map +1 -1
  96. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  97. package/dist/runtime/orchestrate-ops.js +140 -9
  98. package/dist/runtime/orchestrate-ops.js.map +1 -1
  99. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  100. package/dist/runtime/planning-extra-ops.js +51 -0
  101. package/dist/runtime/planning-extra-ops.js.map +1 -1
  102. package/dist/runtime/preflight.d.ts +32 -0
  103. package/dist/runtime/preflight.d.ts.map +1 -0
  104. package/dist/runtime/preflight.js +29 -0
  105. package/dist/runtime/preflight.js.map +1 -0
  106. package/dist/runtime/runtime.d.ts.map +1 -1
  107. package/dist/runtime/runtime.js +33 -2
  108. package/dist/runtime/runtime.js.map +1 -1
  109. package/dist/runtime/types.d.ts +27 -0
  110. package/dist/runtime/types.d.ts.map +1 -1
  111. package/dist/skills/step-tracker.d.ts +39 -0
  112. package/dist/skills/step-tracker.d.ts.map +1 -0
  113. package/dist/skills/step-tracker.js +105 -0
  114. package/dist/skills/step-tracker.js.map +1 -0
  115. package/dist/skills/sync-skills.d.ts +3 -2
  116. package/dist/skills/sync-skills.d.ts.map +1 -1
  117. package/dist/skills/sync-skills.js +42 -8
  118. package/dist/skills/sync-skills.js.map +1 -1
  119. package/dist/subagent/dispatcher.d.ts +4 -3
  120. package/dist/subagent/dispatcher.d.ts.map +1 -1
  121. package/dist/subagent/dispatcher.js +57 -35
  122. package/dist/subagent/dispatcher.js.map +1 -1
  123. package/dist/subagent/index.d.ts +1 -0
  124. package/dist/subagent/index.d.ts.map +1 -1
  125. package/dist/subagent/index.js.map +1 -1
  126. package/dist/subagent/orphan-reaper.d.ts +51 -4
  127. package/dist/subagent/orphan-reaper.d.ts.map +1 -1
  128. package/dist/subagent/orphan-reaper.js +103 -3
  129. package/dist/subagent/orphan-reaper.js.map +1 -1
  130. package/dist/subagent/types.d.ts +7 -0
  131. package/dist/subagent/types.d.ts.map +1 -1
  132. package/dist/subagent/workspace-resolver.d.ts +2 -0
  133. package/dist/subagent/workspace-resolver.d.ts.map +1 -1
  134. package/dist/subagent/workspace-resolver.js +3 -1
  135. package/dist/subagent/workspace-resolver.js.map +1 -1
  136. package/dist/vault/vault-entries.d.ts +18 -0
  137. package/dist/vault/vault-entries.d.ts.map +1 -1
  138. package/dist/vault/vault-entries.js +73 -0
  139. package/dist/vault/vault-entries.js.map +1 -1
  140. package/dist/vault/vault-manager.d.ts.map +1 -1
  141. package/dist/vault/vault-manager.js +1 -0
  142. package/dist/vault/vault-manager.js.map +1 -1
  143. package/dist/vault/vault-schema.d.ts.map +1 -1
  144. package/dist/vault/vault-schema.js +14 -0
  145. package/dist/vault/vault-schema.js.map +1 -1
  146. package/dist/vault/vault.d.ts +1 -0
  147. package/dist/vault/vault.d.ts.map +1 -1
  148. package/dist/vault/vault.js.map +1 -1
  149. package/package.json +3 -5
  150. package/src/__tests__/cron-manager.test.ts +132 -0
  151. package/src/__tests__/deviation-detection.test.ts +234 -0
  152. package/src/__tests__/embeddings.test.ts +536 -0
  153. package/src/__tests__/preflight.test.ts +97 -0
  154. package/src/__tests__/step-persistence.test.ts +324 -0
  155. package/src/__tests__/step-tracker.test.ts +260 -0
  156. package/src/__tests__/subagent/dispatcher.test.ts +122 -4
  157. package/src/__tests__/subagent/orphan-reaper.test.ts +148 -12
  158. package/src/__tests__/subagent/process-lifecycle.test.ts +422 -0
  159. package/src/__tests__/subagent/workspace-resolver.test.ts +6 -1
  160. package/src/adapters/types.ts +2 -0
  161. package/src/brain/brain.ts +117 -9
  162. package/src/dream/cron-manager.ts +137 -0
  163. package/src/dream/dream-engine.ts +119 -0
  164. package/src/dream/dream-ops.ts +56 -0
  165. package/src/dream/dream.test.ts +182 -0
  166. package/src/dream/index.ts +6 -0
  167. package/src/dream/schema.ts +17 -0
  168. package/src/embeddings/openai-provider.ts +158 -0
  169. package/src/embeddings/pipeline.ts +126 -0
  170. package/src/embeddings/types.ts +67 -0
  171. package/src/engine/bin/soleri-engine.ts +4 -1
  172. package/src/engine/module-manifest.test.ts +4 -4
  173. package/src/engine/module-manifest.ts +20 -0
  174. package/src/engine/register-engine.ts +12 -0
  175. package/src/flows/dispatch-registry.ts +44 -1
  176. package/src/flows/executor.ts +93 -2
  177. package/src/flows/index.ts +2 -0
  178. package/src/flows/types.ts +39 -1
  179. package/src/index.ts +12 -0
  180. package/src/persona/defaults.test.ts +39 -1
  181. package/src/persona/defaults.ts +65 -0
  182. package/src/planning/goal-ancestry.test.ts +3 -5
  183. package/src/planning/planner.test.ts +2 -3
  184. package/src/runtime/admin-ops.test.ts +2 -2
  185. package/src/runtime/admin-ops.ts +17 -0
  186. package/src/runtime/admin-setup-ops.ts +2 -2
  187. package/src/runtime/embedding-ops.ts +116 -0
  188. package/src/runtime/facades/admin-facade.test.ts +31 -0
  189. package/src/runtime/facades/embedding-facade.ts +11 -0
  190. package/src/runtime/facades/index.ts +12 -0
  191. package/src/runtime/facades/orchestrate-facade.test.ts +16 -0
  192. package/src/runtime/facades/orchestrate-facade.ts +146 -0
  193. package/src/runtime/feature-flags.ts +4 -0
  194. package/src/runtime/orchestrate-ops.test.ts +131 -0
  195. package/src/runtime/orchestrate-ops.ts +158 -10
  196. package/src/runtime/planning-extra-ops.ts +77 -0
  197. package/src/runtime/preflight.ts +53 -0
  198. package/src/runtime/runtime.ts +41 -2
  199. package/src/runtime/types.ts +20 -0
  200. package/src/skills/__tests__/sync-skills.test.ts +132 -0
  201. package/src/skills/step-tracker.ts +162 -0
  202. package/src/skills/sync-skills.ts +54 -9
  203. package/src/subagent/dispatcher.ts +62 -39
  204. package/src/subagent/index.ts +1 -0
  205. package/src/subagent/orphan-reaper.test.ts +135 -0
  206. package/src/subagent/orphan-reaper.ts +130 -7
  207. package/src/subagent/types.ts +10 -0
  208. package/src/subagent/workspace-resolver.ts +3 -1
  209. package/src/vault/vault-entries.ts +112 -0
  210. package/src/vault/vault-manager.ts +1 -0
  211. package/src/vault/vault-scaling.test.ts +3 -2
  212. package/src/vault/vault-schema.ts +15 -0
  213. package/src/vault/vault.ts +1 -0
  214. package/vitest.config.ts +2 -1
  215. package/dist/brain/strength-scorer.d.ts +0 -31
  216. package/dist/brain/strength-scorer.d.ts.map +0 -1
  217. package/dist/brain/strength-scorer.js +0 -264
  218. package/dist/brain/strength-scorer.js.map +0 -1
  219. package/dist/engine/index.d.ts +0 -21
  220. package/dist/engine/index.d.ts.map +0 -1
  221. package/dist/engine/index.js +0 -18
  222. package/dist/engine/index.js.map +0 -1
  223. package/dist/hooks/index.d.ts +0 -2
  224. package/dist/hooks/index.d.ts.map +0 -1
  225. package/dist/hooks/index.js +0 -2
  226. package/dist/hooks/index.js.map +0 -1
  227. package/dist/persona/index.d.ts +0 -5
  228. package/dist/persona/index.d.ts.map +0 -1
  229. package/dist/persona/index.js +0 -4
  230. package/dist/persona/index.js.map +0 -1
  231. package/dist/vault/vault-interfaces.d.ts +0 -153
  232. package/dist/vault/vault-interfaces.d.ts.map +0 -1
  233. package/dist/vault/vault-interfaces.js +0 -2
  234. package/dist/vault/vault-interfaces.js.map +0 -1
@@ -245,6 +245,137 @@ describe('createOrchestrateOps', () => {
245
245
  expect(rt.contextHealth.track).toHaveBeenCalled();
246
246
  expect(rt.contextHealth.check).toHaveBeenCalled();
247
247
  });
248
+
249
+ // ─── Post-dispatch cleanup (subagent path) ─────────────────
250
+ describe('post-dispatch cleanup', () => {
251
+ function addSubagentDispatcher(
252
+ runtime: AgentRuntime,
253
+ opts: {
254
+ dispatchResult?: Record<string, unknown>;
255
+ dispatchError?: Error;
256
+ reapResult?: string[];
257
+ reapThrows?: boolean;
258
+ } = {},
259
+ ) {
260
+ const dispatchMock = opts.dispatchError
261
+ ? vi.fn().mockRejectedValue(opts.dispatchError)
262
+ : vi.fn().mockResolvedValue(
263
+ opts.dispatchResult ?? {
264
+ status: 'completed',
265
+ totalTasks: 1,
266
+ completed: 1,
267
+ failed: 0,
268
+ durationMs: 100,
269
+ totalUsage: {},
270
+ },
271
+ );
272
+ const reapMock = opts.reapThrows
273
+ ? vi.fn().mockImplementation(() => {
274
+ throw new Error('reap failed');
275
+ })
276
+ : vi.fn().mockReturnValue({
277
+ reaped: opts.reapResult ?? [],
278
+ alive: [],
279
+ });
280
+ const cleanupMock = vi.fn();
281
+
282
+ (runtime as Record<string, unknown>).subagentDispatcher = {
283
+ dispatch: dispatchMock,
284
+ reapOrphans: reapMock,
285
+ cleanup: cleanupMock,
286
+ };
287
+
288
+ return { dispatchMock, reapMock, cleanupMock };
289
+ }
290
+
291
+ it('calls reapOrphans after successful subagent dispatch', async () => {
292
+ const { reapMock } = addSubagentDispatcher(rt);
293
+ ops = createOrchestrateOps(rt);
294
+
295
+ const op = findOp(ops, 'orchestrate_execute');
296
+ await op.handler({ planId: 'legacy-plan', subagent: true });
297
+
298
+ expect(reapMock).toHaveBeenCalledTimes(1);
299
+ });
300
+
301
+ it('calls reapOrphans even when dispatch fails', async () => {
302
+ const { reapMock } = addSubagentDispatcher(rt, {
303
+ dispatchError: new Error('dispatch boom'),
304
+ });
305
+ ops = createOrchestrateOps(rt);
306
+
307
+ const op = findOp(ops, 'orchestrate_execute');
308
+ await expect(op.handler({ planId: 'legacy-plan', subagent: true })).rejects.toThrow(
309
+ 'dispatch boom',
310
+ );
311
+
312
+ expect(reapMock).toHaveBeenCalledTimes(1);
313
+ });
314
+
315
+ it('includes reapedOrphans in result when orphans found', async () => {
316
+ addSubagentDispatcher(rt, {
317
+ reapResult: ['t1'],
318
+ });
319
+ ops = createOrchestrateOps(rt);
320
+
321
+ const op = findOp(ops, 'orchestrate_execute');
322
+ const result = (await op.handler({
323
+ planId: 'legacy-plan',
324
+ subagent: true,
325
+ })) as Record<string, unknown>;
326
+
327
+ expect(result).toHaveProperty('reapedOrphans');
328
+ const reaped = result.reapedOrphans as Array<{ taskId: string }>;
329
+ expect(reaped).toHaveLength(1);
330
+ expect(reaped[0].taskId).toBe('t1');
331
+ });
332
+
333
+ it('omits reapedOrphans from result when none found', async () => {
334
+ addSubagentDispatcher(rt, { reapResult: [] });
335
+ ops = createOrchestrateOps(rt);
336
+
337
+ const op = findOp(ops, 'orchestrate_execute');
338
+ const result = (await op.handler({
339
+ planId: 'legacy-plan',
340
+ subagent: true,
341
+ })) as Record<string, unknown>;
342
+
343
+ expect(result).not.toHaveProperty('reapedOrphans');
344
+ });
345
+
346
+ it('does not throw when reapOrphans fails', async () => {
347
+ addSubagentDispatcher(rt, { reapThrows: true });
348
+ ops = createOrchestrateOps(rt);
349
+
350
+ const op = findOp(ops, 'orchestrate_execute');
351
+ // Should complete successfully despite reap failure
352
+ const result = (await op.handler({
353
+ planId: 'legacy-plan',
354
+ subagent: true,
355
+ })) as Record<string, unknown>;
356
+
357
+ expect(result).toHaveProperty('subagent');
358
+ expect(result).not.toHaveProperty('reapedOrphans');
359
+ });
360
+
361
+ it('logs reaped orphans to stderr', async () => {
362
+ const stderrSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
363
+ addSubagentDispatcher(rt, {
364
+ reapResult: ['t1', 't2'],
365
+ });
366
+ ops = createOrchestrateOps(rt);
367
+
368
+ const op = findOp(ops, 'orchestrate_execute');
369
+ await op.handler({ planId: 'legacy-plan', subagent: true });
370
+
371
+ expect(stderrSpy).toHaveBeenCalledWith(
372
+ expect.stringContaining('Reaped 2 orphaned subagent(s)'),
373
+ );
374
+ expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('t1'));
375
+ expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('t2'));
376
+ stderrSpy.mockRestore();
377
+ });
378
+ });
248
379
  });
249
380
 
250
381
  // ─── orchestrate_complete ─────────────────────────────────────
@@ -15,10 +15,10 @@ import { z } from 'zod';
15
15
  import type { OpDefinition, FacadeConfig } from '../facades/types.js';
16
16
  import type { AgentRuntime } from './types.js';
17
17
  import { buildPlan } from '../flows/plan-builder.js';
18
- import { FlowExecutor } from '../flows/executor.js';
18
+ import { FlowExecutor, getPlanRunDir, loadManifest, saveManifest } from '../flows/executor.js';
19
19
  import { createDispatcher } from '../flows/dispatch-registry.js';
20
20
  import { runEpilogue } from '../flows/epilogue.js';
21
- import type { OrchestrationPlan, ExecutionResult } from '../flows/types.js';
21
+ import type { OrchestrationPlan, ExecutionResult, PlanRunManifest } from '../flows/types.js';
22
22
  import type { ContextHealthStatus } from './context-health.js';
23
23
  import type { OperatorSignals } from '../operator/operator-context-types.js';
24
24
  import { loadAgentWorkflows, getWorkflowForIntent } from '../workflows/workflow-loader.js';
@@ -129,6 +129,13 @@ export function applyWorkflowOverride(plan: OrchestrationPlan, override: Workflo
129
129
  plan.estimatedTools = plan.steps.reduce((acc, s) => acc + s.tools.length, 0);
130
130
  }
131
131
 
132
+ // Set allowedTools from the merged tool set
133
+ for (const step of plan.steps) {
134
+ if (step.tools.length > 0) {
135
+ step.allowedTools = [...new Set(step.tools)];
136
+ }
137
+ }
138
+
132
139
  // Add workflow info to warnings for visibility
133
140
  plan.warnings.push(
134
141
  `Workflow override "${override.name}" applied (${override.gates.length} gate(s), ${override.tools.length} tool(s)).`,
@@ -156,9 +163,14 @@ const planStore = new Map<string, PlanEntry>();
156
163
  * If facades are provided, uses the full dispatch registry.
157
164
  * Otherwise, falls back to a simple runtime-based dispatcher.
158
165
  */
159
- function buildDispatch(agentId: string, runtime: AgentRuntime, facades?: FacadeConfig[]) {
166
+ function buildDispatch(
167
+ agentId: string,
168
+ runtime: AgentRuntime,
169
+ facades?: FacadeConfig[],
170
+ activePlan?: import('../flows/dispatch-registry.js').ActivePlanRef,
171
+ ) {
160
172
  if (facades && facades.length > 0) {
161
- return createDispatcher(agentId, facades);
173
+ return createDispatcher(agentId, facades, activePlan);
162
174
  }
163
175
 
164
176
  // Fallback: runtime-based dispatch for known tool patterns
@@ -534,10 +546,27 @@ export function createOrchestrateOps(
534
546
  })) ??
535
547
  [];
536
548
 
537
- const aggregated = await runtime.subagentDispatcher.dispatch(tasks, {
538
- parallel: parallelMode ?? true,
539
- maxConcurrent: maxConcurrentParam ?? 3,
540
- });
549
+ let aggregated;
550
+ let reapedOrphans: { taskId: string; pid?: number }[] = [];
551
+ try {
552
+ aggregated = await runtime.subagentDispatcher.dispatch(tasks, {
553
+ parallel: parallelMode ?? true,
554
+ maxConcurrent: maxConcurrentParam ?? 3,
555
+ });
556
+ } finally {
557
+ // Post-dispatch cleanup: reap orphaned subagent processes
558
+ try {
559
+ const reapResult = runtime.subagentDispatcher.reapOrphans();
560
+ if (reapResult.reaped.length > 0) {
561
+ reapedOrphans = reapResult.reaped.map((taskId) => ({ taskId }));
562
+ console.error(
563
+ `[soleri] Reaped ${reapResult.reaped.length} orphaned subagent(s): ${reapResult.reaped.join(', ')}`,
564
+ );
565
+ }
566
+ } catch {
567
+ // Orphan reaping is best-effort — never blocks dispatch result
568
+ }
569
+ }
541
570
 
542
571
  // Track in brain session
543
572
  const existingSession = brainIntelligence.getSessionByPlanId(planId);
@@ -569,6 +598,7 @@ export function createOrchestrateOps(
569
598
  durationMs: aggregated.durationMs,
570
599
  totalUsage: aggregated.totalUsage,
571
600
  },
601
+ ...(reapedOrphans.length > 0 ? { reapedOrphans } : {}),
572
602
  ...(healthWarning ? { contextHealth: healthWarning } : {}),
573
603
  };
574
604
  }
@@ -626,8 +656,17 @@ export function createOrchestrateOps(
626
656
 
627
657
  if (entry) {
628
658
  // Flow-engine execution path
629
- const dispatch = buildDispatch(agentId, runtime, facades);
630
- const executor = new FlowExecutor(dispatch);
659
+ const activePlanRef = {
660
+ steps: entry.plan.steps.map((s) => ({
661
+ id: s.id,
662
+ allowedTools: s.allowedTools,
663
+ status: s.status,
664
+ })),
665
+ deviations: entry.plan.deviations,
666
+ };
667
+ const dispatch = buildDispatch(agentId, runtime, facades, activePlanRef);
668
+ const projectPath = (params.projectPath as string) ?? '.';
669
+ const executor = new FlowExecutor(dispatch, projectPath);
631
670
  const executionResult = await executor.execute(entry.plan);
632
671
 
633
672
  // Store result
@@ -1326,5 +1365,114 @@ export function createOrchestrateOps(
1326
1365
  };
1327
1366
  },
1328
1367
  },
1368
+
1369
+ // ─── orchestrate_rerun_step ──────────────────────────────────────
1370
+ {
1371
+ name: 'orchestrate_rerun_step',
1372
+ description:
1373
+ 'Re-execute a single plan step without full restart. Marks the target step as invalidated then rerun, ' +
1374
+ 'marks downstream steps as stale (or rerun if within cascadeTo range). ' +
1375
+ 'Reads and writes the plan-run manifest on disk.',
1376
+ auth: 'write',
1377
+ schema: z.object({
1378
+ planId: z.string().describe('Plan ID'),
1379
+ stepNumber: z.number().describe('0-based step index to re-run'),
1380
+ reason: z.string().describe('Why the step is being re-run'),
1381
+ projectPath: z
1382
+ .string()
1383
+ .optional()
1384
+ .default('.')
1385
+ .describe('Project root (for manifest location)'),
1386
+ cascadeTo: z
1387
+ .number()
1388
+ .optional()
1389
+ .describe(
1390
+ 'If set, also mark steps up to this index (exclusive) as rerun instead of just stale',
1391
+ ),
1392
+ }),
1393
+ handler: async (params) => {
1394
+ const planId = params.planId as string;
1395
+ const stepNumber = params.stepNumber as number;
1396
+ const reason = params.reason as string;
1397
+ const projectPath = params.projectPath as string;
1398
+ const cascadeTo = params.cascadeTo as number | undefined;
1399
+
1400
+ const runDir = getPlanRunDir(projectPath, planId);
1401
+ let manifest: PlanRunManifest;
1402
+ try {
1403
+ manifest = loadManifest(runDir, planId);
1404
+ } catch (err) {
1405
+ return { error: `Failed to load manifest: ${(err as Error).message}` };
1406
+ }
1407
+
1408
+ const stepKeys = Object.keys(manifest.steps);
1409
+ if (stepKeys.length === 0 && stepNumber > 0) {
1410
+ return {
1411
+ error:
1412
+ 'No step data in manifest — the plan may not have been executed with persistence enabled.',
1413
+ };
1414
+ }
1415
+
1416
+ // Find the step key at the target index by checking all steps
1417
+ // Steps are keyed by stepId — we match by position in the plan
1418
+ const allStepIds = Object.keys(manifest.steps);
1419
+ const targetStepId = allStepIds[stepNumber];
1420
+
1421
+ if (!targetStepId && !manifest.steps[String(stepNumber)]) {
1422
+ return {
1423
+ error: `Step ${stepNumber} not found in manifest. Available steps: ${allStepIds.join(', ') || '(none)'}`,
1424
+ };
1425
+ }
1426
+
1427
+ const now = new Date().toISOString();
1428
+ const affected: { stepId: string; status: string }[] = [];
1429
+
1430
+ // Mark all steps by their position
1431
+ const sortedStepIds = Object.keys(manifest.steps);
1432
+ for (let i = 0; i < sortedStepIds.length; i++) {
1433
+ const sid = sortedStepIds[i];
1434
+ const state = manifest.steps[sid];
1435
+
1436
+ if (i === stepNumber) {
1437
+ // Target step: invalidated → rerun
1438
+ state.status = 'rerun';
1439
+ state.rerunCount += 1;
1440
+ state.rerunReason = reason;
1441
+ state.timestamp = now;
1442
+ affected.push({ stepId: sid, status: 'rerun' });
1443
+ } else if (i > stepNumber) {
1444
+ // Downstream step
1445
+ if (cascadeTo !== undefined && i < cascadeTo) {
1446
+ state.status = 'rerun';
1447
+ state.rerunCount += 1;
1448
+ state.rerunReason = `Cascade from step ${stepNumber}: ${reason}`;
1449
+ state.timestamp = now;
1450
+ affected.push({ stepId: sid, status: 'rerun' });
1451
+ } else {
1452
+ state.status = 'stale';
1453
+ state.timestamp = now;
1454
+ affected.push({ stepId: sid, status: 'stale' });
1455
+ }
1456
+ }
1457
+ }
1458
+
1459
+ manifest.lastRun = now;
1460
+
1461
+ try {
1462
+ saveManifest(runDir, manifest);
1463
+ } catch (err) {
1464
+ return { error: `Failed to save manifest: ${(err as Error).message}` };
1465
+ }
1466
+
1467
+ return {
1468
+ planId,
1469
+ stepNumber,
1470
+ reason,
1471
+ cascadeTo: cascadeTo ?? null,
1472
+ affected,
1473
+ manifestPath: runDir,
1474
+ };
1475
+ },
1476
+ },
1329
1477
  ];
1330
1478
  }
@@ -9,10 +9,13 @@
9
9
  * deliverable submission, and deliverable verification.
10
10
  */
11
11
 
12
+ import fs from 'node:fs';
12
13
  import { z } from 'zod';
13
14
  import type { OpDefinition } from '../facades/types.js';
14
15
  import type { AgentRuntime } from './types.js';
15
16
  import type { DriftItem, TaskEvidence } from '../planning/planner.js';
17
+ import type { PlanRunManifest } from '../flows/types.js';
18
+ import { getPlanRunDir } from '../flows/executor.js';
16
19
  import { collectGitEvidence } from '../planning/evidence-collector.js';
17
20
  import { matchPlaybooks, type PlaybookMatchResult } from '../playbooks/index.js';
18
21
  import { entryToPlaybookDefinition } from '../playbooks/index.js';
@@ -178,6 +181,11 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
178
181
  schema: z.object({
179
182
  planId: z.string().describe('Plan ID to reconcile'),
180
183
  actualOutcome: z.string().describe('Description of what actually happened'),
184
+ projectPath: z
185
+ .string()
186
+ .optional()
187
+ .default('.')
188
+ .describe('Project root (for reading rerun manifest)'),
181
189
  driftItems: z
182
190
  .array(
183
191
  z.object({
@@ -195,10 +203,79 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
195
203
  actualOutcome: params.actualOutcome as string,
196
204
  driftItems: params.driftItems as DriftItem[] | undefined,
197
205
  });
206
+
207
+ // Add tool deviations to drift report
208
+ const deviations =
209
+ (
210
+ plan as unknown as {
211
+ deviations?: Array<{
212
+ stepId: string;
213
+ expectedTools: string[];
214
+ actualTool: string;
215
+ timestamp: string;
216
+ }>;
217
+ }
218
+ ).deviations ?? [];
219
+ const toolDeviations =
220
+ deviations.length > 0
221
+ ? {
222
+ count: deviations.length,
223
+ byStep: Object.entries(
224
+ deviations.reduce(
225
+ (acc, d) => {
226
+ (acc[d.stepId] = acc[d.stepId] || []).push(d);
227
+ return acc;
228
+ },
229
+ {} as Record<string, typeof deviations>,
230
+ ),
231
+ ).map(([stepId, devs]) => ({
232
+ stepId,
233
+ deviationCount: devs.length,
234
+ unexpectedTools: [...new Set(devs.map((d) => d.actualTool))],
235
+ })),
236
+ }
237
+ : undefined;
238
+
239
+ // Attach rerun report if a plan-run manifest exists
240
+ let rerunReport: {
241
+ totalReruns: number;
242
+ staleSteps: number;
243
+ steps: { stepId: string; status: string; rerunCount: number; rerunReason?: string }[];
244
+ } | null = null;
245
+
246
+ try {
247
+ const projectPath = params.projectPath as string;
248
+ const runDir = getPlanRunDir(projectPath, params.planId as string);
249
+ const manifestPath = `${runDir}/manifest.json`;
250
+ if (fs.existsSync(manifestPath)) {
251
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as PlanRunManifest;
252
+ const entries = Object.entries(manifest.steps);
253
+ const rerunSteps = entries.filter(
254
+ ([, s]) => s.rerunCount > 0 || s.status === 'rerun' || s.status === 'stale',
255
+ );
256
+ if (rerunSteps.length > 0) {
257
+ rerunReport = {
258
+ totalReruns: rerunSteps.reduce((sum, [, s]) => sum + s.rerunCount, 0),
259
+ staleSteps: entries.filter(([, s]) => s.status === 'stale').length,
260
+ steps: rerunSteps.map(([id, s]) => ({
261
+ stepId: id,
262
+ status: s.status,
263
+ rerunCount: s.rerunCount,
264
+ rerunReason: s.rerunReason,
265
+ })),
266
+ };
267
+ }
268
+ }
269
+ } catch {
270
+ // Manifest reading is best-effort
271
+ }
272
+
198
273
  return {
199
274
  reconciled: true,
200
275
  accuracy: plan.reconciliation!.accuracy,
201
276
  driftCount: plan.reconciliation!.driftItems.length,
277
+ toolDeviations,
278
+ rerunReport,
202
279
  plan,
203
280
  };
204
281
  },
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Builds the pre-flight manifest for session_start responses.
3
+ * Extracted as a pure function for testability.
4
+ */
5
+
6
+ import type { PreflightManifest } from './types.js';
7
+
8
+ export interface PreflightInput {
9
+ /** Facade definitions: array of { name, ops: Array<{ name, description }> } */
10
+ facades: Array<{
11
+ name: string;
12
+ ops: Array<{ name: string; description: string }>;
13
+ }>;
14
+ /** Skill names installed for this agent */
15
+ skills: string[];
16
+ /** Plans currently in executing state */
17
+ executingPlans: Array<{ id: string; objective: string; status: string }>;
18
+ /** Whether the vault is connected */
19
+ vaultConnected?: boolean;
20
+ /** Vault stats */
21
+ vaultStats: {
22
+ totalEntries: number;
23
+ byDomain: Record<string, number>;
24
+ };
25
+ }
26
+
27
+ export function buildPreflightManifest(input: PreflightInput): PreflightManifest {
28
+ const tools: PreflightManifest['tools'] = [];
29
+ for (const facade of input.facades) {
30
+ for (const op of facade.ops) {
31
+ tools.push({ facade: facade.name, op: op.name, description: op.description });
32
+ }
33
+ }
34
+
35
+ const activePlans = input.executingPlans.map((p) => ({
36
+ planId: p.id,
37
+ title: p.objective,
38
+ status: p.status,
39
+ }));
40
+
41
+ const domains = Object.keys(input.vaultStats.byDomain);
42
+
43
+ return {
44
+ tools,
45
+ skills: input.skills,
46
+ activePlans,
47
+ vaultSummary: {
48
+ entryCount: input.vaultStats.totalEntries,
49
+ connected: input.vaultConnected ?? true,
50
+ domains,
51
+ },
52
+ };
53
+ }
@@ -56,6 +56,9 @@ import { PipelineRunner } from '../queue/pipeline-runner.js';
56
56
  import { evaluateQuality } from '../curator/quality-gate.js';
57
57
  import { classifyEntry } from '../curator/classifier.js';
58
58
  import type { AgentRuntimeConfig, AgentRuntime } from './types.js';
59
+ import type { EmbeddingProvider } from '../embeddings/types.js';
60
+ import { OpenAIEmbeddingProvider } from '../embeddings/openai-provider.js';
61
+ import { EmbeddingPipeline } from '../embeddings/pipeline.js';
59
62
  import { loadPersona } from '../persona/loader.js';
60
63
  import { generatePersonaInstructions } from '../persona/prompt-generator.js';
61
64
  import { OperatorProfileStore } from '../operator/operator-profile.js';
@@ -104,12 +107,46 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
104
107
  }
105
108
  }
106
109
 
110
+ // Feature Flags — file-based + env var + runtime toggles (created early so other modules can check)
111
+ const flags = new FeatureFlags(getAgentFlagsPath(agentId));
112
+
107
113
  // Planner — multi-step task tracking
108
114
  const planner = new Planner(plansPath);
109
115
 
116
+ // ─── Embedding Provider (optional) ────────────────────────────────
117
+ // Only initialized when both config.embedding is present AND the
118
+ // 'embedding-enabled' feature flag is on. Brain continues without
119
+ // embeddings when either condition is unmet (vector weight stays 0).
120
+ let embeddingProvider: EmbeddingProvider | undefined;
121
+ let embeddingPipeline: EmbeddingPipeline | undefined;
122
+
123
+ if (config.embedding && flags.isEnabled('embedding-enabled')) {
124
+ try {
125
+ const embeddingConfig = config.embedding;
126
+ if (embeddingConfig.provider === 'openai') {
127
+ const openaiPool = new KeyPool(loadKeyPoolConfig(agentId).openai);
128
+ embeddingProvider = new OpenAIEmbeddingProvider(embeddingConfig, openaiPool);
129
+ }
130
+ // Future providers (ollama, etc.) would be added here
131
+
132
+ if (embeddingProvider) {
133
+ embeddingPipeline = new EmbeddingPipeline(embeddingProvider, vault.getProvider());
134
+ logger.info(
135
+ `[Embedding] Initialized: ${embeddingProvider.providerName}/${embeddingProvider.model} (${embeddingProvider.dimensions}d)`,
136
+ );
137
+ }
138
+ } catch (err) {
139
+ logger.warn(
140
+ `[Embedding] Failed to initialize: ${err instanceof Error ? err.message : String(err)}`,
141
+ );
142
+ // Graceful degradation — continue without embeddings
143
+ }
144
+ }
145
+
110
146
  // Brain — intelligence layer (TF-IDF scoring, auto-tagging, dedup)
111
147
  // Pass vaultManager so intelligentSearch queries all connected sources (not just agent tier)
112
- const brain = new Brain(vault, vaultManager);
148
+ // Pass embeddingProvider for hybrid FTS5+vector search when available
149
+ const brain = new Brain(vault, vaultManager, embeddingProvider);
113
150
 
114
151
  // Brain Intelligence — pattern strengths, session knowledge, intelligence pipeline
115
152
  const brainIntelligence = new BrainIntelligence(vault, brain);
@@ -366,7 +403,7 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
366
403
  intakePipeline,
367
404
  textIngester,
368
405
  authPolicy: { mode: 'permissive', callerLevel: 'admin' },
369
- flags: new FeatureFlags(getAgentFlagsPath(agentId)),
406
+ flags,
370
407
  health,
371
408
  playbookExecutor,
372
409
  pluginRegistry,
@@ -392,6 +429,8 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
392
429
  const p = loadPersona(agentId, config.persona ?? undefined);
393
430
  return generatePersonaInstructions(p);
394
431
  })(),
432
+ embeddingProvider,
433
+ embeddingPipeline,
395
434
  adapterRegistry,
396
435
  subagentDispatcher,
397
436
  contextHealth: new ContextHealthMonitor(),
@@ -39,6 +39,20 @@ import type { ContextHealthMonitor } from './context-health.js';
39
39
  import type { ShutdownRegistry } from './shutdown-registry.js';
40
40
  import type { RuntimeAdapterRegistry } from '../adapters/registry.js';
41
41
  import type { SubagentDispatcher } from '../subagent/dispatcher.js';
42
+ import type { EmbeddingConfig, EmbeddingProvider } from '../embeddings/types.js';
43
+ import type { EmbeddingPipeline } from '../embeddings/pipeline.js';
44
+
45
+ /** Pre-flight manifest returned by session_start for agent self-awareness. */
46
+ export interface PreflightManifest {
47
+ tools: Array<{ facade: string; op: string; description: string }>;
48
+ skills: string[];
49
+ activePlans: Array<{ planId: string; title: string; status: string }>;
50
+ vaultSummary: {
51
+ entryCount: number;
52
+ connected: boolean;
53
+ domains: string[];
54
+ };
55
+ }
42
56
 
43
57
  /**
44
58
  * Configuration for creating an agent runtime.
@@ -63,6 +77,8 @@ export interface AgentRuntimeConfig {
63
77
  agentDir?: string;
64
78
  /** Persona configuration from agent.yaml. If omitted, Italian Craftsperson default is used. */
65
79
  persona?: Partial<import('../persona/types.js').PersonaConfig>;
80
+ /** Embedding provider configuration. If omitted, embeddings are disabled. */
81
+ embedding?: EmbeddingConfig;
66
82
  }
67
83
 
68
84
  /**
@@ -135,6 +151,10 @@ export interface AgentRuntime {
135
151
  adapterRegistry: RuntimeAdapterRegistry;
136
152
  /** Subagent dispatcher — spawn and manage child agent processes. */
137
153
  subagentDispatcher: SubagentDispatcher;
154
+ /** Embedding provider — generates dense vectors for hybrid search (optional). */
155
+ embeddingProvider?: EmbeddingProvider;
156
+ /** Embedding pipeline — batch and incremental embedding of vault entries (optional). */
157
+ embeddingPipeline?: EmbeddingPipeline;
138
158
  /** Context health monitor — tracks tool call volume and context window fill. */
139
159
  contextHealth: ContextHealthMonitor;
140
160
  /** Shutdown registry — centralized cleanup for timers, watchers, child processes. */