swarm_sdk 2.4.2 → 2.4.4

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: 4e0a32d6cd3def4eb9f0df716e7f10076023f8c1efd5442bdd5f6501af95d542
4
- data.tar.gz: e243a4b009844dcf19c7e425ff632a9cf19fe8d7d0e9325107c8ef50efb35f8f
3
+ metadata.gz: 2357a0a9317288666f8e26f001e2d3b42402870d0e7e03560b6ff14109b36f56
4
+ data.tar.gz: 0c11addc8f0578cfd796cda4795dfb51f2f4b134280eab2601e600b0b90670c8
5
5
  SHA512:
6
- metadata.gz: e8493eac228cb132c969476d884c2e2ae3d7008bbceef134c411549545e8c879e6a4aaa75c9c04835555b28193740633163142c2696ba42f6a85d8ea26ed79b3
7
- data.tar.gz: 104440a7754b2fa74bc74eade37cf6ea76cbef2b334d0f5eb0f54bb0f1a38091d3c403294baccbf5a74a44c10468dd5fff89f89abe6c7b5cbba06c36b365e6d0
6
+ metadata.gz: '029817745549dfcd04234c2b4e9f809e139fc222cf717f366d09c5cca6d7b7e2ae50a6f2c52058380a2a1e860e86526618ca06de464589f1d10eb21bebe1bb04'
7
+ data.tar.gz: 2455fab120ec48edeb80adfa0bfa5ede016f09a49eb4ab2dc2e48490432d4b282255999f5540cb494267d4fe46c8d2f72e94ea7fcc37059db58f533ebd0bd834
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ # Global registry for reusable agent definitions
5
+ #
6
+ # AgentRegistry allows declaring agents in separate files that can be
7
+ # referenced by name in swarm definitions. This promotes code reuse and
8
+ # separation of concerns - agent definitions can live in dedicated files
9
+ # while swarm configurations compose them together.
10
+ #
11
+ # ## Usage
12
+ #
13
+ # Register agents globally (typically in separate files):
14
+ #
15
+ # # agents/backend.rb
16
+ # SwarmSDK.agent :backend do
17
+ # model "claude-sonnet-4"
18
+ # description "Backend API developer"
19
+ # system_prompt "You build REST APIs"
20
+ # tools :Read, :Edit, :Bash
21
+ # end
22
+ #
23
+ # Reference registered agents in swarm definitions:
24
+ #
25
+ # # swarm.rb
26
+ # SwarmSDK.build do
27
+ # name "Dev Team"
28
+ # lead :backend
29
+ #
30
+ # agent :backend # Pulls from registry
31
+ # end
32
+ #
33
+ # ## Override Support
34
+ #
35
+ # Registered agents can be extended with additional configuration:
36
+ #
37
+ # SwarmSDK.build do
38
+ # name "Dev Team"
39
+ # lead :backend
40
+ #
41
+ # agent :backend do
42
+ # # Registry config is applied first, then this block
43
+ # tools :CustomTool # Adds to tools from registry
44
+ # delegates_to :database
45
+ # end
46
+ # end
47
+ #
48
+ # @note This registry is not thread-safe. In multi-threaded environments,
49
+ # register all agents before spawning threads, or synchronize access
50
+ # externally. For typical fiber-based async usage (the default in SwarmSDK),
51
+ # this is not a concern.
52
+ #
53
+ class AgentRegistry
54
+ @agents = {}
55
+
56
+ class << self
57
+ # Register an agent definition block
58
+ #
59
+ # Stores a configuration block that will be executed when the agent
60
+ # is referenced in a swarm definition. The block receives an
61
+ # Agent::Builder context and can use all builder DSL methods.
62
+ #
63
+ # @param name [Symbol, String] Agent name (will be symbolized)
64
+ # @yield Agent configuration block using Agent::Builder DSL
65
+ # @return [void]
66
+ # @raise [ArgumentError] If no block is provided
67
+ # @raise [ArgumentError] If agent with same name is already registered
68
+ #
69
+ # @example Register a backend agent
70
+ # SwarmSDK::AgentRegistry.register(:backend) do
71
+ # model "claude-sonnet-4"
72
+ # description "Backend developer"
73
+ # tools :Read, :Edit, :Bash
74
+ # end
75
+ #
76
+ # @example Register with MCP servers
77
+ # SwarmSDK::AgentRegistry.register(:filesystem_agent) do
78
+ # model "gpt-4"
79
+ # description "File manager"
80
+ # mcp_server :fs, type: :stdio, command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem"]
81
+ # end
82
+ def register(name, &block)
83
+ raise ArgumentError, "Block required for agent registration" unless block_given?
84
+
85
+ sym_name = name.to_sym
86
+ if @agents.key?(sym_name)
87
+ raise ArgumentError,
88
+ "Agent '#{sym_name}' is already registered. " \
89
+ "Use SwarmSDK.clear_agent_registry! to reset, or choose a different name."
90
+ end
91
+
92
+ @agents[sym_name] = block
93
+ end
94
+
95
+ # Retrieve a registered agent block
96
+ #
97
+ # @param name [Symbol, String] Agent name
98
+ # @return [Proc, nil] The registration block or nil if not found
99
+ #
100
+ # @example
101
+ # block = SwarmSDK::AgentRegistry.get(:backend)
102
+ # builder.instance_eval(&block) if block
103
+ def get(name)
104
+ @agents[name.to_sym]
105
+ end
106
+
107
+ # Check if an agent is registered
108
+ #
109
+ # @param name [Symbol, String] Agent name
110
+ # @return [Boolean] true if agent is registered
111
+ #
112
+ # @example
113
+ # if SwarmSDK::AgentRegistry.registered?(:backend)
114
+ # puts "Backend agent is available"
115
+ # end
116
+ def registered?(name)
117
+ @agents.key?(name.to_sym)
118
+ end
119
+
120
+ # List all registered agent names
121
+ #
122
+ # @return [Array<Symbol>] Names of all registered agents
123
+ #
124
+ # @example
125
+ # SwarmSDK::AgentRegistry.names
126
+ # # => [:backend, :frontend, :database]
127
+ def names
128
+ @agents.keys
129
+ end
130
+
131
+ # Clear all registrations
132
+ #
133
+ # Primarily useful for testing to ensure clean state between tests.
134
+ #
135
+ # @return [void]
136
+ #
137
+ # @example In test setup/teardown
138
+ # def teardown
139
+ # SwarmSDK::AgentRegistry.clear
140
+ # end
141
+ def clear
142
+ @agents.clear
143
+ end
144
+ end
145
+ end
146
+ end
@@ -64,11 +64,14 @@ module SwarmSDK
64
64
  @swarm_registry_config = builder.registrations
