opencode-swarm-plugin 0.20.0 → 0.21.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 +202 -0
- package/INTEGRATION_EXAMPLE.md +66 -0
- package/README.md +127 -562
- package/dist/index.js +3842 -2917
- package/dist/plugin.js +3824 -2918
- package/docs/analysis/subagent-coordination-patterns.md +2 -0
- package/evals/README.md +116 -0
- package/evals/evalite.config.ts +15 -0
- package/evals/example.eval.ts +32 -0
- package/evals/fixtures/decomposition-cases.ts +105 -0
- package/evals/lib/data-loader.test.ts +288 -0
- package/evals/lib/data-loader.ts +111 -0
- package/evals/lib/llm.ts +115 -0
- package/evals/scorers/index.ts +200 -0
- package/evals/scorers/outcome-scorers.test.ts +27 -0
- package/evals/scorers/outcome-scorers.ts +349 -0
- package/evals/swarm-decomposition.eval.ts +112 -0
- package/package.json +8 -1
- package/src/beads.ts +49 -0
- package/src/eval-capture.ts +487 -0
- package/src/index.ts +45 -3
- package/src/output-guardrails.test.ts +438 -0
- package/src/output-guardrails.ts +381 -0
- package/src/schemas/index.ts +18 -0
- package/src/schemas/swarm-context.ts +115 -0
- package/src/streams/events.test.ts +296 -0
- package/src/streams/events.ts +93 -0
- package/src/streams/migrations.test.ts +24 -20
- package/src/streams/migrations.ts +51 -0
- package/src/streams/projections.ts +187 -0
- package/src/streams/store.ts +275 -0
- package/src/swarm-orchestrate.ts +430 -1
- package/src/swarm-prompts.ts +84 -12
package/src/swarm-orchestrate.ts
CHANGED
|
@@ -37,6 +37,9 @@ import {
|
|
|
37
37
|
releaseSwarmFiles,
|
|
38
38
|
sendSwarmMessage,
|
|
39
39
|
} from "./streams/swarm-mail";
|
|
40
|
+
import { getAgent } from "./streams/projections";
|
|
41
|
+
import { createEvent } from "./streams/events";
|
|
42
|
+
import { appendEvent } from "./streams/store";
|
|
40
43
|
import {
|
|
41
44
|
addStrike,
|
|
42
45
|
clearStrikes,
|
|
@@ -836,7 +839,74 @@ export const swarm_progress = tool({
|
|
|
836
839
|
importance: args.status === "blocked" ? "high" : "normal",
|
|
837
840
|
});
|
|
838
841
|
|
|
839
|
-
|
|
842
|
+
// Auto-checkpoint at milestone progress (25%, 50%, 75%)
|
|
843
|
+
let checkpointCreated = false;
|
|
844
|
+
if (
|
|
845
|
+
args.progress_percent !== undefined &&
|
|
846
|
+
args.files_touched &&
|
|
847
|
+
args.files_touched.length > 0
|
|
848
|
+
) {
|
|
849
|
+
const milestones = [25, 50, 75];
|
|
850
|
+
if (milestones.includes(args.progress_percent)) {
|
|
851
|
+
try {
|
|
852
|
+
// Create checkpoint event directly (non-fatal if it fails)
|
|
853
|
+
const checkpoint = {
|
|
854
|
+
epic_id: epicId,
|
|
855
|
+
bead_id: args.bead_id,
|
|
856
|
+
strategy: "file-based" as const,
|
|
857
|
+
files: args.files_touched,
|
|
858
|
+
dependencies: [] as string[],
|
|
859
|
+
directives: {},
|
|
860
|
+
recovery: {
|
|
861
|
+
last_checkpoint: Date.now(),
|
|
862
|
+
files_modified: args.files_touched,
|
|
863
|
+
progress_percent: args.progress_percent,
|
|
864
|
+
last_message: args.message,
|
|
865
|
+
},
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const event = createEvent("swarm_checkpointed", {
|
|
869
|
+
project_key: args.project_key,
|
|
870
|
+
...checkpoint,
|
|
871
|
+
});
|
|
872
|
+
await appendEvent(event, args.project_key);
|
|
873
|
+
|
|
874
|
+
// Update swarm_contexts table
|
|
875
|
+
const { getDatabase } = await import("./streams/index");
|
|
876
|
+
const db = await getDatabase(args.project_key);
|
|
877
|
+
const now = Date.now();
|
|
878
|
+
await db.query(
|
|
879
|
+
`INSERT INTO swarm_contexts (id, epic_id, bead_id, strategy, files, dependencies, directives, recovery, created_at, updated_at)
|
|
880
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
881
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
882
|
+
files = EXCLUDED.files,
|
|
883
|
+
recovery = EXCLUDED.recovery,
|
|
884
|
+
updated_at = EXCLUDED.updated_at`,
|
|
885
|
+
[
|
|
886
|
+
args.bead_id,
|
|
887
|
+
epicId,
|
|
888
|
+
args.bead_id,
|
|
889
|
+
checkpoint.strategy,
|
|
890
|
+
JSON.stringify(checkpoint.files),
|
|
891
|
+
JSON.stringify(checkpoint.dependencies),
|
|
892
|
+
JSON.stringify(checkpoint.directives),
|
|
893
|
+
JSON.stringify(checkpoint.recovery),
|
|
894
|
+
now,
|
|
895
|
+
now,
|
|
896
|
+
],
|
|
897
|
+
);
|
|
898
|
+
checkpointCreated = true;
|
|
899
|
+
} catch (error) {
|
|
900
|
+
// Non-fatal - log and continue
|
|
901
|
+
console.warn(
|
|
902
|
+
`[swarm_progress] Auto-checkpoint failed at ${args.progress_percent}%:`,
|
|
903
|
+
error,
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
return `Progress reported: ${args.status}${args.progress_percent !== undefined ? ` (${args.progress_percent}%)` : ""}${checkpointCreated ? " [checkpoint created]" : ""}`;
|
|
840
910
|
},
|
|
841
911
|
});
|
|
842
912
|
|
|
@@ -966,8 +1036,64 @@ export const swarm_complete = tool({
|
|
|
966
1036
|
.describe(
|
|
967
1037
|
"Skip ALL verification (UBS, typecheck, tests). Use sparingly! (default: false)",
|
|
968
1038
|
),
|
|
1039
|
+
planned_files: tool.schema
|
|
1040
|
+
.array(tool.schema.string())
|
|
1041
|
+
.optional()
|
|
1042
|
+
.describe("Files that were originally planned to be modified"),
|
|
1043
|
+
start_time: tool.schema
|
|
1044
|
+
.number()
|
|
1045
|
+
.optional()
|
|
1046
|
+
.describe("Task start timestamp (Unix ms) for duration calculation"),
|
|
1047
|
+
error_count: tool.schema
|
|
1048
|
+
.number()
|
|
1049
|
+
.optional()
|
|
1050
|
+
.describe("Number of errors encountered during task"),
|
|
1051
|
+
retry_count: tool.schema
|
|
1052
|
+
.number()
|
|
1053
|
+
.optional()
|
|
1054
|
+
.describe("Number of retry attempts during task"),
|
|
969
1055
|
},
|
|
970
1056
|
async execute(args) {
|
|
1057
|
+
// Verify agent is registered in swarm-mail
|
|
1058
|
+
// This catches agents who skipped swarmmail_init
|
|
1059
|
+
const projectKey = args.project_key.replace(/\//g, "-").replace(/\\/g, "-");
|
|
1060
|
+
let agentRegistered = false;
|
|
1061
|
+
let registrationWarning = "";
|
|
1062
|
+
|
|
1063
|
+
try {
|
|
1064
|
+
const agent = await getAgent(
|
|
1065
|
+
projectKey,
|
|
1066
|
+
args.agent_name,
|
|
1067
|
+
args.project_key,
|
|
1068
|
+
);
|
|
1069
|
+
agentRegistered = agent !== null;
|
|
1070
|
+
|
|
1071
|
+
if (!agentRegistered) {
|
|
1072
|
+
registrationWarning = `⚠️ WARNING: Agent '${args.agent_name}' was NOT registered in swarm-mail for project '${projectKey}'.
|
|
1073
|
+
|
|
1074
|
+
This usually means you skipped the MANDATORY swarmmail_init step.
|
|
1075
|
+
|
|
1076
|
+
**Impact:**
|
|
1077
|
+
- Your work was not tracked in the coordination system
|
|
1078
|
+
- File reservations may not have been managed
|
|
1079
|
+
- Other agents couldn't coordinate with you
|
|
1080
|
+
- Learning/eval data may be incomplete
|
|
1081
|
+
|
|
1082
|
+
**Next time:** Run swarmmail_init(project_path="${args.project_key}", task_description="<task>") FIRST, before any other work.
|
|
1083
|
+
|
|
1084
|
+
Continuing with completion, but this should be fixed for future subtasks.`;
|
|
1085
|
+
|
|
1086
|
+
console.warn(`[swarm_complete] ${registrationWarning}`);
|
|
1087
|
+
}
|
|
1088
|
+
} catch (error) {
|
|
1089
|
+
// Non-fatal - agent might be using legacy workflow
|
|
1090
|
+
console.warn(
|
|
1091
|
+
`[swarm_complete] Could not verify agent registration:`,
|
|
1092
|
+
error,
|
|
1093
|
+
);
|
|
1094
|
+
registrationWarning = `ℹ️ Could not verify swarm-mail registration (database may not be available). Consider running swarmmail_init next time.`;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
971
1097
|
// Run Verification Gate unless explicitly skipped
|
|
972
1098
|
let verificationResult: VerificationGateResult | null = null;
|
|
973
1099
|
|
|
@@ -1086,6 +1212,34 @@ export const swarm_complete = tool({
|
|
|
1086
1212
|
);
|
|
1087
1213
|
}
|
|
1088
1214
|
|
|
1215
|
+
// Emit SubtaskOutcomeEvent for learning system
|
|
1216
|
+
try {
|
|
1217
|
+
const epicId = args.bead_id.includes(".")
|
|
1218
|
+
? args.bead_id.split(".")[0]
|
|
1219
|
+
: args.bead_id;
|
|
1220
|
+
|
|
1221
|
+
const durationMs = args.start_time ? Date.now() - args.start_time : 0;
|
|
1222
|
+
|
|
1223
|
+
const event = createEvent("subtask_outcome", {
|
|
1224
|
+
project_key: args.project_key,
|
|
1225
|
+
epic_id: epicId,
|
|
1226
|
+
bead_id: args.bead_id,
|
|
1227
|
+
planned_files: args.planned_files || [],
|
|
1228
|
+
actual_files: args.files_touched || [],
|
|
1229
|
+
duration_ms: durationMs,
|
|
1230
|
+
error_count: args.error_count || 0,
|
|
1231
|
+
retry_count: args.retry_count || 0,
|
|
1232
|
+
success: true,
|
|
1233
|
+
});
|
|
1234
|
+
await appendEvent(event, args.project_key);
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
// Non-fatal - log and continue
|
|
1237
|
+
console.warn(
|
|
1238
|
+
"[swarm_complete] Failed to emit SubtaskOutcomeEvent:",
|
|
1239
|
+
error,
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1089
1243
|
// Release file reservations for this agent using embedded swarm-mail
|
|
1090
1244
|
try {
|
|
1091
1245
|
await releaseSwarmFiles({
|
|
@@ -1140,6 +1294,10 @@ export const swarm_complete = tool({
|
|
|
1140
1294
|
closed: true,
|
|
1141
1295
|
reservations_released: true,
|
|
1142
1296
|
message_sent: true,
|
|
1297
|
+
agent_registration: {
|
|
1298
|
+
verified: agentRegistered,
|
|
1299
|
+
warning: registrationWarning || undefined,
|
|
1300
|
+
},
|
|
1143
1301
|
verification_gate: verificationResult
|
|
1144
1302
|
? {
|
|
1145
1303
|
passed: true,
|
|
@@ -1647,6 +1805,275 @@ export const swarm_check_strikes = tool({
|
|
|
1647
1805
|
},
|
|
1648
1806
|
});
|
|
1649
1807
|
|
|
1808
|
+
/**
|
|
1809
|
+
* Swarm context shape stored in swarm_contexts table
|
|
1810
|
+
*/
|
|
1811
|
+
interface SwarmBeadContext {
|
|
1812
|
+
id: string;
|
|
1813
|
+
epic_id: string;
|
|
1814
|
+
bead_id: string;
|
|
1815
|
+
strategy: "file-based" | "feature-based" | "risk-based";
|
|
1816
|
+
files: string[];
|
|
1817
|
+
dependencies: string[];
|
|
1818
|
+
directives: {
|
|
1819
|
+
shared_context?: string;
|
|
1820
|
+
skills_to_load?: string[];
|
|
1821
|
+
coordinator_notes?: string;
|
|
1822
|
+
};
|
|
1823
|
+
recovery: {
|
|
1824
|
+
last_checkpoint: number;
|
|
1825
|
+
files_modified: string[];
|
|
1826
|
+
progress_percent: number;
|
|
1827
|
+
last_message?: string;
|
|
1828
|
+
error_context?: string;
|
|
1829
|
+
};
|
|
1830
|
+
created_at: number;
|
|
1831
|
+
updated_at: number;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
/**
|
|
1835
|
+
* Checkpoint swarm context for recovery
|
|
1836
|
+
*
|
|
1837
|
+
* Records the current state of a subtask to enable recovery after crashes,
|
|
1838
|
+
* context overflows, or agent restarts. Non-fatal errors - logs warnings
|
|
1839
|
+
* and continues if checkpoint fails.
|
|
1840
|
+
*
|
|
1841
|
+
* Integration:
|
|
1842
|
+
* - Called automatically by swarm_progress at milestone thresholds (25%, 50%, 75%)
|
|
1843
|
+
* - Can be called manually by agents at critical points
|
|
1844
|
+
* - Emits SwarmCheckpointedEvent for audit trail
|
|
1845
|
+
* - Updates swarm_contexts table for fast recovery queries
|
|
1846
|
+
*/
|
|
1847
|
+
export const swarm_checkpoint = tool({
|
|
1848
|
+
description:
|
|
1849
|
+
"Checkpoint swarm context for recovery. Records current state for crash recovery. Non-fatal errors.",
|
|
1850
|
+
args: {
|
|
1851
|
+
project_key: tool.schema.string().describe("Project path"),
|
|
1852
|
+
agent_name: tool.schema.string().describe("Agent name"),
|
|
1853
|
+
bead_id: tool.schema.string().describe("Subtask bead ID"),
|
|
1854
|
+
epic_id: tool.schema.string().describe("Epic bead ID"),
|
|
1855
|
+
files_modified: tool.schema
|
|
1856
|
+
.array(tool.schema.string())
|
|
1857
|
+
.describe("Files modified so far"),
|
|
1858
|
+
progress_percent: tool.schema
|
|
1859
|
+
.number()
|
|
1860
|
+
.min(0)
|
|
1861
|
+
.max(100)
|
|
1862
|
+
.describe("Current progress"),
|
|
1863
|
+
directives: tool.schema
|
|
1864
|
+
.object({
|
|
1865
|
+
shared_context: tool.schema.string().optional(),
|
|
1866
|
+
skills_to_load: tool.schema.array(tool.schema.string()).optional(),
|
|
1867
|
+
coordinator_notes: tool.schema.string().optional(),
|
|
1868
|
+
})
|
|
1869
|
+
.optional()
|
|
1870
|
+
.describe("Coordinator directives for this subtask"),
|
|
1871
|
+
error_context: tool.schema
|
|
1872
|
+
.string()
|
|
1873
|
+
.optional()
|
|
1874
|
+
.describe("Error context if checkpoint is during error handling"),
|
|
1875
|
+
},
|
|
1876
|
+
async execute(args) {
|
|
1877
|
+
try {
|
|
1878
|
+
// Build checkpoint data
|
|
1879
|
+
const checkpoint: Omit<
|
|
1880
|
+
SwarmBeadContext,
|
|
1881
|
+
"id" | "created_at" | "updated_at"
|
|
1882
|
+
> = {
|
|
1883
|
+
epic_id: args.epic_id,
|
|
1884
|
+
bead_id: args.bead_id,
|
|
1885
|
+
strategy: "file-based", // TODO: Extract from decomposition metadata
|
|
1886
|
+
files: args.files_modified,
|
|
1887
|
+
dependencies: [], // TODO: Extract from bead metadata
|
|
1888
|
+
directives: args.directives || {},
|
|
1889
|
+
recovery: {
|
|
1890
|
+
last_checkpoint: Date.now(),
|
|
1891
|
+
files_modified: args.files_modified,
|
|
1892
|
+
progress_percent: args.progress_percent,
|
|
1893
|
+
error_context: args.error_context,
|
|
1894
|
+
},
|
|
1895
|
+
};
|
|
1896
|
+
|
|
1897
|
+
// Emit checkpoint event
|
|
1898
|
+
const event = createEvent("swarm_checkpointed", {
|
|
1899
|
+
project_key: args.project_key,
|
|
1900
|
+
epic_id: args.epic_id,
|
|
1901
|
+
bead_id: args.bead_id,
|
|
1902
|
+
strategy: checkpoint.strategy,
|
|
1903
|
+
files: checkpoint.files,
|
|
1904
|
+
dependencies: checkpoint.dependencies,
|
|
1905
|
+
directives: checkpoint.directives,
|
|
1906
|
+
recovery: checkpoint.recovery,
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
await appendEvent(event, args.project_key);
|
|
1910
|
+
|
|
1911
|
+
// Update swarm_contexts table for fast recovery
|
|
1912
|
+
const { getDatabase } = await import("./streams/index");
|
|
1913
|
+
const db = await getDatabase(args.project_key);
|
|
1914
|
+
|
|
1915
|
+
const now = Date.now();
|
|
1916
|
+
await db.query(
|
|
1917
|
+
`INSERT INTO swarm_contexts (id, epic_id, bead_id, strategy, files, dependencies, directives, recovery, created_at, updated_at)
|
|
1918
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
1919
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
1920
|
+
files = EXCLUDED.files,
|
|
1921
|
+
recovery = EXCLUDED.recovery,
|
|
1922
|
+
updated_at = EXCLUDED.updated_at`,
|
|
1923
|
+
[
|
|
1924
|
+
args.bead_id, // Use bead_id as unique ID
|
|
1925
|
+
args.epic_id,
|
|
1926
|
+
args.bead_id,
|
|
1927
|
+
checkpoint.strategy,
|
|
1928
|
+
JSON.stringify(checkpoint.files),
|
|
1929
|
+
JSON.stringify(checkpoint.dependencies),
|
|
1930
|
+
JSON.stringify(checkpoint.directives),
|
|
1931
|
+
JSON.stringify(checkpoint.recovery),
|
|
1932
|
+
now,
|
|
1933
|
+
now,
|
|
1934
|
+
],
|
|
1935
|
+
);
|
|
1936
|
+
|
|
1937
|
+
return JSON.stringify(
|
|
1938
|
+
{
|
|
1939
|
+
success: true,
|
|
1940
|
+
checkpoint_timestamp: now,
|
|
1941
|
+
summary: `Checkpoint saved for ${args.bead_id} at ${args.progress_percent}%`,
|
|
1942
|
+
bead_id: args.bead_id,
|
|
1943
|
+
epic_id: args.epic_id,
|
|
1944
|
+
files_tracked: args.files_modified.length,
|
|
1945
|
+
},
|
|
1946
|
+
null,
|
|
1947
|
+
2,
|
|
1948
|
+
);
|
|
1949
|
+
} catch (error) {
|
|
1950
|
+
// Non-fatal - log warning and continue
|
|
1951
|
+
console.warn(
|
|
1952
|
+
`[swarm_checkpoint] Failed to checkpoint ${args.bead_id}:`,
|
|
1953
|
+
error,
|
|
1954
|
+
);
|
|
1955
|
+
return JSON.stringify(
|
|
1956
|
+
{
|
|
1957
|
+
success: false,
|
|
1958
|
+
warning: "Checkpoint failed but continuing",
|
|
1959
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1960
|
+
bead_id: args.bead_id,
|
|
1961
|
+
note: "This is non-fatal. Work can continue without checkpoint.",
|
|
1962
|
+
},
|
|
1963
|
+
null,
|
|
1964
|
+
2,
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
},
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
/**
|
|
1971
|
+
* Recover swarm context from last checkpoint
|
|
1972
|
+
*
|
|
1973
|
+
* Queries swarm_contexts table for the most recent checkpoint of an epic.
|
|
1974
|
+
* Returns the full context including files, progress, and recovery state.
|
|
1975
|
+
* Emits SwarmRecoveredEvent for audit trail.
|
|
1976
|
+
*
|
|
1977
|
+
* Graceful fallback: Returns { found: false } if no checkpoint exists.
|
|
1978
|
+
*/
|
|
1979
|
+
export const swarm_recover = tool({
|
|
1980
|
+
description:
|
|
1981
|
+
"Recover swarm context from last checkpoint. Returns context or null if not found.",
|
|
1982
|
+
args: {
|
|
1983
|
+
project_key: tool.schema.string().describe("Project path"),
|
|
1984
|
+
epic_id: tool.schema.string().describe("Epic bead ID to recover"),
|
|
1985
|
+
},
|
|
1986
|
+
async execute(args) {
|
|
1987
|
+
try {
|
|
1988
|
+
const { getDatabase } = await import("./streams/index");
|
|
1989
|
+
const db = await getDatabase(args.project_key);
|
|
1990
|
+
|
|
1991
|
+
// Query most recent checkpoint for this epic
|
|
1992
|
+
const result = await db.query<{
|
|
1993
|
+
id: string;
|
|
1994
|
+
epic_id: string;
|
|
1995
|
+
bead_id: string;
|
|
1996
|
+
strategy: string;
|
|
1997
|
+
files: string;
|
|
1998
|
+
dependencies: string;
|
|
1999
|
+
directives: string;
|
|
2000
|
+
recovery: string;
|
|
2001
|
+
created_at: number;
|
|
2002
|
+
updated_at: number;
|
|
2003
|
+
}>(
|
|
2004
|
+
`SELECT * FROM swarm_contexts
|
|
2005
|
+
WHERE epic_id = $1
|
|
2006
|
+
ORDER BY updated_at DESC
|
|
2007
|
+
LIMIT 1`,
|
|
2008
|
+
[args.epic_id],
|
|
2009
|
+
);
|
|
2010
|
+
|
|
2011
|
+
if (result.rows.length === 0) {
|
|
2012
|
+
return JSON.stringify(
|
|
2013
|
+
{
|
|
2014
|
+
found: false,
|
|
2015
|
+
message: `No checkpoint found for epic ${args.epic_id}`,
|
|
2016
|
+
epic_id: args.epic_id,
|
|
2017
|
+
},
|
|
2018
|
+
null,
|
|
2019
|
+
2,
|
|
2020
|
+
);
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
const row = result.rows[0];
|
|
2024
|
+
const context: SwarmBeadContext = {
|
|
2025
|
+
id: row.id,
|
|
2026
|
+
epic_id: row.epic_id,
|
|
2027
|
+
bead_id: row.bead_id,
|
|
2028
|
+
strategy: row.strategy as SwarmBeadContext["strategy"],
|
|
2029
|
+
files: JSON.parse(row.files),
|
|
2030
|
+
dependencies: JSON.parse(row.dependencies),
|
|
2031
|
+
directives: JSON.parse(row.directives),
|
|
2032
|
+
recovery: JSON.parse(row.recovery),
|
|
2033
|
+
created_at: row.created_at,
|
|
2034
|
+
updated_at: row.updated_at,
|
|
2035
|
+
};
|
|
2036
|
+
|
|
2037
|
+
// Emit recovery event
|
|
2038
|
+
const event = createEvent("swarm_recovered", {
|
|
2039
|
+
project_key: args.project_key,
|
|
2040
|
+
epic_id: args.epic_id,
|
|
2041
|
+
bead_id: context.bead_id,
|
|
2042
|
+
recovered_from_checkpoint: context.recovery.last_checkpoint,
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
await appendEvent(event, args.project_key);
|
|
2046
|
+
|
|
2047
|
+
return JSON.stringify(
|
|
2048
|
+
{
|
|
2049
|
+
found: true,
|
|
2050
|
+
context,
|
|
2051
|
+
summary: `Recovered checkpoint from ${new Date(context.updated_at).toISOString()}`,
|
|
2052
|
+
age_seconds: Math.round((Date.now() - context.updated_at) / 1000),
|
|
2053
|
+
},
|
|
2054
|
+
null,
|
|
2055
|
+
2,
|
|
2056
|
+
);
|
|
2057
|
+
} catch (error) {
|
|
2058
|
+
// Graceful fallback
|
|
2059
|
+
console.warn(
|
|
2060
|
+
`[swarm_recover] Failed to recover context for ${args.epic_id}:`,
|
|
2061
|
+
error,
|
|
2062
|
+
);
|
|
2063
|
+
return JSON.stringify(
|
|
2064
|
+
{
|
|
2065
|
+
found: false,
|
|
2066
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2067
|
+
message: `Recovery failed for epic ${args.epic_id}`,
|
|
2068
|
+
epic_id: args.epic_id,
|
|
2069
|
+
},
|
|
2070
|
+
null,
|
|
2071
|
+
2,
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
},
|
|
2075
|
+
});
|
|
2076
|
+
|
|
1650
2077
|
/**
|
|
1651
2078
|
* Learn from completed work and optionally create a skill
|
|
1652
2079
|
*
|
|
@@ -1865,5 +2292,7 @@ export const orchestrateTools = {
|
|
|
1865
2292
|
swarm_get_error_context,
|
|
1866
2293
|
swarm_resolve_error,
|
|
1867
2294
|
swarm_check_strikes,
|
|
2295
|
+
swarm_checkpoint,
|
|
2296
|
+
swarm_recover,
|
|
1868
2297
|
swarm_learn,
|
|
1869
2298
|
};
|
package/src/swarm-prompts.ts
CHANGED
|
@@ -273,20 +273,29 @@ Only modify these files. Need others? Message the coordinator.
|
|
|
273
273
|
|
|
274
274
|
{error_context}
|
|
275
275
|
|
|
276
|
-
## [MANDATORY: SWARM MAIL]
|
|
276
|
+
## [MANDATORY: SWARM MAIL INITIALIZATION]
|
|
277
277
|
|
|
278
|
-
**YOU MUST
|
|
278
|
+
**CRITICAL: YOU MUST INITIALIZE SWARM MAIL BEFORE DOING ANY WORK.**
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
\`\`\`
|
|
282
|
-
swarmmail_init(project_path="$PWD", task_description="{subtask_title}")
|
|
283
|
-
\`\`\`
|
|
280
|
+
This is your FIRST step - before reading files, before planning, before ANY other action.
|
|
284
281
|
|
|
285
|
-
###
|
|
282
|
+
### Step 1: Initialize (REQUIRED - DO THIS FIRST)
|
|
286
283
|
\`\`\`
|
|
287
|
-
|
|
284
|
+
swarmmail_init(project_path="{project_path}", task_description="{bead_id}: {subtask_title}")
|
|
288
285
|
\`\`\`
|
|
289
286
|
|
|
287
|
+
**This registers you with the coordination system and enables:**
|
|
288
|
+
- File reservation tracking
|
|
289
|
+
- Inter-agent communication
|
|
290
|
+
- Progress monitoring
|
|
291
|
+
- Conflict detection
|
|
292
|
+
|
|
293
|
+
**If you skip this step, your work will not be tracked and swarm_complete will fail.**
|
|
294
|
+
|
|
295
|
+
## [SWARM MAIL USAGE]
|
|
296
|
+
|
|
297
|
+
After initialization, use Swarm Mail for coordination:
|
|
298
|
+
|
|
290
299
|
### Check Inbox Regularly
|
|
291
300
|
\`\`\`
|
|
292
301
|
swarmmail_inbox() # Check for coordinator messages
|
|
@@ -340,14 +349,17 @@ As you work, note reusable patterns, best practices, or domain insights:
|
|
|
340
349
|
- Skills make swarms smarter over time
|
|
341
350
|
|
|
342
351
|
## [WORKFLOW]
|
|
343
|
-
1. **swarmmail_init** - Initialize session FIRST
|
|
352
|
+
1. **swarmmail_init** - Initialize session (MANDATORY FIRST STEP)
|
|
344
353
|
2. Read assigned files
|
|
345
354
|
3. Implement changes
|
|
346
355
|
4. **swarmmail_send** - Report progress to coordinator
|
|
347
356
|
5. Verify (typecheck)
|
|
348
357
|
6. **swarm_complete** - Mark done, release reservations
|
|
349
358
|
|
|
350
|
-
**CRITICAL
|
|
359
|
+
**CRITICAL REQUIREMENTS:**
|
|
360
|
+
- Step 1 (swarmmail_init) is NON-NEGOTIABLE - do it before anything else
|
|
361
|
+
- Never work silently - send progress updates via swarmmail_send every significant milestone
|
|
362
|
+
- If you complete without initializing, swarm_complete will detect this and warn/fail
|
|
351
363
|
|
|
352
364
|
Begin now.`;
|
|
353
365
|
|
|
@@ -409,6 +421,12 @@ export function formatSubtaskPromptV2(params: {
|
|
|
409
421
|
shared_context?: string;
|
|
410
422
|
compressed_context?: string;
|
|
411
423
|
error_context?: string;
|
|
424
|
+
project_path?: string;
|
|
425
|
+
recovery_context?: {
|
|
426
|
+
shared_context?: string;
|
|
427
|
+
skills_to_load?: string[];
|
|
428
|
+
coordinator_notes?: string;
|
|
429
|
+
};
|
|
412
430
|
}): string {
|
|
413
431
|
const fileList =
|
|
414
432
|
params.files.length > 0
|
|
@@ -421,8 +439,40 @@ export function formatSubtaskPromptV2(params: {
|
|
|
421
439
|
|
|
422
440
|
const errorSection = params.error_context ? params.error_context : "";
|
|
423
441
|
|
|
442
|
+
// Build recovery context section
|
|
443
|
+
let recoverySection = "";
|
|
444
|
+
if (params.recovery_context) {
|
|
445
|
+
const sections: string[] = [];
|
|
446
|
+
|
|
447
|
+
if (params.recovery_context.shared_context) {
|
|
448
|
+
sections.push(
|
|
449
|
+
`### Recovery Context\n${params.recovery_context.shared_context}`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (
|
|
454
|
+
params.recovery_context.skills_to_load &&
|
|
455
|
+
params.recovery_context.skills_to_load.length > 0
|
|
456
|
+
) {
|
|
457
|
+
sections.push(
|
|
458
|
+
`### Skills to Load\nBefore starting work, load these skills for specialized guidance:\n${params.recovery_context.skills_to_load.map((s) => `- skills_use(name="${s}")`).join("\n")}`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (params.recovery_context.coordinator_notes) {
|
|
463
|
+
sections.push(
|
|
464
|
+
`### Coordinator Notes\n${params.recovery_context.coordinator_notes}`,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (sections.length > 0) {
|
|
469
|
+
recoverySection = `\n## [RECOVERY CONTEXT]\n\n${sections.join("\n\n")}`;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
424
473
|
return SUBTASK_PROMPT_V2.replace(/{bead_id}/g, params.bead_id)
|
|
425
474
|
.replace(/{epic_id}/g, params.epic_id)
|
|
475
|
+
.replace(/{project_path}/g, params.project_path || "$PWD")
|
|
426
476
|
.replace("{subtask_title}", params.subtask_title)
|
|
427
477
|
.replace(
|
|
428
478
|
"{subtask_description}",
|
|
@@ -431,7 +481,7 @@ export function formatSubtaskPromptV2(params: {
|
|
|
431
481
|
.replace("{file_list}", fileList)
|
|
432
482
|
.replace("{shared_context}", params.shared_context || "(none)")
|
|
433
483
|
.replace("{compressed_context}", compressedSection)
|
|
434
|
-
.replace("{error_context}", errorSection);
|
|
484
|
+
.replace("{error_context}", errorSection + recoverySection);
|
|
435
485
|
}
|
|
436
486
|
|
|
437
487
|
/**
|
|
@@ -497,6 +547,10 @@ export const swarm_subtask_prompt = tool({
|
|
|
497
547
|
.string()
|
|
498
548
|
.optional()
|
|
499
549
|
.describe("Context shared across all agents"),
|
|
550
|
+
project_path: tool.schema
|
|
551
|
+
.string()
|
|
552
|
+
.optional()
|
|
553
|
+
.describe("Absolute project path for swarmmail_init"),
|
|
500
554
|
},
|
|
501
555
|
async execute(args) {
|
|
502
556
|
const prompt = formatSubtaskPrompt({
|
|
@@ -521,7 +575,7 @@ export const swarm_subtask_prompt = tool({
|
|
|
521
575
|
*/
|
|
522
576
|
export const swarm_spawn_subtask = tool({
|
|
523
577
|
description:
|
|
524
|
-
"Prepare a subtask for spawning. Returns prompt with Agent Mail/beads instructions.",
|
|
578
|
+
"Prepare a subtask for spawning. Returns prompt with Agent Mail/beads instructions. IMPORTANT: Pass project_path for swarmmail_init.",
|
|
525
579
|
args: {
|
|
526
580
|
bead_id: tool.schema.string().describe("Subtask bead ID"),
|
|
527
581
|
epic_id: tool.schema.string().describe("Parent epic bead ID"),
|
|
@@ -537,6 +591,20 @@ export const swarm_spawn_subtask = tool({
|
|
|
537
591
|
.string()
|
|
538
592
|
.optional()
|
|
539
593
|
.describe("Context shared across all agents"),
|
|
594
|
+
project_path: tool.schema
|
|
595
|
+
.string()
|
|
596
|
+
.optional()
|
|
597
|
+
.describe(
|
|
598
|
+
"Absolute project path for swarmmail_init (REQUIRED for tracking)",
|
|
599
|
+
),
|
|
600
|
+
recovery_context: tool.schema
|
|
601
|
+
.object({
|
|
602
|
+
shared_context: tool.schema.string().optional(),
|
|
603
|
+
skills_to_load: tool.schema.array(tool.schema.string()).optional(),
|
|
604
|
+
coordinator_notes: tool.schema.string().optional(),
|
|
605
|
+
})
|
|
606
|
+
.optional()
|
|
607
|
+
.describe("Recovery context from checkpoint compaction"),
|
|
540
608
|
},
|
|
541
609
|
async execute(args) {
|
|
542
610
|
const prompt = formatSubtaskPromptV2({
|
|
@@ -546,6 +614,8 @@ export const swarm_spawn_subtask = tool({
|
|
|
546
614
|
subtask_description: args.subtask_description || "",
|
|
547
615
|
files: args.files,
|
|
548
616
|
shared_context: args.shared_context,
|
|
617
|
+
project_path: args.project_path,
|
|
618
|
+
recovery_context: args.recovery_context,
|
|
549
619
|
});
|
|
550
620
|
|
|
551
621
|
return JSON.stringify(
|
|
@@ -554,6 +624,8 @@ export const swarm_spawn_subtask = tool({
|
|
|
554
624
|
bead_id: args.bead_id,
|
|
555
625
|
epic_id: args.epic_id,
|
|
556
626
|
files: args.files,
|
|
627
|
+
project_path: args.project_path,
|
|
628
|
+
recovery_context: args.recovery_context,
|
|
557
629
|
},
|
|
558
630
|
null,
|
|
559
631
|
2,
|