@aiwg/cli 2026.10.4 → 2026.10.6

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 (220) hide show
  1. package/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
  2. package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
  3. package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
  4. package/agentic/code/providers/model-capabilities.v1.json +74 -50
  5. package/bin/aiwg.mjs +3 -1
  6. package/dist/src/artifacts/browser-export.js +1 -0
  7. package/dist/src/artifacts/citation-parser.js +8 -1
  8. package/dist/src/artifacts/cli.js +72 -9
  9. package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
  10. package/dist/src/artifacts/fortemi-core-sync.js +26 -8
  11. package/dist/src/artifacts/graph-query.js +18 -8
  12. package/dist/src/artifacts/index-adapters.js +339 -0
  13. package/dist/src/artifacts/index-builder.js +146 -16
  14. package/dist/src/artifacts/index-files.js +15 -4
  15. package/dist/src/artifacts/index-preparation.js +91 -26
  16. package/dist/src/artifacts/index-reader.js +44 -0
  17. package/dist/src/artifacts/index-refresh.js +130 -56
  18. package/dist/src/artifacts/index-status.js +20 -5
  19. package/dist/src/artifacts/index-worker.mjs +9 -7
  20. package/dist/src/artifacts/memory-maintenance.js +503 -0
  21. package/dist/src/artifacts/prebuilt-relations.js +3 -1
  22. package/dist/src/artifacts/query-engine.js +263 -108
  23. package/dist/src/artifacts/relations-builder.js +2 -2
  24. package/dist/src/artifacts/source-graph.js +2 -2
  25. package/dist/src/artifacts/types.js +9 -1
  26. package/dist/src/cli/handlers/decision.js +19 -1
  27. package/dist/src/cli/handlers/guided-setup.js +470 -0
  28. package/dist/src/cli/handlers/help.js +2 -2
  29. package/dist/src/cli/handlers/housekeeper.js +1110 -0
  30. package/dist/src/cli/handlers/index.js +4 -2
  31. package/dist/src/cli/handlers/init.js +41 -6
  32. package/dist/src/cli/handlers/local-executor.js +5 -3
  33. package/dist/src/cli/handlers/mc.js +509 -53
  34. package/dist/src/cli/handlers/models.js +32 -2
  35. package/dist/src/cli/handlers/ralph-launcher.js +76 -4
  36. package/dist/src/cli/handlers/rules.js +1 -1
  37. package/dist/src/cli/handlers/script-runner.js +73 -2
  38. package/dist/src/cli/handlers/serve.js +5 -3
  39. package/dist/src/cli/handlers/sessions.js +40 -6
  40. package/dist/src/cli/handlers/setup.js +428 -1
  41. package/dist/src/cli/handlers/skill-lint.js +1 -1
  42. package/dist/src/cli/handlers/subcommands.js +118 -8
  43. package/dist/src/cli/handlers/use.js +279 -93
  44. package/dist/src/cli/handlers/workspace.js +3 -1
  45. package/dist/src/cli/prompt-utils.js +8 -0
  46. package/dist/src/cli/router.js +16 -1
  47. package/dist/src/config/aiwg-config.js +42 -0
  48. package/dist/src/config/cli.js +7 -3
  49. package/dist/src/config/decision-config.js +14 -1
  50. package/dist/src/config/native-sandbox-warnings.js +25 -0
  51. package/dist/src/contributors/validation.js +6 -5
  52. package/dist/src/decision/admission.js +7 -3
  53. package/dist/src/decision/benchmark.js +469 -0
  54. package/dist/src/decision/ledger.js +22 -3
  55. package/dist/src/decision/pricing.js +147 -0
  56. package/dist/src/decision/qualification/gate-evidence.js +18 -15
  57. package/dist/src/decision/qualification/load-staged.js +539 -0
  58. package/dist/src/decision/qualification/load.js +109 -33
  59. package/dist/src/decision/qualification/runner.js +5 -2
  60. package/dist/src/decision/review/service.js +6 -4
  61. package/dist/src/decision/review/validate.js +23 -2
  62. package/dist/src/decision/savings.js +8 -3
  63. package/dist/src/discovery/index.js +492 -0
  64. package/dist/src/discovery/interview.js +233 -0
  65. package/dist/src/discovery/plan-apply.js +571 -0
  66. package/dist/src/discovery/plan-store.js +171 -0
  67. package/dist/src/discovery/plan-types.js +2 -0
  68. package/dist/src/discovery/plan-validation.js +139 -0
  69. package/dist/src/discovery/types.js +2 -0
  70. package/dist/src/extensions/commands/definitions.js +49 -9
  71. package/dist/src/extensions/manifest.js +71 -44
  72. package/dist/src/extensions/project-local-discovery.js +1 -1
  73. package/dist/src/extensions/project-quickref.js +18 -17
  74. package/dist/src/extensions/validation.js +32 -29
  75. package/dist/src/housekeeper/daemon-config.js +148 -0
  76. package/dist/src/housekeeper/ipc.js +84 -0
  77. package/dist/src/housekeeper/lifecycle.js +148 -0
  78. package/dist/src/housekeeper/platform.js +35 -0
  79. package/dist/src/housekeeper/status.js +70 -0
  80. package/dist/src/housekeeper/units.js +114 -0
  81. package/dist/src/jobs/flow.js +22 -21
  82. package/dist/src/lint/rule-activation.js +20 -5
  83. package/dist/src/lint/runner.js +1 -1
  84. package/dist/src/mcp/cli.mjs +5 -6
  85. package/dist/src/mcp/config-file.mjs +6 -2
  86. package/dist/src/mcp/config-layers.mjs +23 -7
  87. package/dist/src/mcp/profiles.js +18 -31
  88. package/dist/src/mcp/profiles.mjs +18 -32
  89. package/dist/src/mcp/registry.js +7 -27
  90. package/dist/src/mcp/registry.mjs +7 -28
  91. package/dist/src/mcp/server.mjs +34 -1
  92. package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
  93. package/dist/src/mcp/tools/command-run.mjs +3 -2
  94. package/dist/src/mcp/tools/decision.mjs +49 -48
  95. package/dist/src/mcp/tools/discovery.mjs +46 -21
  96. package/dist/src/mcp/tools/orchestration.mjs +22 -22
  97. package/dist/src/mcp/tools/subsystems.mjs +9 -8
  98. package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
  99. package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
  100. package/dist/src/memory/audit.js +719 -0
  101. package/dist/src/memory/canonical-context.js +392 -13
  102. package/dist/src/memory/canonical-maintenance.js +95 -0
  103. package/dist/src/memory/cli-result.js +22 -0
  104. package/dist/src/memory/cli.js +176 -0
  105. package/dist/src/memory/context-pack.js +57 -6
  106. package/dist/src/memory/cost.js +512 -0
  107. package/dist/src/memory/firewall-classification.d.mts +7 -0
  108. package/dist/src/memory/firewall-classification.mjs +32 -0
  109. package/dist/src/memory/index.js +8 -0
  110. package/dist/src/memory/line-maintenance.js +68 -0
  111. package/dist/src/memory/maintenance-cli.js +286 -0
  112. package/dist/src/memory/maintenance-contract.js +136 -0
  113. package/dist/src/memory/maintenance-index.js +18 -0
  114. package/dist/src/memory/maintenance-owner.js +2 -0
  115. package/dist/src/memory/maintenance-record.js +138 -0
  116. package/dist/src/memory/maintenance.js +254 -0
  117. package/dist/src/memory/provider-memory-adapter.js +154 -0
  118. package/dist/src/memory/recommend-cli.js +83 -0
  119. package/dist/src/memory/recommend.js +269 -0
  120. package/dist/src/memory/scheduled.js +81 -0
  121. package/dist/src/memory/taxonomy.js +44 -0
  122. package/dist/src/mission-protocol/codecs.js +19 -9
  123. package/dist/src/models/config-loader.js +11 -1
  124. package/dist/src/models/model-capabilities.v1.json +74 -50
  125. package/dist/src/models/provider-policy.js +125 -62
  126. package/dist/src/models/resolver.js +45 -8
  127. package/dist/src/models/worker-installation.d.mts +7 -0
  128. package/dist/src/models/worker-installation.mjs +169 -37
  129. package/dist/src/models/wrapper-route.js +1 -1
  130. package/dist/src/output-modes/registry.js +8 -7
  131. package/dist/src/plugin/metadata-validator.js +1 -1
  132. package/dist/src/providers/deploy-version.d.mts +1 -0
  133. package/dist/src/providers/deploy-version.mjs +17 -0
  134. package/dist/src/providers/provider-definitions.js +19 -16
  135. package/dist/src/providers/skill-ownership.d.mts +11 -2
  136. package/dist/src/providers/skill-ownership.mjs +35 -17
  137. package/dist/src/rules/activation.d.mts +11 -2
  138. package/dist/src/rules/activation.mjs +82 -6
  139. package/dist/src/rules/inventory.js +1 -1
  140. package/dist/src/rules/rule-frontmatter.schema.json +2 -0
  141. package/dist/src/schema/zod-validation.d.mts +6 -0
  142. package/dist/src/schema/zod-validation.mjs +50 -0
  143. package/dist/src/serve/executor-registry.js +297 -20
  144. package/dist/src/serve/executor-token.d.mts +1 -0
  145. package/dist/src/serve/executor-token.mjs +8 -0
  146. package/dist/src/serve/shared-host-scheduler.js +1102 -31
  147. package/dist/src/serve/worker-invocation.js +114 -15
  148. package/dist/src/sessions/adapters/claude.js +19 -18
  149. package/dist/src/sessions/adapters/codex.js +33 -19
  150. package/dist/src/sessions/adapters/copilot.js +14 -13
  151. package/dist/src/sessions/adapters/cursor.js +19 -18
  152. package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
  153. package/dist/src/sessions/adapters/factory.js +11 -10
  154. package/dist/src/sessions/adapters/generic.js +32 -23
  155. package/dist/src/sessions/adapters/grokbot.js +35 -35
  156. package/dist/src/sessions/adapters/hermes.js +20 -19
  157. package/dist/src/sessions/adapters/muse.js +50 -49
  158. package/dist/src/sessions/adapters/omp.js +5 -4
  159. package/dist/src/sessions/adapters/openclaw.js +30 -29
  160. package/dist/src/sessions/adapters/opencode.js +26 -26
  161. package/dist/src/sessions/adapters/openhuman.js +24 -23
  162. package/dist/src/sessions/adapters/pi.js +5 -4
  163. package/dist/src/sessions/adapters/windsurf.js +8 -8
  164. package/dist/src/sessions/analytics.js +10 -9
  165. package/dist/src/sessions/batch-contracts.js +1 -1
  166. package/dist/src/sessions/batch-import.js +202 -2
  167. package/dist/src/sessions/candidates.js +7 -6
  168. package/dist/src/sessions/contracts.js +73 -54
  169. package/dist/src/sessions/coverage-preflight.js +26 -1
  170. package/dist/src/sessions/output-registration.js +11 -10
  171. package/dist/src/sessions/policy.js +18 -7
  172. package/dist/src/sessions/refresh.js +2 -3
  173. package/dist/src/sessions/repository.js +4 -2
  174. package/dist/src/sessions/workspace-discovery.js +92 -26
  175. package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
  176. package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
  177. package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
  178. package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
  179. package/dist/src/update/checker.d.mts +4 -0
  180. package/dist/src/update/checker.mjs +14 -0
  181. package/dist/src/writing/exemplar-selection.js +6 -6
  182. package/dist/src/writing/voice-evaluation.js +24 -23
  183. package/dist/src/writing/writer-migration.js +14 -13
  184. package/dist/src/writing/writer-profile.js +19 -18
  185. package/dist/src/writing/writing-brief.js +18 -17
  186. package/dist/src/writing/writing-receipt.js +47 -46
  187. package/package.json +5 -2
  188. package/schemas/decision/DecisionReview.schema.json +78 -1
  189. package/schemas/mission-v1.schema.json +3 -0
  190. package/tools/agents/deploy-agents.mjs +67 -39
  191. package/tools/agents/providers/antigravity.mjs +6 -1
  192. package/tools/agents/providers/base.mjs +19 -13
  193. package/tools/agents/providers/claude.mjs +1 -1
  194. package/tools/agents/providers/codex.mjs +50 -56
  195. package/tools/agents/providers/copilot.mjs +8 -2
  196. package/tools/agents/providers/cursor.mjs +1 -1
  197. package/tools/agents/providers/deepseek-harness.mjs +1 -1
  198. package/tools/agents/providers/factory.mjs +7 -1
  199. package/tools/agents/providers/grok-build.mjs +18 -2
  200. package/tools/agents/providers/grokbot.mjs +1 -1
  201. package/tools/agents/providers/hermes.mjs +7 -13
  202. package/tools/agents/providers/muse-hooks.mjs +21 -14
  203. package/tools/agents/providers/muse.mjs +7 -2
  204. package/tools/agents/providers/omp.mjs +28 -4
  205. package/tools/agents/providers/opencode.mjs +14 -5
  206. package/tools/agents/providers/openhuman.mjs +1 -1
  207. package/tools/agents/providers/pi.mjs +1 -1
  208. package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
  209. package/tools/agents/providers/rule-delivery.mjs +263 -71
  210. package/tools/agents/providers/windsurf.mjs +7 -0
  211. package/tools/cli/workspace-status.mjs +5 -0
  212. package/tools/commands/deploy-prompts-codex.mjs +11 -8
  213. package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
  214. package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
  215. package/tools/ralph-external/lib/worker-health.mjs +1 -1
  216. package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
  217. package/tools/ralph-external/process-monitor.mjs +7 -4
  218. package/tools/ralph-external/session-launcher.mjs +280 -32
  219. package/tools/skills/deploy-skills-codex.mjs +11 -7
  220. package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