65
65
  end
66
66
 
67
- # Define an agent with fluent API or load from markdown content
67
+ # Define an agent with fluent API, load from markdown, or reference registry
68
68
  #
69
- # Supports two forms:
70
- # 1. Inline DSL: agent :name do ... end
71
- # 2. Markdown content: agent :name, <<~MD ... MD
69
+ # Supports multiple forms:
70
+ # 1. Registry lookup: agent :name (pulls from global registry)
71
+ # 2. Registry + overrides: agent :name do ... end (when registered)
72
+ # 3. Inline DSL: agent :name do ... end (when not registered)
73
+ # 4. Markdown content: agent :name, <<~MD ... MD
74
+ # 5. Markdown + overrides: agent :name, <<~MD do ... end
72
75
  #
73
76
  # @example Inline DSL
74
77
  # agent :backend do
@@ -77,6 +80,15 @@ module SwarmSDK
77
80
  # tools :Read, :Write
78
81
  # end
79
82
  #
83
+ # @example Registry lookup (agent must be registered with SwarmSDK.agent)
84
+ # agent :backend # Pulls configuration from registry
85
+ #
86
+ # @example Registry + overrides
87
+ # agent :backend do
88
+ # # Base config from registry, then apply overrides
89
+ # tools :CustomTool # Adds to registry-defined tools
90
+ # end
91
+ #
80
92
  # @example Markdown content
