opencode-swarm-plugin 0.37.0 → 0.39.1

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 (79) hide show
  1. package/.env +2 -0
  2. package/.hive/eval-results.json +26 -0
  3. package/.hive/issues.jsonl +20 -5
  4. package/.hive/memories.jsonl +35 -1
  5. package/.opencode/eval-history.jsonl +12 -0
  6. package/.turbo/turbo-build.log +4 -4
  7. package/.turbo/turbo-test.log +319 -319
  8. package/CHANGELOG.md +258 -0
  9. package/README.md +50 -0
  10. package/bin/swarm.test.ts +475 -0
  11. package/bin/swarm.ts +385 -208
  12. package/dist/compaction-hook.d.ts +1 -1
  13. package/dist/compaction-hook.d.ts.map +1 -1
  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 +81 -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 +59 -0
  25. package/dist/hive.d.ts.map +1 -1
  26. package/dist/index.d.ts +87 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +823 -131
  29. package/dist/plugin.js +655 -131
  30. package/dist/post-compaction-tracker.d.ts +133 -0
  31. package/dist/post-compaction-tracker.d.ts.map +1 -0
  32. package/dist/swarm-decompose.d.ts +30 -0
  33. package/dist/swarm-decompose.d.ts.map +1 -1
  34. package/dist/swarm-orchestrate.d.ts +23 -0
  35. package/dist/swarm-orchestrate.d.ts.map +1 -1
  36. package/dist/swarm-prompts.d.ts +25 -1
  37. package/dist/swarm-prompts.d.ts.map +1 -1
  38. package/dist/swarm.d.ts +19 -0
  39. package/dist/swarm.d.ts.map +1 -1
  40. package/evals/README.md +595 -94
  41. package/evals/compaction-prompt.eval.ts +149 -0
  42. package/evals/coordinator-behavior.eval.ts +8 -8
  43. package/evals/fixtures/compaction-prompt-cases.ts +305 -0
  44. package/evals/lib/compaction-loader.test.ts +248 -0
  45. package/evals/lib/compaction-loader.ts +320 -0
  46. package/evals/lib/data-loader.test.ts +345 -0
  47. package/evals/lib/data-loader.ts +107 -6
  48. package/evals/scorers/compaction-prompt-scorers.ts +145 -0
  49. package/evals/scorers/compaction-scorers.ts +13 -13
  50. package/evals/scorers/coordinator-discipline.evalite-test.ts +3 -2
  51. package/evals/scorers/coordinator-discipline.ts +13 -13
  52. package/examples/plugin-wrapper-template.ts +177 -8
  53. package/package.json +7 -2
  54. package/scripts/migrate-unknown-sessions.ts +349 -0
  55. package/src/compaction-capture.integration.test.ts +257 -0
  56. package/src/compaction-hook.test.ts +139 -2
  57. package/src/compaction-hook.ts +113 -2
  58. package/src/compaction-prompt-scorers.test.ts +299 -0
  59. package/src/compaction-prompt-scoring.ts +298 -0
  60. package/src/eval-capture.test.ts +422 -0
  61. package/src/eval-capture.ts +94 -2
  62. package/src/eval-gates.test.ts +306 -0
  63. package/src/eval-gates.ts +218 -0
  64. package/src/eval-history.test.ts +508 -0
  65. package/src/eval-history.ts +214 -0
  66. package/src/eval-learning.test.ts +378 -0
  67. package/src/eval-learning.ts +360 -0
  68. package/src/index.ts +61 -1
  69. package/src/post-compaction-tracker.test.ts +251 -0
  70. package/src/post-compaction-tracker.ts +237 -0
  71. package/src/swarm-decompose.test.ts +40 -47
  72. package/src/swarm-decompose.ts +2 -2
  73. package/src/swarm-orchestrate.test.ts +270 -7
  74. package/src/swarm-orchestrate.ts +100 -13
  75. package/src/swarm-prompts.test.ts +121 -0
  76. package/src/swarm-prompts.ts +297 -4
  77. package/src/swarm-research.integration.test.ts +157 -0
  78. package/src/swarm-review.ts +3 -3
  79. /package/evals/{evalite.config.ts → evalite.config.ts.bak} +0 -0
@@ -83,7 +83,8 @@ import {
83
83
  isReviewApproved,
84
84
  getReviewStatus,
85
85
  } from "./swarm-review";
