opencode-swarm-plugin 0.4.0 → 0.6.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.
package/src/swarm.ts CHANGED
@@ -28,11 +28,13 @@ import {
28
28
  import { mcpCall } from "./agent-mail";
29
29
  import {
30
30
  OutcomeSignalsSchema,
31
+ DecompositionStrategySchema,
31
32
  scoreImplicitFeedback,
32
33
  outcomeToFeedback,
33
34
  type OutcomeSignals,
34
35
  type ScoredOutcome,
35
36
  type FeedbackEvent,
37
+ type DecompositionStrategy as LearningDecompositionStrategy,
36
38
  DEFAULT_LEARNING_CONFIG,
37
39
  } from "./learning";
38
40
  import {
@@ -193,6 +195,244 @@ export function detectInstructionConflicts(
193
195
  return conflicts;
194
196
  }
195
197
 
198
+ // ============================================================================
199
+ // Strategy Definitions
200
+ // ============================================================================
201
+
202
+ /**
203
+ * Decomposition strategy types
204
+ */
205
+ export type DecompositionStrategy =
206
+ | "file-based"
207
+ | "feature-based"
208
+ | "risk-based"
209
+ | "auto";
210
+
211
+ /**
212
+ * Strategy definition with keywords, guidelines, and anti-patterns
213
+ */
214
+ export interface StrategyDefinition {
215
+ name: DecompositionStrategy;
216
+ description: string;
217
+ keywords: string[];
218
+ guidelines: string[];
219
+ antiPatterns: string[];
220
+ examples: string[];
221
+ }
222
+
223
+ /**
224
+ * Strategy definitions for task decomposition
225
+ */
226
+ export const STRATEGIES: Record<
227
+ Exclude<DecompositionStrategy, "auto">,
228
+ StrategyDefinition
229
+ > = {
230
+ "file-based": {
231
+ name: "file-based",
232
+ description:
233
+ "Group by file type or directory. Best for refactoring, migrations, and pattern changes across codebase.",
234
+ keywords: [
235
+ "refactor",
236
+ "migrate",
237
+ "update all",
238
+ "rename",
239
+ "replace",
240
+ "convert",
241
+ "upgrade",
242
+ "deprecate",
243
+ "remove",
244
+ "cleanup",
245
+ "lint",
246
+ "format",
247
+ ],
248
+ guidelines: [
249
+ "Group files by directory or type (e.g., all components, all tests)",
250
+ "Minimize cross-directory dependencies within a subtask",
251
+ "Handle shared types/utilities first if they change",
252
+ "Each subtask should be a complete transformation of its file set",
253
+ "Consider import/export relationships when grouping",
254
+ ],
255
+ antiPatterns: [
256
+ "Don't split tightly coupled files across subtasks",
257
+ "Don't group files that have no relationship",
258
+ "Don't forget to update imports when moving/renaming",
259
+ ],
260
+ examples: [
261
+ "Migrate all components to new API → split by component directory",
262
+ "Rename userId to accountId → split by module (types first, then consumers)",
263
+ "Update all tests to use new matcher → split by test directory",
264
+ ],
265
+ },
266
+ "feature-based": {
267
+ name: "feature-based",
268
+ description:
269
+ "Vertical slices with UI + API + data. Best for new features and adding functionality.",
270
+ keywords: [
271
+ "add",
272
+ "implement",
273
+ "build",
274
+ "create",
275
+ "feature",
276
+ "new",
277
+ "integrate",
278
+ "connect",
279
+ "enable",
280
+ "support",
281
+ ],
282
+ guidelines: [
283
+ "Each subtask is a complete vertical slice (UI + logic + data)",
284
+ "Start with data layer/types, then logic, then UI",
285
+ "Keep related components together (form + validation + submission)",
286
+ "Separate concerns that can be developed independently",
287
+ "Consider user-facing features as natural boundaries",
288
+ ],
289
+ antiPatterns: [
290
+ "Don't split a single feature across multiple subtasks",
291
+ "Don't create subtasks that can't be tested independently",
292
+ "Don't forget integration points between features",
293
+ ],
294
+ examples: [
295
+ "Add user auth → [OAuth setup, Session management, Protected routes]",
296
+ "Build dashboard → [Data fetching, Chart components, Layout/navigation]",
297
+ "Add search → [Search API, Search UI, Results display]",
298
+ ],
299
+ },
300
+ "risk-based": {
301
+ name: "risk-based",
302
+ description:
303
+ "Isolate high-risk changes, add tests first. Best for bug fixes, security issues, and critical changes.",
304
+ keywords: [
305
+ "fix",
306
+ "bug",
307
+ "security",
308
+ "vulnerability",
309
+ "critical",
310
+ "urgent",
311
+ "hotfix",
312
+ "patch",
313
+ "audit",
314
+ "review",
315
+ "investigate",
316
+ ],
317
+ guidelines: [
318
+ "Write tests FIRST to capture expected behavior",
319
+ "Isolate the risky change to minimize blast radius",
320
+ "Add monitoring/logging around the change",
321
+ "Create rollback plan as part of the task",
322
+ "Audit similar code for the same issue",
323
+ ],
324
+ antiPatterns: [
325
+ "Don't make multiple risky changes in one subtask",
326
+ "Don't skip tests for 'simple' fixes",
327
+ "Don't forget to check for similar issues elsewhere",
328
+ ],
329
+ examples: [
330
+ "Fix auth bypass → [Add regression test, Fix vulnerability, Audit similar endpoints]",
331
+ "Fix race condition → [Add test reproducing issue, Implement fix, Add concurrency tests]",
332
+ "Security audit → [Scan for vulnerabilities, Fix critical issues, Document remaining risks]",
333
+ ],
334
+ },
335
+ };
336
+
337
+ /**
338
+ * Analyze task description and select best decomposition strategy
339
+ *
340
+ * @param task - Task description
341
+ * @returns Selected strategy with reasoning
342
+ */
343
+ export function selectStrategy(task: string): {
344
+ strategy: Exclude<DecompositionStrategy, "auto">;
345
+ confidence: number;
346
+ reasoning: string;
347
+ alternatives: Array<{
348
+ strategy: Exclude<DecompositionStrategy, "auto">;
349
+ score: number;
350
+ }>;
351
+ } {
352
+ const taskLower = task.toLowerCase();
353
+
354
+ // Score each strategy based on keyword matches
355
+ const scores: Record<Exclude<DecompositionStrategy, "auto">, number> = {
356
+ "file-based": 0,
357
+ "feature-based": 0,
358
+ "risk-based": 0,
359
+ };
360
+
361
+ for (const [strategyName, definition] of Object.entries(STRATEGIES)) {
362
+ const name = strategyName as Exclude<DecompositionStrategy, "auto">;
363
+ for (const keyword of definition.keywords) {
364
+ if (taskLower.includes(keyword)) {
365
+ scores[name] += 1;
366
+ }
367
+ }
368
+ }
369
+
370
+ // Find the winner
371
+ const entries = Object.entries(scores) as Array<
372
+ [Exclude<DecompositionStrategy, "auto">, number]
373
+ >;
374
+ entries.sort((a, b) => b[1] - a[1]);
375
+
376
+ const [winner, winnerScore] = entries[0];
377
+ const [runnerUp, runnerUpScore] = entries[1] || [null, 0];
378
+
379
+ // Calculate confidence based on margin
380
+ const totalScore = entries.reduce((sum, [, score]) => sum + score, 0);
381
+ const confidence =
382
+ totalScore > 0
383
+ ? Math.min(0.95, 0.5 + (winnerScore - runnerUpScore) / totalScore)
384
+ : 0.5; // Default to 50% if no keywords matched
385
+
386
+ // Build reasoning
387
+ let reasoning: string;
388
+ if (winnerScore === 0) {
389
+ reasoning = `No strong keyword signals. Defaulting to feature-based as it's most versatile.`;
390
+ } else {
391
+ const matchedKeywords = STRATEGIES[winner].keywords.filter((k) =>
392
+ taskLower.includes(k),
393
+ );
394
+ reasoning = `Matched keywords: ${matchedKeywords.join(", ")}. ${STRATEGIES[winner].description}`;
395
+ }
396
+
397
+ // If no keywords matched, default to feature-based
398
+ const finalStrategy = winnerScore === 0 ? "feature-based" : winner;
399
+
400
+ return {
401
+ strategy: finalStrategy,
402
+ confidence,
403
+ reasoning,
404
+ alternatives: entries
405
+ .filter(([s]) => s !== finalStrategy)
406
+ .map(([strategy, score]) => ({ strategy, score })),
407
+ };
408
+ }
409
+
410
+ /**
411
+ * Format strategy-specific guidelines for the decomposition prompt
412
+ */
413
+ export function formatStrategyGuidelines(
414
+ strategy: Exclude<DecompositionStrategy, "auto">,
415
+ ): string {
416
+ const def = STRATEGIES[strategy];
417
+
418
+ const guidelines = def.guidelines.map((g) => `- ${g}`).join("\n");
419
+ const antiPatterns = def.antiPatterns.map((a) => `- ${a}`).join("\n");
420
+ const examples = def.examples.map((e) => `- ${e}`).join("\n");
421
+
422
+ return `## Strategy: ${strategy}
423
+
424
+ ${def.description}
425
+
426
+ ### Guidelines
427
+ ${guidelines}
428
+
429
+ ### Anti-Patterns (Avoid These)
430
+ ${antiPatterns}
431
+
432
+ ### Examples
433
+ ${examples}`;
434
+ }
435
+
196
436
  // ============================================================================
197
437
  // Prompt Templates
198
438
  // ============================================================================
@@ -360,72 +600,65 @@ Before writing code:
360
600
  Begin work on your subtask now.`;
361
601
 
362
602
  /**
363
- * Simplified subtask prompt for Task subagents (V2 - coordinator-centric)
603
+ * Streamlined subtask prompt (V2) - still uses Agent Mail and beads
364
604
  *
365
- * This prompt is designed for agents that DON'T have access to Agent Mail or beads tools.
366
- * The coordinator handles all coordination - subagents just do the work and return results.
367
- *
368
- * Key differences from V1:
369
- * - No Agent Mail instructions (subagents can't use it)
370
- * - No beads instructions (subagents can't use it)
371
- * - Expects structured JSON response for coordinator to process
605
+ * This is a cleaner version of SUBTASK_PROMPT that's easier to parse.
606
+ * Agents MUST use Agent Mail for communication and beads for tracking.
372
607
  */
373
- export const SUBTASK_PROMPT_V2 = `You are working on a subtask as part of a larger project.
608
+ export const SUBTASK_PROMPT_V2 = `You are a swarm agent working on: **{subtask_title}**
374
609
 
375
- ## Your Task
376
- **Title**: {subtask_title}
610
+ ## Identity
611
+ - **Bead ID**: {bead_id}
612
+ - **Epic ID**: {epic_id}
377
613
 
614
+ ## Task
378
615
  {subtask_description}
379
616
 
380
- ## Files to Modify
617
+ ## Files (exclusive reservation)
381
618
  {file_list}
382
619
 
383
- **IMPORTANT**: Only modify the files listed above. Do not create new files unless absolutely necessary for the task.
620
+ Only modify these files. Need others? Message the coordinator.
384
621
 
385
622
  ## Context
386
623
  {shared_context}
387
624
 
388
- ## Instructions
625
+ ## MANDATORY: Use These Tools
389
626
 
390
- 1. **Read first** - Understand the current state of the files before making changes
391
- 2. **Plan your approach** - Think through what changes are needed
392
- 3. **Make the changes** - Implement the required functionality
393
- 4. **Verify** - Check that your changes work (run tests/typecheck if applicable)
627
+ ### Agent Mail - communicate with the swarm
628
+ \`\`\`typescript
629
+ // Report progress, ask questions, announce blockers
630
+ agentmail_send({
631
+ to: ["coordinator"],
632
+ subject: "Progress update",
633
+ body: "What you did or need",
634
+ thread_id: "{epic_id}"
635
+ })
636
+ \`\`\`
394
637
 
395
- ## When Complete
638
+ ### Beads - track your work
639
+ - **Blocked?** \`beads_update({ id: "{bead_id}", status: "blocked" })\`
640
+ - **Found bug?** \`beads_create({ title: "Bug description", type: "bug" })\`
641
+ - **Done?** \`swarm_complete({ bead_id: "{bead_id}", summary: "What you did", files_touched: [...] })\`
396
642
 
397
- After finishing your work, provide a summary in this format:
643
+ ## Workflow
398
644
 
399
- \`\`\`json
400
- {
401
- "success": true,
402
- "summary": "Brief description of what you accomplished",
403
- "files_modified": ["list", "of", "files", "you", "changed"],
404
- "files_created": ["any", "new", "files"],
405
- "issues_found": ["any problems or concerns discovered"],
406
- "tests_passed": true,
407
- "notes": "Any additional context for the coordinator"
408
- }
409
- \`\`\`
645
+ 1. **Read** the files first
646
+ 2. **Plan** your approach (message coordinator if complex)
647
+ 3. **Implement** the changes
648
+ 4. **Verify** (typecheck, tests)
649
+ 5. **Report** progress via Agent Mail
650
+ 6. **Complete** with swarm_complete when done
410
651
 
411
- If you encounter a blocker you cannot resolve, return:
412
-
413
- \`\`\`json
414
- {
415
- "success": false,
416
- "summary": "What you attempted",
417
- "blocker": "Description of what's blocking you",
418
- "files_modified": [],
419
- "suggestions": ["possible", "solutions"]
420
- }
421
- \`\`\`
652
+ **Never work silently.** Communicate progress and blockers immediately.
422
653
 
423
- Begin work now.`;
654
+ Begin now.`;
424
655
 
425
656
  /**
426
657
  * Format the V2 subtask prompt for a specific agent
427
658
  */
428
659
  export function formatSubtaskPromptV2(params: {
660
+ bead_id: string;
661
+ epic_id: string;
429
662
  subtask_title: string;
430
663
  subtask_description: string;
431
664
  files: string[];
@@ -434,15 +667,17 @@ export function formatSubtaskPromptV2(params: {
434
667
  const fileList =
435
668
  params.files.length > 0
436
669
  ? params.files.map((f) => `- \`${f}\``).join("\n")
437
- : "(no specific files assigned - use your judgment)";
670
+ : "(no specific files - use judgment)";
438
671
 
439
- return SUBTASK_PROMPT_V2.replace("{subtask_title}", params.subtask_title)
672
+ return SUBTASK_PROMPT_V2.replace(/{bead_id}/g, params.bead_id)
673
+ .replace(/{epic_id}/g, params.epic_id)
674
+ .replace("{subtask_title}", params.subtask_title)
440
675
  .replace(
441
676
  "{subtask_description}",
442
677
  params.subtask_description || "(see title)",
443
678
  )
444
679
  .replace("{file_list}", fileList)
445
- .replace("{shared_context}", params.shared_context || "(none provided)");
680
+ .replace("{shared_context}", params.shared_context || "(none)");
446
681
  }
