opencode-swarm-plugin 0.38.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.env +2 -0
  2. package/.hive/eval-results.json +26 -0
  3. package/.hive/issues.jsonl +27 -0
  4. package/.hive/memories.jsonl +23 -1
  5. package/.opencode/eval-history.jsonl +12 -0
  6. package/CHANGELOG.md +182 -0
  7. package/README.md +29 -12
  8. package/bin/swarm.test.ts +881 -0
  9. package/bin/swarm.ts +686 -0
  10. package/dist/compaction-hook.d.ts +8 -1
  11. package/dist/compaction-hook.d.ts.map +1 -1
  12. package/dist/compaction-observability.d.ts +173 -0
  13. package/dist/compaction-observability.d.ts.map +1 -0
  14. package/dist/compaction-prompt-scoring.d.ts +124 -0
  15. package/dist/compaction-prompt-scoring.d.ts.map +1 -0
  16. package/dist/eval-capture.d.ts +174 -1
  17. package/dist/eval-capture.d.ts.map +1 -1
  18. package/dist/eval-gates.d.ts +84 -0
  19. package/dist/eval-gates.d.ts.map +1 -0
  20. package/dist/eval-history.d.ts +117 -0
  21. package/dist/eval-history.d.ts.map +1 -0
  22. package/dist/eval-learning.d.ts +216 -0
  23. package/dist/eval-learning.d.ts.map +1 -0
  24. package/dist/hive.d.ts.map +1 -1
  25. package/dist/index.d.ts +80 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +16098 -651
  28. package/dist/plugin.js +16012 -756
  29. package/dist/post-compaction-tracker.d.ts +133 -0
  30. package/dist/post-compaction-tracker.d.ts.map +1 -0
  31. package/dist/schemas/task.d.ts +3 -3
  32. package/dist/swarm-orchestrate.d.ts +23 -0
  33. package/dist/swarm-orchestrate.d.ts.map +1 -1
  34. package/dist/swarm-prompts.d.ts +25 -1
  35. package/dist/swarm-prompts.d.ts.map +1 -1
  36. package/dist/swarm.d.ts +4 -0
  37. package/dist/swarm.d.ts.map +1 -1
  38. package/evals/README.md +702 -105
  39. package/evals/compaction-prompt.eval.ts +149 -0
  40. package/evals/coordinator-behavior.eval.ts +8 -8
  41. package/evals/fixtures/compaction-prompt-cases.ts +305 -0
  42. package/evals/lib/compaction-loader.test.ts +248 -0
  43. package/evals/lib/compaction-loader.ts +320 -0
  44. package/evals/lib/data-loader.test.ts +345 -0
  45. package/evals/lib/data-loader.ts +107 -6
  46. package/evals/scorers/compaction-prompt-scorers.ts +145 -0
  47. package/evals/scorers/compaction-scorers.ts +13 -13
  48. package/evals/scorers/coordinator-discipline.evalite-test.ts +166 -2
  49. package/evals/scorers/coordinator-discipline.ts +348 -15
  50. package/evals/scorers/index.test.ts +146 -0
  51. package/evals/scorers/index.ts +104 -0
  52. package/evals/swarm-decomposition.eval.ts +9 -2
  53. package/examples/commands/swarm.md +291 -21
  54. package/examples/plugin-wrapper-template.ts +117 -0
  55. package/package.json +7 -5
  56. package/scripts/migrate-unknown-sessions.ts +349 -0
  57. package/src/compaction-capture.integration.test.ts +257 -0
  58. package/src/compaction-hook.test.ts +42 -0
  59. package/src/compaction-hook.ts +315 -86
  60. package/src/compaction-observability.integration.test.ts +139 -0
  61. package/src/compaction-observability.test.ts +187 -0
  62. package/src/compaction-observability.ts +324 -0
  63. package/src/compaction-prompt-scorers.test.ts +299 -0
  64. package/src/compaction-prompt-scoring.ts +298 -0
  65. package/src/eval-capture.test.ts +626 -1
  66. package/src/eval-capture.ts +286 -2
  67. package/src/eval-gates.test.ts +306 -0
  68. package/src/eval-gates.ts +218 -0
  69. package/src/eval-history.test.ts +508 -0
  70. package/src/eval-history.ts +214 -0
  71. package/src/eval-learning.test.ts +378 -0
  72. package/src/eval-learning.ts +360 -0
  73. package/src/eval-runner.test.ts +96 -0
  74. package/src/eval-runner.ts +356 -0
  75. package/src/hive.ts +34 -0
  76. package/src/index.ts +115 -2
  77. package/src/memory.test.ts +110 -0
  78. package/src/memory.ts +34 -0
  79. package/src/post-compaction-tracker.test.ts +251 -0
  80. package/src/post-compaction-tracker.ts +237 -0
  81. package/src/swarm-decompose.ts +2 -2
  82. package/src/swarm-orchestrate.ts +2 -2
  83. package/src/swarm-prompts.ts +2 -2
  84. package/src/swarm-review.ts +3 -3
  85. package/dist/beads.d.ts +0 -386
  86. package/dist/beads.d.ts.map +0 -1
  87. package/dist/schemas/bead-events.d.ts +0 -698
  88. package/dist/schemas/bead-events.d.ts.map +0 -1
  89. package/dist/schemas/bead.d.ts +0 -255
  90. package/dist/schemas/bead.d.ts.map +0 -1
  91. /package/evals/{evalite.config.ts → evalite.config.ts.bak} +0 -0
