opencode-swarm-plugin 0.30.6 → 0.31.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/src/hive.ts CHANGED
@@ -19,9 +19,11 @@ import {
19
19
  createHiveAdapter,
20
20
  FlushManager,
21
21
  importFromJSONL,
22
+ syncMemories,
22
23
  type HiveAdapter,
23
24
  type Cell as AdapterCell,
24
25
  getSwarmMail,
26
+ resolvePartialId,
25
27
  } from "swarm-mail";
26
28
  import { existsSync, readFileSync } from "node:fs";
27
29
  import { join } from "node:path";
@@ -422,6 +424,78 @@ export async function importJsonlToPGLite(projectPath: string): Promise<{
422
424
  */
423
425
  const adapterCache = new Map<string, HiveAdapter>();
424
426
 
427
+ // ============================================================================
428
+ // Process Exit Hook - Safety Net for Dirty Cells
429
+ // ============================================================================
430
+
431
+ /**
432
+ * Track if exit hook is already registered (prevent duplicate registrations)
433
+ */
434
+ let exitHookRegistered = false;
435
+
436
+ /**
437
+ * Track if exit hook is currently running (prevent re-entry)
438
+ */
439
+ let exitHookRunning = false;
440
+
441
+ /**
442
+ * Register process.on('beforeExit') handler to flush dirty cells
443
+ * This is a safety net - catches any dirty cells that weren't explicitly synced
444
+ *
445
+ * Idempotent - safe to call multiple times (only registers once)
446
+ */
447
+ function registerExitHook(): void {
448
+ if (exitHookRegistered) {
449
+ return; // Already registered
450
+ }
451
+
452
+ exitHookRegistered = true;
453
+
454
+ process.on('beforeExit', async (code) => {
455
+ // Prevent re-entry if already flushing
456
+ if (exitHookRunning) {
457
+ return;
458
+ }
459
+
460
+ exitHookRunning = true;
461
+
462
+ try {
463
+ // Flush all projects that have adapters (and potentially dirty cells)
464
+ const flushPromises: Promise<void>[] = [];
465
+
466
+ for (const [projectKey, adapter] of adapterCache.entries()) {
467
+ const flushPromise = (async () => {
468
+ try {
469
+ ensureHiveDirectory(projectKey);
470
+ const flushManager = new FlushManager({
471
+ adapter,
472
+ projectKey,
473
+ outputPath: `${projectKey}/.hive/issues.jsonl`,
474
+ });
475
+ await flushManager.flush();
476
+ } catch (error) {
477
+ // Non-fatal - log and continue
478
+ console.warn(
479
+ `[hive exit hook] Failed to flush ${projectKey}:`,
480
+ error instanceof Error ? error.message : String(error)
481
+ );
482
+ }
483
+ })();
484
+
485
+ flushPromises.push(flushPromise);
486
+ }
487
+
488
+ // Wait for all flushes to complete
489
+ await Promise.all(flushPromises);
490
+ } finally {
491
+ exitHookRunning = false;
492
+ }
493
+ });
494
+ }
495
+
496
+ // Register exit hook immediately when module is imported
497
+ registerExitHook();
498
+
425
499
  /**
426
500
  * Get or create a HiveAdapter instance for a project
427
501
  * Exported for testing - allows tests to verify state directly
@@ -693,6 +767,23 @@ export const hive_create_epic = tool({
693
767
  }
694
768
  }
695
769
 
770
+ // Sync cells to JSONL so spawned workers can see them immediately
771
+ try {
772
+ ensureHiveDirectory(projectKey);
773
+ const flushManager = new FlushManager({
774
+ adapter,
775
+ projectKey,
776
+ outputPath: `${projectKey}/.hive/issues.jsonl`,
777
+ });
778
+ await flushManager.flush();
779
+ } catch (error) {
780
+ // Non-fatal - log and continue
781
+ console.warn(
782
+ "[hive_create_epic] Failed to sync to JSONL:",
783
+ error,
784
+ );
785
+ }
786
+
696
787
  return JSON.stringify(result, null, 2);
697
788
  } catch (error) {
698
789
  // Partial failure - rollback via deleteCell
@@ -789,7 +880,7 @@ export const hive_query = tool({
789
880
  export const hive_update = tool({
790
881
  description: "Update cell status/description",
791
882
  args: {
792
- id: tool.schema.string().describe("Cell ID"),
883
+ id: tool.schema.string().describe("Cell ID or partial hash"),
793
884
  status: tool.schema
794
885
  .enum(["open", "in_progress", "blocked", "closed"])
795
886
  .optional()
@@ -808,26 +899,29 @@ export const hive_update = tool({
808
899
  const adapter = await getHiveAdapter(projectKey);
809
900
 
810
901
  try {
902
+ // Resolve partial ID to full ID
903
+ const cellId = await resolvePartialId(adapter, projectKey, validated.id) || validated.id;
904
+
811
905
  let cell: AdapterCell;
812
906
 
813
907
  // Status changes use changeCellStatus, other fields use updateCell
814
908
  if (validated.status) {
815
909
  cell = await adapter.changeCellStatus(
816
910
  projectKey,
817
- validated.id,
911
+ cellId,
818
912
  validated.status,
819
913
  );
820
914
  }
821
915
 
822
916
  // Update other fields if provided
823
917
  if (validated.description !== undefined || validated.priority !== undefined) {
824
- cell = await adapter.updateCell(projectKey, validated.id, {
918
+ cell = await adapter.updateCell(projectKey, cellId, {
825
919
  description: validated.description,
826
920
  priority: validated.priority,
827
921
  });
828
922
  } else if (!validated.status) {
829
923
  // No changes requested
830
- const existingCell = await adapter.getCell(projectKey, validated.id);
924
+ const existingCell = await adapter.getCell(projectKey, cellId);
831
925
  if (!existingCell) {
832
926
  throw new HiveError(
833
927
  `Cell not found: ${validated.id}`,
@@ -837,12 +931,27 @@ export const hive_update = tool({
837
931
  cell = existingCell;
838
932
  }
839
933
 
840
- await adapter.markDirty(projectKey, validated.id);
934
+ await adapter.markDirty(projectKey, cellId);
841
935
 
842
936
  const formatted = formatCellForOutput(cell!);
843
937
  return JSON.stringify(formatted, null, 2);
844
938
  } catch (error) {
845
939
  const message = error instanceof Error ? error.message : String(error);
940
+
941
+ // Provide helpful error messages
942
+ if (message.includes("Ambiguous hash")) {
943
+ throw new HiveError(
944
+ `Ambiguous ID '${validated.id}': multiple cells match. Please provide more characters.`,
945
+ "hive_update",
946
+ );
947
+ }
948
+ if (message.includes("Bead not found") || message.includes("Cell not found")) {
949
+ throw new HiveError(
950
+ `No cell found matching ID '${validated.id}'`,
951
+ "hive_update",
952
+ );
953
+ }
954
+
846
955
  throw new HiveError(
847
956
  `Failed to update cell: ${message}`,
848
957
  "hive_update",
@@ -857,7 +966,7 @@ export const hive_update = tool({
857
966
  export const hive_close = tool({
858
967
  description: "Close a cell with reason",
859
968
  args: {
860
- id: tool.schema.string().describe("Cell ID"),
969
+ id: tool.schema.string().describe("Cell ID or partial hash"),
861
970
  reason: tool.schema.string().describe("Completion reason"),
862
971
  },
863
972
  async execute(args, ctx) {
@@ -866,17 +975,35 @@ export const hive_close = tool({
866
975
  const adapter = await getHiveAdapter(projectKey);
867
976
 
868
977
  try {
978
+ // Resolve partial ID to full ID
979
+ const cellId = await resolvePartialId(adapter, projectKey, validated.id) || validated.id;
980
+
869
981
  const cell = await adapter.closeCell(
870
982
  projectKey,
871
- validated.id,
983
+ cellId,
872
984
  validated.reason,
873
985
  );
874
986
 
875
- await adapter.markDirty(projectKey, validated.id);
987
+ await adapter.markDirty(projectKey, cellId);
876
988
 
877
989
  return `Closed ${cell.id}: ${validated.reason}`;
878
990
  } catch (error) {
879
991
  const message = error instanceof Error ? error.message : String(error);
992
+
993
+ // Provide helpful error messages
994
+ if (message.includes("Ambiguous hash")) {
995
+ throw new HiveError(
996
+ `Ambiguous ID '${validated.id}': multiple cells match. Please provide more characters.`,
997
+ "hive_close",
998
+ );
999
+ }
1000
+ if (message.includes("Bead not found") || message.includes("Cell not found")) {
1001
+ throw new HiveError(
1002
+ `No cell found matching ID '${validated.id}'`,
1003
+ "hive_close",
1004
+ );
1005
+ }
1006
+
880
1007
  throw new HiveError(
881
1008
  `Failed to close cell: ${message}`,
882
1009
  "hive_close",
@@ -892,24 +1019,42 @@ export const hive_start = tool({
892
1019
  description:
893
1020
  "Mark a cell as in-progress (shortcut for update --status in_progress)",
894
1021
  args: {
895
- id: tool.schema.string().describe("Cell ID"),
1022
+ id: tool.schema.string().describe("Cell ID or partial hash"),
896
1023
  },
897
1024
  async execute(args, ctx) {
898
1025
  const projectKey = getHiveWorkingDirectory();
899
1026
  const adapter = await getHiveAdapter(projectKey);
900
1027
 
901
1028
  try {
1029
+ // Resolve partial ID to full ID
1030
+ const cellId = await resolvePartialId(adapter, projectKey, args.id) || args.id;
1031
+
902
1032
  const cell = await adapter.changeCellStatus(
903
1033
  projectKey,
904
- args.id,
1034
+ cellId,
905
1035
  "in_progress",
906
1036
  );
907
1037
 
908
- await adapter.markDirty(projectKey, args.id);
1038
+ await adapter.markDirty(projectKey, cellId);
909
1039
 
910
1040
  return `Started: ${cell.id}`;
911
1041
  } catch (error) {
912
1042
  const message = error instanceof Error ? error.message : String(error);
1043
+
1044
+ // Provide helpful error messages
1045
+ if (message.includes("Ambiguous hash")) {
1046
+ throw new HiveError(
1047
+ `Ambiguous ID '${args.id}': multiple cells match. Please provide more characters.`,
1048
+ "hive_start",
1049
+ );
1050
+ }
1051
+ if (message.includes("Bead not found") || message.includes("Cell not found")) {
1052
+ throw new HiveError(
1053
+ `No cell found matching ID '${args.id}'`,
1054
+ "hive_start",
1055
+ );
1056
+ }
1057
+
913
1058
  throw new HiveError(
914
1059
  `Failed to start cell: ${message}`,
915
1060
  "hive_start",
@@ -1012,8 +1157,21 @@ export const hive_sync = tool({
1012
1157
  "flush hive",
1013
1158
  );
1014
1159
 
1015
- if (flushResult.cellsExported === 0) {
1016
- return "No cells to sync";
1160
+ // 2b. Sync memories to JSONL
1161
+ const swarmMail = await getSwarmMail(projectKey);
1162
+ const db = await swarmMail.getDatabase();
1163
+ const hivePath = join(projectKey, ".hive");
1164
+ let memoriesSynced = 0;
1165
+ try {
1166
+ const memoryResult = await syncMemories(db, hivePath);
1167
+ memoriesSynced = memoryResult.exported;
1168
+ } catch (err) {
1169
+ // Memory sync is optional - don't fail if it errors
1170
+ console.warn("[hive_sync] Memory sync warning:", err);
1171
+ }
1172
+
1173
+ if (flushResult.cellsExported === 0 && memoriesSynced === 0) {
1174
+ return "No cells or memories to sync";
1017
1175
  }
1018
1176
 
1019
1177
  // 3. Check if there are changes to commit
@@ -104,7 +104,7 @@ export { createMemoryAdapter };
104
104
  */
105
105
  export const semantic_memory_store = tool({
106
106
  description:
107
- "Store a memory with semantic embedding. Memories are searchable by semantic similarity and can be organized into collections.",
107
+ "Store a memory with semantic embedding. Memories are searchable by semantic similarity and can be organized into collections. Confidence affects decay rate: high confidence (1.0) = 135 day half-life, low confidence (0.0) = 45 day half-life.",
108
108
  args: {
109
109
  information: tool.schema
110
110
  .string()
@@ -121,6 +121,10 @@ export const semantic_memory_store = tool({
121
121
  .string()
122
122
  .optional()
123
123
  .describe("JSON string with additional metadata"),
124
+ confidence: tool.schema
125
+ .number()
126
+ .optional()
127
+ .describe("Confidence level (0.0-1.0) affecting decay rate. Higher = slower decay. Default 0.7"),
124
128
  },
125
129
  async execute(args, ctx: ToolContext) {
126
130
  const adapter = await getMemoryAdapter();
package/src/memory.ts CHANGED
@@ -68,6 +68,8 @@ export interface StoreArgs {
68
68
  readonly collection?: string;
69
69
  readonly tags?: string;
70
70
  readonly metadata?: string;
71
+ /** Confidence level (0.0-1.0) affecting decay rate. Higher = slower decay. Default 0.7 */
72
+ readonly confidence?: number;
71
73
  }
72
74
 
73
75
  /** Arguments for find operation */
@@ -288,12 +290,19 @@ export async function createMemoryAdapter(
288
290
  metadata.tags = tags;
289
291
  }
290
292
 
293
+ // Clamp confidence to valid range [0.0, 1.0]
294
+ const clampConfidence = (c: number | undefined): number => {
295
+ if (c === undefined) return 0.7;
296
+ return Math.max(0.0, Math.min(1.0, c));
297
+ };
298
+
291
299
  const memory: Memory = {
292
300
  id,
293
301
  content: args.information,
294
302
  metadata,
295
303
  collection,
296
304
  createdAt: new Date(),
305
+ confidence: clampConfidence(args.confidence),
297
306
  };
298
307
 
299
308
  // Generate embedding
@@ -52,7 +52,7 @@ Agents MUST update their bead status as they work. No silent progress.
52
52
 
53
53
  ## Requirements
54
54
 
55
- 1. **Break into 2-{max_subtasks} independent subtasks** that can run in parallel
55
+ 1. **Break into independent subtasks** that can run in parallel (as many as needed)
56
56
  2. **Assign files** - each subtask must specify which files it will modify
57
57
  3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
58
58
  4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
@@ -129,7 +129,7 @@ Agents MUST update their bead status as they work. No silent progress.
129
129
 
130
130
  ## Requirements
131
131
 
132
- 1. **Break into 2-{max_subtasks} independent subtasks** that can run in parallel
132
+ 1. **Break into independent subtasks** that can run in parallel (as many as needed)
133
133
  2. **Assign files** - each subtask must specify which files it will modify
134
134
  3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
135
135
  4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
@@ -437,10 +437,9 @@ export const swarm_decompose = tool({
437
437
  max_subtasks: tool.schema
438
438
  .number()
439
439
  .int()
440
- .min(2)
441
- .max(10)
442
- .default(5)
443
- .describe("Maximum number of subtasks (default: 5)"),
440
+ .min(1)
441
+ .optional()
442
+ .describe("Suggested max subtasks (optional - LLM decides if not specified)"),
444
443
  context: tool.schema
445
444
  .string()
446
445
  .optional()
@@ -453,7 +452,6 @@ export const swarm_decompose = tool({
453
452
  .number()
454
453
  .int()
455
454
  .min(1)
456
- .max(10)
457
455
  .optional()
458
456
  .describe("Max CASS results to include (default: 3)"),
459
457
  },
@@ -702,11 +700,9 @@ export const swarm_delegate_planning = tool({
702
700
  max_subtasks: tool.schema
703
701
  .number()
704
702
  .int()
705
- .min(2)
706
- .max(10)
703
+ .min(1)
707
704
  .optional()
708
- .default(5)
709
- .describe("Maximum number of subtasks (default: 5)"),
705
+ .describe("Suggested max subtasks (optional - LLM decides if not specified)"),
710
706
  strategy: tool.schema
711
707
  .enum(["auto", "file-based", "feature-based", "risk-based"])
712
708
  .optional()
@@ -72,7 +72,7 @@ import {
72
72
  isToolAvailable,
73
73
  warnMissingTool,
74
74
  } from "./tool-availability";
75
- import { getHiveAdapter } from "./hive";
75
+ import { getHiveAdapter, hive_sync, setHiveWorkingDirectory, getHiveWorkingDirectory } from "./hive";
76
76
  import { listSkills } from "./skills";
77
77
  import {
78
78
  canUseWorktreeIsolation,
@@ -1570,6 +1570,30 @@ This will be recorded as a negative learning signal.`;
1570
1570
  );
1571
1571
  }
1572
1572
 
1573
+ // Sync cell to .hive/issues.jsonl (auto-sync on complete)
1574
+ // This ensures the worker's completed work persists before process exits
1575
+ let syncSuccess = false;
1576
+ let syncError: string | undefined;
1577
+ try {
1578
+ // Save current working directory and set to project path
1579
+ const previousWorkingDir = getHiveWorkingDirectory();
1580
+ setHiveWorkingDirectory(args.project_key);
1581
+
1582
+ try {
1583
+ const syncResult = await hive_sync.execute({ auto_pull: false }, _ctx);
1584
+ syncSuccess = !syncResult.includes("error");
1585
+ } finally {
1586
+ // Restore previous working directory
1587
+ setHiveWorkingDirectory(previousWorkingDir);
1588
+ }
1589
+ } catch (error) {
1590
+ // Non-fatal - log warning but don't block completion
1591
+ syncError = error instanceof Error ? error.message : String(error);
1592
+ console.warn(
1593
+ `[swarm_complete] Auto-sync failed (non-fatal): ${syncError}`,
1594
+ );
1595
+ }
1596
+
1573
1597
  // Emit SubtaskOutcomeEvent for learning system
1574
1598
  try {
1575
1599
  const epicId = args.bead_id.includes(".")
@@ -1709,6 +1733,8 @@ This will be recorded as a negative learning signal.`;
1709
1733
  bead_id: args.bead_id,
1710
1734
  closed: true,
1711
1735
  reservations_released: true,
1736
+ synced: syncSuccess,
1737
+ sync_error: syncError,
1712
1738
  message_sent: messageSent,
1713
1739
  message_error: messageError,
1714
1740
  agent_registration: {
@@ -113,8 +113,9 @@ describe("SUBTASK_PROMPT_V2", () => {
113
113
  new RegExp(`### Step ${lastStepNum}:[\\s\\S]*?(?=## \\[|$)`)
114
114
  );
115
115
  expect(lastStepMatch).not.toBeNull();
116
+ if (!lastStepMatch) return;
116
117
 
117
- const lastStepContent = lastStepMatch![0];
118
+ const lastStepContent = lastStepMatch[0];
118
119
  expect(lastStepContent).toContain("swarm_complete");
119
120
  expect(lastStepContent).toMatch(/NOT.*hive_close|DO NOT.*hive_close/i);
120
121
  });
@@ -124,16 +125,18 @@ describe("SUBTASK_PROMPT_V2", () => {
124
125
  test("lists memory query as non-negotiable", () => {
125
126
  const criticalSection = SUBTASK_PROMPT_V2.match(/\[CRITICAL REQUIREMENTS\][\s\S]*?Begin now/);
126
127
  expect(criticalSection).not.toBeNull();
128
+ if (!criticalSection) return;
127
129
 
128
- expect(criticalSection![0]).toMatch(/semantic-memory_find|memory.*MUST|Step 2.*MUST/i);
130
+ expect(criticalSection[0]).toMatch(/semantic-memory_find|memory.*MUST|Step 2.*MUST/i);
129
131
  });
130
132
 
131
133
  test("lists consequences of skipping memory steps", () => {
132
134
  const criticalSection = SUBTASK_PROMPT_V2.match(/\[CRITICAL REQUIREMENTS\][\s\S]*?Begin now/);
133
135
  expect(criticalSection).not.toBeNull();
136
+ if (!criticalSection) return;
134
137
 
135
138
  // Should mention consequences for skipping memory
136
- expect(criticalSection![0]).toMatch(/repeat|waste|already.solved|mistakes/i);
139
+ expect(criticalSection[0]).toMatch(/repeat|waste|already.solved|mistakes/i);
137
140
  });
138
141
  });
139
142
  });
@@ -141,8 +144,8 @@ describe("SUBTASK_PROMPT_V2", () => {
141
144
  describe("formatSubtaskPromptV2", () => {
142
145
  test("substitutes all placeholders correctly", () => {
143
146
  const result = formatSubtaskPromptV2({
144
- bead_id: "test-bead-123",
145
- epic_id: "test-epic-456",
147
+ bead_id: "test-project-abc123-bead456",
148
+ epic_id: "test-project-abc123-epic789",
146
149
  subtask_title: "Test Subtask",
147
150
  subtask_description: "Do the test thing",
148
151
  files: ["src/test.ts", "src/test.test.ts"],
@@ -150,8 +153,8 @@ describe("formatSubtaskPromptV2", () => {
150
153
  project_path: "/path/to/project",
151
154
  });
152
155
 
153
- expect(result).toContain("test-bead-123");
154
- expect(result).toContain("test-epic-456");
156
+ expect(result).toContain("test-project-abc123-bead456");
157
+ expect(result).toContain("test-project-abc123-epic789");
155
158
  expect(result).toContain("Test Subtask");
156
159
  expect(result).toContain("Do the test thing");
157
160
  expect(result).toContain("src/test.ts");
@@ -160,8 +163,8 @@ describe("formatSubtaskPromptV2", () => {
160
163
 
161
164
  test("includes memory query step with MANDATORY emphasis", () => {
162
165
  const result = formatSubtaskPromptV2({
163
- bead_id: "test-bead",
164
- epic_id: "test-epic",
166
+ bead_id: "test-project-abc123-def456",
167
+ epic_id: "test-project-abc123-ghi789",
165
168
  subtask_title: "Test",
166
169
  subtask_description: "",
167
170
  files: [],
@@ -46,7 +46,7 @@ Agents MUST update their cell status as they work. No silent progress.
46
46
 
47
47
  ## Requirements
48
48
 
49
- 1. **Break into 2-{max_subtasks} independent subtasks** that can run in parallel
49
+ 1. **Break into independent subtasks** that can run in parallel (as many as needed)
50
50
  2. **Assign files** - each subtask must specify which files it will modify
51
51
  3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
52
52
  4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
@@ -123,7 +123,7 @@ Agents MUST update their cell status as they work. No silent progress.
123
123
 
124
124
  ## Requirements
125
125
 
126
- 1. **Break into 2-{max_subtasks} independent subtasks** that can run in parallel
126
+ 1. **Break into independent subtasks** that can run in parallel (as many as needed)
127
127
  2. **Assign files** - each subtask must specify which files it will modify
128
128
  3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
129
129
  4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
@@ -406,21 +406,44 @@ swarm_checkpoint(
406
406
 
407
407
  **Checkpoints preserve context so you can recover if things go wrong.**
408
408
 
409
- ### Step 8: Store Learnings (if you discovered something)
409
+ ### Step 8: 💾 STORE YOUR LEARNINGS (if you discovered something)
410
+
411
+ **If you learned it the hard way, STORE IT so the next agent doesn't have to.**
412
+
410
413
  \`\`\`
411
414
  semantic-memory_store(
412
415
  information="<what you learned, WHY it matters, how to apply it>",
413
- metadata="<tags: domain, tech-stack, pattern-type>"
416
+ tags="<domain, tech-stack, pattern-type>"
414
417
  )
415
418
  \`\`\`
416
419
 
417
- **Store:**
418
- - Tricky bugs you solved (root cause + solution)
419
- - Project-specific patterns or domain rules
420
- - Tool/library gotchas and workarounds
421
- - Failed approaches (anti-patterns to avoid)
420
+ **MANDATORY Storage Triggers - Store when you:**
421
+ - 🐛 **Solved a tricky bug** (>15min debugging) - include root cause + solution
422
+ - 💡 **Discovered a project-specific pattern** - domain rules, business logic quirks
423
+ - ⚠️ **Found a tool/library gotcha** - API quirks, version-specific bugs, workarounds
424
+ - 🚫 **Tried an approach that failed** - anti-patterns to avoid, why it didn't work
425
+ - 🏗️ **Made an architectural decision** - reasoning, alternatives considered, tradeoffs
422
426
 
423
- **Don't store generic knowledge.** Store the WHY, not just the WHAT.
427
+ **What Makes a GOOD Memory:**
428
+
429
+ ✅ **GOOD** (actionable, explains WHY):
430
+ \`\`\`
431
+ "OAuth refresh tokens need 5min buffer before expiry to avoid race conditions.
432
+ Without buffer, token refresh can fail mid-request if expiry happens between
433
+ check and use. Implemented with: if (expiresAt - Date.now() < 300000) refresh()"
434
+ \`\`\`
435
+
436
+ ❌ **BAD** (generic, no context):
437
+ \`\`\`
438
+ "Fixed the auth bug by adding a null check"
439
+ \`\`\`
440
+
441
+ **What NOT to Store:**
442
+ - Generic knowledge that's in official documentation
443
+ - Implementation details that change frequently
444
+ - Vague descriptions without context ("fixed the thing")
445
+
446
+ **The WHY matters more than the WHAT.** Future agents need context to apply your learning.
424
447
 
425
448
  ### Step 9: Complete (REQUIRED - releases reservations)
426
449
  \`\`\`
@@ -511,17 +534,20 @@ Other cell operations:
511
534
 
512
535
  **NON-NEGOTIABLE:**
513
536
  1. Step 1 (swarmmail_init) MUST be first - do it before anything else
514
- 2. Step 2 (semantic-memory_find) MUST happen before starting work
537
+ 2. 🧠 Step 2 (semantic-memory_find) MUST happen BEFORE starting work - query first, code second
515
538
  3. Step 4 (swarmmail_reserve) - YOU reserve files, not coordinator
516
539
  4. Step 6 (swarm_progress) - Report at milestones, don't work silently
517
- 5. Step 9 (swarm_complete) - Use this to close, NOT hive_close
540
+ 5. 💾 Step 8 (semantic-memory_store) - If you learned something hard, STORE IT
541
+ 6. Step 9 (swarm_complete) - Use this to close, NOT hive_close
518
542
 
519
543
  **If you skip these steps:**
520
544
  - Your work won't be tracked (swarm_complete will fail)
521
- - You'll waste time repeating solved problems (no semantic memory query)
545
+ - 🔄 You'll waste time repeating already-solved problems (no semantic memory query)
522
546
  - Edit conflicts with other agents (no file reservation)
523
547
  - Lost work if you crash (no checkpoints)
524
- - Future agents repeat your mistakes (no learnings stored)
548
+ - 🔄 Future agents repeat YOUR mistakes (no learnings stored)
549
+
550
+ **Memory is the swarm's collective intelligence. Query it. Feed it.**
525
551
 
526
552
  Begin now.`;
527
553
 
@@ -901,10 +927,9 @@ export const swarm_plan_prompt = tool({
901
927
  max_subtasks: tool.schema
902
928
  .number()
903
929
  .int()
904
- .min(2)
905
- .max(10)
906
- .default(5)
907
- .describe("Maximum number of subtasks (default: 5)"),
930
+ .min(1)
931
+ .optional()
932
+ .describe("Suggested max subtasks (optional - LLM decides if not specified)"),
908
933
  context: tool.schema
909
934
  .string()
910
935
  .optional()
@@ -917,7 +942,6 @@ export const swarm_plan_prompt = tool({
917
942
  .number()
918
943
  .int()
919
944
  .min(1)
920
- .max(10)
921
945
  .optional()
922
946
  .describe("Max CASS results to include (default: 3)"),
923
947
  include_skills: tool.schema