81
93
  # agent :backend, <<~MD
82
94
  # ---
@@ -87,19 +99,38 @@ module SwarmSDK
87
99
  # You build APIs.
88
100
  # MD
89
101
  def agent(name, content = nil, &block)
102
+ name = name.to_sym
103
+
90
104
  # Case 1: agent :name, <<~MD do ... end (markdown + overrides)
91
105
  if content.is_a?(String) && block_given? && markdown_content?(content)
92
106
  load_agent_from_markdown_with_overrides(content, name, &block)
107
+
93
108
  # Case 2: agent :name, <<~MD (markdown only)
94
109
  elsif content.is_a?(String) && !block_given? && markdown_content?(content)
95
110
  load_agent_from_markdown(content, name)
96
- # Case 3: agent :name do ... end (inline DSL)
111
+
112
+ # Case 3: agent :name (registry lookup only - no content, no block)
113
+ elsif content.nil? && !block_given?
114
+ load_agent_from_registry(name)
115
+
116
+ # Case 4: agent :name do ... end (with registered agent - registry + overrides)
117
+ elsif content.nil? && block_given? && AgentRegistry.registered?(name)
118
+ load_agent_from_registry_with_overrides(name, &block)
119
+
120
+ # Case 5: agent :name do ... end (inline DSL - not registered)
97
121
  elsif block_given?
98
122
  builder = Agent::Builder.new(name)
99
123
  builder.instance_eval(&block)
100
124
  @agents[name] = builder
125
+
101
126
  else
102
- raise ArgumentError, "Invalid agent definition. Use: agent :name { ... } OR agent :name, <<~MD ... MD OR agent :name, <<~MD do ... end"
127
+ raise ArgumentError,
128
+ "Invalid agent definition for '#{name}'. Use:\n " \
129
+ "agent :#{name} { ... } # Inline DSL\n " \
130
+ "agent :#{name} # Registry lookup\n " \
131
+ "agent :#{name} { ... } # Registry + overrides (if registered)\n " \
132
+ "agent :#{name}, <<~MD ... MD # Markdown\n " \
133
+ "agent :#{name}, <<~MD do ... end # Markdown + overrides"
103
134
  end
104
135
  end
105
136
 
@@ -138,6 +169,54 @@ module SwarmSDK
138
169
  str.start_with?("---") || str.include?("\n---\n")
139
170
  end
140
171
 
172
+ # Load an agent from the global registry
173
+ #
174
+ # Retrieves the registered agent block and executes it in the context
175
+ # of a new Agent::Builder.
176
+ #
177
+ # @param name [Symbol] Agent name
178
+ # @return [void]
179
+ # @raise [ConfigurationError] If agent is not registered
180
+ #
181
+ # @example
182
+ # load_agent_from_registry(:backend)
183
+ def load_agent_from_registry(name)
184
+ registered_proc = AgentRegistry.get(name)
185
+ unless registered_proc
186
+ raise ConfigurationError,
187
+ "Agent '#{name}' not found in registry. " \
188
+ "Either define inline with `agent :#{name} do ... end` or " \
189
+ "register globally with `SwarmSDK.agent :#{name} do ... end`"
190
+ end
191
+
192
+ builder = Agent::Builder.new(name)
193
+ builder.instance_eval(&registered_proc)
194
+ @agents[name] = builder
195
+ end
196
+
197
+ # Load an agent from the registry with additional overrides
198
+ #
199
+ # Applies the registered configuration first, then executes the
200
+ # override block to customize the agent.
201
+ #
202
+ # @param name [Symbol] Agent name
203
+ # @yield Override block with additional configuration
204
+ # @return [void]
205
+ #
206
+ # @example
207
+ # load_agent_from_registry_with_overrides(:backend) do
208
+ # tools :CustomTool # Adds to registry-defined tools
209
+ # end
210
+ def load_agent_from_registry_with_overrides(name, &override_block)
211
+ registered_proc = AgentRegistry.get(name)
212
+ # Guaranteed to exist since we checked in the condition
213
+
214
+ builder = Agent::Builder.new(name)
215
+ builder.instance_eval(&registered_proc) # Base config from registry
216
+ builder.instance_eval(&override_block) # Apply overrides
217
+ @agents[name] = builder
218
+ end
219
+
141
220
  # Load an agent from markdown content