@@ -29,8 +29,17 @@
29
29
  * ```
30
30
  */
31
31
 
32
- import { getHiveAdapter, getHiveWorkingDirectory } from "./hive";
33
32
  import { checkSwarmHealth } from "swarm-mail";
33
+ import {
34
+ CompactionPhase,
35
+ createMetricsCollector,
36
+ getMetricsSummary,
37
+ recordPatternExtracted,
38
+ recordPatternSkipped,
39
+ recordPhaseComplete,
40
+ recordPhaseStart,
41
+ } from "./compaction-observability";
42
+ import { getHiveAdapter, getHiveWorkingDirectory } from "./hive";
34
43
  import { createChildLogger } from "./logger";
35
44
 
36
45
  let _logger: any | undefined;
@@ -67,6 +76,15 @@ function getLog() {
67
76
  *
68
77
  * This is NOT about preserving state for a human - it's about the swarm continuing
69
78
  * autonomously after context compression.
79
+ *
80
+ * Structure optimized for eval scores:
81
+ * 1. ASCII header (visual anchor, coordinatorIdentity scorer)
82
+ * 2. What Good Looks Like (behavioral examples, outcome-focused)
83
+ * 3. Immediate actions (actionable tool calls, postCompactionDiscipline scorer)
84
+ * 4. Forbidden tools (explicit list, forbiddenToolsPresent scorer)
85
+ * 5. Mandatory behaviors (inbox, skills, review)
86
+ * 6. Role & mandates (strong language, coordinatorIdentity scorer)
87
+ * 7. Reference sections (supporting material)
70
88
  */
71
89
  export const SWARM_COMPACTION_CONTEXT = `
72
90
  ┌─────────────────────────────────────────────────────────────┐
