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,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ module Stores
6
+ # TodoManager provides per-agent todo list storage
7
+ #
8
+ # Each agent maintains its own independent todo list that persists
9
+ # throughout the agent's execution session. This allows agents to
10
+ # track progress on complex multi-step tasks.
11
+ class TodoManager
12
+ @storage = {}
13
+ @mutex = Mutex.new
14
+
15
+ class << self
16
+ # Get the current todo list for an agent
17
+ #
18
+ # @param agent_id [Symbol, String] Unique agent identifier
19
+ # @return [Array<Hash>] Array of todo items
20
+ def get_todos(agent_id)
21
+ @mutex.synchronize do
22
+ @storage[agent_id.to_sym] ||= []
23
+ end
24
+ end
25
+
26
+ # Set the todo list for an agent
27
+ #
28
+ # @param agent_id [Symbol, String] Unique agent identifier
29
+ # @param todos [Array<Hash>] Array of todo items
30
+ # @return [Array<Hash>] The stored todos
31
+ def set_todos(agent_id, todos)
32
+ @mutex.synchronize do
33
+ @storage[agent_id.to_sym] = todos
34
+ end
35
+ end
36
+
37
+ # Clear all todos for an agent
38
+ #
39
+ # @param agent_id [Symbol, String] Unique agent identifier
40
+ def clear_todos(agent_id)
41
+ @mutex.synchronize do
42
+ @storage.delete(agent_id.to_sym)
43
+ end
44
+ end
45
+
46
+ # Clear all todos for all agents
47
+ def clear_all
48
+ @mutex.synchronize do
49
+ @storage.clear
50
+ end
51
+ end
52
+
53
+ # Get summary of all agent todo lists
54
+ #
55
+ # @return [Hash] Map of agent_id => todo count
56
+ def summary
57
+ @mutex.synchronize do
58
+ @storage.transform_values(&:size)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # Think tool for explicit reasoning and planning
6
+ #
7
+ # Allows the agent to write down thoughts, plans, strategies, and intermediate
8
+ # calculations. These thoughts become part of the conversation context, enabling
9
+ # better attention and reasoning through complex problems.
10
+ #
11
+ # This is inspired by research showing that explicitly articulating reasoning steps
12
+ # (chain-of-thought prompting) leads to significantly better outcomes, especially
13
+ # for complex tasks requiring multi-step reasoning or arithmetic.
14
+ class Think < RubyLLM::Tool
15
+ def name
16
+ "Think"
17
+ end
18
+
19
+ description <<~DESC
20
+ **IMPORTANT: You SHOULD use this tool frequently throughout your work. Using this tool leads to significantly
21
+ better outcomes and more accurate solutions. Make it a habit to think before acting.**
22
+
23
+ This tool allows you to write down your thoughts, plans, strategies, and intermediate calculations.
24
+ Think of it as your working memory - just as humans think before speaking or acting, you should think before
25
+ using other tools or providing responses.
26
+
27
+ **STRONGLY RECOMMENDED to use this tool:**
28
+ - **ALWAYS** before starting any task (even simple ones)
29
+ - **ALWAYS** when you need to do any arithmetic or counting
30
+ - **ALWAYS** after reading files or getting search results to process what you learned
31
+ - **FREQUENTLY** between steps to track progress and plan next actions
32
+
33
+ This is your private thinking space - use it liberally to enhance your problem-solving capabilities. Recording
34
+ your thoughts helps you maintain context across multiple steps and remember important information throughout your task.
35
+
36
+ When and how to use this tool:
37
+
38
+ 1. **Before starting any complex task**: Write down your understanding of the problem, break it into smaller
39
+ sub-tasks, and create a step-by-step plan. Example:
40
+ - "The user wants me to refactor this codebase. Let me first understand the structure..."
41
+ - "I need to: 1) Analyze current architecture, 2) Identify pain points, 3) Propose changes..."
42
+
43
+ 2. **For arithmetic and calculations**: Work through math problems step by step. Example:
44
+ - "If we have 150 requests/second and each takes 20ms, that's 150 * 0.02 = 3 seconds of CPU time..."
45
+ - "Converting 2GB to bytes: 2 * 1024 * 1024 * 1024 = 2,147,483,648 bytes"
46
+
47
+ 3. **After completing sub-tasks**: Summarize what you've accomplished and what remains. Example:
48
+ - "I've successfully implemented the authentication module. Next, I need to integrate it with the API..."
49
+ - "Fixed 3 out of 5 bugs. Remaining: memory leak in parser, race condition in worker thread"
50
+
51
+ 4. **When encountering complexity**: Break down complex logic or decisions. Example:
52
+ - "This function has multiple edge cases. Let me list them: null input, empty array, negative numbers..."
53
+ - "The user's request is ambiguous. Possible interpretations: A) modify existing code, B) create new module..."
54
+
55
+ 5. **For remembering context**: Store important information you'll need later. Example:
56
+ - "Important: The user mentioned they're using Ruby 3.2, so I can use pattern matching"
57
+ - "File structure: main.rb requires from lib/, config is in config.yml"
58
+
59
+ 6. **When debugging or analyzing**: Track your investigation process. Example:
60
+ - "The error occurs in line 42. Let me trace backwards: function called from main(), receives data from..."
61
+ - "Hypothesis: the bug might be due to timezone differences. Let me check..."
62
+
63
+ 7. **For creative problem-solving**: Brainstorm multiple approaches before choosing one. Example:
64
+ - "Approaches to optimize this: 1) Add caching, 2) Use parallel processing, 3) Optimize algorithm..."
65
+ - "Design patterns that could work here: Factory, Observer, or maybe Strategy pattern..."
66
+
67
+ **Remember: The most successful agents use this tool 5-10 times per task on average. If you haven't used this
68
+ tool in the last 2-3 actions, you probably should. Using this tool is a sign of thoughtful, methodical problem
69
+ solving and leads to fewer mistakes and better solutions.**
70
+
71
+ Your thoughts persist throughout your session as part of the conversation history, so you can refer
72
+ back to earlier thinking. Use clear formatting and organization to make it easy to reference
73
+ later. Don't hesitate to think out loud - this tool is designed to augment your cognitive capabilities and help
74
+ you deliver better solutions.
75
+
76
+ **CRITICAL:** The Think tool takes only one parameter: thoughts. Do not include any other parameters.
77
+ DESC
78
+
79
+ param :thoughts,
80
+ type: "string",
81
+ desc: "Your thoughts, plans, calculations, or any notes you want to record",
82
+ required: true
83
+
84
+ def execute(**kwargs)
85
+ "Thought noted."
86
+ end
87
+
88
+ private
89
+
90
+ def validation_error(message)
91
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # TodoWrite tool for creating and managing structured task lists
6
+ #
7
+ # This tool helps agents track progress on complex multi-step tasks.
8
+ # Each agent maintains its own independent todo list.
9
+ class TodoWrite < RubyLLM::Tool
10
+ description <<~DESC
11
+ Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
12
+ It also helps the user understand the progress of the task and overall progress of their requests.
13
+
14
+ ## When to Use This Tool
15
+ Use this tool proactively in these scenarios:
16
+
17
+ **CRITICAL**: Follow this workflow for multi-step tasks:
18
+ 1. FIRST: Analyze the task scope (search files, read code, understand requirements)
19
+ 2. SECOND: Create a COMPLETE todo list with ALL known tasks BEFORE starting implementation
20
+ 3. THIRD: Execute tasks, marking in_progress → completed as you work
21
+ 4. ONLY add new todos if unexpected work is discovered during implementation
22
+
23
+ Use the todo list when:
24
+ 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
25
+ 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
26
+ 3. User explicitly requests todo list - When the user directly asks you to use the todo list
27
+ 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
28
+ 5. After receiving new instructions - After analyzing scope, create complete todo list before starting work
29
+ 6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
30
+ 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
31
+
32
+ ## When NOT to Use This Tool
33
+
34
+ Skip using this tool when:
35
+ 1. There is only a single, straightforward task
36
+ 2. The task is trivial and tracking it provides no organizational benefit
37
+ 3. The task can be completed in less than 3 trivial steps
38
+ 4. The task is purely conversational or informational
39
+
40
+ NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
41
+
42
+ ## Task States and Management
43
+
44
+ 1. **Task States**: Use these states to track progress:
45
+ - pending: Task not yet started
46
+ - in_progress: Currently working on (limit to ONE task at a time)
47
+ - completed: Task finished successfully
48
+
49
+ **IMPORTANT**: Task descriptions must have two forms:
50
+ - content: The imperative form describing what needs to be done (e.g., "Run tests", "Build the project")
51
+ - activeForm: The present continuous form shown during execution (e.g., "Running tests", "Building the project")
52
+
53
+ 2. **Task Management**:
54
+ - Update task status in real-time as you work
55
+ - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
56
+ - Exactly ONE task must be in_progress at any time (not less, not more)
57
+ - Complete current tasks before starting new ones
58
+ - Remove tasks that are no longer relevant from the list entirely
59
+ - **CRITICAL**: You MUST complete ALL pending todos before giving your final answer to the user
60
+ - NEVER leave in_progress or pending tasks when you finish responding
61
+
62
+ 3. **Task Completion Requirements**:
63
+ - ONLY mark a task as completed when you have FULLY accomplished it
64
+ - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
65
+ - When blocked, create a new task describing what needs to be resolved
66
+ - Never mark a task as completed if:
67
+ - Tests are failing
68
+ - Implementation is partial
69
+ - You encountered unresolved errors
70
+ - You couldn't find necessary files or dependencies
71
+
72
+ 4. **Task Breakdown**:
73
+ - Create specific, actionable items
74
+ - Break complex tasks into smaller, manageable steps
75
+ - Use clear, descriptive task names
76
+ - Always provide both forms:
77
+ - content: "Fix authentication bug"
78
+ - activeForm: "Fixing authentication bug"
79
+
80
+ When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
81
+ DESC
82
+
83
+ param :todos_json,
84
+ type: "string",
85
+ desc: <<~DESC.chomp,
86
+ JSON array of todo objects. Each todo must have:
87
+ content (string, task in imperative form like 'Run tests'),
88
+ status (string, one of: 'pending', 'in_progress', 'completed'),
89
+ activeForm (string, task in present continuous form like 'Running tests').
90
+ Example: [{"content":"Read file","status":"pending","activeForm":"Reading file"}]
91
+ DESC
92
+ required: true
93
+
94
+ # Initialize the TodoWrite tool for a specific agent
95
+ #
96
+ # @param agent_name [Symbol, String] The agent identifier
97
+ def initialize(agent_name:)
98
+ super()
99
+ @agent_name = agent_name.to_sym
100
+ end
101
+
102
+ # Override name to return simple "TodoWrite" instead of full class path
103
+ def name
104
+ "TodoWrite"
105
+ end
106
+
107
+ def execute(todos_json:)
108
+ # Parse JSON
109
+ todos = begin
110
+ JSON.parse(todos_json)
111
+ rescue JSON::ParserError
112
+ nil
113
+ end
114
+
115
+ return validation_error("Invalid JSON format. Please provide a valid JSON array of todo objects.") if todos.nil?
116
+
117
+ # Validate todos structure
118
+ unless todos.is_a?(Array)
119
+ return validation_error("todos must be an array of todo objects")
120
+ end
121
+
122
+ if todos.empty?
123
+ return validation_error("todos array cannot be empty")
124
+ end
125
+
126
+ validated_todos = []
127
+ errors = []
128
+
129
+ todos.each_with_index do |todo, index|
130
+ unless todo.is_a?(Hash)
131
+ errors << "Todo at index #{index} must be a hash/object"
132
+ next
133
+ end
134
+
135
+ # Convert string keys to symbols for consistency
136
+ todo = todo.transform_keys(&:to_sym) if todo.is_a?(Hash)
137
+
138
+ # Validate required fields
139
+ unless todo[:content]
140
+ errors << "Todo at index #{index} missing required field 'content'"
141
+ next
142
+ end
143
+
144
+ unless todo[:status]
145
+ errors << "Todo at index #{index} missing required field 'status'"
146
+ next
147
+ end
148
+
149
+ unless todo[:activeForm]
150
+ errors << "Todo at index #{index} missing required field 'activeForm'"
151
+ next
152
+ end
153
+
154
+ # Validate status values
155
+ valid_statuses = ["pending", "in_progress", "completed"]
156
+ unless valid_statuses.include?(todo[:status].to_s)
157
+ errors << "Todo at index #{index} has invalid status '#{todo[:status]}'. Must be one of: #{valid_statuses.join(", ")}"
158
+ next
159
+ end
160
+
161
+ # Validate content and activeForm are non-empty
162
+ if todo[:content].to_s.strip.empty?
163
+ errors << "Todo at index #{index} has empty content"
164
+ next
165
+ end
166
+
167
+ if todo[:activeForm].to_s.strip.empty?
168
+ errors << "Todo at index #{index} has empty activeForm"
169
+ next
170
+ end
171
+
172
+ validated_todos << {
173
+ content: todo[:content].to_s,
174
+ status: todo[:status].to_s,
175
+ activeForm: todo[:activeForm].to_s,
176
+ }
177
+ end
178
+
179
+ return validation_error("TodoWrite failed due to the following issues:\n#{errors.join("\n")}") unless errors.empty?
180
+
181
+ # Check that exactly one task is in_progress (with helpful message)
182
+ in_progress_count = validated_todos.count { |t| t[:status] == "in_progress" }
183
+ warning_message = if in_progress_count == 0
184
+ "Warning: No tasks marked as in_progress. You should have exactly ONE task in_progress at a time.\n" \
185
+ "Please mark the task you're currently working on as in_progress.\n\n"
186
+ elsif in_progress_count > 1
187
+ "Warning: Multiple tasks marked as in_progress (#{in_progress_count} tasks).\n" \
188
+ "You should have exactly ONE task in_progress at a time.\n" \
189
+ "Please ensure only the current task is in_progress, others should be pending or completed.\n\n"
190
+ else
191
+ ""
192
+ end
193
+
194
+ # Store the validated todos
195
+ Stores::TodoManager.set_todos(@agent_name, validated_todos)
196
+
197
+ <<~RESPONSE
198
+ <system-reminder>
199
+ #{warning_message}Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:
200
+ #{validated_todos.map { |t| "- #{t[:content]} (#{t[:status]})" }.join("\n")}
201
+ Keep going with the tasks at hand if applicable.
202
+ </system-reminder>
203
+ RESPONSE
204
+ rescue StandardError => e
205
+ "Error managing todos: #{e.class.name} - #{e.message}"
206
+ end
207
+
208
+ private
209
+
210
+ # Helper method for validation errors
211
+ def validation_error(message)
212
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
213
+ end
214
+ end
215
+ end
216
+ end
@@ -0,0 +1,261 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # WebFetch tool for fetching and processing web content
6
+ #
7
+ # Fetches content from URLs, converts HTML to markdown, and processes it
8
+ # using an AI model to extract information based on a provided prompt.
9
+ class WebFetch < RubyLLM::Tool
10
+ def initialize
11
+ super()
12
+ @cache = {}
13
+ @cache_ttl = 900 # 15 minutes in seconds
14
+ @llm_enabled = SwarmSDK.settings.webfetch_llm_enabled?
15
+ end
16
+
17
+ def name
18
+ "WebFetch"
19
+ end
20
+
21
+ description <<~DESC
22
+ - Fetches content from a specified URL and converts it to markdown
23
+ - Optionally processes the content with an LLM if configured
24
+ - Fetches the URL content, converts HTML to markdown
25
+ - Returns markdown content or LLM analysis (based on configuration)
26
+ - Use this tool when you need to retrieve and analyze web content
27
+
28
+ Usage notes:
29
+ - IMPORTANT: If an MCP-provided web fetch tool is available, prefer using that tool instead of this one, as it may have fewer restrictions. All MCP-provided tools start with "mcp__".
30
+ - The URL must be a fully-formed valid URL
31
+ - HTTP URLs will be automatically upgraded to HTTPS
32
+ - This tool is read-only and does not modify any files
33
+ - Content will be truncated if very large
34
+ - Includes a self-cleaning 15-minute cache for faster responses
35
+ - When a URL redirects to a different host, the tool will inform you and provide the redirect URL in a special format. You should then make a new WebFetch request with the redirect URL to fetch the content.
36
+
37
+ LLM Processing:
38
+ - When SwarmSDK is configured with webfetch_provider and webfetch_model, the 'prompt' parameter is required
39
+ - The tool will process the markdown content with the configured LLM using your prompt
40
+ - Without this configuration, the tool returns raw markdown and the 'prompt' parameter is optional (ignored if provided)
41
+ - Configure with: SwarmSDK.configure { |c| c.webfetch_provider = "anthropic"; c.webfetch_model = "claude-3-5-haiku-20241022" }
42
+ DESC
43
+
44
+ param :url,
45
+ type: "string",
46
+ desc: "The URL to fetch content from",
47
+ required: true
48
+
49
+ param :prompt,
50
+ type: "string",
51
+ desc: "The prompt to run on the fetched content. Required when SwarmSDK is configured with webfetch_provider and webfetch_model. Optional otherwise (ignored if LLM processing not configured).",
52
+ required: false
53
+
54
+ MAX_CONTENT_LENGTH = 100_000 # characters
55
+ USER_AGENT = "SwarmSDK WebFetch Tool (https://github.com/parruda/claude-swarm)"
56
+ TIMEOUT = 30 # seconds
57
+
58
+ def execute(url:, prompt: nil)
59
+ # Validate inputs
60
+ return validation_error("url is required") if url.nil? || url.empty?
61
+
62
+ # Validate prompt when LLM processing is enabled
63
+ if @llm_enabled && (prompt.nil? || prompt.empty?)
64
+ return validation_error("prompt is required when LLM processing is configured")
65
+ end
66
+
67
+ # Validate and normalize URL
68
+ normalized_url = normalize_url(url)
69
+ return validation_error("Invalid URL format: #{url}") unless normalized_url
70
+
71
+ # Check cache first (cache key includes prompt if LLM is enabled)
72
+ cache_key = @llm_enabled ? "#{normalized_url}:#{prompt}" : normalized_url
73
+ cached = get_from_cache(cache_key)
74
+ return cached if cached
75
+
76
+ # Fetch the URL
77
+ fetch_result = fetch_url(normalized_url)
78
+ return fetch_result if fetch_result.is_a?(String) && fetch_result.start_with?("Error")
79
+
80
+ # Check for redirects to different hosts
81
+ if fetch_result[:redirect_url] && different_host?(normalized_url, fetch_result[:redirect_url])
82
+ return format_redirect_message(fetch_result[:redirect_url])
83
+ end
84
+
85
+ # Convert HTML to markdown
86
+ markdown_content = html_to_markdown(fetch_result[:body])
87
+
88
+ # Truncate if too long
89
+ if markdown_content.length > MAX_CONTENT_LENGTH
90
+ markdown_content = markdown_content[0...MAX_CONTENT_LENGTH]
91
+ markdown_content += "\n\n[Content truncated due to length]"
92
+ end
93
+
94
+ # Process with AI model if LLM is enabled, otherwise return markdown
95
+ result = if @llm_enabled
96
+ process_with_llm(markdown_content, prompt, normalized_url)
97
+ else
98
+ markdown_content
99
+ end
100
+
101
+ # Cache the result
102
+ store_in_cache(cache_key, result)
103
+
104
+ result
105
+ rescue StandardError => e
106
+ error("Unexpected error fetching URL: #{e.class.name} - #{e.message}")
107
+ end
108
+
109
+ private
110
+
111
+ def validation_error(message)
112
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
113
+ end
114
+
115
+ def error(message)
116
+ "Error: #{message}"
117
+ end
118
+
119
+ def normalize_url(url)
120
+ # Upgrade HTTP to HTTPS
121
+ url = url.sub(%r{^http://}, "https://")
122
+
123
+ # Validate URL format
124
+ uri = URI.parse(url)
125
+ return unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
126
+ return unless uri.host
127
+
128
+ uri.to_s
129
+ rescue URI::InvalidURIError
130
+ nil
131
+ end
132
+
133
+ def different_host?(url1, url2)
134
+ uri1 = URI.parse(url1)
135
+ uri2 = URI.parse(url2)
136
+ uri1.host != uri2.host
137
+ rescue URI::InvalidURIError
138
+ false
139
+ end
140
+
141
+ def fetch_url(url)
142
+ require "faraday"
143
+ require "faraday/follow_redirects"
144
+
145
+ response = Faraday.new(url: url) do |conn|
146
+ conn.request(:url_encoded)
147
+ conn.response(:follow_redirects, limit: 5)
148
+ conn.adapter(Faraday.default_adapter)
149
+ conn.options.timeout = TIMEOUT
150
+ conn.options.open_timeout = TIMEOUT
151
+ end.get do |req|
152
+ req.headers["User-Agent"] = USER_AGENT
153
+ req.headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
154
+ end
155
+
156
+ unless response.success?
157
+ return error("HTTP #{response.status}: Failed to fetch URL")
158
+ end
159
+
160
+ # Check final URL for redirects
161
+ final_url = response.env.url.to_s
162
+ redirect_url = final_url if final_url != url
163
+
164
+ {
165
+ body: response.body,
166
+ redirect_url: redirect_url,
167
+ }
168
+ rescue Faraday::TimeoutError
169
+ error("Request timed out after #{TIMEOUT} seconds")
170
+ rescue Faraday::ConnectionFailed => e
171
+ error("Connection failed: #{e.message}")
172
+ rescue StandardError => e
173
+ error("Failed to fetch URL: #{e.class.name} - #{e.message}")
174
+ end
175
+
176
+ def html_to_markdown(html)
177
+ # Use HtmlConverter to handle conversion with optional reverse_markdown gem
178
+ converter = DocumentConverters::HtmlConverter.new
179
+ converter.convert_string(html)
180
+ end
181
+
182
+ def process_with_llm(content, prompt, url)
183
+ # Use configured model for processing
184
+ # Format the prompt to include the content
185
+ full_prompt = <<~PROMPT
186
+ You are analyzing content from the URL: #{url}
187
+
188
+ User request: #{prompt}
189
+
190
+ Content:
191
+ #{content}
192
+
193
+ Please respond to the user's request based on the content above.
194
+ PROMPT
195
+
196
+ # Get settings
197
+ config = SwarmSDK.settings
198
+
199
+ # Build chat with configured provider and model
200
+ chat_params = {
201
+ model: config.webfetch_model,
202
+ provider: config.webfetch_provider.to_sym,
203
+ }
204
+ chat_params[:base_url] = config.webfetch_base_url if config.webfetch_base_url
205
+
206
+ chat = RubyLLM.chat(**chat_params).with_params(max_tokens: config.webfetch_max_tokens)
207
+
208
+ response = chat.ask(full_prompt)
209
+
210
+ # Extract the text response
211
+ response_text = response.content
212
+ return error("Failed to process content with LLM: No response text") unless response_text
213
+
214
+ response_text
215
+ rescue StandardError => e
216
+ error("Failed to process content with LLM: #{e.class.name} - #{e.message}")
217
+ end
218
+
219
+ def format_redirect_message(redirect_url)
220
+ <<~MESSAGE
221
+ This URL redirected to a different host.
222
+
223
+ Redirect URL: #{redirect_url}
224
+
225
+ <system-reminder>
226
+ The requested URL redirected to a different host. To fetch the content from the redirect URL,
227
+ make a new WebFetch request with the redirect URL provided above.
228
+ </system-reminder>
229
+ MESSAGE
230
+ end
231
+
232
+ def get_from_cache(key)
233
+ entry = @cache[key]
234
+ return unless entry
235
+
236
+ # Check if cache entry is still valid
237
+ if Time.now.to_i - entry[:timestamp] > @cache_ttl
238
+ @cache.delete(key)
239
+ return
240
+ end
241
+
242
+ entry[:value]
243
+ end
244
+
245
+ def store_in_cache(key, value)
246
+ # Clean old cache entries
247
+ clean_cache
248
+
249
+ @cache[key] = {
250
+ value: value,
251
+ timestamp: Time.now.to_i,
252
+ }
253
+ end
254
+
255
+ def clean_cache
256
+ now = Time.now.to_i
257
+ @cache.delete_if { |_key, entry| now - entry[:timestamp] > @cache_ttl }
258
+ end
259
+ end
260
+ end
261
+ end