swarm_sdk 2.2.0 → 2.4.0
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/builder.rb +58 -0
- data/lib/swarm_sdk/agent/chat.rb +527 -1059
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +262 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +11 -13
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
- data/lib/swarm_sdk/agent/context.rb +1 -2
- data/lib/swarm_sdk/agent/definition.rb +66 -154
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
- data/lib/swarm_sdk/builders/base_builder.rb +409 -0
- data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
- data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
- data/lib/swarm_sdk/concerns/validatable.rb +55 -0
- data/lib/swarm_sdk/config.rb +301 -0
- data/lib/swarm_sdk/configuration/parser.rb +353 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +65 -543
- data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
- data/lib/swarm_sdk/context_compactor.rb +6 -11
- data/lib/swarm_sdk/context_management/builder.rb +128 -0
- data/lib/swarm_sdk/context_management/context.rb +328 -0
- data/lib/swarm_sdk/defaults.rb +196 -0
- data/lib/swarm_sdk/events_to_messages.rb +18 -0
- data/lib/swarm_sdk/hooks/adapter.rb +3 -3
- data/lib/swarm_sdk/hooks/shell_executor.rb +4 -2
- data/lib/swarm_sdk/log_collector.rb +179 -29
- data/lib/swarm_sdk/log_stream.rb +29 -0
- data/lib/swarm_sdk/models.json +4333 -1
- data/lib/swarm_sdk/node_context.rb +1 -1
- data/lib/swarm_sdk/observer/builder.rb +81 -0
- data/lib/swarm_sdk/observer/config.rb +45 -0
- data/lib/swarm_sdk/observer/manager.rb +236 -0
- data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
- data/lib/swarm_sdk/plugin.rb +93 -3
- data/lib/swarm_sdk/snapshot.rb +6 -6
- data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
- data/lib/swarm_sdk/state_restorer.rb +136 -151
- data/lib/swarm_sdk/state_snapshot.rb +65 -100
- data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
- data/lib/swarm_sdk/swarm/builder.rb +44 -578
- data/lib/swarm_sdk/swarm/executor.rb +213 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
- data/lib/swarm_sdk/swarm/tool_configurator.rb +44 -140
- data/lib/swarm_sdk/swarm.rb +146 -689
- data/lib/swarm_sdk/tools/bash.rb +14 -8
- data/lib/swarm_sdk/tools/delegate.rb +61 -43
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +12 -4
- data/lib/swarm_sdk/tools/grep.rb +7 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
- data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
- data/lib/swarm_sdk/tools/read.rb +16 -18
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +9 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/todo_write.rb +7 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +20 -17
- data/lib/swarm_sdk/tools/write.rb +8 -13
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +143 -0
- data/lib/swarm_sdk/workflow/executor.rb +497 -0
- data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +7 -5
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +5 -3
- data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
- data/lib/swarm_sdk.rb +64 -104
- metadata +68 -15
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
|
@@ -5,24 +5,51 @@ module SwarmSDK
|
|
|
5
5
|
# Registry for built-in SwarmSDK tools
|
|
6
6
|
#
|
|
7
7
|
# Maps tool names (symbols) to their RubyLLM::Tool classes.
|
|
8
|
-
# Provides validation and
|
|
8
|
+
# Provides validation, lookup, and factory functionality for tool registration.
|
|
9
|
+
#
|
|
10
|
+
# ## Tool Creation Pattern
|
|
11
|
+
#
|
|
12
|
+
# Tools register themselves with their creation requirements via the `tool_factory` method.
|
|
13
|
+
# This eliminates the need for a giant case statement in ToolConfigurator.
|
|
14
|
+
#
|
|
15
|
+
# Tools fall into three categories:
|
|
16
|
+
# 1. **No params**: Simple tools with no initialization requirements (Think, Clock)
|
|
17
|
+
# 2. **Directory only**: Tools needing working directory (Bash, Grep, Glob)
|
|
18
|
+
# 3. **Agent context**: Tools needing agent tracking (Read, Write, Edit, MultiEdit)
|
|
19
|
+
# 4. **Scratchpad**: Tools needing scratchpad storage instance
|
|
20
|
+
#
|
|
21
|
+
# @example Adding a new tool with creation requirements
|
|
22
|
+
# # In the tool class:
|
|
23
|
+
# class MyTool < RubyLLM::Tool
|
|
24
|
+
# def self.creation_requirements
|
|
25
|
+
# [:agent_name, :directory]
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# # In registry:
|
|
30
|
+
# BUILTIN_TOOLS = {
|
|
31
|
+
# MyTool: SwarmSDK::Tools::MyTool,
|
|
32
|
+
# }
|
|
9
33
|
#
|
|
10
34
|
# Note: Plugin-provided tools (e.g., memory tools) are NOT in this registry.
|
|
11
35
|
# They are registered via SwarmSDK::PluginRegistry instead.
|
|
12
36
|
class Registry
|
|
13
37
|
# All available built-in tools
|
|
38
|
+
#
|
|
39
|
+
# Maps tool names to their classes. The class must respond to `creation_requirements`
|
|
40
|
+
# to specify what parameters are needed for instantiation.
|
|
14
41
|
BUILTIN_TOOLS = {
|
|
15
|
-
Read:
|
|
16
|
-
Write:
|
|
17
|
-
Edit:
|
|
42
|
+
Read: SwarmSDK::Tools::Read,
|
|
43
|
+
Write: SwarmSDK::Tools::Write,
|
|
44
|
+
Edit: SwarmSDK::Tools::Edit,
|
|
45
|
+
MultiEdit: SwarmSDK::Tools::MultiEdit,
|
|
18
46
|
Bash: SwarmSDK::Tools::Bash,
|
|
19
47
|
Grep: SwarmSDK::Tools::Grep,
|
|
20
48
|
Glob: SwarmSDK::Tools::Glob,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
ScratchpadList: :special, # Requires scratchpad storage instance
|
|
49
|
+
TodoWrite: SwarmSDK::Tools::TodoWrite,
|
|
50
|
+
ScratchpadWrite: :scratchpad, # Requires scratchpad storage instance
|
|
51
|
+
ScratchpadRead: :scratchpad, # Requires scratchpad storage instance
|
|
52
|
+
ScratchpadList: :scratchpad, # Requires scratchpad storage instance
|
|
26
53
|
Think: SwarmSDK::Tools::Think,
|
|
27
54
|
WebFetch: SwarmSDK::Tools::WebFetch,
|
|
28
55
|
Clock: SwarmSDK::Tools::Clock,
|
|
@@ -35,12 +62,49 @@ module SwarmSDK
|
|
|
35
62
|
# They are managed by SwarmSDK::PluginRegistry instead.
|
|
36
63
|
#
|
|
37
64
|
# @param name [Symbol, String] Tool name
|
|
38
|
-
# @return [Class, Symbol, nil] Tool class, :
|
|
65
|
+
# @return [Class, Symbol, nil] Tool class, :scratchpad marker, or nil if not found
|
|
39
66
|
def get(name)
|
|
40
67
|
name_sym = name.to_sym
|
|
41
68
|
BUILTIN_TOOLS[name_sym]
|
|
42
69
|
end
|
|
43
70
|
|
|
71
|
+
# Create a tool instance using the Factory Pattern
|
|
72
|
+
#
|
|
73
|
+
# Uses the tool's `creation_requirements` class method to determine
|
|
74
|
+
# what parameters to pass to the constructor.
|
|
75
|
+
#
|
|
76
|
+
# @param name [Symbol, String] Tool name
|
|
77
|
+
# @param context [Hash] Available context for tool creation
|
|
78
|
+
# @option context [Symbol] :agent_name Agent identifier
|
|
79
|
+
# @option context [String] :directory Agent's working directory
|
|
80
|
+
# @option context [Object] :scratchpad_storage Scratchpad storage instance
|
|
81
|
+
# @return [RubyLLM::Tool] Instantiated tool
|
|
82
|
+
# @raise [ConfigurationError] If tool is unknown or has unmet requirements
|
|
83
|
+
def create(name, context = {})
|
|
84
|
+
name_sym = name.to_sym
|
|
85
|
+
tool_entry = BUILTIN_TOOLS[name_sym]
|
|
86
|
+
|
|
87
|
+
raise ConfigurationError, "Unknown tool: #{name}" unless tool_entry
|
|
88
|
+
|
|
89
|
+
# Handle scratchpad tools specially (they use factory methods)
|
|
90
|
+
if tool_entry == :scratchpad
|
|
91
|
+
return create_scratchpad_tool(name_sym, context[:scratchpad_storage])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Get the tool class and its requirements
|
|
95
|
+
tool_class = tool_entry
|
|
96
|
+
|
|
97
|
+
# Check if tool defines creation requirements
|
|
98
|
+
if tool_class.respond_to?(:creation_requirements)
|
|
99
|
+
requirements = tool_class.creation_requirements
|
|
100
|
+
params = extract_params(requirements, context, name)
|
|
101
|
+
tool_class.new(**params)
|
|
102
|
+
else
|
|
103
|
+
# No requirements - simple instantiation
|
|
104
|
+
tool_class.new
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
44
108
|
# Get multiple tool classes by names
|
|
45
109
|
#
|
|
46
110
|
# @param names [Array<Symbol, String>] Tool names
|
|
@@ -87,6 +151,54 @@ module SwarmSDK
|
|
|
87
151
|
def validate(names)
|
|
88
152
|
names.reject { |name| exists?(name) }
|
|
89
153
|
end
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
# Extract required parameters from context
|
|
158
|
+
#
|
|
159
|
+
# @param requirements [Array<Symbol>] Required parameter names
|
|
160
|
+
# @param context [Hash] Available context
|
|
161
|
+
# @param tool_name [Symbol] Tool name for error messages
|
|
162
|
+
# @return [Hash] Parameters to pass to tool constructor
|
|
163
|
+
# @raise [ConfigurationError] If required parameter is missing
|
|
164
|
+
def extract_params(requirements, context, tool_name)
|
|
165
|
+
params = {}
|
|
166
|
+
|
|
167
|
+
requirements.each do |req|
|
|
168
|
+
unless context.key?(req)
|
|
169
|
+
raise ConfigurationError,
|
|
170
|
+
"Tool #{tool_name} requires #{req} but it was not provided in context"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
params[req] = context[req]
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
params
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Create a scratchpad tool using its factory method
|
|
180
|
+
#
|
|
181
|
+
# @param name [Symbol] Scratchpad tool name
|
|
182
|
+
# @param storage [Object] Scratchpad storage instance
|
|
183
|
+
# @return [RubyLLM::Tool] Instantiated scratchpad tool
|
|
184
|
+
# @raise [ConfigurationError] If storage is not provided
|
|
185
|
+
def create_scratchpad_tool(name, storage)
|
|
186
|
+
unless storage
|
|
187
|
+
raise ConfigurationError,
|
|
188
|
+
"Scratchpad tool #{name} requires scratchpad_storage in context"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
case name
|
|
192
|
+
when :ScratchpadWrite
|
|
193
|
+
Tools::Scratchpad::ScratchpadWrite.create_for_scratchpad(storage)
|
|
194
|
+
when :ScratchpadRead
|
|
195
|
+
Tools::Scratchpad::ScratchpadRead.create_for_scratchpad(storage)
|
|
196
|
+
when :ScratchpadList
|
|
197
|
+
Tools::Scratchpad::ScratchpadList.create_for_scratchpad(storage)
|
|
198
|
+
else
|
|
199
|
+
raise ConfigurationError, "Unknown scratchpad tool: #{name}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
90
202
|
end
|
|
91
203
|
end
|
|
92
204
|
end
|
|
@@ -15,10 +15,13 @@ module SwarmSDK
|
|
|
15
15
|
# Use for temporary, cross-agent communication within a single session.
|
|
16
16
|
class ScratchpadStorage < Storage
|
|
17
17
|
# Initialize scratchpad storage (always volatile)
|
|
18
|
-
|
|
18
|
+
#
|
|
19
|
+
# @param total_size_limit [Integer, nil] Maximum total size in bytes (defaults to Defaults::Storage::TOTAL_SIZE_BYTES)
|
|
20
|
+
def initialize(total_size_limit: nil)
|
|
19
21
|
super() # Initialize parent Storage class
|
|
20
22
|
@entries = {}
|
|
21
23
|
@total_size = 0
|
|
24
|
+
@total_size_limit = total_size_limit || SwarmSDK.config.scratchpad_total_size_limit
|
|
22
25
|
@mutex = Mutex.new
|
|
23
26
|
end
|
|
24
27
|
|
|
@@ -38,8 +41,9 @@ module SwarmSDK
|
|
|
38
41
|
content_size = content.bytesize
|
|
39
42
|
|
|
40
43
|
# Check entry size limit
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
entry_size_limit = SwarmSDK.config.scratchpad_entry_size_limit
|
|
45
|
+
if content_size > entry_size_limit
|
|
46
|
+
raise ArgumentError, "Content exceeds maximum size (#{format_bytes(entry_size_limit)}). " \
|
|
43
47
|
"Current: #{format_bytes(content_size)}"
|
|
44
48
|
end
|
|
45
49
|
|
|
@@ -49,8 +53,8 @@ module SwarmSDK
|
|
|
49
53
|
new_total_size = @total_size - existing_size + content_size
|
|
50
54
|
|
|
51
55
|
# Check total size limit
|
|
52
|
-
if new_total_size >
|
|
53
|
-
raise ArgumentError, "Scratchpad full (#{format_bytes(
|
|
56
|
+
if new_total_size > @total_size_limit
|
|
57
|
+
raise ArgumentError, "Scratchpad full (#{format_bytes(@total_size_limit)} limit). " \
|
|
54
58
|
"Current: #{format_bytes(@total_size)}, " \
|
|
55
59
|
"Would be: #{format_bytes(new_total_size)}. " \
|
|
56
60
|
"Clear old entries or use smaller content."
|
|
@@ -14,12 +14,6 @@ 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 (3MB)
|
|
18
|
-
MAX_ENTRY_SIZE = 3_000_000
|
|
19
|
-
|
|
20
|
-
# Maximum total storage size (100GB)
|
|
21
|
-
MAX_TOTAL_SIZE = 100_000_000_000
|
|
22
|
-
|
|
23
17
|
# Represents a single storage entry with metadata
|
|
24
18
|
Entry = Struct.new(:content, :title, :updated_at, :size, keyword_init: true)
|
|
25
19
|
|
|
@@ -7,6 +7,13 @@ module SwarmSDK
|
|
|
7
7
|
# This tool helps agents track progress on complex multi-step tasks.
|
|
8
8
|
# Each agent maintains its own independent todo list.
|
|
9
9
|
class TodoWrite < RubyLLM::Tool
|
|
10
|
+
# Factory pattern: declare what parameters this tool needs for instantiation
|
|
11
|
+
class << self
|
|
12
|
+
def creation_requirements
|
|
13
|
+
[:agent_name]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
10
17
|
description <<~DESC
|
|
11
18
|
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
19
|
It also helps the user understand the progress of the task and overall progress of their requests.
|
|
@@ -11,7 +11,6 @@ module SwarmSDK
|
|
|
11
11
|
super()
|
|
12
12
|
@cache = {}
|
|
13
13
|
@cache_ttl = 900 # 15 minutes in seconds
|
|
14
|
-
@llm_enabled = SwarmSDK.settings.webfetch_llm_enabled?
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def name
|
|
@@ -51,16 +50,18 @@ module SwarmSDK
|
|
|
51
50
|
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
51
|
required: false
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
# NOTE: Content length and timeout now accessed via SwarmSDK.config
|
|
55
54
|
USER_AGENT = "SwarmSDK WebFetch Tool (https://github.com/parruda/claude-swarm)"
|
|
56
|
-
TIMEOUT = 30 # seconds
|
|
57
55
|
|
|
58
56
|
def execute(url:, prompt: nil)
|
|
59
57
|
# Validate inputs
|
|
60
58
|
return validation_error("url is required") if url.nil? || url.empty?
|
|
61
59
|
|
|
60
|
+
# Check if LLM processing is enabled (lazy check)
|
|
61
|
+
llm_enabled = SwarmSDK.config.webfetch_llm_enabled?
|
|
62
|
+
|
|
62
63
|
# Validate prompt when LLM processing is enabled
|
|
63
|
-
if
|
|
64
|
+
if llm_enabled && (prompt.nil? || prompt.empty?)
|
|
64
65
|
return validation_error("prompt is required when LLM processing is configured")
|
|
65
66
|
end
|
|
66
67
|
|
|
@@ -69,7 +70,7 @@ module SwarmSDK
|
|
|
69
70
|
return validation_error("Invalid URL format: #{url}") unless normalized_url
|
|
70
71
|
|
|
71
72
|
# Check cache first (cache key includes prompt if LLM is enabled)
|
|
72
|
-
cache_key =
|
|
73
|
+
cache_key = llm_enabled ? "#{normalized_url}:#{prompt}" : normalized_url
|
|
73
74
|
cached = get_from_cache(cache_key)
|
|
74
75
|
return cached if cached
|
|
75
76
|
|
|
@@ -86,13 +87,14 @@ module SwarmSDK
|
|
|
86
87
|
markdown_content = html_to_markdown(fetch_result[:body])
|
|
87
88
|
|
|
88
89
|
# Truncate if too long
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
max_content = SwarmSDK.config.web_fetch_character_limit
|
|
91
|
+
if markdown_content.length > max_content
|
|
92
|
+
markdown_content = markdown_content[0...max_content]
|
|
91
93
|
markdown_content += "\n\n[Content truncated due to length]"
|
|
92
94
|
end
|
|
93
95
|
|
|
94
96
|
# Process with AI model if LLM is enabled, otherwise return markdown
|
|
95
|
-
result = if
|
|
97
|
+
result = if llm_enabled
|
|
96
98
|
process_with_llm(markdown_content, prompt, normalized_url)
|
|
97
99
|
else
|
|
98
100
|
markdown_content
|
|
@@ -142,12 +144,13 @@ module SwarmSDK
|
|
|
142
144
|
require "faraday"
|
|
143
145
|
require "faraday/follow_redirects"
|
|
144
146
|
|
|
147
|
+
timeout = SwarmSDK.config.web_fetch_timeout
|
|
145
148
|
response = Faraday.new(url: url) do |conn|
|
|
146
149
|
conn.request(:url_encoded)
|
|
147
150
|
conn.response(:follow_redirects, limit: 5)
|
|
148
151
|
conn.adapter(Faraday.default_adapter)
|
|
149
|
-
conn.options.timeout =
|
|
150
|
-
conn.options.open_timeout =
|
|
152
|
+
conn.options.timeout = timeout
|
|
153
|
+
conn.options.open_timeout = timeout
|
|
151
154
|
end.get do |req|
|
|
152
155
|
req.headers["User-Agent"] = USER_AGENT
|
|
153
156
|
req.headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
|
@@ -166,7 +169,7 @@ module SwarmSDK
|
|
|
166
169
|
redirect_url: redirect_url,
|
|
167
170
|
}
|
|
168
171
|
rescue Faraday::TimeoutError
|
|
169
|
-
error("Request timed out after #{
|
|
172
|
+
error("Request timed out after #{SwarmSDK.config.web_fetch_timeout} seconds")
|
|
170
173
|
rescue Faraday::ConnectionFailed => e
|
|
171
174
|
error("Connection failed: #{e.message}")
|
|
172
175
|
rescue StandardError => e
|
|
@@ -193,17 +196,17 @@ module SwarmSDK
|
|
|
193
196
|
Please respond to the user's request based on the content above.
|
|
194
197
|
PROMPT
|
|
195
198
|
|
|
196
|
-
# Get
|
|
197
|
-
|
|
199
|
+
# Get config
|
|
200
|
+
sdk_config = SwarmSDK.config
|
|
198
201
|
|
|
199
202
|
# Build chat with configured provider and model
|
|
200
203
|
chat_params = {
|
|
201
|
-
model:
|
|
202
|
-
provider:
|
|
204
|
+
model: sdk_config.webfetch_model,
|
|
205
|
+
provider: sdk_config.webfetch_provider.to_sym,
|
|
203
206
|
}
|
|
204
|
-
chat_params[:base_url] =
|
|
207
|
+
chat_params[:base_url] = sdk_config.webfetch_base_url if sdk_config.webfetch_base_url
|
|
205
208
|
|
|
206
|
-
chat = RubyLLM.chat(**chat_params).with_params(max_tokens:
|
|
209
|
+
chat = RubyLLM.chat(**chat_params).with_params(max_tokens: sdk_config.webfetch_max_tokens)
|
|
207
210
|
|
|
208
211
|
response = chat.ask(full_prompt)
|
|
209
212
|
|
|
@@ -10,6 +10,13 @@ module SwarmSDK
|
|
|
10
10
|
class Write < RubyLLM::Tool
|
|
11
11
|
include PathResolver
|
|
12
12
|
|
|
13
|
+
# Factory pattern: declare what parameters this tool needs for instantiation
|
|
14
|
+
class << self
|
|
15
|
+
def creation_requirements
|
|
16
|
+
[:agent_name, :directory]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
13
20
|
description <<~DESC
|
|
14
21
|
Writes a file to the local filesystem.
|
|
15
22
|
This tool will overwrite the existing file if there is one at the provided path.
|
|
@@ -42,8 +49,7 @@ module SwarmSDK
|
|
|
42
49
|
# @param directory [String] Agent's working directory
|
|
43
50
|
def initialize(agent_name:, directory:)
|
|
44
51
|
super()
|
|
45
|
-
|
|
46
|
-
@directory = File.expand_path(directory)
|
|
52
|
+
initialize_agent_context(agent_name: agent_name, directory: directory)
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
# Override name to return simple "Write" instead of full class path
|
|
@@ -101,17 +107,6 @@ module SwarmSDK
|
|
|
101
107
|
rescue StandardError => e
|
|
102
108
|
error("Unexpected error writing file: #{e.class.name} - #{e.message}")
|
|
103
109
|
end
|
|
104
|
-
|
|
105
|
-
private
|
|
106
|
-
|
|
107
|
-
# Helper methods
|
|
108
|
-
def validation_error(message)
|
|
109
|
-
"<tool_use_error>InputValidationError: #{message}</tool_use_error>"
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def error(message)
|
|
113
|
-
"Error: #{message}"
|
|
114
|
-
end
|
|
115
110
|
end
|
|
116
111
|
end
|
|
117
112
|
end
|
data/lib/swarm_sdk/version.rb
CHANGED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
class Workflow
|
|
5
|
+
# Builder provides DSL for building multi-node workflows
|
|
6
|
+
# This is the top-level builder accessed via SwarmSDK.workflow
|
|
7
|
+
#
|
|
8
|
+
# The DSL enables:
|
|
9
|
+
# - Node-based workflow configuration
|
|
10
|
+
# - Agent delegation per node
|
|
11
|
+
# - Input/output transformers for data flow
|
|
12
|
+
# - Context preservation across nodes
|
|
13
|
+
#
|
|
14
|
+
# @example Multi-stage workflow
|
|
15
|
+
# workflow = SwarmSDK.workflow do
|
|
16
|
+
# name "Build Pipeline"
|
|
17
|
+
# start_node :planning
|
|
18
|
+
#
|
|
19
|
+
# agent :architect do
|
|
20
|
+
# model "gpt-5"
|
|
21
|
+
# prompt "You design systems"
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# agent :coder do
|
|
25
|
+
# model "gpt-4"
|
|
26
|
+
# prompt "You implement code"
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# node :planning do
|
|
30
|
+
# agent(:architect)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# node :implementation do
|
|
34
|
+
# agent(:coder)
|
|
35
|
+
# depends_on :planning
|
|
36
|
+
# end
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# workflow.execute("Build auth system")
|
|
40
|
+
class Builder < Builders::BaseBuilder
|
|
41
|
+
# Main entry point for DSL
|
|
42
|
+
#
|
|
43
|
+
# @example
|
|
44
|
+
# workflow = SwarmSDK.workflow do
|
|
45
|
+
# name "Pipeline"
|
|
46
|
+
# start_node :planning
|
|
47
|
+
# node(:planning) { agent(:architect) }
|
|
48
|
+
# end
|
|
49
|
+
class << self
|
|
50
|
+
def build(allow_filesystem_tools: nil, &block)
|
|
51
|
+
builder = new(allow_filesystem_tools: allow_filesystem_tools)
|
|
52
|
+
builder.instance_eval(&block)
|
|
53
|
+
builder.build_swarm
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def initialize(allow_filesystem_tools: nil)
|
|
58
|
+
super
|
|
59
|
+
@nodes = {}
|
|
60
|
+
@start_node = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Define a node (mini-swarm execution stage)
|
|
64
|
+
#
|
|
65
|
+
# Nodes enable multi-stage workflows where different agent teams
|
|
66
|
+
# collaborate in sequence. Each node is an independent swarm execution.
|
|
67
|
+
#
|
|
68
|
+
# @param name [Symbol] Node name
|
|
69
|
+
# @yield Block for node configuration
|
|
70
|
+
# @return [void]
|
|
71
|
+
#
|
|
72
|
+
# @example Solo agent node
|
|
73
|
+
# node :planning do
|
|
74
|
+
# agent(:architect)
|
|
75
|
+
# end
|
|
76
|
+
#
|
|
77
|
+
# @example Multi-agent node with delegation
|
|
78
|
+
# node :implementation do
|
|
79
|
+
# agent(:backend).delegates_to(:tester, :database)
|
|
80
|
+
# agent(:tester).delegates_to(:database)
|
|
81
|
+
# agent(:database)
|
|
82
|
+
# depends_on :planning
|
|
83
|
+
# end
|
|
84
|
+
def node(name, &block)
|
|
85
|
+
builder = Workflow::NodeBuilder.new(name)
|
|
86
|
+
builder.instance_eval(&block)
|
|
87
|
+
@nodes[name] = builder
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Set the starting node for workflow execution
|
|
91
|
+
#
|
|
92
|
+
# Required when nodes are defined. Specifies which node to execute first.
|
|
93
|
+
#
|
|
94
|
+
# @param name [Symbol] Name of starting node
|
|
95
|
+
# @return [void]
|
|
96
|
+
#
|
|
97
|
+
# @example
|
|
98
|
+
# start_node :planning
|
|
99
|
+
def start_node(name)
|
|
100
|
+
@start_node = name.to_sym
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Build the actual Workflow instance
|
|
104
|
+
def build_swarm # Returns Workflow despite method name
|
|
105
|
+
raise ConfigurationError, "Workflow name not set. Use: name 'My Workflow'" unless @swarm_name
|
|
106
|
+
raise ConfigurationError, "No nodes defined. Use: node :name { ... }" if @nodes.empty?
|
|
107
|
+
raise ConfigurationError, "start_node not set. Use: start_node :name" unless @start_node
|
|
108
|
+
|
|
109
|
+
# Validate filesystem tools BEFORE building
|
|
110
|
+
validate_all_agents_filesystem_tools if @all_agents_config
|
|
111
|
+
validate_agent_filesystem_tools
|
|
112
|
+
|
|
113
|
+
build_workflow
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Build a node-based workflow
|
|
119
|
+
#
|
|
120
|
+
# @return [Workflow] Configured workflow instance
|
|
121
|
+
def build_workflow
|
|
122
|
+
# Build agent definitions
|
|
123
|
+
agent_definitions = build_agent_definitions
|
|
124
|
+
|
|
125
|
+
# Create workflow
|
|
126
|
+
workflow = Workflow.new(
|
|
127
|
+
swarm_name: @swarm_name,
|
|
128
|
+
swarm_id: @swarm_id,
|
|
129
|
+
agent_definitions: agent_definitions,
|
|
130
|
+
nodes: @nodes,
|
|
131
|
+
start_node: @start_node,
|
|
132
|
+
scratchpad: @scratchpad,
|
|
133
|
+
allow_filesystem_tools: @allow_filesystem_tools,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Pass swarm registry config to workflow if external swarms registered
|
|
137
|
+
workflow.swarm_registry_config = @swarm_registry_config if @swarm_registry_config.any?
|
|
138
|
+
|
|
139
|
+
workflow
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|