@yemi33/minions 0.1.2378 → 0.1.2380

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 (100) hide show
  1. package/bin/minions.js +19 -9
  2. package/dashboard/js/refresh.js +2 -3
  3. package/dashboard/js/render-prd.js +1 -1
  4. package/dashboard/js/render-work-items.js +13 -6
  5. package/dashboard.js +393 -721
  6. package/docs/README.md +1 -0
  7. package/docs/architecture-review-2026-07-09.md +2 -4
  8. package/docs/auto-discovery.md +36 -49
  9. package/docs/blog-first-successful-dispatch.md +1 -1
  10. package/docs/branch-derivation.md +2 -2
  11. package/docs/command-center.md +1 -1
  12. package/docs/completion-reports.md +14 -4
  13. package/docs/constellation-bridge.md +59 -10
  14. package/docs/constellation-style-telemetry.md +6 -6
  15. package/docs/cooldown-merge-semantics.md +4 -0
  16. package/docs/copilot-cli-schema.md +3 -3
  17. package/docs/cross-repo-plans.md +17 -17
  18. package/docs/deprecated.json +2 -2
  19. package/docs/design-inbox-entries-schema.md +31 -39
  20. package/docs/design-state-storage.md +1 -1
  21. package/docs/documentation-audit-2026-07-09.md +2 -2
  22. package/docs/engine-restart.md +20 -8
  23. package/docs/harness-mode.md +1 -1
  24. package/docs/managed-spawn.md +4 -4
  25. package/docs/onboarding.md +1 -2
  26. package/docs/pr-comment-followup.md +3 -3
  27. package/docs/pr-review-fix-loop.md +1 -1
  28. package/docs/proposals/repo-pool-for-live-checkout.md +1 -2
  29. package/docs/qa-runbook-lifecycle.md +4 -4
  30. package/docs/qa-runbooks.md +2 -2
  31. package/docs/rfc-completion-json.md +4 -1
  32. package/docs/runtime-adapters.md +1 -1
  33. package/docs/self-improvement.md +4 -5
  34. package/docs/shared-lifecycle-module-map.md +3 -1
  35. package/docs/slim-ux/architecture-suggestions.md +5 -6
  36. package/docs/slim-ux/concepts.md +23 -25
  37. package/docs/watches.md +7 -7
  38. package/docs/workspace-manifests.md +1 -1
  39. package/docs/worktree-lifecycle.md +1 -1
  40. package/engine/abandoned-pr-reconciliation.js +4 -5
  41. package/engine/acp-transport.js +49 -8
  42. package/engine/ado-status.js +5 -5
  43. package/engine/ado.js +20 -25
  44. package/engine/agent-worker-pool.js +124 -15
  45. package/engine/bridge.js +260 -5
  46. package/engine/cleanup.js +48 -131
  47. package/engine/cli.js +125 -83
  48. package/engine/cooldown.js +9 -16
  49. package/engine/db/index.js +22 -9
  50. package/engine/db/migrations/009-qa.js +1 -1
  51. package/engine/db/migrations/020-qa-session-scopes.js +23 -0
  52. package/engine/db/migrations/021-archived-work-items.js +72 -0
  53. package/engine/db/migrations/022-global-cc-session.js +31 -0
  54. package/engine/db/migrations/023-engine-state.js +30 -0
  55. package/engine/db/migrations/024-prd-ghost-collisions.js +53 -0
  56. package/engine/db/migrations/025-malformed-work-item-phantoms.js +33 -0
  57. package/engine/dispatch-store.js +2 -7
  58. package/engine/dispatch.js +34 -41
  59. package/engine/github.js +20 -27
  60. package/engine/lifecycle.js +221 -283
  61. package/engine/llm.js +1 -1
  62. package/engine/logs-store.js +2 -2
  63. package/engine/managed-spawn.js +2 -23
  64. package/engine/meeting.js +6 -6
  65. package/engine/metrics-store.js +2 -2
  66. package/engine/note-link-backfill.js +6 -11
  67. package/engine/pipeline.js +18 -36
  68. package/engine/playbook.js +1 -1
  69. package/engine/pooled-agent-process.js +46 -26
  70. package/engine/prd-store.js +73 -54
  71. package/engine/preflight.js +2 -5
  72. package/engine/projects.js +15 -62
  73. package/engine/pull-requests-store.js +0 -17
  74. package/engine/qa-runbooks.js +2 -2
  75. package/engine/qa-runs.js +1 -8
  76. package/engine/qa-sessions.js +41 -64
  77. package/engine/queries.js +120 -219
  78. package/engine/routing.js +3 -5
  79. package/engine/scheduler.js +0 -4
  80. package/engine/shared-branch-pr-reconcile.js +2 -3
  81. package/engine/shared.js +132 -637
  82. package/engine/small-state-store.js +89 -10
  83. package/engine/state-operations.js +16 -4
  84. package/engine/stdio-timestamps.js +1 -1
  85. package/engine/timeout.js +5 -12
  86. package/engine/watch-actions.js +20 -22
  87. package/engine/watches-store.js +1 -1
  88. package/engine/watches.js +6 -10
  89. package/engine/work-item-validation.js +52 -0
  90. package/engine/work-items-store.js +127 -29
  91. package/engine/worktree-gc.js +2 -2
  92. package/engine/worktree-pool.js +8 -18
  93. package/engine.js +167 -349
  94. package/minions.js +2 -2
  95. package/package.json +1 -1
  96. package/playbooks/plan-to-prd.md +2 -2
  97. package/playbooks/shared-rules.md +3 -3
  98. package/playbooks/templates/followup-dispatch.md +1 -1
  99. package/playbooks/verify.md +1 -1
  100. package/prompts/cc-system.md +2 -2
