claude_swarm 1.0.1 → 1.0.2

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 (267) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +1 -1
  3. data/.claude/hooks/lint-code-files.rb +65 -0
  4. data/.rubocop.yml +22 -2
  5. data/CHANGELOG.md +14 -1
  6. data/CLAUDE.md +1 -1
  7. data/CONTRIBUTING.md +69 -0
  8. data/README.md +27 -2
  9. data/Rakefile +71 -3
  10. data/analyze_coverage.rb +94 -0
  11. data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
  12. data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
  13. data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
  14. data/docs/v2/README.md +308 -0
  15. data/docs/v2/guides/claude-code-agents.md +262 -0
  16. data/docs/v2/guides/complete-tutorial.md +3088 -0
  17. data/docs/v2/guides/getting-started.md +1456 -0
  18. data/docs/v2/guides/memory-adapters.md +998 -0
  19. data/docs/v2/guides/plugins.md +816 -0
  20. data/docs/v2/guides/quick-start-cli.md +1745 -0
  21. data/docs/v2/guides/rails-integration.md +1902 -0
  22. data/docs/v2/guides/swarm-memory.md +599 -0
  23. data/docs/v2/reference/cli.md +729 -0
  24. data/docs/v2/reference/ruby-dsl.md +2154 -0
  25. data/docs/v2/reference/yaml.md +1835 -0
  26. data/docs-team-swarm.yml +2222 -0
  27. data/examples/learning-assistant/assistant.md +7 -0
  28. data/examples/learning-assistant/example-memories/concept-example.md +90 -0
  29. data/examples/learning-assistant/example-memories/experience-example.md +66 -0
  30. data/examples/learning-assistant/example-memories/fact-example.md +76 -0
  31. data/examples/learning-assistant/example-memories/memory-index.md +78 -0
  32. data/examples/learning-assistant/example-memories/skill-example.md +168 -0
  33. data/examples/learning-assistant/learning_assistant.rb +34 -0
  34. data/examples/learning-assistant/learning_assistant.yml +20 -0
  35. data/examples/v2/dsl/01_basic.rb +44 -0
  36. data/examples/v2/dsl/02_core_parameters.rb +59 -0
  37. data/examples/v2/dsl/03_capabilities.rb +71 -0
  38. data/examples/v2/dsl/04_llm_parameters.rb +56 -0
  39. data/examples/v2/dsl/05_advanced_flags.rb +73 -0
  40. data/examples/v2/dsl/06_permissions.rb +80 -0
  41. data/examples/v2/dsl/07_mcp_server.rb +62 -0
  42. data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
  43. data/examples/v2/dsl/09_agent_hooks.rb +67 -0
  44. data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
  45. data/examples/v2/dsl/11_delegation.rb +60 -0
  46. data/examples/v2/dsl/12_complete_integration.rb +137 -0
  47. data/examples/v2/file_tools_swarm.yml +102 -0
  48. data/examples/v2/hooks/01_basic_hooks.rb +133 -0
  49. data/examples/v2/hooks/02_usage_tracking.rb +201 -0
  50. data/examples/v2/hooks/03_production_monitoring.rb +429 -0
  51. data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
  52. data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
  53. data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
  54. data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
  55. data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
  56. data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
  57. data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
  58. data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
  59. data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
  60. data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
  61. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
  62. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
  63. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
  64. data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
  65. data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
  66. data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
  67. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
  68. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
  69. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
  70. data/examples/v2/hooks/swarm_summary.sh +44 -0
  71. data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
  72. data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
  73. data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
  74. data/examples/v2/hooks/validate_bash.rb +59 -0
  75. data/examples/v2/multi_directory_permissions.yml +221 -0
  76. data/examples/v2/node_context_demo.rb +127 -0
  77. data/examples/v2/node_workflow.rb +173 -0
  78. data/examples/v2/path_resolution_demo.rb +216 -0
  79. data/examples/v2/simple-swarm-v2.rb +90 -0
  80. data/examples/v2/simple-swarm-v2.yml +62 -0
  81. data/examples/v2/swarm.yml +71 -0
  82. data/examples/v2/swarm_with_hooks.yml +61 -0
  83. data/examples/v2/swarm_with_hooks_simple.yml +25 -0
  84. data/examples/v2/think_tool_demo.rb +62 -0
  85. data/exe/swarm +6 -0
  86. data/lib/claude_swarm/claude_mcp_server.rb +0 -6
  87. data/lib/claude_swarm/cli.rb +10 -3
  88. data/lib/claude_swarm/commands/ps.rb +19 -20
  89. data/lib/claude_swarm/commands/show.rb +1 -1
  90. data/lib/claude_swarm/configuration.rb +10 -12
  91. data/lib/claude_swarm/mcp_generator.rb +10 -1
  92. data/lib/claude_swarm/orchestrator.rb +73 -49
  93. data/lib/claude_swarm/system_utils.rb +37 -11
  94. data/lib/claude_swarm/version.rb +1 -1
  95. data/lib/claude_swarm/worktree_manager.rb +1 -0
  96. data/lib/claude_swarm/yaml_loader.rb +22 -0
  97. data/lib/claude_swarm.rb +6 -2
  98. data/lib/swarm_cli/cli.rb +201 -0
  99. data/lib/swarm_cli/command_registry.rb +61 -0
  100. data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
  101. data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
  102. data/lib/swarm_cli/commands/migrate.rb +55 -0
  103. data/lib/swarm_cli/commands/run.rb +173 -0
  104. data/lib/swarm_cli/config_loader.rb +97 -0
  105. data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
  106. data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
  107. data/lib/swarm_cli/interactive_repl.rb +918 -0
  108. data/lib/swarm_cli/mcp_serve_options.rb +44 -0
  109. data/lib/swarm_cli/mcp_tools_options.rb +59 -0
  110. data/lib/swarm_cli/migrate_options.rb +54 -0
  111. data/lib/swarm_cli/migrator.rb +132 -0
  112. data/lib/swarm_cli/options.rb +151 -0
  113. data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
  114. data/lib/swarm_cli/ui/components/content_block.rb +120 -0
  115. data/lib/swarm_cli/ui/components/divider.rb +57 -0
  116. data/lib/swarm_cli/ui/components/panel.rb +62 -0
  117. data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
  118. data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
  119. data/lib/swarm_cli/ui/formatters/number.rb +58 -0
  120. data/lib/swarm_cli/ui/formatters/text.rb +77 -0
  121. data/lib/swarm_cli/ui/formatters/time.rb +73 -0
  122. data/lib/swarm_cli/ui/icons.rb +59 -0
  123. data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
  124. data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
  125. data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
  126. data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
  127. data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
  128. data/lib/swarm_cli/version.rb +5 -0
  129. data/lib/swarm_cli.rb +44 -0
  130. data/lib/swarm_memory/adapters/base.rb +141 -0
  131. data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
  132. data/lib/swarm_memory/chat_extension.rb +34 -0
  133. data/lib/swarm_memory/cli/commands.rb +306 -0
  134. data/lib/swarm_memory/core/entry.rb +37 -0
  135. data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
  136. data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
  137. data/lib/swarm_memory/core/path_normalizer.rb +75 -0
  138. data/lib/swarm_memory/core/semantic_index.rb +244 -0
  139. data/lib/swarm_memory/core/storage.rb +288 -0
  140. data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
  141. data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
  142. data/lib/swarm_memory/dsl/memory_config.rb +113 -0
  143. data/lib/swarm_memory/embeddings/embedder.rb +36 -0
  144. data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
  145. data/lib/swarm_memory/errors.rb +21 -0
  146. data/lib/swarm_memory/integration/cli_registration.rb +30 -0
  147. data/lib/swarm_memory/integration/configuration.rb +43 -0
  148. data/lib/swarm_memory/integration/registration.rb +31 -0
  149. data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
  150. data/lib/swarm_memory/optimization/analyzer.rb +244 -0
  151. data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
  152. data/lib/swarm_memory/prompts/memory.md.erb +109 -0
  153. data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
  154. data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
  155. data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
  156. data/lib/swarm_memory/search/semantic_search.rb +112 -0
  157. data/lib/swarm_memory/search/text_search.rb +42 -0
  158. data/lib/swarm_memory/search/text_similarity.rb +80 -0
  159. data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
  160. data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
  161. data/lib/swarm_memory/tools/load_skill.rb +313 -0
  162. data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
  163. data/lib/swarm_memory/tools/memory_delete.rb +99 -0
  164. data/lib/swarm_memory/tools/memory_edit.rb +185 -0
  165. data/lib/swarm_memory/tools/memory_glob.rb +160 -0
  166. data/lib/swarm_memory/tools/memory_grep.rb +247 -0
  167. data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
  168. data/lib/swarm_memory/tools/memory_read.rb +123 -0
  169. data/lib/swarm_memory/tools/memory_write.rb +231 -0
  170. data/lib/swarm_memory/utils.rb +50 -0
  171. data/lib/swarm_memory/version.rb +5 -0
  172. data/lib/swarm_memory.rb +166 -0
  173. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
  174. data/lib/swarm_sdk/agent/builder.rb +461 -0
  175. data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
  176. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  177. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
  178. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
  179. data/lib/swarm_sdk/agent/chat.rb +1159 -0
  180. data/lib/swarm_sdk/agent/context.rb +112 -0
  181. data/lib/swarm_sdk/agent/context_manager.rb +309 -0
  182. data/lib/swarm_sdk/agent/definition.rb +556 -0
  183. data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
  184. data/lib/swarm_sdk/configuration.rb +296 -0
  185. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  186. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  187. data/lib/swarm_sdk/context_compactor.rb +340 -0
  188. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  189. data/lib/swarm_sdk/hooks/context.rb +197 -0
  190. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  191. data/lib/swarm_sdk/hooks/error.rb +29 -0
  192. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  193. data/lib/swarm_sdk/hooks/registry.rb +147 -0
  194. data/lib/swarm_sdk/hooks/result.rb +150 -0
  195. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  196. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  197. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  198. data/lib/swarm_sdk/log_collector.rb +51 -0
  199. data/lib/swarm_sdk/log_stream.rb +69 -0
  200. data/lib/swarm_sdk/markdown_parser.rb +75 -0
  201. data/lib/swarm_sdk/model_aliases.json +5 -0
  202. data/lib/swarm_sdk/models.json +1 -0
  203. data/lib/swarm_sdk/models.rb +120 -0
  204. data/lib/swarm_sdk/node/agent_config.rb +49 -0
  205. data/lib/swarm_sdk/node/builder.rb +439 -0
  206. data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
  207. data/lib/swarm_sdk/node_context.rb +170 -0
  208. data/lib/swarm_sdk/node_orchestrator.rb +384 -0
  209. data/lib/swarm_sdk/permissions/config.rb +239 -0
  210. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  211. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  212. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  213. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  214. data/lib/swarm_sdk/plugin.rb +147 -0
  215. data/lib/swarm_sdk/plugin_registry.rb +101 -0
  216. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
  217. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  218. data/lib/swarm_sdk/result.rb +97 -0
  219. data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
  220. data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
  221. data/lib/swarm_sdk/swarm/builder.rb +586 -0
  222. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  223. data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
  224. data/lib/swarm_sdk/swarm.rb +982 -0
  225. data/lib/swarm_sdk/tools/bash.rb +274 -0
  226. data/lib/swarm_sdk/tools/clock.rb +44 -0
  227. data/lib/swarm_sdk/tools/delegate.rb +164 -0
  228. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  229. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  230. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
  231. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  232. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  233. data/lib/swarm_sdk/tools/edit.rb +150 -0
  234. data/lib/swarm_sdk/tools/glob.rb +158 -0
  235. data/lib/swarm_sdk/tools/grep.rb +228 -0
  236. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  237. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  238. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  239. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  240. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  241. data/lib/swarm_sdk/tools/read.rb +251 -0
  242. data/lib/swarm_sdk/tools/registry.rb +93 -0
  243. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
  244. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
  245. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
  246. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  247. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
  248. data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
  249. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  250. data/lib/swarm_sdk/tools/think.rb +95 -0
  251. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  252. data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
  253. data/lib/swarm_sdk/tools/write.rb +117 -0
  254. data/lib/swarm_sdk/utils.rb +50 -0
  255. data/lib/swarm_sdk/version.rb +5 -0
  256. data/lib/swarm_sdk.rb +157 -0
  257. data/llm.v2.txt +13407 -0
  258. data/rubocop/cop/security/no_reflection_methods.rb +47 -0
  259. data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
  260. data/swarm_cli.gemspec +57 -0
  261. data/swarm_memory.gemspec +28 -0
  262. data/swarm_sdk.gemspec +41 -0
  263. data/team.yml +1 -1
  264. data/team_full.yml +1875 -0
  265. data/{team_v2.yml → team_sdk.yml} +121 -52
  266. metadata +247 -4
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,71 @@
1
+ version: 2
2
+
3
+ swarm:
4
+ name: "Simple Swarm"
5
+ # all_agents:
6
+ # permissions:
7
+ # Bash:
8
+ # denied_commands: ["^ls.*"]
9
+ # allowed_paths: ["tmp/LOL/**/*"]
10
+ # Read:
11
+ # denied_paths: ["lib/**/*"]
12
+ lead: coordinator
13
+
14
+ agents:
15
+ # Coordinator agent - orchestrates file operations and delegates to worker
16
+ coordinator:
17
+ description: "A helpful assistant"
18
+ model: claude-sonnet-4-5-20250929
19
+ provider: openai
20
+ parameters:
21
+ temperature: 1.0
22
+ mcp_servers:
23
+ - name: filesystem
24
+ type: stdio
25
+ command: npx
26
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
27
+ env: {}
28
+ timeout: 30 # seconds
29
+ # api_version: "v1/responses"
30
+
31
+ # Coordinator has read-only tools plus ability to delegate
32
+ # TodoWrite helps track progress on complex multi-step tasks
33
+ tools:
34
+ - Bash
35
+ # - Write
36
+
37
+ # Can delegate work to the worker agent
38
+ delegates_to:
39
+ - worker
40
+
41
+ directory: .
42
+
43
+ system_prompt: |
44
+ You are a helpful assistant.
45
+
46
+ # Worker agent - performs file write/edit operations in tmp/ directory only
47
+ worker:
48
+ description: "A helpful assistant"
49
+ model: claude-sonnet-4-5-20250929
50
+ provider: openai
51
+ # api_version: "v1/responses"
52
+
53
+ # Worker has all tools including Write and Edit
54
+ # TodoWrite helps track progress, MultiEdit enables multiple replacements
55
+ tools:
56
+ - Read
57
+ # - Write
58
+ # - Edit
59
+ # - MultiEdit
60
+ - Grep
61
+ - Glob
62
+ - TodoWrite
63
+
64
+ # Worker doesn't delegate - it's a leaf node
65
+ delegates_to: []
66
+
67
+ directory: .
68
+
69
+ system_prompt: |
70
+ You are a helpful assistant.
71
+
@@ -0,0 +1,61 @@
1
+ # SwarmSDK Example: YAML Hooks Feature
2
+ #
3
+ # This example demonstrates YAML hooks that execute shell commands at various
4
+ # lifecycle points. Both YAML and Ruby DSL support hooks using the same terminology.
5
+ #
6
+ # YAML hooks: Shell commands defined in YAML files
7
+ # DSL hooks: Ruby blocks defined in code
8
+ #
9
+ # Setup: export OPENAI_API_KEY=your-api-key
10
+ # Run: swarm run lib/swarm_sdk/examples/swarm_with_hooks.yml -p "your prompt"
11
+ #
12
+ # Or programmatically:
13
+ # swarm = SwarmSDK::Swarm.load("lib/swarm_sdk/examples/swarm_with_hooks.yml")
14
+ # result = swarm.execute("your prompt")
15
+
16
+ version: 2
17
+
18
+ swarm:
19
+ name: "Development Team with Hooks"
20
+ lead: backend
21
+
22
+ # Swarm-level hooks (only swarm_start and swarm_stop)
23
+ # These run at swarm lifecycle events
24
+ hooks:
25
+ swarm_start:
26
+ - type: command
27
+ command: "echo 'Swarm starting: Development Team' > tmp/log.txt"
28
+ timeout: 5
29
+
30
+ swarm_stop:
31
+ - type: command
32
+ command: "echo 'Swarm completed successfully' >> tmp/log.txt"
33
+ timeout: 5
34
+
35
+ agents:
36
+ backend:
37
+ description: "Backend developer with custom hooks"
38
+ model: gpt-5
39
+ provider: openai
40
+ system_prompt: "You are a backend developer. Build APIs and databases."
41
+ tools:
42
+ - Read
43
+ - Glob
44
+ hooks:
45
+ user_prompt:
46
+ - type: command
47
+ command: "echo 'Cannot proceed with this prompt' >&2;exit 2"
48
+ timeout: 10
49
+ pre_tool_use:
50
+ - matcher: "Read"
51
+ type: command
52
+ command: "echo \"you can't read files\" >&2; echo \"Can't read files!\" >> tmp/log.txt; exit 2"
53
+ timeout: 10
54
+ - matcher: "Glob"
55
+ type: command
56
+ command: "echo \"you can't use Glob, not authorized\" >&2; echo cannot use Glob, not authorized >> tmp/log.txt; exit 2"
57
+ timeout: 10
58
+ - matcher: "Grep"
59
+ type: command
60
+ command: "echo \"you can't use Grep, not authorized\" >&2; echo cannot use Grep, not authorized >> tmp/log.txt; exit 2"
61
+ timeout: 10
@@ -0,0 +1,25 @@
1
+ version: 2
2
+
3
+ swarm:
4
+ name: "Simple Hooks Test"
5
+ lead: backend
6
+
7
+ hooks:
8
+ swarm_start:
9
+ - type: command
10
+ command: "echo 'Swarm starting' >&2"
11
+
12
+ agents:
13
+ backend:
14
+ description: "Backend developer"
15
+ model: gpt-5
16
+ system_prompt: "You are a backend developer."
17
+ tools:
18
+ - Read
19
+ - Bash
20
+
21
+ hooks:
22
+ pre_tool_use:
23
+ - matcher: "Bash"
24
+ type: command
25
+ command: "python3 lib/swarm_sdk/examples/hooks/validate_bash.py"
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Think Tool Demonstration
4
+ #
5
+ # This example demonstrates how to use the Think tool for explicit reasoning.
6
+ # The Think tool allows agents to "think out loud" by recording thoughts that
7
+ # become part of the conversation context, leading to better reasoning and
8
+ # problem-solving outcomes.
9
+ #
10
+ # Usage:
11
+ # swarm run examples/v2/think_tool_demo.rb
12
+ # swarm run examples/v2/think_tool_demo.rb "Calculate the optimal caching strategy for this API"
13
+ # swarm run examples/v2/think_tool_demo.rb -p "Design a database schema for an e-commerce system"
14
+
15
+ SwarmSDK.build do
16
+ name "Think Tool Demo"
17
+ lead :problem_solver
18
+
19
+ # Problem solver agent that uses the Think tool frequently
20
+ agent :problem_solver do
21
+ coding_agent(true)
22
+ description "Problem solver who thinks through solutions step-by-step"
23
+ model "gpt-5-mini"
24
+ provider "openai"
25
+
26
+ system_prompt <<~PROMPT
27
+ You are a thoughtful problem solver who uses explicit reasoning to tackle complex tasks.
28
+
29
+ **IMPORTANT: Use the Think tool frequently throughout your work!**
30
+
31
+ The Think tool allows you to write down your thoughts, plans, and intermediate
32
+ calculations. This leads to significantly better outcomes because:
33
+
34
+ 1. It helps you break down complex problems into manageable steps
35
+ 2. It allows you to track your progress and plan next actions
36
+ 3. It helps with arithmetic and calculations
37
+ 4. It maintains context across multiple steps
38
+
39
+ **Recommended usage pattern:**
40
+ 1. THINK before starting any task - understand the problem and create a plan
41
+ 2. THINK after reading files or getting information - process what you learned
42
+ 3. THINK between steps - track progress and decide next actions
43
+ 4. THINK when doing calculations - work through math step by step
44
+ 5. THINK when encountering complexity - break down the problem
45
+
46
+ Example workflow for a coding task:
47
+ 1. Think: "User wants X. Let me break this into: 1) Read code, 2) Identify changes, 3) Implement, 4) Test"
48
+ 2. Read relevant files
49
+ 3. Think: "I see the structure. Key files are A, B. I need to modify B's function foo()"
50
+ 4. Make changes
51
+ 5. Think: "Changes made. Next: verify the tests pass"
52
+ 6. Run tests
53
+ 7. Think: "Tests pass. Task complete."
54
+
55
+ For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
56
+
57
+ Remember: Successful agents use Think 5-10 times per task on average!
58
+ PROMPT
59
+
60
+ tools :Think, :Read, :Write, :Edit, :Bash, :Grep, :Glob
61
+ end
62
+ end
data/exe/swarm ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "swarm_cli"
5
+
6
+ SwarmCLI::CLI.start(ARGV)
@@ -52,12 +52,6 @@ module ClaudeSwarm
52
52
  end