142
221
  #
143
222
  # Returns a hash of the agent config (not a Definition yet) so that
@@ -91,6 +91,7 @@ module SwarmSDK
91
91
  webfetch_base_url: ["SWARM_SDK_WEBFETCH_BASE_URL", nil],
92
92
  webfetch_max_tokens: ["SWARM_SDK_WEBFETCH_MAX_TOKENS", 4096],
93
93
  allow_filesystem_tools: ["SWARM_SDK_ALLOW_FILESYSTEM_TOOLS", true],
94
+ env_interpolation: ["SWARM_SDK_ENV_INTERPOLATION", true],
94
95
  }.freeze
95
96
 
96
97
  class << self
@@ -279,7 +280,7 @@ module SwarmSDK
279
280
  # @return [Integer, Float, Boolean, String] The parsed value
280
281
  def parse_env_value(value, key)
281
282
  case key
282
- when :allow_filesystem_tools
283
+ when :allow_filesystem_tools, :env_interpolation
283
284
  # Convert string to boolean
284
285
  case value.to_s.downcase
285
286
  when "true", "yes", "1", "on", "enabled"
@@ -30,9 +30,18 @@ module SwarmSDK
30
30
  :nodes,
31
31
  :external_swarms
32
32
 
33
- def initialize(yaml_content, base_dir:)
33
+ # Initialize parser with YAML content and options
34
+ #
35
+ # @param yaml_content [String] YAML configuration content
36
+ # @param base_dir [String, Pathname] Base directory for resolving paths
37
+ # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
38
+ # When nil, uses the global SwarmSDK.config.env_interpolation setting.
39
+ # When true, interpolates ${VAR} and ${VAR:=default} patterns.
40
+ # When false, skips interpolation entirely.
41
+ def initialize(yaml_content, base_dir:, env_interpolation: nil)
34
42
  @yaml_content = yaml_content
35
43
  @base_dir = Pathname.new(base_dir).expand_path
44
+ @env_interpolation = env_interpolation
36
45
  @config_type = nil
37
46
  @swarm_id = nil
38
47
  @swarm_name = nil
@@ -55,7 +64,7 @@ module SwarmSDK
55
64
  end
56
65
 
57
66
  @config = Utils.symbolize_keys(@config)
58
- interpolate_env_vars!(@config)
67
+ interpolate_env_vars!(@config) if env_interpolation_enabled?
59
68
 
60
69
  validate_version
61
70
  detect_and_validate_type
@@ -86,6 +95,17 @@ module SwarmSDK
86
95
 
87
96
  private
88
97
 
98
+ # Check if environment variable interpolation is enabled
99
+ #
100
+ # Uses the local setting if explicitly set, otherwise falls back to global config.
101
+ #
102
+ # @return [Boolean] true if interpolation should be performed
103
+ def env_interpolation_enabled?
104
+ return @env_interpolation unless @env_interpolation.nil?
105
+
106
+ SwarmSDK.config.env_interpolation
107
+ end
108
+
89
109
  def validate_version
90
110
  version = @config[:version]
91
111
  raise ConfigurationError, "Missing 'version' field in configuration" unless version
@@ -38,9 +38,13 @@ module SwarmSDK
38
38
  # Load configuration from YAML file
39
39
  #
40
40
  # @param path [String, Pathname] Path to YAML configuration file
