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/.beads/issues.jsonl +24 -24
- package/dist/index.js +302 -47
- package/dist/plugin.js +302 -47
- package/examples/commands/swarm.md +59 -0
- package/global-skills/swarm-coordination/SKILL.md +70 -0
- package/package.json +1 -1
- package/src/learning.ts +106 -0
- package/src/rate-limiter.ts +48 -4
- package/src/streams/index.ts +29 -0
- package/src/streams/projections.ts +15 -0
- package/src/streams/store.ts +141 -72
- package/src/swarm.ts +294 -57
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
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
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
|
-
|
|
2518
|
-
|
|
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
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
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": {
|