53
53
 
54
54
  def start
55
- # Track this process
56
- if @executor.session_path && File.exist?(@executor.session_path)
57
- tracker = ProcessTracker.new(@executor.session_path)
58
- tracker.track_pid(Process.pid, "mcp_#{@instance_config[:name]}")
59
- end
60
-
61
55
  server = FastMcp::Server.new(
62
56
  name: @instance_config[:name],
63
57
  version: "1.0.0",
@@ -3,6 +3,7 @@
3
3
  module ClaudeSwarm
4
4
  class CLI < Thor
5
5
  include SystemUtils
6
+
6
7
  class << self
7
8
  def exit_on_failure?
8
9
  true
@@ -458,9 +459,15 @@ module ClaudeSwarm
458
459
  next unless File.exist?(config_file)
459
460
 
460
461
  # Load the config to get swarm info
461
- config_data = YAML.load_file(config_file)
462
- swarm_name = config_data.dig("swarm", "name") || "Unknown"
463
- main_instance = config_data.dig("swarm", "main") || "Unknown"
462
+ begin
463
+ config_data = YamlLoader.load_config_file(config_file)
464
+ swarm_name = config_data.dig("swarm", "name") || "Unknown"
465
+ main_instance = config_data.dig("swarm", "main") || "Unknown"
466
+ rescue ClaudeSwarm::Error => e
467
+ # Warn about corrupted config files but continue
468
+ say_error("⚠️ Skipping session #{session_id} - #{e.message}")
469
+ next
470
+ end
464
471
 
465
472
  mcp_files = Dir.glob(File.join(session_path, "*.mcp.json"))
466
473
 
@@ -10,22 +10,9 @@ module ClaudeSwarm
10
10
  return
11
11
  end
12
12
 
13
- sessions = []
14
-
15
- # Read all symlinks in run directory
16
- Dir.glob("#{run_dir}/*").each do |symlink|
17
- next unless File.symlink?(symlink)
18
-
19
- begin
20
- session_dir = File.readlink(symlink)
21
- # Skip if target doesn't exist (stale symlink)
22
- next unless Dir.exist?(session_dir)
23
-
24
- session_info = parse_session_info(session_dir)
25
- sessions << session_info if session_info
26
- rescue StandardError
27
- # Skip problematic symlinks
28
- end
13
+ # Read all symlinks in run directory and process them
14
+ sessions = Dir.glob("#{run_dir}/*").filter_map do |symlink|
15
+ process_symlink(symlink)
29
16
  end
30
17
 
31
18
  if sessions.empty?
@@ -83,14 +70,28 @@ module ClaudeSwarm
83
70
 
84
71
  private
85
72
 
86
- def parse_session_info(session_dir)
73
+ def process_symlink(symlink)
74
+ session_dir = File.readlink(symlink)
87
75
  session_id = File.basename(session_dir)
76
+ # Skip if target doesn't exist (stale symlink)
77
+ return unless Dir.exist?(session_dir)
88
78
 
79
+ parse_session_info(session_id, session_dir)
80
+ rescue Errno::EINVAL
81
+ # Not a symlink, skip it
82
+ nil
83
+ rescue StandardError => e
84
+ # Try to get session_id if we have session_dir
85
+ warn("⚠️ Skipping session #{session_id}: #{e.message}")
86
+ nil
87
+ end
88
+
89
+ def parse_session_info(session_id, session_dir)
89
90
  # Load config for swarm name and main directory
90
91
  config_file = File.join(session_dir, "config.yml")
91
92
  return unless File.exist?(config_file)
92
93
 
93
- config = YAML.load_file(config_file)
94
+ config = YamlLoader.load_config_file(config_file)
94
95
  swarm_name = config.dig("swarm", "name") || "Unknown"
95
96
  main_instance = config.dig("swarm", "main")
96
97
 
@@ -139,8 +140,6 @@ module ClaudeSwarm
139
140
  directory: directories_str,
140
141
  start_time: start_time,
141
142
  }