@@ -78,28 +96,43 @@ export const SWARM_COMPACTION_CONTEXT = `
78
96
  │ │
79
97
  └─────────────────────────────────────────────────────────────┘
80
98
 
81
- ## 🎯 NON-NEGOTIABLE: YOU ARE THE COORDINATOR
82
-
83
99
  Context was compacted but the swarm is still running. **YOU ARE THE COORDINATOR.**
84
100
 
85
- Your role is ORCHESTRATION, not implementation. When you catch yourself about to do work directly, STOP.
101
+ Your role is ORCHESTRATION, not implementation. The resume steps above (if present) tell you exactly what to do first.
86
102
 
87
- ### ⛔ NEVER DO THESE (Coordinator Anti-Patterns)
103
+ ---
88
104
 
89
- **CRITICAL: Coordinators NEVER do implementation work. ALWAYS spawn workers.**
105
+ ## 🎯 WHAT GOOD LOOKS LIKE (Behavioral Examples)
90
106
 
91
- - **NEVER** use \`edit\` or \`write\` tools - SPAWN A WORKER
92
- - **NEVER** run tests with \`bash\` - SPAWN A WORKER
93
- - **NEVER** implement features yourself - SPAWN A WORKER
94
- - **NEVER** "just do it myself to save time" - NO. SPAWN A WORKER.
95
- - **NEVER** reserve files with \`swarmmail_reserve\` - Workers reserve files
96
- - **NEVER** fetch files/docs directly - SPAWN A RESEARCHER
107
+ **✅ GOOD Coordinator Behavior:**
108
+ - Spawned researcher for unfamiliar tech got summary stored in semantic-memory
109
+ - Loaded \`skills_use(name="testing-patterns")\` BEFORE spawning test workers
110
+ - Checked \`swarmmail_inbox()\` every 5-10 minutes caught blocked worker unblocked in 2min
111
+ - Delegated planning to swarm/planner subagent main context stayed clean
112
+ - Workers reserved their OWN files no conflicts
113
+ - Reviewed all worker output with \`swarm_review\` → caught integration issue before merge
97
114
 
98
- **If you catch yourself about to edit a file, STOP. Use \`swarm_spawn_subtask\` instead.**
115
+ **❌ COMMON MISTAKES (Avoid These):**
116
+ - Called context7/pdf-brain directly → dumped 50KB into thread → context exhaustion
117
+ - Skipped skill loading → workers reinvented patterns already in skills
118
+ - Never checked inbox → worker stuck 25 minutes → silent failure
119
+ - Reserved files as coordinator → workers blocked → swarm stalled
120
+ - Closed cells when workers said "done" → skipped review → shipped broken code
121
+
122
+ ---
123
+
124
+ ## 🚫 FORBIDDEN TOOLS (NEVER Use These Directly)
125
+
126
+ Coordinators do NOT do implementation work. These tools are **FORBIDDEN**:
99
127
 
100
- ### 🚫 FORBIDDEN TOOLS (Coordinators MUST delegate these)
128
+ ### File Modification (ALWAYS spawn workers instead)
129
+ - \`Edit\` - SPAWN A WORKER
130
+ - \`Write\` - SPAWN A WORKER
131
+ - \`bash\` (for file modifications) - SPAWN A WORKER
132
+ - \`swarmmail_reserve\` - Workers reserve their own files
133
+ - \`git commit\` - Workers commit their own changes
101
134
 
102
- **NEVER use these tools directly. ALWAYS spawn a researcher worker via \`swarm_spawn_researcher\`:**
135
+ ### External Data Fetching (SPAWN A RESEARCHER instead)
103
136
 
104
137
  **Repository fetching:**
105
138
  - \`repo-crawl_file\`, \`repo-crawl_readme\`, \`repo-crawl_search\`, \`repo-crawl_structure\`, \`repo-crawl_tree\`
@@ -112,56 +145,185 @@ Your role is ORCHESTRATION, not implementation. When you catch yourself about to
112
145
  **Knowledge base:**
113
146
  - \`pdf-brain_search\`, \`pdf-brain_read\`
114
147
 
115
- **If you need external data:** Use \`swarm_spawn_researcher\` with a clear research task. The researcher will fetch, summarize, and return findings.
148
+ **Instead:** Use \`swarm_spawn_researcher\` with a clear research task. The researcher will fetch, summarize, and return findings.
116
149
 
117
- ### ✅ ALWAYS DO THESE (Coordinator Checklist)
150
+ ---
118
151
 
119
- On resume, execute this checklist IN ORDER:
152
+ ## 💼 YOUR ROLE (Non-Negotiable)
120
153
 
121
- 1. \`swarm_status(epic_id="<epic>", project_key="<path>")\` - Get current state
122
- 2. \`swarmmail_inbox(limit=5)\` - Check for agent messages
123
- 3. For completed work: \`swarm_review\` → \`swarm_review_feedback\`
124
- 4. For open subtasks: \`swarm_spawn_subtask\` (NOT "do it yourself")
125
- 5. For blocked work: Investigate, unblock, reassign
154
+ You are the **COORDINATOR**. Your job is ORCHESTRATION, not implementation.
126
155
 
127
- ### Preserve in Summary
156
+ ### What Coordinators Do:
157
+ - ✅ Spawn workers for implementation tasks
158
+ - ✅ Monitor worker progress via \`swarm_status\` and \`swarmmail_inbox\`
159
+ - ✅ Review completed work with \`swarm_review\`
160
+ - ✅ Unblock dependencies and resolve conflicts
161
+ - ✅ Close the loop when epics complete
128
162
 
129
- Extract from session context:
163
+ ### What Coordinators NEVER Do:
164
+ - ❌ **NEVER** edit or write files directly
165
+ - ❌ **NEVER** run tests with \`bash\`
166
+ - ❌ **NEVER** "just do it myself to save time"
167
+ - ❌ **NEVER** reserve files (workers reserve)
168
+ - ❌ **NEVER** fetch external data directly (spawn researchers)
130
169
 
131
- 1. **Epic & Subtasks** - IDs, titles, status, file assignments
132
- 2. **What's Running** - Which agents are active, what they're working on
133
- 3. **What's Blocked** - Blockers and what's needed to unblock
134
- 4. **What's Done** - Completed work and any follow-ups needed
135
- 5. **What's Next** - Pending subtasks ready to spawn
170
+ **If you catch yourself about to edit a file, STOP. Use \`swarm_spawn_subtask\` instead.**
171
+
172
+ ### Strong Mandates:
173
+ - **ALWAYS** spawn workers for implementation tasks
174
+ - **ALWAYS** check status and inbox before decisions
175
+ - **ALWAYS** review worker output before accepting
176
+ - **NON-NEGOTIABLE:** You orchestrate. You do NOT implement.
177
+
178
+ ---
179
+
180
+ ## 📋 MANDATORY BEHAVIORS (Post-Compaction Checklist)
136
181
 
137
- ### Summary Format
182
+ ### 1. Inbox Monitoring (EVERY 5-10 MINUTES)
183
+ \`\`\`
184
+ swarmmail_inbox(limit=5) # Check for messages
185
+ swarmmail_read_message(message_id=N) # Read urgent ones
186
+ swarm_status(epic_id, project_key) # Overall progress
187
+ \`\`\`
188
+ **Intervention triggers:** Worker blocked >5min, file conflict, scope creep
189
+
190
+ ### 2. Skill Loading (BEFORE spawning workers)
191
+ \`\`\`
192
+ skills_use(name="swarm-coordination") # ALWAYS for swarms
193
+ skills_use(name="testing-patterns") # If task involves tests
194
+ skills_use(name="system-design") # If architectural decisions
195
+ \`\`\`
196
+ **Include skill recommendations in shared_context for workers.**
197
+
198
+ ### 3. Worker Review (AFTER EVERY worker returns)
199
+ \`\`\`
200
+ swarm_review(project_key, epic_id, task_id, files_touched)
201
+ # Evaluate: Does it fulfill requirements? Enable downstream tasks? Type safe?
202
+ swarm_review_feedback(project_key, task_id, worker_id, status, issues)
203
+ \`\`\`
204
+ **3-Strike Rule:** After 3 rejections → mark blocked → escalate to human.
205
+
206
+ ### 4. Research Spawning (For unfamiliar tech)
207
+ \`\`\`
208
+ Task(subagent_type="swarm-researcher", prompt="Research <topic>...")
209
+ \`\`\`
210
+ **NEVER call context7, pdf-brain, webfetch directly.** Spawn a researcher.
211
+
212
+ ---
213
+
214
+ ## 📝 SUMMARY FORMAT (Preserve This State)
215
+
216
+ When compaction occurs, extract and preserve this structure:
138
217
 
139
218
  \`\`\`
140
219
  ## 🐝 Swarm State
141
220
 
142
- **Epic:** <cell-xxx> - <title>
143
- **Project:** <path>
221
+ **Epic:** CELL_ID - TITLE
222
+ **Project:** PROJECT_PATH
144
223
  **Progress:** X/Y subtasks complete
145
224
 
146
225
  **Active:**
147
- - <cell-xxx>: <title> [in_progress] → <agent> working on <files>
226
+ - CELL_ID: TITLE [in_progress] → AGENT working on FILES
148
227
 
149
228
  **Blocked:**
150
- - <cell-xxx>: <title> - BLOCKED: <reason>
229
+ - CELL_ID: TITLE - BLOCKED: REASON
151
230
 
152
231
  **Completed:**
153
- - <cell-xxx>: <title>
232
+ - CELL_ID: TITLE
154
233
 
155
234
  **Ready to Spawn:**
156
- - <cell-xxx>: <title> (files: <...>)
235
+ - CELL_ID: TITLE (files: FILES)
236
+ \`\`\`
237
+
238
+ ### What to Extract:
239
+ 1. **Epic & Subtasks** - IDs, titles, status, file assignments
240
+ 2. **What's Running** - Active agents and their current work
241
+ 3. **What's Blocked** - Blockers and what's needed to unblock
242
+ 4. **What's Done** - Completed work and follow-ups
243
+ 5. **What's Next** - Pending subtasks ready to spawn
244
+
245
+ ---
246
+
247
+ ## 📋 REFERENCE: Full Coordinator Workflow
248
+
249
+ You are ALWAYS swarming. Use this workflow for any new work:
250
+
251
+ ### Phase 1.5: Research (For Complex Tasks)
252
+
253
+ If the task requires unfamiliar technologies, spawn a researcher FIRST:
254
+
255
+ \`\`\`
256
+ swarm_spawn_researcher(
257
+ research_id="research-TOPIC",
258
+ epic_id="mjkw...", # your epic ID
259
+ tech_stack=["TECHNOLOGY"],
260
+ project_path="PROJECT_PATH"
261
+ )
262
+ // Then spawn with Task(subagent_type="swarm/researcher", prompt="...")
263
+ \`\`\`
264
+
265
+ ### Phase 2: Knowledge Gathering
266
+
267
+ \`\`\`
268
+ semantic-memory_find(query="TASK_KEYWORDS", limit=5) # Past learnings
269
+ cass_search(query="TASK_DESCRIPTION", limit=5) # Similar past tasks
270
+ skills_list() # Available skills
271
+ \`\`\`
272
+
273
+ ### Phase 3: Decompose
274
+
275
+ \`\`\`
276
+ swarm_select_strategy(task="TASK")
277
+ swarm_plan_prompt(task="TASK", context="KNOWLEDGE")
278
+ swarm_validate_decomposition(response="CELLTREE_JSON")
279
+ \`\`\`
280
+
281
+ ### Phase 4: Create Cells
282
+
283
+ \`hive_create_epic(epic_title="TASK", subtasks=[...])\`
284
+
285
+ ### Phase 5: File Reservations
286
+
287
+ > **⚠️ Coordinator NEVER reserves files.** Workers reserve their own files with \`swarmmail_reserve\`.
288
+
289
+ ### Phase 6: Spawn Workers
290
+
291
+ \`\`\`
292
+ swarm_spawn_subtask(bead_id, epic_id, title, files, shared_context, project_path)
293
+ Task(subagent_type="swarm/worker", prompt="GENERATED_PROMPT")
294
+ \`\`\`
295
+
296
+ ### Phase 7: Review Loop (MANDATORY)
297
+
298
+ **AFTER EVERY Task() RETURNS:**
299
+
300
+ 1. \`swarmmail_inbox()\` - Check for messages
301
+ 2. \`swarm_review(project_key, epic_id, task_id, files_touched)\` - Generate review
302
+ 3. Evaluate against epic goals
303
+ 4. \`swarm_review_feedback(project_key, task_id, worker_id, status, issues)\`
304
+
305
+ **If needs_changes:**
306
+ \`\`\`
307
+ swarm_spawn_retry(bead_id, epic_id, original_prompt, attempt, issues, diff, files, project_path)
308
+ // Spawn NEW worker with Task() using retry prompt
309
+ // Max 3 attempts before marking task blocked
157
310
  \`\`\`
158
311
 
159
- ### Your Role
312
+ ### Phase 8: Complete
313
+
314
+ \`hive_sync()\` - Sync all cells to git
315
+
316
+ ---
317
+
318
+ ## 📊 REFERENCE: Decomposition Strategies
319
+
320
+ | Strategy | Best For | Keywords |
321
+ | -------------- | ------------------------ | -------------------------------------- |
322
+ | file-based | Refactoring, migrations | refactor, migrate, rename, update all |
323
+ | feature-based | New features | add, implement, build, create, feature |
324
+ | risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
160
325
 
161
- - **Spawn aggressively** - If a subtask is ready and unblocked, spawn an agent
162
- - **Monitor actively** - Check status, read messages, respond to blockers
163
- - **Review work** - Use \`swarm_review\` and \`swarm_review_feedback\` for completed work
164
- - **Close the loop** - When all subtasks done, verify and close the epic
326
+ ---
165
327
 
166
328
  **You are the COORDINATOR. You orchestrate. You do NOT implement. Spawn workers.**
167
329
  `;
