@yemi33/minions 0.1.2448 → 0.1.2449

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 (315) hide show
  1. package/bin/cli-api-client.js +1 -1
  2. package/bin/install-internal-minions.js +1382 -44
  3. package/bin/install-layout.js +150 -0
  4. package/bin/minions.js +460 -167
  5. package/dashboard/docs/typography.md +65 -12
  6. package/dashboard/js/command-center.js +58 -4
  7. package/dashboard/js/detail-panel.js +36 -0
  8. package/dashboard/js/memory-panel.js +59 -12
  9. package/dashboard/js/qa.js +179 -20
  10. package/dashboard/js/refresh.js +148 -12
  11. package/dashboard/js/render-dispatch.js +3 -4
  12. package/dashboard/js/render-inbox.js +2 -2
  13. package/dashboard/js/render-other.js +3 -3
  14. package/dashboard/js/render-pipelines.js +14 -0
  15. package/dashboard/js/render-plans.js +57 -9
  16. package/dashboard/js/render-prd.js +132 -23
  17. package/dashboard/js/render-prs.js +195 -166
  18. package/dashboard/js/render-schedules.js +63 -3
  19. package/dashboard/js/render-utils.js +3 -3
  20. package/dashboard/js/render-watches.js +19 -3
  21. package/dashboard/js/render-work-items.js +238 -30
  22. package/dashboard/js/settings.js +205 -54
  23. package/dashboard/js/utils.js +51 -1
  24. package/dashboard/pages/home.html +1 -1
  25. package/dashboard/pages/qa.html +1 -16
  26. package/dashboard/pages/work.html +40 -0
  27. package/dashboard/shared/cc-limits.js +79 -0
  28. package/dashboard/shared/pr-filters.js +21 -38
  29. package/dashboard/shared/project-git-summary.js +1 -1
  30. package/dashboard/shared/record-filters.js +169 -0
  31. package/dashboard/shared/watches-source.js +1 -1
  32. package/dashboard/shared/welcome-popup.js +1 -1
  33. package/dashboard/shared/wi-filters.js +302 -0
  34. package/dashboard/slim/body.html +1 -0
  35. package/dashboard/slim/js/command-send.js +26 -0
  36. package/dashboard/slim/js/modals-tiles.js +380 -39
  37. package/dashboard/slim/js/status.js +13 -21
  38. package/dashboard/slim/layout.html +1 -0
  39. package/dashboard/slim/panel-bootstrap.js +6 -2
  40. package/dashboard/slim/styles.css +38 -0
  41. package/dashboard/styles.css +159 -55
  42. package/dashboard-build.js +13 -2
  43. package/dashboard.js +956 -423
  44. package/docs/README.md +11 -6
  45. package/docs/api-errors.md +2 -2
  46. package/docs/architecture-review-2026-07-09.md +1 -1
  47. package/docs/architecture.excalidraw +2 -2
  48. package/docs/auto-discovery.md +18 -9
  49. package/docs/branch-derivation.md +4 -4
  50. package/docs/capture-demos.js +39 -2
  51. package/docs/ci-runner-canary.md +123 -0
  52. package/docs/claude-md-propagation.md +2 -2
  53. package/docs/cloud-agent-dispatch.md +204 -0
  54. package/docs/command-center.md +7 -7
  55. package/docs/completion-reports.md +43 -20
  56. package/docs/constants.md +10 -3
  57. package/docs/constellation-bridge.md +134 -6
  58. package/docs/constellation-style-telemetry.md +4 -4
  59. package/docs/contracts/capability-protocol.v1.json +165 -0
  60. package/docs/cooldown-merge-semantics.md +12 -12
  61. package/docs/copilot-cli-schema.md +7 -7
  62. package/docs/cross-repo-plans.md +10 -10
  63. package/docs/dead-code-audit-retractions.md +5 -5
  64. package/docs/default-branch-ci.md +173 -0
  65. package/docs/deprecated.json +31 -31
  66. package/docs/design-inbox-entries-schema.md +3 -3
  67. package/docs/design-language.md +1051 -0
  68. package/docs/design-state-storage.md +11 -11
  69. package/docs/diagnostics-crash-reports.md +9 -9
  70. package/docs/diagnostics-memory.md +5 -5
  71. package/docs/documentation-audit-2026-07-09.md +7 -7
  72. package/docs/engine-restart.md +90 -5
  73. package/docs/harness-mode.md +1 -1
  74. package/docs/internal-install.md +338 -39
  75. package/docs/kb-dedup-duplicate-pair-investigation.md +5 -5
  76. package/docs/kb-pr3223-cascade-archiving.md +1 -1
  77. package/docs/kb-pr696-merge-conflict-docs.md +6 -6
  78. package/docs/kb-sweep.md +35 -35
  79. package/docs/keep-processes.md +1 -1
  80. package/docs/live-checkout-mode.md +30 -30
  81. package/docs/managed-spawn.md +18 -14
  82. package/docs/named-agents.md +7 -7
  83. package/docs/plan-lifecycle.md +69 -2
  84. package/docs/pr-author-identity.md +114 -0
  85. package/docs/pr-auto-fix-dispatch.md +19 -4
  86. package/docs/pr-comment-followup.md +6 -6
  87. package/docs/pr-review-fix-loop.md +59 -10
  88. package/docs/process-termination.md +40 -0
  89. package/docs/proposals/repo-pool-for-live-checkout.md +13 -13
  90. package/docs/qa-runbook-lifecycle.md +367 -17
  91. package/docs/qa-runbooks.md +3 -3
  92. package/docs/rfc-completion-json.md +18 -18
  93. package/docs/runtime-adapters.md +26 -21
  94. package/docs/security.md +6 -6
  95. package/docs/self-improvement.md +4 -4
  96. package/docs/shared-lifecycle-module-map.md +473 -472
  97. package/docs/skills.md +52 -3
  98. package/docs/slim-ux/concepts.md +121 -116
  99. package/docs/specs/agent-configurability.md +18 -18
  100. package/docs/specs/agent-rename.md +18 -18
  101. package/docs/team-memory.md +38 -21
  102. package/docs/timeouts-and-liveness.md +118 -10
  103. package/docs/tutorials/01-install-and-connect.md +1 -1
  104. package/docs/watches.md +40 -39
  105. package/docs/workspace-manifests.md +4 -4
  106. package/docs/worktree-lifecycle.md +293 -14
  107. package/engine/README.md +46 -0
  108. package/engine/{ado-comment.js → ado/comment.js} +8 -8
  109. package/engine/{ado-git-auth.js → ado/git-auth.js} +4 -4
  110. package/engine/{ado.js → ado/index.js} +417 -63
  111. package/engine/{ado-status.js → ado/status.js} +6 -8
  112. package/engine/{ado-token.js → ado/token.js} +1 -1
  113. package/engine/{acp-transport.js → agents/acp-transport.js} +62 -22
  114. package/engine/{agent-worker-pool.js → agents/agent-worker-pool.js} +17 -8
  115. package/engine/{cc-worker-pool.js → agents/cc-worker-pool.js} +16 -6
  116. package/engine/{claude-md-context.js → agents/claude-md-context.js} +5 -5
  117. package/engine/{harness-context.js → agents/harness-context.js} +5 -5
  118. package/engine/{harness.js → agents/harness.js} +3 -3
  119. package/engine/{llm.js → agents/llm.js} +18 -14
  120. package/engine/{model-discovery.js → agents/model-discovery.js} +2 -2
  121. package/engine/{playbook.js → agents/playbook.js} +155 -22
  122. package/engine/{pooled-agent-process.js → agents/pooled-agent-process.js} +14 -12
  123. package/engine/{preflight.js → agents/preflight.js} +29 -10
  124. package/engine/{spawn-agent.js → agents/spawn-agent.js} +25 -14
  125. package/engine/{spawn-phase-watchdog.js → agents/spawn-phase-watchdog.js} +16 -7
  126. package/engine/{steering.js → agents/steering.js} +5 -5
  127. package/engine/{tools-inventory.js → agents/tools-inventory.js} +2 -2
  128. package/engine/{agent-api-validation.js → api/agent-api-validation.js} +2 -2
  129. package/engine/{api-validation.js → api/api-validation.js} +1 -1
  130. package/engine/api/bridge.js +787 -0
  131. package/engine/{cc-api-validation.js → api/cc-api-validation.js} +1 -1
  132. package/engine/api/companion.js +560 -0
  133. package/engine/{content-api-validation.js → api/content-api-validation.js} +2 -2
  134. package/engine/{pr-issue-validation.js → api/pr-issue-validation.js} +33 -6
  135. package/engine/{settings-validation.js → api/settings-validation.js} +32 -4
  136. package/engine/api-contracts/agent-content.js +4 -4
  137. package/engine/api-contracts/capability-manifest.js +236 -0
  138. package/engine/api-contracts/capability-protocol.js +333 -0
  139. package/engine/api-contracts/cc-ops.js +1 -1
  140. package/engine/api-contracts/config-runtime.js +5 -0
  141. package/engine/api-contracts/core.js +28 -1
  142. package/engine/api-contracts/index.js +100 -0
  143. package/engine/api-contracts/orchestration.js +18 -5
  144. package/engine/api-contracts/pull-requests.js +37 -6
  145. package/engine/api-contracts/qa-process.js +29 -6
  146. package/engine/api-contracts/work-plan-prd.js +21 -1
  147. package/engine/cloud/contract.js +212 -0
  148. package/engine/cloud/index.js +159 -0
  149. package/engine/{execution-model.js → core/execution-model.js} +1 -1
  150. package/engine/{features.js → core/features.js} +4 -4
  151. package/engine/{operator-identity.js → core/operator-identity.js} +1 -1
  152. package/engine/{queries.js → core/queries.js} +201 -36
  153. package/engine/{safe-expr.js → core/safe-expr.js} +1 -1
  154. package/engine/{shared.js → core/shared.js} +1637 -175
  155. package/engine/{stdio-timestamps.js → core/stdio-timestamps.js} +1 -1
  156. package/engine/{untrusted-fence.js → core/untrusted-fence.js} +3 -3
  157. package/engine/db/index.js +11 -2
  158. package/engine/db/migrations/002-dispatches.js +3 -3
  159. package/engine/db/migrations/003-work-items.js +1 -1
  160. package/engine/db/migrations/004-pull-requests.js +1 -1
  161. package/engine/db/migrations/006-metrics.js +1 -1
  162. package/engine/db/migrations/007-watches.js +2 -2
  163. package/engine/db/migrations/008-small-state.js +1 -1
  164. package/engine/db/migrations/009-qa.js +1 -1
  165. package/engine/db/migrations/010-pr-links.js +1 -1
  166. package/engine/db/migrations/011-remaining-state.js +1 -1
  167. package/engine/db/migrations/012-steering-deliveries.js +2 -2
  168. package/engine/db/migrations/013-backfill-broken-note-links.js +1 -1
  169. package/engine/db/migrations/014-pr-fix-target-prefs.js +2 -2
  170. package/engine/db/migrations/015-plans-prds.js +0 -0
  171. package/engine/db/migrations/018-sql-only-cutover.js +2 -2
  172. package/engine/db/migrations/021-archived-work-items.js +1 -1
  173. package/engine/db/migrations/022-global-cc-session.js +1 -1
  174. package/engine/db/migrations/023-engine-state.js +1 -1
  175. package/engine/db/migrations/025-malformed-work-item-phantoms.js +1 -1
  176. package/engine/db/migrations/027-review-learning-lifecycle.js +1 -1
  177. package/engine/db/migrations/029-repair-reused-versions.js +20 -0
  178. package/engine/db/migrations/031-pr-author-identity.js +137 -0
  179. package/engine/{consolidation.js → memory/consolidation.js} +6 -6
  180. package/engine/{kb-sweep-runner.js → memory/kb-sweep-runner.js} +2 -2
  181. package/engine/{kb-sweep.js → memory/kb-sweep.js} +9 -7
  182. package/engine/{memory-retrieval.js → memory/memory-retrieval.js} +46 -4
  183. package/engine/{memory-store.js → memory/memory-store.js} +3 -3
  184. package/engine/{promotion.js → memory/promotion.js} +3 -3
  185. package/engine/{review-learning-backfill.js → memory/review-learning-backfill.js} +6 -6
  186. package/engine/{review-learning.js → memory/review-learning.js} +10 -5
  187. package/engine/{diagnostics-memory.js → observability/diagnostics-memory.js} +1 -1
  188. package/engine/{logs-store.js → observability/logs-store.js} +5 -5
  189. package/engine/{metrics-store.js → observability/metrics-store.js} +4 -4
  190. package/engine/{check-status.js → operations/check-status.js} +3 -3
  191. package/engine/{cli.js → operations/cli.js} +271 -113
  192. package/engine/{distribution.js → operations/distribution.js} +5 -6
  193. package/engine/{cleanup.js → orchestration/cleanup.js} +72 -45
  194. package/engine/{cooldown.js → orchestration/cooldown.js} +5 -5
  195. package/engine/{dispatch-events.js → orchestration/dispatch-events.js} +2 -2
  196. package/engine/{dispatch.js → orchestration/dispatch.js} +129 -36
  197. package/engine/orchestration/failed-scheduled-cleanup.js +274 -0
  198. package/engine/{lifecycle.js → orchestration/lifecycle.js} +198 -90
  199. package/engine/{meeting.js → orchestration/meeting.js} +6 -16
  200. package/engine/{pipeline.js → orchestration/pipeline.js} +12 -12
  201. package/engine/{pre-dispatch-eval.js → orchestration/pre-dispatch-eval.js} +10 -9
  202. package/engine/{routing.js → orchestration/routing.js} +3 -3
  203. package/engine/{schedule-bootstrap.js → orchestration/schedule-bootstrap.js} +4 -4
  204. package/engine/{scheduler.js → orchestration/scheduler.js} +38 -8
  205. package/engine/{timeout.js → orchestration/timeout.js} +158 -109
  206. package/engine/{db-events.js → persistence/db-events.js} +2 -2
  207. package/engine/{dispatch-store.js → persistence/dispatch-store.js} +7 -7
  208. package/engine/{inbox-store.js → persistence/inbox-store.js} +2 -2
  209. package/engine/{note-link-backfill.js → persistence/note-link-backfill.js} +4 -4
  210. package/engine/{pr-fix-target-store.js → persistence/pr-fix-target-store.js} +8 -8
  211. package/engine/{pull-requests-store.js → persistence/pull-requests-store.js} +21 -7
  212. package/engine/{small-state-store.js → persistence/small-state-store.js} +31 -31
  213. package/engine/persistence/state-operations.js +350 -0
  214. package/engine/{steering-store.js → persistence/steering-store.js} +6 -6
  215. package/engine/{issues.js → planning/issues.js} +2 -2
  216. package/engine/{plan-prd-validation.js → planning/plan-prd-validation.js} +8 -2
  217. package/engine/planning/prd-result-sidecar.js +190 -0
  218. package/engine/{prd-store.js → planning/prd-store.js} +17 -17
  219. package/engine/{project-discovery.js → planning/project-discovery.js} +5 -5
  220. package/engine/{projects.js → planning/projects.js} +10 -10
  221. package/engine/{resolve-area.js → planning/resolve-area.js} +1 -1
  222. package/engine/{work-item-validation.js → planning/work-item-validation.js} +39 -3
  223. package/engine/{work-items-store.js → planning/work-items-store.js} +29 -21
  224. package/engine/{keep-process-sweep.js → processes/keep-process-sweep.js} +57 -17
  225. package/engine/{managed-spawn-launcher.js → processes/managed-spawn-launcher.js} +3 -3
  226. package/engine/{managed-spawn.js → processes/managed-spawn.js} +97 -46
  227. package/engine/{process-utils.js → processes/process-utils.js} +599 -55
  228. package/engine/{abandoned-pr-reconciliation.js → providers/abandoned-pr-reconciliation.js} +17 -7
  229. package/engine/{comment-classifier.js → providers/comment-classifier.js} +85 -17
  230. package/engine/{comment-format.js → providers/comment-format.js} +5 -5
  231. package/engine/{gh-comment.js → providers/gh-comment.js} +15 -15
  232. package/engine/{gh-token.js → providers/gh-token.js} +4 -4
  233. package/engine/{github.js → providers/github.js} +131 -54
  234. package/engine/{pr-action.js → providers/pr-action.js} +13 -12
  235. package/engine/{pr-clone-keep.js → providers/pr-clone-keep.js} +7 -7
  236. package/engine/{pr-devbox.js → providers/pr-devbox.js} +6 -6
  237. package/engine/{pr-fix-target.js → providers/pr-fix-target.js} +13 -13
  238. package/engine/{pr-remote-patch.js → providers/pr-remote-patch.js} +4 -4
  239. package/engine/{pr-resolve.js → providers/pr-resolve.js} +7 -7
  240. package/engine/{pr-temp-clone.js → providers/pr-temp-clone.js} +5 -5
  241. package/engine/{pr-track.js → providers/pr-track.js} +11 -13
  242. package/engine/{shared-branch-pr-reconcile.js → providers/shared-branch-pr-reconcile.js} +4 -4
  243. package/engine/qa/auto-prd-qa.js +313 -0
  244. package/engine/{qa-from-prd.js → qa/from-prd.js} +42 -12
  245. package/engine/qa/prd-session.js +240 -0
  246. package/engine/{qa-process-validation.js → qa/process-validation.js} +14 -9
  247. package/engine/{qa-runbooks.js → qa/runbooks.js} +1 -1
  248. package/engine/{qa-runs.js → qa/runs.js} +286 -15
  249. package/engine/{qa-sessions.js → qa/sessions.js} +595 -49
  250. package/engine/qa/visual-journey.js +654 -0
  251. package/engine/{qa-runners.js → qa-runners/index.js} +7 -7
  252. package/engine/qa-runners/maestro.js +3 -3
  253. package/engine/qa-runners/playwright.js +2 -2
  254. package/engine/{restart-health.js → recovery/restart-health.js} +48 -4
  255. package/engine/recovery/stop-stack.js +607 -0
  256. package/engine/{supervisor.js → recovery/supervisor.js} +105 -175
  257. package/engine/{watchdog.js → recovery/watchdog.js} +136 -13
  258. package/engine/runtimes/claude.js +14 -12
  259. package/engine/runtimes/codex.js +8 -6
  260. package/engine/runtimes/copilot.js +17 -16
  261. package/engine/{watch-actions.js → watches/actions.js} +13 -13
  262. package/engine/{watches.js → watches/index.js} +43 -32
  263. package/engine/{watches-store.js → watches/store.js} +4 -4
  264. package/engine/{create-pr-worktree.js → worktrees/create-pr.js} +1 -1
  265. package/engine/{worktree-gc.js → worktrees/gc.js} +70 -22
  266. package/engine/worktrees/inventory.js +671 -0
  267. package/engine/{live-checkout.js → worktrees/live-checkout.js} +4 -4
  268. package/engine/{worktree-pool.js → worktrees/pool.js} +2 -2
  269. package/engine/{worktree-preflight.js → worktrees/preflight.js} +1 -0
  270. package/engine/worktrees/quarantine-refs.js +173 -0
  271. package/engine.js +1137 -208
  272. package/minions.js +147 -77
  273. package/package.json +10 -6
  274. package/playbooks/_pr-description-audit.md +110 -78
  275. package/playbooks/build-fix-complex.md +2 -0
  276. package/playbooks/fix.md +16 -12
  277. package/playbooks/implement-shared.md +2 -0
  278. package/playbooks/implement.md +19 -20
  279. package/playbooks/plan-to-prd.md +18 -3
  280. package/playbooks/qa-session-draft.md +136 -1
  281. package/playbooks/qa-session-execute.md +80 -2
  282. package/playbooks/qa-session-setup.md +17 -1
  283. package/playbooks/qa-validate.md +1 -1
  284. package/playbooks/setup.md +2 -0
  285. package/playbooks/shared-rules.md +25 -32
  286. package/playbooks/templates/followup-dispatch.md +4 -3
  287. package/playbooks/verify.md +1 -1
  288. package/prompts/cc-system.md +19 -27
  289. package/watch-plugins/README.md +92 -0
  290. package/watch-plugins/ado-author-prs.js +336 -0
  291. package/watch-plugins/gh-author-prs.js +375 -0
  292. package/watch-plugins/http.js +474 -0
  293. package/watch-plugins/teams-channel.js +869 -0
  294. package/docs/dev-composite-workflow.md +0 -101
  295. package/docs/pr-screenshots/pr-886/after-single-header.png +0 -0
  296. package/docs/pr-screenshots/pr-886/before-duplicate-header.png +0 -0
  297. package/docs/pr-screenshots/pr-895/01-cancellation-reason-detail.png +0 -0
  298. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  299. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  300. package/docs/pr-screenshots/pr-901/projects-tab-default.png +0 -0
  301. package/docs/pr-screenshots/pr-901/projects-tab-fmf-selected.png +0 -0
  302. package/docs/pr-screenshots/pr-916/model-picker-AFTER-crop.png +0 -0
  303. package/docs/pr-screenshots/pr-916/model-picker-AFTER.png +0 -0
  304. package/docs/pr-screenshots/pr-916/model-picker-BEFORE-crop.png +0 -0
  305. package/docs/pr-screenshots/pr-916/model-picker-BEFORE.png +0 -0
  306. package/docs/pr-screenshots/pr-916/model-picker-dropdown-AFTER.png +0 -0
  307. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  308. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  309. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  310. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  311. package/docs/visual-evidence-ci.md +0 -103
  312. package/engine/bridge.js +0 -379
  313. package/engine/quarantine-refs.js +0 -103
  314. package/engine/state-operations.js +0 -178
  315. /package/engine/{steering-constraints.js → agents/steering-constraints.js} +0 -0
