swarm_sdk 2.1.2 → 2.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a77daa5d8abcd0cae1b1edad441d93421d25ecbf15a0713697d9eabb6c4dc00e
4
- data.tar.gz: 97c037f892d03992b12292a31bc3ab9359ccacead39d37466af84ef5660e4933
3
+ metadata.gz: e5d0702a4e7e567c81f3a9974fdc197f4e3247fe9d6ce38d5a80b373423fff40
4
+ data.tar.gz: f6907d5c9baa55ab32e19cbe440643cb169aac1d1c9ef4501679357f63ec364c
5
5
  SHA512:
6
- metadata.gz: e7620b869014c9f1b889795310ec1d648cbcc55fc53a3cbb7023c37b403f83c57e5f3645b4f8ffdd38d60fa4877fa24d9ab3f0ccf0de3b2be6a45420516dbf50
7
- data.tar.gz: bc081f3a444bb410a22dc2e5971f258dd43dadaa69d53558e799d036149412bfecf8d95396aa0f6e1f0c39cf6716b986722669265b32cc2b550f7daa0a6e9c2c
6
+ metadata.gz: 460b59be54dc659ba6eb6cc37e9946ac9edd14d946bde9672f7a02c119cd89792b7253f2de29238fc9493cc4cdffa19283e83c4f86417840050fd1cf1dd33766
7
+ data.tar.gz: 5a377235df41937afacc67ac76ba8420960931bd1f72f64d77db2c86741a55bf0ea05918f78922f7bf4645f7beb0a8b2420413395a5fec267f098e10106d28e4
@@ -358,7 +358,7 @@ module SwarmSDK
358
358
 
359
359
  def render_non_coding_base_prompt
360
360
  # Simplified base prompt for non-coding agents
361
- # Includes environment info, TODO, and Scratchpad tool information
361
+ # Includes environment info only
362
362
  # Does not steer towards coding tasks
363
363
  cwd = @directory || Dir.pwd
364
364
  platform = RUBY_PLATFORM
@@ -383,25 +383,6 @@ module SwarmSDK
383
383
  Platform: #{platform}
384
384
  OS Version: #{os_version}
385
385
  </env>
386
-
387
- # Task Management
388
-
389
- You have access to the TodoWrite tool to help you manage and plan tasks. Use this tool to track your progress and give visibility into your work.
390
-
391
- When working on multi-step tasks:
392
- 1. Create a todo list with all known tasks before starting work
393
- 2. Mark each task as in_progress when you start it
394
- 3. Mark each task as completed IMMEDIATELY after finishing it
395
- 4. Complete ALL pending todos before finishing your response
396
-
397
- # Scratchpad Storage
398
-
399
- You have access to Scratchpad tools for storing and retrieving information:
400
- - **ScratchpadWrite**: Store detailed outputs, analysis, or results that are too long for direct responses
401
- - **ScratchpadRead**: Retrieve previously stored content
402
- - **ScratchpadList**: List available scratchpad entries
403
-
404
- Use the scratchpad to share information that would otherwise clutter your responses.
405
386
  PROMPT
406
387
  end
407
388
 
@@ -4,17 +4,43 @@ module SwarmSDK
4
4
  class Configuration
5
5
  ENV_VAR_WITH_DEFAULT_PATTERN = /\$\{([^:}]+)(:=([^}]*))?\}/
6
6
 
7
- attr_reader :config_path, :swarm_name, :lead_agent, :agents, :all_agents_config, :swarm_hooks, :all_agents_hooks, :scratchpad_enabled
7
+ attr_reader :swarm_name, :lead_agent, :agents, :all_agents_config, :swarm_hooks, :all_agents_hooks, :scratchpad_enabled
8
8
 
9
9
  class << self
10
- def load(path)
11
- new(path).tap(&:load_and_validate)
10
+ # Load configuration from YAML file
11
+ #
12
+ # Convenience method that reads the file and uses the file's directory
13
+ # as the base directory for resolving agent file paths.
14
+ #
15
+ # @param path [String, Pathname] Path to YAML configuration file
16
+ # @return [Configuration] Validated configuration instance
17
+ # @raise [ConfigurationError] If file not found or invalid
18
+ def load_file(path)
19
+ path = Pathname.new(path).expand_path
20
+
21
+ unless path.exist?
22
+ raise ConfigurationError, "Configuration file not found: #{path}"
23
+ end
24
+
25
+ yaml_content = File.read(path)
26
+ base_dir = path.dirname
27
+
28
+ new(yaml_content, base_dir: base_dir).tap(&:load_and_validate)
29
+ rescue Errno::ENOENT
30
+ raise ConfigurationError, "Configuration file not found: #{path}"
12
31
  end
13
32
  end
14
33
 
15
- def initialize(config_path)
16
- @config_path = Pathname.new(config_path).expand_path
17
- @config_dir = @config_path.dirname
34
+ # Initialize configuration from YAML string
35
+ #
36
+ # @param yaml_content [String] YAML configuration content
37
+ # @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
38
+ def initialize(yaml_content, base_dir: Dir.pwd)
39
+ raise ArgumentError, "yaml_content cannot be nil" if yaml_content.nil?
40
+ raise ArgumentError, "base_dir cannot be nil" if base_dir.nil?
41
+
42
+ @yaml_content = yaml_content
43
+ @base_dir = Pathname.new(base_dir).expand_path
18
44
  @agents = {}