86
- import { captureCoordinatorEvent } from "./eval-capture.js";
86
+ import { captureCoordinatorEvent, type EvalRecord } from "./eval-capture.js";
87
+ import { formatResearcherPrompt } from "./swarm-prompts";
87
88
 
88
89
  // ============================================================================
89
90
  // Helper Functions
@@ -1710,11 +1711,36 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
1710
1711
  },
1711
1712
  };
1712
1713
 
1714
+ // Capture subtask completion outcome for eval data
1715
+ try {
1716
+ const { captureSubtaskOutcome } = await import("./eval-capture.js");
1717
+ const durationMs = args.start_time ? Date.now() - args.start_time : 0;
1718
+
1719
+ // Determine epic ID: use parent_id if available, otherwise fall back to extracting from bead_id
1720
+ const evalEpicId = cell.parent_id || epicId;
1721
+
1722
+ captureSubtaskOutcome({
1723
+ epicId: evalEpicId,
1724
+ projectPath: args.project_key,
1725
+ beadId: args.bead_id,
1726
+ title: cell.title,
1727
+ plannedFiles: args.planned_files || [],
1728
+ actualFiles: args.files_touched || [],
1729
+ durationMs,
1730
+ errorCount: args.error_count || 0,
1731
+ retryCount: args.retry_count || 0,
1732
+ success: true,
1733
+ });
1734
+ } catch (error) {
1735
+ // Non-fatal - don't block completion if capture fails
1736
+ console.warn("[swarm_complete] Failed to capture subtask outcome:", error);
1737
+ }
1738
+
1713
1739
  // Capture subtask completion outcome