447
682
 
448
683
  /**
@@ -749,6 +984,227 @@ function formatCassHistoryForPrompt(history: CassSearchResult): string {
749
984
  // Tool Definitions
750
985
  // ============================================================================
751
986
 
987
+ /**
988
+ * Select the best decomposition strategy for a task
989
+ *
990
+ * Analyzes task description and recommends a strategy with reasoning.
991
+ * Use this before swarm_plan_prompt to understand the recommended approach.
992
+ */
993
+ export const swarm_select_strategy = tool({
994
+ description:
995
+ "Analyze task and recommend decomposition strategy (file-based, feature-based, or risk-based)",
996
+ args: {
997
+ task: tool.schema.string().min(1).describe("Task description to analyze"),
998
+ codebase_context: tool.schema
999
+ .string()
1000
+ .optional()
1001
+ .describe("Optional codebase context (file structure, tech stack, etc.)"),
1002
+ },
1003
+ async execute(args) {
1004
+ const result = selectStrategy(args.task);
1005
+
1006
+ // Enhance reasoning with codebase context if provided
1007
+ let enhancedReasoning = result.reasoning;
1008
+ if (args.codebase_context) {
1009
+ enhancedReasoning += `\n\nCodebase context considered: ${args.codebase_context.slice(0, 200)}...`;
1010
+ }
1011
+
1012
+ return JSON.stringify(
1013
+ {
1014
+ strategy: result.strategy,
1015
+ confidence: Math.round(result.confidence * 100) / 100,
1016
+ reasoning: enhancedReasoning,
1017
+ description: STRATEGIES[result.strategy].description,
1018
+ guidelines: STRATEGIES[result.strategy].guidelines,
1019
+ anti_patterns: STRATEGIES[result.strategy].antiPatterns,
1020
+ alternatives: result.alternatives.map((alt) => ({
1021
+ strategy: alt.strategy,
1022
+ description: STRATEGIES[alt.strategy].description,
1023
+ score: alt.score,
1024
+ })),
1025
+ },
1026
+ null,
1027
+ 2,
1028
+ );
1029
+ },
1030
+ });
1031
+
1032
+ /**
1033
+ * Strategy-specific decomposition prompt template
1034
+ */
1035
+ const STRATEGY_DECOMPOSITION_PROMPT = `You are decomposing a task into parallelizable subtasks for a swarm of agents.
1036
+
1037
+ ## Task
1038
+ {task}
1039
+
1040
+ {strategy_guidelines}
1041
+
1042
+ {context_section}
1043
+
1044
+ {cass_history}
1045
+
1046
+ ## MANDATORY: Beads Issue Tracking
1047
+
1048
+ **Every subtask MUST become a bead.** This is non-negotiable.
1049
+
1050
+ After decomposition, the coordinator will:
1051
+ 1. Create an epic bead for the overall task
1052
+ 2. Create child beads for each subtask
1053
+ 3. Track progress through bead status updates
1054
+ 4. Close beads with summaries when complete
1055
+
1056
+ Agents MUST update their bead status as they work. No silent progress.
1057
+
1058
+ ## Requirements
1059
+
1060
+ 1. **Break into 2-{max_subtasks} independent subtasks** that can run in parallel
1061
+ 2. **Assign files** - each subtask must specify which files it will modify
1062
+ 3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
1063
+ 4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
1064
+ 5. **Estimate complexity** - 1 (trivial) to 5 (complex)
1065
+ 6. **Plan aggressively** - break down more than you think necessary, smaller is better
1066
+
1067
+ ## Response Format
1068
+
1069
+ Respond with a JSON object matching this schema:
1070
+
1071
+ \`\`\`typescript
1072
+ {
1073
+ epic: {
1074
+ title: string, // Epic title for the beads tracker
1075
+ description?: string // Brief description of the overall goal
1076
+ },
1077
+ subtasks: [
1078
+ {
1079
+ title: string, // What this subtask accomplishes
1080
+ description?: string, // Detailed instructions for the agent
1081
+ files: string[], // Files this subtask will modify (globs allowed)
1082
+ dependencies: number[], // Indices of subtasks this depends on (0-indexed)
1083
+ estimated_complexity: 1-5 // Effort estimate
1084
+ },
1085
+ // ... more subtasks
1086
+ ]
1087
+ }
1088
+ \`\`\`
1089
+
1090
+ Now decompose the task:`;
1091
+
1092
+ /**
1093
+ * Generate a strategy-specific planning prompt
1094
+ *
1095
+ * Higher-level than swarm_decompose - includes strategy selection and guidelines.
1096
+ * Use this when you want the full planning experience with strategy-specific advice.
1097
+ */
1098
+ export const swarm_plan_prompt = tool({
1099
+ description:
1100
+ "Generate strategy-specific decomposition prompt. Auto-selects strategy or uses provided one. Queries CASS for similar tasks.",
1101
+ args: {
1102
+ task: tool.schema.string().min(1).describe("Task description to decompose"),
1103
+ strategy: tool.schema
1104
+ .enum(["file-based", "feature-based", "risk-based", "auto"])
1105
+ .optional()
1106
+ .describe("Decomposition strategy (default: auto-detect)"),
1107
+ max_subtasks: tool.schema
1108
+ .number()
1109
+ .int()
1110
+ .min(2)
1111
+ .max(10)
1112
+ .default(5)
1113
+ .describe("Maximum number of subtasks (default: 5)"),
1114
+ context: tool.schema
1115
+ .string()
1116
+ .optional()
1117
+ .describe("Additional context (codebase info, constraints, etc.)"),
1118
+ query_cass: tool.schema
1119
+ .boolean()
1120
+ .optional()
1121
+ .describe("Query CASS for similar past tasks (default: true)"),
1122
+ cass_limit: tool.schema
1123
+ .number()
1124
+ .int()
1125
+ .min(1)
1126
+ .max(10)
1127
+ .optional()
1128
+ .describe("Max CASS results to include (default: 3)"),
1129
+ },
1130
+ async execute(args) {
1131
+ // Select strategy
1132
+ let selectedStrategy: Exclude<DecompositionStrategy, "auto">;
1133
+ let strategyReasoning: string;
1134
+
1135
+ if (args.strategy && args.strategy !== "auto") {
1136
+ selectedStrategy = args.strategy;
1137
+ strategyReasoning = `User-specified strategy: ${selectedStrategy}`;
1138
+ } else {
1139
+ const selection = selectStrategy(args.task);
1140
+ selectedStrategy = selection.strategy;
1141
+ strategyReasoning = selection.reasoning;
1142
+ }
1143
+
1144
+ // Query CASS for similar past tasks
1145
+ let cassContext = "";
1146
+ let cassResult: CassSearchResult | null = null;
1147
+
1148
+ if (args.query_cass !== false) {
1149
+ cassResult = await queryCassHistory(args.task, args.cass_limit ?? 3);
1150
+ if (cassResult && cassResult.results.length > 0) {
1151
+ cassContext = formatCassHistoryForPrompt(cassResult);
1152
+ }
1153
+ }
1154
+
1155
+ // Format strategy guidelines
1156
+ const strategyGuidelines = formatStrategyGuidelines(selectedStrategy);
1157
+
1158
+ // Combine user context
1159
+ const contextSection = args.context
1160
+ ? `## Additional Context\n${args.context}`
1161
+ : "## Additional Context\n(none provided)";
1162
+
1163
+ // Build the prompt
1164
+ const prompt = STRATEGY_DECOMPOSITION_PROMPT.replace("{task}", args.task)
1165
+ .replace("{strategy_guidelines}", strategyGuidelines)
1166
+ .replace("{context_section}", contextSection)
1167
+ .replace("{cass_history}", cassContext || "")
1168
+ .replace("{max_subtasks}", (args.max_subtasks ?? 5).toString());
1169
+
1170
+ return JSON.stringify(
1171
+ {
1172
+ prompt,
1173
+ strategy: {
1174
+ selected: selectedStrategy,
1175
+ reasoning: strategyReasoning,
1176
+ guidelines: STRATEGIES[selectedStrategy].guidelines,
1177
+ anti_patterns: STRATEGIES[selectedStrategy].antiPatterns,
1178
+ },
1179
+ expected_schema: "BeadTree",
1180
+ schema_hint: {
1181
+ epic: { title: "string", description: "string?" },
1182
+ subtasks: [
1183
+ {
1184
+ title: "string",
1185
+ description: "string?",
1186
+ files: "string[]",
1187
+ dependencies: "number[]",
1188
+ estimated_complexity: "1-5",
1189
+ },
1190
+ ],
1191
+ },
1192
+ validation_note:
1193
+ "Parse agent response as JSON and validate with swarm_validate_decomposition",
1194
+ cass_history: cassResult
1195
+ ? {
1196
+ queried: true,
1197
+ results_found: cassResult.results.length,
1198
+ included_in_context: cassResult.results.length > 0,
1199
+ }
1200
+ : { queried: false, reason: "disabled or unavailable" },
1201
+ },
1202
+ null,
1203
+ 2,
1204
+ );
1205
+ },
1206
+ });
1207
+
752
1208
  /**
753
1209
  * Decompose a task into a bead tree
754
1210
  *
@@ -1372,6 +1828,9 @@ export const swarm_complete = tool({
1372
1828
  * decomposition quality over time. This data feeds into criterion
1373
1829
  * weight calculations.
1374
1830
  *
1831
+ * Strategy tracking enables learning about which decomposition strategies
1832
+ * work best for different task types.
1833
+ *
1375
1834
  * @see src/learning.ts for scoring logic
1376
1835
  */
