opencode-swarm-plugin 0.39.1 → 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 (42) hide show
  1. package/.hive/issues.jsonl +16 -0
  2. package/CHANGELOG.md +52 -0
  3. package/bin/swarm.test.ts +406 -0
  4. package/bin/swarm.ts +303 -0
  5. package/dist/compaction-hook.d.ts +8 -1
  6. package/dist/compaction-hook.d.ts.map +1 -1
  7. package/dist/compaction-observability.d.ts +173 -0
  8. package/dist/compaction-observability.d.ts.map +1 -0
  9. package/dist/eval-capture.d.ts +93 -0
  10. package/dist/eval-capture.d.ts.map +1 -1
  11. package/dist/hive.d.ts.map +1 -1
  12. package/dist/index.d.ts +36 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +15670 -580
  15. package/dist/plugin.js +15623 -557
  16. package/dist/schemas/task.d.ts +3 -3
  17. package/evals/README.md +113 -0
  18. package/evals/scorers/coordinator-discipline.evalite-test.ts +163 -0
  19. package/evals/scorers/coordinator-discipline.ts +335 -2
  20. package/evals/scorers/index.test.ts +146 -0
  21. package/evals/scorers/index.ts +104 -0
  22. package/evals/swarm-decomposition.eval.ts +9 -2
  23. package/examples/commands/swarm.md +291 -21
  24. package/package.json +1 -1
  25. package/src/compaction-hook.ts +258 -110
  26. package/src/compaction-observability.integration.test.ts +139 -0
  27. package/src/compaction-observability.test.ts +187 -0
  28. package/src/compaction-observability.ts +324 -0
  29. package/src/eval-capture.test.ts +204 -1
  30. package/src/eval-capture.ts +194 -2
  31. package/src/eval-runner.test.ts +96 -0
  32. package/src/eval-runner.ts +356 -0
  33. package/src/hive.ts +34 -0
  34. package/src/index.ts +54 -1
  35. package/src/memory.test.ts +110 -0
  36. package/src/memory.ts +34 -0
  37. package/dist/beads.d.ts +0 -386
  38. package/dist/beads.d.ts.map +0 -1
  39. package/dist/schemas/bead-events.d.ts +0 -698
  40. package/dist/schemas/bead-events.d.ts.map +0 -1
  41. package/dist/schemas/bead.d.ts +0 -255
  42. package/dist/schemas/bead.d.ts.map +0 -1
@@ -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
99
121
 
100
- ### 🚫 FORBIDDEN TOOLS (Coordinators MUST delegate these)
122
+ ---
123
+
124
+ ## 🚫 FORBIDDEN TOOLS (NEVER Use These Directly)
125
+
126
+ Coordinators do NOT do implementation work. These tools are **FORBIDDEN**:
101
127
 
