@yemi33/minions 0.1.2424 → 0.1.2425

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 (182) hide show
  1. package/bin/install-internal-minions.js +1209 -0
  2. package/bin/minions.js +75 -9
  3. package/dashboard/docs/typography.md +27 -12
  4. package/dashboard/js/command-center.js +13 -11
  5. package/dashboard/js/live-stream.js +1 -1
  6. package/dashboard/js/memory-search.js +388 -43
  7. package/dashboard/js/modal-qa.js +5 -5
  8. package/dashboard/js/qa.js +39 -26
  9. package/dashboard/js/refresh.js +37 -18
  10. package/dashboard/js/render-agents.js +14 -18
  11. package/dashboard/js/render-dispatch.js +14 -74
  12. package/dashboard/js/render-inbox.js +3 -3
  13. package/dashboard/js/render-meetings.js +8 -8
  14. package/dashboard/js/render-other.js +45 -20
  15. package/dashboard/js/render-pipelines.js +6 -6
  16. package/dashboard/js/render-plans.js +14 -14
  17. package/dashboard/js/render-prd.js +46 -46
  18. package/dashboard/js/render-prs.js +9 -65
  19. package/dashboard/js/render-schedules.js +5 -5
  20. package/dashboard/js/render-skills.js +6 -6
  21. package/dashboard/js/render-utils.js +4 -4
  22. package/dashboard/js/render-watches.js +4 -16
  23. package/dashboard/js/render-work-items.js +95 -107
  24. package/dashboard/js/settings.js +175 -74
  25. package/dashboard/layout.html +3 -2
  26. package/dashboard/pages/inbox.html +1 -1
  27. package/dashboard/pages/work.html +1 -1
  28. package/dashboard/shared/model-display.js +9 -0
  29. package/dashboard/shared/watches-source.js +43 -0
  30. package/dashboard/shared/welcome-popup.js +259 -0
  31. package/dashboard/slim/body.html +6 -6
  32. package/dashboard/slim/js/chat.js +4 -2
  33. package/dashboard/slim/js/history.js +10 -3
  34. package/dashboard/slim/js/modals-tiles.js +85 -16
  35. package/dashboard/slim/js/status.js +12 -16
  36. package/dashboard/slim/styles.css +24 -4
  37. package/dashboard/styles.css +62 -27
  38. package/dashboard-build.js +8 -2
  39. package/dashboard.js +3854 -2514
  40. package/docs/README.md +6 -4
  41. package/docs/api-errors.md +144 -0
  42. package/docs/auto-discovery.md +83 -61
  43. package/docs/capture-demos.js +173 -30
  44. package/docs/command-center.md +5 -1
  45. package/docs/completion-reports.md +127 -11
  46. package/docs/constants.md +15 -3
  47. package/docs/constellation-style-telemetry.md +2 -2
  48. package/docs/copilot-cli-schema.md +32 -22
  49. package/docs/cross-repo-plans.md +30 -30
  50. package/docs/demo/memory-system.html +1 -1
  51. package/docs/deprecated.json +132 -18
  52. package/docs/diagnostics-memory.md +8 -4
  53. package/docs/human-vs-automated.md +2 -2
  54. package/docs/internal-install.md +190 -0
  55. package/docs/kb-pr3223-cascade-archiving.md +16 -0
  56. package/docs/kb-pr696-merge-conflict-docs.md +23 -0
  57. package/docs/kb-sweep.md +2 -2
  58. package/docs/keep-processes.md +8 -1
  59. package/docs/live-checkout-mode.md +1 -1
  60. package/docs/managed-spawn.md +21 -3
  61. package/docs/named-agents.md +3 -2
  62. package/docs/plan-lifecycle.md +32 -32
  63. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  64. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  65. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  66. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  67. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  68. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  69. package/docs/qa-runbook-lifecycle.md +67 -51
  70. package/docs/qa-runbooks.md +2 -2
  71. package/docs/runtime-adapters.md +101 -50
  72. package/docs/security.md +28 -0
  73. package/docs/self-improvement.md +47 -13
  74. package/docs/skills.md +29 -0
  75. package/docs/slim-ux/concepts.md +2 -2
  76. package/docs/specs/agent-configurability.md +43 -41
  77. package/docs/specs/agent-rename.md +13 -13
  78. package/docs/team-memory.md +283 -14
  79. package/docs/tutorials/03-plan-driven-feature.md +3 -3
  80. package/docs/tutorials/04-runtimes-and-harness.md +18 -21
  81. package/docs/tutorials/08-operations-and-recovery.md +3 -2
  82. package/docs/tutorials/README.md +4 -3
  83. package/docs/visual-evidence-ci.md +103 -0
  84. package/docs/watches.md +35 -30
  85. package/docs/worktree-lifecycle.md +151 -13
  86. package/engine/acp-transport.js +1 -0
  87. package/engine/ado.js +80 -33
  88. package/engine/agent-api-validation.js +571 -0
  89. package/engine/agent-worker-pool.js +7 -6
  90. package/engine/api-contracts/agent-content.js +761 -0
  91. package/engine/api-contracts/cc-ops.js +219 -0
  92. package/engine/api-contracts/config-runtime.js +419 -0
  93. package/engine/api-contracts/core.js +138 -0
  94. package/engine/api-contracts/index.js +506 -0
  95. package/engine/api-contracts/orchestration.js +602 -0
  96. package/engine/api-contracts/pull-requests.js +323 -0
  97. package/engine/api-contracts/qa-process.js +764 -0
  98. package/engine/api-contracts/work-plan-prd.js +494 -0
  99. package/engine/api-validation.js +543 -0
  100. package/engine/cc-api-validation.js +586 -0
  101. package/engine/cc-worker-pool.js +9 -1
  102. package/engine/cleanup.js +393 -271
  103. package/engine/cli.js +65 -7
  104. package/engine/comment-classifier.js +70 -5
  105. package/engine/consolidation.js +123 -5
  106. package/engine/content-api-validation.js +606 -0
  107. package/engine/create-pr-worktree.js +22 -1
  108. package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
  109. package/engine/dispatch.js +33 -0
  110. package/engine/distribution.js +189 -0
  111. package/engine/execution-model.js +59 -0
  112. package/engine/features.js +12 -0
  113. package/engine/github.js +67 -25
  114. package/engine/harness-context.js +313 -0
  115. package/engine/inbox-store.js +1 -1
  116. package/engine/kb-sweep.js +161 -38
  117. package/engine/keep-process-sweep.js +43 -3
  118. package/engine/lifecycle.js +1066 -165
  119. package/engine/llm.js +4 -0
  120. package/engine/managed-spawn.js +463 -48
  121. package/engine/meeting.js +197 -34
  122. package/engine/memory-retrieval.js +280 -0
  123. package/engine/memory-store.js +821 -38
  124. package/engine/model-discovery.js +17 -3
  125. package/engine/pipeline.js +802 -14
  126. package/engine/plan-prd-validation.js +745 -0
  127. package/engine/playbook.js +206 -17
  128. package/engine/pooled-agent-process.js +6 -1
  129. package/engine/pr-action.js +1 -1
  130. package/engine/pr-issue-validation.js +653 -0
  131. package/engine/pr-resolve.js +75 -13
  132. package/engine/prd-store.js +39 -6
  133. package/engine/preflight.js +7 -2
  134. package/engine/process-utils.js +79 -31
  135. package/engine/projects.js +198 -12
  136. package/engine/promotion.js +371 -0
  137. package/engine/qa-from-prd.js +3 -0
  138. package/engine/qa-process-validation.js +572 -0
  139. package/engine/qa-runbooks.js +41 -37
  140. package/engine/qa-runners/maestro.js +11 -7
  141. package/engine/qa-runners/playwright.js +37 -14
  142. package/engine/qa-runners.js +9 -10
  143. package/engine/qa-runs.js +124 -11
  144. package/engine/qa-sessions.js +139 -40
  145. package/engine/quarantine-refs.js +81 -11
  146. package/engine/queries.js +163 -9
  147. package/engine/review-learning-backfill.js +482 -0
  148. package/engine/review-learning.js +1236 -0
  149. package/engine/runtimes/claude.js +60 -2
  150. package/engine/runtimes/codex.js +57 -16
  151. package/engine/runtimes/contract.js +21 -0
  152. package/engine/runtimes/copilot.js +51 -3
  153. package/engine/runtimes/index.js +1 -0
  154. package/engine/scheduler.js +201 -32
  155. package/engine/settings-validation.js +909 -0
  156. package/engine/shared.js +705 -208
  157. package/engine/spawn-agent.js +48 -21
  158. package/engine/steering-constraints.js +31 -0
  159. package/engine/steering-store.js +10 -2
  160. package/engine/steering.js +14 -2
  161. package/engine/timeout.js +404 -111
  162. package/engine/untrusted-fence.js +24 -10
  163. package/engine/watch-actions.js +7 -2
  164. package/engine/watches.js +353 -51
  165. package/engine/work-item-validation.js +561 -0
  166. package/engine/work-items-store.js +61 -1
  167. package/engine/worktree-gc.js +656 -37
  168. package/engine/worktree-preflight.js +154 -0
  169. package/engine.js +2068 -342
  170. package/package.json +1 -1
  171. package/playbooks/_pr-description-audit.md +88 -41
  172. package/playbooks/build-fix-complex.md +3 -3
  173. package/playbooks/docs.md +2 -2
  174. package/playbooks/fix.md +18 -18
  175. package/playbooks/implement.md +18 -18
  176. package/playbooks/qa-session-draft.md +10 -12
  177. package/playbooks/qa-session-execute.md +16 -12
  178. package/playbooks/qa-validate.md +8 -7
  179. package/playbooks/shared-rules.md +45 -0
  180. package/playbooks/test.md +4 -0
  181. package/prompts/cc-system.md +1 -1
  182. package/skills/check-self-authored-review-comment/SKILL.md +2 -2
