opencode-swarm-plugin 0.17.0 → 0.18.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
@@ -38,6 +38,10 @@ import {
38
38
  outcomeToFeedback,
39
39
  ErrorAccumulator,
40
40
  ErrorEntrySchema,
41
+ formatMemoryStoreOnSuccess,
42
+ formatMemoryStoreOn3Strike,
43
+ formatMemoryQueryForDecomposition,
44
+ formatMemoryValidationHint,
41
45
  type OutcomeSignals,
42
46
  type ScoredOutcome,
43
47
  type FeedbackEvent,
@@ -1429,6 +1433,224 @@ export const swarm_plan_prompt = tool({
1429
1433
  "Parse agent response as JSON and validate with swarm_validate_decomposition",
1430
1434
  cass_history: cassResultInfo,
1431
1435
  skills: skillsInfo,
1436
+ // Add semantic-memory query instruction
1437
+ memory_query: formatMemoryQueryForDecomposition(args.task, 3),
1438
+ },
1439
+ null,
1440
+ 2,
1441
+ );
1442
+ },
1443
+ });
1444
+
1445
+ /**
1446
+ * Delegate task decomposition to a swarm/planner subagent
1447
+ *
1448
+ * Returns a prompt for spawning a planner agent that will handle all decomposition
1449
+ * reasoning. This keeps the coordinator context lean by offloading:
1450
+ * - Strategy selection
1451
+ * - CASS queries
1452
+ * - Skills discovery
1453
+ * - File analysis
1454
+ * - BeadTree generation
1455
+ *
1456
+ * The planner returns ONLY structured BeadTree JSON, which the coordinator
1457
+ * validates and uses to create beads.
1458
+ *
1459
+ * @example
1460
+ * ```typescript
1461
+ * // Coordinator workflow:
1462
+ * const delegateResult = await swarm_delegate_planning({
1463
+ * task: "Add user authentication",
1464
+ * context: "Next.js 14 app",
1465
+ * });
1466
+ *
1467
+ * // Parse the result
1468
+ * const { prompt, subagent_type } = JSON.parse(delegateResult);
1469
+ *
1470
+ * // Spawn subagent using Task tool
1471
+ * const plannerResponse = await Task(prompt, subagent_type);
1472
+ *
1473
+ * // Validate the response
1474
+ * await swarm_validate_decomposition({ response: plannerResponse });
1475
+ * ```
1476
+ */
1477
+ export const swarm_delegate_planning = tool({
1478
+ description:
1479
+ "Delegate task decomposition to a swarm/planner subagent. Returns a prompt to spawn the planner. Use this to keep coordinator context lean - all planning reasoning happens in the subagent.",
1480
+ args: {
1481
+ task: tool.schema.string().min(1).describe("The task to decompose"),
1482
+ context: tool.schema
1483
+ .string()
1484
+ .optional()
1485
+ .describe("Additional context to include"),
1486
+ max_subtasks: tool.schema
1487
+ .number()
1488
+ .int()
1489
+ .min(2)
1490
+ .max(10)
1491
+ .optional()
1492
+ .default(5)
1493
+ .describe("Maximum number of subtasks (default: 5)"),
1494
+ strategy: tool.schema
1495
+ .enum(["auto", "file-based", "feature-based", "risk-based"])
1496
+ .optional()
1497
+ .default("auto")
1498
+ .describe("Decomposition strategy (default: auto-detect)"),
1499
+ query_cass: tool.schema
1500
+ .boolean()
1501
+ .optional()
1502
+ .default(true)
1503
+ .describe("Query CASS for similar past tasks (default: true)"),
1504
+ },
1505
+ async execute(args) {
1506
+ // Select strategy
1507
+ let selectedStrategy: Exclude<DecompositionStrategy, "auto">;
1508
+ let strategyReasoning: string;
1509
+
1510
+ if (args.strategy && args.strategy !== "auto") {
1511
+ selectedStrategy = args.strategy;
1512
+ strategyReasoning = `User-specified strategy: ${selectedStrategy}`;
1513
+ } else {
1514
+ const selection = selectStrategy(args.task);
1515
+ selectedStrategy = selection.strategy;
1516
+ strategyReasoning = selection.reasoning;
1517
+ }
1518
+
1519
+ // Query CASS for similar past tasks
1520
+ let cassContext = "";
1521
+ let cassResultInfo: {
1522
+ queried: boolean;
1523
+ results_found?: number;
1524
+ included_in_context?: boolean;
1525
+ reason?: string;
1526
+ };
1527
+
1528
+ if (args.query_cass !== false) {
1529
+ const cassResult = await queryCassHistory(args.task, 3);
1530
+ if (cassResult.status === "success") {
1531
+ cassContext = formatCassHistoryForPrompt(cassResult.data);
1532
+ cassResultInfo = {
1533
+ queried: true,
1534
+ results_found: cassResult.data.results.length,
1535
+ included_in_context: true,
1536
+ };
1537
+ } else {
1538
+ cassResultInfo = {
1539
+ queried: true,
1540
+ results_found: 0,
1541
+ included_in_context: false,
1542
+ reason: cassResult.status,
1543
+ };
1544
+ }
1545
+ } else {
1546
+ cassResultInfo = { queried: false, reason: "disabled" };
1547
+ }
1548
+
1549
+ // Fetch skills context
1550
+ let skillsContext = "";
1551
+ let skillsInfo: { included: boolean; count?: number; relevant?: string[] } =
1552
+ {
1553
+ included: false,
1554
+ };
1555
+
1556
+ const allSkills = await listSkills();
1557
+ if (allSkills.length > 0) {
1558
+ skillsContext = await getSkillsContextForSwarm();
1559
+ const relevantSkills = await findRelevantSkills(args.task);
1560
+ skillsInfo = {
1561
+ included: true,
1562
+ count: allSkills.length,
1563
+ relevant: relevantSkills,
1564
+ };
1565
+
1566
+ // Add suggestion for relevant skills
1567
+ if (relevantSkills.length > 0) {
1568
+ skillsContext += `\n\n**Suggested skills for this task**: ${relevantSkills.join(", ")}`;
1569
+ }
1570
+ }
1571
+
1572
+ // Format strategy guidelines
1573
+ const strategyGuidelines = formatStrategyGuidelines(selectedStrategy);
1574
+
1575
+ // Combine user context
1576
+ const contextSection = args.context
1577
+ ? `## Additional Context\n${args.context}`
1578
+ : "## Additional Context\n(none provided)";
1579
+
1580
+ // Build the planning prompt with clear instructions for JSON-only output
1581
+ const planningPrompt = STRATEGY_DECOMPOSITION_PROMPT.replace(
1582
+ "{task}",
1583
+ args.task,
1584
+ )
1585
+ .replace("{strategy_guidelines}", strategyGuidelines)
1586
+ .replace("{context_section}", contextSection)
1587
+ .replace("{cass_history}", cassContext || "")
1588
+ .replace("{skills_context}", skillsContext || "")
1589
+ .replace("{max_subtasks}", (args.max_subtasks ?? 5).toString());
1590
+
1591
+ // Add strict JSON-only instructions for the subagent
1592
+ const subagentInstructions = `
1593
+ ## CRITICAL: Output Format
1594
+
1595
+ You are a planner subagent. Your ONLY output must be valid JSON matching the BeadTree schema.
1596
+
1597
+ DO NOT include:
1598
+ - Explanatory text before or after the JSON
1599
+ - Markdown code fences (\`\`\`json)
1600
+ - Commentary or reasoning
1601
+
1602
+ OUTPUT ONLY the raw JSON object.
1603
+
1604
+ ## Example Output
1605
+
1606
+ {
1607
+ "epic": {
1608
+ "title": "Add user authentication",
1609
+ "description": "Implement OAuth-based authentication system"
1610
+ },
1611
+ "subtasks": [
1612
+ {
1613
+ "title": "Set up OAuth provider",
1614
+ "description": "Configure OAuth client credentials and redirect URLs",
1615
+ "files": ["src/auth/oauth.ts", "src/config/auth.ts"],
1616
+ "dependencies": [],
1617
+ "estimated_complexity": 2
1618
+ },
1619
+ {
1620
+ "title": "Create auth routes",
1621
+ "description": "Implement login, logout, and callback routes",
1622
+ "files": ["src/app/api/auth/[...nextauth]/route.ts"],
1623
+ "dependencies": [0],
1624
+ "estimated_complexity": 3
1625
+ }
1626
+ ]
1627
+ }
1628
+
1629
+ Now generate the BeadTree for the given task.`;
1630
+
1631
+ const fullPrompt = `${planningPrompt}\n\n${subagentInstructions}`;
1632
+
1633
+ // Return structured output for coordinator
1634
+ return JSON.stringify(
1635
+ {
1636
+ prompt: fullPrompt,
1637
+ subagent_type: "swarm/planner",
1638
+ description: "Task decomposition planning",
1639
+ strategy: {
1640
+ selected: selectedStrategy,
1641
+ reasoning: strategyReasoning,
1642
+ },
1643
+ expected_output: "BeadTree JSON (raw JSON, no markdown)",
1644
+ next_steps: [
1645
+ "1. Spawn subagent with Task tool using returned prompt",
1646
+ "2. Parse subagent response as JSON",
1647
+ "3. Validate with swarm_validate_decomposition",
1648
+ "4. Create beads with beads_create_epic",
1649
+ ],
1650
+ cass_history: cassResultInfo,
1651
+ skills: skillsInfo,
1652
+ // Add semantic-memory query instruction
1653
+ memory_query: formatMemoryQueryForDecomposition(args.task, 3),
1432
1654
  },
1433
1655
  null,
1434
1656
  2,
@@ -1537,6 +1759,8 @@ export const swarm_decompose = tool({
1537
1759
  validation_note:
1538
1760
  "Parse agent response as JSON and validate with BeadTreeSchema from schemas/bead.ts",
1539
1761
  cass_history: cassResultInfo,
1762
+ // Add semantic-memory query instruction
1763
+ memory_query: formatMemoryQueryForDecomposition(args.task, 3),
1540
1764
  },
1541
1765
  null,
1542
1766
  2,
@@ -2465,43 +2689,43 @@ export const swarm_complete = tool({
2465
2689
  importance: "normal",
2466
2690
  });
2467
2691
 
2468
- return JSON.stringify(
2469
- {
2470
- success: true,
2471
- bead_id: args.bead_id,
2472
- closed: true,
2473
- reservations_released: true,
2474
- message_sent: true,
2475
- verification_gate: verificationResult
2476
- ? {
2477
- passed: true,
2478
- summary: verificationResult.summary,
2479
- steps: verificationResult.steps.map((s) => ({
2480
- name: s.name,
2481
- passed: s.passed,
2482
- skipped: s.skipped,
2483
- skipReason: s.skipReason,
2484
- })),
2485
- }
2486
- : args.skip_verification
2487
- ? { skipped: true, reason: "skip_verification=true" }
2488
- : { skipped: true, reason: "no files_touched provided" },
2489
- ubs_scan: ubsResult
2490
- ? {
2491
- ran: true,
2492
- bugs_found: ubsResult.summary.total,
2493
- summary: ubsResult.summary,
2494
- warnings: ubsResult.bugs.filter((b) => b.severity !== "critical"),
2495
- }
2496
- : verificationResult
2497
- ? { ran: true, included_in_verification_gate: true }
2498
- : {
2499
- ran: false,
2500
- reason: args.skip_ubs_scan
2501
- ? "skipped"
2502
- : "no files or ubs unavailable",
2503
- },
2504
- learning_prompt: `## Reflection
2692
+ // Build success response with semantic-memory integration
2693
+ const response = {
2694
+ success: true,
2695
+ bead_id: args.bead_id,
2696
+ closed: true,
2697
+ reservations_released: true,
2698
+ message_sent: true,
2699
+ verification_gate: verificationResult
2700
+ ? {
2701
+ passed: true,
2702
+ summary: verificationResult.summary,
2703
+ steps: verificationResult.steps.map((s) => ({
2704
+ name: s.name,
2705
+ passed: s.passed,
2706
+ skipped: s.skipped,
2707
+ skipReason: s.skipReason,
2708
+ })),
2709
+ }
2710
+ : args.skip_verification
2711
+ ? { skipped: true, reason: "skip_verification=true" }
2712
+ : { skipped: true, reason: "no files_touched provided" },
2713
+ ubs_scan: ubsResult
2714
+ ? {
2715
+ ran: true,
2716
+ bugs_found: ubsResult.summary.total,
2717
+ summary: ubsResult.summary,
2718
+ warnings: ubsResult.bugs.filter((b) => b.severity !== "critical"),
2719
+ }
2720
+ : verificationResult
2721
+ ? { ran: true, included_in_verification_gate: true }
2722
+ : {
2723
+ ran: false,
2724
+ reason: args.skip_ubs_scan
2725
+ ? "skipped"
2726
+ : "no files or ubs unavailable",
2727
+ },
2728
+ learning_prompt: `## Reflection
2505
2729
 
2506
2730
  Did you learn anything reusable during this subtask? Consider:
2507
2731
 
@@ -2513,10 +2737,15 @@ Did you learn anything reusable during this subtask? Consider:
2513
2737
  If you discovered something valuable, use \`swarm_learn\` or \`skills_create\` to preserve it as a skill for future swarms.
2514
2738
 
2515
2739
  Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
2516
- },
2517
- null,
2518
- 2,
2519
- );
2740
+ // Add semantic-memory integration on success
2741
+ memory_store: formatMemoryStoreOnSuccess(
2742
+ args.bead_id,
2743
+ args.summary,
2744
+ args.files_touched || [],
2745
+ ),
2746
+ };
2747
+
2748
+ return JSON.stringify(response, null, 2);
2520
2749
  },
2521
2750
  });
2522
2751
 
@@ -3453,6 +3682,7 @@ export const swarmTools = {
3453
3682
  swarm_init: swarm_init,
3454
3683
  swarm_select_strategy: swarm_select_strategy,
3455
3684
  swarm_plan_prompt: swarm_plan_prompt,
3685
+ swarm_delegate_planning: swarm_delegate_planning,
3456
3686
  swarm_decompose: swarm_decompose,
3457
3687
  swarm_validate_decomposition: swarm_validate_decomposition,
3458
3688
  swarm_status: swarm_status,
@@ -3574,22 +3804,29 @@ export const swarm_check_strikes = tool({
3574
3804
 
3575
3805
  const strikedOut = record.strike_count >= 3;
3576
3806
 
3577
- return JSON.stringify(
3578
- {
3579
- bead_id: args.bead_id,
3580
- strike_count: record.strike_count,
3581
- is_striked_out: strikedOut,
3582
- failures: record.failures,
3583
- message: strikedOut
3584
- ? "⚠️ STRUCK OUT: 3 strikes reached. STOP and question the architecture."
3585
- : `Strike ${record.strike_count} recorded. ${3 - record.strike_count} remaining.`,
3586
- warning: strikedOut
3587
- ? "DO NOT attempt Fix #4. Call with action=get_prompt for architecture review."
3588
- : undefined,
3589
- },
3590
- null,
3591
- 2,
3592
- );
3807
+ // Build response with memory storage hint on 3-strike
3808
+ const response: Record<string, unknown> = {
3809
+ bead_id: args.bead_id,
3810
+ strike_count: record.strike_count,
3811
+ is_striked_out: strikedOut,
3812
+ failures: record.failures,
3813
+ message: strikedOut
3814
+ ? "⚠️ STRUCK OUT: 3 strikes reached. STOP and question the architecture."
3815
+ : `Strike ${record.strike_count} recorded. ${3 - record.strike_count} remaining.`,
3816
+ warning: strikedOut
3817
+ ? "DO NOT attempt Fix #4. Call with action=get_prompt for architecture review."
3818
+ : undefined,
3819
+ };
3820
+
3821
+ // Add semantic-memory storage hint on 3-strike
3822
+ if (strikedOut) {
3823
+ response.memory_store = formatMemoryStoreOn3Strike(
3824
+ args.bead_id,
3825
+ record.failures,
3826
+ );
3827
+ }
3828
+
3829
+ return JSON.stringify(response, null, 2);
3593
3830
  }
3594
3831
 
3595
3832
  case "clear": {