@@ -0,0 +1,204 @@
1
+ # Cloud agent dispatch (Agency / Copilot cloud)
2
+
3
+ **Status: Phase 1 — surface + provider seam only. No provider ships yet, so
4
+ every cloud-declared work item is currently refused, loudly and non-retryably.**
5
+
6
+ ## Why this exists
7
+
8
+ Minions is being wired as a *front-door* to remote coding agents. The observed
9
+ result that motivated it: a one-line intent ("in loop android, the builds rely
10
+ on droidx86 — replace it with droidx64") produces a mediocre cloud PR, but the
11
+ same intent run through a local Minions investigation first — which expands it
12
+ into scoped instructions naming exact files, line numbers, and which references
13
+ to change vs. deliberately leave alone — produces a markedly better one.
14
+
15
+ So the value is not "run agents in the cloud." It is **local investigation +
16
+ prompt rendering, remote execution**. The prompt Minions already builds is the
17
+ product; the cloud provider is just where it gets sent.
18
+
19
+ ## Why cloud is an execution surface, not a runtime
20
+
21
+ `engine/runtimes/contract.js` describes a **local CLI process**: argv building,
22
+ prompt-on-stdin, PID tracking, stdout stream parsing, session resume, per-turn
23
+ cost accounting. A remote coding agent has none of that — you submit a task, get
24
+ an opaque handle, poll it, and a PR eventually appears.
25
+
26
+ Modelling it as a runtime would force every adapter method to lie (`buildArgs`
27
+ for a process that never spawns, `parseStreamChunk` for a stream that does not
28
+ exist) *and* would drag cloud dispatch through the local spawn, timeout, and
29
+ worktree machinery it specifically needs to avoid.
30
+
31
+ Instead cloud is a third value of the existing `EXECUTION_SURFACE` enum, which
32
+ already answers the load-bearing question "does this dispatch need a checkout?":
33
+
34
+ | Surface | Checkout | Local process | Branch | Deliverable |
35
+ |---|---|---|---|---|
36
+ | `repo` (default) | worktree or live checkout | yes | `work/<wi-id>` | commits in that checkout |
37
+ | `control-plane` | none | yes (runs at `MINIONS_DIR`) | none | Minions/REST + `gh`/`az` calls |
38
+ | `cloud` | none | **no** | none (remote agent pushes its own) | a PR Minions adopts |
39
+
40
+ ## Declaring it
41
+
42
+ Structured and fail-closed, exactly like `control-plane` — never inferred from
43
+ prose:
44
+
45
+ ```json
46
+ {
47
+ "type": "implement",
48
+ "title": "Replace droidx86 reliance with droidx64 in Loop Android",
49
+ "meta": {
50
+ "executionSurface": "cloud",
51
+ "cloudProvider": "agency"
52
+ }
53
+ }
54
+ ```
55
+
56
+ `meta.cloudProvider` is optional; resolution is most-specific-first:
57
+
58
+ ```
59
+ item.meta.cloudProvider → project.cloudProvider → engine.cloudProvider
60
+ ```
61
+
62
+ When none is declared, or the declared id is not registered, or the provider
63
+ reports itself unconfigured for the project, the dispatch is **refused** with
64
+ non-retryable `FAILURE_CLASS.CLOUD_PROVIDER_UNAVAILABLE`.
65
+
66
+ ### Eligible work types
67
+
68
+ `shared.CLOUD_ELIGIBLE_TYPES` = `implement`, `fix`, `test`, `docs`.
69
+
70
+ The allowlist asks a different question than the control-plane one. Control-plane
71
+ asks *"can this run with no checkout at all?"*; cloud asks *"does this produce a
72
+ PR that a remote agent can author?"* The two sets are deliberately **disjoint**,
73
+ and a test enforces that.
74
+
75
+ Notable refusals and why:
76
+
77
+ - **`review` / `verify`** — their entire deliverable is validation: build the
78
+ code, run the tests, inspect the tree. A remote agent that cannot run your
79
+ local validation build produces a *worse* review, not a cheaper one. This is
80
+ the single most common objection to cloud agents from people who use them, and
81
+ the allowlist encodes it rather than leaving it to judgment.
82
+ - **`build-fix-complex`** — needs live CI logs and the PR's own branch.
83
+ - **`implement:large`** — decomposed into children first; the *children* may go
84
+ to the cloud.
85
+ - **`setup`** — produces no PR and usually drives local processes
86
+ (`managed_spawn`), which a remote agent cannot leave running on this machine.
87
+ - **`ask` / `explore` / `meeting`** — read-only; their value is output text,
88
+ which the cloud surface does not stream back.
89
+
90
+ ## Fail-closed behavior, end to end
91
+
92
+ Every gate degrades toward *today's behavior* (a local dispatch) or toward a
93
+ loud refusal — never toward silently shipping work somewhere unexpected:
94
+
95
+ - An **invalid or ineligible** `executionSurface` declaration is rejected at
96
+ `POST /api/work-items`, in `validateScheduleDefinition`, and again in
97
+ `spawnAgent` (`FAILURE_CLASS.INVALID_EXECUTION_SURFACE`, non-retryable).
98
+ - `shared.isCloudExecution()` returns `false` for anything invalid, so a
99
+ malformed opt-in **degrades to a normal local dispatch** — the behavior the
100
+ item would have had anyway.
101
+ - A **valid** cloud declaration with no usable provider is **refused**, not
102
+ downgraded: the engine never runs cloud-declared work locally, because a local
103
+ agent and a remote agent produce different work, in different checkouts, under
104
+ different identities.
105
+ - A provider whose `isConfigured()` **throws** is treated as unavailable. One
106
+ that cannot answer the cheap question is not one we hand a work item to.
107
+ - `normalizeSessionStatus` **rejects** `state: "succeeded"` with no `prUrl` — a
108
+ cloud dispatch succeeds only when it produced a PR Minions can adopt.
109
+
110
+ ## Allocation-time gating
111
+
112
+ `engine.js#dispatchPending` resolves the surface once, fail-closed, into
113
+ `_itemSkipsCheckoutAllocation` — *"does this item allocate a local checkout at
114
+ all?"* — rather than testing for control-plane specifically. Every gate that
115
+ exists to protect a local checkout is skipped for both no-checkout surfaces:
116
+
117
+ | Gate | Why it cannot apply |
118
+ |---|---|
119
+ | live-mode concurrency cap (1 mutating dispatch per project) | consumes no operator checkout |
120
+ | operator branch hold | derives no local branch at all |
121
+ | worktree free-space preflight | never runs `git worktree add` |
122
+ | live-checkout dirty / stale-base precheck | never reads the operator tree |
123
+
124
+ Keying on the *surface* rather than on one surface's name is deliberate: a
125
+ second no-checkout surface added later cannot inherit repo-shaped gates by
126
+ omission, and a cloud item can never pend as `worktree_low_disk_space` when the
127
+ honest answer is `cloud-provider-unavailable`. A regression test asserts each
128
+ gate is individually guarded, with a proximity bound so a gate that loses its
129
+ guard cannot pass by borrowing a neighbour's.
130
+
131
+ ## Provider contract
132
+
133
+ `engine/cloud/contract.js`, API version 1. Register through
134
+ `engine/cloud/index.js`; orchestration resolves via `resolveCloudProvider()` /
135
+ `resolveCloudDispatchTarget()` and **never** imports a concrete provider or
136
+ branches on a provider id — same discipline as the runtime registry.
137
+
138
+ ```
139
+ isConfigured({ config, project }) -> boolean (sync, no network)
140
+ createSession({ project, repo, baseBranch, prompt,
141
+ workItem, workdir }) -> { sessionId, url?, providerRef? }
142
+ pollSession(sessionId, { project }) -> { state, prUrl?, summary?, error?, raw? }
143
+ cancelSession(sessionId, { reason, project }) -> void (required iff capabilities.cancel)
144
+ ```
145
+
146
+ `state ∈ queued | running | succeeded | failed | cancelled` — deliberately
147
+ smaller than any provider's native state machine, so orchestration never learns
148
+ provider-specific vocabulary.
149
+
150
+ Registration is strict: a missing required method, a wrong/absent `apiVersion`,
151
+ or a provider that advertises `capabilities.cancel` without implementing
152
+ `cancelSession()` all throw at registration rather than at dispatch.
153
+
154
+ ## Planned providers
155
+
156
+ | id | Status | Notes |
157
+ |---|---|---|
158
+ | `ado-assign` | planned, Phase 3 | Automates the current manual flow: create an ADO work item carrying the rendered dispatch instructions, assign it to the Agency identity, poll for the linked PR. Buildable today with the existing `az` / `engine/ado` integration — **no unknown API**. |
159
+ | `agency` | blocked | Native Agency Hub session API. Needs the endpoint, auth model ("a different permissions protocol" per the Agency team), repo/branch binding, and PR linkage. The provider seam means this drops in without touching orchestration. |
160
+ | `github-copilot` | optional | The v1 path — assign a GitHub issue to the Copilot coding agent. The only one testable on a public repo. |
161
+
162
+ ## Configuration
163
+
164
+ | Field | Default | Meaning |
165
+ |---|---|---|
166
+ | `engine.cloudProvider` | *(unset)* | Fleet-wide default provider id. Unset ⇒ cloud dispatch unavailable fleet-wide. Settings → Agents Runtime & Models → **Cloud dispatch**. |
167
+ | `project.cloudProvider` | *(unset)* | Per-project override. |
168
+ | `meta.cloudProvider` | *(unset)* | Per-work-item override (most specific). |
169
+
170
+ `engine.cloudProvider` is validated against the **live registry**, not accepted
171
+ as free text — an id that resolves to nothing cannot be saved. The Settings
172
+ control (Agents Runtime & Models → **Cloud dispatch**) is a selector populated
173
+ from `GET /api/cloud-providers`; with no provider registered it disables itself
174
+ and states why, rather than offering a value the server would reject. A stored
175
+ value that is no longer registered stays selectable so an operator can clear it,
176
+ and an unreachable endpoint degrades without touching the stored config.
177
+
178
+ ### Discovery
179
+
180
+ `GET /api/cloud-providers` → `{ providers: [{ name, capabilities }] }` — the
181
+ cloud counterpart to `GET /api/runtimes`. Read-only, no input.
182
+
183
+ ## Still to come (Phases 2–5)
184
+
185
+ - **Phase 2 — dispatch + lifecycle.** `spawnAgent` renders the prompt and calls
186
+ `createSession`; a `pollCloudDispatches` tick phase adopts the resulting PR
187
+ into the existing PR record so the ADO/GitHub pollers and review/fix loops
188
+ take over unchanged. Two constraints already identified in the code:
189
+ `engine/orchestration/timeout.js` treats any processless active dispatch as a
190
+ stale orphan after `engine.heartbeatTimeout` (5 min), so cloud dispatches must
191
+ be excluded and given their own `cloudSessionTimeout` → `cancelSession`; and
192
+ they must not consume `engine.maxConcurrent` local slots.
193
+ - **Phase 3 — the `ado-assign` provider.**
194
+ - **Phase 4 — investigate-then-delegate.** A local `explore` agent runs first and
195
+ produces the scoped instructions; the engine then creates the cloud WI carrying
196
+ them. This is the actual product.
197
+ - **Phase 5 — dashboard surfaces + the native `agency` provider.**
198
+
199
+ ## Non-goals
200
+
201
+ - Cloud never becomes a default or a fallback for local dispatch.
202
+ - No attempt to run validation builds in the cloud. Local `verify` / `test` work
203
+ items still own validation, and the cloud PR flows into them.
204
+ - No new runtime adapter.
@@ -2,7 +2,7 @@
2
2
 
3
3
  The Command Center (CC) is the dashboard's conversational chat panel. It opens from the **CC** button in the top-right header and lets you drive the engine — dispatching work items, saving notes, approving plans, and asking questions about Minions state — through a persistent session on the configured CC runtime.
4
4
 
5
- CC is intentionally a thin wrapper around the runtime CLI: state changes happen via tool-driven calls to the dashboard's own REST API, not via parsed delimiter blocks. The end-to-end flow is `dashboard/js/command-center.js` `_ccDoSend()` → `POST /api/command-center` (or `/api/command-center/stream`) in `dashboard.js` (`handleCommandCenter`) → `engine/llm.js` `callLLM({ direct: true })` → runtime session persisted in `engine/state.db` (`cc_sessions`). Per-turn API mutations are correlated via the `X-CC-Turn-Id` header and surfaced as standalone `role='action'` chips rendered outside the assistant bubble (`_ccActionResultLine` + `addMsg('action', ...)`).
5
+ CC is intentionally a thin wrapper around the runtime CLI: state changes happen via tool-driven calls to the dashboard's own REST API, not via parsed delimiter blocks. The end-to-end flow is `dashboard/js/command-center.js` `_ccDoSend()` → `POST /api/command-center` (or `/api/command-center/stream`) in `dashboard.js` (`handleCommandCenter`) → `engine/agents/llm.js` `callLLM({ direct: true })` → runtime session persisted in `engine/state.db` (`cc_sessions`). Per-turn API mutations are correlated via the `X-CC-Turn-Id` header and surfaced as standalone `role='action'` chips rendered outside the assistant bubble (`_ccActionResultLine` + `addMsg('action', ...)`).
6
6
 
7
7
  For canonical detail (system prompt, session lifecycle, turn-ID surfacing pipeline, doc-chat integration, and CC API contract), read [`CLAUDE.md`](../CLAUDE.md) — see the **CC API Contract** and **Sessions** sections — and the source in [`dashboard/js/command-center.js`](../dashboard/js/command-center.js), [`dashboard.js`](../dashboard.js) (`handleCommandCenter`), and [`prompts/cc-system.md`](../prompts/cc-system.md).
8
8
 
@@ -91,13 +91,13 @@ All bundled runtimes currently enable it; a future adapter without image support
91
91
  | copilot | `true` | Images materialized to tmp files and passed as `--attachment <path>` (W-mqv7324u0021db5d). |
92
92
  | codex | `true` | Images materialized to tmp files and passed as repeatable `--image <path>` arguments. |
93
93
 
94
- **Worker-pool interaction (W-mray463s001zcee2).** The opt-in Copilot ACP worker pool (`engine.ccUseWorkerPool: true`, default ON for Copilot CC) is a **separate protocol path** from the table above — `engine/cc-worker-pool.js`'s `session/prompt` call only ever sends a single text content block (`[{ type: 'text', text: prompt }]`) and has no image/attachment content-block support today. This used to mean image turns were silently dropped whenever the pool was active, with a comment incorrectly blaming a Copilot capability gap (`imageInput:false`) that doesn't exist — Copilot's `imageInput` is `true` and fully supports images on the direct spawn path. The fix: `_invokeCcStream` (dashboard.js) now checks for a non-empty `images` array **before** routing through `resolveCcUseWorkerPool` — image-bearing turns bypass the warm pool for that turn only and cold-spawn a one-off CLI process via the working `--attachment` path (same as the non-pooled path always used), while image-less turns keep using the fast warm pool. If `engine/cc-worker-pool.js`'s ACP session protocol is later extended to carry attachments (Option A), this per-turn bypass in `_invokeCcStream` should be revisited/removed.
94
+ **Worker-pool interaction (W-mray463s001zcee2).** The opt-in Copilot ACP worker pool (`engine.ccUseWorkerPool: true`, default ON for Copilot CC) is a **separate protocol path** from the table above — `engine/agents/cc-worker-pool.js`'s `session/prompt` call only ever sends a single text content block (`[{ type: 'text', text: prompt }]`) and has no image/attachment content-block support today. This used to mean image turns were silently dropped whenever the pool was active, with a comment incorrectly blaming a Copilot capability gap (`imageInput:false`) that doesn't exist — Copilot's `imageInput` is `true` and fully supports images on the direct spawn path. The fix: `_invokeCcStream` (dashboard.js) now checks for a non-empty `images` array **before** routing through `resolveCcUseWorkerPool` — image-bearing turns bypass the warm pool for that turn only and cold-spawn a one-off CLI process via the working `--attachment` path (same as the non-pooled path always used), while image-less turns keep using the fast warm pool. If `engine/agents/cc-worker-pool.js`'s ACP session protocol is later extended to carry attachments (Option A), this per-turn bypass in `_invokeCcStream` should be revisited/removed.
95
95
 
96
96
  Because that bypass can spend tens of seconds starting a one-off process before the first model token, the SSE stream shows a user-focused “Looking at the image…” status instead of exposing the cold-start implementation detail or leaving an apparently stalled spinner. The one-off process does not resume the live worker's process-scoped ACP session ID: it starts a transport-compatible fresh session and receives prior conversation context through the existing transcript carryover. Every parsed direct-runtime event, including startup and reasoning events, resets the five-minute silence watchdog; the safety bound therefore still catches a truly silent process without killing an image turn that is making progress.
97
97
 
98
- `_resolveImageOpts` (in `engine/llm.js`) is pure and unit-tested: it forwards the `images` list only when `runtime.capabilities.imageInput` is truthy, otherwise returns the typed error so CC/doc-chat surface the envelope instead of a silently-text-only reply. `_spawnProcess` re-applies the same gate (`if (!caps.imageInput) adapterOpts.images = undefined`) as defense in depth. Image **filenames** are run through the untrusted-input fence (`buildSource('cc-image-filename', …)`) before they reach prompt text, since they are user-supplied.
98
+ `_resolveImageOpts` (in `engine/agents/llm.js`) is pure and unit-tested: it forwards the `images` list only when `runtime.capabilities.imageInput` is truthy, otherwise returns the typed error so CC/doc-chat surface the envelope instead of a silently-text-only reply. `_spawnProcess` re-applies the same gate (`if (!caps.imageInput) adapterOpts.images = undefined`) as defense in depth. Image **filenames** are run through the untrusted-input fence (`buildSource('cc-image-filename', …)`) before they reach prompt text, since they are user-supplied.
99
99
 
100
- **No new `engine.*` flag.** The limits above are module-level constants in `dashboard.js` (`CC_IMAGE_MAX_COUNT`, `CC_IMAGE_MAX_DECODED_BYTES`, `CC_IMAGE_MIME_ALLOWLIST`), not config keys; `engine/shared.js` `ENGINE_DEFAULTS` was not touched. Per CLAUDE.md Best Practice #9 (Settings parity), no Settings toggle is added because no new `engine.*` flag was introduced. Runtime selection that determines whether images are accepted is the existing `engine.ccCli` / `ccModel` override, which already has a Settings control.
100
+ **No new `engine.*` flag.** The limits above are module-level constants in `dashboard.js` (`CC_IMAGE_MAX_COUNT`, `CC_IMAGE_MAX_DECODED_BYTES`, `CC_IMAGE_MIME_ALLOWLIST`), not config keys; `engine/core/shared.js` `ENGINE_DEFAULTS` was not touched. Per CLAUDE.md Best Practice #9 (Settings parity), no Settings toggle is added because no new `engine.*` flag was introduced. Runtime selection that determines whether images are accepted is the existing `engine.ccCli` / `ccModel` override, which already has a Settings control.
101
101
 
102
102
  ## Operator-checkout write authority (interactive turns)
103
103
 
@@ -112,7 +112,7 @@ The "Create PR" chip offered by CC after a local edit follows the **same checkou
112
112
  - **`checkoutMode: 'live'`** (default for live-mode projects): unchanged behavior — the agent commits and pushes directly in the operator's live checkout as the CC system prompt instructs.
113
113
  - **`checkoutMode: 'worktree'`** (the default for most projects): the engine routes the changes through an isolated worktree deterministically and server-side so nothing depends on the LLM running git correctly.
114
114
 
115
- For worktree-mode projects the flow uses **`engine/create-pr-worktree.js`**:
115
+ For worktree-mode projects the flow uses **`engine/worktrees/create-pr.js`**:
116
116
 
117
117
  1. `prepareCreatePrWorktree` captures the live checkout's uncommitted changes (tracked diff via `git diff --binary HEAD` + untracked files) and applies them into a fresh worktree on a `cc-pr/<project>-<uid>` branch. The worktree is populated and verified **before** the live checkout is touched; on apply failure the worktree is removed and the live checkout is left untouched (operator never loses work).
118
118
  2. The agent then commits, pushes, and opens the PR from inside the worktree.
@@ -122,8 +122,8 @@ Dashboard endpoints added by this feature:
122
122
  - `POST /api/pr-action/prepare-create-pr-worktree` — called before the agent's git operations
123
123
  - `POST /api/pr-action/cleanup-create-pr-worktree` — called after the PR is open (or on failure)
124
124
 
125
- The `buildCreatePrFollowups` helper in `engine/pr-action.js` emits worktree-aware instructions for worktree mode and the legacy live-checkout instructions for live mode; `dashboard.js /api/pr-action/offer-create-pr` resolves the checkout mode and passes it through.
125
+ The `buildCreatePrFollowups` helper in `engine/providers/pr-action.js` emits worktree-aware instructions for worktree mode and the legacy live-checkout instructions for live mode; `dashboard.js /api/pr-action/offer-create-pr` resolves the checkout mode and passes it through.
126
126
 
127
127
  ## Per-turn surfacing pipeline
128
128
 
129
- CC handler generates `ccTurnId = 'cct-' + shared.uid()` per request; injected into sysprompt AND prompt body via `_ccTurnHeaderPart(turnId)` (load-bearing: on resumed sessions `engine/llm.js` skips re-sending the sysprompt, so without body injection CC keeps the stale turn ID). Handler reads via `_readCcTurnIdHeader(req)` and calls `_recordCcTurnCreation(turnId, ...)` on success. End-of-turn: `_buildSyntheticActionResultsForTurn` produces synthetic `{action, result}` pairs (`_serverExecuted: true`). Client renders as standalone `role='action'` messages outside the assistant bubble. TTL: 5 min. Endpoints wired: `/api/work-items`, `/api/notes`, `/api/plan`, `/api/knowledge`, `/api/watches`.
129
+ CC handler generates `ccTurnId = 'cct-' + shared.uid()` per request; injected into sysprompt AND prompt body via `_ccTurnHeaderPart(turnId)` (load-bearing: on resumed sessions `engine/agents/llm.js` skips re-sending the sysprompt, so without body injection CC keeps the stale turn ID). Handler reads via `_readCcTurnIdHeader(req)` and calls `_recordCcTurnCreation(turnId, ...)` on success. End-of-turn: `_buildSyntheticActionResultsForTurn` produces synthetic `{action, result}` pairs (`_serverExecuted: true`). Client renders as standalone `role='action'` messages outside the assistant bubble. TTL: 5 min. Endpoints wired: `/api/work-items`, `/api/notes`, `/api/plan`, `/api/knowledge`, `/api/watches`.
@@ -13,7 +13,7 @@ The engine delivers the absolute path two ways before spawning each agent:
13
13
  spawns
14
14
  - The same path appears in the rendered playbook prompt
15
15
 
16
- Path shape (resolved by `shared.dispatchCompletionReportPath()` in `engine/shared.js`):
16
+ Path shape (resolved by `shared.dispatchCompletionReportPath()` in `engine/core/shared.js`):
17
17
 
18
18
  ```
19
19
  <MINIONS_DIR>/engine/completions/<dispatch-id>.json
@@ -39,7 +39,7 @@ nonce never enters reusable process state or untrusted task content. The engine
39
39
  generates it with `crypto.randomBytes(16).toString('hex')` in
40
40
  `engine.js:spawnAgent()` and stores it on the in-memory active-process record.
41
41
  The agent is required to copy the value verbatim into the report's `nonce`
42
- field. On parse, `engine/lifecycle.js:runPostCompletionHooks()` compares
42
+ field. On parse, `engine/orchestration/lifecycle.js:runPostCompletionHooks()` compares
43
43
  `report.nonce` against the in-memory value:
44
44
 
45
45
  - **Match** — the report is trusted and processed normally.
@@ -60,6 +60,25 @@ Security event log lines are emitted on the `error` channel and are designed to
60
60
 
61
61
  Do **not** invent, regenerate, or share the nonce across dispatches — each spawn gets a unique value.
62
62
 
63
+ ### The report is also the completion boundary (W-ms9tcs3o01y03383)
64
+
65
+ The nonce does more than gate trust after the fact — combined with a confirmed
66
+ terminal process exit it *is* the signal that the prompt is finished.
67
+ `engine.js#resolveCloseCompletionBoundary` accepts a report only when the child
68
+ exited with code `0` **and** `shared.acceptCompletionReportForNonce` matches the
69
+ report's `nonce` to this dispatch's spawn value (carried across steering
70
+ resumes). When both hold, the engine releases any queued steering-resume
71
+ bookkeeping instead of spawning it, so a finished dispatch terminalizes on the
72
+ next event-loop turn rather than waiting out `engine.resumeHeartbeatTimeout`.
73
+
74
+ This is strictly additive to validation: a non-zero exit, a missing report, a
75
+ malformed report, or a missing/mismatched nonce all fall through to exactly the
76
+ failure and timeout paths described above. The same predicate backs the
77
+ resume-stall watchdog in `engine/orchestration/timeout.js`, so both finalizers
78
+ apply one trust boundary. Completed dispatch records record which path ran in
79
+ `completionPath` (`shared.DISPATCH_COMPLETION_PATH`); see
80
+ [timeouts-and-liveness.md](timeouts-and-liveness.md#completion-boundary-terminal-exit--a-dispatch-owned-report-w-ms9tcs3o01y03383).
81
+
63
82
  ## Top-level schema
64
83
 
65
84
  ```json
@@ -84,10 +103,10 @@ Do **not** invent, regenerate, or share the nonce across dispatches — each spa
84
103
  | Field | Type | Notes |
85
104
  |---|---|---|
86
105
  | `status` | string | One of `success`, `partial`, `failed`. Aliases `done` / `complete` are accepted on read for `success`. The engine refuses to mark a work item done without a status. |
87
- | `summary` | string | Short prose describing what changed and how it was validated. Truncated to 500 chars in dashboard surfaces (`engine/queries.js`). Do not summarize validation as "tests passed" — name the commands that ran. |
106
+ | `summary` | string | Short prose describing what changed and how it was validated. Truncated to 500 chars in dashboard surfaces (`engine/core/queries.js`). Do not summarize validation as "tests passed" — name the commands that ran. |
88
107
  | `verdict` | string \| null | Required for review tasks: `approved` or `changes-requested`. `null` for non-review tasks. Aliases: `approve`, `request_changes`, `changes_requested`. |
89
108
  | `pr` | string | PR URL, `PR-<number>`, or `N/A`. The engine uses this to attach the PR to the work item; missing-PR detection treats anything other than a recognizable URL/PR id as missing unless `noop: true` is set. |
90
- | `failure_class` | string | One of the `failure_class` enum values below, or `N/A`. Drives retry policy in `engine/dispatch.js`. |
109
+ | `failure_class` | string | One of the `failure_class` enum values below, or `N/A`. Drives retry policy in `engine/orchestration/dispatch.js`. |
91
110
  | `retryable` | boolean | `true` if the engine should auto-retry the dispatch on failure. Overrides the default per-class retry policy when present. |
92
111
  | `needs_rerun` | boolean | `true` if the same work needs to be re-dispatched (vs. retried). Used by build-fix and review-fix loops. |
93
112
  | `artifacts` | array | Durable artifacts the agent created or updated; surfaces in the dashboard work-item detail modal. See [Artifacts](#artifacts). |
@@ -131,11 +150,11 @@ derive an identity from reviewer prose.
131
150
  "project": "minions-opg",
132
151
  "authorAgent": "dallas",
133
152
  "applicability": "project-team",
134
- "files": ["engine/lifecycle.js"],
153
+ "files": ["engine/orchestration/lifecycle.js"],
135
154
  "symbols": ["normalizeReviewLearningCompletionPayload"],
136
155
  "tags": ["completion-contract"],
137
156
  "evidence": [
138
- "engine/lifecycle.js:5742",
157
+ "engine/orchestration/lifecycle.js:5742",
139
158
  "test/unit/structured-completion.test.js:158"
140
159
  ],
141
160
  "confidence": 0.92,
@@ -146,7 +165,7 @@ derive an identity from reviewer prose.
146
165
  {
147
166
  "id": "M-review-lesson-1",
148
167
  "outcome": "applied",
149
- "evidence": "engine/lifecycle.js:5742 validates each optional field independently."
168
+ "evidence": "engine/orchestration/lifecycle.js:5742 validates each optional field independently."
150
169
  }
151
170
  ]
152
171
  }
@@ -270,11 +289,11 @@ P-mqyp0009y025z6a7. An optional `invalidates: string[]` field in the completion
270
289
  - Each cancellation stamps `cancellationReason: "invalidated-by:<source-wi-id>"` and `cancelledAt` on the cancelled WI.
271
290
  - A `work_items` state event is emitted for each successful cancellation so the dashboard reflects it.
272
291
 
273
- **Implementation:** `engine/lifecycle.js#applyGoalInvalidation`, called from `runPostCompletionHooks` after the source WI is marked done.
292
+ **Implementation:** `engine/orchestration/lifecycle.js#applyGoalInvalidation`, called from `runPostCompletionHooks` after the source WI is marked done.
274
293
 
275
294
  ## `failure_class` enum
276
295
 
277
- Defined in `engine/shared.js` as `FAILURE_CLASS`. Use the canonical hyphenated string.
296
+ Defined in `engine/core/shared.js` as `FAILURE_CLASS`. Use the canonical hyphenated string.
278
297
 
279
298
  | Value | When to use | Default escalation |
280
299
  |---|---|---|
@@ -284,6 +303,7 @@ Defined in `engine/shared.js` as `FAILURE_CLASS`. Use the canonical hyphenated s
284
303
  | `merge-conflict` | Git merge conflict in worktree or dependency | Retry same agent |
285
304
  | `build-failure` | Compilation, lint, or test failure introduced by the agent | Retry same agent |
286
305
  | `timeout` | Hard runtime timeout or stale-orphan timeout | Retry with fresh session |
306
+ | `engine-restart` | Dispatch process ownership was lost across an engine restart | Retry against the bounded infrastructure retry counter, not the semantic agent retry budget |
287
307
  | `empty-output` | Agent produced no meaningful output | Flag for human review |
288
308
  | `runtime-truncated-exit` | The runtime process exited `0` in the MIDDLE of a turn: its output holds a tool call that was started and never completed (observed: Copilot exiting right after starting a long `npm test`), and no completion report was written. Detected through the optional adapter hook `hasUnfinishedToolCall`. Distinct from `completion-report-missing`, which means the turn ended normally but the agent skipped the report. | Retry — a fresh attempt normally runs to completion |
289
309
  | `runtime-session-stale` | A `--resume`d runtime died because its persisted session is unusable: the backing conversation is gone, or its persisted working directory no longer exists (the engine removed/recycled that worktree). Previously surfaced as an opaque `empty-output`. | Retry — the engine clears the cached session first |
@@ -311,6 +331,9 @@ Defined in `engine/shared.js` as `FAILURE_CLASS`. Use the canonical hyphenated s
311
331
  | `live-checkout-stale-base` | Spawn-time defense found local `mainRef` ahead of `origin/<mainRef>`; forking would inherit committed contamination | Never retry — operator reconciles the local base first |
312
332
  | `live-checkout-wrong-base` | New-branch fork requested but operator HEAD is on an unrelated branch, not the project base | Never retry — operator checks out `mainRef` first (or enable `liveCheckoutAutoBaseRepair`) |
313
333
  | `invalid-workdir` | Dispatch carried a `meta.workdir` override that failed validation (absolute path, `..` segment, escape, etc.) | Never retry until the WI's `meta.workdir` is fixed |
334
+ | `invalid-execution-surface` | `meta.executionSurface` failed fail-closed validation: an unknown/non-string surface, `control-plane` on a work type that reads or mutates repository files, or `cloud` on a work type whose deliverable is not a PR. Each surface is an ALLOWLIST (`shared.CONTROL_PLANE_ELIGIBLE_TYPES` / `shared.CLOUD_ELIGIBLE_TYPES`), never inferred from prose. | Never retry — operator fixes or removes the field |
335
+ | `cloud-provider-unavailable` | The WI validly declared `meta.executionSurface: "cloud"` but no registered **and** configured cloud provider resolved. Structural, not connectivity — the engine never downgrades the refusal to a local dispatch. | Never retry — operator configures a provider or drops the declaration |
336
+ | `git-ref-lock` | `git fetch` lost a **local** ref-lock race on a shared remote-tracking ref (`cannot lock ref 'refs/remotes/origin/<main>'`). A serialization defect, deliberately not `network-error`, which previously hid the cause and burned retries. `shared.withSerializedRemoteRefRefresh` removes the in-process races; this covers the residual cross-process case. | Retry — the ref lock clears in seconds |
314
337
  | `model-unavailable` | Requested model returned `overloaded_error` / 503 / service unavailable | Retry — engine swaps in the runtime-appropriate fallback model |
315
338
  | `workspace-manifest-repo-forbidden` | Dispatch routed an agent to a repo not in its `workspace_manifest.allowed_repos`. See [Pre-spawn preflight vs agent failure](#pre-spawn-preflight-vs-agent-failure). | Never retry |
316
339
  | `workspace-manifest-tool-forbidden` | Out-of-scope tool call caught by manifest enforcement at the runtime gate | Never retry as-is |
@@ -328,7 +351,7 @@ Use `"N/A"` when `status` is `success` or `partial` without a failure.
328
351
 
329
352
  A handful of `failure_class` values are written by the **engine at dispatch time**, *before the agent process is ever spawned*. They are emitted from `spawnAgent` (`engine.js`) while it sets up the worktree, validates the checkout, or enforces the workspace manifest — the engine calls `completeDispatch(... DISPATCH_RESULT.ERROR ...)` and returns without launching any runtime. **A completion report carrying one of these classes does not imply the agent ran any work**; no prompt was sent, no edits were attempted, and there is no agent-authored `note` or output to interpret.
330
353
 
331
- | `failure_class` | Pre-spawn trigger | `engine/shared.js` |
354
+ | `failure_class` | Pre-spawn trigger | `engine/core/shared.js` |
332
355
  |---|---|---|
333
356
  | `worktree-preflight` | Worktree placement validation failed: the worktree would nest inside the project, the resolved root collapsed to a drive root, or the base directory was missing. | `FAILURE_CLASS.WORKTREE_PREFLIGHT` (~`shared.js:4279`) |
334
357
  | `live-checkout-dirty` | Live-checkout mode (`project.checkoutMode: 'live'`) found uncommitted changes in `project.localPath`; the engine never `reset`/`clean`s the operator tree, so it refuses to spawn. | `FAILURE_CLASS.LIVE_CHECKOUT_DIRTY` (~`shared.js:4289`) |
@@ -354,7 +377,7 @@ To signal a no-op:
354
377
  }
355
378
  ```
356
379
 
357
- Engine behavior when `noop: true` (`engine/lifecycle.js` `parseCompletionNoop` + `runPostCompletionHooks`):
380
+ Engine behavior when `noop: true` (`engine/orchestration/lifecycle.js` `parseCompletionNoop` + `runPostCompletionHooks`):
358
381
 
359
382
  - The work item is marked `done` with the rationale stored as `_noopReason` (visible in the dashboard).
360
383
  - The PR-attachment contract is **skipped**, so an empty `pr` field will not be flagged as a silent failure or auto-retried.
@@ -373,7 +396,7 @@ If the live PR branch did not advance, the completion must set `noop: true` and
373
396
  "reviewFindingResolution": {
374
397
  "findingId": "review-0123456789abcdef",
375
398
  "disposition": "satisfied-elsewhere",
376
- "currentCodeEvidence": "engine/lifecycle.js:4600 already enforces the condition; test/unit/example.test.js covers it."
399
+ "currentCodeEvidence": "engine/orchestration/lifecycle.js:4600 already enforces the condition; test/unit/example.test.js covers it."
377
400
  }
378
401
  }
379
402
  ```
@@ -405,7 +428,7 @@ optional `followups` array on its completion report:
405
428
  }
406
429
  ```
407
430
 
408
- Engine behavior (`engine/lifecycle.js` `processCompletionFollowups`):
431
+ Engine behavior (`engine/orchestration/lifecycle.js` `processCompletionFollowups`):
409
432
 
410
433
  - Each entry is logged at `info`: `Followup audit (<agent> wi=<parent>): dispatched <wi_id> — <title> (comment=<id>) — <reason>`.
411
434
  - If the claimed `wi_id` does not exist in any project SQL scope at parse time, a `warn` is emitted. This catches dispatches that were deduped to a 409 / 200-duplicate response, reverted, or never actually landed.
@@ -420,7 +443,7 @@ Both default to `false`. Together they let the agent override the engine's defau
420
443
  - `retryable: false` — engine should not auto-retry; the failure is terminal for this dispatch.
421
444
  - `needs_rerun: true` — even on `status: success`, the same work item should be re-dispatched (used by review-fix and build-fix loops where one pass cannot complete the work).
422
445
 
423
- When the agent sets either field, it overrides the per-`failure_class` default in `isRetryableFailureReason()` (`engine/dispatch.js`).
446
+ When the agent sets either field, it overrides the per-`failure_class` default in `isRetryableFailureReason()` (`engine/orchestration/dispatch.js`).
424
447
 
425
448
  ## Artifacts
426
449
 
@@ -436,11 +459,11 @@ When the agent sets either field, it overrides the per-`failure_class` default i
436
459
  | `path` | string | Relative repo path, absolute path, or URL. |
437
460
  | `title` | string | Short label used in the dashboard list. |
438
461
 
439
- The dashboard caps the rendered list at 20 artifacts per report (`engine/queries.js`). Include every durable artifact you created or updated — PRs, plan/PRD files, key source files, inbox notes — so the work-item detail modal can show them.
462
+ The dashboard caps the rendered list at 20 artifacts per report (`engine/core/queries.js`). Include every durable artifact you created or updated — PRs, plan/PRD files, key source files, inbox notes — so the work-item detail modal can show them.
440
463
 
441
464
  ## Validation and source-of-truth precedence
442
465
 
443
- The engine reads completion signals in this order (`engine/lifecycle.js`):
466
+ The engine reads completion signals in this order (`engine/orchestration/lifecycle.js`):
444
467
 
445
468
  1. The JSON report at `MINIONS_COMPLETION_REPORT` — sole structured-completion source. `parseCompletionReportFile()` requires a well-formed plain-object JSON with a `status` field; anything else is dropped with a warn log.
446
469
  2. Runtime result events + the `[process-exit]` sentinel in `live-output.log` — used to confirm the dispatch actually exited and to capture the runtime's own result prose into `resultSummary`. These cannot supply structured fields (`pr`, `failure_class`, `retryable`, `noop`, `verdict`, `artifacts`, …); without a report on disk those signals are simply absent.
@@ -462,7 +485,7 @@ The legacy fenced ` ```completion ` block parser and the `task_complete` prose-s
462
485
  "needs_rerun": false,
463
486
  "artifacts": [
464
487
  {"type": "pr", "path": "https://github.com/opg-microsoft/minions/pull/2360", "title": "PR-2360"},
465
- {"type": "file", "path": "engine/spawn-agent.js", "title": "Stale-HEAD guard"},
488
+ {"type": "file", "path": "engine/agents/spawn-agent.js", "title": "Stale-HEAD guard"},
466
489
  {"type": "file", "path": "test/unit/spawn-agent.test.js", "title": "New unit test"}
467
490
  ]
468
491
  }