1714
1740
  try {
1715
1741
  const durationMs = args.start_time ? Date.now() - args.start_time : 0;
1716
1742
  captureCoordinatorEvent({
1717
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
1743
+ session_id: _ctx.sessionID || "unknown",
1718
1744
  epic_id: epicId,
1719
1745
  timestamp: new Date().toISOString(),
1720
1746
  event_type: "OUTCOME",
@@ -1823,7 +1849,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
1823
1849
  try {
1824
1850
  const durationMs = args.start_time ? Date.now() - args.start_time : 0;
1825
1851
  captureCoordinatorEvent({
1826
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
1852
+ session_id: _ctx.sessionID || "unknown",
1827
1853
  epic_id: epicId,
1828
1854
  timestamp: new Date().toISOString(),
1829
1855
  event_type: "OUTCOME",
@@ -1946,6 +1972,14 @@ export const swarm_record_outcome = tool({
1946
1972
  .string()
1947
1973
  .optional()
1948
1974
  .describe("Detailed failure context (error message, stack trace, etc.)"),
1975
+ project_path: tool.schema
1976
+ .string()
1977
+ .optional()
1978
+ .describe("Project path (for finalizing eval records when all subtasks complete)"),
1979
+ epic_id: tool.schema
1980
+ .string()
1981
+ .optional()
1982
+ .describe("Epic ID (for finalizing eval records when all subtasks complete)"),
1949
1983
  },
1950
1984
  async execute(args) {
1951
1985
  // Build outcome signals
@@ -1980,6 +2014,21 @@ export const swarm_record_outcome = tool({
1980
2014
  // Get error patterns from accumulator
1981
2015
  const errorStats = await globalErrorAccumulator.getErrorStats(args.bead_id);
1982
2016
 
2017
+ // Finalize eval record if project_path and epic_id provided
2018
+ let finalizedRecord: EvalRecord | null = null;
2019
+ if (args.project_path && args.epic_id) {
2020
+ try {
2021
+ const { finalizeEvalRecord } = await import("./eval-capture.js");
2022
+ finalizedRecord = finalizeEvalRecord({
2023
+ epicId: args.epic_id,
2024
+ projectPath: args.project_path,
2025
+ });
2026
+ } catch (error) {
2027
+ // Non-fatal - log and continue
2028
+ console.warn("[swarm_record_outcome] Failed to finalize eval record:", error);
2029
+ }
2030
+ }
2031
+
1983
2032
  // Generate feedback events for each criterion
1984
2033
  const criteriaToScore = args.criteria ?? [
1985
2034
  "type_safe",
@@ -2030,6 +2079,7 @@ export const swarm_record_outcome = tool({
2030
2079
  accumulated_errors: errorStats.total,
2031
2080
  unresolved_errors: errorStats.unresolved,
2032
2081
  },
2082
+ finalized_eval_record: finalizedRecord || undefined,
2033
2083
  note: "Feedback events should be stored for criterion weight calculation. Use learning.ts functions to apply weights.",
2034
2084
  },
2035
2085
  null,
@@ -2087,12 +2137,28 @@ export function extractTechStack(task: string): string[] {
2087
2137
  return Array.from(detected);
2088
2138
  }
2089
2139
 
2140
+ /**
2141
+ * Spawn instruction for a researcher worker
2142
+ */
2143
+ export interface ResearchSpawnInstruction {
2144
+ /** Unique ID for this research task */
2145
+ research_id: string;
2146
+ /** Technology being researched */
2147
+ tech: string;
2148
+ /** Full prompt for the researcher agent */
2149
+ prompt: string;
2150
+ /** Agent type for the Task tool */
2151
+ subagent_type: "swarm/researcher";
2152
+ }
2153
+
2090
2154
  /**
2091
2155
  * Research result from documentation discovery phase
2092
2156
  */
2093
2157
  export interface ResearchResult {
2094
2158
  /** Technologies identified and researched */
2095
2159
  tech_stack: string[];
2160
+ /** Spawn instructions for researcher workers */
2161
+ spawn_instructions: ResearchSpawnInstruction[];
2096
2162
  /** Summaries keyed by technology name */
2097
2163
  summaries: Record<string, string>;
2098
2164
  /** Semantic-memory IDs where research is stored */
@@ -2154,24 +2220,45 @@ export async function runResearchPhase(
2154
2220
  if (techStack.length === 0) {
2155
2221
  return {
2156
2222
  tech_stack: [],
2223
+ spawn_instructions: [],
2157
2224
  summaries: {},
2158
2225
  memory_ids: [],
2159
2226
  };
2160
2227
  }
2161
2228
 
2162
- // Step 2: For each technology, spawn a researcher
2163
- // TODO: Implement researcher spawning using swarm_spawn_researcher
2164
- // and Task tool. This requires coordination logic that will be
2165
- // added in a future iteration.
2166
-
2167
- // For now, return empty summaries (GREEN phase - make tests pass)
2168
- // The full implementation will spawn researchers in parallel and
2169
- // collect their findings.
2229
+ // Step 2: Generate spawn instructions for each technology
2230
+ // The coordinator will use these to spawn researcher workers via Task()
2231
+ const spawnInstructions: ResearchSpawnInstruction[] = [];
2232
+
2233
+ for (const tech of techStack) {
2234
+ // Generate unique research ID
2235
+ const researchId = `research-${tech}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
2236
+
2237
+ // Generate researcher prompt
2238
+ const prompt = formatResearcherPrompt({
2239
+ research_id: researchId,
2240
+ epic_id: "standalone-research", // No epic context for standalone research
2241
+ tech_stack: [tech], // Single tech per researcher
2242
+ project_path: projectPath,
2243
+ check_upgrades: options?.checkUpgrades ?? false,
2244
+ });
2245
+
2246
+ spawnInstructions.push({
2247
+ research_id: researchId,
2248
+ tech,
2249
+ prompt,
2250
+ subagent_type: "swarm/researcher",
2251
+ });
2252
+ }
2170
2253
 
2254
+ // Step 3: Return spawn instructions for coordinator
2255
+ // The coordinator will spawn Task() agents using these instructions
2256
+ // and collect results from swarm mail after completion
2171
2257
  return {
2172
2258
  tech_stack: techStack,
2173
- summaries: {},
2174
- memory_ids: [],
2259
+ spawn_instructions: spawnInstructions,
2260
+ summaries: {}, // Will be populated by coordinator after researchers complete
2261
+ memory_ids: [], // Will be populated by coordinator after researchers store in semantic-memory
2175
2262
  };
2176
2263
  }
2177
2264
 
@@ -9,8 +9,10 @@ import { describe, expect, test } from "bun:test";
9
9
  import {
10
10
  formatSubtaskPromptV2,
11
11
  formatResearcherPrompt,
12
+ formatCoordinatorPrompt,
12
13
  SUBTASK_PROMPT_V2,
13
14
  RESEARCHER_PROMPT,
15
+ COORDINATOR_PROMPT,
14
16
  } from "./swarm-prompts";
15
17
 
16
18
  describe("SUBTASK_PROMPT_V2", () => {
@@ -818,3 +820,122 @@ describe("swarm_spawn_retry tool", () => {
818
820
  expect(parsed.prompt).toMatch(/preserve.*working|fix.*while preserving/i);
819
821
  });
820
822
  });
823
+
824
+ describe("COORDINATOR_PROMPT", () => {
825
+ test("constant exists and is exported", () => {
826
+ expect(COORDINATOR_PROMPT).toBeDefined();
827
+ expect(typeof COORDINATOR_PROMPT).toBe("string");
828
+ expect(COORDINATOR_PROMPT.length).toBeGreaterThan(100);
829
+ });
830
+
831
+ test("contains all phase headers (0-8)", () => {
832
+ expect(COORDINATOR_PROMPT).toContain("Phase 0:");
833
+ expect(COORDINATOR_PROMPT).toContain("Phase 1:");
834
+ expect(COORDINATOR_PROMPT).toContain("Phase 2:");
835
+ expect(COORDINATOR_PROMPT).toContain("Phase 3:");
836
+ expect(COORDINATOR_PROMPT).toContain("Phase 4:");
837
+ expect(COORDINATOR_PROMPT).toContain("Phase 5:");
838
+ expect(COORDINATOR_PROMPT).toContain("Phase 6:");
839
+ expect(COORDINATOR_PROMPT).toContain("Phase 7:");
840
+ expect(COORDINATOR_PROMPT).toContain("Phase 8:");
841
+ });
842
+
843
+ test("contains Phase 1.5: Research Phase section", () => {
844
+ expect(COORDINATOR_PROMPT).toContain("Phase 1.5:");
845
+ expect(COORDINATOR_PROMPT).toMatch(/Phase 1\.5:.*Research/i);
846
+ });
847
+
848
+ test("Phase 1.5 documents swarm_spawn_researcher usage", () => {
849
+ // Extract Phase 1.5 section
850
+ const phase15Match = COORDINATOR_PROMPT.match(/Phase 1\.5:[\s\S]*?Phase 2:/);
851
+ expect(phase15Match).not.toBeNull();
852
+ if (!phase15Match) return;
853
+ const phase15Content = phase15Match[0];
854
+
855
+ expect(phase15Content).toContain("swarm_spawn_researcher");
856
+ expect(phase15Content).toContain("Task(subagent_type=\"swarm/researcher\"");
857
+ });
858
+
859
+ test("has section explicitly forbidding direct research tool calls", () => {
860
+ expect(COORDINATOR_PROMPT).toMatch(/NEVER.*direct|forbidden.*tools|do not call directly/i);
861
+ });
862
+
863
+ test("forbidden tools section lists all prohibited tools", () => {
864
+ const forbiddenTools = [
865
+ "repo-crawl_",
866
+ "repo-autopsy_",
867
+ "webfetch",
868
+ "fetch_fetch",
869
+ "context7_",
870
+ "pdf-brain_search",
871
+ "pdf-brain_read"
872
+ ];
873
+
874
+ for (const tool of forbiddenTools) {
875
+ expect(COORDINATOR_PROMPT).toContain(tool);
876
+ }
877
+ });
878
+
879
+ test("forbidden tools section explains to use swarm_spawn_researcher instead", () => {
880
+ // Find the forbidden tools section
881
+ const forbiddenMatch = COORDINATOR_PROMPT.match(/(FORBIDDEN.*for coordinators|NEVER.*FETCH.*DIRECTLY)[\s\S]{0,500}swarm_spawn_researcher/i);
882
+ expect(forbiddenMatch).not.toBeNull();
883
+ });
884
+
885
+ test("contains coordinator role boundaries section", () => {
886
+ expect(COORDINATOR_PROMPT).toContain("Coordinator Role Boundaries");
887
+ expect(COORDINATOR_PROMPT).toMatch(/COORDINATORS NEVER.*EXECUTE.*WORK/i);
888
+ });
889
+
890
+ test("contains MANDATORY review loop section", () => {
891
+ expect(COORDINATOR_PROMPT).toContain("MANDATORY Review Loop");
892
+ expect(COORDINATOR_PROMPT).toContain("swarm_review");
893
+ expect(COORDINATOR_PROMPT).toContain("swarm_review_feedback");
894
+ });
895
+
896
+ test("Phase 1.5 positioned between Phase 1 (Initialize) and Phase 2 (Knowledge)", () => {
897
+ const phase1Pos = COORDINATOR_PROMPT.indexOf("Phase 1:");
898
+ const phase15Pos = COORDINATOR_PROMPT.indexOf("Phase 1.5:");
899
+ const phase2Pos = COORDINATOR_PROMPT.indexOf("Phase 2:");
900
+
901
+ expect(phase15Pos).toBeGreaterThan(phase1Pos);
902
+ expect(phase15Pos).toBeLessThan(phase2Pos);
903
+ });
904
+ });
905
+
906
+ describe("formatCoordinatorPrompt", () => {
907
+ test("function exists and returns string", () => {
908
+ expect(formatCoordinatorPrompt).toBeDefined();
909
+ const result = formatCoordinatorPrompt({ task: "test task", projectPath: "/test" });
910
+ expect(typeof result).toBe("string");
911
+ });
912
+
913
+ test("substitutes {task} placeholder", () => {
914
+ const result = formatCoordinatorPrompt({
915
+ task: "Implement auth",
916
+ projectPath: "/test"
917
+ });
918
+ expect(result).toContain("Implement auth");
919
+ });
920
+
921
+ test("substitutes {project_path} placeholder", () => {
922
+ const result = formatCoordinatorPrompt({
923
+ task: "test",
924
+ projectPath: "/Users/joel/my-project"
925
+ });
926
+ expect(result).toContain("/Users/joel/my-project");
927
+ });
928
+
929
+ test("returns complete prompt with all phases", () => {
930
+ const result = formatCoordinatorPrompt({
931
+ task: "test",
932
+ projectPath: "/test"
933
+ });
934
+
935
+ // Should contain all phase headers
936
+ for (let i = 0; i <= 8; i++) {
937
+ expect(result).toContain(`Phase ${i}:`);
938
+ }
939
+ expect(result).toContain("Phase 1.5:");
940
+ });
941
+ });
@@ -576,8 +576,289 @@ Other cell operations:
576
576
  Begin now.`;
577
577
 
578
578
  /**
579
- * Researcher prompt template for documentation discovery
580
- *
579
+ * Coordinator Agent Prompt Template
580
+ *
581
+ * Used by the /swarm command to instruct coordinators on their role.
582
+ * Coordinators NEVER execute work directly - they clarify, decompose, spawn workers, and review.
583
+ *
584
+ * Key sections:
585
+ * - Role boundaries (what coordinators NEVER do)
586
+ * - Phase 1.5: Research Phase (spawn researchers, DON'T fetch docs directly)
587
+ * - Forbidden tools (repo-crawl, webfetch, context7, pdf-brain_search)
588
+ * - MANDATORY review loop after each worker completes
589
+ *
590
+ * Placeholders:
591
+ * - {task} - The task description from user
592
+ * - {project_path} - Absolute path to project root
593
+ */
594
+ export const COORDINATOR_PROMPT = `You are a swarm coordinator. Your job is to clarify the task, decompose it into cells, and spawn parallel agents.
595
+
596
+ ## Task
597
+
598
+ {task}
599
+
600
+ ## CRITICAL: Coordinator Role Boundaries
601
+
602
+ **⚠️ COORDINATORS NEVER EXECUTE WORK DIRECTLY**
603
+
604
+ Your role is **ONLY** to:
605
+ 1. **Clarify** - Ask questions to understand scope
606
+ 2. **Decompose** - Break into subtasks with clear boundaries
607
+ 3. **Spawn** - Create worker agents for ALL subtasks
608
+ 4. **Monitor** - Check progress, unblock, mediate conflicts
609
+ 5. **Verify** - Confirm completion, run final checks
610
+
611
+ **YOU DO NOT:**
612
+ - Read implementation files (only metadata/structure for planning)
613
+ - Edit code directly
614
+ - Run tests yourself (workers run tests)
615
+ - Implement features
616
+ - Fix bugs inline
617
+ - Make "quick fixes" yourself
618
+
619
+ **ALWAYS spawn workers, even for sequential tasks.** Sequential just means spawn them in order and wait for each to complete before spawning the next.
620
+
621
+ ### Why This Matters
622
+
623
+ | Coordinator Work | Worker Work | Consequence of Mixing |
624
+ |-----------------|-------------|----------------------|
625
+ | Sonnet context ($$$) | Disposable context | Expensive context waste |
626
+ | Long-lived state | Task-scoped state | Context exhaustion |
627
+ | Orchestration concerns | Implementation concerns | Mixed concerns |
628
+ | No checkpoints | Checkpoints enabled | No recovery |
629
+ | No learning signals | Outcomes tracked | No improvement |
630
+
631
+ ## CRITICAL: NEVER Fetch Documentation Directly
632
+
633
+ **⚠️ COORDINATORS DO NOT CALL RESEARCH TOOLS DIRECTLY**
634
+
635
+ The following tools are **FORBIDDEN** for coordinators to call:
636
+
637
+ - \`repo-crawl_file\`, \`repo-crawl_readme\`, \`repo-crawl_search\`, \`repo-crawl_structure\`, \`repo-crawl_tree\`
638
+ - \`repo-autopsy_*\` (all variants)
639
+ - \`webfetch\`, \`fetch_fetch\`
640
+ - \`context7_resolve-library-id\`, \`context7_get-library-docs\`
641
+ - \`pdf-brain_search\`, \`pdf-brain_read\`
642
+
643
+ **WHY?** These tools dump massive context that exhausts your expensive Sonnet context. Your job is orchestration, not research.
644
+
645
+ **INSTEAD:** Use \`swarm_spawn_researcher\` (see Phase 1.5 below) to spawn a researcher worker who:
646
+ - Fetches documentation in disposable context
647
+ - Stores full details in semantic-memory
648
+ - Returns a condensed summary for shared_context
649
+
650
+ ## Workflow
651
+
652
+ ### Phase 0: Socratic Planning (INTERACTIVE - unless --fast)
653
+
654
+ **Before decomposing, clarify the task with the user.**
655
+
656
+ Check for flags in the task:
657
+ - \`--fast\` → Skip questions, use reasonable defaults
658
+ - \`--auto\` → Zero interaction, heuristic decisions
659
+ - \`--confirm-only\` → Show plan, get yes/no only
660
+
661
+ **Default (no flags): Full Socratic Mode**
662
+
663
+ 1. **Analyze task for ambiguity:**
664
+ - Scope unclear? (what's included/excluded)
665
+ - Strategy unclear? (file-based vs feature-based)
666
+ - Dependencies unclear? (what needs to exist first)
667
+ - Success criteria unclear? (how do we know it's done)
668
+
669
+ 2. **If clarification needed, ask ONE question at a time:**
670
+ \`\`\`
671
+ The task "<task>" needs clarification before I can decompose it.
672
+
673
+ **Question:** <specific question>
674
+
675
+ Options:
676
+ a) <option 1> - <tradeoff>
677
+ b) <option 2> - <tradeoff>
678
+ c) <option 3> - <tradeoff>
679
+
680
+ I'd recommend (b) because <reason>. Which approach?
681
+ \`\`\`
682
+
683
+ 3. **Wait for user response before proceeding**
684
+
685
+ 4. **Iterate if needed** (max 2-3 questions)
686
+
687
+ **Rules:**
688
+ - ONE question at a time - don't overwhelm
689
+ - Offer concrete options - not open-ended
690
+ - Lead with recommendation - save cognitive load
691
+ - Wait for answer - don't assume
692
+
693
+ ### Phase 1: Initialize
694
+ \`swarmmail_init(project_path="{project_path}", task_description="Swarm: {task}")\`
695
+
696
+ ### Phase 1.5: Research Phase (FOR COMPLEX TASKS)
697
+
698
+ **⚠️ If the task requires understanding unfamiliar technologies, APIs, or libraries, spawn a researcher FIRST.**
699
+
700
+ **DO NOT call documentation tools directly.** Instead:
701
+
702
+ \`\`\`
703
+ // 1. Spawn researcher with explicit tech stack
704
+ swarm_spawn_researcher(
705
+ research_id="research-nextjs-cache-components",
706
+ epic_id="<epic-id>",
707
+ tech_stack=["Next.js 16 Cache Components", "React Server Components"],
708
+ project_path="{project_path}"
709
+ )
710
+
711
+ // 2. Spawn researcher as Task subagent
712
+ const researchFindings = await Task(subagent_type="swarm/researcher", prompt="<from above>")
713
+
714
+ // 3. Researcher returns condensed summary
715
+ // Use this summary in shared_context for workers
716
+ \`\`\`
717
+
718
+ **When to spawn a researcher:**
719
+ - Task involves unfamiliar framework versions (e.g., Next.js 16 vs 14)
720
+ - Need to compare installed vs latest library APIs
721
+ - Working with experimental/preview features
722
+ - Need architectural guidance from documentation
723
+
724
+ **When NOT to spawn a researcher:**
725
+ - Using well-known stable APIs (React hooks, Express middleware)
726
+ - Task is purely refactoring existing code
727
+ - You already have relevant findings from semantic-memory or CASS
728
+
729
+ **Researcher output:**
730
+ - Full findings stored in semantic-memory (searchable by future agents)
731
+ - Condensed 3-5 bullet summary returned for shared_context
732
+
733
+ ### Phase 2: Knowledge Gathering (MANDATORY)
734
+
735
+ **Before decomposing, query ALL knowledge sources:**
736
+
737
+ \`\`\`
738
+ semantic-memory_find(query="<task keywords>", limit=5) # Past learnings
739
+ cass_search(query="<task description>", limit=5) # Similar past tasks
740
+ skills_list() # Available skills
741
+ \`\`\`
742
+
743
+ Synthesize findings into shared_context for workers.
744
+
745
+ ### Phase 3: Decompose
746
+ \`\`\`
747
+ swarm_select_strategy(task="<task>")
748
+ swarm_plan_prompt(task="<task>", context="<synthesized knowledge>")
749
+ swarm_validate_decomposition(response="<CellTree JSON>")
750
+ \`\`\`
751
+
752
+ ### Phase 4: Create Cells
753
+ \`hive_create_epic(epic_title="<task>", subtasks=[...])\`
754
+
755
+ ### Phase 5: DO NOT Reserve Files
756
+
757
+ > **⚠️ Coordinator NEVER reserves files.** Workers reserve their own files.
758
+ > If coordinator reserves, workers get blocked and swarm stalls.
759
+
760
+ ### Phase 6: Spawn Workers for ALL Subtasks (MANDATORY)
761
+
762
+ > **⚠️ ALWAYS spawn workers, even for sequential tasks.**
763
+ > - Parallel tasks: Spawn ALL in a single message
764
+ > - Sequential tasks: Spawn one, wait for completion, spawn next
765
+
766
+ **For parallel work:**
767
+ \`\`\`
768
+ // Single message with multiple Task calls
769
+ swarm_spawn_subtask(bead_id_1, epic_id, title_1, files_1, shared_context, project_path="{project_path}")
770
+ Task(subagent_type="swarm/worker", prompt="<from above>")
771
+ swarm_spawn_subtask(bead_id_2, epic_id, title_2, files_2, shared_context, project_path="{project_path}")
772
+ Task(subagent_type="swarm/worker", prompt="<from above>")
773
+ \`\`\`
774
+
775
+ **For sequential work:**
776
+ \`\`\`
777
+ // Spawn worker 1, wait for completion
778
+ swarm_spawn_subtask(bead_id_1, ...)
779
+ const result1 = await Task(subagent_type="swarm/worker", prompt="<from above>")
780
+
781
+ // THEN spawn worker 2 with context from worker 1
782
+ swarm_spawn_subtask(bead_id_2, ..., shared_context="Worker 1 completed: " + result1)
783
+ const result2 = await Task(subagent_type="swarm/worker", prompt="<from above>")
784
+ \`\`\`
785
+
786
+ **NEVER do the work yourself.** Even if it seems faster, spawn a worker.
787
+
788
+ **IMPORTANT:** Pass \`project_path\` to \`swarm_spawn_subtask\` so workers can call \`swarmmail_init\`.
789
+
790
+ ### Phase 7: MANDATORY Review Loop (NON-NEGOTIABLE)
791
+
792
+ **⚠️ AFTER EVERY Task() RETURNS, YOU MUST:**
793
+
794
+ 1. **CHECK INBOX** - Worker may have sent messages
795
+ \`swarmmail_inbox()\`
796
+ \`swarmmail_read_message(message_id=N)\`
797
+
798
+ 2. **REVIEW WORK** - Generate review with diff
799
+ \`swarm_review(project_key, epic_id, task_id, files_touched)\`
800
+
801
+ 3. **EVALUATE** - Does it meet epic goals?
802
+ - Fulfills subtask requirements?
803
+ - Serves overall epic goal?
804
+ - Enables downstream tasks?
805
+ - Type safety, no obvious bugs?
806
+
807
+ 4. **SEND FEEDBACK** - Approve or request changes
808
+ \`swarm_review_feedback(project_key, task_id, worker_id, status, issues)\`
809
+
810
+ **If approved:**
811
+ - Close cell, spawn next worker
812
+
813
+ **If needs_changes:**
814
+ - \`swarm_review_feedback\` returns \`retry_context\` (NOT sends message - worker is dead)
815
+ - Generate retry prompt: \`swarm_spawn_retry(retry_context)\`
816
+ - Spawn NEW worker with Task() using retry prompt
817
+ - Max 3 attempts before marking task blocked
818
+
819
+ **If 3 failures:**
820
+ - Mark task blocked, escalate to human
821
+
822
+ 5. **ONLY THEN** - Spawn next worker or complete
823
+
824
+ **DO NOT skip this. DO NOT batch reviews. Review EACH worker IMMEDIATELY after return.**
825
+
826
+ **Intervene if:**
827
+ - Worker blocked >5min → unblock or reassign
828
+ - File conflicts → mediate between workers
829
+ - Scope creep → approve or reject expansion
830
+ - Review fails 3x → mark task blocked, escalate to human
831
+
832
+ ### Phase 8: Complete
833
+ \`\`\`
834
+ # After all workers complete and reviews pass:
835
+ hive_sync() # Sync all cells to git
836
+ # Coordinator does NOT call swarm_complete - workers do that
837
+ \`\`\`
838
+
839
+ ## Strategy Reference
840
+
841
+ | Strategy | Best For | Keywords |
842
+ | -------------- | ------------------------ | -------------------------------------- |
843
+ | file-based | Refactoring, migrations | refactor, migrate, rename, update all |
844
+ | feature-based | New features | add, implement, build, create, feature |
845
+ | risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
846
+ | research-based | Investigation, discovery | research, investigate, explore, learn |
847
+
848
+ ## Flag Reference
849
+
850
+ | Flag | Effect |
851
+ |------|--------|
852
+ | \`--fast\` | Skip Socratic questions, use defaults |
853
+ | \`--auto\` | Zero interaction, heuristic decisions |
854
+ | \`--confirm-only\` | Show plan, get yes/no only |
855
+
856
+ Begin with Phase 0 (Socratic Planning) unless \`--fast\` or \`--auto\` flag is present.
857
+ `;
858
+
859
+ /**
860
+ * Researcher Agent Prompt Template
861
+ *
581
862
  * Spawned BEFORE decomposition to gather technology documentation.
582
863
  * Researchers receive an EXPLICIT list of technologies to research from the coordinator.
583
864
  * They dynamically discover WHAT TOOLS are available to fetch docs.
@@ -844,6 +1125,18 @@ export function formatResearcherPrompt(params: {
844
1125
  .replace("{check_upgrades}", upgradesMode);
845
1126
  }
846
1127
 
1128
+ /**
1129
+ * Format the coordinator prompt with task and project path substitution
1130
+ */
1131
+ export function formatCoordinatorPrompt(params: {
1132
+ task: string;
1133
+ projectPath: string;
1134
+ }): string {
1135
+ return COORDINATOR_PROMPT
1136
+ .replace(/{task}/g, params.task)
1137
+ .replace(/{project_path}/g, params.projectPath);
1138
+ }
1139
+
847
1140
  /**
848
1141
  * Format the V2 subtask prompt for a specific agent
849
1142
  */
@@ -1065,7 +1358,7 @@ export const swarm_spawn_subtask = tool({
1065
1358
  .optional()
1066
1359
  .describe("Optional explicit model override (auto-selected if not provided)"),
1067
1360
  },
1068
- async execute(args) {
1361
+ async execute(args, _ctx) {
1069
1362
  const prompt = formatSubtaskPromptV2({
1070
1363
  bead_id: args.bead_id,
1071
1364
  epic_id: args.epic_id,
@@ -1111,7 +1404,7 @@ export const swarm_spawn_subtask = tool({
1111
1404
  // Capture worker spawn decision
1112
1405
  try {
1113
1406
  captureCoordinatorEvent({
1114
- session_id: process.env.OPENCODE_SESSION_ID || "unknown",
1407
+ session_id: _ctx.sessionID || "unknown",
1115
1408
  epic_id: args.epic_id,
1116
1409
  timestamp: new Date().toISOString(),
1117
1410
  event_type: "DECISION",