@@ -10,18 +10,18 @@ W-mpdad3mq000m53bb).
10
10
  ## Runbook location
11
11
 
12
12
  `<MINIONS_DIR>/projects/<project-name>/runbooks/<runbook-id>.json`. Each entry:
13
- `{ id, name, target, steps, expectedArtifacts, createdAt, createdBy }`.
14
- CRUD via `GET/POST /api/qa/runbooks` (POST returns the new runbook with
15
- engine-assigned `id`). `target` is a name from `/api/managed-processes` or
13
+ `{ id, name, project, targetName, steps, expectedArtifacts, createdAt, updatedAt }`.
14
+ CRUD via `GET/POST /api/qa/runbooks`; POST accepts the full spec, including the
15
+ caller-supplied kebab-case `id`, and returns the saved record. `targetName` is a name from `/api/managed-processes` or
16
16
  `/api/keep-processes` (deduped by `<project>::<name>`, managed wins).
17
17
 
18
18
  ## Run-record path
19
19
 
20
- The SQL `qa_runs` table stores newest-first, capped run records. Each run:
21
- `{ id, runbookId, runbookName, target, status, startedAt, completedAt, workItemId, agentId, artifacts }`.
22
- `status` ∈ `pending|dispatched|running|passed|failed|error`. Created by
23
- `POST /api/qa/runbooks/run` (`{ id }`), which dispatches a `qa-validate`
24
- work item and seeds the run with `dispatched`. Read via
20
+ The SQL `qa_runs` table stores capped run records, sorted newest-first on read. Each run:
21
+ `{ id, runbookId, targetName, project, workItemId, status, startedAt, completedAt, artifacts, summary, createdAt }`.
22
+ `status` ∈ `pending|running|passed|failed|errored`. Created by
23
+ `POST /api/qa/runbooks/run` (`{ runbookId, targetName, agent? }`), which dispatches a `qa-validate`
24
+ work item and seeds the run with `pending`. Read via
25
25
  `GET /api/qa/runs?limit=50` — UI polls every 5s while the QA page is active
26
26
  and clears the interval on page navigation via the `switchPage` wrapper in
27
27
  `dashboard/js/qa.js` (matches `_stopPlanPoll`/`_stopMeetingPoll` pattern in
@@ -49,46 +49,53 @@ only), confirms with the user, and surfaces success/error via
49
49
 
50
50
  ## Artifact contract
51
51
 
52
- `engine/qa-artifacts/<runId>/<file>`, served via
53
- `GET /api/qa/artifacts/<runId>/<file>`. Files are agent-uploaded screenshots,
54
- video recordings, and log captures listed in the run record's
55
- `artifacts: [{ file, kind }]`. UI auto-detects:
52
+ Artifacts live at `<MINIONS_DIR>/engine/qa-artifacts/<runId>/<path>`, served via
53
+ `GET /api/qa/artifacts/<runId>/<path>`. `createRun()` pre-creates the per-run
54
+ directory, and the rendered QA prompt injects its absolute path as
55
+ `qa_artifacts_dir` so project-worktree agents write to the engine-owned
56
+ directory rather than their disposable worktree. The agent lists metadata in
57
+ the result sidecar using paths relative to the run's artifact directory as
58
+ `artifacts: [{ type, path, label, capturedAt }]`. UI auto-detects:
56
59
  `screenshot`/`png|jpg|jpeg|gif|webp|svg` → `<img>`;
57
60
  `video`/`mp4|webm|ogg|mov` → `<video controls>`; everything else → log
58
61
  preview (first 40 lines fetched lazily) with a `View full` link to the same
59
62
  endpoint. **No direct filesystem paths are exposed** — every artifact URL
