opencode-swarm-plugin 0.12.9 → 0.12.11
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 +7 -7
- package/dist/index.js +486 -41
- package/dist/plugin.js +484 -41
- package/package.json +1 -1
- package/src/index.ts +21 -0
- package/src/learning.ts +19 -0
- package/src/repo-crawl.ts +610 -0
- package/src/schemas/evaluation.ts +21 -0
- package/src/swarm.ts +203 -37
|
@@ -131,3 +131,24 @@ export const ValidationResultSchema = z.object({
|
|
|
131
131
|
extractionMethod: z.string().optional(),
|
|
132
132
|
});
|
|
133
133
|
export type ValidationResult = z.infer<typeof ValidationResultSchema>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Failure mode taxonomy for task failures
|
|
137
|
+
*
|
|
138
|
+
* Classifies WHY tasks fail, not just that they failed.
|
|
139
|
+
* Used in outcome tracking to learn from failure patterns.
|
|
140
|
+
*
|
|
141
|
+
* @see src/learning.ts OutcomeSignalsSchema
|
|
142
|
+
* @see "Patterns for Building AI Agents" p.46
|
|
143
|
+
*/
|
|
144
|
+
export const FailureModeSchema = z.enum([
|
|
145
|
+
"timeout", // Task exceeded time limit
|
|
146
|
+
"conflict", // File reservation conflict
|
|
147
|
+
"validation", // Output failed schema validation
|
|
148
|
+
"tool_failure", // Tool call returned error
|
|
149
|
+
"context_overflow", // Ran out of context window
|
|
150
|
+
"dependency_blocked", // Waiting on another subtask
|
|
151
|
+
"user_cancelled", // User interrupted
|
|
152
|
+
"unknown", // Unclassified
|
|
153
|
+
]);
|
|
154
|
+
export type FailureMode = z.infer<typeof FailureModeSchema>;
|
package/src/swarm.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
type SpawnedAgent,
|
|
26
26
|
type Bead,
|
|
27
27
|
} from "./schemas";
|
|
28
|
-
import { mcpCall } from "./agent-mail";
|
|
28
|
+
import { mcpCall, requireState } from "./agent-mail";
|
|
29
29
|
import {
|
|
30
30
|
OutcomeSignalsSchema,
|
|
31
31
|
DecompositionStrategySchema,
|
|
@@ -344,19 +344,31 @@ export const STRATEGIES: Record<
|
|
|
344
344
|
"research",
|
|
345
345
|
"investigate",
|
|
346
346
|
"explore",
|
|
347
|
-
"find",
|
|
347
|
+
"find out",
|
|
348
348
|
"discover",
|
|
349
349
|
"understand",
|
|
350
|
-
"learn",
|
|
350
|
+
"learn about",
|
|
351
351
|
"analyze",
|
|
352
|
-
"what",
|
|
353
|
-
"
|
|
354
|
-
"
|
|
352
|
+
"what is",
|
|
353
|
+
"what are",
|
|
354
|
+
"how does",
|
|
355
|
+
"how do",
|
|
356
|
+
"why does",
|
|
357
|
+
"why do",
|
|
355
358
|
"compare",
|
|
356
359
|
"evaluate",
|
|
357
360
|
"study",
|
|
358
361
|
"look up",
|
|
359
|
-
"
|
|
362
|
+
"look into",
|
|
363
|
+
"search for",
|
|
364
|
+
"dig into",
|
|
365
|
+
"figure out",
|
|
366
|
+
"debug options",
|
|
367
|
+
"debug levers",
|
|
368
|
+
"configuration options",
|
|
369
|
+
"environment variables",
|
|
370
|
+
"available options",
|
|
371
|
+
"documentation",
|
|
360
372
|
],
|
|
361
373
|
guidelines: [
|
|
362
374
|
"Split by information source (PDFs, repos, history, web)",
|
|
@@ -408,8 +420,18 @@ export function selectStrategy(task: string): {
|
|
|
408
420
|
for (const [strategyName, definition] of Object.entries(STRATEGIES)) {
|
|
409
421
|
const name = strategyName as Exclude<DecompositionStrategy, "auto">;
|
|
410
422
|
for (const keyword of definition.keywords) {
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
// Use word boundary matching to avoid "debug" matching "bug"
|
|
424
|
+
// For multi-word keywords, just check includes (they're specific enough)
|
|
425
|
+
if (keyword.includes(" ")) {
|
|
426
|
+
if (taskLower.includes(keyword)) {
|
|
427
|
+
scores[name] += 1;
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
// Single word: use word boundary regex
|
|
431
|
+
const regex = new RegExp(`\\b${keyword}\\b`, "i");
|
|
432
|
+
if (regex.test(taskLower)) {
|
|
433
|
+
scores[name] += 1;
|
|
434
|
+
}
|
|
413
435
|
}
|
|
414
436
|
}
|
|
415
437
|
}
|
|
@@ -656,51 +678,46 @@ Begin work on your subtask now.`;
|
|
|
656
678
|
*/
|
|
657
679
|
export const SUBTASK_PROMPT_V2 = `You are a swarm agent working on: **{subtask_title}**
|
|
658
680
|
|
|
659
|
-
##
|
|
660
|
-
|
|
661
|
-
|
|
681
|
+
## [IDENTITY]
|
|
682
|
+
Agent: (assigned at spawn)
|
|
683
|
+
Bead: {bead_id}
|
|
684
|
+
Epic: {epic_id}
|
|
662
685
|
|
|
663
|
-
##
|
|
686
|
+
## [TASK]
|
|
664
687
|
{subtask_description}
|
|
665
688
|
|
|
666
|
-
##
|
|
689
|
+
## [FILES]
|
|
690
|
+
Reserved (exclusive):
|
|
667
691
|
{file_list}
|
|
668
692
|
|
|
669
693
|
Only modify these files. Need others? Message the coordinator.
|
|
670
694
|
|
|
671
|
-
##
|
|
695
|
+
## [CONTEXT]
|
|
672
696
|
{shared_context}
|
|
673
697
|
|
|
674
698
|
{compressed_context}
|
|
675
699
|
|
|
676
700
|
{error_context}
|
|
677
701
|
|
|
678
|
-
##
|
|
702
|
+
## [TOOLS]
|
|
703
|
+
### Beads
|
|
704
|
+
- beads_update (status: blocked)
|
|
705
|
+
- beads_create (new bugs)
|
|
706
|
+
- beads_close (via swarm_complete)
|
|
679
707
|
|
|
680
|
-
### Agent Mail
|
|
681
|
-
|
|
682
|
-
// Report progress, ask questions, announce blockers
|
|
683
|
-
agentmail_send({
|
|
684
|
-
to: ["coordinator"],
|
|
685
|
-
subject: "Progress update",
|
|
686
|
-
body: "What you did or need",
|
|
687
|
-
thread_id: "{epic_id}"
|
|
688
|
-
})
|
|
689
|
-
\`\`\`
|
|
708
|
+
### Agent Mail
|
|
709
|
+
- agentmail_send (thread_id: {epic_id})
|
|
690
710
|
|
|
691
|
-
###
|
|
692
|
-
-
|
|
693
|
-
- **Found bug?** \`beads_create({ title: "Bug description", type: "bug" })\`
|
|
694
|
-
- **Done?** \`swarm_complete({ bead_id: "{bead_id}", summary: "What you did", files_touched: [...] })\`
|
|
711
|
+
### Completion
|
|
712
|
+
- swarm_complete (REQUIRED when done)
|
|
695
713
|
|
|
696
|
-
##
|
|
714
|
+
## [OUTPUT]
|
|
715
|
+
1. Read files first
|
|
716
|
+
2. Implement changes
|
|
717
|
+
3. Verify (typecheck)
|
|
718
|
+
4. Complete with swarm_complete
|
|
697
719
|
|
|
698
|
-
|
|
699
|
-
2. **Plan** your approach (message coordinator if complex)
|
|
700
|
-
3. **Implement** the changes
|
|
701
|
-
4. **Verify** (typecheck, tests)
|
|
702
|
-
5. **Report** progress via Agent Mail
|
|
703
|
-
6. **Complete** with swarm_complete when done
|
|
720
|
+
Return: Summary of changes made
|
|
704
721
|
|
|
705
722
|
**Never work silently.** Communicate progress and blockers immediately.
|
|
706
723
|
|
|
@@ -1733,6 +1750,92 @@ async function runUbsScan(files: string[]): Promise<UbsScanResult | null> {
|
|
|
1733
1750
|
}
|
|
1734
1751
|
}
|
|
1735
1752
|
|
|
1753
|
+
/**
|
|
1754
|
+
* Broadcast context updates to all agents in the epic
|
|
1755
|
+
*
|
|
1756
|
+
* Enables mid-task coordination by sharing discoveries, warnings, or blockers
|
|
1757
|
+
* with all agents working on the same epic. Agents can broadcast without
|
|
1758
|
+
* waiting for task completion.
|
|
1759
|
+
*
|
|
1760
|
+
* Based on "Patterns for Building AI Agents" p.31: "Ensure subagents can share context along the way"
|
|
1761
|
+
*/
|
|
1762
|
+
export const swarm_broadcast = tool({
|
|
1763
|
+
description:
|
|
1764
|
+
"Broadcast context update to all agents working on the same epic",
|
|
1765
|
+
args: {
|
|
1766
|
+
epic_id: tool.schema.string().describe("Epic ID (e.g., bd-abc123)"),
|
|
1767
|
+
message: tool.schema
|
|
1768
|
+
.string()
|
|
1769
|
+
.describe("Context update to share (what changed, what was learned)"),
|
|
1770
|
+
importance: tool.schema
|
|
1771
|
+
.enum(["info", "warning", "blocker"])
|
|
1772
|
+
.default("info")
|
|
1773
|
+
.describe("Priority level (default: info)"),
|
|
1774
|
+
files_affected: tool.schema
|
|
1775
|
+
.array(tool.schema.string())
|
|
1776
|
+
.optional()
|
|
1777
|
+
.describe("Files this context relates to"),
|
|
1778
|
+
},
|
|
1779
|
+
async execute(args, ctx) {
|
|
1780
|
+
// Get agent state - requires prior initialization
|
|
1781
|
+
const state = requireState(ctx.sessionID);
|
|
1782
|
+
|
|
1783
|
+
// Extract bead_id from context if available (for traceability)
|
|
1784
|
+
// In the swarm flow, ctx might have the current bead being worked on
|
|
1785
|
+
const beadId = (ctx as { beadId?: string }).beadId || "unknown";
|
|
1786
|
+
|
|
1787
|
+
// Format the broadcast message
|
|
1788
|
+
const body = [
|
|
1789
|
+
`## Context Update`,
|
|
1790
|
+
"",
|
|
1791
|
+
`**From**: ${state.agentName} (${beadId})`,
|
|
1792
|
+
`**Priority**: ${args.importance.toUpperCase()}`,
|
|
1793
|
+
"",
|
|
1794
|
+
args.message,
|
|
1795
|
+
"",
|
|
1796
|
+
args.files_affected && args.files_affected.length > 0
|
|
1797
|
+
? `**Files affected**:\n${args.files_affected.map((f) => `- \`${f}\``).join("\n")}`
|
|
1798
|
+
: "",
|
|
1799
|
+
]
|
|
1800
|
+
.filter(Boolean)
|
|
1801
|
+
.join("\n");
|
|
1802
|
+
|
|
1803
|
+
// Map importance to Agent Mail importance
|
|
1804
|
+
const mailImportance =
|
|
1805
|
+
args.importance === "blocker"
|
|
1806
|
+
? "urgent"
|
|
1807
|
+
: args.importance === "warning"
|
|
1808
|
+
? "high"
|
|
1809
|
+
: "normal";
|
|
1810
|
+
|
|
1811
|
+
// Send as broadcast to thread (empty 'to' = all agents in thread)
|
|
1812
|
+
await mcpCall("send_message", {
|
|
1813
|
+
project_key: state.projectKey,
|
|
1814
|
+
sender_name: state.agentName,
|
|
1815
|
+
to: [], // Broadcast to thread
|
|
1816
|
+
subject: `[${args.importance.toUpperCase()}] Context update from ${state.agentName}`,
|
|
1817
|
+
body_md: body,
|
|
1818
|
+
thread_id: args.epic_id,
|
|
1819
|
+
importance: mailImportance,
|
|
1820
|
+
ack_required: args.importance === "blocker", // Require ack for blockers
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
return JSON.stringify(
|
|
1824
|
+
{
|
|
1825
|
+
broadcast: true,
|
|
1826
|
+
epic_id: args.epic_id,
|
|
1827
|
+
from: state.agentName,
|
|
1828
|
+
bead_id: beadId,
|
|
1829
|
+
importance: args.importance,
|
|
1830
|
+
recipients: "all agents in epic",
|
|
1831
|
+
ack_required: args.importance === "blocker",
|
|
1832
|
+
},
|
|
1833
|
+
null,
|
|
1834
|
+
2,
|
|
1835
|
+
);
|
|
1836
|
+
},
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1736
1839
|
/**
|
|
1737
1840
|
* Mark a subtask as complete
|
|
1738
1841
|
*
|
|
@@ -1897,6 +2000,40 @@ export const swarm_complete = tool({
|
|
|
1897
2000
|
},
|
|
1898
2001
|
});
|
|
1899
2002
|
|
|
2003
|
+
/**
|
|
2004
|
+
* Classify failure based on error message heuristics
|
|
2005
|
+
*
|
|
2006
|
+
* Simple pattern matching to categorize why a task failed.
|
|
2007
|
+
* Used when failure_mode is not explicitly provided.
|
|
2008
|
+
*
|
|
2009
|
+
* @param error - Error object or message
|
|
2010
|
+
* @returns FailureMode classification
|
|
2011
|
+
*/
|
|
2012
|
+
function classifyFailure(error: Error | string): string {
|
|
2013
|
+
const msg = (typeof error === "string" ? error : error.message).toLowerCase();
|
|
2014
|
+
|
|
2015
|
+
if (msg.includes("timeout")) return "timeout";
|
|
2016
|
+
if (msg.includes("conflict") || msg.includes("reservation"))
|
|
2017
|
+
return "conflict";
|
|
2018
|
+
if (msg.includes("validation") || msg.includes("schema")) return "validation";
|
|
2019
|
+
if (msg.includes("context") || msg.includes("token"))
|
|
2020
|
+
return "context_overflow";
|
|
2021
|
+
if (msg.includes("blocked") || msg.includes("dependency"))
|
|
2022
|
+
return "dependency_blocked";
|
|
2023
|
+
if (msg.includes("cancel")) return "user_cancelled";
|
|
2024
|
+
|
|
2025
|
+
// Check for tool failure patterns
|
|
2026
|
+
if (
|
|
2027
|
+
msg.includes("tool") ||
|
|
2028
|
+
msg.includes("command") ||
|
|
2029
|
+
msg.includes("failed to execute")
|
|
2030
|
+
) {
|
|
2031
|
+
return "tool_failure";
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
return "unknown";
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1900
2037
|
/**
|
|
1901
2038
|
* Record outcome signals from a completed subtask
|
|
1902
2039
|
*
|
|
@@ -1946,6 +2083,25 @@ export const swarm_record_outcome = tool({
|
|
|
1946
2083
|
.enum(["file-based", "feature-based", "risk-based", "research-based"])
|
|
1947
2084
|
.optional()
|
|
1948
2085
|
.describe("Decomposition strategy used for this task"),
|
|
2086
|
+
failure_mode: tool.schema
|
|
2087
|
+
.enum([
|
|
2088
|
+
"timeout",
|
|
2089
|
+
"conflict",
|
|
2090
|
+
"validation",
|
|
2091
|
+
"tool_failure",
|
|
2092
|
+
"context_overflow",
|
|
2093
|
+
"dependency_blocked",
|
|
2094
|
+
"user_cancelled",
|
|
2095
|
+
"unknown",
|
|
2096
|
+
])
|
|
2097
|
+
.optional()
|
|
2098
|
+
.describe(
|
|
2099
|
+
"Failure classification (only when success=false). Auto-classified if not provided.",
|
|
2100
|
+
),
|
|
2101
|
+
failure_details: tool.schema
|
|
2102
|
+
.string()
|
|
2103
|
+
.optional()
|
|
2104
|
+
.describe("Detailed failure context (error message, stack trace, etc.)"),
|
|
1949
2105
|
},
|
|
1950
2106
|
async execute(args) {
|
|
1951
2107
|
// Build outcome signals
|
|
@@ -1958,8 +2114,15 @@ export const swarm_record_outcome = tool({
|
|
|
1958
2114
|
files_touched: args.files_touched ?? [],
|
|
1959
2115
|
timestamp: new Date().toISOString(),
|
|
1960
2116
|
strategy: args.strategy as LearningDecompositionStrategy | undefined,
|
|
2117
|
+
failure_mode: args.failure_mode,
|
|
2118
|
+
failure_details: args.failure_details,
|
|
1961
2119
|
};
|
|
1962
2120
|
|
|
2121
|
+
// If task failed but no failure_mode provided, try to classify from failure_details
|
|
2122
|
+
if (!args.success && !args.failure_mode && args.failure_details) {
|
|
2123
|
+
signals.failure_mode = classifyFailure(args.failure_details) as any;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
1963
2126
|
// Validate signals
|
|
1964
2127
|
const validated = OutcomeSignalsSchema.parse(signals);
|
|
1965
2128
|
|
|
@@ -2017,6 +2180,8 @@ export const swarm_record_outcome = tool({
|
|
|
2017
2180
|
retry_count: args.retry_count ?? 0,
|
|
2018
2181
|
success: args.success,
|
|
2019
2182
|
strategy: args.strategy,
|
|
2183
|
+
failure_mode: validated.failure_mode,
|
|
2184
|
+
failure_details: validated.failure_details,
|
|
2020
2185
|
accumulated_errors: errorStats.total,
|
|
2021
2186
|
unresolved_errors: errorStats.unresolved,
|
|
2022
2187
|
},
|
|
@@ -2537,6 +2702,7 @@ export const swarmTools = {
|
|
|
2537
2702
|
swarm_validate_decomposition: swarm_validate_decomposition,
|
|
2538
2703
|
swarm_status: swarm_status,
|
|
2539
2704
|
swarm_progress: swarm_progress,
|
|
2705
|
+
swarm_broadcast: swarm_broadcast,
|
|
2540
2706
|
swarm_complete: swarm_complete,
|
|
2541
2707
|
swarm_record_outcome: swarm_record_outcome,
|
|
2542
2708
|
swarm_subtask_prompt: swarm_subtask_prompt,
|