19
45
  @all_agents_config = {} # Settings applied to all agents
20
46
  @swarm_hooks = {} # Swarm-level hooks (swarm_start, swarm_stop)
@@ -22,7 +48,7 @@ module SwarmSDK
22
48
  end
23
49
 
24
50
  def load_and_validate
25
- @config = YAML.load_file(@config_path, aliases: true)
51
+ @config = YAML.safe_load(@yaml_content, permitted_classes: [Symbol], aliases: true)
26
52
 
27
53
  unless @config.is_a?(Hash)
28
54
  raise ConfigurationError, "Invalid YAML syntax: configuration must be a Hash"
@@ -37,8 +63,6 @@ module SwarmSDK
37
63
  load_agents
38
64
  detect_circular_dependencies
39
65
  self
40
- rescue Errno::ENOENT
41
- raise ConfigurationError, "Configuration file not found: #{@config_path}"
42
66
  rescue Psych::SyntaxError => e
43
67
  raise ConfigurationError, "Invalid YAML syntax: #{e.message}"
44
68
  end
@@ -260,7 +284,7 @@ module SwarmSDK
260
284
  def resolve_agent_file_path(file_path)
261
285
  return file_path if Pathname.new(file_path).absolute?
262
286
 
263
- @config_dir.join(file_path).to_s
287
+ @base_dir.join(file_path).to_s
264
288
  end
265
289
 
266
290
  def detect_circular_dependencies
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module MCP
5
+ class << self
6
+ # Lazy load ruby_llm-mcp only when MCP servers are used
7
+ def lazy_load
8
+ return if @loaded
9
+
10
+ require "ruby_llm/mcp"
11
+
12
+ @loaded = true
13
+ end
14
+ end
15
+ end
16
+ end
@@ -69,139 +69,15 @@ When making changes to files, first understand the file's conventions. Mimic exi
69
69
  - When you edit something, first look at the surrounding context (especially imports/requires) to understand the choice of frameworks and libraries. Then consider how to make the given change in a way that is most consistent with existing patterns.
70
70
  - Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to repositories.
71
71
 
72
- # Task Management
73
-
74
- You have access to the TodoWrite tool to help you manage and plan tasks. Use this tool VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
75
- This tool is also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
76
-
77
- **CRITICAL WORKFLOW**: When starting a multi-step task:
78
- 1. **FIRST**: Analyze what needs to be done (search, read files, understand scope)
79
- 2. **SECOND**: Create a COMPLETE todo list with ALL known tasks before starting work
80
- 3. **THIRD**: Begin executing tasks, marking them in_progress → completed as you work
81
- 4. **ONLY add new todos** if you discover unexpected work during implementation
82
-
83
- **CRITICAL RULES FOR TODO COMPLETION**:
84
- - Mark EACH task as completed IMMEDIATELY after finishing it (do not batch updates)
85
- - You MUST complete ALL pending todos before giving your final answer to the user
86
- - If a task becomes irrelevant, remove it from the list or mark it completed with a note
87
- - NEVER leave in_progress or pending tasks when you finish responding to the user
88
- - Before giving your final response, verify all todos are marked completed
89
-
90
- Examples:
91
-
92
- <example>
93
- user: Run the build and fix any type errors
94
- assistant: I'll run the build first to identify all type errors, then create a complete todo list.
95
-
96
- [Runs build and finds 3 type errors in 3 different files]
97
-
98
- Now I'll create a complete todo list with all the work:
99
-
100
- [Uses TodoWrite to create full list:]
101
- 1. Fix type error in auth.ts:45 (in_progress)
102
- 2. Fix type error in user.ts:23 (pending)
103
- 3. Fix type error in api.ts:67 (pending)
104
- 4. Run build again to verify all fixes (pending)
105
-
106
- Starting with the first error in auth.ts...
107
- [Fixes auth.ts error]
108
-
109
- [Updates TodoWrite - marks task 1 completed, task 2 in_progress]
110
-
111
- Now fixing user.ts...
112
- [Fixes user.ts error]
113
-
114
- [Updates TodoWrite - marks task 2 completed, task 3 in_progress]
115
-
116
- Now fixing api.ts...
117
- [Fixes api.ts error]
118
-
119
- [Updates TodoWrite - marks task 3 completed, task 4 in_progress]
120
-
121
- Running build to verify...
122
- [Runs build - all errors fixed!]
123
-
124
- [Updates TodoWrite - marks task 4 completed]
125
-
126
- All type errors have been fixed and the build passes successfully.
127
- </example>
128
- In this example, the assistant: (1) analyzed scope first, (2) created COMPLETE todo list, (3) marked EACH task completed immediately after finishing it, (4) completed ALL tasks before giving final response.
129
-
130
- <example>
131
- user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
132
-
133
- A: I'll help you implement a usage metrics tracking and export feature. Let me first research the codebase to understand the scope, then create a complete todo list.
134
-
135
- [Searches for existing metrics/telemetry code, examines architecture, identifies integration points]
136
-
137
- Based on my analysis, here's the complete implementation plan:
138
-
139
- [Uses TodoWrite to create full list:]
140
- 1. Create MetricsCollector class in src/metrics/ (in_progress)
141
- 2. Add database schema for metrics storage (pending)
142
- 3. Implement CSV export format (pending)
143
- 4. Implement JSON export format (pending)
144
- 5. Create MetricsService API endpoints (pending)
145
- 6. Add frontend UI for export (pending)
146
- 7. Write unit tests for collectors and exporters (pending)
147
- 8. Update documentation (pending)
148
-
149
- Now starting with the MetricsCollector class...
150
- [Implements MetricsCollector]
151
-
152
- [Updates TodoWrite - marks task 1 completed, task 2 in_progress]
153
-
154
- Adding database schema...
155
- [Creates schema]
156
-
157
- [Updates TodoWrite - marks task 2 completed, task 3 in_progress]
158
-
159
- Implementing CSV export...
160
- [Implements CSV exporter]
161
-
162
- [Updates TodoWrite - marks task 3 completed, task 4 in_progress]
163
-
164
- Implementing JSON export...
165
- [Implements JSON exporter]
166
-
167
- [Updates TodoWrite - marks task 4 completed, task 5 in_progress]
168
-
169
- Creating MetricsService API endpoints...
170
- [Implements API endpoints]
171
-
172
- [Updates TodoWrite - marks task 5 completed, task 6 in_progress]
173
-
174
- Adding frontend UI for export...
175
- [Implements UI components]
176
-
177
- [Updates TodoWrite - marks task 6 completed, task 7 in_progress]
178
-
179
- Writing unit tests...
180
- [Writes and runs tests - all pass]
181
-
182
- [Updates TodoWrite - marks task 7 completed, task 8 in_progress]
183
-
184
- Updating documentation...
185
- [Updates docs with usage examples]
186
-
187
- [Updates TodoWrite - marks task 8 completed]
188
-
189
- The metrics tracking and export feature is now complete. Users can collect metrics and export them to CSV or JSON formats through both the API and the frontend UI.
190
- </example>
191
-
192
72
  # Doing tasks