102
- **NEVER use these tools directly. ALWAYS spawn a researcher worker via \`swarm_spawn_researcher\`:**
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
134
+
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,111 +145,155 @@ 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)
181
+
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
+ ---
136
213
 
137
- ### Summary Format
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)
157
236
  \`\`\`
158
237
 
159
- ### Your Role
160
-
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
165
-
166
- **You are the COORDINATOR. You orchestrate. You do NOT implement. Spawn workers.**
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
167
244
 
168
245
  ---
169
246
 
170
- ## 📋 FULL COORDINATOR WORKFLOW (Reference)
247
+ ## 📋 REFERENCE: Full Coordinator Workflow
171
248
 
172
- You are ALWAYS swarming. Here is the complete workflow for any new work:
249
+ You are ALWAYS swarming. Use this workflow for any new work:
173
250
 
174
- ### Phase 1.5: Research Phase (FOR COMPLEX TASKS)
251
+ ### Phase 1.5: Research (For Complex Tasks)
175
252
 
176
- **If the task requires understanding unfamiliar technologies, spawn a researcher FIRST:**
253
+ If the task requires unfamiliar technologies, spawn a researcher FIRST:
177
254
 
178
255
  \`\`\`
179
256
  swarm_spawn_researcher(
180
- research_id="research-<topic>",
181
- epic_id="<epic-id>",
182
- tech_stack=["<technology>"],
183
- project_path="<path>"
257
+ research_id="research-TOPIC",
258
+ epic_id="mjkw...", # your epic ID
259
+ tech_stack=["TECHNOLOGY"],
260
+ project_path="PROJECT_PATH"
184
261
  )
185
- // Then spawn with Task(subagent_type="swarm/researcher", prompt="<from above>")
262
+ // Then spawn with Task(subagent_type="swarm/researcher", prompt="...")
186
263
  \`\`\`
187
264
 
188
265
  ### Phase 2: Knowledge Gathering
189
266
 
190
267
  \`\`\`
191
- semantic-memory_find(query="<task keywords>", limit=5) # Past learnings
192
- cass_search(query="<task description>", limit=5) # Similar past tasks
193
- skills_list() # Available skills
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
194
271
  \`\`\`
195
272
 
196
273
  ### Phase 3: Decompose
197
274
 
198
275
  \`\`\`
199
- swarm_select_strategy(task="<task>")
200
- swarm_plan_prompt(task="<task>", context="<synthesized knowledge>")
201
- swarm_validate_decomposition(response="<CellTree JSON>")
276
+ swarm_select_strategy(task="TASK")
277
+ swarm_plan_prompt(task="TASK", context="KNOWLEDGE")
278
+ swarm_validate_decomposition(response="CELLTREE_JSON")
202
279
  \`\`\`
203
280
 
204
281
  ### Phase 4: Create Cells
205
282
 
206
- \`hive_create_epic(epic_title="<task>", subtasks=[...])\`
283
+ \`hive_create_epic(epic_title="TASK", subtasks=[...])\`
207
284
 
208
- ### Phase 5: DO NOT Reserve Files
285
+ ### Phase 5: File Reservations
209
286
 
210
- > **⚠️ Coordinator NEVER reserves files.** Workers reserve their own files.
287
+ > **⚠️ Coordinator NEVER reserves files.** Workers reserve their own files with \`swarmmail_reserve\`.
211
288
 
212
289
  ### Phase 6: Spawn Workers
213
290
 
214
291
  \`\`\`
215
292
  swarm_spawn_subtask(bead_id, epic_id, title, files, shared_context, project_path)
216
- Task(subagent_type="swarm/worker", prompt="<from above>")
293
+ Task(subagent_type="swarm/worker", prompt="GENERATED_PROMPT")
217
294
  \`\`\`
218
295
 
219
- ### Phase 7: MANDATORY Review Loop
296
+ ### Phase 7: Review Loop (MANDATORY)
220
297
 
221
298
  **AFTER EVERY Task() RETURNS:**
222
299
 
@@ -236,7 +313,9 @@ swarm_spawn_retry(bead_id, epic_id, original_prompt, attempt, issues, diff, file
236
313
 
237
314
  \`hive_sync()\` - Sync all cells to git
238
315
 
239
- ## Strategy Reference
316
+ ---
317
+
318
+ ## 📊 REFERENCE: Decomposition Strategies
240
319
 
241
320
  | Strategy | Best For | Keywords |
242
321
  | -------------- | ------------------------ | -------------------------------------- |
@@ -244,6 +323,8 @@ swarm_spawn_retry(bead_id, epic_id, original_prompt, attempt, issues, diff, file
244
323
  | feature-based | New features | add, implement, build, create, feature |
245
324
  | risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
246
325
 
326
+ ---
327
+
247
328
  **You are the COORDINATOR. You orchestrate. You do NOT implement. Spawn workers.**
248
329
  `;
249
330
 
@@ -301,7 +382,27 @@ Include this in your summary:
301
382
  function buildDynamicSwarmState(state: SwarmState): string {
302
383
  const parts: string[] = [];
303
384
 
304
- 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`);
305
406
 
306
407
  if (state.epicId && state.epicTitle) {
307
408
  parts.push(`**Epic:** ${state.epicId} - ${state.epicTitle}`);
@@ -318,21 +419,7 @@ function buildDynamicSwarmState(state: SwarmState): string {
318
419
  }
319
420
  }
320
421
 
321
- parts.push(`**Project:** ${state.projectPath}`);
322
-
323
- if (state.epicId) {
324
- parts.push(`\n## 🎯 YOU ARE THE COORDINATOR`);
325
- parts.push(``);
326
- parts.push(`**Primary role:** Orchestrate workers, review their output, unblock dependencies.`);
327
- parts.push(`**Spawn workers** for implementation tasks - don't do them yourself.`);
328
- parts.push(``);
329
- parts.push(`**RESUME STEPS:**`);
330
- parts.push(`1. Check swarm status: \`swarm_status(epic_id="${state.epicId}", project_key="${state.projectPath}")\``);
331
- parts.push(`2. Check inbox for worker messages: \`swarmmail_inbox(limit=5)\``);
332
- parts.push(`3. For in_progress subtasks: Review worker results with \`swarm_review\``);
333
- parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
334
- parts.push(`5. For blocked subtasks: Investigate and unblock`);
335
- }
422
+ parts.push(`**Project:** ${state.projectPath}\n`);
336
423
 
