swarm_sdk 2.4.2 → 2.4.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 +4 -4
- data/lib/swarm_sdk/agent_registry.rb +146 -0
- data/lib/swarm_sdk/builders/base_builder.rb +85 -6
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +49 -0
- data/lib/swarm_sdk.rb +67 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 370423fc8b77c4d3cc159123a64795b04bcea24ed6b96090a194329bb1d5af73
|
|
4
|
+
data.tar.gz: 05fa10de4f50a64e3bdb919fbea65a4e480cf0eb0b054b2c1e32ef6f5a84c65d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 064afcb3024b9a3006ba398f1237f5b7c189ae307a7603ea744312ec885f8ea307b1e9cb186d0b0a9da6b9908c46711b58a50c849dfdefce089ec82c989e5b26
|
|
7
|
+
data.tar.gz: eaded0c55ebc089acd20ad14a73a97ae24fe8cd5acf0cc2c557733ac03d224b96291e0717025998b9ba1c703e96465178b1dfab02c563c1d206790729daf3d21
|
|
@@ -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
|
|
67
|
+
# Define an agent with fluent API, load from markdown, or reference registry
|
|
68
68
|
#
|
|
69
|
-
# Supports
|
|
70
|
-
# 1.
|
|
71
|
-
# 2.
|
|
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
|
-
|
|
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,
|
|
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(®istered_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(®istered_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
|
data/lib/swarm_sdk/version.rb
CHANGED
|
@@ -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
|
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.
|
|
4
|
+
version: 2.4.3
|
|
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
|