opencode-swarm-plugin 0.35.0 → 0.36.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.
@@ -236,29 +236,30 @@ interface ToolPart {
236
236
  /**
237
237
  * Tool state (completed tools have input/output we need)
238
238
  */
239
- type ToolState = {
240
- status: "completed";
241
- input: { [key: string]: unknown };
242
- output: string;
243
- title: string;
244
- metadata: { [key: string]: unknown };
245
- time: { start: number; end: number };
246
- } | {
247
- status: string;
248
- [key: string]: unknown;
249
- };
239
+ type ToolState =
240
+ | {
241
+ status: "completed";
242
+ input: { [key: string]: unknown };
243
+ output: string;
244
+ title: string;
245
+ metadata: { [key: string]: unknown };
246
+ time: { start: number; end: number };
247
+ }
248
+ | {
249
+ status: string;
250
+ [key: string]: unknown;
251
+ };
250
252
 
251
253
  /**
252
254
  * SDK Client type (minimal interface for scanSessionMessages)
255
+ *
256
+ * The actual SDK client uses a more complex Options-based API:
257
+ * client.session.messages({ path: { id: sessionID }, query: { limit } })
258
+ *
259
+ * We accept `unknown` and handle the type internally to avoid
260
+ * tight coupling to SDK internals.
253
261
  */
254
- interface OpencodeClient {
255
- session: {
256
- messages: (opts: { sessionID: string; limit?: number }) => Promise<{
257
- info: { id: string; sessionID: string };
258
- parts: ToolPart[];
259
- }[]>;
260
- };
261
- }
262
+ export type OpencodeClient = unknown;
262
263
 
263
264
  /**
264
265
  * Scanned swarm state extracted from session messages
@@ -268,29 +269,32 @@ export interface ScannedSwarmState {
268
269
  epicTitle?: string;
269
270
  projectPath?: string;
270
271
  agentName?: string;
271
- subtasks: Map<string, { title: string; status: string; worker?: string; files?: string[] }>;
272
+ subtasks: Map<
273
+ string,
274
+ { title: string; status: string; worker?: string; files?: string[] }
275
+ >;
272
276
  lastAction?: { tool: string; args: unknown; timestamp: number };
273
277
  }
274
278
 
275
279
  /**
276
280
  * Scan session messages for swarm state using SDK client
277
- *
281
+ *
278
282
  * Extracts swarm coordination state from actual tool calls:
279
283
  * - swarm_spawn_subtask → subtask tracking
280
284
  * - swarmmail_init → agent name, project path
281
285
  * - hive_create_epic → epic ID and title
282
286
  * - swarm_status → epic reference
283
287
  * - swarm_complete → subtask completion
284
- *
288
+ *
285
289
  * @param client - OpenCode SDK client (undefined if not available)
286
290
  * @param sessionID - Session to scan
287
291
  * @param limit - Max messages to fetch (default 100)
288
292
  * @returns Extracted swarm state
289
293
  */
290
294
  export async function scanSessionMessages(
291
- client: OpencodeClient | undefined,
295
+ client: OpencodeClient,
292
296
  sessionID: string,
293
- limit: number = 100
297
+ limit: number = 100,
294
298
  ): Promise<ScannedSwarmState> {
295
299
  const state: ScannedSwarmState = {
296
300
  subtasks: new Map(),
@@ -301,7 +305,22 @@ export async function scanSessionMessages(
301
305
  }
302
306
 
303
307
  try {
304
- const messages = await client.session.messages({ sessionID, limit });
308
+ // SDK client uses Options-based API: { path: { id }, query: { limit } }
309
+ const sdkClient = client as {
310
+ session: {
311
+ messages: (opts: {
312
+ path: { id: string };
313
+ query?: { limit?: number };
314
+ }) => Promise<{ data?: Array<{ info: unknown; parts: ToolPart[] }> }>;
315
+ };
316
+ };
317
+
318
+ const response = await sdkClient.session.messages({
319
+ path: { id: sessionID },
320
+ query: { limit },
321
+ });
322
+
323
+ const messages = response.data || [];
305
324
 
306
325
  for (const message of messages) {
307
326
  for (const part of message.parts) {
@@ -310,7 +329,10 @@ export async function scanSessionMessages(
310
329
  }
311
330
 
312
331
  const { tool, state: toolState } = part;
313
- const { input, output, time } = toolState as Extract<ToolState, { status: "completed" }>;
332
+ const { input, output, time } = toolState as Extract<
333
+ ToolState,
334
+ { status: "completed" }
335
+ >;
314
336
 
315
337
  // Track last action
316
338
  state.lastAction = {
@@ -407,12 +429,102 @@ export async function scanSessionMessages(
407
429
  }
408
430
  }
409
431
  } catch (error) {
432
+ getLog().debug(
433
+ {
434
+ error: error instanceof Error ? error.message : String(error),
435
+ },
436
+ "SDK message scanning failed",
437
+ );
410
438
  // SDK not available or error fetching messages - return what we have
411
439
  }
412
440
 
413
441
  return state;
414
442
  }
415
443
 
444
+ /**
445
+ * Build dynamic swarm state from scanned messages (more precise than hive detection)
446
+ */
447
+ function buildDynamicSwarmStateFromScanned(
448
+ scanned: ScannedSwarmState,
449
+ detected: SwarmState,
450
+ ): string {
451
+ const parts: string[] = [];
452
+
453
+ parts.push("## 🐝 Current Swarm State\n");
454
+
455
+ // Prefer scanned data over detected
456
+ const epicId = scanned.epicId || detected.epicId;
457
+ const epicTitle = scanned.epicTitle || detected.epicTitle;
458
+ const projectPath = scanned.projectPath || detected.projectPath;
459
+
460
+ if (epicId) {
461
+ parts.push(`**Epic:** ${epicId}${epicTitle ? ` - ${epicTitle}` : ""}`);
462
+ }
463
+
464
+ if (scanned.agentName) {
465
+ parts.push(`**Coordinator:** ${scanned.agentName}`);
466
+ }
467
+
468
+ parts.push(`**Project:** ${projectPath}`);
469
+
470
+ // Show detailed subtask info from scanned state
471
+ if (scanned.subtasks.size > 0) {
472
+ parts.push(`\n**Subtasks:**`);
473
+ for (const [id, subtask] of scanned.subtasks) {
474
+ const status = subtask.status === "completed" ? "✓" : `[${subtask.status}]`;
475
+ const worker = subtask.worker ? ` → ${subtask.worker}` : "";
476
+ const files = subtask.files?.length ? ` (${subtask.files.join(", ")})` : "";
477
+ parts.push(` - ${id}: ${subtask.title} ${status}${worker}${files}`);
478
+ }
479
+ } else if (detected.subtasks) {
480
+ // Fall back to counts from hive detection
481
+ const total =
482
+ detected.subtasks.closed +
483
+ detected.subtasks.in_progress +
484
+ detected.subtasks.open +
485
+ detected.subtasks.blocked;
486
+
487
+ if (total > 0) {
488
+ parts.push(`**Subtasks:**`);
489
+ if (detected.subtasks.closed > 0)
490
+ parts.push(` - ${detected.subtasks.closed} closed`);
491
+ if (detected.subtasks.in_progress > 0)
492
+ parts.push(` - ${detected.subtasks.in_progress} in_progress`);
493
+ if (detected.subtasks.open > 0)
494
+ parts.push(` - ${detected.subtasks.open} open`);
495
+ if (detected.subtasks.blocked > 0)
496
+ parts.push(` - ${detected.subtasks.blocked} blocked`);
497
+ }
498
+ }
499
+
500
+ // Show last action if available
501
+ if (scanned.lastAction) {
502
+ parts.push(`\n**Last Action:** \`${scanned.lastAction.tool}\``);
503
+ }
504
+
505
+ if (epicId) {
506
+ parts.push(`\n## 🎯 YOU ARE THE COORDINATOR`);
507
+ parts.push(``);
508
+ parts.push(
509
+ `**Primary role:** Orchestrate workers, review their output, unblock dependencies.`,
510
+ );
511
+ parts.push(`**Spawn workers** for implementation tasks - don't do them yourself.`);
512
+ parts.push(``);
513
+ parts.push(`**RESUME STEPS:**`);
514
+ parts.push(
515
+ `1. Check swarm status: \`swarm_status(epic_id="${epicId}", project_key="${projectPath}")\``,
516
+ );
517
+ parts.push(`2. Check inbox for worker messages: \`swarmmail_inbox(limit=5)\``);
518
+ parts.push(
519
+ `3. For in_progress subtasks: Review worker results with \`swarm_review\``,
520
+ );
521
+ parts.push(`4. For open subtasks: Spawn workers with \`swarm_spawn_subtask\``);
522
+ parts.push(`5. For blocked subtasks: Investigate and unblock`);
523
+ }
524
+
525
+ return parts.join("\n");
526
+ }
527
+
416
528
  // ============================================================================
417
529
  // Swarm Detection
418
530
  // ============================================================================
@@ -678,17 +790,21 @@ async function detectSwarm(): Promise<SwarmDetection> {
678
790
  * Philosophy: Err on the side of continuation. A false positive costs
679
791
  * a bit of context space. A false negative loses the swarm.
680
792
  *
793
+ * @param client - Optional OpenCode SDK client for scanning session messages.
794
+ * When provided, extracts PRECISE swarm state from actual tool calls.
795
+ * When undefined, falls back to hive/swarm-mail heuristic detection.
796
+ *
681
797
  * @example
682
798
  * ```typescript
683
799
  * import { createCompactionHook } from "opencode-swarm-plugin";
684
800
  *
685
- * export const SwarmPlugin: Plugin = async () => ({
801
+ * export const SwarmPlugin: Plugin = async (input) => ({
686
802
  * tool: { ... },
687
- * "experimental.session.compacting": createCompactionHook(),
803
+ * "experimental.session.compacting": createCompactionHook(input.client),
688
804
  * });
689
805
  * ```
690
806
  */
691
- export function createCompactionHook() {
807
+ export function createCompactionHook(client?: OpencodeClient) {
692
808
  return async (
693
809
  input: { sessionID: string },
694
810
  output: { context: string[] },
@@ -699,41 +815,73 @@ export function createCompactionHook() {
699
815
  {
700
816
  session_id: input.sessionID,
701
817
  trigger: "session_compaction",
818
+ has_sdk_client: !!client,
702
819
  },
703
820
  "compaction started",
704
821
  );
705
822
 
706
823
  try {
824
+ // Scan session messages for precise swarm state (if client available)
825
+ const scannedState = await scanSessionMessages(client, input.sessionID);
826
+
827
+ // Also run heuristic detection from hive/swarm-mail
707
828
  const detection = await detectSwarm();
708
829
 
830
+ // Boost confidence if we found swarm evidence in session messages
831
+ let effectiveConfidence = detection.confidence;
832
+ if (scannedState.epicId || scannedState.subtasks.size > 0) {
833
+ // Session messages show swarm activity - this is HIGH confidence
834
+ if (effectiveConfidence === "none" || effectiveConfidence === "low") {
835
+ effectiveConfidence = "medium";
836
+ detection.reasons.push("swarm tool calls found in session");
837
+ }
838
+ if (scannedState.subtasks.size > 0) {
839
+ effectiveConfidence = "high";
840
+ detection.reasons.push(`${scannedState.subtasks.size} subtasks spawned`);
841
+ }
842
+ }
843
+
709
844
  if (
710
- detection.confidence === "high" ||
711
- detection.confidence === "medium"
845
+ effectiveConfidence === "high" ||
846
+ effectiveConfidence === "medium"
712
847
  ) {
713
848
  // Definite or probable swarm - inject full context
714
849
  const header = `[Swarm detected: ${detection.reasons.join(", ")}]\n\n`;
715
-
716
- // Build dynamic state section if we have specific data
850
+
851
+ // Build dynamic state section - prefer scanned state (ground truth) over detected
717
852
  let dynamicState = "";
718
- if (detection.state && detection.state.epicId) {
853
+ if (scannedState.epicId || scannedState.subtasks.size > 0) {
854
+ // Use scanned state (more precise)
855
+ dynamicState =
856
+ buildDynamicSwarmStateFromScanned(
857
+ scannedState,
858
+ detection.state || {
859
+ projectPath: scannedState.projectPath || process.cwd(),
860
+ subtasks: { closed: 0, in_progress: 0, open: 0, blocked: 0 },
861
+ },
862
+ ) + "\n\n";
863
+ } else if (detection.state && detection.state.epicId) {
864
+ // Fall back to hive-detected state
719
865
  dynamicState = buildDynamicSwarmState(detection.state) + "\n\n";
720
866
  }
721
-
867
+
722
868
  const contextContent = header + dynamicState + SWARM_COMPACTION_CONTEXT;
723
869
  output.context.push(contextContent);
724
870
 
725
871
  getLog().info(
726
872
  {
727
- confidence: detection.confidence,
873
+ confidence: effectiveConfidence,
728
874
  context_length: contextContent.length,
729
875
  context_type: "full",
730
876
  reasons: detection.reasons,
731
877
  has_dynamic_state: !!dynamicState,
732
- epic_id: detection.state?.epicId,
878
+ epic_id: scannedState.epicId || detection.state?.epicId,
879
+ scanned_subtasks: scannedState.subtasks.size,
880
+ scanned_agent: scannedState.agentName,
733
881
  },
734
882
  "injected swarm context",
735
883
  );
736
- } else if (detection.confidence === "low") {
884
+ } else if (effectiveConfidence === "low") {
737
885
  // Possible swarm - inject fallback detection prompt
738
886
  const header = `[Possible swarm: ${detection.reasons.join(", ")}]\n\n`;
739
887
  const contextContent = header + SWARM_DETECTION_FALLBACK;
@@ -741,7 +889,7 @@ export function createCompactionHook() {
741
889
 
742
890
  getLog().info(
743
891
  {
744
- confidence: detection.confidence,
892
+ confidence: effectiveConfidence,
745
893
  context_length: contextContent.length,
746
894
  context_type: "fallback",
747
895
  reasons: detection.reasons,
@@ -751,7 +899,7 @@ export function createCompactionHook() {
751
899
  } else {
752
900
  getLog().debug(
753
901
  {
754
- confidence: detection.confidence,
902
+ confidence: effectiveConfidence,
755
903
  context_type: "none",
756
904
  },
757
905
  "no swarm detected, skipping injection",
@@ -764,8 +912,8 @@ export function createCompactionHook() {
764
912
  {
765
913
  duration_ms: duration,
766
914
  success: true,
767
- detected: detection.detected,
768
- confidence: detection.confidence,
915
+ detected: detection.detected || scannedState.epicId !== undefined,
916
+ confidence: effectiveConfidence,
769
917
  context_injected: output.context.length > 0,
770
918
  },
771
919
  "compaction complete",
@@ -63,8 +63,8 @@ export const EvalRecordSchema = z.object({
63
63
  context: z.string().optional(),
64
64
  /** Strategy used for decomposition */
65
65
  strategy: z.enum(["file-based", "feature-based", "risk-based", "auto"]),
66
- /** Max subtasks requested */
67
- max_subtasks: z.number().int().min(1).max(10),
66
+ /** Number of subtasks generated */
67
+ subtask_count: z.number().int().min(1),
68
68
 
69
69
  // OUTPUT (the decomposition)
70
70
  /** Epic title */
@@ -238,7 +238,6 @@ export function captureDecomposition(params: {
238
238
  task: string;
239
239
  context?: string;
240
240
  strategy: "file-based" | "feature-based" | "risk-based" | "auto";
241
- maxSubtasks: number;
242
241
  epicTitle: string;
243
242
  epicDescription?: string;
244
243
  subtasks: Array<{
@@ -256,7 +255,7 @@ export function captureDecomposition(params: {
256
255
  task: params.task,
257
256
  context: params.context,
258
257
  strategy: params.strategy,
259
- max_subtasks: params.maxSubtasks,
258
+ subtask_count: params.subtasks.length,
260
259
  epic_title: params.epicTitle,
261
260
  epic_description: params.epicDescription,
262
261
  subtasks: params.subtasks,
@@ -409,7 +408,7 @@ export function exportForEvalite(projectPath: string): Array<{
409
408
  input: { task: string; context?: string };
410
409
  expected: {
411
410
  minSubtasks: number;
412
- maxSubtasks: number;
411
+ subtaskCount: number;
413
412
  requiredFiles?: string[];
414
413
  overallSuccess?: boolean;
415
414
  };
@@ -426,7 +425,7 @@ export function exportForEvalite(projectPath: string): Array<{
426
425
  },
427
426
  expected: {
428
427
  minSubtasks: 2,
429
- maxSubtasks: record.max_subtasks,
428
+ subtaskCount: record.subtask_count,
430
429
  requiredFiles: record.subtasks.flatMap((s) => s.files),
431
430
  overallSuccess: record.overall_success,
432
431
  },
package/src/index.ts CHANGED
@@ -58,6 +58,7 @@ import {
58
58
  analyzeTodoWrite,
59
59
  shouldAnalyzeTool,
60
60
  } from "./planning-guardrails";
61
+ import { createCompactionHook } from "./compaction-hook";
61
62
 
62
63
  /**
63
64
  * OpenCode Swarm Plugin
@@ -80,7 +81,7 @@ import {
80
81
  export const SwarmPlugin: Plugin = async (
81
82
  input: PluginInput,
82
83
  ): Promise<Hooks> => {
83
- const { $, directory } = input;
84
+ const { $, directory, client } = input;
84
85
 
85
86
  // Set the working directory for hive commands
86
87
  // This ensures hive operations run in the project directory, not ~/.config/opencode
@@ -261,6 +262,25 @@ export const SwarmPlugin: Plugin = async (
261
262
  // Auto-sync was removed because bd CLI is deprecated
262
263
  // The hive_sync tool handles flushing to JSONL and git commit/push
263
264
  },
265
+
266
+ /**
267
+ * Compaction hook for swarm context preservation
268
+ *
269
+ * When OpenCode compacts session context, this hook injects swarm state
270
+ * to ensure coordinators can resume orchestration seamlessly.
271
+ *
272
+ * Uses SDK client to scan actual session messages for precise swarm state
273
+ * (epic IDs, subtask status, agent names) rather than relying solely on
274
+ * heuristic detection from hive/swarm-mail.
275
+ *
276
+ * Note: This hook is experimental and may not be in the published Hooks type yet.
277
+ */
278
+ "experimental.session.compacting": createCompactionHook(client),
279
+ } as Hooks & {
280
+ "experimental.session.compacting"?: (
281
+ input: { sessionID: string },
282
+ output: { context: string[] },
283
+ ) => Promise<void>;
264
284
  };
265
285
  };
266
286
 
@@ -976,7 +976,6 @@ describe("Swarm Tool Integrations", () => {
976
976
  const result = await swarm_decompose.execute(
977
977
  {
978
978
  task: "Add user authentication",
979
- max_subtasks: 3,
980
979
  query_cass: true,
981
980
  },
982
981
  mockContext,
@@ -992,7 +991,6 @@ describe("Swarm Tool Integrations", () => {
992
991
  const result = await swarm_decompose.execute(
993
992
  {
994
993
  task: "Add user authentication",
995
- max_subtasks: 3,
996
994
  query_cass: false,
997
995
  },
998
996
  mockContext,
@@ -87,7 +87,6 @@ export type TaskDecomposition = z.infer<typeof TaskDecompositionSchema>;
87
87
  */
88
88
  export const DecomposeArgsSchema = z.object({
89
89
  task: z.string().min(1),
90
- max_subtasks: z.number().int().min(1).default(5),
91
90
  context: z.string().optional(),
92
91
  });
93
92
  export type DecomposeArgs = z.infer<typeof DecomposeArgsSchema>;
@@ -690,12 +690,6 @@ export const swarm_delegate_planning = tool({
690
690
  .string()
691
691
  .optional()
692
692
  .describe("Additional context to include"),
693
- max_subtasks: tool.schema
694
- .number()
695
- .int()
696
- .min(1)
697
- .optional()
698
- .describe("Suggested max subtasks (optional - LLM decides if not specified)"),
699
693
  strategy: tool.schema
700
694
  .enum(["auto", "file-based", "feature-based", "risk-based"])
701
695
  .optional()
@@ -797,8 +791,7 @@ export const swarm_delegate_planning = tool({
797
791
  .replace("{strategy_guidelines}", strategyGuidelines)
798
792
  .replace("{context_section}", contextSection)
799
793
  .replace("{cass_history}", cassContext || "")
800
- .replace("{skills_context}", skillsContext || "")
801
- .replace("{max_subtasks}", (args.max_subtasks ?? 5).toString());
794
+ .replace("{skills_context}", skillsContext || "");
802
795
 
803
796
  // Add strict JSON-only instructions for the subagent
804
797
  const subagentInstructions = `
@@ -79,7 +79,6 @@ describe("swarm_decompose", () => {
79
79
  const result = await swarm_decompose.execute(
80
80
  {
81
81
  task: "Add user authentication with OAuth",
82
- max_subtasks: 3,
83
82
  },
84
83
  mockContext,
85
84
  );
@@ -97,7 +96,6 @@ describe("swarm_decompose", () => {
97
96
  const result = await swarm_decompose.execute(
98
97
  {
99
98
  task: "Refactor the API routes",
100
- max_subtasks: 5,
101
99
  context: "Using Next.js App Router with RSC",
102
100
  },
103
101
  mockContext,
@@ -109,20 +107,6 @@ describe("swarm_decompose", () => {
109
107
  expect(parsed.prompt).toContain("Additional Context");
110
108
  });
111
109
 
112
- it("uses default max_subtasks when not provided", async () => {
113
- const result = await swarm_decompose.execute(
114
- {
115
- task: "Simple task",
116
- max_subtasks: 5, // Explicit default since schema requires it
117
- },
118
- mockContext,
119
- );
120
-
121
- const parsed = JSON.parse(result);
122
-
123
- // Prompt should say "as many as needed" (max_subtasks no longer in template)
124
- expect(parsed.prompt).toContain("as many as needed");
125
- });
126
110
  });
127
111
 
128
112
  // ============================================================================
@@ -262,7 +246,6 @@ describe("swarm_plan_prompt", () => {
262
246
  const result = await swarm_plan_prompt.execute(
263
247
  {
264
248
  task: "Add user settings page",
265
- max_subtasks: 3,
266
249
  query_cass: false, // Disable CASS to isolate test
267
250
  },
268
251
  mockContext,
@@ -281,7 +264,6 @@ describe("swarm_plan_prompt", () => {
281
264
  {
282
265
  task: "Do something",
283
266
  strategy: "risk-based",
284
- max_subtasks: 3,
285
267
  query_cass: false,
286
268
  },
287
269
  mockContext,
@@ -296,7 +278,6 @@ describe("swarm_plan_prompt", () => {
296
278
  const result = await swarm_plan_prompt.execute(
297
279
  {
298
280
  task: "Refactor the codebase",
299
- max_subtasks: 4,
300
281
  query_cass: false,
301
282
  },
302
283
  mockContext,
@@ -314,7 +295,6 @@ describe("swarm_plan_prompt", () => {
314
295
  const result = await swarm_plan_prompt.execute(
315
296
  {
316
297
  task: "Build new feature",
317
- max_subtasks: 3,
318
298
  query_cass: false,
319
299
  },
320
300
  mockContext,
@@ -330,7 +310,6 @@ describe("swarm_plan_prompt", () => {
330
310
  const result = await swarm_plan_prompt.execute(
331
311
  {
332
312
  task: "Some task",
333
- max_subtasks: 5,
334
313
  query_cass: false,
335
314
  },
336
315
  mockContext,
@@ -350,7 +329,6 @@ describe("swarm_plan_prompt", () => {
350
329
  const result = await swarm_plan_prompt.execute(
351
330
  {
352
331
  task: "Add feature",
353
- max_subtasks: 3,
354
332
  },
355
333
  mockContext,
356
334
  );
@@ -375,7 +353,6 @@ describe("swarm_plan_prompt", () => {
375
353
  const result = await swarm_plan_prompt.execute(
376
354
  {
377
355
  task: "Add user profile",
378
- max_subtasks: 3,
379
356
  context: "We use Next.js App Router with server components",
380
357
  query_cass: false,
381
358
  },
@@ -387,19 +364,6 @@ describe("swarm_plan_prompt", () => {
387
364
  expect(parsed.prompt).toContain("server components");
388
365
  });
389
366
 
390
- it("includes max_subtasks in prompt", async () => {
391
- const result = await swarm_plan_prompt.execute(
392
- {
393
- task: "Build something",
394
- max_subtasks: 7,
395
- query_cass: false,
396
- },
397
- mockContext,
398
- );
399
- const parsed = JSON.parse(result);
400
-
401
- expect(parsed.prompt).toContain("as many as needed");
402
- });
403
367
  });
404
368
 
405
369
  describe("swarm_validate_decomposition", () => {
@@ -920,7 +884,6 @@ describe("full swarm flow (integration)", () => {
920
884
  const decomposeResult = await swarm_decompose.execute(
921
885
  {
922
886
  task: "Add unit tests for auth module",
923
- max_subtasks: 2,
924
887
  },
925
888
  ctx,
926
889
  );
@@ -1228,7 +1191,6 @@ describe("swarm_init", () => {
1228
1191
  const result = await swarm_decompose.execute(
1229
1192
  {
1230
1193
  task: "Add user authentication",
1231
- max_subtasks: 3,
1232
1194
  query_cass: true, // Request CASS but it may not be available
1233
1195
  },
1234
1196
  mockContext,
@@ -1249,7 +1211,6 @@ describe("swarm_init", () => {
1249
1211
  const result = await swarm_decompose.execute(
1250
1212
  {
1251
1213
  task: "Add user authentication",
1252
- max_subtasks: 3,
1253
1214
  query_cass: false, // Explicitly skip CASS
1254
1215
  },
1255
1216
  mockContext,
@@ -1264,7 +1225,6 @@ describe("swarm_init", () => {
1264
1225
  const result = await swarm_decompose.execute(
1265
1226
  {
1266
1227
  task: "Build feature X",
1267
- max_subtasks: 3,
1268
1228
  },
1269
1229
  mockContext,
1270
1230
  );