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.
@@ -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
- return `Progress reported: ${args.status}${args.progress_percent !== undefined ? ` (${args.progress_percent}%)` : ""}`;
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
  };
@@ -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 USE SWARM MAIL FOR ALL COORDINATION.** This is non-negotiable.
278
+ **CRITICAL: YOU MUST INITIALIZE SWARM MAIL BEFORE DOING ANY WORK.**
279
279
 
280
- ### Initialize FIRST (before any work)
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
- ### Reserve Files (if not already reserved by coordinator)
282
+ ### Step 1: Initialize (REQUIRED - DO THIS FIRST)
286
283
  \`\`\`
287
- swarmmail_reserve(paths=[...files...], reason="{bead_id}: {subtask_title}")
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: Never work silently. Send progress updates via swarmmail_send every significant milestone.**
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,