193
73
 
194
74
  The user will primarily request you perform tasks. This includes solving problems, adding new functionality, refactoring, explaining content, and more. For these tasks the following steps are recommended:
195
75
 
196
- - Use the TodoWrite tool to plan the task if required
197
76
  - Use the available search tools to understand the context and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially.
198
77
  - Implement the solution using all tools available to you
199
- - Mark each todo completed IMMEDIATELY after finishing it
200
78
  - Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the project documentation or search to determine the testing approach.
201
79
  - When you have completed a task, if there are linting or validation commands available to you, run them to ensure your work is correct. NEVER assume what these commands are - check the project documentation first.
202
80
  NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.
203
- - Before giving your final response: Ensure ALL todos are marked completed. NEVER leave pending or in_progress tasks.
204
- - IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
205
81
 
206
82
  # Tool usage policy
207
83
 
@@ -211,8 +87,6 @@ NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTAN
211
87
  - If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to delegate a task to multiple agents in parallel, send a single message with multiple DelegateTask tool calls.
212
88
  - Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit/MultiEdit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
213
89
 
214
- IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
215
-
216
90
  You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.
217
91
 
218
92
 
@@ -4,25 +4,10 @@ module SwarmSDK
4
4
  # Swarm orchestrates multiple AI agents with shared rate limiting and coordination.
5
5
  #
6
6
  # This is the main user-facing API for SwarmSDK. Users create swarms using:
7
- # - Direct API: Create Agent::Definition objects and add to swarm
8
- # - Ruby DSL: Use Swarm::Builder for fluent configuration
9
- # - YAML: Load from configuration files
10
- #
11
- # ## Direct API
12
- #
13
- # swarm = Swarm.new(name: "Development Team")
14
- #
15
- # backend_agent = Agent::Definition.new(:backend, {
16
- # description: "Backend developer",
17
- # model: "gpt-5",
18
- # system_prompt: "You build APIs and databases...",
19
- # tools: [:Read, :Edit, :Bash],
20
- # delegates_to: [:database]
21
- # })
22
- # swarm.add_agent(backend_agent)
23
- #
24
- # swarm.lead = :backend
25
- # result = swarm.execute("Build authentication")
7
+ # - Ruby DSL: SwarmSDK.build { ... } (Recommended)
8
+ # - YAML String: SwarmSDK.load(yaml, base_dir:)
9
+ # - YAML File: SwarmSDK.load_file(path)
10
+ # - Direct API: Swarm.new + add_agent (Advanced)
26
11
  #
27
12
  # ## Ruby DSL (Recommended)
28
13
  #
@@ -39,14 +24,36 @@ module SwarmSDK
39
24
  # end
40
25
  # result = swarm.execute("Build authentication")
41
26
  #
