swarm_memory 2.0.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.
Files changed (189) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/lib/claude_swarm/base_executor.rb +133 -0
  4. data/lib/claude_swarm/claude_code_executor.rb +349 -0
  5. data/lib/claude_swarm/claude_mcp_server.rb +77 -0
  6. data/lib/claude_swarm/cli.rb +712 -0
  7. data/lib/claude_swarm/commands/ps.rb +216 -0
  8. data/lib/claude_swarm/commands/show.rb +139 -0
  9. data/lib/claude_swarm/configuration.rb +363 -0
  10. data/lib/claude_swarm/hooks/session_start_hook.rb +42 -0
  11. data/lib/claude_swarm/json_handler.rb +91 -0
  12. data/lib/claude_swarm/mcp_generator.rb +248 -0
  13. data/lib/claude_swarm/openai/chat_completion.rb +264 -0
  14. data/lib/claude_swarm/openai/executor.rb +254 -0
  15. data/lib/claude_swarm/openai/responses.rb +338 -0
  16. data/lib/claude_swarm/orchestrator.rb +879 -0
  17. data/lib/claude_swarm/process_tracker.rb +78 -0
  18. data/lib/claude_swarm/session_cost_calculator.rb +209 -0
  19. data/lib/claude_swarm/session_path.rb +42 -0
  20. data/lib/claude_swarm/settings_generator.rb +77 -0
  21. data/lib/claude_swarm/system_utils.rb +46 -0
  22. data/lib/claude_swarm/templates/generation_prompt.md.erb +230 -0
  23. data/lib/claude_swarm/tools/reset_session_tool.rb +24 -0
  24. data/lib/claude_swarm/tools/session_info_tool.rb +24 -0
  25. data/lib/claude_swarm/tools/task_tool.rb +63 -0
  26. data/lib/claude_swarm/version.rb +5 -0
  27. data/lib/claude_swarm/worktree_manager.rb +475 -0
  28. data/lib/claude_swarm/yaml_loader.rb +22 -0
  29. data/lib/claude_swarm.rb +69 -0
  30. data/lib/swarm_cli/cli.rb +201 -0
  31. data/lib/swarm_cli/command_registry.rb +61 -0
  32. data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
  33. data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
  34. data/lib/swarm_cli/commands/migrate.rb +55 -0
  35. data/lib/swarm_cli/commands/run.rb +173 -0
  36. data/lib/swarm_cli/config_loader.rb +97 -0
  37. data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
  38. data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
  39. data/lib/swarm_cli/interactive_repl.rb +918 -0
  40. data/lib/swarm_cli/mcp_serve_options.rb +44 -0
  41. data/lib/swarm_cli/mcp_tools_options.rb +59 -0
  42. data/lib/swarm_cli/migrate_options.rb +54 -0
  43. data/lib/swarm_cli/migrator.rb +132 -0
  44. data/lib/swarm_cli/options.rb +151 -0
  45. data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
  46. data/lib/swarm_cli/ui/components/content_block.rb +120 -0
  47. data/lib/swarm_cli/ui/components/divider.rb +57 -0
  48. data/lib/swarm_cli/ui/components/panel.rb +62 -0
  49. data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
  50. data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
  51. data/lib/swarm_cli/ui/formatters/number.rb +58 -0
  52. data/lib/swarm_cli/ui/formatters/text.rb +77 -0
  53. data/lib/swarm_cli/ui/formatters/time.rb +73 -0
  54. data/lib/swarm_cli/ui/icons.rb +59 -0
  55. data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
  56. data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
  57. data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
  58. data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
  59. data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
  60. data/lib/swarm_cli/version.rb +5 -0
  61. data/lib/swarm_cli.rb +45 -0
  62. data/lib/swarm_memory/adapters/base.rb +140 -0
  63. data/lib/swarm_memory/adapters/filesystem_adapter.rb +789 -0
  64. data/lib/swarm_memory/chat_extension.rb +34 -0
  65. data/lib/swarm_memory/cli/commands.rb +306 -0
  66. data/lib/swarm_memory/core/entry.rb +37 -0
  67. data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
  68. data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
  69. data/lib/swarm_memory/core/path_normalizer.rb +75 -0
  70. data/lib/swarm_memory/core/semantic_index.rb +244 -0
  71. data/lib/swarm_memory/core/storage.rb +286 -0
  72. data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
  73. data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
  74. data/lib/swarm_memory/dsl/memory_config.rb +113 -0
  75. data/lib/swarm_memory/embeddings/embedder.rb +36 -0
  76. data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
  77. data/lib/swarm_memory/errors.rb +21 -0
  78. data/lib/swarm_memory/integration/cli_registration.rb +30 -0
  79. data/lib/swarm_memory/integration/configuration.rb +43 -0
  80. data/lib/swarm_memory/integration/registration.rb +31 -0
  81. data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
  82. data/lib/swarm_memory/optimization/analyzer.rb +244 -0
  83. data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
  84. data/lib/swarm_memory/prompts/memory.md.erb +109 -0
  85. data/lib/swarm_memory/prompts/memory_assistant.md.erb +139 -0
  86. data/lib/swarm_memory/prompts/memory_researcher.md.erb +201 -0
  87. data/lib/swarm_memory/prompts/memory_retrieval.md.erb +76 -0
  88. data/lib/swarm_memory/search/semantic_search.rb +112 -0
  89. data/lib/swarm_memory/search/text_search.rb +40 -0
  90. data/lib/swarm_memory/search/text_similarity.rb +80 -0
  91. data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
  92. data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
  93. data/lib/swarm_memory/tools/load_skill.rb +313 -0
  94. data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
  95. data/lib/swarm_memory/tools/memory_delete.rb +99 -0
  96. data/lib/swarm_memory/tools/memory_edit.rb +185 -0
  97. data/lib/swarm_memory/tools/memory_glob.rb +145 -0
  98. data/lib/swarm_memory/tools/memory_grep.rb +209 -0
  99. data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
  100. data/lib/swarm_memory/tools/memory_read.rb +123 -0
  101. data/lib/swarm_memory/tools/memory_write.rb +215 -0
  102. data/lib/swarm_memory/utils.rb +50 -0
  103. data/lib/swarm_memory/version.rb +5 -0
  104. data/lib/swarm_memory.rb +166 -0
  105. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
  106. data/lib/swarm_sdk/agent/builder.rb +461 -0
  107. data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
  108. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  109. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
  110. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
  111. data/lib/swarm_sdk/agent/chat.rb +1144 -0
  112. data/lib/swarm_sdk/agent/context.rb +112 -0
  113. data/lib/swarm_sdk/agent/context_manager.rb +309 -0
  114. data/lib/swarm_sdk/agent/definition.rb +556 -0
  115. data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
  116. data/lib/swarm_sdk/configuration.rb +296 -0
  117. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  118. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  119. data/lib/swarm_sdk/context_compactor.rb +340 -0
  120. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  121. data/lib/swarm_sdk/hooks/context.rb +197 -0
  122. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  123. data/lib/swarm_sdk/hooks/error.rb +29 -0
  124. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  125. data/lib/swarm_sdk/hooks/registry.rb +147 -0
  126. data/lib/swarm_sdk/hooks/result.rb +150 -0
  127. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  128. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  129. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  130. data/lib/swarm_sdk/log_collector.rb +51 -0
  131. data/lib/swarm_sdk/log_stream.rb +69 -0
  132. data/lib/swarm_sdk/markdown_parser.rb +75 -0
  133. data/lib/swarm_sdk/model_aliases.json +5 -0
  134. data/lib/swarm_sdk/models.json +1 -0
  135. data/lib/swarm_sdk/models.rb +120 -0
  136. data/lib/swarm_sdk/node/agent_config.rb +49 -0
  137. data/lib/swarm_sdk/node/builder.rb +439 -0
  138. data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
  139. data/lib/swarm_sdk/node_context.rb +170 -0
  140. data/lib/swarm_sdk/node_orchestrator.rb +384 -0
  141. data/lib/swarm_sdk/permissions/config.rb +239 -0
  142. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  143. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  144. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  145. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  146. data/lib/swarm_sdk/plugin.rb +147 -0
  147. data/lib/swarm_sdk/plugin_registry.rb +101 -0
  148. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
  149. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  150. data/lib/swarm_sdk/result.rb +97 -0
  151. data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
  152. data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
  153. data/lib/swarm_sdk/swarm/builder.rb +586 -0
  154. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  155. data/lib/swarm_sdk/swarm/tool_configurator.rb +416 -0
  156. data/lib/swarm_sdk/swarm.rb +982 -0
  157. data/lib/swarm_sdk/tools/bash.rb +274 -0
  158. data/lib/swarm_sdk/tools/clock.rb +44 -0
  159. data/lib/swarm_sdk/tools/delegate.rb +164 -0
  160. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  161. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  162. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
  163. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  164. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  165. data/lib/swarm_sdk/tools/edit.rb +150 -0
  166. data/lib/swarm_sdk/tools/glob.rb +158 -0
  167. data/lib/swarm_sdk/tools/grep.rb +228 -0
  168. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  169. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  170. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  171. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  172. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  173. data/lib/swarm_sdk/tools/read.rb +251 -0
  174. data/lib/swarm_sdk/tools/registry.rb +93 -0
  175. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
  176. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
  177. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
  178. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  179. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
  180. data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
  181. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  182. data/lib/swarm_sdk/tools/think.rb +95 -0
  183. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  184. data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
  185. data/lib/swarm_sdk/tools/write.rb +117 -0
  186. data/lib/swarm_sdk/utils.rb +50 -0
  187. data/lib/swarm_sdk/version.rb +5 -0
  188. data/lib/swarm_sdk.rb +167 -0
  189. metadata +313 -0
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ class ProcessTracker
5
+ PIDS_DIR = "pids"
6
+
7
+ def initialize(session_path)
8
+ @session_path = session_path
9
+ @pids_dir = File.join(@session_path, PIDS_DIR)
10
+ ensure_pids_directory
11
+ end
12
+
13
+ def track_pid(pid, name)
14
+ pid_file = File.join(@pids_dir, pid.to_s)
15
+ File.write(pid_file, name)
16
+ end
17
+
18
+ def cleanup_all
19
+ return unless Dir.exist?(@pids_dir)
20
+
21
+ # Get all PID files
22
+ pid_files = Dir.glob(File.join(@pids_dir, "*"))
23
+
24
+ pid_files.each do |pid_file|
25
+ pid = File.basename(pid_file).to_i
26
+ name = begin
27
+ File.read(pid_file).strip
28
+ rescue StandardError
29
+ "unknown"
30
+ end
31
+
32
+ begin
33
+ # Check if process is still running
34
+ Process.kill(0, pid)
35
+ # If we get here, process is running, so kill it
36
+ Process.kill("TERM", pid)
37
+ puts "✓ Terminated MCP server: #{name} (PID: #{pid})"
38
+
39
+ # Give it a moment to terminate gracefully
40
+ sleep(0.1)
41
+
42
+ # Force kill if still running
43
+ begin
44
+ Process.kill(0, pid)
45
+ Process.kill("KILL", pid)
46
+ puts " → Force killed #{name} (PID: #{pid})"
47
+ rescue Errno::ESRCH
48
+ # Process is gone, which is what we want
49
+ end
50
+ rescue Errno::ESRCH
51
+ # Process not found, already terminated
52
+ puts " → MCP server #{name} (PID: #{pid}) already terminated"
53
+ rescue Errno::EPERM
54
+ # Permission denied
55
+ puts " ⚠️ No permission to terminate #{name} (PID: #{pid})"
56
+ end
57
+ end
58
+
59
+ # Clean up the pids directory
60
+ FileUtils.rm_rf(@pids_dir)
61
+ end
62
+
63
+ class << self
64
+ def cleanup_session(session_path)
65
+ return unless Dir.exist?(File.join(session_path, PIDS_DIR))
66
+
67
+ tracker = new(session_path)
68
+ tracker.cleanup_all
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def ensure_pids_directory
75
+ FileUtils.mkdir_p(@pids_dir)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ module SessionCostCalculator
5
+ extend self
6
+
7
+ # Model pricing in dollars per million tokens
8
+ MODEL_PRICING = {
9
+ opus: {
10
+ input: 15.0,
11
+ output: 75.0,
12
+ cache_write: 18.75,
13
+ cache_read: 1.50,
14
+ },
15
+ sonnet: {
16
+ input: 3.0,
17
+ output: 15.0,
18
+ cache_write: 3.75,
19
+ cache_read: 0.30,
20
+ },
21
+ haiku: {
22
+ input: 0.80,
23
+ output: 4.0,
24
+ cache_write: 1.0,
25
+ cache_read: 0.08,
26
+ },
27
+ }.freeze
28
+
29
+ # Determine model type from model name
30
+ def model_type_from_name(model_name)
31
+ return unless model_name
32
+
33
+ model_name_lower = model_name.downcase
34
+ if model_name_lower.include?("opus")
35
+ :opus
36
+ elsif model_name_lower.include?("sonnet")
37
+ :sonnet
38
+ elsif model_name_lower.include?("haiku")
39
+ :haiku
40
+ end
41
+ end
42
+
43
+ # Calculate cost from token usage
44
+ def calculate_token_cost(usage, model_name)
45
+ model_type = model_type_from_name(model_name)
46
+ return 0.0 unless model_type && usage
47
+
48
+ pricing = MODEL_PRICING[model_type]
49
+ return 0.0 unless pricing
50
+
51
+ cost = 0.0
52
+
53
+ # Regular input tokens
54
+ if usage["input_tokens"]
55
+ cost += (usage["input_tokens"] / 1_000_000.0) * pricing[:input]
56
+ end
57
+
58
+ # Output tokens
59
+ if usage["output_tokens"]
60
+ cost += (usage["output_tokens"] / 1_000_000.0) * pricing[:output]
61
+ end
62
+
63
+ # Cache creation tokens (write)
64
+ if usage["cache_creation_input_tokens"]
65
+ cost += (usage["cache_creation_input_tokens"] / 1_000_000.0) * pricing[:cache_write]
66
+ end
67
+
68
+ # Cache read tokens
69
+ if usage["cache_read_input_tokens"]
70
+ cost += (usage["cache_read_input_tokens"] / 1_000_000.0) * pricing[:cache_read]
71
+ end
72
+
73
+ cost
74
+ end
75
+
76
+ # Calculate total cost from session log file
77
+ # Returns a hash with:
78
+ # - total_cost: Total cost in USD (sum of cost_usd for instances, token costs for main)
79
+ # - instances_with_cost: Set of instance names that have cost data
80
+ def calculate_total_cost(session_log_path)
81
+ return { total_cost: 0.0, instances_with_cost: Set.new } unless File.exist?(session_log_path)
82
+
83
+ # Track costs per instance - simple sum of cost_usd
84
+ instance_costs = {}
85
+ instances_with_cost = Set.new
86
+ main_instance_cost = 0.0
87
+
88
+ File.foreach(session_log_path) do |line|
89
+ data = JsonHandler.parse(line)
90
+ next if data == line # Skip unparseable lines
91
+
92
+ instance_name = data["instance"]
93
+ instance_id = data["instance_id"]
94
+
95
+ # Handle main instance token-based costs
96
+ if instance_id == "main" && data.dig("event", "type") == "assistant"
97
+ usage = data.dig("event", "message", "usage")
98
+ model = data.dig("event", "message", "model")
99
+ if usage && model
100
+ token_cost = calculate_token_cost(usage, model)
101
+ main_instance_cost += token_cost
102
+ instances_with_cost << instance_name if token_cost > 0
103
+ end
104
+ # Handle other instances with cost_usd (non-cumulative)
105
+ elsif instance_id != "main" && data.dig("event", "type") == "result"
106
+ # Use cost_usd (non-cumulative) instead of total_cost_usd (cumulative)
107
+ if (cost = data.dig("event", "cost_usd"))
108
+ instances_with_cost << instance_name
109
+ instance_costs[instance_name] ||= 0.0
110
+ instance_costs[instance_name] += cost
111
+ end
112
+ end
113
+ end
114
+
115
+ # Calculate total: sum of all instance costs + main instance token costs
116
+ other_instances_cost = instance_costs.values.sum
117
+ total_cost = other_instances_cost + main_instance_cost
118
+
119
+ {
120
+ total_cost: total_cost,
121
+ instances_with_cost: instances_with_cost,
122
+ }
123
+ end
124
+
125
+ # Calculate simple total cost (for backward compatibility)
126
+ def calculate_simple_total(session_log_path)
127
+ calculate_total_cost(session_log_path)[:total_cost]
128
+ end
129
+
130
+ # Parse instance hierarchy with costs from session log
131
+ # Returns a hash of instances with their cost data and relationships
132
+ def parse_instance_hierarchy(session_log_path)
133
+ instances = {}
134
+ # Track main instance token costs
135
+ main_instance_costs = {}
136
+
137
+ return instances unless File.exist?(session_log_path)
138
+
139
+ File.foreach(session_log_path) do |line|
140
+ data = JsonHandler.parse(line)
141
+ next if data == line # Skip unparseable lines
142
+
143
+ instance_name = data["instance"]
144
+ instance_id = data["instance_id"]
145
+ calling_instance = data["calling_instance"]
146
+
147
+ # Initialize instance data
148
+ instances[instance_name] ||= {
149
+ name: instance_name,
150
+ id: instance_id,
151
+ cost: 0.0,
152
+ calls: 0,
153
+ called_by: Set.new,
154
+ calls_to: Set.new,
155
+ has_cost_data: false,
156
+ }
157
+
158
+ # Track relationships
159
+ if calling_instance && calling_instance != instance_name
160
+ instances[instance_name][:called_by] << calling_instance
161
+
162
+ instances[calling_instance] ||= {
163
+ name: calling_instance,
164
+ id: data["calling_instance_id"],
165
+ cost: 0.0,
166
+ calls: 0,
167
+ called_by: Set.new,
168
+ calls_to: Set.new,
169
+ has_cost_data: false,
170
+ }
171
+ instances[calling_instance][:calls_to] << instance_name
172
+ end
173
+
174
+ # Handle main instance token-based costs
175
+ if instance_id == "main" && data.dig("event", "type") == "assistant"
176
+ usage = data.dig("event", "message", "usage")
177
+ model = data.dig("event", "message", "model")
178
+ if usage && model
179
+ token_cost = calculate_token_cost(usage, model)
180
+ if token_cost > 0
181
+ main_instance_costs[instance_name] ||= 0.0
182
+ main_instance_costs[instance_name] += token_cost
183
+ instances[instance_name][:has_cost_data] = true
184
+ instances[instance_name][:calls] += 1
185
+ end
186
+ end
187
+ # Track costs and calls for non-main instances using cost_usd
188
+ elsif data.dig("event", "type") == "result" && instance_id != "main"
189
+ instances[instance_name][:calls] += 1
190
+ # Use cost_usd (non-cumulative) instead of total_cost_usd
191
+ if (cost = data.dig("event", "cost_usd"))
192
+ instances[instance_name][:cost] += cost
193
+ instances[instance_name][:has_cost_data] = true
194
+ end
195
+ end
196
+ end
197
+
198
+ # Set main instance costs (replace, don't add)
199
+ main_instance_costs.each do |name, cost|
200
+ if instances[name]
201
+ # For main instances, use ONLY token costs, not cumulative costs
202
+ instances[name][:cost] = cost
203
+ end
204
+ end
205
+
206
+ instances
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ module SessionPath
5
+ class << self
6
+ # Convert a directory path to a safe folder name using + as separator
7
+ def project_folder_name(working_dir = Dir.pwd)
8
+ # Don't expand path if it's already expanded (avoids double expansion on Windows)
9
+ path = working_dir.start_with?("/") || working_dir.match?(/^[A-Za-z]:/) ? working_dir : File.expand_path(working_dir)
10
+
11
+ # Handle Windows drive letters (C:\ → C)
12
+ path = path.gsub(/^([A-Za-z]):/, '\1')
13
+
14
+ # Remove leading slash/backslash
15
+ path = path.sub(%r{^[/\\]}, "")
16
+
17
+ # Replace all path separators with +
18
+ path.gsub(%r{[/\\]}, "+")
19
+ end
20
+
21
+ # Generate a full session path for a given directory and session ID
22
+ def generate(working_dir: Dir.pwd, session_id: SecureRandom.uuid)
23
+ project_name = project_folder_name(working_dir)
24
+ ClaudeSwarm.joined_sessions_dir(project_name, session_id)
25
+ end
26
+
27
+ # Ensure the session directory exists
28
+ def ensure_directory(session_path)
29
+ FileUtils.mkdir_p(session_path)
30
+
31
+ # Add .gitignore to swarm home if it doesn't exist
32
+ gitignore_path = ClaudeSwarm.joined_home_dir(".gitignore")
33
+ File.write(gitignore_path, "*\n") unless File.exist?(gitignore_path)
34
+ end
35
+
36
+ # Get the session path from environment (required)
37
+ def from_env
38
+ ENV["CLAUDE_SWARM_SESSION_PATH"] or raise "CLAUDE_SWARM_SESSION_PATH not set"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ class SettingsGenerator
5
+ def initialize(configuration)
6
+ @config = configuration
7
+ end
8
+
9
+ def generate_all
10
+ ensure_session_directory
11
+
12
+ @config.instances.each do |name, instance|
13
+ generate_settings(name, instance)
14
+ end
15
+ end
16
+
17
+ def settings_path(instance_name)
18
+ File.join(session_path, "#{instance_name}_settings.json")
19
+ end
20
+
21
+ private
22
+
23
+ def session_path
24
+ @session_path ||= SessionPath.from_env
25
+ end
26
+
27
+ def ensure_session_directory
28
+ # Session directory is already created by orchestrator
29
+ # Just ensure it exists
30
+ SessionPath.ensure_directory(session_path)
31
+ end
32
+
33
+ def generate_settings(name, instance)
34
+ settings = {}
35
+
36
+ # Add hooks if configured
37
+ if instance[:hooks] && !instance[:hooks].empty?
38
+ settings["hooks"] = instance[:hooks]
39
+ end
40
+
41
+ # Add SessionStart hook for main instance to capture transcript path
42
+ if name == @config.main_instance
43
+ session_start_hook = build_session_start_hook
44
+
45
+ # Initialize hooks if not present
46
+ settings["hooks"] ||= {}
47
+ settings["hooks"]["SessionStart"] ||= []
48
+
49
+ # Add our hook to the SessionStart hooks
50
+ settings["hooks"]["SessionStart"] << session_start_hook
51
+ end
52
+
53
+ # Only write settings file if there are settings to write
54
+ return if settings.empty?
55
+
56
+ # Write settings file
57
+ JsonHandler.write_file!(settings_path(name), settings)
58
+ end
59
+
60
+ def build_session_start_hook
61
+ hook_script_path = File.expand_path("hooks/session_start_hook.rb", __dir__)
62
+ # Pass session path as an argument since ENV may not be inherited
63
+ session_path_arg = session_path
64
+
65
+ {
66
+ "matcher" => "startup",
67
+ "hooks" => [
68
+ {
69
+ "type" => "command",
70
+ "command" => "ruby #{hook_script_path} '#{session_path_arg}'",
71
+ "timeout" => 5,
72
+ },
73
+ ],
74
+ }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ClaudeSwarm
4
+ module SystemUtils
5
+ def system!(*args)
6
+ system(*args)
7
+ handle_command_failure(last_status, args)
8
+ end
9
+
10
+ def system_with_pid!(*args)
11
+ # Spawn the process - by default, inherits the parent's I/O
12
+ pid = Process.spawn(*args)
13
+
14
+ # Yield the PID to the block if given
15
+ yield(pid) if block_given?
16
+
17
+ # Wait for the process to complete
18
+ _, status = Process.wait2(pid)
19
+
20
+ # Check the exit status
21
+ handle_command_failure(status, args)
22
+ end
23
+
24
+ def last_status
25
+ $CHILD_STATUS
26
+ end
27
+
28
+ private
29
+
30
+ def handle_command_failure(status, args) # rubocop:disable Naming/PredicateMethod
31
+ return true if status&.success?
32
+
33
+ exit_status = status&.exitstatus || 1
34
+ command_str = args.size == 1 ? args.first : args.join(" ")
35
+
36
+ if exit_status == 143 # timeout command exit status = 128 + 15 (SIGTERM)
37
+ warn("⏱️ Command timeout: #{command_str}")
38
+ else
39
+ warn("❌ Command failed with exit status: #{exit_status}")
40
+ raise Error, "Command failed with exit status #{exit_status}: #{command_str}"
41
+ end
42
+
43
+ false
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,230 @@
1
+ You are a Claude Swarm configuration generator assistant. Your role is to help the user create a well-structured swarm YAML file through an interactive conversation.
2
+
3
+ ## Claude Swarm Overview
4
+ Claude Swarm is a Ruby gem that orchestrates multiple Claude Code instances as a collaborative AI development team. It enables running AI agents with specialized roles, tools, and directory contexts, communicating via MCP (Model Context Protocol).
5
+
6
+ Key capabilities:
7
+ - Define multiple AI instances with different roles and specializations
8
+ - Set up connections between instances for collaboration
9
+ - Restrict tools based on each instance's responsibilities
10
+ - Run instances in different directories or Git worktrees
11
+ - Support for custom system prompts per instance
12
+ - Choose appropriate models (opus for complex tasks, sonnet for simpler ones)
13
+ - Support for OpenAI instances
14
+
15
+ ## Your Task
16
+ 1. Start by asking about the user's project structure and development needs
17
+ 2. Understand what kind of team they need (roles, specializations)
18
+ 3. Suggest an appropriate swarm topology based on their needs
19
+ 4. Help them refine and customize the configuration
20
+ 5. Generate the final claude-swarm.yml content
21
+ 6. When the configuration is complete, save it to: <%= output_file || "a descriptive filename based on the swarm's function" %>
22
+
23
+ ## File Naming Convention
24
+ <% if output_file %>
25
+ The user has specified the output file: <%= output_file %>
26
+ <% else %>
27
+ Since no output file was specified, name the file based on the swarm's function. Examples:
28
+ - web-dev-swarm.yml for full-stack web development teams
29
+ - data-pipeline-swarm.yml for data processing teams
30
+ - microservices-swarm.yml for microservice architectures
31
+ - mobile-app-swarm.yml for mobile development teams
32
+ - ml-research-swarm.yml for machine learning teams
33
+ - devops-swarm.yml for infrastructure and deployment teams
34
+ Use descriptive names that clearly indicate the swarm's purpose.
35
+ <% end %>
36
+
37
+ ## Configuration Structure
38
+ ```yaml
39
+ version: 1
40
+ swarm:
41
+ name: "Descriptive Swarm Name"
42
+ main: main_instance_name
43
+ instances:
44
+ instance_name:
45
+ description: "Clear description of role and responsibilities"
46
+ directory: ./path/to/directory
47
+ model: opus
48
+ allowed_tools: [Read, Edit, Write, Bash]
49
+ connections: [other_instance_names] # Optional
50
+ prompt: |
51
+ Custom system prompt for specialization
52
+
53
+ ```
54
+
55
+ ## Best Practices to Follow
56
+ - Use descriptive, role-based instance names (e.g., frontend_dev, api_architect)
57
+ - Write prompts using multi-line strings to make them more readable.
58
+ - Write clear descriptions explaining each instance's responsibilities
59
+ - Choose opus model for complex architectural or algorithmic tasks and routine development.
60
+ - Choose sonnet model for simpler tasks
61
+ - Set up logical connections (e.g., lead → team members, architect → implementers), but do not create circular dependencies.
62
+ - Always add this to the end of every prompt: `For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.`
63
+ - Select tools based on each instance's actual needs:
64
+ - Read: For code review and analysis roles
65
+ - Edit: For active development roles
66
+ - Write: For creating new files
67
+ - Bash: For running commands, tests, builds
68
+ - MultiEdit: For editing multiple files at once
69
+ - WebFetch: For fetching information from the web
70
+ - WebSearch: For searching the web
71
+ - Use custom prompts to specialize each instance's expertise
72
+ - Organize directories to match project structure
73
+
74
+ ## Interactive Questions to Ask
75
+ - What type of project are you working on?
76
+ - What are the main technologies/frameworks you're using?
77
+ - What development tasks do you need help with?
78
+ - Do you need specialized roles (testing, DevOps, documentation)?
79
+ - Are there specific areas that need focused attention?
80
+ - Do you have multiple repositories or services to coordinate?
81
+
82
+ <full_readme>
83
+ <%= readme_content %>
84
+ </full_readme>
85
+
86
+ <prompt_best_practices>
87
+ # Claude 4 prompt engineering best practices
88
+
89
+ This guide provides specific prompt engineering techniques for Claude 4 models (Opus 4 and Sonnet 4) to help you achieve optimal results in your applications. These models have been trained for more precise instruction following than previous generations of Claude models.
90
+
91
+ ## General principles
92
+
93
+ ### Be explicit with your instructions
94
+
95
+ Claude 4 models respond well to clear, explicit instructions. Being specific about your desired output can help enhance results. Customers who desire the "above and beyond" behavior from previous Claude models might need to more explicitly request these behaviors with Claude 4.
96
+
97
+ **Less effective:**
98
+
99
+ ```text
100
+ Create an analytics dashboard
101
+ ```
102
+
103
+ **More effective:**
104
+
105
+ ```text
106
+ Create an analytics dashboard. Include as many relevant features and interactions as possible. Go beyond the basics to create a fully-featured implementation.
107
+ ```
108
+
109
+ ### Add context to improve performance
110
+
111
+ Providing context or motivation behind your instructions, such as explaining to Claude why such behavior is important, can help Claude 4 better understand your goals and deliver more targeted responses.
112
+
113
+ **Less effective:**
114
+
115
+ ```text
116
+ NEVER use ellipses
117
+ ```
118
+
119
+ **More effective:**
120
+
121
+ ```text
122
+ Your response will be read aloud by a text-to-speech engine, so never use ellipses since the text-to-speech engine will not know how to pronounce them.
123
+ ```
124
+
125
+ Claude is smart enough to generalize from the explanation.
126
+
127
+ ### Be vigilant with examples & details
128
+
129
+ Claude 4 models pay attention to details and examples as part of instruction following. Ensure that your examples align with the behaviors you want to encourage and minimize behaviors you want to avoid.
130
+
131
+ ## Guidance for specific situations
132
+
133
+ ### Control the format of responses
134
+
135
+ There are a few ways that we have found to be particularly effective in seering output formatting in Claude 4 models:
136
+
137
+ 1. **Tell Claude what to do instead of what not to do**
138
+
139
+ * Instead of: "Do not use markdown in your response"
140
+ * Try: "Your response should be composed of smoothly flowing prose paragraphs."
141
+
142
+ 2. **Use XML format indicators**
143
+
144
+ * Try: "Write the prose sections of your response in <smoothly_flowing_prose_paragraphs> tags."
145
+
146
+ 3. **Match your prompt style to the desired output**
147
+
148
+ The formatting style used in your prompt may influence Claude's response style. If you are still experiencing steerability issues with output formatting, we recommend as best as you can matching your prompt style to your desired output style. For exmaple, removing markdown from your prompt can reduce the volume of markdown in the output.
149
+
150
+ ### Leverage thinking & interleaved thinking capabilities
151
+
152
+ Claude 4 offers thinking capabilities that can be especially helpful for tasks involving reflection after tool use or complex multi-step reasoning. You can guide its initial or interleaved thinking for better results.
153
+
154
+ ```text Example prompt
155
+ After receiving tool results, carefully reflect on their quality and determine optimal next steps before proceeding. Use your thinking to plan and iterate based on this new information, and then take the best next action.
156
+ ```
157
+
158
+ ### Optimize parallel tool calling
159
+
160
+ Claude 4 models excel at parallel tool execution. They have a high success rate in using parallel tool calling without any prompting to do so, but some minor prompting can boost this behavior to ~100% parallel tool use success rate. We have found this prompt to be most effective:
161
+
162
+ ```text Sample prompt for agents
163
+ For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
164
+ ```
165
+
166
+ ### Reduce file creation in agentic coding
167
+
168
+ Claude 4 models may sometimes create new files for testing and iteration purposes, particularly when working with code. This approach allows Claude to use files, especially python scripts, as a 'temporary scratchpad' before saving its final output. Using temporary files can improve outcomes particularly for agentic coding use cases.
169
+
170
+ If you'd prefer to minimize net new file creation, you can instruct Claude to clean up after itself:
171
+
172
+ ```text Sample prompt
173
+ If you create any temporary new files, scripts, or helper files for iteration, clean up these files by removing them at the end of the task.
174
+ ```
175
+
176
+ ### Enhance visual and frontend code generation
177
+
178
+ For frontend code generation, you can steer Claude 4 models to create complex, detailed, and interactive designs by providing explicit encouragement:
179
+
180
+ ```text Sample prompt
181
+ Don't hold back. Give it your all.
182
+ ```
183
+
184
+ You can also improve Claude's frontend performance in specific areas by providing additional modifiers and details on what to focus on:
185
+
186
+ * "Include as many relevant features and interactions as possible"
187
+ * "Add thoughtful details like hover states, transitions, and micro-interactions"
188
+ * "Create an impressive demonstration showcasing web development capabilities"
189
+ * "Apply design principles: hierarchy, contrast, balance, and movement"
190
+
191
+ ### Avoid focusing on passing tests and hard-coding
192
+
193
+ Frontier language models can sometimes focus too heavily on making tests pass at the expense of more general solutions. To prevent this behavior and ensure robust, generalizable solutions:
194
+
195
+ ```text
196
+ Please write a high quality, general purpose solution. Implement a solution that works correctly for all valid inputs, not just the test cases. Do not hard-code values or create solutions that only work for specific test inputs. Instead, implement the actual logic that solves the problem generally.
197
+
198
+ Focus on understanding the problem requirements and implementing the correct algorithm. Tests are there to verify correctness, not to define the solution. Provide a principled implementation that follows best practices and software design principles.
199
+
200
+ If the task is unreasonable or infeasible, or if any of the tests are incorrect, please tell me. The solution should be robust, maintainable, and extendable.
201
+ ```
202
+
203
+ 1. **Be specific about desired behavior**: Consider describing exactly what you'd like to see in the output.
204
+
205
+ 2. **Frame your instructions with modifiers**: Adding modifiers that encourage Claude to increase the quality and detail of its output can help better shape Claude's performance. For example, instead of "Create an analytics dashboard", use "Create an analytics dashboard. Include as many relevant features and interactions as possible. Go beyond the basics to create a fully-featured implementation."
206
+
207
+ 3. **Request specific features explicitly**: Animations and interactive elements should be requested explicitly when desired.
208
+
209
+
210
+ # Be clear, direct, and detailed
211
+
212
+ When interacting with Claude, think of it as a brilliant but very new employee (with amnesia) who needs explicit instructions. Like any new employee, Claude does not have context on your norms, styles, guidelines, or preferred ways of working.
213
+ The more precisely you explain what you want, the better Claude's response will be.
214
+
215
+ **The golden rule of clear prompting:** Show your prompt to a colleague, ideally someone who has minimal context on the task, and ask them to follow the instructions. If they're confused, Claude will likely be too.
216
+
217
+ ## How to be clear, contextual, and specific
218
+
219
+ * **Give Claude contextual information:** Just like you might be able to better perform on a task if you knew more context, Claude will perform better if it has more contextual information. Some examples of contextual information:
220
+ * What the task results will be used for
221
+ * What audience the output is meant for
222
+ * What workflow the task is a part of, and where this task belongs in that workflow
223
+ * The end goal of the task, or what a successful task completion looks like
224
+ * **Be specific about what you want Claude to do:** For example, if you want Claude to output only code and nothing else, say so.
225
+ * **Provide instructions as sequential steps:** Use numbered lists or bullet points to better ensure that Claude carries out the task the exact way you want it to.
226
+
227
+ </prompt_best_practices>
228
+
229
+
230
+ IMPORTANT: Do not generate swarms with circular dependencies. For example, instance A connections to instance B, and instance B connections to instance A.