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/.beads/issues.jsonl +487 -0
- package/README.md +107 -12
- package/dist/index.js +38 -38
- package/dist/plugin.js +38 -38
- package/examples/agents/swarm-planner.md +138 -0
- package/examples/commands/swarm.md +277 -0
- package/package.json +1 -1
- package/src/index.ts +12 -0
- package/src/learning.ts +13 -0
- package/src/swarm.integration.test.ts +315 -42
- package/src/swarm.ts +528 -52
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
|
-
*
|
|
603
|
+
* Streamlined subtask prompt (V2) - still uses Agent Mail and beads
|
|
364
604
|
*
|
|
365
|
-
* This
|
|
366
|
-
*
|
|
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
|
|
608
|
+
export const SUBTASK_PROMPT_V2 = `You are a swarm agent working on: **{subtask_title}**
|
|
374
609
|
|
|
375
|
-
##
|
|
376
|
-
**
|
|
610
|
+
## Identity
|
|
611
|
+
- **Bead ID**: {bead_id}
|
|
612
|
+
- **Epic ID**: {epic_id}
|
|
377
613
|
|
|
614
|
+
## Task
|
|
378
615
|
{subtask_description}
|
|
379
616
|
|
|
380
|
-
## Files
|
|
617
|
+
## Files (exclusive reservation)
|
|
381
618
|
{file_list}
|
|
382
619
|
|
|
383
|
-
|
|
620
|
+
Only modify these files. Need others? Message the coordinator.
|
|
384
621
|
|
|
385
622
|
## Context
|
|
386
623
|
{shared_context}
|
|
387
624
|
|
|
388
|
-
##
|
|
625
|
+
## MANDATORY: Use These Tools
|
|
389
626
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
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
|
-
|
|
643
|
+
## Workflow
|
|
398
644
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
670
|
+
: "(no specific files - use judgment)";
|
|
438
671
|
|
|
439
|
-
return SUBTASK_PROMPT_V2.replace(
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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,
|