42
- # ## YAML API
27
+ # ## YAML String API
43
28
  #
44
- # swarm = Swarm.load("swarm.yml")
29
+ # yaml = File.read("swarm.yml")
30
+ # swarm = SwarmSDK.load(yaml, base_dir: "/path/to/project")
31
+ # result = swarm.execute("Build authentication")
32
+ #
33
+ # ## YAML File API (Convenience)
34
+ #
35
+ # swarm = SwarmSDK.load_file("swarm.yml")
36
+ # result = swarm.execute("Build authentication")
37
+ #
38
+ # ## Direct API (Advanced)
39
+ #
40
+ # swarm = Swarm.new(name: "Development Team")
41
+ #
42
+ # backend_agent = Agent::Definition.new(:backend, {
43
+ # description: "Backend developer",
44
+ # model: "gpt-5",
45
+ # system_prompt: "You build APIs and databases...",
46
+ # tools: [:Read, :Edit, :Bash],
47
+ # delegates_to: [:database]
48
+ # })
49
+ # swarm.add_agent(backend_agent)
50
+ #
51
+ # swarm.lead = :backend
45
52
  # result = swarm.execute("Build authentication")
46
53
  #
47
54
  # ## Architecture
48
55
  #
49
- # All three APIs converge on Agent::Definition for validation.
56
+ # All APIs converge on Agent::Definition for validation.
50
57
  # Swarm delegates to specialized concerns:
51
58
  # - Agent::Definition: Validates configuration, builds system prompts
52
59
  # - AgentInitializer: Complex 5-pass agent setup
@@ -96,39 +103,14 @@ module SwarmSDK
96
103
  def apply_mcp_logging_configuration
97
104
  return if @mcp_logging_configured
98
105
 
106
+ SwarmSDK::MCP.lazy_load
107
+
99
108
  RubyLLM::MCP.configure do |config|
100
109
  config.log_level = @mcp_log_level
101
110
  end
102
111
 
103
112
  @mcp_logging_configured = true
104
113
  end
105
-
106
- # Load swarm from YAML configuration file
107
- #
108
- # @param config_path [String] Path to YAML configuration file
109
- # @return [Swarm] Configured swarm instance
110
- def load(config_path)
111
- config = Configuration.load(config_path)
112
- swarm = config.to_swarm
113
-
114
- # Apply hooks if any are configured (YAML-only feature)
115
- if hooks_configured?(config)
116
- Hooks::Adapter.apply_hooks(swarm, config)
117
- end
118
-
119
- # Store config reference for agent hooks (applied during initialize_agents)
120
- swarm.config_for_hooks = config
121
-
122
- swarm
123
- end
124
-
125
- private
126
-
127
- def hooks_configured?(config)
128
- config.swarm_hooks.any? ||
129
- config.all_agents_hooks.any? ||
130
- config.agents.any? { |_, agent_def| agent_def.hooks&.any? }
131
- end
132
114
  end
133
115
 
134
116
  # Initialize a new Swarm
@@ -433,7 +415,7 @@ module SwarmSDK
433
415
  # @return [Array<Hash>] Array of warning hashes from all agent definitions
434
416
  #
435
417
  # @example
436
- # swarm = Swarm.load("config.yml")
418
+ # swarm = SwarmSDK.load_file("config.yml")
437
419
  # warnings = swarm.validate
438
420
  # warnings.each do |warning|
439
421
  # puts "⚠️ #{warning[:agent]}: #{warning[:model]} not found"
@@ -12,8 +12,29 @@ module SwarmSDK
12
12
 
13
13
  description <<~DESC
14
14
  List all entries in scratchpad with their metadata.
15
- Shows path, title, size, and last updated time for each entry.
16
- Use this to discover what's stored in the scratchpad.
15
+
16
+ ## When to Use ScratchpadList
17
+
18
+ Use ScratchpadList to:
19
+ - Discover what content is available in the scratchpad
20
+ - Check what other agents have stored
21
+ - Find relevant entries before reading them
22
+ - Review all stored outputs and analysis
23
+ - Check entry sizes and last update times
24
+
25
+ ## Best Practices
26
+
27
+ - Use this before ScratchpadRead if you don't know what's stored
28
+ - Filter by prefix to narrow down results (e.g., 'notes/' lists all notes)
29
+ - Shows path, title, size, and last updated time for each entry
30
+ - Any agent can see all scratchpad entries
31
+ - Helps coordinate multi-agent workflows
32
+
33
+ ## Examples
34
+
35
+ - List all entries: (no prefix parameter)
36
+ - List notes only: prefix='notes/'
37
+ - List analysis results: prefix='analysis/'
17
38
  DESC
18
39
 
19
40
  param :prefix,
@@ -12,8 +12,29 @@ module SwarmSDK
12
12
 
13
13
  description <<~DESC
14
14
  Read content from scratchpad.