60
63
  goes through `/api/qa/artifacts/` so path traversal is server-gated.
61
- Optional config: `engine.qaArtifactsMaxBytes` caps per-file upload size;
62
- when set, dashboard Settings exposes a matching toggle (CLAUDE.md
63
- best-practice #15).
64
+ There is no configurable per-file size cap or completion-time copy step.
64
65
 
65
66
  ## Agent sidecar shape
66
67
 
67
- The `qa-validate` agent writes `agents/<id>/qa-run.json` before exit:
68
+ The retired draft path `agents/<id>/qa-run.json` is not consumed.
69
+ The `qa-validate` agent writes the injected absolute `qa_result_sidecar` path,
70
+ which resolves to `<MINIONS_DIR>/agents/<id>/qa-run-result.json`, before exit:
68
71
 
69
72
  ```json
70
- { "runId": "qa-run-<id>",
71
- "status": "passed|failed|error",
73
+ { "runId": "qarun-<id>",
74
+ "status": "passed|failed|errored",
72
75
  "summary": "...",
73
- "artifacts": [ { "file": "dashboard.png", "kind": "screenshot" },
74
- { "file": "test.log", "kind": "log" } ],
75
- "written_by": "<agentId>", "wi_id": "<workItemId>" }
76
+ "artifacts": [
77
+ { "type": "screenshot", "path": "engine/qa-artifacts/<runId>/dashboard.png",
78
+ "label": "Dashboard", "capturedAt": "2026-05-20T20:42:00.000Z" },
79
+ { "type": "log", "path": "engine/qa-artifacts/<runId>/test.log",
80
+ "label": "Test log", "capturedAt": "2026-05-20T20:42:00.000Z" }
81
+ ] }
76
82
  ```
77
83
 
78
- The engine reads the sidecar in `onAgentClose`, copies the listed files into
79
- `engine/qa-artifacts/<runId>/`, and stamps the run record with `status`,
80
- `completedAt`, and the recorded `artifacts`. Sidecar validation failure is
81
- non-retryable (`failure_class: invalid-qa-run`); listed files outside the
82
- agent worktree or larger than `qaArtifactsMaxBytes` are rejected without
83
- stamping the run.
84
+ Before spawning a run, the engine removes any prior sidecar for that agent.
85
+ `runPostCompletionHooks` then verifies `runId` matches the current run, consumes
86
+ the file exactly once, and stamps the run record with `status`, `completedAt`,
87
+ `summary`, and normalized artifact metadata. Missing, malformed, stale-run, or
88
+ unknown-status sidecars mark the run `errored`. Lifecycle does not copy
89
+ or size-check artifact files; agents must write them to the injected absolute
90
+ artifact directory before exit.
84
91
 
85
92
  ## Entry point
86
93
 
87
94
  `playbooks/qa-validate.md`. Dispatched by `POST /api/qa/runbooks/run`;
88
- receives `target`, `steps`, `expectedArtifacts` as template vars; required
89
- to write the sidecar above before exit. Routing line in `routing.md` maps
90
- the synthetic `qa-validate` task-type to the playbook so manual dispatches
91
- work too.
95
+ receives `target`, `steps`, `expectedArtifacts`, `qa_artifacts_dir`, and
96
+ `qa_result_sidecar` as template vars; required to write the sidecar above
97
+ before exit. Routing line in `routing.md` maps the synthetic `qa-validate`
98
+ task-type to the playbook so manual dispatches work too.
92
99
 
93
100
  # QA Sessions (PL-qa-sessions)
94
101
 
@@ -182,16 +189,17 @@ routing default):
182
189
  metadata via `/api/managed-processes/by-name/qa-session-<id>`, calls the
183
190
  resolved runner's `generateBrief({target, flowsRaw, capture})` hook, and
184
191
  writes the runner-native test file to
185
- `engine/qa-tests/<sessionId>/test.<ext>`. The runner's
186
- `validateOutputDir` hook gates DRAFT success a missing or malformed
187
- test file lands the session in `failed`. In `confirm` mode the session
188
- parks at `awaiting-approval`; in `auto` mode it auto-chains to EXECUTE.
192
+ the injected absolute `engine/qa-tests/<sessionId>/test.<ext>` path.
193
+ The completion report's `testFile` value is stored on the session. In
194
+ `confirm` mode the session parks at `awaiting-approval`; in `auto` mode
195
+ it auto-chains to EXECUTE.
189
196
 
190
197
  3. **EXECUTE** (`playbooks/qa-session-execute.md`) runs the drafted test
191
198
  against the live spawn via the runner's `executeBrief` hook, captures
192
199
  artifacts per `spec.capture: { video, screenshots, logs }`, and writes
193
- `agents/<agentId>/qa-run-result.json` — the same sidecar the
194
- `qa-validate` runbook flow uses. The existing
200
+ the injected absolute `qa_result_sidecar` path — the same single-use sidecar
201
+ the `qa-validate` runbook flow uses. Artifacts go to the injected absolute
202
+ `qa_artifacts_dir` keyed by the linked QA run id, not the session id. The
195
203
  `engine/lifecycle.js#runPostCompletionHooks` `meta.qaRunId` hook ingests
196
204
  the sidecar and marks the linked `qa-runs` record terminal; the
197
205
  session-level `handleExecuteComplete` then reads the `qa-run` terminal
@@ -212,20 +220,27 @@ Documented in `dashboard.js`; routes are visible at `GET /api/routes`.
212
220
  | POST | `/api/qa/from-prd` | "qa this PRD" bridge. Build a QA Session spec from a completed/approved PRD via `engine/qa-from-prd.js` (pure) then reuse `createSession` + `queueSetup`. Body: `{ prd, mode?, runner? }`. Non-ready / unknown / malformed PRD → **400** (`QaFromPrdError.reason`). Returns `sessionId`, `state`, `setupWorkItemId`, `managedSpawnName`, `projects`, `flowSource`, `warnings[]`, `note` (repo-native-harness copy), and optional `runnerHint`. The test is drafted on the repo's native test harness. |
213
221
  | GET | `/api/qa/sessions` | List sessions newest-first. Optional `?limit=N` and `?state=pending\|spawning\|drafting\|awaiting-approval\|executing\|done\|failed\|killed`. |
214
222
  | GET | `/api/qa/sessions/<id>` | Fetch a single session record by id. |
215
- | POST | `/api/qa/sessions/<id>/approve` | `awaiting-approval → executing`. Server-side creates the linked `qa-runs` record (synthetic `runbookId='qa-session-<id>'`), queues EXECUTE WI, stamps `qaRunId` on the session. |
223
+ | POST | `/api/qa/sessions/<id>/approve` | `awaiting-approval → executing`. Accepts no request body. Server-side creates the linked `qa-runs` record (synthetic `runbookId='qa-session-<id>'`), queues EXECUTE WI, stamps `qaRunId` on the session. |
216
224
  | POST | `/api/qa/sessions/<id>/edit` | `awaiting-approval → drafting`. Body: `{ feedback }`. Re-fires DRAFT with the reviewer feedback threaded into the prompt. |