@@ -1,7 +1,7 @@
1
1
  # inbox_entries Table Schema
2
2
 
3
3
  ## Overview
4
- The `inbox_entries` table is the persistent store for operator and agent inbox messages within the Minions orchestration engine. It provides a time-ordered, unread-tracking message queue decoupled from the runtime work-item/dispatch lifecycle.
4
+ The `inbox_entries` table and `engine/inbox-store.js` implement a persistent, time-ordered, unread-tracking message queue decoupled from the runtime work-item/dispatch lifecycle. The store API exists, but production engine, dashboard, and consolidation paths do not consume it yet; those paths still use their existing inbox mechanisms.
5
5
 
6
6
  ## Schema Specification
7
7
 
@@ -9,26 +9,27 @@ The `inbox_entries` table is the persistent store for operator and agent inbox m
9
9
 
10
10
  | Column | Type | Constraints | Purpose |
11
11
  |--------|------|-----------|---------|
12
- | `id` | TEXT | PRIMARY KEY | Unique message identifier; generated as `<recipient>-<timestamp>-<sender>-<seq>` format |
12
+ | `id` | TEXT | PRIMARY KEY | Unique message identifier; generated as `<recipient>-<timestamp>-<sender>-<seq>-<counter>-<random>` |
13
13
  | `recipient_session_id` | TEXT | NOT NULL | Session ID of the inbox owner (operator or agent); routes messages to specific actor |
14
14
  | `sender_id` | TEXT | NOT NULL | Identifier of the message originator; structured as `<agent-id>` or `'system'` or `'operator'` |
15
15
  | `sender_name` | TEXT | NOT NULL | Human-readable sender name for UI display; persisted at write-time for offline/archived reference |
16
- | `sender_type` | TEXT | NOT NULL | Enum: `'agent'`, `'system'`, `'operator'`; constrains allowed sender_id values |
17
- | `interaction_id` | TEXT | NOT NULL | Group ID linking related messages (e.g., work-item dispatch feedback, PR review batch); enables threading and deduplication |
18
- | `sequence` | INTEGER | NOT NULL, DEFAULT 0 | Per-interaction message counter; disambiguates bursts and enables ordering within same millisecond |
16
+ | `sender_type` | TEXT | NOT NULL | Sender category; `createInboxEntry()` accepts `'agent'`, `'system'`, or `'operator'` |
17
+ | `interaction_id` | TEXT | NOT NULL | Caller-supplied group ID for related messages |
18
+ | `sequence` | INTEGER | NOT NULL, DEFAULT 0 | Caller-supplied per-interaction ordering metadata; not required to be unique |
19
19
  | `summary` | TEXT | NOT NULL | Title/subject line; used for list views and user scanning; always present even if content is truncated |
20
20
  | `content` | TEXT | NOT NULL | Full message body; may contain Markdown, code blocks, or structured JSON sidecars (not yet parsed at table level) |
21
- | `unread` | INTEGER | NOT NULL, DEFAULT 1 | Boolean flag (0/1); toggled by reader, cleared on archive/delete; enables fast unread-count queries |
21
+ | `unread` | INTEGER | NOT NULL, DEFAULT 1 | Boolean flag (0/1); changed by the read/unread mutators |
22
22
  | `sent_at` | INTEGER | NOT NULL | Unix timestamp (milliseconds) of message creation; primary sort key for time-ordered display |
23
23
  | `read_at` | INTEGER | Nullable | Unix timestamp (milliseconds) when recipient first read the message; null until marked read |
24
24
  | `notified_at` | INTEGER | Nullable | Unix timestamp (milliseconds) of last notification event sent to operator; null if no notification dispatched |
25
25
 
26
26
  ### Constraints & Relationships
27
27
 
28
- - **PK constraint:** `id` is globally unique across all inboxes; collision risk is negligible given the composite structure (recipient + timestamp + sender + seq).
28
+ - **PK constraint:** `id` is globally unique across all inboxes. The store combines caller context with a process-local counter and random suffix, then retries a conflicting insert up to five times.
29
29
  - **FK (soft):** `recipient_session_id` does NOT enforce a referential constraint to `sessions` table (sessions may be archived). Queries SHOULD handle missing recipients gracefully.
30
- - **FK (soft):** `sender_id` is informational only; no validation that the sender exists in the `config.agents` or active sessions. Invalid senders are logged but do not block writes.
31
- - **No composite index on (recipient_session_id, sent_at):** See Performance section.
30
+ - **FK (soft):** `sender_id` is informational only; no validation requires it to exist in `config.agents` or active sessions.
31
+ - **Application validation:** `createInboxEntry()` accepts only `agent`, `system`, or `operator` for `sender_type`; SQLite does not enforce that enum.
32
+ - **Indexes:** Migration 019 creates composite recipient/time and interaction indexes. See Performance Considerations.
32
33
 
33
34
  ## Architectural Rationale
34
35
 
@@ -36,71 +37,62 @@ The `inbox_entries` table is the persistent store for operator and agent inbox m
36
37
 
37
38
  1. **Separate inbox table vs. inline dispatch/work-item attachments.**
38
39
  - Inboxes can outlive their source work items (e.g., a feedback message remains useful after the WI is done).
39
- - Decoupling allows independent inbox lifecycle (archive, export, cleanup) without cascading work-item deletes.
40
- - Supports rich async consolidation (team notes, KB articles) that don't belong to any single WI.
40
+ - Decoupling allows independent read/unread and deletion lifecycle without cascading work-item deletes.
41
41
 