15
- Use this to retrieve temporary notes, results, or messages stored by any agent.
16
- Any agent can read any scratchpad content.
15
+
16
+ ## When to Use ScratchpadRead
17
+
18
+ Use ScratchpadRead to:
19
+ - Retrieve previously stored content and outputs
20
+ - Access detailed analysis or results from earlier steps
21
+ - Read messages or notes left by other agents
22
+ - Access cached computed data
23
+ - Retrieve content that was too long for direct responses
24
+
25
+ ## Best Practices
26
+
27
+ - Any agent can read any scratchpad content
28
+ - Content is returned with line numbers for easy reference
29
+ - Use ScratchpadList first if you don't know what's stored
30
+ - Scratchpad data is temporary and lost when swarm ends
31
+ - For persistent data, use MemoryRead instead
32
+
33
+ ## Examples
34
+
35
+ - Read status: file_path='status'
36
+ - Read analysis: file_path='api_analysis'
37
+ - Read agent notes: file_path='notes/backend'
17
38
  DESC
18
39
 
19
40
  param :file_path,
@@ -13,12 +13,29 @@ module SwarmSDK
13
13
 
14
14
  description <<~DESC
15
15
  Store content in scratchpad for temporary cross-agent communication.
16
- Use this for quick notes, intermediate results, or coordination messages.
17
- Any agent can read this content. Data is lost when the swarm ends.
18
16
 
19
- For persistent storage that survives across sessions, use MemoryWrite instead.
17
+ ## When to Use Scratchpad
20
18
 
21
- Choose a simple, descriptive path. Examples: 'status', 'result', 'notes/agent_x'
19
+ Use ScratchpadWrite to:
20
+ - Store detailed outputs, analysis, or results that are too long for direct responses
21
+ - Share information that would otherwise clutter your responses
22
+ - Store intermediate results during multi-step tasks
23
+ - Leave coordination messages for other agents
24
+ - Cache computed data for quick retrieval
25
+
26
+ ## Best Practices
27
+
28
+ - Choose simple, descriptive paths: 'status', 'result', 'notes/agent_x'
29
+ - Use hierarchical paths for organization: 'analysis/step1', 'analysis/step2'
30
+ - Keep entries focused - one piece of information per entry
31
+ - Any agent can read scratchpad content
32
+ - Data is lost when the swarm ends (use MemoryWrite for persistent storage)
33
+ - Maximum 1MB per entry
34
+
35
+ ## Examples
36
+
37
+ Good paths: 'status', 'api_analysis', 'test_results', 'notes/backend'
38
+ Bad paths: 'scratch/temp/file123.txt', 'output.log'
22
39
  DESC
23
40
 
24
41
  param :file_path,
@@ -14,11 +14,11 @@ module SwarmSDK
14
14
  # - Search capabilities: Glob patterns and grep-style content search
15
15
  # - Thread-safe: Mutex-protected operations
16
16
  class Storage
17
- # Maximum size per entry (1MB)
18
- MAX_ENTRY_SIZE = 1_000_000
17
+ # Maximum size per entry (3MB)
18
+ MAX_ENTRY_SIZE = 3_000_000
19
19
 
20
- # Maximum total storage size (100MB)
21
- MAX_TOTAL_SIZE = 100_000_000
20
+ # Maximum total storage size (100GB)
21
+ MAX_TOTAL_SIZE = 100_000_000_000
22
22
 
23
23
  # Represents a single storage entry with metadata
24
24
  Entry = Struct.new(:content, :title, :updated_at, :size, keyword_init: true)
@@ -82,7 +82,10 @@ module SwarmSDK
82
82
  required: true
83
83
 
84
84
  def execute(**kwargs)
85
- "Thought noted."
85
+ <<~RESP
86
+ Thought noted.
87
+ RESP
88
+ # <system-reminder>The user cannot see your thoughts. You MUST NOT stop without giving the user a response.</system-reminder>
86
89
  end
87
90
 
88
91
  private
@@ -8,17 +8,17 @@ module SwarmSDK
8
8
  # Each agent maintains its own independent todo list.
9
9
  class TodoWrite < RubyLLM::Tool
10
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.
11
+ Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
12
12
  It also helps the user understand the progress of the task and overall progress of their requests.
13
13
 
14
14
  ## When to Use This Tool
15
15
  Use this tool proactively in these scenarios:
16
16
 
17
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
18
+ 1. FIRST: Analyze the task scope (gather information, understand requirements)
19
+ 2. SECOND: Create a COMPLETE todo list with ALL known tasks BEFORE starting work
20
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
21
+ 4. ONLY add new todos if unexpected work is discovered during execution
22
22
 
23
23
  Use the todo list when:
24
24
  1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
@@ -27,7 +27,7 @@ module SwarmSDK
27
27
  4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
28
28
  5. After receiving new instructions - After analyzing scope, create complete todo list before starting work
29
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
30
+ 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during execution
31
31
 
32
32
  ## When NOT to Use This Tool
33
33
 
@@ -73,9 +73,21 @@ module SwarmSDK
73
73
  - Create specific, actionable items
74
74
  - Break complex tasks into smaller, manageable steps
75
75
  - Use clear, descriptive task names