337
424
  return parts.join("\n");
338
425
  }
@@ -571,22 +658,44 @@ function buildDynamicSwarmStateFromScanned(
571
658
  ): string {
572
659
  const parts: string[] = [];
573
660
 
574
- parts.push("## 🐝 Current Swarm State\n");
575
-
576
661
  // Prefer scanned data over detected
577
662
  const epicId = scanned.epicId || detected.epicId;
578
663
  const epicTitle = scanned.epicTitle || detected.epicTitle;
579
664
  const projectPath = scanned.projectPath || detected.projectPath;
580
665
 
581
- if (epicId) {
582
- 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}**`);
583
671
  }
584
672
 
585
673
  if (scanned.agentName) {
586
- parts.push(`**Coordinator:** ${scanned.agentName}`);
674
+ parts.push(`Coordinator: ${scanned.agentName}`);
675
+ }
676
+
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`);
587
691
  }
588
692
 
589
- parts.push(`**Project:** ${projectPath}`);
693
+ // Swarm state summary
694
+ parts.push(`## 🐝 Current Swarm State\n`);
695
+
696
+ if (epicId) {
697
+ parts.push(`**Epic:** ${epicId}${epicTitle ? ` - ${epicTitle}` : ""}`);
698
+ }
590
699
 
591
700
  // Show detailed subtask info from scanned state