@@ -8,7 +8,7 @@ import { isDeepStrictEqual } from 'node:util';
8
8
  import path from 'node:path';
9
9
  import { glob } from 'glob';
10
10
  import { load as loadYaml } from 'js-yaml';
11
- import { compileModelPolicy, validateCanonicalModelPolicy, validateUserProjectModelConfig, loadProviderModelCapabilities, WorkerPolicyError, } from '../../models/provider-policy.js';
11
+ import { compileModelPolicy, validateCanonicalModelPolicy, validateUserProjectModelConfig, loadProviderModelCapabilities, WorkerPolicyError, assertWorkerRoleEffort, } from '../../models/provider-policy.js';
12
12
  import { resolveGrokBuildRoleModel } from '../../models/grok-build-models.js';
13
13
  import { readAiwgConfig, emptyConfig } from '../../config/aiwg-config.js';
14
14
  import { ConfigLoader } from '../../models/config-loader.js';
@@ -39,8 +39,38 @@ export async function loadWorkerInputs(ctx) {
39
39
  return { snapshot: { config, models, modelDigests: loader.getInputDigests(), catalog,
40
40
  registry, installations: {} }, drivers: {} };
41
41
  }
42
+ // Explain missing policy before a recorded approval's digest check can
43
+ // mask it. The host owner first preserves its missing-installation errors.
44
+ const parallelism = config.parallelism;
45
+ const precheckPolicy = () => {
46
+ if (!ctx.args.some(arg => arg === '--role' || arg.startsWith('--role=')))
47
+ return;
48
+ const request = parseWorkerCommandArgs(ctx.args.slice(1));
49
+ const members = Object.entries(parallelism.groups ?? {})
50
+ .filter(([id]) => !request.group || request.group === id)
51
+ .flatMap(([, group]) => group.members).filter(member => member.enabled !== false
52
+ && parallelism.clis?.[member.cli]?.enabled !== false && member.roles.includes(request.role));
53
+ const requiresExplicit = members.length > 0 && members.every(member => {
54
+ // Health metadata can identify the provider before an operator has
55
+ // qualified its dispatch adapter. It grants no dispatch authority.
56
+ const [namespace, id] = (parallelism.clis?.[member.cli]?.adapter ?? `providers:${member.cli}`).split(':');
57
+ const provider = namespace === 'providers' ? registry.providers[id] : undefined;
58
+ const adapter = namespace === 'workerAdapters' ? registry.workerAdapters?.[id]
59
+ : provider?.workerAdapter ?? provider?.workerHealthAdapter;
60
+ const observation = (catalog.discovery?.instances?.[member.cli]
61
+ ?? catalog.discovery?.providers?.[adapter?.provider ?? id])?.models.find(row => row.id === member.model)?.worker;
62
+ if (observation?.effortSupport === 'explicit')
63
+ return true;
64
+ if (observation?.effortSupport === 'not-applicable')
65
+ return false;
66
+ return adapter?.capabilities.effort === 'explicit';
67
+ });
68
+ // With no matching worker, do not invent policy needs.
69
+ if (members.length)
70
+ assertWorkerRoleEffort(models.defaults.workerSelection, request.role, requiresExplicit);
71
+ };
42
72
  const { registry: qualifiedRegistry, installations, drivers, observations } = await loadRecordedWorkerInstallations({
43
- registry, cwd: ctx.cwd, policy: models.defaults.workerSelection, parallelism: config.parallelism,
73
+ registry, cwd: ctx.cwd, policy: models.defaults.workerSelection, parallelism: config.parallelism, precheckPolicy,
44
74
  });