76
- - Always provide both forms:
77
- - content: "Fix authentication bug"
78
- - activeForm: "Fixing authentication bug"
76
+ - Always provide both forms (content and activeForm)
77
+
78
+ ## Examples
79
+
80
+ **Coding Tasks**:
81
+ - content: "Fix authentication bug in login handler"
82
+ - activeForm: "Fixing authentication bug in login handler"
83
+
84
+ **Non-Coding Tasks**:
85
+ - content: "Analyze customer feedback from Q4 survey"
86
+ - activeForm: "Analyzing customer feedback from Q4 survey"
87
+
88
+ **Research Tasks**:
89
+ - content: "Research best practices for API rate limiting"
90
+ - activeForm: "Researching best practices for API rate limiting"
79
91
 
80
92
  When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
81
93
  DESC
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.1.2"
4
+ VERSION = "2.1.3"
5
5
  end
data/lib/swarm_sdk.rb CHANGED
@@ -15,7 +15,6 @@ require "yaml"
15
15
  require "async"
16
16
  require "async/semaphore"
17
17
  require "ruby_llm"
18
- require "ruby_llm/mcp"
19
18
 
20
19
  require_relative "swarm_sdk/version"
21
20
 
@@ -48,6 +47,170 @@ module SwarmSDK
48
47
  Swarm::Builder.build(&block)
49
48
  end
50
49
 
50
+ # Validate YAML configuration without creating a swarm
51
+ #
52
+ # Performs comprehensive validation of YAML configuration including:
53
+ # - YAML syntax
54
+ # - Required fields (version, swarm name, lead, agents)
55
+ # - Agent configurations (description, directory existence)
56
+ # - Circular dependencies
57
+ # - File references (agent_file paths)
58
+ # - Hook configurations
59
+ #
60
+ # @param yaml_content [String] YAML configuration content
61
+ # @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
62
+ # @return [Array<Hash>] Array of error hashes (empty if valid)
63
+ #
64
+ # @example Validate YAML string
65
+ # errors = SwarmSDK.validate(yaml_content)
66
+ # if errors.empty?
67
+ # puts "Configuration is valid!"
68
+ # else
69
+ # errors.each do |error|
70
+ # puts "#{error[:field]}: #{error[:message]}"
71
+ # end
72
+ # end
73
+ #
74
+ # @example Error hash structure
75
+ # {
76
+ # type: :missing_field, # Error type
77
+ # field: "swarm.agents.backend.description", # JSON-style path to field
78
+ # message: "Agent 'backend' missing required 'description' field",
79
+ # agent: "backend" # Optional, present if error is agent-specific
80
+ # }
81
+ def validate(yaml_content, base_dir: Dir.pwd)
82
+ errors = []
83
+
84
+ begin
85
+ config = Configuration.new(yaml_content, base_dir: base_dir)
86
+ config.load_and_validate
87
+ rescue ConfigurationError, CircularDependencyError => e
88
+ errors << parse_configuration_error(e)
89
+ rescue StandardError => e
90
+ errors << {
91
+ type: :unknown_error,
92
+ field: nil,
93
+ message: e.message,
94
+ }
95
+ end
96
+
97
+ errors
98
+ end
99
+
100
+ # Validate YAML configuration file
101
+ #
102
+ # Convenience method that reads the file and validates the content.
103
+ #
104
+ # @param path [String, Pathname] Path to YAML configuration file
105
+ # @return [Array<Hash>] Array of error hashes (empty if valid)
106
+ #
107
+ # @example
108
+ # errors = SwarmSDK.validate_file("config.yml")
109
+ # if errors.empty?
110
+ # puts "Valid configuration!"
111
+ # swarm = SwarmSDK.load_file("config.yml")
112
+ # else
113
+ # errors.each { |e| puts "Error: #{e[:message]}" }
114
+ # end
115
+ def validate_file(path)
116
+ path = Pathname.new(path).expand_path
117
+
118
+ unless path.exist?
119
+ return [{
120
+ type: :file_not_found,
121
+ field: nil,
122
+ message: "Configuration file not found: #{path}",
123
+ }]
124
+ end
125
+
126
+ yaml_content = File.read(path)
127
+ base_dir = path.dirname
128
+
129
+ validate(yaml_content, base_dir: base_dir)
130
+ rescue StandardError => e
131
+ [{
132
+ type: :file_read_error,
133
+ field: nil,
134
+ message: "Error reading file: #{e.message}",
135
+ }]
136
+ end
137
+
138
+ # Load swarm from YAML string
139
+ #
140
+ # This is the primary programmatic API for loading YAML configurations.
141
+ # For file-based loading, use SwarmSDK.load_file for convenience.
142
+ #
143
+ # @param yaml_content [String] YAML configuration content
144
+ # @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
145
+ # @return [Swarm, NodeOrchestrator] Configured swarm or orchestrator instance
146
+ # @raise [ConfigurationError] If YAML is invalid or configuration is incorrect
147
+ #
148
+ # @example Load from YAML string
149
+ # yaml = <<~YAML
150
+ # version: 2
151
+ # swarm:
152
+ # name: "Dev Team"
153
+ # lead: backend
154
+ # agents:
155
+ # backend:
156
+ # description: "Backend developer"
157
+ # model: "gpt-4"
158
+ # agent_file: "agents/backend.md" # Resolved relative to base_dir
159
+ # YAML
160
+ #
161
+ # swarm = SwarmSDK.load(yaml, base_dir: "/path/to/project")
162
+ # result = swarm.execute("Build authentication")
163
+ #
164
+ # @example Load with default base_dir (Dir.pwd)
165
+ # yaml = File.read("config.yml")
166
+ # swarm = SwarmSDK.load(yaml) # base_dir defaults to Dir.pwd
167
+ def load(yaml_content, base_dir: Dir.pwd)
168
+ config = Configuration.new(yaml_content, base_dir: base_dir)
169
+ config.load_and_validate
170
+ swarm = config.to_swarm
171
+
172
+ # Apply hooks if any are configured (YAML-only feature)
173
+ if hooks_configured?(config)
174
+ Hooks::Adapter.apply_hooks(swarm, config)
175
+ end
176
+
177
+ # Store config reference for agent hooks (applied during initialize_agents)
178
+ swarm.config_for_hooks = config
179
+
180
+ swarm
181
+ end
182
+
183
+ # Load swarm from YAML file (convenience method)
184
+ #
185
+ # Reads the YAML file and uses the file's directory as the base directory
186
+ # for resolving agent file paths. This is the recommended method for
187
+ # loading swarms from configuration files.
188
+ #
189
+ # @param path [String, Pathname] Path to YAML configuration file
190
+ # @return [Swarm, NodeOrchestrator] Configured swarm or orchestrator instance
191
+ # @raise [ConfigurationError] If file not found or configuration invalid
192
+ #
193
+ # @example
194
+ # swarm = SwarmSDK.load_file("config.yml")
195
+ # result = swarm.execute("Build authentication")
196
+ #
197
+ # @example With absolute path
198
+ # swarm = SwarmSDK.load_file("/absolute/path/config.yml")
199
+ def load_file(path)
200
+ config = Configuration.load_file(path)
201
+ swarm = config.to_swarm
202
+
203
+ # Apply hooks if any are configured (YAML-only feature)
204
+ if hooks_configured?(config)
205
+ Hooks::Adapter.apply_hooks(swarm, config)
206
+ end
207
+
208
+ # Store config reference for agent hooks (applied during initialize_agents)
209
+ swarm.config_for_hooks = config
210
+
211
+ swarm
212
+ end
213
+
51
214
  # Configure SwarmSDK global settings