217
225
  | POST | `/api/qa/sessions/<id>/cancel` | Non-terminal → `killed`. Optional `{ reason }`. Does NOT touch the managed-spawn — use `/kill` for that. |
218
- | POST | `/api/qa/sessions/<id>/kill` | Non-terminal → `killed` AND `removeManagedSpec(<name>)`. Best-effort on the spawn kill (no-op if no spawn). |
226
+ | POST | `/api/qa/sessions/<id>/kill` | Non-terminal → `killed`, then generation-safely removes the primary and every multi-project co-service managed-spawn. Best-effort when a spawn is absent or concurrently replaced. |
219
227
  | POST | `/api/qa/sessions/<id>/dismiss` | Non-terminal → `done`. Accept the draft as final; leaves spawn alive. Optional `{ summary }`. |
220
228
  | GET | `/api/qa/runners` | List registered runner adapters (built-ins + `qa-runners.d/` plugins). Metadata only — hooks (functions) are stripped. |
221
- | POST | `/api/qa/runners/reload` | Clear in-process registry, re-register built-ins, re-scan `qa-runners.d/` for plugin edits. Returns the fresh runner list. |
229
+ | POST | `/api/qa/runners/reload` | Accepts no request body. Clears the in-process registry, re-registers built-ins, re-scans `qa-runners.d/` for plugin edits, and returns the fresh runner list. |
222
230
  | DELETE | `/api/qa/runs/<id>` | Hard-delete a single QA run record (terminal-status runs only) and best-effort wipe its artifact directory under `engine/qa-artifacts/<runId>/`. Returns `{ ok: true, id, artifactsRemoved }` on success, `409 not_terminal` for in-flight runs, `404 not_found` for unknown ids. |
223
231
 
232
+ The audited contracts returned by `GET /api/routes` expose the canonical enum,
233
+ length, list, nested-field, path, and lifecycle constraints for these routes.
234
+ Validation failures use field/path-aware details and occur before QA records,
235
+ work items, runbooks, plugins, artifacts, or managed processes are mutated.
236
+ Traversal remains `403`, missing resources `404`, and wrong-state conflicts
237
+ `409`.
238
+
224
239
  The single-session POSTs share `_qaSessionAction` in `dashboard.js`; module
225
240
  errors are mapped to HTTP via `_qaSessionsErrorToStatus`:
226
241
 
227
242
  - `'session not found'` → 404
228
- - `'unsafe sessionId'` / `'invalid spec'` / `'requires …'` (size cap) → 400
243
+ - `'unsafe sessionId'` / `'invalid spec'` / `'requires …'` / `'exceeds …'` (spec or feedback validation) → 400
229
244
  - `'illegal state transition'` / `'requires state …'` / `'requires non-terminal'` → 409
230
245
 
231
246
  ## File locations
@@ -243,14 +258,14 @@ errors are mapped to HTTP via `_qaSessionsErrorToStatus`:
243
258
  session's `cancelSession` if the operator opts for the cancel-without-
244
259
  spawn-kill path.
245
260
  - **EXECUTE artifacts**: same as runbooks — `engine/qa-artifacts/<runId>/`
246
- served via `GET /api/qa/artifacts/<runId>/<file>`.
261
+ served via `GET /api/qa/artifacts/<runId>/<path>`.
247
262
 
248
263
  ## Runner adapters (P-c4a9e7f3 / P-b8e1d4a6)
249
264
 
250
265
  Pluggable test-runner registry at `engine/qa-runners.js`. Built-in
251
- adapters: `playwright` (priority 100, detects `playwright.config.*`),
252
- `maestro` (priority 80, detects `.maestro/`). Each adapter exports five
253
- hooks:
266
+ adapters: `playwright` (priority 50, always-true safe default),
267
+ `maestro` (priority 80, detects `.maestro/` and therefore wins when
268
+ present). Each adapter exports five hooks:
254
269
 
255
270
  - `detect(target, project)` → `boolean`. Auto-detect for blank-runner
256
271
  sessions.
@@ -258,8 +273,9 @@ hooks:
258
273
  string. Handed to the DRAFT agent so it emits a runner-native test file.
259
274
  - `executeBrief({sessionId, target, capture, project})` → instructions /
260
275
  command for the EXECUTE agent.
261
- - `validateOutputDir(dir)` → `{ ok, errors[] }`. Gates the DRAFT → EXECUTE
262
- transition.
276
+ - `validateOutputDir(dir)` → `{ ok, errors[] }`. Available to explicit
277
+ callers and tests; the production DRAFT transition does not invoke it
278
+ automatically today.
263
279
  - `installHint` → string shown when `detect()` returns true but the
264
280
  runner CLI is missing.
265
281
 
@@ -399,10 +415,10 @@ maps to `POST /api/qa/from-prd` (see `prompts/cc-system.md`).
399
415
  `'managed-spawn-healthcheck-failed'`. The `error` field carries the
400
416
  reason; check `engine/managed-logs/qa-session-<sessionId>.log` for the
401
417
  failing healthcheck.
402
- - **DRAFT validateOutputDir rejects** → `failure_class:
403
- 'qa-session-draft-failed'`. Inspect the drafted file at
404
- `engine/qa-tests/<sessionId>/`; the runner's `validateOutputDir.errors`
405
- array tells you what's missing.
418
+ - **DRAFT agent reports failure** → inspect the DRAFT completion summary and
419
+ the injected
420
+ `engine/qa-tests/<sessionId>/` directory. The session failure class is
421
+ `qa-session-draft-failed` when the agent reports that contract failure.
406
422
  - **EXECUTE qa-run terminal status is `failed`/`errored`** →
407
423
  `failure_class: 'qa-session-execute-failed'` /
408
424
  `'qa-session-execute-errored'`. The linked `qa-runs` record (joined via
@@ -99,7 +99,7 @@ unlink).
99
99
 
100
100
  The deferred follow-up items (W-mpeiwz6k0005bf34-b/c/d) have since landed. Brief pointers — see [CLAUDE.md](../CLAUDE.md) → "QA validation runs" for the deep dive:
101
101
 