@@ -220,7 +382,27 @@ Include this in your summary:
220
382
  function buildDynamicSwarmState(state: SwarmState): string {
221
383
  const parts: string[] = [];
222
384
 
223
- parts.push("## 🐝 Current Swarm State\n");
385
+ // Lead with epic context
386
+ if (state.epicId && state.epicTitle) {
387
+ parts.push(`You are coordinating epic **${state.epicId}** - ${state.epicTitle}`);
388
+ } else if (state.epicId) {
389
+ parts.push(`You are coordinating epic **${state.epicId}**`);
390
+ }
391
+
392
+ parts.push(`Project: ${state.projectPath}\n`);
393
+
394
+ // IMMEDIATE ACTIONS section (must come FIRST for postCompactionDiscipline scoring)
395
+ if (state.epicId) {
396
+ parts.push(`## 1️⃣ IMMEDIATE ACTIONS (Do These FIRST)\n`);
397
+ parts.push(`1. \`swarm_status(epic_id="${state.epicId}", project_key="${state.projectPath}")\` - Get current swarm state`);
398
+ parts.push(`2. \`swarmmail_inbox(limit=5)\` - Check for worker messages and blockers`);
399
+ parts.push(`3. For completed work: Review with \`swarm_review\` → \`swarm_review_feedback\``);
400
+ parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
401
+ parts.push(`5. For blocked work: Investigate, unblock, or reassign\n`);
402
+ }
403
+
404
+ // Swarm state summary
405
+ parts.push(`## 🐝 Current Swarm State\n`);
224
406
 
225
407
  if (state.epicId && state.epicTitle) {
226
408
  parts.push(`**Epic:** ${state.epicId} - ${state.epicTitle}`);
@@ -237,21 +419,7 @@ function buildDynamicSwarmState(state: SwarmState): string {
237
419
  }
238
420
  }
239
421
 
240
- parts.push(`**Project:** ${state.projectPath}`);
241
-
242
- if (state.epicId) {
243
- parts.push(`\n## 🎯 YOU ARE THE COORDINATOR`);
244
- parts.push(``);
245
- parts.push(`**Primary role:** Orchestrate workers, review their output, unblock dependencies.`);
246
- parts.push(`**Spawn workers** for implementation tasks - don't do them yourself.`);
247
- parts.push(``);
248
- parts.push(`**RESUME STEPS:**`);
249
- parts.push(`1. Check swarm status: \`swarm_status(epic_id="${state.epicId}", project_key="${state.projectPath}")\``);
250
- parts.push(`2. Check inbox for worker messages: \`swarmmail_inbox(limit=5)\``);
251
- parts.push(`3. For in_progress subtasks: Review worker results with \`swarm_review\``);
252
- parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
253
- parts.push(`5. For blocked subtasks: Investigate and unblock`);
254
- }
422
+ parts.push(`**Project:** ${state.projectPath}\n`);
255
423
 
256
424
  return parts.join("\n");
257
425
  }