41
+ # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
42
+ # When nil, uses the global SwarmSDK.config.env_interpolation setting.
43
+ # When true, interpolates ${VAR} and ${VAR:=default} patterns.
44
+ # When false, skips interpolation entirely.
41
45
  # @return [Configuration] Validated configuration instance
42
46
  # @raise [ConfigurationError] If file not found or invalid
43
- def load_file(path)
47
+ def load_file(path, env_interpolation: nil)
44
48
  path = Pathname.new(path).expand_path
45
49
 
46
50
  unless path.exist?
@@ -50,7 +54,7 @@ module SwarmSDK
50
54
  yaml_content = File.read(path)
51
55
  base_dir = path.dirname
52
56
 
53
- new(yaml_content, base_dir: base_dir).tap(&:load_and_validate)
57
+ new(yaml_content, base_dir: base_dir, env_interpolation: env_interpolation).tap(&:load_and_validate)
54
58
  rescue Errno::ENOENT
55
59
  raise ConfigurationError, "Configuration file not found: #{path}"
56
60
  end
@@ -60,12 +64,17 @@ module SwarmSDK
60
64
  #
61
65
  # @param yaml_content [String] YAML configuration content
62
66
  # @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
63
- def initialize(yaml_content, base_dir: Dir.pwd)
67
+ # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
68
+ # When nil, uses the global SwarmSDK.config.env_interpolation setting.
69
+ # When true, interpolates ${VAR} and ${VAR:=default} patterns.
70
+ # When false, skips interpolation entirely.
71
+ def initialize(yaml_content, base_dir: Dir.pwd, env_interpolation: nil)
64
72
  raise ArgumentError, "yaml_content cannot be nil" if yaml_content.nil?
65
73
  raise ArgumentError, "base_dir cannot be nil" if base_dir.nil?
66
74
 
67
75
  @yaml_content = yaml_content
68
76
  @base_dir = Pathname.new(base_dir).expand_path
77
+ @env_interpolation = env_interpolation
69
78
  @parser = nil
70
79
  @translator = nil
71
80
  end
@@ -77,7 +86,7 @@ module SwarmSDK
77
86
  #
78
87
  # @return [self]
79
88
  def load_and_validate
80
- @parser = Parser.new(@yaml_content, base_dir: @base_dir)
89
+ @parser = Parser.new(@yaml_content, base_dir: @base_dir, env_interpolation: @env_interpolation)
81
90
  @parser.parse
82
91
 
83
92
  # Sync parsed data to instance variables for backward compatibility
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.4.2"
4
+ VERSION = "2.4.4"
5
5
  end
@@ -119,6 +119,9 @@ module SwarmSDK
119
119
  #
120
120
  # @return [Workflow] Configured workflow instance
121
121
  def build_workflow
122
+ # Resolve any missing agents from global registry before building definitions
123
+ resolve_missing_agents_from_registry
124
+
122
125
  # Build agent definitions
123
126
  agent_definitions = build_agent_definitions
124
127
 
@@ -138,6 +141,52 @@ module SwarmSDK
138
141
 
139
142
  workflow
140
143
  end