592
701
  if (scanned.subtasks.size > 0) {
@@ -606,7 +715,7 @@ function buildDynamicSwarmStateFromScanned(
606
715
  detected.subtasks.blocked;
607
716
 
608
717
  if (total > 0) {
609
- parts.push(`**Subtasks:**`);
718
+ parts.push(`\n**Subtasks:**`);
610
719
  if (detected.subtasks.closed > 0)
611
720
  parts.push(` - ${detected.subtasks.closed} closed`);
612
721
  if (detected.subtasks.in_progress > 0)
@@ -618,29 +727,11 @@ function buildDynamicSwarmStateFromScanned(
618
727
  }
619
728
  }
620
729
 
730
+ parts.push(`\n**Project:** ${projectPath}`);
731
+
621
732
  // Show last action if available
622
733
  if (scanned.lastAction) {
623
- parts.push(`\n**Last Action:** \`${scanned.lastAction.tool}\``);
624
- }
625
-
626
- if (epicId) {
627
- parts.push(`\n## 🎯 YOU ARE THE COORDINATOR`);
628
- parts.push(``);
629
- parts.push(
630
- `**Primary role:** Orchestrate workers, review their output, unblock dependencies.`,
631
- );
632
- parts.push(`**Spawn workers** for implementation tasks - don't do them yourself.`);
633
- parts.push(``);
634
- parts.push(`**RESUME STEPS:**`);
635
- parts.push(
636
- `1. Check swarm status: \`swarm_status(epic_id="${epicId}", project_key="${projectPath}")\``,
637
- );
638
- parts.push(`2. Check inbox for worker messages: \`swarmmail_inbox(limit=5)\``);
639
- parts.push(
640
- `3. For in_progress subtasks: Review worker results with \`swarm_review\``,
641
- );
642
- parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
643
- parts.push(`5. For blocked subtasks: Investigate and unblock`);
734
+ parts.push(`**Last Action:** \`${scanned.lastAction.tool}\``);
644
735
  }
645
736
 
646
737
  return parts.join("\n");
@@ -931,6 +1022,12 @@ export function createCompactionHook(client?: OpencodeClient) {
931
1022
  output: { context: string[] },
932
1023
  ): Promise<void> => {
933
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
+ });
934
1031
 
935
1032
  getLog().info(
936
1033
  {
@@ -940,12 +1037,19 @@ export function createCompactionHook(client?: OpencodeClient) {
940
1037
  },
941
1038
  "compaction started",
942
1039
  );
1040
+
1041
+ recordPhaseStart(metrics, CompactionPhase.START);
943
1042
 
944
1043
  try {
1044
+ recordPhaseComplete(metrics, CompactionPhase.START);
1045
+
945
1046
  // Scan session messages for precise swarm state (if client available)
1047
+ recordPhaseStart(metrics, CompactionPhase.GATHER_SWARM_MAIL);
946
1048
  const scannedState = await scanSessionMessages(client, input.sessionID);
1049
+ recordPhaseComplete(metrics, CompactionPhase.GATHER_SWARM_MAIL);
947
1050
 
948
1051
  // Also run heuristic detection from hive/swarm-mail
1052
+ recordPhaseStart(metrics, CompactionPhase.DETECT);
949
1053
  const detection = await detectSwarm();
950
1054
 
951
1055
  // Boost confidence if we found swarm evidence in session messages
@@ -955,13 +1059,21 @@ export function createCompactionHook(client?: OpencodeClient) {
955
1059
  if (effectiveConfidence === "none" || effectiveConfidence === "low") {
956
1060
  effectiveConfidence = "medium";
957
1061
  detection.reasons.push("swarm tool calls found in session");
1062
+ recordPatternExtracted(metrics, "swarm_tool_calls", "Found swarm tool calls in session");
958
1063
  }
959
1064
  if (scannedState.subtasks.size > 0) {
960
1065
  effectiveConfidence = "high";
961
1066
  detection.reasons.push(`${scannedState.subtasks.size} subtasks spawned`);
1067
+ recordPatternExtracted(metrics, "subtasks", `${scannedState.subtasks.size} subtasks spawned`);
962
1068
  }
963
1069
  }
1070
+
1071
+ recordPhaseComplete(metrics, CompactionPhase.DETECT, {
1072
+ confidence: effectiveConfidence,
1073
+ detected: detection.detected || scannedState.epicId !== undefined,
1074
+ });
964
1075
 
1076
+ recordPhaseStart(metrics, CompactionPhase.INJECT);
965
1077
  if (
966
1078
  effectiveConfidence === "high" ||
967
1079
  effectiveConfidence === "medium"
@@ -988,6 +1100,11 @@ export function createCompactionHook(client?: OpencodeClient) {
988
1100
 
989
1101
  const contextContent = header + dynamicState + SWARM_COMPACTION_CONTEXT;
990
1102
  output.context.push(contextContent);
1103
+
1104
+ recordPhaseComplete(metrics, CompactionPhase.INJECT, {
1105
+ context_length: contextContent.length,
1106
+ context_type: "full",
1107
+ });
991
1108
 
992
1109
  getLog().info(
993
1110
  {
@@ -1007,6 +1124,11 @@ export function createCompactionHook(client?: OpencodeClient) {
1007
1124
  const header = `[Possible swarm: ${detection.reasons.join(", ")}]\n\n`;
1008
1125
  const contextContent = header + SWARM_DETECTION_FALLBACK;
1009
1126
  output.context.push(contextContent);
1127
+
1128
+ recordPhaseComplete(metrics, CompactionPhase.INJECT, {
1129
+ context_length: contextContent.length,
1130
+ context_type: "fallback",
1131
+ });
1010
1132
 
1011
1133
  getLog().info(
1012
1134
  {
@@ -1018,6 +1140,10 @@ export function createCompactionHook(client?: OpencodeClient) {
1018
1140
  "injected swarm context",
1019
1141
  );
1020
1142
  } else {
1143
+ recordPhaseComplete(metrics, CompactionPhase.INJECT, {
1144
+ context_type: "none",
1145
+ });
1146
+
1021
1147
  getLog().debug(
1022
1148
  {
1023
1149
  confidence: effectiveConfidence,
@@ -1028,7 +1154,10 @@ export function createCompactionHook(client?: OpencodeClient) {
1028
1154
  }
1029
1155
  // confidence === "none" - no injection, probably not a swarm
1030
1156
 
1157
+ recordPhaseStart(metrics, CompactionPhase.COMPLETE);
1031
1158
  const duration = Date.now() - startTime;
1159
+ const summary = getMetricsSummary(metrics);
1160
+
1032
1161
  getLog().info(
1033
1162
  {
1034
1163
  duration_ms: duration,
@@ -1036,11 +1165,30 @@ export function createCompactionHook(client?: OpencodeClient) {
1036
1165
  detected: detection.detected || scannedState.epicId !== undefined,
1037
1166
  confidence: effectiveConfidence,
1038
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
+ },
1039
1179
  },
1040
1180
  "compaction complete",
1041
1181
  );
1182
+
1183
+ recordPhaseComplete(metrics, CompactionPhase.COMPLETE);
1042
1184
  } catch (error) {
1043
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
+
1044
1192
  getLog().error(
1045
1193
  {
1046
1194
  duration_ms: duration,