102
- - **Run dispatch + persistence** (`engine/qa-runs.js`): `POST /api/qa/runbooks/run` creates a SQL QA-run record with `status ∈ pending|dispatched|running|passed|failed|error` and dispatches a `qa-validate` work item against the runbook's `target`. Read via `GET /api/qa/runs?limit=N&status=...` and `GET /api/qa/runs/<id>`.
103
- - **Artifact contract**: the `qa-validate` agent writes `agents/<id>/qa-run.json` before exit; the engine copies listed files into `engine/qa-artifacts/<runId>/` and serves them via `GET /api/qa/artifacts/<runId>/<file>` (path-traversal-gated, 403 on escape). Per-file size cap: `engine.qaArtifactsMaxBytes`.
102
+ - **Run dispatch + persistence** (`engine/qa-runs.js`): `POST /api/qa/runbooks/run` creates a SQL QA-run record with `status ∈ pending|running|passed|failed|errored` and dispatches a `qa-validate` work item against the runbook's `targetName`. Read via `GET /api/qa/runs?limit=N&status=...` and `GET /api/qa/runs/<id>`.
103
+ - **Artifact contract**: the engine pre-creates `engine/qa-artifacts/<runId>/`; the `qa-validate` agent writes files there and records run-root-relative paths in the injected absolute `agents/<id>/qa-run-result.json` path. The engine clears that sidecar before dispatch, verifies its `runId`, and removes it after consumption. Lifecycle persists the metadata, and `GET /api/qa/artifacts/<runId>/<path>` serves root-level or nested files behind traversal guards (403 on escape). There is no `engine.qaArtifactsMaxBytes` setting or copy-on-completion gate.
104
104
  - **UI**: `/qa` dashboard page (`dashboard/pages/qa.html`, `dashboard/js/qa.js`) polls `GET /api/qa/runs` every 5s while active; auto-detects screenshots/videos/logs for inline preview.
105
105
  - **Playbook**: `playbooks/qa-validate.md` (routed via the synthetic `qa-validate` task-type in `routing.md`).
@@ -1,7 +1,7 @@
1
1
  # Runtime Adapters
2
2
 
3
- How Minions abstracts over CLI runtimes (Claude Code, GitHub Copilot CLI, future
4
- adapters). Engine code never special-cases a runtime by name — every CLI-specific
3
+ How Minions abstracts over CLI runtimes (Claude Code, GitHub Copilot CLI, Codex,
4
+ and future adapters). Engine code never special-cases a runtime by name — every CLI-specific
5
5
  behavior is hidden behind an adapter object resolved through `resolveRuntime()`.
6
6
 
7
7
  ## What lives in `engine/runtimes/`
@@ -33,18 +33,18 @@ methods that genuinely differ.
33
33
  |--------|----------------|---------|
34
34
  | `apiVersion` | number | Contract version. Bundled adapters use `1`; incompatible versions fail at registration. |
35
35
  | `name` | string | Adapter identifier (matches the registry key). |
36
- | `capabilities` | flag object (see below) | Feature flags consumed by engine code. |
36
+ | `capabilities` | flag object (see below) | Behavioral and declarative flags (see below). |
37
37
  | `resolveBinary({ env, config? })` | `{ bin, native, leadingArgs }` or null | Probe PATH, npm-globals, package overrides; cached to `engine/<name>-caps.json`. `leadingArgs` is `[]` for direct binaries and `['copilot']` for the `gh copilot` subcommand fallback. |
38
38
  | `capsFile` | absolute path | Cache file for `resolveBinary()` results. |
39
39
  | `installHint` | string | Surfaced when `resolveBinary()` returns null. Consumed by `engine/preflight.js` and `engine/spawn-agent.js`. |
40
40
  | `listModels()` | `Promise<{id,name,provider}[] | null>` | Model catalog or null when discovery isn't supported. |
41
41
  | `modelsCache` | absolute path | Cache file for `listModels()` results. |
42
- | `spawnScript` | absolute path | Path to the runtime-agnostic spawn wrapper (currently `engine/spawn-agent.js` for both runtimes). |
42
+ | `spawnScript` | absolute path | Path to the runtime-agnostic spawn wrapper (currently `engine/spawn-agent.js` for all bundled runtimes). |
43
43
  | `buildArgs(opts)` | `string[]` | CLI args excluding the binary itself. |
44
44
  | `resolveInvocationOptions({ options, engineConfig, config, failureClass })` | object | Optional; maps harness-specific config and retry policy into semantic invocation options. Core code never reads harness-specific invocation keys. |
45
45
  | `buildSpawnFlags(opts)` | `string[]` | Optional compatibility flags for older spawn wrappers. New fields travel in the opaque `--runtime-options` bag automatically. |
46
- | `buildPrompt(promptText, sysPromptText)` | string | Final prompt delivered to the CLI. Adapters that lack `--system-prompt-file` (Copilot) prepend a `<system>` block here. |
47
- | `getUserAssetDirs({ homeDir })` | `string[]` | Runtime-native global asset roots passed to spawn as `--add-dir` so worktrees still see them. |
46
+ | `buildPrompt(promptText, sysPromptText, opts)` | string | Final prompt delivered to the CLI. Adapters that lack `--system-prompt-file` prepend a `<system>` block on fresh calls and omit that replay when `opts.sessionId` resumes an existing session. |
47
+ | `getUserAssetDirs({ homeDir })` | `string[]` | Runtime-native global asset roots exposed by read-only harness diagnostics. |
48
48
  | `getSkillRoots({ homeDir, project? })` | `[{ scope, dir, projectName? }]` | Where `collectSkillFiles` looks for native + project skill markdown. |
49
49
  | `getSkillWriteTargets({ homeDir, project? })` | `{ personal, project }` | Where `extractSkillsFromOutput` writes auto-extracted skills. |
50
50
  | `getResumeSessionId(...)` | string or null | Pre-spawn resume probe; runtime-agnostic stamp prevents cross-runtime session-ID confusion. |
@@ -58,8 +58,9 @@ methods that genuinely differ.
58
58
  | `prepareWorkspace(ctx)` | result object | Optional synchronous hook for harness-owned workspace state, such as trust configuration. |
59
59
  | `isSpawnStartupEvent(event)` | boolean | Optional startup-event classifier used by the spawn-phase watchdog. |
60
60
  | `shouldSuppressPostMutationError(ctx)` | boolean | Optional harness quirk policy for an error received after a confirmed mutation. |
61
+ | `hasUnfinishedToolCall(raw)` | boolean | Optional truncation detector: `true` when the raw output holds a tool call that was started and never completed. Lets the engine diagnose an exit-0 mid-turn death as `runtime-truncated-exit` instead of the generic `completion-report-missing`. Defaults to `false` when not implemented. |
61
62
  | `buildWorkerArgs(opts)` | string[] | Required with `acpWorkerPool`; owns persistent-worker CLI flags. |
62
- | `encodePooledOutput(event)` | string | Required with `acpWorkerPool`; translates semantic pooled events into the adapter's normal parser format. |
63
+ | `encodePooledOutput(event)` | string | Required with `acpWorkerPool`; translates semantic `model` / `chunk` / `result` events into the adapter's normal parser format. |
63
64
  | `detectPermissionGate`, `getPromptDeliveryMode`, `usesSystemPromptFile`, `classifyFailure` | misc | Adapter-owned policy that orchestration reads through the facade instead of branching on `runtime.name`. |