45
75
  catalog.discovery ??= { source: 'static', fetchedAt: new Date().toISOString(), providers: {} };
46
76
  catalog.discovery.instances = { ...catalog.discovery.instances };
@@ -62,6 +62,13 @@ export function generateLoopId(objective) {
62
62
  * Build command-line arguments for the external Ralph process
63
63
  */
64
64
  export function buildArgs(options) {
65
+ if (options.managedWorker) {
66
+ if (!options.managedAdmission)
67
+ throw new Error('Managed worker execution requires admission');
68
+ return ['--launch-worker', '--invocation', options.managedWorker.invocationPath,
69
+ '--run-dir', options.managedWorker.runDir, '--managed-request', options.managedAdmission.requestId,
70
+ '--managed-generation', String(options.managedAdmission.generation)];
71
+ }
65
72
  const args = [options.objective];
66
73
  args.push('--completion', options.completionCriteria);
67
74
  if (options.maxIterations) {
@@ -175,7 +182,9 @@ export async function assertRalphParallelismCap(projectRoot) {
175
182
  * Launch the external Ralph process as a detached daemon
176
183
  */
177
184
  export async function launchExternalRalph(frameworkRoot, projectRoot, options) {
178
- const orchestratorPath = getOrchestratorPath(frameworkRoot);
185
+ const orchestratorPath = options.managedWorker
186
+ ? join(frameworkRoot, 'tools', 'ralph-external', 'session-launcher.mjs')
187
+ : getOrchestratorPath(frameworkRoot);
179
188
  if (!existsSync(orchestratorPath)) {
180
189
  throw new AiwgError({
181
190
  code: 'ERR_RALPH_ORCHESTRATOR_MISSING',
@@ -191,6 +200,9 @@ export async function launchExternalRalph(frameworkRoot, projectRoot, options) {
191
200
  // surprising. Tell the operator what's running and how to lift the cap.
192
201
  await assertRalphParallelismCap(projectRoot);
193
202
  const loopId = options.loopId || generateLoopId(options.objective);
203
+ if (loadLauncherRegistry(projectRoot).loops[loopId]?.managedAdmission) {
204
+ throw new Error('Managed loop identity cannot be relaunched before admission reconciliation');
205
+ }
194
206
  const loopDir = join(registryDir, 'loops', loopId);
195
207
  mkdirSync(loopDir, { recursive: true });
196
208
  // Create output file for the detached process
@@ -200,6 +212,17 @@ export async function launchExternalRalph(frameworkRoot, projectRoot, options) {
200
212
  const promptFile = join(loopDir, 'prompts', '001-prompt.md');
201
213
  // Build arguments
202
214
  const args = buildArgs(options);
215
+ if (options.managedAdmission) {
216
+ const registry = loadLauncherRegistry(projectRoot);
217
+ registry.loops[loopId] = { loopId, pid: 0, objective: options.objective,
218
+ completionCriteria: options.completionCriteria, status: 'running',
219
+ startedAt: new Date().toISOString(), lastUpdate: new Date().toISOString(),
220
+ iteration: 0, maxIterations: options.managedWorker ? 1 : options.maxIterations || 5, outputFile,
221
+ sessionStdoutFile, sessionStderrFile, promptFile, provider: options.provider,
222
+ managedAdmission: { ...options.managedAdmission },
223
+ ...(options.managedWorker ? { managedWorker: { ...options.managedWorker } } : {}) };
224
+ saveLauncherRegistry(projectRoot, registry);
225
+ }
203
226
  // Create output file descriptors
204
227
  const { openSync, closeSync } = await import('fs');
205
228
  const outFd = openSync(outputFile, 'w');
@@ -232,6 +255,25 @@ export async function launchExternalRalph(frameworkRoot, projectRoot, options) {
232
255
  exitCode: EXIT_CODES.GENERAL,
233
256
  });
234
257
  }
258
+ let managedAdmission;
259
+ if (options.managedAdmission) {
260
+ const { sampleWorkerIdentity, createWorkerIdentityCapture } = await import(join(frameworkRoot, 'tools', 'ralph-external', 'process-monitor.mjs'));
261
+ const capture = createWorkerIdentityCapture({ timeoutMs: 250 });
262
+ const identityDeadline = process.hrtime.bigint() + 250000000n;
263
+ let identity;
264
+ while (!identity && process.hrtime.bigint() < identityDeadline) {
265
+ if ((child.exitCode !== null && child.exitCode !== undefined) || child.signalCode)
266
+ break;
267
+ identity = capture.observe(sampleWorkerIdentity(pid));
268
+ if (!identity)
269
+ await new Promise(resolve => setTimeout(resolve, 10));
270
+ }
271
+ if (!identity?.bootId || !identity.startTicks || !Number.isSafeInteger(identity.uid)) {
272
+ throw new Error('managed Ralph execution identity unavailable; reconcile uncertain launch');
273
+ }
274
+ managedAdmission = { ...options.managedAdmission,
275
+ execution: { pid, startedAt: `${identity.bootId}:${identity.startTicks}:${identity.uid}` }, processIdentity: identity };
276
+ }
235
277
  // Record the loop in our launcher registry (backup to the external-multi-loop-state-manager)
236
278
  const launcherRegistry = loadLauncherRegistry(projectRoot);
237
279
  launcherRegistry.loops[loopId] = {
@@ -243,12 +285,14 @@ export async function launchExternalRalph(frameworkRoot, projectRoot, options) {
243
285
  startedAt: new Date().toISOString(),
244
286
  lastUpdate: new Date().toISOString(),
245
287
  iteration: 0,
246
- maxIterations: options.maxIterations || 5,
288
+ maxIterations: options.managedWorker ? 1 : options.maxIterations || 5,
247
289
  outputFile,
248
290
  sessionStdoutFile,
249
291
  sessionStderrFile,
250
292
  promptFile,
251
293
  provider: options.provider,
294
+ ...(managedAdmission ? { managedAdmission } : {}),
295
+ ...(options.managedWorker ? { managedWorker: { ...options.managedWorker } } : {}),
252
296
  };
253
297
  saveLauncherRegistry(projectRoot, launcherRegistry);
254
298
  return {
@@ -257,6 +301,7 @@ export async function launchExternalRalph(frameworkRoot, projectRoot, options) {
257
301
  pid,
258
302
  message: `agent loop started (${loopId}). Check status: aiwg ralph-status`,
259
303
  registryPath: getRegistryPath(projectRoot),
304
+ ...(managedAdmission ? { execution: managedAdmission.execution } : {}),
260
305
  };
261
306
  }
262
307
  /**
@@ -341,7 +386,7 @@ export function getLoopStatuses(projectRoot, options = {}) {
341
386
  entry.stale = true;
342
387
  }
343
388
  // Auto-clean completed entries if requested
344
- if (options.autoCleanCompleted && (entry.status === 'completed')) {
389
+ if (options.autoCleanCompleted && !entry.managedAdmission && (entry.status === 'completed')) {
345
390
  cleanupCompletedLoop(projectRoot, loopId);
346
391
  registryDirty = true;
347
392
  continue; // Don't include in statuses
@@ -372,6 +417,8 @@ export function abortLoop(projectRoot, loopId) {
372
417
  if (!entry) {
373
418
  return { success: false, message: `Loop not found: ${loopId}` };
374
419
  }
420
+ if (entry.managedAdmission)
421
+ return { success: false, message: 'Managed loop cancellation requires its current admission generation' };
375
422
  if (!isProcessAlive(entry.pid)) {
376
423
  entry.status = 'aborted';
377
424
  saveLauncherRegistry(projectRoot, registry);
@@ -391,6 +438,26 @@ export function abortLoop(projectRoot, loopId) {
391
438
  };
392
439
  }
393
440
  }
441
+ /** Signal only the birth identity bound to this current managed reservation.
442
+ * Native Ralph exit/status cannot prove descendant termination, so cancellation
443
+ * always leaves its admission reservation uncertain for explicit reconciliation.
444
+ */
445
+ export async function cancelManagedRalph(frameworkRoot, projectRoot, loopId, binding) {
446
+ const entry = loadLauncherRegistry(projectRoot).loops[loopId];
447
+ const owned = entry?.managedAdmission;
448
+ if (!owned?.execution || !owned.processIdentity || owned.requestId !== binding.requestId || owned.generation !== binding.generation
449
+ || owned.execution.pid !== binding.execution.pid || owned.execution.startedAt !== binding.execution.startedAt) {
450
+ return { state: 'uncertain', signalled: false };
451
+ }
452
+ const { sampleWorkerIdentity } = await import(join(frameworkRoot, 'tools', 'ralph-external', 'process-monitor.mjs'));
453
+ const identity = sampleWorkerIdentity(owned.execution.pid);
454
+ const expected = owned.processIdentity;
455
+ if (!identity || ['pid', 'uid', 'bootId', 'startTicks', 'executable', 'pgid', 'sessionId']
456
+ .some(key => identity[key] !== expected[key]))
457
+ return { state: 'uncertain', signalled: false };
458
+ const { signalOwnedWorker } = await import(join(frameworkRoot, 'tools', 'ralph-external', 'session-launcher.mjs'));
459
+ return { state: 'uncertain', signalled: signalOwnedWorker(identity, 'SIGTERM') };
460
+ }
394
461
  /**
395
462
  * Resume an interrupted agent loop
396
463
  */
@@ -438,6 +505,8 @@ export async function resumeLoop(frameworkRoot, projectRoot, loopId, overrides)
438
505
  exitCode: EXIT_CODES.USAGE,
439
506
  });
440
507
  }
508
+ if (entry.managedAdmission)
509
+ throw new Error('Managed loops require policy and admission reconciliation before resume');
441
510
  const loopDir = join(registryDir, 'loops', targetLoopId);
442
511
  const outputFile = join(loopDir, 'daemon-output.log');
443
512
  // Create output file descriptor
@@ -494,6 +563,9 @@ export function cleanupCompletedLoop(projectRoot, loopId, options = {}) {
494
563
  const registryDir = getRegistryDir(projectRoot);
495
564
  const loopDir = join(registryDir, 'loops', loopId);
496
565
  const preserved = [];
566
+ if (loadLauncherRegistry(projectRoot).loops[loopId]?.managedAdmission) {
567
+ return { success: false, message: 'Managed loop artifacts require admission reconciliation before cleanup', preserved };
568
+ }
497
569
  // Preserve completion reports before deleting the loop directory
498
570
  if (existsSync(loopDir)) {
499
571
  try {
@@ -616,7 +688,7 @@ export function cleanupRegistry(projectRoot, keepDays = 7) {
616
688
  const cutoff = Date.now() - keepDays * 24 * 60 * 60 * 1000;
617
689
  let cleaned = 0;
618
690
  for (const [loopId, entry] of Object.entries(registry.loops)) {
619
- if (entry.status !== 'running') {
691
+ if (entry.status !== 'running' && !entry.managedAdmission) {
620
692
  const lastUpdate = new Date(entry.lastUpdate).getTime();
621
693
  if (lastUpdate < cutoff) {
622
694
  delete registry.loops[loopId];
@@ -68,7 +68,7 @@ export const rulesHandler = {
68
68
  throw new Error('Invalid inventory scope');
69
69
  const activation = list('--activation');
70
70
  const tier = list('--tier');
71
- if (activation?.some(value => !['kernel', 'summary', 'bound', 'paths', 'on-demand'].includes(value)))
71
+ if (activation?.some(value => !['core', 'kernel', 'summary', 'bound', 'paths', 'on-demand'].includes(value)))
72
72
  throw new Error('Invalid activation filter');
73
73
  if (tier?.some(value => !['critical', 'high', 'medium', 'low'].includes(value)))
74
74
  throw new Error('Invalid tier filter');
@@ -11,10 +11,17 @@
11
11
  import { signalChild } from '../../process/signal-child.mjs';
12
12
  import { spawn } from 'child_process';
13
13
  import path from 'path';
14
+ import { pathToFileURL } from 'node:url';
15
+ import { Console } from 'node:console';
16
+ import { Writable } from 'node:stream';
17
+ import { writeSync } from 'node:fs';
18
+ // Embedded deployers use process-wide console/environment APIs. Serialize the
19
+ // complete invocation (including import), and restore them even after failure.
20
+ let deploymentQueue = Promise.resolve();
14
21
  /**
15
22
  * Default script runner implementation
16
23
  *
17
- * Delegates command execution to existing Node.js scripts.
24
+ * Embeds repeatable agent deployment passes; runs other scripts in a child.
18
25
  */
19
26
  export class DefaultScriptRunner {
20
27
  frameworkRoot;
@@ -30,12 +37,26 @@ export class DefaultScriptRunner {
30
37
  */
31
38
  async run(scriptPath, args = [], options = {}) {
32
39
  const fullPath = path.join(this.frameworkRoot, scriptPath);
40
+ if (scriptPath === 'tools/agents/deploy-agents.mjs'
41
+ && process.env.AIWG_DEPLOY_SUBPROCESS !== '1'
42
+ && options.env?.AIWG_DEPLOY_SUBPROCESS !== '1'
43
+ && !options.timeout
44
+ && (!options.cwd || path.resolve(options.cwd) === process.cwd())
45
+ // Other environment overrides can affect module initialization. Keep
46
+ // those callers isolated; AIWG_ROOT is resolved afresh by each pass.
47
+ && Object.keys(options.env ?? {}).every(key => key === 'AIWG_ROOT')) {
48
+ const invocation = deploymentQueue.then(() => this.runDeployment(fullPath, args, options));
49
+ deploymentQueue = invocation.catch(() => { });
50
+ return invocation;
51
+ }
33
52
  return new Promise((resolve) => {
34
53
  // When capture mode is enabled, stdout/stderr are piped so that
35
54
  // quiet-mode callers can suppress output. When not
36
55
  // capturing, stdio: 'inherit' lets subprocess output flow directly to
37
56
  // the terminal (verbose mode).
38
- const stdio = options.capture ? 'pipe' : 'inherit';
57
+ const stdio = options.capture
58
+ ? ['pipe', options.stdoutFd ?? 'pipe', 'pipe']
59
+ : 'inherit';
39
60
  const child = spawn('node', [fullPath, ...args], {
40
61
  stdio,
41
62
  cwd: options.cwd || process.cwd(),
@@ -122,6 +143,56 @@ export class DefaultScriptRunner {
122
143
  });
123
144
  });
124
145
  }
146
+ async runDeployment(fullPath, args, options) {
147
+ const previousConsole = globalThis.console;
148
+ const previousRoot = process.env.AIWG_ROOT;
149
+ const stderrLimit = 1024 * 1024;
150
+ const diagnostics = [];
151
+ let diagnosticBytes = 0;
152
+ try {
153
+ if (options.env?.AIWG_ROOT !== undefined)
154
+ process.env.AIWG_ROOT = options.env.AIWG_ROOT;
155
+ if (options.capture) {
156
+ const stdout = new Writable({ write(chunk, _encoding, done) {
157
+ try {
158
+ if (options.stdoutFd !== undefined)
159
+ writeSync(options.stdoutFd, chunk);
160
+ done();
161
+ }
162
+ catch (error) {
163
+ done(error);
164
+ }
165
+ } });
166
+ const stderr = new Writable({ write(chunk, _encoding, done) {
167
+ const retained = chunk.subarray(0, stderrLimit - diagnosticBytes);
168
+ if (retained.length) {
169
+ diagnostics.push(Buffer.from(retained));
170
+ diagnosticBytes += retained.length;
171
+ }
172
+ done();
173
+ } });
174
+ globalThis.console = new Console({ stdout, stderr });
175
+ }
176
+ // Runtime loading follows the provider preparation coordinator precedent;
177
+ // the runner still supports arbitrary scripts via its subprocess path.
178
+ const deployer = await import(pathToFileURL(fullPath).href);
179
+ const exitCode = (await deployer.main(args, { capture: options.capture === true })) ?? 0;
180
+ const message = Buffer.concat(diagnostics).toString().trim();
181
+ return exitCode && message ? { exitCode, message } : { exitCode };
182
+ }
183
+ catch (error) {
184
+ const message = Buffer.concat(diagnostics).toString().trim()
185
+ || (error instanceof Error ? error.message : String(error));
186
+ return { exitCode: 1, message };
187
+ }
188
+ finally {
189
+ globalThis.console = previousConsole;
190
+ if (previousRoot === undefined)
191
+ delete process.env.AIWG_ROOT;
192
+ else
193
+ process.env.AIWG_ROOT = previousRoot;
194
+ }
195
+ }
125
196
  }
126
197
  /**
127
198
  * Create a script runner for the given framework root
@@ -735,9 +735,11 @@ export async function startServer(opts) {
735
735
  if (!valid) {
736
736
  return c.json({ error: `Invalid register payload: ${errors}` }, 400);
737
737
  }
738
- const result = executorRegistry.register(body);
739
- if ('status' in result && result.status === 400) {
740
- return c.json({ error: result.error }, 400);
738
+ const authHeader = c.req.header('authorization') ?? '';
739
+ const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : '';
740
+ const result = executorRegistry.register(body, token);
741
+ if ('status' in result) {
742
+ return c.json({ error: result.error }, result.status);
741
743
  }
742
744
  // Emit telemetry (reuse agent.spawn as closest match for executor registration)
743
745
  const resp = result;
@@ -2,6 +2,7 @@ import { existsSync, linkSync, mkdirSync, readFileSync, realpathSync, renameSync
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { basename, dirname, isAbsolute, join, resolve, } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
+ import { providerScopeWarnings } from '../../sessions/coverage-preflight.js';
5
6
  import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, GROKBOT_ADAPTER_VERSION, GROKBOT_NATIVE_REPLICA_LOCATOR_CLASS, GrokbotSessionAdapter, GROK_BUILD_ADAPTER_VERSION, GROK_BUILD_CLI_EXPORT_LOCATOR_CLASS, GrokBuildSessionAdapter, grokBuildList, grokBuildSearch, grokBuildExport, grokHeadlessSessionIdFromFile, MUSE_ADAPTER_VERSION, MuseSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, DEEPSEEK_HARNESS_ADAPTER_VERSION, DeepSeekHarnessSessionAdapter, OmpSessionAdapter, OMP_ADAPTER_VERSION, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, createSessionAdapterRegistry, coveragePreflight, expectedSessionProviders, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, buildSessionExportPlan, reverifySessionExportPlan, buildSessionAiwgFortemiIndexExport, recoverAiwgFortemiIndexFromFullV1Shard, embedAttachmentsInFullV1Shard, recoverEmbeddedAttachments, SESSION_EXPORT_PLAN_SCHEMA_VERSION, } from '../../sessions/index.js';
6
7
  import { resolveImportExclusions } from '../../sessions/import-exclusions.js';
7
8
  import { resolveImportLimits } from '../../sessions/import-limits.js';
@@ -58,7 +59,7 @@ Commands:
58
59
  audit --workspace <id> Read content-free mutation events
59
60
  analytics <view> --workspace <id> Summary, tool-calls, escalations, or HITL
60
61
  forensics <view> --workspace <id> Authorized timeline, indicators, or evidence
61
- doctor Check catalog availability and integrity
62
+ doctor Check catalog integrity and configured provider scope
62
63
 
63
64
  Options:
64
65
  --json Emit the versioned JSON contract
@@ -70,10 +71,12 @@ Options:
70
71
  Catalog defaults to <project-or-cwd>/.aiwg/sessions/catalog.sqlite
71
72
  Imports for other workspaces stay in the selected catalog;
72
73
  use --db to read that catalog from another directory
73
- --expected-providers <ids> Comma-separated authorized scope, independent of runtime inventory
74
+ --expected-providers <ids> Explicit scope for this invocation; refresh re-reads workspace config
75
+ --carry-forward <catalog> Rebuild from sources plus re-redacted stored history (import-discovered)
74
76
  --manifest <path> Override the discovery manifest path
75
77
  --provider-home <path> Override the provider home root (testing/portable homes)
76
78
  --codex-root <path> Authorize a shared Codex sessions/export root (repeatable)
79
+ Adds to defaults separately authorized by --provider-home
77
80
  --omp-root <path> Explicitly authorize an OMP profile sessions root
78
81
  --muse-root <path> Explicitly authorize a Muse Code sessions root
79
82
  --dsh-root <path> Explicitly authorize a DeepSeek Harness sessions root
@@ -725,12 +728,22 @@ async function executeCommand(ctx, args) {
725
728
  providerLogsModified: false,
726
729
  });
727
730
  }
728
- case 'doctor':
731
+ case 'doctor': {
732
+ const workspacePath = resolve(ctx.cwd, args.values.get('--workspace') ?? ctx.cwd);
733
+ const workspace = existsSync(workspacePath) ? realpathSync(workspacePath) : workspacePath;
734
+ const config = await readAiwgConfig(workspace);
735
+ const manifestPath = resolve(ctx.cwd, args.values.get('--manifest') ?? defaultDiscoveryManifestPath(workspace));
736
+ const preflight = existsSync(manifestPath)
737
+ ? (await readDiscoveryManifest(manifestPath)).preflight
738
+ : repository.getCoverage(workspace).preflight;
739
+ const warnings = providerScopeWarnings(config?.providers ?? [], preflight?.expectedProviders ?? [], preflight?.scopeWarnings);
729
740
  return ok(command, {
730
741
  database: redactSourceLocator(databasePath(ctx, args)),
731
742
  health: repository.doctor(),
743
+ warnings,
732
744
  jsonContractVersion: JSON_CONTRACT_VERSION,
733
745
  });
746
+ }
734
747
  default:
735
748
  throw new CliError('UNKNOWN_COMMAND', `unknown sessions command: ${command}`, EXIT.usage);
736
749
  }
@@ -1283,6 +1296,7 @@ async function discoverWorkspace(ctx, args) {
1283
1296
  ],
1284
1297
  });
1285
1298
  const data = {
1299
+ warnings: manifest.preflight?.scopeWarnings ?? [],
1286
1300
  manifest: publicDiscoveryManifest(manifest),
1287
1301
  manifestPath: redactSourceLocator(manifestPath),
1288
1302
  exactManifestRequiredForImport: true,
@@ -1304,7 +1318,20 @@ async function importDiscovered(ctx, args) {
1304
1318
  if (manifest.workspaceId !== canonicalWorkspace) {
1305
1319
  throw new CliError('MANIFEST_WORKSPACE_MISMATCH', 'discovery manifest does not belong to the explicitly authorized workspace', EXIT.contract);
1306
1320
  }
1321
+ const carryPath = args.values.has('--carry-forward')
1322
+ ? resolve(ctx.cwd, args.values.get('--carry-forward')) : undefined;
1323
+ if (carryPath) {
1324
+ const sourceStat = statSync(carryPath);
1325
+ const target = databasePath(ctx, args);
1326
+ if (existsSync(target)) {
1327
+ const targetStat = statSync(target);
1328
+ if (sourceStat.dev === targetStat.dev && sourceStat.ino === targetStat.ino) {
1329
+ throw new CliError('INVALID_ARGUMENT', 'carry-forward requires a separate destination catalog', EXIT.usage);
1330
+ }
1331
+ }
1332
+ }
1307
1333
  const expected = requestedProviders(args);
1334
+ const warnings = providerScopeWarnings(config?.providers ?? [], manifest.preflight?.expectedProviders ?? [], manifest.preflight?.scopeWarnings);
1308
1335
  if (expected && JSON.stringify(expected) !== JSON.stringify(manifest.preflight?.expectedProviders)) {
1309
1336
  throw new SessionContractError('INVALID_ARGUMENT', 'expected provider scope differs from the saved manifest; rediscover with --expected-providers');
1310
1337
  }
@@ -1320,22 +1347,27 @@ async function importDiscovered(ctx, args) {
1320
1347
  // A preview can still describe the exact manifest before a catalog exists.
1321
1348
  }
1322
1349
  return preview('import-discovered', {
1350
+ warnings,
1323
1351
  manifest: publicDiscoveryManifest(manifest),
1324
1352
  receipt: previewDiscoveryImport(manifest, existing, new Date(), exclude),
1325
1353
  confirmationRequired: !isDryRun(ctx, args),
1326
1354
  wouldPersist: false,
1355
+ ...(carryPath ? { carryForward: redactSourceLocator(carryPath) } : {}),
1327
1356
  });
1328
1357
  }
1329
1358
  const lease = await acquireImportLease(databasePath(ctx, args), sha256(['discovered-import', manifest.manifestId, manifest.workspaceId].join('\0')), {
1330
1359
  waitMs: boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms'),
1331
1360
  });
1332
1361
  try {
1333
- const repository = openRepository(ctx, args);
1362
+ const previous = carryPath ? new SessionRepository(carryPath, { readonly: true }) : undefined;
1363
+ let repository;
1334
1364
  try {
1365
+ repository = openRepository(ctx, args);
1335
1366
  const interruptedRuns = repository.interruptRunningBatchImports();
1336
1367
  const receipt = await importDiscoveryManifest({
1337
1368
  manifest,
1338
1369
  repository,
1370
+ carryForward: previous,
1339
1371
  limits,
1340
1372
  exclude,
1341
1373
  signal: ctx.signal,
@@ -1343,6 +1375,7 @@ async function importDiscovered(ctx, args) {
1343
1375
  });
1344
1376
  receipt.interruptedRuns = interruptedRuns;
1345
1377
  return ok('import-discovered', {
1378
+ warnings,
1346
1379
  manifestId: manifest.manifestId,
1347
1380
  receipt,
1348
1381
  resumable: receipt.run.status !== 'complete',
@@ -1354,7 +1387,8 @@ async function importDiscovered(ctx, args) {
1354
1387
  });
1355
1388
  }
1356
1389
  finally {
1357
- repository.close();
1390
+ repository?.close();
1391
+ previous?.close();
1358
1392
  }
1359
1393
  }
1360
1394
  finally {
@@ -1710,7 +1744,7 @@ function parseArgs(argv) {
1710
1744
  '--entity', '--sensitivity', '--extraction-state', '--page-size', '--max-documents',
1711
1745
  '--state', '--reviewer', '--reason', '--policy-version', '--min-confidence',
1712
1746
  '--consumer', '--actor-class', '--reason-code', '--dependent-action', '--basis',
1713
- '--manifest', '--provider-home', '--codex-root', '--omp-root', '--muse-root', '--dsh-root', '--grokbot-root', '--lock-wait-ms', '--min-coverage', '--gap',
1747
+ '--carry-forward', '--manifest', '--provider-home', '--codex-root', '--omp-root', '--muse-root', '--dsh-root', '--grokbot-root', '--lock-wait-ms', '--min-coverage', '--gap',
1714
1748
  '--inactivity-threshold', '--expected-providers', '--exclude', '--max-record-bytes', '--max-source-bytes',
1715
1749
  '--control-events',
1716
1750
  '--session', '--status', '--actor', '--group-by',