142
- rescue StandardError
143
- nil
144
143
  end
145
144
 
146
145
  def get_start_time(session_dir)
@@ -11,7 +11,7 @@ module ClaudeSwarm
11
11
  end
12
12
 
13
13
  # Load config to get main instance name
14
- config = YAML.load_file(File.join(session_path, "config.yml"))
14
+ config = YamlLoader.load_config_file(File.join(session_path, "config.yml"))
15
15
  main_instance_name = config.dig("swarm", "main")
16
16
 
17
17
  # Parse all events to build instance data
@@ -11,14 +11,15 @@ module ClaudeSwarm
11
11
  # Regex patterns
12
12
  ENV_VAR_PATTERN = /\$\{([^}]+)\}/
13
13
  ENV_VAR_WITH_DEFAULT_PATTERN = /\$\{([^:}]+)(:=([^}]*))?\}/
14
- O_SERIES_MODEL_PATTERN = /^o\d+(\s+(Preview|preview))?(-pro|-mini|-deep-research|-mini-deep-research)?$/
14
+ O_SERIES_MODEL_PATTERN = /^(o\d+(\s+(Preview|preview))?(-pro|-mini|-deep-research|-mini-deep-research)?|gpt-5(-mini|-nano)?)$/
15
15
 
16
- attr_reader :config, :config_path, :swarm, :swarm_name, :main_instance, :instances
16
+ attr_reader :config, :config_path, :swarm, :swarm_name, :main_instance, :instances, :root_directory
17
17
 