42
42
  2. **`unread` flag instead of soft-delete or status enum.**
43
43
  - Fast count queries for badge (`SELECT COUNT(*) WHERE recipient_session_id = ? AND unread = 1`).
44
- - Unread state is mutable; allows mark-as-read UX without data loss.
45
- - Simplifies archive/export (no tombstone filtering needed).
44
+ - Unread state is mutable; allows mark-as-read and mark-as-unread behavior without deleting content.
46
45
 
47
46
  3. **`sender_type` as an explicit column.**
48
- - Allows schema validation (e.g., "agent" senders must have valid IDs; "system" is always valid).
47
+ - Allows application-level validation of the sender category.
49
48
  - Enables filtered views ("show only agent messages" or "system alerts only") without string parsing.
50
49
 
51
50
  4. **`interaction_id` for grouping.**
52
- - Enables undo/redo on related messages (e.g., canceling all build-failure notifications from one dispatch).
53
- - Supports consolidation: multiple small notifications can be collapsed into one summary message.
51
+ - `getInboxByInteraction()` retrieves related messages in chronological order.
54
52
 
55
53
  5. **Persistent `sender_name` at write-time.**
56
54
  - Operator may rename agents or disable them; storing the name ensures historical accuracy.
57
- - UI doesn't need to perform runtime lookups on archived inboxes.
55
+ - Future consumers can render the stored name without a runtime agent lookup.
58
56
 
59
57
  6. **Unix timestamps (milliseconds) instead of ISO strings.**
60
58
  - Enables range queries (`sent_at BETWEEN ? AND ?`) without parsing.
61
59
  - Smaller column footprint; easier to sort.
62
60
  - Downside: not human-readable; UI must convert to locale strings.
63
61
 
64
- ### Entity Relationships
62
+ ### Integration Status
65
63
 
66
- - **inbox_entries dispatch WI feedback:** A failed dispatch may create multiple inbox entries (error summary, retry recommendation, logs snippet). Threaded via `interaction_id`.
67
- - **inbox_entries ← PR reviews:** Humans and auto-review agents post comments; summarized as inbox alerts to project owners via `interaction_id` per-PR.
68
- - **inbox_entries ← consolidation:** `engine/consolidation.js` reads inbox entries to extract `agent:` frontmatter and route findings into per-agent knowledge files.
64
+ The table currently has no foreign keys and `engine/inbox-store.js` has no production callers. Dispatch feedback, PR notifications, dashboard inbox views, and consolidation are potential consumers, not implemented relationships. Until those call sites migrate, this table is not the source of truth for `notes/inbox/` or agent steering inbox files.
69
65
 
70
66
  ## Performance Considerations
71
67
 
72
68
  ### Indexing Strategy
73
69
 
74
- **Current indexes (recommended):**
75
- - `idx_recipient_sent` on `(recipient_session_id, sent_at DESC)` — enables efficient "unread count" and "latest N messages" queries without table scans.
76
- - `idx_interaction` on `(interaction_id)` — enables grouping/dedup queries (e.g., "was this interaction already notified?").
70
+ **Current indexes (created by migration 019):**
71
+ - `idx_inbox_recipient_sent` on `(recipient_session_id, sent_at DESC)` — supports recipient-scoped chronological reads.
72
+ - `idx_inbox_interaction` on `(interaction_id)` — supports grouping by interaction.
77
73
  - PK `id` is implicit index.
78
74
 
79
75
  **Query patterns:**
80
- - **Unread count per session:** `SELECT COUNT(*) WHERE recipient_session_id = ? AND unread = 1` must scan `idx_recipient_sent` to avoid full table.
81
- - **Latest messages:** `SELECT * WHERE recipient_session_id = ? ORDER BY sent_at DESC LIMIT 20` seeks on `idx_recipient_sent` DESC.
82
- - **Thread view:** `SELECT * WHERE interaction_id = ? ORDER BY sent_at` seeks on `idx_interaction`, then sorts in application or via secondary index.
83
- - **Cleanup:** `DELETE WHERE sent_at < ? AND unread = 0` full table scan (acceptable; cleanup is infrequent, low-priority background task).
76
+ - **Unread count per session:** `SELECT COUNT(*) ... WHERE recipient_session_id = ? AND unread = 1` narrows by recipient, then filters `unread`.
77
+ - **Latest messages:** `SELECT * ... WHERE recipient_session_id = ? ORDER BY sent_at DESC LIMIT ? OFFSET ?` uses `idx_inbox_recipient_sent`.
78
+ - **Thread view:** `SELECT * ... WHERE interaction_id = ? ORDER BY sent_at ASC` uses `idx_inbox_interaction`, then sorts matching rows.
79
+ - **Cleanup:** `deleteInboxEntriesOlderThan(timestampMs, { unreadOnly })` deletes all older rows by default or only rows with `unread = 1`; no production cleanup job calls it yet.
84
80
 
85
81
  **Missing indexes (future optimization):**
86
82
  - `idx_sender_id` — if queries like "messages from this agent" are common, consider adding.
87
83
  - `idx_unread` — if badge ("N unread") is a hot query, consider combining with recipient (`idx_recipient_unread` on `(recipient_session_id, unread)`).
88
84
 
89
- ### Size Estimates & Cleanup
85
+ ### Cleanup
90
86
 
91
- - **Row size:** ~500–2000 bytes (summary + content; content can be verbose with logs/code snippets).
92
- - **Growth:** ~5–50 entries/day (depends on agent activity and verbose flagging).
93
- - **Retention:** No automatic cleanup; operators may archive/export and manually delete aged entries. Engine does NOT auto-prune.
94
- - **Recommended**: Implement a dashboard setting to soft-delete entries older than N days (default 30) with an undo/export option.
87
+ - **Retention:** No automatic cleanup is wired. The store exposes explicit single, batch, and age-based hard-delete helpers.
95
88
 