1377
1836
  export const swarm_record_outcome = tool({
@@ -1407,6 +1866,10 @@ export const swarm_record_outcome = tool({
1407
1866
  .describe(
1408
1867
  "Criteria to generate feedback for (default: all default criteria)",
1409
1868
  ),
1869
+ strategy: tool.schema
1870
+ .enum(["file-based", "feature-based", "risk-based"])
1871
+ .optional()
1872
+ .describe("Decomposition strategy used for this task"),
1410
1873
  },
1411
1874
  async execute(args) {
1412
1875
  // Build outcome signals
@@ -1418,6 +1881,7 @@ export const swarm_record_outcome = tool({
1418
1881
  success: args.success,
1419
1882
  files_touched: args.files_touched ?? [],
1420
1883
  timestamp: new Date().toISOString(),
1884
+ strategy: args.strategy as LearningDecompositionStrategy | undefined,
1421
1885
  };
1422
1886
 
1423
1887
  // Validate signals
@@ -1436,9 +1900,15 @@ export const swarm_record_outcome = tool({
1436
1900
  "patterns",
1437
1901
  "readable",
1438
1902
  ];
1439
- const feedbackEvents: FeedbackEvent[] = criteriaToScore.map((criterion) =>
1440
- outcomeToFeedback(scored, criterion),
1441
- );
1903
+ const feedbackEvents: FeedbackEvent[] = criteriaToScore.map((criterion) => {
1904
+ const event = outcomeToFeedback(scored, criterion);
1905
+ // Include strategy in feedback context for future analysis
1906
+ if (args.strategy) {
1907
+ event.context =
1908
+ `${event.context || ""} [strategy: ${args.strategy}]`.trim();
1909
+ }
1910
+ return event;
1911
+ });
1442
1912
 
1443
1913
  return JSON.stringify(
1444
1914
  {
@@ -1458,6 +1928,7 @@ export const swarm_record_outcome = tool({
1458
1928
  error_count: args.error_count ?? 0,
1459
1929
  retry_count: args.retry_count ?? 0,
1460
1930
  success: args.success,
1931
+ strategy: args.strategy,
1461
1932
  },
1462
1933
  note: "Feedback events should be stored for criterion weight calculation. Use learning.ts functions to apply weights.",
1463
1934
  },
@@ -1507,15 +1978,15 @@ export const swarm_subtask_prompt = tool({
1507
1978
  /**
1508
1979
  * Prepare a subtask for spawning with Task tool (V2 prompt)
1509
1980
  *
1510
- * This is a simplified tool for coordinators that generates a prompt using
1511
- * the V2 template (no Agent Mail/beads instructions - coordinator handles coordination).
1981
+ * Generates a streamlined prompt that tells agents to USE Agent Mail and beads.
1512
1982
  * Returns JSON that can be directly used with Task tool.
1513
1983
  */
1514
1984
  export const swarm_spawn_subtask = tool({
1515
1985
  description:
1516
- "Prepare a subtask for spawning with Task tool. Returns prompt and metadata for coordinator to use.",
1986
+ "Prepare a subtask for spawning. Returns prompt with Agent Mail/beads instructions.",
1517
1987
  args: {
1518
1988
  bead_id: tool.schema.string().describe("Subtask bead ID"),
1989
+ epic_id: tool.schema.string().describe("Parent epic bead ID"),
1519
1990
  subtask_title: tool.schema.string().describe("Subtask title"),
1520
1991
  subtask_description: tool.schema
1521
1992
  .string()
@@ -1531,6 +2002,8 @@ export const swarm_spawn_subtask = tool({
1531
2002
  },
1532
2003
  async execute(args) {
1533
2004
  const prompt = formatSubtaskPromptV2({
2005
+ bead_id: args.bead_id,
2006
+ epic_id: args.epic_id,
1534
2007
  subtask_title: args.subtask_title,
1535
2008
  subtask_description: args.subtask_description || "",
1536
2009
  files: args.files,
@@ -1541,6 +2014,7 @@ export const swarm_spawn_subtask = tool({
1541
2014
  {
1542
2015
  prompt,
1543
2016
  bead_id: args.bead_id,
2017
+ epic_id: args.epic_id,
1544
2018
  files: args.files,
1545
2019
  },
1546
2020
  null,
@@ -1829,6 +2303,8 @@ export const swarm_init = tool({
1829
2303
 
1830
2304
  export const swarmTools = {
1831
2305
  swarm_init: swarm_init,
2306
+ swarm_select_strategy: swarm_select_strategy,
2307
+ swarm_plan_prompt: swarm_plan_prompt,
1832
2308
  swarm_decompose: swarm_decompose,
1833
2309
  swarm_validate_decomposition: swarm_validate_decomposition,
1834
2310
  swarm_status: swarm_status,