@@ -514,8 +537,8 @@ The legacy fenced ` ```completion ` block parser and the `task_complete` prose-s
514
537
 
515
538
  ## Related
516
539
 
517
- - `engine/shared.js` — `FAILURE_CLASS`, `COMPLETION_FIELDS`, `dispatchCompletionReportPath()`
518
- - `engine/lifecycle.js` — `parseCompletionReportFile()`, `parseCompletionNoop()`, `enforcePrAttachmentContract()`
519
- - `engine/dispatch.js` — `isRetryableFailureReason()`, `writeFailedAgentReport()`
540
+ - `engine/core/shared.js` — `FAILURE_CLASS`, `COMPLETION_FIELDS`, `dispatchCompletionReportPath()`
541
+ - `engine/orchestration/lifecycle.js` — `parseCompletionReportFile()`, `parseCompletionNoop()`, `enforcePrAttachmentContract()`
542
+ - `engine/orchestration/dispatch.js` — `isRetryableFailureReason()`, `writeFailedAgentReport()`
520
543
  - `docs/rfc-completion-json.md` — original RFC describing the protocol's design
521
544
  - `playbooks/shared-rules.md` — the per-task "Completion Reports" instruction every playbook inherits
package/docs/constants.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Constants — No Magic Strings
2
2
 
3
- Cross-cutting status / type / condition values are defined in [`engine/shared.js`](../engine/shared.js). Engine and dashboard code should use these constants instead of raw strings; source-inspection tests enforce the load-bearing comparisons.
3
+ Cross-cutting status / type / condition values are defined in [`engine/core/shared.js`](../engine/core/shared.js). Engine and dashboard code should use these constants instead of raw strings; source-inspection tests enforce the load-bearing comparisons.
4
4
 
5
5
  ```js