18
18
  def initialize(config_path, base_dir: nil, options: {})
19
19
  @config_path = Pathname.new(config_path).expand_path
20
20
  @config_dir = @config_path.dirname
21
21
  @base_dir = base_dir || @config_dir
22
+ @root_directory = @base_dir
22
23
  @options = options
23
24
  load_and_validate
24
25
  end
@@ -59,7 +60,7 @@ module ClaudeSwarm
59
60
  end
60
61
 
61
62
  def load_and_validate
62
- @config = YAML.load_file(@config_path, aliases: true)
63
+ @config = YamlLoader.load_config_file(@config_path)
63
64
  interpolate_env_vars!(@config)
64
65
  validate_version
65
66
  validate_swarm
@@ -67,10 +68,6 @@ module ClaudeSwarm
67
68
  # Skip directory validation if before commands are present
68
69
  # They might create the directories
69
70
  validate_directories unless has_before_commands?
70
- rescue Errno::ENOENT
71
- raise Error, "Configuration file not found: #{@config_path}"
72
- rescue Psych::SyntaxError => e
73
- raise Error, "Invalid YAML syntax: #{e.message}"
74
71
  end
75
72
 
76
73
  def interpolate_env_vars!(obj)
@@ -164,16 +161,16 @@ module ClaudeSwarm
164
161
  raise Error, "Instance '#{name}' has invalid reasoning_effort '#{config["reasoning_effort"]}'. Must be 'low', 'medium', or 'high'"