52
215
  def configure
53
216
  self.settings ||= Settings.new
@@ -62,6 +225,171 @@ module SwarmSDK
62
225
  # Alias for backward compatibility
63
226
  alias_method :configuration, :settings
64
227
  alias_method :reset_configuration!, :reset_settings!
228
+
229
+ private
230
+
231
+ # Check if hooks are configured in the configuration
232
+ #
233
+ # @param config [Configuration] Configuration instance
234
+ # @return [Boolean] true if any hooks are configured
235
+ def hooks_configured?(config)
236
+ config.swarm_hooks.any? ||
237
+ config.all_agents_hooks.any? ||
238
+ config.agents.any? { |_, agent_def| agent_def.hooks&.any? }
239
+ end
240
+
241
+ # Parse configuration error and extract structured information
242
+ #
243
+ # Attempts to extract field path and agent name from error messages.
244
+ # Returns a structured error hash with type, field, message, and optional agent.
245
+ #
246
+ # @param error [StandardError] The caught error
247
+ # @return [Hash] Structured error hash
248
+ def parse_configuration_error(error)
249
+ message = error.message
250
+ error_hash = { message: message }
251
+
252
+ # Detect error type and extract field information
253
+ case message
254
+ # YAML syntax errors
255
+ when /Invalid YAML syntax/i
256
+ error_hash.merge!(
257
+ type: :syntax_error,
258
+ field: nil,
259
+ )
260
+
261
+ # Missing version field
262
+ when /Missing 'version' field/i
263
+ error_hash.merge!(
264
+ type: :missing_field,
265
+ field: "version",
266
+ )
267
+
268
+ # Invalid version
269
+ when /SwarmSDK requires version: (\d+)/i
270
+ error_hash.merge!(
271
+ type: :invalid_value,
272
+ field: "version",
273
+ )
274
+
275
+ # Missing swarm fields
276
+ when /Missing '(\w+)' field in swarm configuration/i
277
+ field_name = Regexp.last_match(1)
278
+ error_hash.merge!(
279
+ type: :missing_field,
280
+ field: "swarm.#{field_name}",
281
+ )
282
+
283
+ # Agent missing required field
284
+ when /Agent '([^']+)' missing required '([^']+)' field/i
285
+ agent_name = Regexp.last_match(1)
286
+ field_name = Regexp.last_match(2)
287
+ error_hash.merge!(
288
+ type: :missing_field,
289
+ field: "swarm.agents.#{agent_name}.#{field_name}",
290
+ agent: agent_name,
291
+ )
292
+
293
+ # Directory does not exist
294
+ when /Directory '([^']+)' for agent '([^']+)' does not exist/i
295
+ agent_name = Regexp.last_match(2)
296
+ error_hash.merge!(
297
+ type: :directory_not_found,
298
+ field: "swarm.agents.#{agent_name}.directory",
299
+ agent: agent_name,
300
+ )
301
+
302
+ # Error loading agent from file (must come before "Agent file not found")
303
+ when /Error loading agent '([^']+)' from file/i
304
+ agent_name = Regexp.last_match(1)
305
+ error_hash.merge!(
306
+ type: :file_load_error,
307
+ field: "swarm.agents.#{agent_name}.agent_file",
308
+ agent: agent_name,
309
+ )
310
+
311
+ # Agent file not found
312
+ when /Agent file not found: (.+)/i
313
+ # Try to extract agent name from the error context if available
314
+ error_hash.merge!(
315
+ type: :file_not_found,
316
+ field: nil, # We don't know which agent without more context
317
+ )
318
+
319
+ # Lead agent not found
320
+ when /Lead agent '([^']+)' not found in agents/i
321
+ error_hash.merge!(
322
+ type: :invalid_reference,
323
+ field: "swarm.lead",
324
+ )
325
+
326
+ # Unknown agent in connections
327
+ when /Agent '([^']+)' has connection to unknown agent '([^']+)'/i
328
+ agent_name = Regexp.last_match(1)
329
+ error_hash.merge!(
330
+ type: :invalid_reference,
331
+ field: "swarm.agents.#{agent_name}.delegates_to",
332
+ agent: agent_name,
333
+ )
334
+
335
+ # Circular dependency
336
+ when /Circular dependency detected/i
337
+ error_hash.merge!(
338
+ type: :circular_dependency,
339
+ field: nil,
340
+ )
341
+
342
+ # Configuration file not found
343
+ when /Configuration file not found/i
344
+ error_hash.merge!(
345
+ type: :file_not_found,
346
+ field: nil,
347
+ )
348
+
349
+ # Invalid hook event
350
+ when /Invalid hook event '([^']+)' for agent '([^']+)'/i
351
+ agent_name = Regexp.last_match(2)
352
+ error_hash.merge!(
353
+ type: :invalid_value,
354
+ field: "swarm.agents.#{agent_name}.hooks",
355
+ agent: agent_name,
356
+ )
357
+
358
+ # api_version validation error
359
+ when /Agent '([^']+)' has api_version set, but provider is/i
360
+ agent_name = Regexp.last_match(1)
361
+ error_hash.merge!(
362
+ type: :invalid_value,
363
+ field: "swarm.agents.#{agent_name}.api_version",
364
+ agent: agent_name,
365
+ )
366
+
367
+ # api_version invalid value
368
+ when /Agent '([^']+)' has invalid api_version/i
369
+ agent_name = Regexp.last_match(1)
370
+ error_hash.merge!(
371
+ type: :invalid_value,
372
+ field: "swarm.agents.#{agent_name}.api_version",
373
+ agent: agent_name,
374
+ )
375
+
376
+ # No agents defined
377
+ when /No agents defined/i
378
+ error_hash.merge!(
379
+ type: :missing_field,
380
+ field: "swarm.agents",
381
+ )
382
+
383
+ # Default: unknown error
384
+ else
385
+ error_hash.merge!(
386
+ type: :validation_error,
387
+ field: nil,
388
+ )
389
+ end
390
+
391
+ error_hash.compact
392
+ end
65
393
  end