96
89
  ## Migration & Backward Compatibility
97
90
 
98
- - **Migration 018 or later** creates `inbox_entries` as part of SQL state cutover. Legacy `inbox/` Markdown files are NOT imported; operators must re-send or manually recreate critical messages.
99
- - **No schema versioning needed** for now; table is immutable-append-only (reads/writes only).
91
+ - **Migration 019** creates `inbox_entries` and its two secondary indexes. It does not import existing Markdown inbox files.
92
+ - The store supports inserts, read/unread updates, notification timestamps, and hard deletes. Every mutator attempts a best-effort `inbox` state event after a real change so dashboard cache versions can advance once consumers are wired.
100
93
 
101
94
  ## Related Code
102
95
 
103
- - **Writes:** `engine/inbox-store.js` (primary mutator; uses `getDb()` and `emitStateEvent`).
104
- - **Reads:** `engine/queries.js#getInboxForSession()`, dashboard `/api/inbox`, `engine/consolidation.js`.
105
- - **UI:** `dashboard/pages/inbox.html`, `dashboard/js/inbox.js`.
106
-
96
+ - **Schema:** `engine/db/migrations/019-inbox-entries.js`.
97
+ - **Store:** `engine/inbox-store.js` (reads, transactional mutators, and best-effort `emitStateEvent('inbox', ...)` after real changes).
98
+ - **Consumers:** none in production yet.
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Author: Rebecca (Architect) | Date: 2026-04-07 | Status: **Implemented**
4
4
 
5
- > **Implementation status (as of 2026-06):** Migrations 001–018 implement the full SQLite cutover. `engine/state.db` is the sole runtime-state authority; migration 018 validates the database, records a durable cutover marker, and retires known legacy mirrors. Historical migrations retain one-time JSON import support. Normal runtime never reads or writes retired mirrors. `minions state check`, `backup`, and `export` provide operations support; explicit offline recovery import is available through `minions state import-legacy-json <dir> --confirm`. Sections 1–3 below are retained as historical design rationale.
5
+ > **Implementation status:** Migrations 001–024 implement the full SQLite cutover and final compatibility cleanup. `engine/state.db` is the sole runtime-state authority; migration 018 records the cutover, migrations 020–024 absorb the remaining scoped/archive/session state and one-time PRD collision repair. Historical migrations retain one-time JSON import support. Normal runtime never references retired mirrors. `minions state check`, `backup`, and `export` provide operations support; explicit offline recovery import is available through `minions state import-legacy-json <dir> --confirm`. Sections 1–3 below are retained as historical design rationale.
6
6
 
7
7
  ## Executive Summary
8
8
 
@@ -13,11 +13,11 @@ canonical data, or misunderstand routing and dashboard behavior.
13
13
  |---|---|---|
14
14
  | Node.js 18+ was sufficient | Node.js 22.5+ is required for `node:sqlite` | `package.json:68` |
15
15
  | Agent dispatch was described as Claude-only | Copilot is the default fleet runtime; Claude and Codex remain supported | `engine/shared.js:3520`, `engine/runtimes/index.js` |
16
- | Root and per-project JSON trackers were described as primary state | Migrated runtime state is primary in `engine/state.db`; JSON trackers are passive mirrors | `engine/dispatch-store.js:49-68`, `CLAUDE.md` "State storage" |
16
+ | Root and per-project JSON trackers were described as primary state | `engine/state.db` is the sole runtime-state authority; retired trackers are migration inputs only | `engine/db/migrations/018-sql-only-cutover.js`, `CLAUDE.md` "State storage" |
17
17
  | `minions preflight` was documented as a public command | `minions doctor` is public; lighter checks run internally during initialization and startup | `bin/minions.js:1207-1264`, `engine/cli.js:232-255` |
18
18
  | Git repository host default was documented as ADO | The fallback host is GitHub | `engine/projects.js:504`, `engine/projects.js:564` |
19
19
  | `WORK_TYPE` documentation omitted complex build fixes | Added `BUILD_FIX_COMPLEX` | `engine/shared.js:4370-4396`, `routing.md:10-28` |
20
- | Command Center sessions were described as a JSON-file store | Sessions are stored in SQLite; the JSON file is a mirror | `engine/shared.js:1830`, `engine/db/migrations/011-remaining-state.js` |
20
+ | Command Center sessions were described as a JSON-file store | Per-tab and global sessions are stored only in SQLite | `engine/small-state-store.js`, `engine/db/migrations/022-global-cc-session.js` |
21
21
  | Watches were described as checking every three ticks and persisting to JSON | The default cadence is 18 ticks; persistence routes through the SQL watch store | `engine/shared.js` `watchPollEvery`, `engine/watches-store.js` |
22
22
  | Two local documentation links targeted files that are not shipped | Removed the runtime `notes.md` link and the stale managed-spawn plan link | `docs/README.md`, `docs/managed-spawn.md` |
23
23
 