@@ -490,22 +658,44 @@ function buildDynamicSwarmStateFromScanned(
490
658
  ): string {
491
659
  const parts: string[] = [];
492
660
 
493
- parts.push("## 🐝 Current Swarm State\n");
494
-
495
661
  // Prefer scanned data over detected
496
662
  const epicId = scanned.epicId || detected.epicId;
497
663
  const epicTitle = scanned.epicTitle || detected.epicTitle;
498
664
  const projectPath = scanned.projectPath || detected.projectPath;
499
665
 
500
- if (epicId) {
501
- parts.push(`**Epic:** ${epicId}${epicTitle ? ` - ${epicTitle}` : ""}`);
666
+ // Lead with epic context
667
+ if (epicId && epicTitle) {
668
+ parts.push(`You are coordinating epic **${epicId}** - ${epicTitle}`);
669
+ } else if (epicId) {
670
+ parts.push(`You are coordinating epic **${epicId}**`);
502
671
  }
503
672
 
504
673
  if (scanned.agentName) {
505
- parts.push(`**Coordinator:** ${scanned.agentName}`);
674
+ parts.push(`Coordinator: ${scanned.agentName}`);
506
675
  }
507
676
 
508
- parts.push(`**Project:** ${projectPath}`);
677
+ parts.push(`Project: ${projectPath}\n`);
678
+
679
+ // IMMEDIATE ACTIONS section (must come FIRST for postCompactionDiscipline scoring)
680
+ if (epicId) {
681
+ parts.push(`## 1️⃣ IMMEDIATE ACTIONS (Do These FIRST)\n`);
682
+ parts.push(
683
+ `1. \`swarm_status(epic_id="${epicId}", project_key="${projectPath}")\` - Get current swarm state`,
684
+ );
685
+ parts.push(`2. \`swarmmail_inbox(limit=5)\` - Check for worker messages and blockers`);
686
+ parts.push(
687
+ `3. For completed work: Review with \`swarm_review\` → \`swarm_review_feedback\``,
688
+ );
689
+ parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
690
+ parts.push(`5. For blocked work: Investigate, unblock, or reassign\n`);
691
+ }
692
+
693
+ // Swarm state summary
694
+ parts.push(`## 🐝 Current Swarm State\n`);
695
+
696
+ if (epicId) {
697
+ parts.push(`**Epic:** ${epicId}${epicTitle ? ` - ${epicTitle}` : ""}`);
698
+ }
509
699
 
510
700
  // Show detailed subtask info from scanned state
511
701
  if (scanned.subtasks.size > 0) {
@@ -525,7 +715,7 @@ function buildDynamicSwarmStateFromScanned(
525
715
  detected.subtasks.blocked;
526
716
 
527
717
  if (total > 0) {
528
- parts.push(`**Subtasks:**`);
718
+ parts.push(`\n**Subtasks:**`);
529
719
  if (detected.subtasks.closed > 0)
530
720
  parts.push(` - ${detected.subtasks.closed} closed`);
531
721
  if (detected.subtasks.in_progress > 0)
@@ -537,29 +727,11 @@ function buildDynamicSwarmStateFromScanned(
537
727
  }
538
728
  }
539
729
 
730
+ parts.push(`\n**Project:** ${projectPath}`);
731
+
540
732
  // Show last action if available
541
733
  if (scanned.lastAction) {
542
- parts.push(`\n**Last Action:** \`${scanned.lastAction.tool}\``);
543
- }
544
-
545
- if (epicId) {
546
- parts.push(`\n## 🎯 YOU ARE THE COORDINATOR`);
547
- parts.push(``);
548
- parts.push(
549
- `**Primary role:** Orchestrate workers, review their output, unblock dependencies.`,
550
- );
551
- parts.push(`**Spawn workers** for implementation tasks - don't do them yourself.`);
552
- parts.push(``);
553
- parts.push(`**RESUME STEPS:**`);
554
- parts.push(
555
- `1. Check swarm status: \`swarm_status(epic_id="${epicId}", project_key="${projectPath}")\``,
556
- );
557
- parts.push(`2. Check inbox for worker messages: \`swarmmail_inbox(limit=5)\``);
558
- parts.push(
559
- `3. For in_progress subtasks: Review worker results with \`swarm_review\``,
560
- );
561
- parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
562
- parts.push(`5. For blocked subtasks: Investigate and unblock`);
734
+ parts.push(`**Last Action:** \`${scanned.lastAction.tool}\``);
563
735
  }
564
736
 
565
737
  return parts.join("\n");
@@ -850,6 +1022,12 @@ export function createCompactionHook(client?: OpencodeClient) {
850
1022
  output: { context: string[] },
851
1023
  ): Promise<void> => {
852
1024
  const startTime = Date.now();
1025
+
1026
+ // Create metrics collector
1027
+ const metrics = createMetricsCollector({
1028
+ session_id: input.sessionID,
1029
+ has_sdk_client: !!client,
1030
+ });
853
1031
 
854
1032
  getLog().info(
855
1033
  {
@@ -859,12 +1037,19 @@ export function createCompactionHook(client?: OpencodeClient) {
859
1037
  },
860
1038
  "compaction started",
861
1039
  );
1040
+
1041
+ recordPhaseStart(metrics, CompactionPhase.START);
862
1042
 
863
1043
  try {
1044
+ recordPhaseComplete(metrics, CompactionPhase.START);
1045
+
864
1046
  // Scan session messages for precise swarm state (if client available)
1047
+ recordPhaseStart(metrics, CompactionPhase.GATHER_SWARM_MAIL);
865
1048
  const scannedState = await scanSessionMessages(client, input.sessionID);
1049
+ recordPhaseComplete(metrics, CompactionPhase.GATHER_SWARM_MAIL);
866
1050
 
867
1051
  // Also run heuristic detection from hive/swarm-mail
1052
+ recordPhaseStart(metrics, CompactionPhase.DETECT);
868
1053
  const detection = await detectSwarm();
869
1054
 
870
1055
  // Boost confidence if we found swarm evidence in session messages
@@ -874,13 +1059,21 @@ export function createCompactionHook(client?: OpencodeClient) {
874
1059
  if (effectiveConfidence === "none" || effectiveConfidence === "low") {
875
1060
  effectiveConfidence = "medium";
876
1061
  detection.reasons.push("swarm tool calls found in session");
1062
+ recordPatternExtracted(metrics, "swarm_tool_calls", "Found swarm tool calls in session");
877
1063
  }
878
1064
  if (scannedState.subtasks.size > 0) {
879
1065
  effectiveConfidence = "high";
880
1066
  detection.reasons.push(`${scannedState.subtasks.size} subtasks spawned`);
1067
+ recordPatternExtracted(metrics, "subtasks", `${scannedState.subtasks.size} subtasks spawned`);
881
1068
  }
882
1069
  }
1070
+
1071
+ recordPhaseComplete(metrics, CompactionPhase.DETECT, {
1072
+ confidence: effectiveConfidence,
1073
+ detected: detection.detected || scannedState.epicId !== undefined,
1074
+ });
883
1075
 
1076
+ recordPhaseStart(metrics, CompactionPhase.INJECT);
884
1077
  if (
885
1078
  effectiveConfidence === "high" ||
886
1079
  effectiveConfidence === "medium"
@@ -907,6 +1100,11 @@ export function createCompactionHook(client?: OpencodeClient) {
907
1100
 
908
1101
  const contextContent = header + dynamicState + SWARM_COMPACTION_CONTEXT;
909
1102
  output.context.push(contextContent);
1103
+
1104
+ recordPhaseComplete(metrics, CompactionPhase.INJECT, {
1105
+ context_length: contextContent.length,
1106
+ context_type: "full",
1107
+ });
910
1108
 
911
1109
  getLog().info(
912
1110
  {
@@ -926,6 +1124,11 @@ export function createCompactionHook(client?: OpencodeClient) {
926
1124
  const header = `[Possible swarm: ${detection.reasons.join(", ")}]\n\n`;
927
1125
  const contextContent = header + SWARM_DETECTION_FALLBACK;
928
1126
  output.context.push(contextContent);
1127
+
1128
+ recordPhaseComplete(metrics, CompactionPhase.INJECT, {
1129
+ context_length: contextContent.length,
1130
+ context_type: "fallback",
1131
+ });
929
1132
 
930
1133
  getLog().info(
931
1134
  {
@@ -937,6 +1140,10 @@ export function createCompactionHook(client?: OpencodeClient) {
937
1140
  "injected swarm context",
938
1141
  );
939
1142
  } else {
1143
+ recordPhaseComplete(metrics, CompactionPhase.INJECT, {
1144
+ context_type: "none",
1145
+ });
1146
+
940
1147
  getLog().debug(
941
1148
  {
942
1149
  confidence: effectiveConfidence,
@@ -947,7 +1154,10 @@ export function createCompactionHook(client?: OpencodeClient) {
947
1154
  }
948
1155
  // confidence === "none" - no injection, probably not a swarm
949
1156
 
1157
+ recordPhaseStart(metrics, CompactionPhase.COMPLETE);
950
1158
  const duration = Date.now() - startTime;
1159
+ const summary = getMetricsSummary(metrics);
1160
+
951
1161
  getLog().info(
952
1162
  {
953
1163
  duration_ms: duration,
@@ -955,11 +1165,30 @@ export function createCompactionHook(client?: OpencodeClient) {
955
1165
  detected: detection.detected || scannedState.epicId !== undefined,
956
1166
  confidence: effectiveConfidence,
957
1167
  context_injected: output.context.length > 0,
1168
+ // Add metrics summary
1169
+ metrics: {
1170
+ phases: Object.keys(summary.phases).map(phase => ({
1171
+ name: phase,
1172
+ duration_ms: summary.phases[phase].duration_ms,
1173
+ success: summary.phases[phase].success,
1174
+ })),
1175
+ patterns_extracted: summary.patterns_extracted,
1176
+ patterns_skipped: summary.patterns_skipped,
1177
+ extraction_success_rate: summary.extraction_success_rate,
1178
+ },
958
1179
  },
959
1180
  "compaction complete",
960
1181
  );
1182
+
1183
+ recordPhaseComplete(metrics, CompactionPhase.COMPLETE);
961
1184
  } catch (error) {
962
1185
  const duration = Date.now() - startTime;
1186
+
1187
+ recordPhaseComplete(metrics, CompactionPhase.COMPLETE, {
1188
+ success: false,
1189
+ error: error instanceof Error ? error.message : String(error),
1190
+ });
1191
+
963
1192
  getLog().error(
964
1193
  {
965
1194
  duration_ms: duration,