64
65
 
65
66
  `engine/runtimes/contract.js` is authoritative. Registration normalizes legacy
@@ -89,10 +90,24 @@ Retirement gates for the temporary compatibility surfaces are tracked in
89
90
  `preapprove-workspace-mcps-wrapper`. Calendar dates never override their
90
91
  external-usage gates.
91
92
 
93
+ ### Execution model attribution
94
+
95
+ Dispatch records keep the normalized model sent to the runtime as
96
+ `requestedModel`. Streaming runtimes then replace that provisional evidence with
97
+ their reported model in `executionModel`; pooled ACP dispatches obtain the same
98
+ authoritative value from `session/new.models.currentModelId` and re-emit it in
99
+ the Copilot JSONL shape. Review attribution resolves evidence in this order:
100
+ completion output, captured session output, requested model, then `unknown`.
101
+ `minions pr comment` uses the active dispatch to stamp the shared GitHub/ADO
102
+ marker and visible sign-off, and lifecycle persists the same value as
103
+ `minionsReview.model`. Model catalogs are never used to guess an implicit
104
+ runtime default.
105
+
92
106
  ## Capability Flags
93
107
 
94
- Engine code branches on flags, never on runtime names. Add a flag whenever a
95
- real behavioral split appears between adapters.
108
+ Engine code branches on consumed flags, never on runtime names. A capability
109
+ may also begin as declarative metadata; add a consumer when orchestration needs
110
+ to act on that behavioral split.
96
111
 
97
112
  | Flag | Claude | Copilot | Codex | Gates |
98
113
  |------|--------|---------|-------|-------|
@@ -104,15 +119,18 @@ real behavioral split appears between adapters.
104
119
  | `costTracking` | ✓ | ✗ | ✗ | USD + token counts in the result event. Copilot only emits `premiumRequests`; Codex accounting is not assumed. |
105
120
  | `modelShorthands` | ✓ | ✗ | ✗ | Bare `sonnet`/`opus`/`haiku` accepted by Claude only. |
106
121
  | `modelDiscovery` | ✗ | ✓ | ✓ | `listModels()` returns a real catalog when supported (Copilot API, Codex `debug models --bundled`). |
122
+ | `strictModelCatalog` | — | — | ✗ | An explicit false lets the runtime validate IDs missing from its cached catalog; Codex uses this because bundled catalogs can lag staged/custom models. |
107
123
  | `promptViaArg` | ✗ | ✗ | ✗ | If true, prompt goes via `--prompt <text>` instead of stdin. False for all current runtimes. |
108
124
  | `budgetCap` | ✓ | ✗ | ✗ | `--max-budget-usd <n>`. |
109
125
  | `bareMode` | ✓ | ✗ | ✗ | `--bare` (suppresses CLAUDE.md auto-discovery). Other runtimes use their own config/suppression knobs. |
126
+ | `claudeMdNativeDiscovery` | ✓ | ✗ | ✗ | Skip engine-side CLAUDE.md propagation when the runtime already loads it natively. |
110
127
  | `fallbackModel` | ✓ | ✗ | ✗ | `--fallback-model <id>` on rate-limit. |
111
128
  | `sessionPersistenceControl` | ✓ | ✗ | ✗ | Engine writes `session.json`; Copilot and Codex own their runtime stores, while Minions records IDs only for safe resume. |
112
129
  | `resumePromptCarryover` | ✗ | ✓ | ✓ | CC resume turns prepend recent visible Q&A in stdin when the runtime session store is opaque to Minions. |
113
130
  | `resumeBookkeepingTurn` | ✓ | — | — | Claude CLI injects a synthetic "Continue from where you left off." meta turn on `--resume`; CC prompts must tell the model not to treat it as user intent. |
114
131
  | `streamConsumer` | ✓ | ✓ | ✓ | Adapter implements `createStreamConsumer(ctx)` — required by `engine/llm.js` accumulator. |
115
132
  | `imageInput` | ✓ | ✓ | ✓ | Runtime accepts `images: [{mimeType, dataBase64}]` and delivers them to the CLI using adapter-owned transport. When false, `_resolveImageOpts` returns a typed `model-unavailable` error. |
133
+ | `skillDiscovery` | ✓ | — | — | Currently set only by Claude to declare native `.claude/skills` discovery. It is metadata-only today; core orchestration has no consumer yet. |
116
134
  | `acpWorkerPool` | ✗ | ✓ | ✗ | Runtime supports `<bin> --acp` (Agent Client Protocol), so it can be routed through a persistent worker pool instead of cold-spawning a fresh CLI process per turn/dispatch. Two independent consumers gate on this flag: `engine/cc-worker-pool.js` (CC/doc-chat tabs, `resolveCcUseWorkerPool`) and `engine/agent-worker-pool.js` (fleet agent dispatch, `resolveAgentUseWorkerPool`). Both resolvers hard-return `false` whenever this flag isn't `true` on the resolved runtime, regardless of any operator override, so a future non-ACP runtime can never be silently pooled. See [ACP Worker Pool](#acp-worker-pool-fleet-agent-dispatch) below. |
117
135
 
118
136
  When a behavior is genuinely uniform across all current adapters, it still gets
@@ -120,10 +138,10 @@ a flag if a future runtime might disagree — flags are cheap.
120
138
 
121
139
  ## ACP Worker Pool (Fleet Agent Dispatch)
122
140
 
123
- Copilot's `acpWorkerPool` capability backs **two** independent opt-in pools that
124
- avoid cold-spawning a fresh `copilot` CLI process for every turn/dispatch. Both
125
- share the same spawn + JSON-RPC transport (`engine/acp-transport.js`) but have
126
- different ownership models:
141
+ The `acpWorkerPool` capability backs **two** independent pools that avoid a
142
+ cold harness spawn for every turn/dispatch. Both share the runtime-neutral
143
+ JSON-RPC transport (`engine/acp-transport.js`); the adapter owns worker argv and
144
+ pooled-output encoding.
127
145
 
128
146
  | Pool | Module | Ownership model | Config | Default |
129
147
  |------|--------|------------------|--------|---------|
@@ -136,14 +154,16 @@ This section covers the fleet agent pool (P-9b3d5f61 / P-1d8f0b93 / P-4c6e8a72).
136
154
 
137
155
  | Key | Resolver | Chain | Default |
138
156
  |-----|----------|-------|---------|