144
+
145
+ # Resolve agents referenced in nodes that aren't defined at workflow level
146
+ #
147
+ # For each agent referenced in a node but not defined with `agent :name do ... end`,
148
+ # checks the global AgentRegistry and loads the agent if found.
149
+ #
150
+ # This allows workflows to reference globally registered agents without
151
+ # explicitly re-declaring them at the workflow level.
152
+ #
153
+ # @return [void]
154
+ # @raise [ConfigurationError] If agent not found in workflow or registry
155
+ def resolve_missing_agents_from_registry
156
+ # Collect all agents referenced in nodes
157
+ referenced_agents = collect_referenced_agents
158
+
159
+ # Find agents that aren't defined at workflow level
160
+ defined_agents = @agents.keys
161
+ missing_agents = referenced_agents - defined_agents
162
+
163
+ # Try to resolve each missing agent from the global registry
164
+ missing_agents.each do |agent_name|
165
+ if AgentRegistry.registered?(agent_name)
166
+ # Load from registry (uses same method as BaseBuilder)
167
+ load_agent_from_registry(agent_name)
168
+ else
169
+ raise ConfigurationError,
170
+ "Agent '#{agent_name}' referenced in node but not found. " \
171
+ "Either define at workflow level with `agent :#{agent_name} do ... end` " \
172
+ "or register globally with `SwarmSDK.agent :#{agent_name} do ... end`"
173
+ end
174
+ end
175
+ end
176
+
177
+ # Collect all agent names referenced across all nodes
178
+ #
179
+ # Includes both directly referenced agents and delegation targets.
180
+ #
181
+ # @return [Array<Symbol>] Unique agent names referenced in nodes
182
+ def collect_referenced_agents
183
+ @nodes.values.flat_map do |node_builder|
184
+ # Collect both direct agents and their delegation targets
185
+ node_builder.agent_configs.flat_map do |config|
186
+ [config[:agent]] + Array(config[:delegates_to])
187
+ end
188
+ end.uniq
189
+ end
141
190
  end
142
191
  end
143
192
  end
data/lib/swarm_sdk.rb CHANGED
@@ -95,6 +95,73 @@ module SwarmSDK
95
95
  Config.reset!
96
96
  end
97
97
 
98
+ # Register a global agent definition
99
+ #
100
+ # Declares an agent configuration that can be referenced by name in any
101
+ # swarm definition. This allows defining agents in separate files and
102
+ # composing them into swarms without duplication.
103
+ #
104
+ # The registered block uses the Agent::Builder DSL and is executed when
105
+ # the agent is referenced in a swarm definition.
106
+ #
107
+ # @param name [Symbol, String] Agent name (will be symbolized)
108
+ # @yield Agent configuration block using Agent::Builder DSL
109
+ # @return [void]
110
+ # @raise [ArgumentError] If no block is provided
111
+ #
112
+ # @example Register agent in separate file
113
+ # # agents/backend.rb
114
+ # SwarmSDK.agent :backend do
115
+ # model "claude-sonnet-4"
116
+ # description "Backend API developer"
117
+ # system_prompt "You build REST APIs"
118
+ # tools :Read, :Edit, :Bash
119
+ # delegates_to :database
120
+ # end
121
+ #
122
+ # @example Reference in swarm definition
123
+ # # swarm.rb
124
+ # require_relative "agents/backend"
125
+ #
126
+ # SwarmSDK.build do
127
+ # name "Dev Team"
128
+ # lead :backend
129
+ #
130
+ # agent :backend # Pulls from registry
131
+ # end
132
+ #
133
+ # @example Extend registered agent with overrides
134
+ # SwarmSDK.build do
135
+ # name "Extended Team"
136
+ # lead :backend
137
+ #
138
+ # agent :backend do
139
+ # # Registry config applied first, then this block
140
+ # tools :CustomTool # Adds to existing tools
141
+ # delegates_to :cache # Adds delegation target
142
+ # end
143
+ # end
144
+ #
145
+ # @see AgentRegistry
146
+ def agent(name, &block)
147
+ AgentRegistry.register(name, &block)
148
+ end
149
+
150
+ # Clear the global agent registry
151
+ #
152
+ # Removes all registered agent definitions. Primarily useful for testing
153
+ # to ensure clean state between tests.
154
+ #
155
+ # @return [void]
156
+ #
157
+ # @example In test teardown
158
+ # def teardown
159
+ # SwarmSDK.clear_agent_registry!
160
+ # end
161
+ def clear_agent_registry!
162
+ AgentRegistry.clear
163
+ end
164
+
98
165
  # Main entry point for DSL - builds simple multi-agent swarms
99
166
  #
100
167
  # @return [Swarm] Always returns a Swarm instance
@@ -208,6 +275,11 @@ module SwarmSDK
208
275
  #