165
162
  end
166
163
 
167
- # Validate it's only used with o-series models
168
- # Support patterns like: o1, o1-mini, o1-pro, o1 Preview, o3-deep-research, o4-mini-deep-research, etc.
164
+ # Validate it's only used with o-series or gpt-5 models
165
+ # Support patterns like: o1, o1-mini, o1-pro, o1 Preview, o3-deep-research, o4-mini-deep-research, gpt-5, gpt-5-mini, gpt-5-nano, etc.
169
166
  unless model&.match?(O_SERIES_MODEL_PATTERN)
170
- raise Error, "Instance '#{name}' has reasoning_effort but model '#{model}' is not an o-series model (o1, o1 Preview, o1-mini, o1-pro, o3, o3-mini, o3-pro, o3-deep-research, o4-mini, o4-mini-deep-research, etc.)"
167
+ raise Error, "Instance '#{name}' has reasoning_effort but model '#{model}' is not an o-series or gpt-5 model (o1, o1 Preview, o1-mini, o1-pro, o3, o3-mini, o3-pro, o3-deep-research, o4-mini, o4-mini-deep-research, gpt-5, gpt-5-mini, gpt-5-nano, etc.)"
171
168
  end
172
169
  end
173
170
 
174
- # Validate temperature is not used with o-series models when provider is openai
171
+ # Validate temperature is not used with o-series or gpt-5 models when provider is openai
175
172
  if provider == "openai" && config["temperature"] && model&.match?(O_SERIES_MODEL_PATTERN)