139
- | `engine.agentUseWorkerPool` | `shared.resolveAgentUseWorkerPool(engine, runtime)` | 1. Hard `false` unless `runtime.capabilities.acpWorkerPool === true` (Copilot only). 2. `engine.agentUseWorkerPool` explicit `true`/`false` override. 3. `ENGINE_DEFAULTS.agentUseWorkerPool`. | `false` |
157
+ | `engine.agentUseWorkerPool` | `shared.resolveAgentUseWorkerPool(engine, runtime)` | 1. Hard `false` unless `runtime.capabilities.acpWorkerPool === true`. 2. `engine.agentUseWorkerPool` explicit `true`/`false` override. 3. `ENGINE_DEFAULTS.agentUseWorkerPool`. | `false` |
140
158
  | `engine.agentAcpPoolSize` | `shared.resolveAgentAcpPoolSize(engine)` | 1. `engine.agentAcpPoolSize` (positive number, else ignored). 2. `engine.maxConcurrent` (fleet dispatch concurrency cap). 3. `ENGINE_DEFAULTS.maxConcurrent`. | `ENGINE_DEFAULTS.maxConcurrent` (currently `5`) |
141
159
 
142
- `agentUseWorkerPool` is deliberately **opt-in** (`false` by default), unlike
143
- `ccUseWorkerPool`'s default-`true` the fleet dispatch path has a much larger
144
- MCP-auth-popup / isolation surface than the single CC singleton, so it stays
145
- off until proven safe in a given operator's environment. `agentAcpPoolSize`
146
- defaults to the same cap the engine would already use for cold-spawn
160
+ `agentUseWorkerPool` defaults to `false`; `ccUseWorkerPool` remains enabled by
161
+ default for Copilot. Fleet pooling must be explicitly opted into while its
162
+ newer dispatch-lifecycle path stabilizes.
163
+ Work items requesting `meta.keep_processes` always use the cold wrapper, even
164
+ when fleet pooling is enabled, so the established descendant-ownership contract
165
+ remains authoritative.
166
+ `agentAcpPoolSize` defaults to the same cap the engine would already use for cold-spawn
147
167
  concurrency (`maxConcurrent`), so enabling the pool never queues more
148
168
  concurrent dispatches than the fleet would have run anyway — it only changes
149
169
  *how* those dispatches get a CLI process, not how many run at once.
@@ -159,31 +179,65 @@ handlers, PID-file writing, live-output.log, timeout/steering cancel-vs-kill,
159
179
  restart-reattach) runs unmodified regardless of whether `proc` is a real OS
160
180
  process or a pooled lease. Assignment strategy, in order:
161
181
 
162
- 1. A **free** worker whose MCP-server set, process environment, and canonical
163
- process cwd already match wins first reused via a fresh `session/new`, no
164
- respawn.
182
+ 1. A **free** worker whose MCP-server set and process launch context already
183
+ match wins first. A persisted session prefers the worker that already owns
184
+ it; otherwise ACP `session/load` restores it. A dispatch without resume state
185
+ receives a fresh `session/new`.
165
186
  2. Otherwise another free worker is **evicted** (closed) and a replacement is
166
- spawned fresh with the requested MCP servers/cwd (MCP servers are resolved
167
- at `copilot --acp` process boot, so a changed server set or process cwd
168
- requires a full respawn, not just a new session). This prevents a worker OS
169
- process rooted in a live operator checkout from carrying that cwd into an
170
- isolated worktree dispatch.
187
+ spawned fresh with the requested MCP servers/cwd/process policy. MCP servers
188
+ and flags such as `--disable-builtin-mcps`,
189
+ `--max-autopilot-continues`, and reasoning summaries are process-scoped, so
190
+ changing them requires a full respawn, not just a new session.
171
191
  3. Otherwise, if the pool has room (fewer than `agentAcpPoolSize` workers
172
192
  allocated across free + busy + in-flight-spawn), a brand-new worker spawns.
173
193
  4. Otherwise the request queues FIFO until a `release()` or worker crash frees
174
194
  a slot.
175
195
 