209
276
  # @param yaml_content [String] YAML configuration content
210
277
  # @param base_dir [String, Pathname] Base directory for resolving agent file paths (default: Dir.pwd)
278
+ # @param allow_filesystem_tools [Boolean, nil] Whether to allow filesystem tools (nil uses global setting)
279
+ # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
280
+ # When nil, uses the global SwarmSDK.config.env_interpolation setting.
281
+ # When true, interpolates ${VAR} and ${VAR:=default} patterns.
282
+ # When false, skips interpolation entirely.
211
283
  # @return [Swarm, Workflow] Configured swarm or workflow instance
212
284
  # @raise [ConfigurationError] If YAML is invalid or configuration is incorrect
213
285
  #
@@ -230,8 +302,11 @@ module SwarmSDK
230
302
  # @example Load with default base_dir (Dir.pwd)
231
303
  # yaml = File.read("config.yml")
232
304
  # swarm = SwarmSDK.load(yaml) # base_dir defaults to Dir.pwd
233
- def load(yaml_content, base_dir: Dir.pwd, allow_filesystem_tools: nil)
234
- config = Configuration.new(yaml_content, base_dir: base_dir)
305
+ #
306
+ # @example Load without environment variable interpolation
307
+ # swarm = SwarmSDK.load(yaml, env_interpolation: false)
308
+ def load(yaml_content, base_dir: Dir.pwd, allow_filesystem_tools: nil, env_interpolation: nil)
309
+ config = Configuration.new(yaml_content, base_dir: base_dir, env_interpolation: env_interpolation)
235
310
  config.load_and_validate
236
311
  swarm = config.to_swarm(allow_filesystem_tools: allow_filesystem_tools)
237
312
 
@@ -253,6 +328,11 @@ module SwarmSDK
253
328
  # loading swarms from configuration files.
254
329
  #
255
330
  # @param path [String, Pathname] Path to YAML configuration file
331
+ # @param allow_filesystem_tools [Boolean, nil] Whether to allow filesystem tools (nil uses global setting)
332
+ # @param env_interpolation [Boolean, nil] Whether to interpolate environment variables.
333
+ # When nil, uses the global SwarmSDK.config.env_interpolation setting.
334
+ # When true, interpolates ${VAR} and ${VAR:=default} patterns.
335
+ # When false, skips interpolation entirely.
256
336
  # @return [Swarm, Workflow] Configured swarm or workflow instance
257
337
  # @raise [ConfigurationError] If file not found or configuration invalid
258
338
  #
@@ -262,8 +342,11 @@ module SwarmSDK
262
342
  #
263
343
  # @example With absolute path
264
344
  # swarm = SwarmSDK.load_file("/absolute/path/config.yml")
265
- def load_file(path, allow_filesystem_tools: nil)
266
- config = Configuration.load_file(path)
345
+ #
346
+ # @example Load without environment variable interpolation
347
+ # swarm = SwarmSDK.load_file("config.yml", env_interpolation: false)
348
+ def load_file(path, allow_filesystem_tools: nil, env_interpolation: nil)
349
+ config = Configuration.load_file(path, env_interpolation: env_interpolation)
267
350
  swarm = config.to_swarm(allow_filesystem_tools: allow_filesystem_tools)
268
351
 
269
352
  # Apply hooks if any are configured (YAML-only feature)
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.4.2
4
+ version: 2.4.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
@@ -139,6 +139,7 @@ files:
139
139
  - lib/swarm_sdk/agent/definition.rb
140
140
  - lib/swarm_sdk/agent/llm_instrumentation_middleware.rb
141
141
  - lib/swarm_sdk/agent/system_prompt_builder.rb
142
+ - lib/swarm_sdk/agent_registry.rb
142
143
  - lib/swarm_sdk/builders/base_builder.rb
143
144
  - lib/swarm_sdk/claude_code_agent_adapter.rb
144
145
  - lib/swarm_sdk/concerns/cleanupable.rb