66
394
 
67
395
  # Settings class for SwarmSDK global settings (not to be confused with Configuration for YAML loading)
@@ -132,22 +460,3 @@ RubyLLM.configure do |config|
132
460
  config.gpustack_api_base ||= ENV["GPUSTACK_API_BASE"]
133
461
  config.gpustack_api_key ||= ENV["GPUSTACK_API_KEY"]
134
462
  end
135
-
136
- # monkey patches
137
- # ruby_llm/mcp
138
- # - add `id` when sending "notifications/initialized" message: https://github.com/patvice/ruby_llm-mcp/issues/65
139
- # - remove `to_sym` on MCP parameter type: https://github.com/patvice/ruby_llm-mcp/issues/62#issuecomment-3421488406
140
- require "ruby_llm/mcp/notifications/initialize"
141
- require "ruby_llm/mcp/parameter"
142
-
143
- module RubyLLM
144
- module MCP
145
- module Notifications
146
- class Initialize
147
- def call
148
- @coordinator.request(notification_body, add_id: true, wait_for_response: false)
149
- end
150
- end
151
- end
152
- end
153
- end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swarm_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.6.2
46
+ version: 0.6.3
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: 0.6.2
53
+ version: 0.6.3
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: zeitwerk
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -106,6 +106,7 @@ files:
106
106
  - lib/swarm_sdk/log_collector.rb
107
107
  - lib/swarm_sdk/log_stream.rb
108
108
  - lib/swarm_sdk/markdown_parser.rb
109
+ - lib/swarm_sdk/mcp.rb
109
110
  - lib/swarm_sdk/model_aliases.json
110
111
  - lib/swarm_sdk/models.json
111
112
  - lib/swarm_sdk/models.rb