176
- raise Error, "Instance '#{name}' has temperature parameter but model '#{model}' is an o-series model. O-series models use deterministic reasoning and don't accept temperature settings"
173
+ raise Error, "Instance '#{name}' has temperature parameter but model '#{model}' is an o-series or gpt-5 model. O-series and gpt-5 models use deterministic reasoning and don't accept temperature settings"
177
174
  end
178
175
 
179
176
  # Validate OpenAI-specific fields only when provider is not "openai"
@@ -211,6 +208,7 @@ module ClaudeSwarm
211
208
  disallowed_tools: Array(config["disallowed_tools"]),
212
209
  mcps: parse_mcps(config["mcps"] || []),
213
210
  prompt: config["prompt"],
211
+ prompt_file: config["prompt_file"],
214
212
  description: config["description"],
215
213
  vibe: config["vibe"],
216
214
  worktree: parse_worktree_value(config["worktree"]),
@@ -134,7 +134,16 @@ module ClaudeSwarm
134
134
  args.push("--directories", *instance[:directories]) if instance[:directories] && instance[:directories].size > 1
135
135
 
136
136
  # Add optional arguments
137
- args.push("--prompt", instance[:prompt]) if instance[:prompt]
137
+ # Handle prompt_file by reading the file contents
138
+ if instance[:prompt_file]
139
+ prompt_file_path = File.join(@config.root_directory, instance[:prompt_file])
140
+ if File.exist?(prompt_file_path)
141
+ prompt_content = File.read(prompt_file_path)
142
+ args.push("--prompt", prompt_content)
143
+ end
144
+ elsif instance[:prompt]
145
+ args.push("--prompt", instance[:prompt])
146
+ end
138
147
 