@@ -10,11 +10,11 @@ When the engine restarts, it loses its in-memory process handles (`activeProcess
10
10
 
11
11
  | State | Storage | Survives Restart |
12
12
  |-------|---------|-----------------|
13
- | Dispatch queue (pending/active/completed) | `engine/state.db` (`dispatches`; `engine/dispatch.json` is a passive mirror) | Yes |
13
+ | Dispatch queue (pending/active/completed) | `engine/state.db` (`dispatches`) | Yes |
14
14
  | Agent status (working/idle/error) | Derived from `engine/state.db` | Yes |
15
15
  | Agent live output | `agents/*/live-output.log` | Yes (mtime used for orphan cleanup) |
16
16
  | Process handles (`ChildProcess`) | In-memory Map | **No** |
17
- | Cooldown timestamps | In-memory Map | **No** (repopulated from `engine/cooldowns.json`) |
17
+ | Cooldown timestamps | In-memory Map + `engine/state.db` | Yes (repopulated from SQL) |
18
18
 
19
19
  ## Protection Mechanisms
20
20
 
@@ -37,7 +37,20 @@ After the grace period expires, a dispatch with a tracked live process keeps run
37
37
 
38
38
  If there is no live tracked process, the engine uses `live-output.log` mtime as indirect evidence. Once the log is stale for `engine.heartbeatTimeout`, the dispatch is treated as an orphan and marked failed.
39
39
 
40
- ### 3. Stop Warning
40
+ ### 3. Intentional Restart Drains Pooled Leases
41
+
42
+ `engine.js stop` writes a shutdown request into `control.json`; it does not
43
+ tree-kill the daemon. The running engine stops dispatching new work and drains
44
+ fleet ACP-pool leases for up to `engine.shutdownTimeout` (5 minutes by
45
+ default). Cold-spawn agents are detached and are not part of this wait, so they
46
+ can survive for restart reattachment. A pooled lease cannot be reattached:
47
+ when the drain deadline expires it is cancelled and terminalized as a
48
+ retryable timeout before the old engine exits.
49
+
50
+ Unexpected crashes remain fail-safe: persisted `item.pooled` dispatches never
51
+ trust the worker PID and are reaped through the normal retry path on startup.
52
+
53
+ ### 4. Stop Warning
41
54
 
42
55
  `engine.js stop` checks for active dispatches and warns:
43
56
  ```
@@ -45,16 +58,15 @@ WARNING: 2 agent(s) are still working:
45
58
  - Dallas: [office-bohemia] Build & test PR PR-4959092
46
59
  - Rebecca: [office-bohemia] Review PR PR-4964594
47
60
 
48
- These agents will continue running but the engine won't monitor them.
49
- On next start, they'll get a 20-min grace period before being marked as orphans.
61
+ Detached agents will continue for restart reattachment; pooled agents will drain before exit.
50
62
  To kill them now, run: node engine.js kill
51
63
  ```
52
64
 
53
- ### 4. Exponential Backoff on Failures
65
+ ### 5. Exponential Backoff on Failures
54
66
 
55
67
  If an agent is killed as an orphan and the work item retries, cooldowns use exponential backoff (2^failures, max 8x) to prevent spam-retrying broken tasks.
56
68
 
57
- ### 5. Crash-Loop Counter + Alert (W-mr2azk6i000y06e0)
69
+ ### 6. Crash-Loop Counter + Alert (W-mr2azk6i000y06e0)
58
70
 
59
71
  The engine.js *process itself* (not an agent) can crash and get auto-respawned by three independent mechanisms: `engine/supervisor.js#checkEngine` (dead-PID), `engine/supervisor.js#checkEngineHung` (stale-heartbeat), and `engine/watchdog.js` (OS-scheduled external recovery); `dashboard.js`'s in-process 30s watchdog also auto-restarts on a dead PID (the user-triggered Restart Engine button is excluded). Each successful respawn calls `shared.recordEngineRespawn(source)`, which appends `{ts, source}` to `control.json`'s `engineRespawns[]` (rolling window, default `CRASH_LOOP_WINDOW_MS` = 10 min). Once respawns within the window reach `CRASH_LOOP_THRESHOLD` (default 3), `control.crashLoopAlert` is set (dashboard-visible via `/api/status`) and a deduped (one-per-day) `notes/inbox/` alert is written; the alert clears automatically once the window rolls past the incident. Override the window/threshold via `MINIONS_CRASH_LOOP_WINDOW_MS` / `MINIONS_CRASH_LOOP_THRESHOLD` env vars.
60
72
 
@@ -80,7 +92,7 @@ T+0s engine.js stop (warns about active agents)
80
92
  Engine process exits. Agents keep running as OS processes.
81
93
 
82
94
  T+30s Code changes made. engine.js start.
83
- Engine reads dispatch.json — finds 2 active items.
95
+ Engine reads SQL dispatch state — finds 2 active items.
84
96
  Sets grace period: 20 min from now.
85
97
  Logs: "2 active dispatch(es) from previous session"
86
98
 
@@ -88,5 +88,5 @@ Within a single tick the standard scheduled-work dedup is keyed by `_scheduleId`
88
88
 
89
89
  - Tri-agent items are **schedule-driven** — there's no manual "fire a harness mission" entry point. Add a schedule with `harness_mode: "tri_agent"` to opt in.
90
90
  - Iteration pairs always reuse the original mission's artifact path, threshold, max-iterations, and rubric. The evaluator's verdict feedback is appended to the next generator's prompt.
91
- - Mission state lives entirely on disk: the work-items.json trio + the artifact file. No new DB tables.
91
+ - Mission work-item state uses the existing SQL store; mission artifacts remain file-backed. No new tables are required.
92
92
  - Each iteration's evaluator is a separate work item, so dispatch retries, cooldowns, and steering apply normally to every role.
@@ -172,7 +172,7 @@ Single quotes (`'…'`) work the same way. No `powershell -Command "& '<path>' <
172
172
  ┌───────────────────────────────────────────────────────────────────┐
173
173
  │ 3. spawnManagedSpec(spec) per spec │
174
174
  │ - Uses the proven Windows detached-spawn pattern │
175
- │ - Records each in engine/managed-processes.json (locked write)
175
+ │ - Records each in the SQL managed-process store
176
176
  │ - Stdio → engine/managed-logs/<name>.log (append fd, NOT pipe) │
177
177
  └───────────────────────────────────┬───────────────────────────────┘
178
178
 
@@ -241,7 +241,7 @@ The list endpoint returns an ETag; pass it as `If-None-Match` on follow-up polls
241
241
  | Kill (PID terminated, removed from state) | `POST /api/managed-processes/kill` `{"name":"…"}` | Per-row "Kill" button |
242
242
  | Restart (kill old PID, respawn from saved state, kick first healthcheck) | `POST /api/managed-processes/restart` `{"name":"…"}` | Per-row "Restart" button |
243
243
 
244
- Killing a spec from outside Minions (raw `Stop-Process`) leaves a stale row in `managed-processes.json` until the next 30-tick sweep notices the dead PID. Prefer the API.
244
+ Killing a spec from outside Minions (raw `Stop-Process`) leaves a stale SQL row until the next 30-tick sweep notices the dead PID. Prefer the API.
245
245
 
246
246
  ### View logs
247
247
 
@@ -282,9 +282,9 @@ All knobs live under `engine.managedSpawn` in `engine/shared.js` (`ENGINE_DEFAUL
282
282
 
283
283
  ## Performance budget
284
284
 
285
- - **Per-tick contribution**: zero unless the 30-tick sweep fires. The sweep is `O(N)` over specs in `managed-processes.json` with one `process.kill(pid, 0)` per spec.
285
+ - **Per-tick contribution**: zero unless the 30-tick sweep fires. The sweep is `O(N)` over managed specs with one `process.kill(pid, 0)` per spec.
286
286
  - **Healthcheck loops**: per-spec, self-scheduled (not tick-coupled). 10 specs × 1s pre-healthy cadence ⇒ 10 probes/s peak. Post-healthy drops to one probe per `healthBackoffSec` (30s).
287
- - **Lock contention** on `managed-processes.json`: writes batch on transitions (healthy ↔ unhealthy), and `last_health_at` only persists every `healthBackoffSec` to avoid flap. See [`test/perf/managed-spawn-load.test.js`](../test/perf/managed-spawn-load.test.js) for the 10-spec / 3-project load assertion.
287
+ - **Write contention**: SQL updates batch on transitions (healthy ↔ unhealthy), and `last_health_at` only persists every `healthBackoffSec` to avoid flap. See [`test/perf/managed-spawn-load.test.js`](../test/perf/managed-spawn-load.test.js) for the 10-spec / 3-project load assertion.
288
288
  - **Hard cap recommendation**: 50 specs across one engine. Above that, `process.kill(pid, 0)` shells out enough on Windows to dent tick latency.
289
289
 
290
290
  ## Source map
@@ -30,8 +30,7 @@ minions config set-cli claude
30
30
  ```
31
31
 
32
32
  `minions init` creates `~/.minions/`. Primary migrated runtime state lives in
33
- `engine/state.db` (SQLite, WAL mode). Files such as `engine/dispatch.json` and
34
- `engine/metrics.json` are passive diagnostic mirrors; `engine/control.json`
33
+ `engine/state.db` (SQLite, WAL mode). Retired JSON mirrors are removed; `engine/control.json`
35
34
  remains a live JSON control file.
36
35
 
37
36
  ## 2. Link a project
@@ -66,7 +66,7 @@ playbook contract. The poller is unchanged; the carve-out is purely additive.
66
66
  - Validates `meta.pr_followup` shape (all four fields required, ≤ 512
67
67
  chars each). Malformed → `400 { error: "<message>" }`.
68
68
  - Checks for an existing follow-up with the same `parent_comment_id` in
69
- the target project's `work-items.json`. Duplicate → `409 {
69
+ the target project's SQL work-item scope. Duplicate → `409 {
70
70
  error: "followup_already_dispatched", existingWiId: "W-…" }` (no second WI
71
71
  created).
72
72
  - Persists the validated `meta.pr_followup`, and stores
@@ -104,7 +104,7 @@ playbook contract. The poller is unchanged; the carve-out is purely additive.
104
104
  ```
105
105
  `engine/lifecycle.js processCompletionFollowups` logs each entry at `info`
106
106
  and warns if the claimed `wi_id` is missing from any project's
107
- `work-items.json` (catches reverted or non-landing dispatches).
107
+ SQL work-item store (catches reverted or non-landing dispatches).
108
108
 
109
109
  ## Server contract reference
110
110
 
@@ -131,7 +131,7 @@ Any missing/empty field, oversized field, or non-string value → `400` with a
131
131
  descriptive message.
132
132
 
133
133
  **Dedup.** A second `POST /api/work-items` with the same
134
- `meta.pr_followup.parent_comment_id` in the same project's `work-items.json`
134
+ `meta.pr_followup.parent_comment_id` in the same project's SQL scope
135
135
  returns `409 { error: "followup_already_dispatched", existingWiId: "<id>" }`.
136
136
  This is checked under the same `mutateWorkItems` lock used to insert the new
137
137
  WI, so two pollers racing on the same comment cannot both win.
@@ -5,7 +5,7 @@ How the engine manages the lifecycle of a PR from creation through review, fix,
5
5
  ## 1. Implement agent creates PR
6
6
 
7
7
  - Agent pushes code, output contains PR URL
8
- - `syncPrsFromOutput()` (lifecycle.js) extracts URL via regex, creates `pull-requests.json` entry, links to work item via `addPrLink()`
8
+ - `syncPrsFromOutput()` (lifecycle.js) extracts the URL, creates a SQL PR record, and links it to the work item via `addPrLink()`
9
9
 
10
10
  ## 2. Engine discovers PR needs review
11
11
 
@@ -87,8 +87,7 @@ additive.
87
87
  ### Persistence (survives restart)
88
88
 
89
89
  - Dispatch state lives in **SQLite** `engine/state.db` `dispatches` table via
90
- `engine/dispatch-store.js` (`readDispatchSectioned` / `applyDispatchMutation`),
91
- mirrored read-only to `dispatch.json` (source: `engine/dispatch-store.js:49,244,298`).
90
+ `engine/dispatch-store.js` (`readDispatchSectioned` / `applyDispatchMutation`).
92
91
  Each active dispatch is one JSON record. **Because `liveProjectsInUse` is
93
92
  rebuilt from `dispatch.active` every tick, recording the claimed enlistment
94
93
  path on the active dispatch record makes the per-path claim automatically
@@ -9,7 +9,7 @@ W-mpdad3mq000m53bb).
9
9
 
10
10
  ## Runbook location
11
11
 
12
- `qa-runbooks.json` (engine state, JSON list keyed by `id`). Each entry:
12
+ `<MINIONS_DIR>/projects/<project-name>/runbooks/<runbook-id>.json`. Each entry:
13
13
  `{ id, name, target, steps, expectedArtifacts, createdAt, createdBy }`.
14
14
  CRUD via `GET/POST /api/qa/runbooks` (POST returns the new runbook with
15
15
  engine-assigned `id`). `target` is a name from `/api/managed-processes` or
@@ -97,7 +97,7 @@ plain prose, the engine sets up a live target, an agent drafts a
97
97
  runner-native test file, and (with user approval) a second agent executes
98
98
  it. Sessions are a thin orchestration layer on top of the same
99
99
  `managed-spawn` + `qa-run-result.json` infrastructure that powers
100
- runbooks above — they reuse `qa-runs.json`, `engine/qa-artifacts/`, and
100
+ runbooks above — they reuse the SQL QA-run store, `engine/qa-artifacts/`, and
101
101
  the existing `engine/lifecycle.js#runPostCompletionHooks` qa-run sidecar
102
102
  hook. Surfaced on `/qa` (sessions card list above the runbooks/runs
103
103
  tables) and proxied by the Command Center natural-language shortcut.
@@ -161,7 +161,7 @@ routing default):
161
161
  **Multi-project fan-out (W-mpq6xqzj000606d0):** when `spec.projects`
162
162
  contains more than one project name, SETUP is fanned out — one work item
163
163
  per project, queued in parallel into each project's own
164
- `work-items.json`. The first project (`spec.projects[0]`) is the
164
+ the target project's SQL work-item scope. The first project (`spec.projects[0]`) is the
165
165
  **primary** (`meta.qaSession.primary === true`); the rest are
166
166
  **co-services** (`primary === false`). Each WI's
167
167
  `meta.qaSession.coServices` lists the co-service project names (only the
@@ -230,7 +230,7 @@ errors are mapped to HTTP via `_qaSessionsErrorToStatus`:
230
230
 
231
231
  ## File locations
232
232
 
233
- - **Session state**: `engine/qa-sessions.json` (single file, all projects,
233
+ - **Session state**: `engine/state.db` `qa_sessions` (all projects,
234
234
  capped at `QA_SESSIONS_MAX_RECORDS = 500` via `createSession`-time
235
235
  rotation by `createdAt`).
236
236
  - **Drafted test files**: `engine/qa-tests/<sessionId>/test.<ext>`. Created
@@ -11,7 +11,7 @@ Runbooks are per-project test plans. Each runbook is a single JSON file at:
11
11
  <MINIONS_DIR>/projects/<project-name>/runbooks/<runbook-id>.json
12
12
  ```
13
13
 
14
- This mirrors the `projects/<name>/pull-requests.json` precedent — anything
14
+ This follows the project-scoped PR-store precedent — anything
15
15
  scoped to a single project lives under its `projects/<name>/` state dir
16
16
  rather than a root-level `runbooks/` directory. Two reasons:
17
17
 
@@ -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 `qa-runs.json` 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>`.
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
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`.
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,8 +1,11 @@
1
1
  # RFC: `completion.json` — Structured Agent Control-Plane Protocol
2
2
 
3
- > Author: Dallas (Engineer) | Date: 2026-04-27 | Status: **awaiting-approval**
3
+ > Author: Dallas (Engineer) | Date: 2026-04-27 | Status: **historical design record**
4
4
  > Plan: `minions-2026-04-27.json` | Plan item: `P-7a8b9c1d` (SEC-07 / C1)
5
5
  > Note: this RFC lives in `docs/` rather than `plans/` because the repo's `.gitignore` excludes `plans/`. The task description explicitly allowed "or similar".
6
+ > Storage references below describe the pre-SQL implementation evaluated by the
7
+ > RFC. Current completion processing writes one-shot sidecars and persists runtime
8
+ > records in `engine/state.db`.
6
9
 
7
10
  ## TL;DR
8
11
 
@@ -139,7 +139,7 @@ process or a pooled lease. Assignment strategy, in order:
139
139
  4. Otherwise the request queues FIFO until a `release()` or worker crash frees
140
140
  a slot.
141
141
 
142
- Dispatch records persist `item.pooled = true` on disk (`dispatch.json`), not
142
+ Dispatch records persist `item.pooled = true` in SQLite, not
143
143
  just the in-memory `activeProcesses._pooled` flag (which is wiped on engine
144
144
  restart) — the on-disk flag is what tells the restart-reattach path
145
145
  (`engine/cli.js`) that a live worker PID is a pooled lease, not a resumable
@@ -12,7 +12,7 @@ Agent completes task
12
12
  ├─ 1. Learnings Inbox → notes.md (all future agents see it)
13
13
  ├─ 2. Per-Agent History → history.md (agent sees its own past)
14
14
  ├─ 3. Review Feedback Loop → author gets reviewer's findings
15
- └─ 4. Quality Metrics → engine/metrics.json (tracks performance)
15
+ └─ 4. Quality Metrics → engine/state.db (tracks performance)
16
16
  ```
17
17
 
18
18
  ## 1. Learnings Inbox → notes.md
@@ -182,7 +182,7 @@ Human comments on PR with "@minions fix the error handling here"
182
182
 
183
183
  ## 5. Quality Metrics
184
184
 
185
- The engine tracks per-agent performance metrics in `engine/metrics.json`. Updated after every task completion and PR review.
185
+ The engine tracks per-agent performance metrics in the SQL metrics store. It updates them after every task completion and PR review.
186
186
 
187
187
  ### Metrics tracked
188
188
 
@@ -330,7 +330,7 @@ When a git merge or rebase produces conflicts in yarn.lock.
330
330
  | Stale flag auto-reset | 5 min (300,000ms) | `_consolidationInFlight` flag auto-resets after this duration to unblock future runs |
331
331
  | notes.md max size | 50KB | Trims to cap above this; overflow archived to `notes/archive/notes-overflow.md` (not discarded) |
332
332
  | Agent history entries | 20 | Max entries kept in history.md |
333
- | Metrics file | `engine/metrics.json` | Auto-created on first completion |
333
+ | Metrics store | `engine/state.db` (`metrics`) | Created by database migrations |
334
334
 
335
335
  ## Files
336
336
 
@@ -340,6 +340,5 @@ When a git merge or rebase produces conflicts in yarn.lock.
340
340
  | `notes/archive/*.md` | Archived inbox files | Engine (consolidation) |
341
341
  | `notes.md` | Accumulated team knowledge | Engine (consolidation) |
342
342
  | `agents/<name>/history.md` | Per-agent task history | Engine (post-completion) |
343
- | `engine/metrics.json` | Quality metrics per agent | Engine (post-completion + review) |
343
+ | `engine/state.db` `metrics` | Quality metrics per agent | Engine (post-completion + review) |
344
344
  | `notes/inbox/feedback-*.md` | Review feedback for authors | Engine (post-review) |
345
-
@@ -1,6 +1,8 @@
1
1
  # `engine/shared.js` / `engine/lifecycle.js` module-boundary audit
2
2
 
3
- **Status:** documentation only no function bodies were moved or edited as part of this audit.
3
+ **Status:** historical point-in-time audit. It predates the SQL-only state cutover cleanup;
4
+ removed JSON-routing helpers and old line numbers below are retained only as audit evidence, not
5
+ as the current API contract. Use `engine/shared.js` and `engine/lifecycle.js` for current symbols.
4
6
 
5
7
  **Scope:** `engine/shared.js` (9,877 lines, 338 top-level functions) and `engine/lifecycle.js`
6
8
  (6,988 lines, 121 top-level functions) as of this audit's HEAD commit. The PRD item that requested
@@ -81,7 +81,7 @@ shape:
81
81
  ```
82
82
 
83
83
  Plus `/api/events?since=…&limit=…` for paged reads. The engine already writes most of these
84
- moments to `engine/log.json` (the audit ring buffer); we just need to enrich them and expose
84
+ moments to the SQL log store; we just need to enrich them and expose
85
85
  them. The History panel becomes one fetch.
86
86
 
87
87
  **Cost.** ~1.5 days. The hard part is catching every event-emit site — dispatch start, dispatch
@@ -98,8 +98,7 @@ each lifecycle path emits an event.
98
98
  **Problem.** Carlos called out "Work Items" and "Pipelines" as conceptually overlapping in the
99
99
  existing UI. Looking at the data: a Pipeline is a sequence of stages where each stage *is*
100
100
  either a Work Item, a Meeting, or a Plan. So a Pipeline isn't really a separate concept — it's
101
- a Work Item with a stage list. Yet they have entirely separate stores
102
- (`engine/dispatch.json` vs `engine/pipeline-runs.json`), separate API surfaces
101
+ a Work Item with a stage list. Yet they have separate SQL tables, separate API surfaces
103
102
  (`/api/work-items*` vs `/api/pipelines*`), and separate UIs.
104
103
 
105
104
  **Proposal.** Don't merge the *storage* — that's a deep migration. Instead, introduce a
@@ -199,11 +198,11 @@ becomes a "freshness" badge on the Knowledge entry rather than a separate tab.
199
198
 
200
199
  **Problem.** Schedule = "fire on a cron pattern." Watch = "fire when a condition flips." They
201
200
  share most of the lifecycle (definition, fire history, pause/resume, expire/stopAfter). The
202
- two state files (`schedule-runs.json` / `watches.json`) and parallel CRUD APIs add maintenance
201
+ two SQL stores and parallel CRUD APIs add maintenance
203
202
  without adding real concepts.
204
203
 
205
- **Proposal.** Generalize to **Trigger**, with two `kind`s: `cron` and `event`. Backend stays
206
- in two files for now (avoid the migration), but the API and UI present them as one.
204
+ **Proposal.** Generalize to **Trigger**, with two `kind`s: `cron` and `event`. Backend tables
205
+ stay separate, but the API and UI present them as one.
207
206
 
208
207
  ```jsonc
209
208
  {