opencode-swarm-plugin 0.37.0 → 0.39.1
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/.env +2 -0
- package/.hive/eval-results.json +26 -0
- package/.hive/issues.jsonl +20 -5
- package/.hive/memories.jsonl +35 -1
- package/.opencode/eval-history.jsonl +12 -0
- package/.turbo/turbo-build.log +4 -4
- package/.turbo/turbo-test.log +319 -319
- package/CHANGELOG.md +258 -0
- package/README.md +50 -0
- package/bin/swarm.test.ts +475 -0
- package/bin/swarm.ts +385 -208
- package/dist/compaction-hook.d.ts +1 -1
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/compaction-prompt-scoring.d.ts +124 -0
- package/dist/compaction-prompt-scoring.d.ts.map +1 -0
- package/dist/eval-capture.d.ts +81 -1
- package/dist/eval-capture.d.ts.map +1 -1
- package/dist/eval-gates.d.ts +84 -0
- package/dist/eval-gates.d.ts.map +1 -0
- package/dist/eval-history.d.ts +117 -0
- package/dist/eval-history.d.ts.map +1 -0
- package/dist/eval-learning.d.ts +216 -0
- package/dist/eval-learning.d.ts.map +1 -0
- package/dist/hive.d.ts +59 -0
- package/dist/hive.d.ts.map +1 -1
- package/dist/index.d.ts +87 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +823 -131
- package/dist/plugin.js +655 -131
- package/dist/post-compaction-tracker.d.ts +133 -0
- package/dist/post-compaction-tracker.d.ts.map +1 -0
- package/dist/swarm-decompose.d.ts +30 -0
- package/dist/swarm-decompose.d.ts.map +1 -1
- package/dist/swarm-orchestrate.d.ts +23 -0
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/swarm-prompts.d.ts +25 -1
- package/dist/swarm-prompts.d.ts.map +1 -1
- package/dist/swarm.d.ts +19 -0
- package/dist/swarm.d.ts.map +1 -1
- package/evals/README.md +595 -94
- package/evals/compaction-prompt.eval.ts +149 -0
- package/evals/coordinator-behavior.eval.ts +8 -8
- package/evals/fixtures/compaction-prompt-cases.ts +305 -0
- package/evals/lib/compaction-loader.test.ts +248 -0
- package/evals/lib/compaction-loader.ts +320 -0
- package/evals/lib/data-loader.test.ts +345 -0
- package/evals/lib/data-loader.ts +107 -6
- package/evals/scorers/compaction-prompt-scorers.ts +145 -0
- package/evals/scorers/compaction-scorers.ts +13 -13
- package/evals/scorers/coordinator-discipline.evalite-test.ts +3 -2
- package/evals/scorers/coordinator-discipline.ts +13 -13
- package/examples/plugin-wrapper-template.ts +177 -8
- package/package.json +7 -2
- package/scripts/migrate-unknown-sessions.ts +349 -0
- package/src/compaction-capture.integration.test.ts +257 -0
- package/src/compaction-hook.test.ts +139 -2
- package/src/compaction-hook.ts +113 -2
- package/src/compaction-prompt-scorers.test.ts +299 -0
- package/src/compaction-prompt-scoring.ts +298 -0
- package/src/eval-capture.test.ts +422 -0
- package/src/eval-capture.ts +94 -2
- package/src/eval-gates.test.ts +306 -0
- package/src/eval-gates.ts +218 -0
- package/src/eval-history.test.ts +508 -0
- package/src/eval-history.ts +214 -0
- package/src/eval-learning.test.ts +378 -0
- package/src/eval-learning.ts +360 -0
- package/src/index.ts +61 -1
- package/src/post-compaction-tracker.test.ts +251 -0
- package/src/post-compaction-tracker.ts +237 -0
- package/src/swarm-decompose.test.ts +40 -47
- package/src/swarm-decompose.ts +2 -2
- package/src/swarm-orchestrate.test.ts +270 -7
- package/src/swarm-orchestrate.ts +100 -13
- package/src/swarm-prompts.test.ts +121 -0
- package/src/swarm-prompts.ts +297 -4
- package/src/swarm-research.integration.test.ts +157 -0
- package/src/swarm-review.ts +3 -3
- /package/evals/{evalite.config.ts → evalite.config.ts.bak} +0 -0
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -83,7 +83,8 @@ import {
|
|
|
83
83
|
isReviewApproved,
|
|
84
84
|
getReviewStatus,
|
|
85
85
|
} from "./swarm-review";
|
|
86
|
-
import { captureCoordinatorEvent } from "./eval-capture.js";
|
|
86
|
+
import { captureCoordinatorEvent, type EvalRecord } from "./eval-capture.js";
|
|
87
|
+
import { formatResearcherPrompt } from "./swarm-prompts";
|
|
87
88
|
|
|
88
89
|
// ============================================================================
|
|
89
90
|
// Helper Functions
|
|
@@ -1710,11 +1711,36 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1710
1711
|
},
|
|
1711
1712
|
};
|
|
1712
1713
|
|
|
1714
|
+
// Capture subtask completion outcome for eval data
|
|
1715
|
+
try {
|
|
1716
|
+
const { captureSubtaskOutcome } = await import("./eval-capture.js");
|
|
1717
|
+
const durationMs = args.start_time ? Date.now() - args.start_time : 0;
|
|
1718
|
+
|
|
1719
|
+
// Determine epic ID: use parent_id if available, otherwise fall back to extracting from bead_id
|
|
1720
|
+
const evalEpicId = cell.parent_id || epicId;
|
|
1721
|
+
|
|
1722
|
+
captureSubtaskOutcome({
|
|
1723
|
+
epicId: evalEpicId,
|
|
1724
|
+
projectPath: args.project_key,
|
|
1725
|
+
beadId: args.bead_id,
|
|
1726
|
+
title: cell.title,
|
|
1727
|
+
plannedFiles: args.planned_files || [],
|
|
1728
|
+
actualFiles: args.files_touched || [],
|
|
1729
|
+
durationMs,
|
|
1730
|
+
errorCount: args.error_count || 0,
|
|
1731
|
+
retryCount: args.retry_count || 0,
|
|
1732
|
+
success: true,
|
|
1733
|
+
});
|
|
1734
|
+
} catch (error) {
|
|
1735
|
+
// Non-fatal - don't block completion if capture fails
|
|
1736
|
+
console.warn("[swarm_complete] Failed to capture subtask outcome:", error);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1713
1739
|
// Capture subtask completion outcome
|
|
1714
1740
|
try {
|
|
1715
1741
|
const durationMs = args.start_time ? Date.now() - args.start_time : 0;
|
|
1716
1742
|
captureCoordinatorEvent({
|
|
1717
|
-
session_id:
|
|
1743
|
+
session_id: _ctx.sessionID || "unknown",
|
|
1718
1744
|
epic_id: epicId,
|
|
1719
1745
|
timestamp: new Date().toISOString(),
|
|
1720
1746
|
event_type: "OUTCOME",
|
|
@@ -1823,7 +1849,7 @@ Files touched: ${args.files_touched?.join(", ") || "none recorded"}`,
|
|
|
1823
1849
|
try {
|
|
1824
1850
|
const durationMs = args.start_time ? Date.now() - args.start_time : 0;
|
|
1825
1851
|
captureCoordinatorEvent({
|
|
1826
|
-
session_id:
|
|
1852
|
+
session_id: _ctx.sessionID || "unknown",
|
|
1827
1853
|
epic_id: epicId,
|
|
1828
1854
|
timestamp: new Date().toISOString(),
|
|
1829
1855
|
event_type: "OUTCOME",
|
|
@@ -1946,6 +1972,14 @@ export const swarm_record_outcome = tool({
|
|
|
1946
1972
|
.string()
|
|
1947
1973
|
.optional()
|
|
1948
1974
|
.describe("Detailed failure context (error message, stack trace, etc.)"),
|
|
1975
|
+
project_path: tool.schema
|
|
1976
|
+
.string()
|
|
1977
|
+
.optional()
|
|
1978
|
+
.describe("Project path (for finalizing eval records when all subtasks complete)"),
|
|
1979
|
+
epic_id: tool.schema
|
|
1980
|
+
.string()
|
|
1981
|
+
.optional()
|
|
1982
|
+
.describe("Epic ID (for finalizing eval records when all subtasks complete)"),
|
|
1949
1983
|
},
|
|
1950
1984
|
async execute(args) {
|
|
1951
1985
|
// Build outcome signals
|
|
@@ -1980,6 +2014,21 @@ export const swarm_record_outcome = tool({
|
|
|
1980
2014
|
// Get error patterns from accumulator
|
|
1981
2015
|
const errorStats = await globalErrorAccumulator.getErrorStats(args.bead_id);
|
|
1982
2016
|
|
|
2017
|
+
// Finalize eval record if project_path and epic_id provided
|
|
2018
|
+
let finalizedRecord: EvalRecord | null = null;
|
|
2019
|
+
if (args.project_path && args.epic_id) {
|
|
2020
|
+
try {
|
|
2021
|
+
const { finalizeEvalRecord } = await import("./eval-capture.js");
|
|
2022
|
+
finalizedRecord = finalizeEvalRecord({
|
|
2023
|
+
epicId: args.epic_id,
|
|
2024
|
+
projectPath: args.project_path,
|
|
2025
|
+
});
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
// Non-fatal - log and continue
|
|
2028
|
+
console.warn("[swarm_record_outcome] Failed to finalize eval record:", error);
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
1983
2032
|
// Generate feedback events for each criterion
|
|
1984
2033
|
const criteriaToScore = args.criteria ?? [
|
|
1985
2034
|
"type_safe",
|
|
@@ -2030,6 +2079,7 @@ export const swarm_record_outcome = tool({
|
|
|
2030
2079
|
accumulated_errors: errorStats.total,
|
|
2031
2080
|
unresolved_errors: errorStats.unresolved,
|
|
2032
2081
|
},
|
|
2082
|
+
finalized_eval_record: finalizedRecord || undefined,
|
|
2033
2083
|
note: "Feedback events should be stored for criterion weight calculation. Use learning.ts functions to apply weights.",
|
|
2034
2084
|
},
|
|
2035
2085
|
null,
|
|
@@ -2087,12 +2137,28 @@ export function extractTechStack(task: string): string[] {
|
|
|
2087
2137
|
return Array.from(detected);
|
|
2088
2138
|
}
|
|
2089
2139
|
|
|
2140
|
+
/**
|
|
2141
|
+
* Spawn instruction for a researcher worker
|
|
2142
|
+
*/
|
|
2143
|
+
export interface ResearchSpawnInstruction {
|
|
2144
|
+
/** Unique ID for this research task */
|
|
2145
|
+
research_id: string;
|
|
2146
|
+
/** Technology being researched */
|
|
2147
|
+
tech: string;
|
|
2148
|
+
/** Full prompt for the researcher agent */
|
|
2149
|
+
prompt: string;
|
|
2150
|
+
/** Agent type for the Task tool */
|
|
2151
|
+
subagent_type: "swarm/researcher";
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2090
2154
|
/**
|
|
2091
2155
|
* Research result from documentation discovery phase
|
|
2092
2156
|
*/
|
|
2093
2157
|
export interface ResearchResult {
|
|
2094
2158
|
/** Technologies identified and researched */
|
|
2095
2159
|
tech_stack: string[];
|
|
2160
|
+
/** Spawn instructions for researcher workers */
|
|
2161
|
+
spawn_instructions: ResearchSpawnInstruction[];
|
|
2096
2162
|
/** Summaries keyed by technology name */
|
|
2097
2163
|
summaries: Record<string, string>;
|
|
2098
2164
|
/** Semantic-memory IDs where research is stored */
|
|
@@ -2154,24 +2220,45 @@ export async function runResearchPhase(
|
|
|
2154
2220
|
if (techStack.length === 0) {
|
|
2155
2221
|
return {
|
|
2156
2222
|
tech_stack: [],
|
|
2223
|
+
spawn_instructions: [],
|
|
2157
2224
|
summaries: {},
|
|
2158
2225
|
memory_ids: [],
|
|
2159
2226
|
};
|
|
2160
2227
|
}
|
|
2161
2228
|
|
|
2162
|
-
// Step 2:
|
|
2163
|
-
//
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2229
|
+
// Step 2: Generate spawn instructions for each technology
|
|
2230
|
+
// The coordinator will use these to spawn researcher workers via Task()
|
|
2231
|
+
const spawnInstructions: ResearchSpawnInstruction[] = [];
|
|
2232
|
+
|
|
2233
|
+
for (const tech of techStack) {
|
|
2234
|
+
// Generate unique research ID
|
|
2235
|
+
const researchId = `research-${tech}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2236
|
+
|
|
2237
|
+
// Generate researcher prompt
|
|
2238
|
+
const prompt = formatResearcherPrompt({
|
|
2239
|
+
research_id: researchId,
|
|
2240
|
+
epic_id: "standalone-research", // No epic context for standalone research
|
|
2241
|
+
tech_stack: [tech], // Single tech per researcher
|
|
2242
|
+
project_path: projectPath,
|
|
2243
|
+
check_upgrades: options?.checkUpgrades ?? false,
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
spawnInstructions.push({
|
|
2247
|
+
research_id: researchId,
|
|
2248
|
+
tech,
|
|
2249
|
+
prompt,
|
|
2250
|
+
subagent_type: "swarm/researcher",
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2170
2253
|
|
|
2254
|
+
// Step 3: Return spawn instructions for coordinator
|
|
2255
|
+
// The coordinator will spawn Task() agents using these instructions
|
|
2256
|
+
// and collect results from swarm mail after completion
|
|
2171
2257
|
return {
|
|
2172
2258
|
tech_stack: techStack,
|
|
2173
|
-
|
|
2174
|
-
|
|
2259
|
+
spawn_instructions: spawnInstructions,
|
|
2260
|
+
summaries: {}, // Will be populated by coordinator after researchers complete
|
|
2261
|
+
memory_ids: [], // Will be populated by coordinator after researchers store in semantic-memory
|
|
2175
2262
|
};
|
|
2176
2263
|
}
|
|
2177
2264
|
|
|
@@ -9,8 +9,10 @@ import { describe, expect, test } from "bun:test";
|
|
|
9
9
|
import {
|
|
10
10
|
formatSubtaskPromptV2,
|
|
11
11
|
formatResearcherPrompt,
|
|
12
|
+
formatCoordinatorPrompt,
|
|
12
13
|
SUBTASK_PROMPT_V2,
|
|
13
14
|
RESEARCHER_PROMPT,
|
|
15
|
+
COORDINATOR_PROMPT,
|
|
14
16
|
} from "./swarm-prompts";
|
|
15
17
|
|
|
16
18
|
describe("SUBTASK_PROMPT_V2", () => {
|
|
@@ -818,3 +820,122 @@ describe("swarm_spawn_retry tool", () => {
|
|
|
818
820
|
expect(parsed.prompt).toMatch(/preserve.*working|fix.*while preserving/i);
|
|
819
821
|
});
|
|
820
822
|
});
|
|
823
|
+
|
|
824
|
+
describe("COORDINATOR_PROMPT", () => {
|
|
825
|
+
test("constant exists and is exported", () => {
|
|
826
|
+
expect(COORDINATOR_PROMPT).toBeDefined();
|
|
827
|
+
expect(typeof COORDINATOR_PROMPT).toBe("string");
|
|
828
|
+
expect(COORDINATOR_PROMPT.length).toBeGreaterThan(100);
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
test("contains all phase headers (0-8)", () => {
|
|
832
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 0:");
|
|
833
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 1:");
|
|
834
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 2:");
|
|
835
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 3:");
|
|
836
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 4:");
|
|
837
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 5:");
|
|
838
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 6:");
|
|
839
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 7:");
|
|
840
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 8:");
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
test("contains Phase 1.5: Research Phase section", () => {
|
|
844
|
+
expect(COORDINATOR_PROMPT).toContain("Phase 1.5:");
|
|
845
|
+
expect(COORDINATOR_PROMPT).toMatch(/Phase 1\.5:.*Research/i);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("Phase 1.5 documents swarm_spawn_researcher usage", () => {
|
|
849
|
+
// Extract Phase 1.5 section
|
|
850
|
+
const phase15Match = COORDINATOR_PROMPT.match(/Phase 1\.5:[\s\S]*?Phase 2:/);
|
|
851
|
+
expect(phase15Match).not.toBeNull();
|
|
852
|
+
if (!phase15Match) return;
|
|
853
|
+
const phase15Content = phase15Match[0];
|
|
854
|
+
|
|
855
|
+
expect(phase15Content).toContain("swarm_spawn_researcher");
|
|
856
|
+
expect(phase15Content).toContain("Task(subagent_type=\"swarm/researcher\"");
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
test("has section explicitly forbidding direct research tool calls", () => {
|
|
860
|
+
expect(COORDINATOR_PROMPT).toMatch(/NEVER.*direct|forbidden.*tools|do not call directly/i);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
test("forbidden tools section lists all prohibited tools", () => {
|
|
864
|
+
const forbiddenTools = [
|
|
865
|
+
"repo-crawl_",
|
|
866
|
+
"repo-autopsy_",
|
|
867
|
+
"webfetch",
|
|
868
|
+
"fetch_fetch",
|
|
869
|
+
"context7_",
|
|
870
|
+
"pdf-brain_search",
|
|
871
|
+
"pdf-brain_read"
|
|
872
|
+
];
|
|
873
|
+
|
|
874
|
+
for (const tool of forbiddenTools) {
|
|
875
|
+
expect(COORDINATOR_PROMPT).toContain(tool);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
test("forbidden tools section explains to use swarm_spawn_researcher instead", () => {
|
|
880
|
+
// Find the forbidden tools section
|
|
881
|
+
const forbiddenMatch = COORDINATOR_PROMPT.match(/(FORBIDDEN.*for coordinators|NEVER.*FETCH.*DIRECTLY)[\s\S]{0,500}swarm_spawn_researcher/i);
|
|
882
|
+
expect(forbiddenMatch).not.toBeNull();
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
test("contains coordinator role boundaries section", () => {
|
|
886
|
+
expect(COORDINATOR_PROMPT).toContain("Coordinator Role Boundaries");
|
|
887
|
+
expect(COORDINATOR_PROMPT).toMatch(/COORDINATORS NEVER.*EXECUTE.*WORK/i);
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test("contains MANDATORY review loop section", () => {
|
|
891
|
+
expect(COORDINATOR_PROMPT).toContain("MANDATORY Review Loop");
|
|
892
|
+
expect(COORDINATOR_PROMPT).toContain("swarm_review");
|
|
893
|
+
expect(COORDINATOR_PROMPT).toContain("swarm_review_feedback");
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
test("Phase 1.5 positioned between Phase 1 (Initialize) and Phase 2 (Knowledge)", () => {
|
|
897
|
+
const phase1Pos = COORDINATOR_PROMPT.indexOf("Phase 1:");
|
|
898
|
+
const phase15Pos = COORDINATOR_PROMPT.indexOf("Phase 1.5:");
|
|
899
|
+
const phase2Pos = COORDINATOR_PROMPT.indexOf("Phase 2:");
|
|
900
|
+
|
|
901
|
+
expect(phase15Pos).toBeGreaterThan(phase1Pos);
|
|
902
|
+
expect(phase15Pos).toBeLessThan(phase2Pos);
|
|
903
|
+
});
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
describe("formatCoordinatorPrompt", () => {
|
|
907
|
+
test("function exists and returns string", () => {
|
|
908
|
+
expect(formatCoordinatorPrompt).toBeDefined();
|
|
909
|
+
const result = formatCoordinatorPrompt({ task: "test task", projectPath: "/test" });
|
|
910
|
+
expect(typeof result).toBe("string");
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
test("substitutes {task} placeholder", () => {
|
|
914
|
+
const result = formatCoordinatorPrompt({
|
|
915
|
+
task: "Implement auth",
|
|
916
|
+
projectPath: "/test"
|
|
917
|
+
});
|
|
918
|
+
expect(result).toContain("Implement auth");
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
test("substitutes {project_path} placeholder", () => {
|
|
922
|
+
const result = formatCoordinatorPrompt({
|
|
923
|
+
task: "test",
|
|
924
|
+
projectPath: "/Users/joel/my-project"
|
|
925
|
+
});
|
|
926
|
+
expect(result).toContain("/Users/joel/my-project");
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
test("returns complete prompt with all phases", () => {
|
|
930
|
+
const result = formatCoordinatorPrompt({
|
|
931
|
+
task: "test",
|
|
932
|
+
projectPath: "/test"
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// Should contain all phase headers
|
|
936
|
+
for (let i = 0; i <= 8; i++) {
|
|
937
|
+
expect(result).toContain(`Phase ${i}:`);
|
|
938
|
+
}
|
|
939
|
+
expect(result).toContain("Phase 1.5:");
|
|
940
|
+
});
|
|
941
|
+
});
|
package/src/swarm-prompts.ts
CHANGED
|
@@ -576,8 +576,289 @@ Other cell operations:
|
|
|
576
576
|
Begin now.`;
|
|
577
577
|
|
|
578
578
|
/**
|
|
579
|
-
*
|
|
580
|
-
*
|
|
579
|
+
* Coordinator Agent Prompt Template
|
|
580
|
+
*
|
|
581
|
+
* Used by the /swarm command to instruct coordinators on their role.
|
|
582
|
+
* Coordinators NEVER execute work directly - they clarify, decompose, spawn workers, and review.
|
|
583
|
+
*
|
|
584
|
+
* Key sections:
|
|
585
|
+
* - Role boundaries (what coordinators NEVER do)
|
|
586
|
+
* - Phase 1.5: Research Phase (spawn researchers, DON'T fetch docs directly)
|
|
587
|
+
* - Forbidden tools (repo-crawl, webfetch, context7, pdf-brain_search)
|
|
588
|
+
* - MANDATORY review loop after each worker completes
|
|
589
|
+
*
|
|
590
|
+
* Placeholders:
|
|
591
|
+
* - {task} - The task description from user
|
|
592
|
+
* - {project_path} - Absolute path to project root
|
|
593
|
+
*/
|
|
594
|
+
export const COORDINATOR_PROMPT = `You are a swarm coordinator. Your job is to clarify the task, decompose it into cells, and spawn parallel agents.
|
|
595
|
+
|
|
596
|
+
## Task
|
|
597
|
+
|
|
598
|
+
{task}
|
|
599
|
+
|
|
600
|
+
## CRITICAL: Coordinator Role Boundaries
|
|
601
|
+
|
|
602
|
+
**⚠️ COORDINATORS NEVER EXECUTE WORK DIRECTLY**
|
|
603
|
+
|
|
604
|
+
Your role is **ONLY** to:
|
|
605
|
+
1. **Clarify** - Ask questions to understand scope
|
|
606
|
+
2. **Decompose** - Break into subtasks with clear boundaries
|
|
607
|
+
3. **Spawn** - Create worker agents for ALL subtasks
|
|
608
|
+
4. **Monitor** - Check progress, unblock, mediate conflicts
|
|
609
|
+
5. **Verify** - Confirm completion, run final checks
|
|
610
|
+
|
|
611
|
+
**YOU DO NOT:**
|
|
612
|
+
- Read implementation files (only metadata/structure for planning)
|
|
613
|
+
- Edit code directly
|
|
614
|
+
- Run tests yourself (workers run tests)
|
|
615
|
+
- Implement features
|
|
616
|
+
- Fix bugs inline
|
|
617
|
+
- Make "quick fixes" yourself
|
|
618
|
+
|
|
619
|
+
**ALWAYS spawn workers, even for sequential tasks.** Sequential just means spawn them in order and wait for each to complete before spawning the next.
|
|
620
|
+
|
|
621
|
+
### Why This Matters
|
|
622
|
+
|
|
623
|
+
| Coordinator Work | Worker Work | Consequence of Mixing |
|
|
624
|
+
|-----------------|-------------|----------------------|
|
|
625
|
+
| Sonnet context ($$$) | Disposable context | Expensive context waste |
|
|
626
|
+
| Long-lived state | Task-scoped state | Context exhaustion |
|
|
627
|
+
| Orchestration concerns | Implementation concerns | Mixed concerns |
|
|
628
|
+
| No checkpoints | Checkpoints enabled | No recovery |
|
|
629
|
+
| No learning signals | Outcomes tracked | No improvement |
|
|
630
|
+
|
|
631
|
+
## CRITICAL: NEVER Fetch Documentation Directly
|
|
632
|
+
|
|
633
|
+
**⚠️ COORDINATORS DO NOT CALL RESEARCH TOOLS DIRECTLY**
|
|
634
|
+
|
|
635
|
+
The following tools are **FORBIDDEN** for coordinators to call:
|
|
636
|
+
|
|
637
|
+
- \`repo-crawl_file\`, \`repo-crawl_readme\`, \`repo-crawl_search\`, \`repo-crawl_structure\`, \`repo-crawl_tree\`
|
|
638
|
+
- \`repo-autopsy_*\` (all variants)
|
|
639
|
+
- \`webfetch\`, \`fetch_fetch\`
|
|
640
|
+
- \`context7_resolve-library-id\`, \`context7_get-library-docs\`
|
|
641
|
+
- \`pdf-brain_search\`, \`pdf-brain_read\`
|
|
642
|
+
|
|
643
|
+
**WHY?** These tools dump massive context that exhausts your expensive Sonnet context. Your job is orchestration, not research.
|
|
644
|
+
|
|
645
|
+
**INSTEAD:** Use \`swarm_spawn_researcher\` (see Phase 1.5 below) to spawn a researcher worker who:
|
|
646
|
+
- Fetches documentation in disposable context
|
|
647
|
+
- Stores full details in semantic-memory
|
|
648
|
+
- Returns a condensed summary for shared_context
|
|
649
|
+
|
|
650
|
+
## Workflow
|
|
651
|
+
|
|
652
|
+
### Phase 0: Socratic Planning (INTERACTIVE - unless --fast)
|
|
653
|
+
|
|
654
|
+
**Before decomposing, clarify the task with the user.**
|
|
655
|
+
|
|
656
|
+
Check for flags in the task:
|
|
657
|
+
- \`--fast\` → Skip questions, use reasonable defaults
|
|
658
|
+
- \`--auto\` → Zero interaction, heuristic decisions
|
|
659
|
+
- \`--confirm-only\` → Show plan, get yes/no only
|
|
660
|
+
|
|
661
|
+
**Default (no flags): Full Socratic Mode**
|
|
662
|
+
|
|
663
|
+
1. **Analyze task for ambiguity:**
|
|
664
|
+
- Scope unclear? (what's included/excluded)
|
|
665
|
+
- Strategy unclear? (file-based vs feature-based)
|
|
666
|
+
- Dependencies unclear? (what needs to exist first)
|
|
667
|
+
- Success criteria unclear? (how do we know it's done)
|
|
668
|
+
|
|
669
|
+
2. **If clarification needed, ask ONE question at a time:**
|
|
670
|
+
\`\`\`
|
|
671
|
+
The task "<task>" needs clarification before I can decompose it.
|
|
672
|
+
|
|
673
|
+
**Question:** <specific question>
|
|
674
|
+
|
|
675
|
+
Options:
|
|
676
|
+
a) <option 1> - <tradeoff>
|
|
677
|
+
b) <option 2> - <tradeoff>
|
|
678
|
+
c) <option 3> - <tradeoff>
|
|
679
|
+
|
|
680
|
+
I'd recommend (b) because <reason>. Which approach?
|
|
681
|
+
\`\`\`
|
|
682
|
+
|
|
683
|
+
3. **Wait for user response before proceeding**
|
|
684
|
+
|
|
685
|
+
4. **Iterate if needed** (max 2-3 questions)
|
|
686
|
+
|
|
687
|
+
**Rules:**
|
|
688
|
+
- ONE question at a time - don't overwhelm
|
|
689
|
+
- Offer concrete options - not open-ended
|
|
690
|
+
- Lead with recommendation - save cognitive load
|
|
691
|
+
- Wait for answer - don't assume
|
|
692
|
+
|
|
693
|
+
### Phase 1: Initialize
|
|
694
|
+
\`swarmmail_init(project_path="{project_path}", task_description="Swarm: {task}")\`
|
|
695
|
+
|
|
696
|
+
### Phase 1.5: Research Phase (FOR COMPLEX TASKS)
|
|
697
|
+
|
|
698
|
+
**⚠️ If the task requires understanding unfamiliar technologies, APIs, or libraries, spawn a researcher FIRST.**
|
|
699
|
+
|
|
700
|
+
**DO NOT call documentation tools directly.** Instead:
|
|
701
|
+
|
|
702
|
+
\`\`\`
|
|
703
|
+
// 1. Spawn researcher with explicit tech stack
|
|
704
|
+
swarm_spawn_researcher(
|
|
705
|
+
research_id="research-nextjs-cache-components",
|
|
706
|
+
epic_id="<epic-id>",
|
|
707
|
+
tech_stack=["Next.js 16 Cache Components", "React Server Components"],
|
|
708
|
+
project_path="{project_path}"
|
|
709
|
+
)
|
|
710
|
+
|
|
711
|
+
// 2. Spawn researcher as Task subagent
|
|
712
|
+
const researchFindings = await Task(subagent_type="swarm/researcher", prompt="<from above>")
|
|
713
|
+
|
|
714
|
+
// 3. Researcher returns condensed summary
|
|
715
|
+
// Use this summary in shared_context for workers
|
|
716
|
+
\`\`\`
|
|
717
|
+
|
|
718
|
+
**When to spawn a researcher:**
|
|
719
|
+
- Task involves unfamiliar framework versions (e.g., Next.js 16 vs 14)
|
|
720
|
+
- Need to compare installed vs latest library APIs
|
|
721
|
+
- Working with experimental/preview features
|
|
722
|
+
- Need architectural guidance from documentation
|
|
723
|
+
|
|
724
|
+
**When NOT to spawn a researcher:**
|
|
725
|
+
- Using well-known stable APIs (React hooks, Express middleware)
|
|
726
|
+
- Task is purely refactoring existing code
|
|
727
|
+
- You already have relevant findings from semantic-memory or CASS
|
|
728
|
+
|
|
729
|
+
**Researcher output:**
|
|
730
|
+
- Full findings stored in semantic-memory (searchable by future agents)
|
|
731
|
+
- Condensed 3-5 bullet summary returned for shared_context
|
|
732
|
+
|
|
733
|
+
### Phase 2: Knowledge Gathering (MANDATORY)
|
|
734
|
+
|
|
735
|
+
**Before decomposing, query ALL knowledge sources:**
|
|
736
|
+
|
|
737
|
+
\`\`\`
|
|
738
|
+
semantic-memory_find(query="<task keywords>", limit=5) # Past learnings
|
|
739
|
+
cass_search(query="<task description>", limit=5) # Similar past tasks
|
|
740
|
+
skills_list() # Available skills
|
|
741
|
+
\`\`\`
|
|
742
|
+
|
|
743
|
+
Synthesize findings into shared_context for workers.
|
|
744
|
+
|
|
745
|
+
### Phase 3: Decompose
|
|
746
|
+
\`\`\`
|
|
747
|
+
swarm_select_strategy(task="<task>")
|
|
748
|
+
swarm_plan_prompt(task="<task>", context="<synthesized knowledge>")
|
|
749
|
+
swarm_validate_decomposition(response="<CellTree JSON>")
|
|
750
|
+
\`\`\`
|
|
751
|
+
|
|
752
|
+
### Phase 4: Create Cells
|
|
753
|
+
\`hive_create_epic(epic_title="<task>", subtasks=[...])\`
|
|
754
|
+
|
|
755
|
+
### Phase 5: DO NOT Reserve Files
|
|
756
|
+
|
|
757
|
+
> **⚠️ Coordinator NEVER reserves files.** Workers reserve their own files.
|
|
758
|
+
> If coordinator reserves, workers get blocked and swarm stalls.
|
|
759
|
+
|
|
760
|
+
### Phase 6: Spawn Workers for ALL Subtasks (MANDATORY)
|
|
761
|
+
|
|
762
|
+
> **⚠️ ALWAYS spawn workers, even for sequential tasks.**
|
|
763
|
+
> - Parallel tasks: Spawn ALL in a single message
|
|
764
|
+
> - Sequential tasks: Spawn one, wait for completion, spawn next
|
|
765
|
+
|
|
766
|
+
**For parallel work:**
|
|
767
|
+
\`\`\`
|
|
768
|
+
// Single message with multiple Task calls
|
|
769
|
+
swarm_spawn_subtask(bead_id_1, epic_id, title_1, files_1, shared_context, project_path="{project_path}")
|
|
770
|
+
Task(subagent_type="swarm/worker", prompt="<from above>")
|
|
771
|
+
swarm_spawn_subtask(bead_id_2, epic_id, title_2, files_2, shared_context, project_path="{project_path}")
|
|
772
|
+
Task(subagent_type="swarm/worker", prompt="<from above>")
|
|
773
|
+
\`\`\`
|
|
774
|
+
|
|
775
|
+
**For sequential work:**
|
|
776
|
+
\`\`\`
|
|
777
|
+
// Spawn worker 1, wait for completion
|
|
778
|
+
swarm_spawn_subtask(bead_id_1, ...)
|
|
779
|
+
const result1 = await Task(subagent_type="swarm/worker", prompt="<from above>")
|
|
780
|
+
|
|
781
|
+
// THEN spawn worker 2 with context from worker 1
|
|
782
|
+
swarm_spawn_subtask(bead_id_2, ..., shared_context="Worker 1 completed: " + result1)
|
|
783
|
+
const result2 = await Task(subagent_type="swarm/worker", prompt="<from above>")
|
|
784
|
+
\`\`\`
|
|
785
|
+
|
|
786
|
+
**NEVER do the work yourself.** Even if it seems faster, spawn a worker.
|
|
787
|
+
|
|
788
|
+
**IMPORTANT:** Pass \`project_path\` to \`swarm_spawn_subtask\` so workers can call \`swarmmail_init\`.
|
|
789
|
+
|
|
790
|
+
### Phase 7: MANDATORY Review Loop (NON-NEGOTIABLE)
|
|
791
|
+
|
|
792
|
+
**⚠️ AFTER EVERY Task() RETURNS, YOU MUST:**
|
|
793
|
+
|
|
794
|
+
1. **CHECK INBOX** - Worker may have sent messages
|
|
795
|
+
\`swarmmail_inbox()\`
|
|
796
|
+
\`swarmmail_read_message(message_id=N)\`
|
|
797
|
+
|
|
798
|
+
2. **REVIEW WORK** - Generate review with diff
|
|
799
|
+
\`swarm_review(project_key, epic_id, task_id, files_touched)\`
|
|
800
|
+
|
|
801
|
+
3. **EVALUATE** - Does it meet epic goals?
|
|
802
|
+
- Fulfills subtask requirements?
|
|
803
|
+
- Serves overall epic goal?
|
|
804
|
+
- Enables downstream tasks?
|
|
805
|
+
- Type safety, no obvious bugs?
|
|
806
|
+
|
|
807
|
+
4. **SEND FEEDBACK** - Approve or request changes
|
|
808
|
+
\`swarm_review_feedback(project_key, task_id, worker_id, status, issues)\`
|
|
809
|
+
|
|
810
|
+
**If approved:**
|
|
811
|
+
- Close cell, spawn next worker
|
|
812
|
+
|
|
813
|
+
**If needs_changes:**
|
|
814
|
+
- \`swarm_review_feedback\` returns \`retry_context\` (NOT sends message - worker is dead)
|
|
815
|
+
- Generate retry prompt: \`swarm_spawn_retry(retry_context)\`
|
|
816
|
+
- Spawn NEW worker with Task() using retry prompt
|
|
817
|
+
- Max 3 attempts before marking task blocked
|
|
818
|
+
|
|
819
|
+
**If 3 failures:**
|
|
820
|
+
- Mark task blocked, escalate to human
|
|
821
|
+
|
|
822
|
+
5. **ONLY THEN** - Spawn next worker or complete
|
|
823
|
+
|
|
824
|
+
**DO NOT skip this. DO NOT batch reviews. Review EACH worker IMMEDIATELY after return.**
|
|
825
|
+
|
|
826
|
+
**Intervene if:**
|
|
827
|
+
- Worker blocked >5min → unblock or reassign
|
|
828
|
+
- File conflicts → mediate between workers
|
|
829
|
+
- Scope creep → approve or reject expansion
|
|
830
|
+
- Review fails 3x → mark task blocked, escalate to human
|
|
831
|
+
|
|
832
|
+
### Phase 8: Complete
|
|
833
|
+
\`\`\`
|
|
834
|
+
# After all workers complete and reviews pass:
|
|
835
|
+
hive_sync() # Sync all cells to git
|
|
836
|
+
# Coordinator does NOT call swarm_complete - workers do that
|
|
837
|
+
\`\`\`
|
|
838
|
+
|
|
839
|
+
## Strategy Reference
|
|
840
|
+
|
|
841
|
+
| Strategy | Best For | Keywords |
|
|
842
|
+
| -------------- | ------------------------ | -------------------------------------- |
|
|
843
|
+
| file-based | Refactoring, migrations | refactor, migrate, rename, update all |
|
|
844
|
+
| feature-based | New features | add, implement, build, create, feature |
|
|
845
|
+
| risk-based | Bug fixes, security | fix, bug, security, critical, urgent |
|
|
846
|
+
| research-based | Investigation, discovery | research, investigate, explore, learn |
|
|
847
|
+
|
|
848
|
+
## Flag Reference
|
|
849
|
+
|
|
850
|
+
| Flag | Effect |
|
|
851
|
+
|------|--------|
|
|
852
|
+
| \`--fast\` | Skip Socratic questions, use defaults |
|
|
853
|
+
| \`--auto\` | Zero interaction, heuristic decisions |
|
|
854
|
+
| \`--confirm-only\` | Show plan, get yes/no only |
|
|
855
|
+
|
|
856
|
+
Begin with Phase 0 (Socratic Planning) unless \`--fast\` or \`--auto\` flag is present.
|
|
857
|
+
`;
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* Researcher Agent Prompt Template
|
|
861
|
+
*
|
|
581
862
|
* Spawned BEFORE decomposition to gather technology documentation.
|
|
582
863
|
* Researchers receive an EXPLICIT list of technologies to research from the coordinator.
|
|
583
864
|
* They dynamically discover WHAT TOOLS are available to fetch docs.
|
|
@@ -844,6 +1125,18 @@ export function formatResearcherPrompt(params: {
|
|
|
844
1125
|
.replace("{check_upgrades}", upgradesMode);
|
|
845
1126
|
}
|
|
846
1127
|
|
|
1128
|
+
/**
|
|
1129
|
+
* Format the coordinator prompt with task and project path substitution
|
|
1130
|
+
*/
|
|
1131
|
+
export function formatCoordinatorPrompt(params: {
|
|
1132
|
+
task: string;
|
|
1133
|
+
projectPath: string;
|
|
1134
|
+
}): string {
|
|
1135
|
+
return COORDINATOR_PROMPT
|
|
1136
|
+
.replace(/{task}/g, params.task)
|
|
1137
|
+
.replace(/{project_path}/g, params.projectPath);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
847
1140
|
/**
|
|
848
1141
|
* Format the V2 subtask prompt for a specific agent
|
|
849
1142
|
*/
|
|
@@ -1065,7 +1358,7 @@ export const swarm_spawn_subtask = tool({
|
|
|
1065
1358
|
.optional()
|
|
1066
1359
|
.describe("Optional explicit model override (auto-selected if not provided)"),
|
|
1067
1360
|
},
|
|
1068
|
-
async execute(args) {
|
|
1361
|
+
async execute(args, _ctx) {
|
|
1069
1362
|
const prompt = formatSubtaskPromptV2({
|
|
1070
1363
|
bead_id: args.bead_id,
|
|
1071
1364
|
epic_id: args.epic_id,
|
|
@@ -1111,7 +1404,7 @@ export const swarm_spawn_subtask = tool({
|
|
|
1111
1404
|
// Capture worker spawn decision
|
|
1112
1405
|
try {
|
|
1113
1406
|
captureCoordinatorEvent({
|
|
1114
|
-
session_id:
|
|
1407
|
+
session_id: _ctx.sessionID || "unknown",
|
|
1115
1408
|
epic_id: args.epic_id,
|
|
1116
1409
|
timestamp: new Date().toISOString(),
|
|
1117
1410
|
event_type: "DECISION",
|