139
148
  args.push("--description", instance[:description]) if instance[:description]
140
149
 
@@ -6,12 +6,6 @@ module ClaudeSwarm
6
6
 
7
7
  attr_reader :config, :session_path, :session_log_path
8
8
 
9
- ["INT", "TERM", "QUIT"].each do |signal|
10
- Signal.trap(signal) do
11
- puts "\n🛑 Received #{signal} signal."
12
- end
13
- end
14
-
15
9
  def initialize(configuration, mcp_generator, vibe: false, prompt: nil, interactive_prompt: nil, stream_logs: false, debug: false,
16
10
  restore_session_path: nil, worktree: nil, session_id: nil)
17
11
  @config = configuration
@@ -72,19 +66,40 @@ module ClaudeSwarm
72
66
  # Track start time
73
67
  @start_time = Time.now
74
68
 
69
+ # Setup signal handlers for graceful shutdown
70
+ setup_signal_handlers do
71
+ @signal_received = true
72
+ cleanup_all
73
+ end
74
+
75
75
  begin
76
76
  start_internal
77
77
  rescue StandardError => e
78
78
  # Ensure cleanup happens even on unexpected errors
79
- cleanup_processes
80
- cleanup_run_symlink
81
- cleanup_worktrees
79
+ cleanup_all
82
80
  raise e
83
81
  end
84
82
  end
85
83
 
86
84
  private
87
85
 
86
+ def cleanup_all
87
+ execute_after_commands_once
88
+ cleanup_processes
89
+ cleanup_run_symlink
90
+ cleanup_worktrees
91
+ end
92
+
93
+ def setup_signal_handlers(&cleanup_block)
94
+ ["INT", "TERM", "QUIT", "HUP"].each do |signal|
95
+ Signal.trap(signal) do
96
+ puts "\n🛑 Received #{signal} signal. Shutting down gracefully..."
97
+ cleanup_block&.call
98
+ exit(0)
99
+ end
100
+ end
101
+ end
102
+
88
103
  def start_internal
89
104
  if @restore_session_path
90
105
  non_interactive_output do
@@ -142,9 +157,7 @@ module ClaudeSwarm
142
157
  end
143
158
  rescue StandardError => e
144
159
  non_interactive_output { print("❌ Failed to setup worktrees: #{e.message}") }
145
- cleanup_processes
146
- cleanup_run_symlink
147
- cleanup_worktrees
160
+ cleanup_all
148
161
  raise
149
162
  end
150
163
  end
@@ -226,9 +239,7 @@ module ClaudeSwarm
226
239
  success = execute_before_commands?(before_commands)
227
240
  unless success
228
241
  non_interactive_output { print("❌ Before commands failed. Aborting swarm launch.") }
229
- cleanup_processes
230
- cleanup_run_symlink
231
- cleanup_worktrees
242
+ cleanup_all
232
243
  exit(1)
233
244
  end
234
245
  end
@@ -245,9 +256,7 @@ module ClaudeSwarm
245
256
  end
246
257
  rescue ClaudeSwarm::Error => e