196
+ Acquisition is cancellable and the initialize/session handshake is bounded to
197
+ 60 seconds. Cancellation quarantines the lease until descendant cleanup
198
+ finishes, then discards that worker so no late response can contaminate another
199
+ dispatch. The engine applies settings every tick: lowering `agentAcpPoolSize`
200
+ immediately closes excess idle workers, and turning `agentUseWorkerPool` off
201
+ closes idle workers while active leases drain.
202
+
203
+ The facade also mirrors cold-spawn descendant cleanup. It preserves descendants
204
+ that existed before the lease (the worker's MCP subtree), tracks process trees
205
+ created during the lease, validates PID birth identity before killing, honors
206
+ validated `keep_processes` anchors, and reaps the rest before reuse. A worker
207
+ that produced kept descendants is discarded with a root-only kill so those
208
+ anchors survive. Tool-call-triggered snapshots plus a final worktree-CWD holder
209
+ scan catch fast-reparented subprocesses; if cleanup cannot be proven safe, the
210
+ dispatch fails closed.
211
+ The long-lived ACP process itself starts in project-scoped engine scratch while
212
+ each ACP session receives its dispatch worktree as `cwd`; a warm worker therefore
213
+ cannot pin a disposable Windows worktree or mutate the operator checkout through
214
+ its process cwd.
215
+
176
216
  Dispatch records persist `item.pooled = true` in SQLite, not
177
217
  just the in-memory `activeProcesses._pooled` flag (which is wiped on engine
178
218
  restart) — the on-disk flag is what tells the restart-reattach path
179
219
  (`engine/cli.js`) that a live worker PID is a pooled lease, not a resumable
180
220
  cold-spawned child process.
221
+ If checkpoint steering hands a pooled dispatch to a detached cold resume,
222
+ the engine clears `pooled` first and replaces the canonical dispatch PID file,
223
+ restoring normal restart reattachment for the resumed process.
224
+
225
+ On an intentional stop/restart, the fleet pool enters drain mode: new
226
+ acquisitions are rejected, idle workers close immediately, and active leases
227
+ may finish until `engine.shutdownTimeout`. Detached cold-spawn agents are not
228
+ waited on and remain eligible for normal restart reattachment. If the drain
229
+ times out, the engine cancels each pooled facade, awaits its descendant/anchor
230
+ cleanup, and completes its dispatch as a retryable timeout before exiting.
231
+ After an unexpected crash, the persisted
232
+ `pooled` flag still bypasses PID trust and routes the dispatch through orphan
233
+ retry because a replacement engine cannot adopt the old ACP worker's stdio.
181
234
 
182
235
  ### Isolation tradeoff — read this before enabling `agentUseWorkerPool`
183
236
 
184
- **Enabling the pool bounds the MCP-auth-popup / cross-dispatch isolation risk
185
- by pool size — it does not eliminate it.** Cold-spawn gives every dispatch its
186
- own fresh OS process and its own `COPILOT_HOME`; the pool instead keeps up to
237
+ **Enabling the pool bounds cross-dispatch process-state risk by pool size — it
238
+ does not eliminate it.** Lease-created descendants and session state are reset,
239
+ but cold-spawn still gives every dispatch a stronger fresh OS
240
+ process; the pool instead keeps up to
187
241
  `agentAcpPoolSize` `copilot --acp` processes warm and reuses them across
188
242
  dispatches whenever the MCP-server set matches. Concretely:
189
243
 
@@ -199,14 +253,11 @@ dispatches whenever the MCP-server set matches. Concretely:
199
253
  size narrows it at the cost of more evictions/respawns (and therefore more
200
254
  MCP-auth prompts) when the requested MCP-server set changes between
201
255
  dispatches.
202
- - This mirrors the same tradeoff `engine.ccUseWorkerPool` already accepts for
203
- CC tabs (see [docs/command-center.md](./command-center.md)), which is why
204
- `agentUseWorkerPool` defaults `false`: the fleet dispatch surface has more,
205
- and more varied, work items than a single CC session, so the exposure window
206
- per warm worker is larger and the default stays conservative until an
207
- operator has evaluated the tradeoff for their own MCP-server set.
208
-
209
- Operators enabling `engine.agentUseWorkerPool` should choose `agentAcpPoolSize`
256
+ - This mirrors the same tradeoff `engine.ccUseWorkerPool` accepts for CC tabs
257
+ (see [docs/command-center.md](./command-center.md)). Operators can explicitly
258
+ disable either pool when strict per-dispatch process isolation is required.
259
+
260
+ Operators using `engine.agentUseWorkerPool` should choose `agentAcpPoolSize`
210
261
  deliberately: a pool size of `1` gives the strongest isolation (still weaker
211
262
  than cold-spawn, since consecutive dispatches on that single worker still
212
263
  share process state) at the cost of maximum serialization/respawns; a larger
@@ -314,24 +365,24 @@ if (mappedEffort) args.push('--effort', mappedEffort);
314
365
  When `resolveAgentModel` returns undefined the adapter omits `--model` from
315
366
  `buildArgs` entirely and the underlying CLI uses its own default.
316
367
 
317
- ## Per-Runtime Skill Roots
368
+ ## Per-Runtime Native Skill Roots
318
369
 
319
- Skills live in runtime-native directories so a skill placed there is genuinely
320
- visible to that runtime when invoked outside Minions. The adapter's
321
- `getSkillRoots()` and `getSkillWriteTargets()` are the discovery and write
322
- contracts.
370
+ Skills must live in directories the selected CLI natively discovers. The table
371
+ below lists the reliable native roots; adapter diagnostics may inventory
372
+ additional directories, but inventory does not make those directories visible
373
+ to the CLI.
323
374
 
324
375
  | Adapter | Personal skill root | Project skill roots | Auto-extraction write targets |
325
376
  |---------|---------------------|---------------------|-------------------------------|
326
- | Claude | `~/.claude/skills` | `<repo>/.claude/skills` | personal: `~/.claude/skills`; project: `<repo>/.claude/skills` |
327
- | Copilot | `~/.copilot/skills` (+ `~/.agents/skills`) | `<repo>/.github/skills`, `<repo>/.agents/skills` | personal: `~/.copilot/skills`; project: `<repo>/.github/skills` |
377
+ | Claude | `~/.claude/skills` | `<repo>/.claude/skills` | personal: `~/.claude/skills`; project: `<repo>/.claude/skills` |
378
+ | Copilot | `~/.copilot/skills`, `~/.agents/skills` | `<repo>/.github/skills`, `<repo>/.claude/skills`, `<repo>/.agents/skills` | personal: `~/.copilot/skills`; project: `<repo>/.github/skills` |
328
379
  | Codex | `~/.agents/skills` (+ `/etc/codex/skills`) | `<repo>/.agents/skills` | personal: `~/.agents/skills`; project: `<repo>/.agents/skills` |
329
380
 
330
- `getUserAssetDirs()` returns the union of runtime-native global roots and is
331
- passed to spawn as `--add-dir` so spawned agents read the same global assets the
332
- user sees outside Minions. The cross-runtime portable location
333
- `~/.agents/skills` is included by every adapter that opts into it (Copilot today;
334
- add it to new adapters when the runtime can read directly from there).
381
+ `getSkillRoots()` feeds the dashboard and `minions doctor --harness` inventory;
382
+ `getSkillWriteTargets()` controls skill auto-extraction. Normal dispatch does not
383
+ turn inventory rows into an engine-computed `--add-dir` list: the selected CLI
384
+ performs native discovery from its cwd and user configuration. In particular,
385
+ Claude does not natively discover `.agents/skills`.
335
386
 
336
387
  For a full per-runtime survey of which dirs the engine actually surfaces today
337
388
  — grouped by scope label and with on-disk-missing flags — run
package/docs/security.md CHANGED
@@ -139,6 +139,34 @@ previous tick — cannot be silently consumed. Anything in the report body
139
139
  itself remains agent-controlled and is treated as such (no `eval`, no shell
140
140
  interpolation, schema-validated fields only).
141
141
 
142
+ ### Review-learning trust boundaries
143
+
144
+ Structured review learning (see
145
+ [team-memory.md](team-memory.md#security-and-trust-boundaries)) introduces a new
146
+ memory-derived prompt layer, so it observes the same "memory is evidence, not an
147
+ instruction channel" boundary as the rest of retrieval, plus review-specific
148
+ defenses enforced at the persistence boundary in `engine/review-learning.js`:
149
+
150
+ - **Fenced, bounded injection.** Recalled lessons are wrapped with
151
+ `engine/untrusted-fence.js` and capped at 4 records / 4 KiB, independent of the
152
+ generic memory budget. They carry provenance and instruct the agent to verify
153
+ against live code.
154
+ - **No raw reviewer prose is ever stored.** Candidate/lesson bodies are engine-
155
+ generated. Raw comment, transcript, reasoning, and chain-of-thought fields are
156
+ rejected (`assertNoProhibitedReviewPayload`), and secret-shaped values are
157
+ redacted, so the store never becomes a covert channel for injected reviewer text.
158
+ - **Trusted retrieval-trace binding.** An agent may only report an application or
159
+ trigger a contradiction for a lesson its own dispatch's persisted retrieval trace
160
+ actually showed it, and only within its own project. A forged id, an unshown
161
+ lesson, or a cross-project id is rejected as a diagnostic — it cannot quarantine a
162
+ lesson it was never given.
163
+ - **Flag-gated, capture-only-safe.** Both flags default off; capture-only changes no
164
+ rendered prompt, and disabling recall is a complete prompt rollback. Stored
165
+ records stay auditable through the diagnostics endpoints regardless of flag state.
166
+ - **Injection-flag / nonce gates still apply.** A completion flagged
167
+ `failure_class: "injection-flagged"` or failing the nonce check never reaches
168
+ review-learning capture or application processing.
169
+
142
170
  ## 4. Secret management
143
171
 
144
172
  - **PATs and API tokens live in environment variables only.** GitHub tokens