6
6
  WI_STATUS = { PENDING, DISPATCHED, DONE, FAILED, PAUSED, QUEUED, DECOMPOSED, CANCELLED }
@@ -20,7 +20,7 @@ WATCH_CONDITION = { MERGED, BUILD_FAIL, BUILD_PASS, COMPLETED, FAILED, STATUS_CH
20
20
  STAGE_ADVANCED, STUCK_IN_STAGE }
21
21
  WATCH_ABSOLUTE_CONDITIONS = Set([MERGED, BUILD_FAIL, BUILD_PASS, COMPLETED, FAILED, CONCLUDED, APPROVED,
22
22
  REJECTED, READY_FOR_MERGE, RETRY_LIMIT_REACHED, ALL_ITEMS_DONE, ITEM_FAILED_N_TIMES]) // fire-once
23
- WATCH_TARGET_TYPE = { PR, WORK_ITEM, MEETING, PLAN, SCHEDULE, PIPELINE, DISPATCH, AGENT } // built-ins; watches.d/*.js may register more
23
+ WATCH_TARGET_TYPE = { PR, WORK_ITEM, MEETING, PLAN, SCHEDULE, PIPELINE, DISPATCH, AGENT } // built-ins; watch-plugins/*.js may register more
24
24
  WATCH_ACTION_TYPE = { NOTIFY, DISPATCH_WORK_ITEM, RUN_SKILL, WEBHOOK, MINIONS_API,
25
25
  CANCEL_WORK_ITEM, TRIGGER_PIPELINE, ARCHIVE_PLAN, RESUME_PLAN, CC_TRIAGE }