247
258
  non_interactive_output { print("❌ Directory validation failed: #{e.message}") }
248
- cleanup_processes
249
- cleanup_run_symlink
250
- cleanup_worktrees
259
+ cleanup_all
251
260
  exit(1)
252
261
  end
253
262
  end
@@ -261,7 +270,12 @@ module ClaudeSwarm
261
270
  if @non_interactive_prompt
262
271
  stream_to_session_log(*command)
263
272
  else
264
- system!(*command)
273
+ system_with_pid!(*command) do |pid|
274
+ @process_tracker.track_pid(pid, "claude_#{@config.main_instance}")
275
+ non_interactive_output do
276
+ puts "✓ Claude instance started with PID: #{pid}"
277
+ end
278
+ end
265
279
  end
266
280
  end
267
281
  end
@@ -279,37 +293,10 @@ module ClaudeSwarm
279
293
  display_summary
280
294
 
281
295
  # Execute after commands if specified
282
- # Use the same logic as before commands for consistency
283
- after_commands = @config.after_commands
284
- if after_commands.any? && !@restore_session_path
285
- # Determine where to run after commands (same logic as before commands)
286
- if File.exist?(main_instance[:directory])
287
- # Directory exists, run commands in it
288
- after_commands_dir = main_instance[:directory]
289
- else
290
- # Directory doesn't exist (shouldn't happen after main instance runs, but be safe)
291
- parent_dir = File.dirname(File.expand_path(main_instance[:directory]))
292
- after_commands_dir = parent_dir
293
- end
294
-
295
- Dir.chdir(after_commands_dir) do
296
- non_interactive_output do
297
- print("⚙️ Executing after commands...")
298
- end
299
-
300
- success = execute_after_commands?(after_commands)
301
- unless success
302
- non_interactive_output do
303
- puts "⚠️ Some after commands failed"
304
- end
305
- end
306
- end
307
- end
296
+ execute_after_commands_once
308
297
 
309
298
  # Clean up child processes and run symlink
310
- cleanup_processes
311
- cleanup_run_symlink
312
- cleanup_worktrees
299
+ cleanup_all
313
300
  end
314
301
 
315
302
  def non_interactive_output
@@ -327,6 +314,42 @@ module ClaudeSwarm
327
314
  execute_commands(commands, phase: "after", fail_fast: false)
328
315
  end
329
316
 
317
+ def execute_after_commands_once
318
+ # Ensure after commands are only executed once
319
+ return if @after_commands_executed
320
+
321
+ @after_commands_executed = true
322
+
323
+ # Use the same logic as before commands for consistency
324
+ after_commands = @config.after_commands
325
+ return if after_commands.empty? || @restore_session_path
326
+
327
+ main_instance = @config.main_instance_config
328
+
329
+ # Determine where to run after commands (same logic as before commands)
330
+ if File.exist?(main_instance[:directory])
331
+ # Directory exists, run commands in it
332
+ after_commands_dir = main_instance[:directory]
333
+ else
334
+ # Directory doesn't exist (shouldn't happen after main instance runs, but be safe)
335
+ parent_dir = File.dirname(File.expand_path(main_instance[:directory]))
336
+ after_commands_dir = parent_dir
337
+ end
338
+
339
+ Dir.chdir(after_commands_dir) do
340
+ non_interactive_output do
341
+ print("⚙️ Executing after commands...")
342
+ end
343
+
344
+ success = execute_after_commands?(after_commands)
345
+ unless success
346
+ non_interactive_output do
347
+ puts "⚠️ Some after commands failed"
348
+ end
349
+ end
350
+ end
351
+ end
352
+
330
353
  def save_swarm_config_path(session_path)
331
354
  # Copy the YAML config file to the session directory
332
355
  config_copy_path = File.join(session_path, "config.yml")
@@ -800,7 +823,8 @@ module ClaudeSwarm
800
823
  all_succeeded = true
801
824
 
802
825
  # Setup logger for session logging if we have a session path
803
- logger = Logger.new(@session_log_path, level: :info)
826
+ log_dev = @signal_received ? nil : @session_log_path
827
+ logger = Logger.new(log_dev, level: :info)
804
828
 
805
829
  commands.each_with_index do |command, index|
806
830
  # Log the command execution to session log
@@ -3,18 +3,44 @@
3
3
  module ClaudeSwarm
4
4
  module SystemUtils
5
5
  def system!(*args)
6
- success = system(*args)
7
- unless success
8
- exit_status = $CHILD_STATUS&.exitstatus || 1
9
- command_str = args.size == 1 ? args.first : args.join(" ")
10
- if exit_status == 143 # timeout command exit status = 128 + 15 (SIGTERM)
11
- warn("⏱️ Command timeout: #{command_str}")
12
- else
13
- warn("❌ Command failed with exit status: #{exit_status}")
14
- raise Error, "Command failed with exit status #{exit_status}: #{command_str}"
15
- end
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}"
16
41
  end
17
- success
42
+
43
+ false
18
44
  end
19
45
  end
20
46
  end