26
26
  BUILD_STATUS = { PASSING, FAILING, RUNNING, NONE }
@@ -30,12 +30,19 @@ MEETING_STATUS = { INVESTIGATING, DEBATING, CONCLUDING, COMPLETED, ARCHIVED }
30
30
  PIPELINE_STATUS = { PENDING, RUNNING, COMPLETED, FAILED, PAUSED, WAITING_HUMAN, STOPPED }
31
31
  STAGE_TYPE = { TASK, MEETING, PLAN, API, MERGE_PRS, SCHEDULE, WAIT, PARALLEL, CONDITION } // pipelines/*.json stage kinds
32
32
  CHECKOUT_MODES = { WORKTREE, LIVE } // project.checkoutMode; see docs/live-checkout-mode.md
33
+ CONTROL_PLANE_ELIGIBLE_TYPES = Set([setup, ask, explore, meeting]) // meta.executionSurface: 'control-plane' allowlist — fail-closed
34
+ CLOUD_ELIGIBLE_TYPES = Set([implement, fix, test, docs]) // meta.executionSurface: 'cloud' allowlist — deliberately DISJOINT from the control-plane set
35
+ KB_CATEGORIES = [architecture, conventions, project-notes, build-reports, reviews] // knowledge/<category>/
36
+ WORKTREE_OWNER_MARKER = '.minions-worktree' // engine-created worktrees; unmarked out-of-root paths are foreign (GC fails open)
37
+ QUARANTINE_REF_RETENTION_MS = 30d // single source for BOTH quarantine dirs and refs/minions/quarantine-wip/*; resolve overrides via resolveQuarantineRetentionMs(config)
33
38
  FAILURE_CLASS = { ... } // dispatch failure taxonomy — full enum in docs/completion-reports.md
34
39
  ```
35
40
 
41
+ `PROCESS_TERMINATION_SITES` (46 registered sites) is the one inventory that lives in [`engine/processes/process-utils.js`](../engine/processes/process-utils.js) rather than `shared.js`. Every kill must name a registered site via `shared.terminateProcess(<site>, target, ownership)` — see [docs/process-termination.md](process-termination.md).
42
+
36
43
  ## Engine defaults
37
44
 
38
- Retry/timeout limits, sweep cadences, fleet ceilings, and managed-spawn caps live in `ENGINE_DEFAULTS` (also in `engine/shared.js`). Read defaults from there rather than re-declaring. Some keys resolve a `config.engine.*` override through a helper (`resolveAgentMaxBudget`, etc.); others are source-only, so verify the call site before documenting a Settings/config knob.
45
+ Retry/timeout limits, sweep cadences, fleet ceilings, and managed-spawn caps live in `ENGINE_DEFAULTS` (also in `engine/core/shared.js`). Read defaults from there rather than re-declaring. Some keys resolve a `config.engine.*` override through a helper (`resolveAgentMaxBudget`, etc.); others are source-only, so verify the call site before documenting a Settings/config knob.
39
46
 
40
47
  ## Invariants
41
48