swarm_memory 2.1.2 → 2.1.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 +4 -4
- data/lib/claude_swarm/claude_mcp_server.rb +1 -0
- data/lib/claude_swarm/cli.rb +5 -18
- data/lib/claude_swarm/configuration.rb +30 -19
- data/lib/claude_swarm/mcp_generator.rb +5 -10
- data/lib/claude_swarm/openai/chat_completion.rb +4 -12
- data/lib/claude_swarm/openai/executor.rb +3 -1
- data/lib/claude_swarm/openai/responses.rb +13 -32
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
- data/lib/swarm_cli/commands/run.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +14 -14
- data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
- data/lib/swarm_cli/interactive_repl.rb +11 -5
- data/lib/swarm_cli/ui/icons.rb +0 -23
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/adapters/base.rb +4 -4
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
- data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
- data/lib/swarm_memory/integration/cli_registration.rb +3 -2
- data/lib/swarm_memory/integration/sdk_plugin.rb +98 -12
- data/lib/swarm_memory/tools/memory_edit.rb +2 -2
- data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
- data/lib/swarm_memory/tools/memory_read.rb +3 -3
- data/lib/swarm_memory/version.rb +1 -1
- data/lib/swarm_memory.rb +6 -1
- data/lib/swarm_sdk/agent/builder.rb +91 -0
- data/lib/swarm_sdk/agent/chat.rb +540 -925
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +33 -79
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +147 -39
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -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 +22 -38
- 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 +8 -4
- data/lib/swarm_sdk/agent/context_manager.rb +6 -0
- data/lib/swarm_sdk/agent/definition.rb +79 -174
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +182 -0
- 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/configuration/parser.rb +353 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +100 -261
- data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
- 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 +199 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
- data/lib/swarm_sdk/log_collector.rb +192 -16
- data/lib/swarm_sdk/log_stream.rb +66 -8
- data/lib/swarm_sdk/model_aliases.json +4 -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/proc_helpers.rb +53 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
- data/lib/swarm_sdk/restore_result.rb +65 -0
- data/lib/swarm_sdk/snapshot.rb +156 -0
- data/lib/swarm_sdk/snapshot_from_events.rb +397 -0
- data/lib/swarm_sdk/state_restorer.rb +476 -0
- data/lib/swarm_sdk/state_snapshot.rb +334 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +428 -79
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
- data/lib/swarm_sdk/swarm/builder.rb +69 -407
- 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/swarm_registry_builder.rb +67 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +88 -149
- data/lib/swarm_sdk/swarm.rb +366 -631
- data/lib/swarm_sdk/swarm_loader.rb +145 -0
- data/lib/swarm_sdk/swarm_registry.rb +136 -0
- data/lib/swarm_sdk/tools/bash.rb +11 -3
- data/lib/swarm_sdk/tools/delegate.rb +127 -24
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +9 -1
- 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 +28 -18
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +53 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/think.rb +4 -1
- data/lib/swarm_sdk/tools/todo_write.rb +27 -8
- data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
- data/lib/swarm_sdk/tools/write.rb +8 -13
- data/lib/swarm_sdk/utils.rb +18 -0
- data/lib/swarm_sdk/validation_result.rb +33 -0
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/{node → workflow}/agent_config.rb +34 -9
- 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} +42 -21
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
- data/lib/swarm_sdk/workflow.rb +554 -0
- data/lib/swarm_sdk.rb +393 -22
- metadata +51 -16
- data/lib/swarm_memory/chat_extension.rb +0 -34
- data/lib/swarm_sdk/node_orchestrator.rb +0 -591
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -582
|
@@ -33,6 +33,7 @@ module SwarmSDK
|
|
|
33
33
|
@parameters = nil
|
|
34
34
|
@headers = nil
|
|
35
35
|
@coding_agent = nil
|
|
36
|
+
@disable_default_tools = nil
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# Set model for all agents
|
|
@@ -75,6 +76,15 @@ module SwarmSDK
|
|
|
75
76
|
@coding_agent = enabled
|
|
76
77
|
end
|
|
77
78
|
|
|
79
|
+
# Disable default tools for all agents
|
|
80
|
+
#
|
|
81
|
+
# @param value [Boolean, Array<Symbol>]
|
|
82
|
+
# - true: Disable ALL default tools
|
|
83
|
+
# - Array of symbols: Disable specific tools (e.g., [:Think, :TodoWrite])
|
|
84
|
+
def disable_default_tools(value)
|
|
85
|
+
@disable_default_tools = value
|
|
86
|
+
end
|
|
87
|
+
|
|
78
88
|
# Add tools that all agents will have
|
|
79
89
|
def tools(*tool_names)
|
|
80
90
|
@tools_list.concat(tool_names)
|
|
@@ -92,6 +102,7 @@ module SwarmSDK
|
|
|
92
102
|
:pre_tool_use,
|
|
93
103
|
:post_tool_use,
|
|
94
104
|
:user_prompt,
|
|
105
|
+
:agent_step,
|
|
95
106
|
:agent_stop,
|
|
96
107
|
:first_message,
|
|
97
108
|
:pre_delegation,
|
|
@@ -108,7 +119,11 @@ module SwarmSDK
|
|
|
108
119
|
|
|
109
120
|
# Configure permissions for all agents
|
|
110
121
|
#
|
|
111
|
-
#
|
|
122
|
+
# Supports two forms:
|
|
123
|
+
# 1. Block form (DSL): permissions do ... end
|
|
124
|
+
# 2. Direct hash (internal/YAML): set_permissions_hash(hash)
|
|
125
|
+
#
|
|
126
|
+
# @example Block form
|
|
112
127
|
# permissions do
|
|
113
128
|
# Write.allow_paths "tmp/**/*"
|
|
114
129
|
# Write.deny_paths "tmp/secrets/**"
|
|
@@ -118,6 +133,17 @@ module SwarmSDK
|
|
|
118
133
|
@permissions_config = PermissionsBuilder.build(&block)
|
|
119
134
|
end
|
|
120
135
|
|
|
136
|
+
# Set permissions directly from hash (for YAML translation)
|
|
137
|
+
#
|
|
138
|
+
# This is intentionally separate from permissions() to keep the DSL clean.
|
|
139
|
+
# Called by Configuration when translating YAML permissions.
|
|
140
|
+
#
|
|
141
|
+
# @param hash [Hash] Permissions configuration hash
|
|
142
|
+
# @return [void]
|
|
143
|
+
def permissions_hash=(hash)
|
|
144
|
+
@permissions_config = hash || {}
|
|
145
|
+
end
|
|
146
|
+
|
|
121
147
|
# Convert to hash for merging with agent configs
|
|
122
148
|
#
|
|
123
149
|
# @return [Hash] Configuration hash
|
|
@@ -131,6 +157,7 @@ module SwarmSDK
|
|
|
131
157
|
parameters: @parameters,
|
|
132
158
|
headers: @headers,
|
|
133
159
|
coding_agent: @coding_agent,
|
|
160
|
+
disable_default_tools: @disable_default_tools,
|
|
134
161
|
tools: @tools_list,
|
|
135
162
|
permissions: @permissions_config,
|
|
136
163
|
}.compact
|
|
@@ -28,7 +28,7 @@ module SwarmSDK
|
|
|
28
28
|
# end
|
|
29
29
|
#
|
|
30
30
|
# swarm.execute("Build auth API")
|
|
31
|
-
class Builder
|
|
31
|
+
class Builder < Builders::BaseBuilder
|
|
32
32
|
# Main entry point for DSL
|
|
33
33
|
#
|
|
34
34
|
# @example
|
|
@@ -37,27 +37,18 @@ module SwarmSDK
|
|
|
37
37
|
# agent :backend { ... }
|
|
38
38
|
# end
|
|
39
39
|
class << self
|
|
40
|
-
def build(&block)
|
|
41
|
-
builder = new
|
|
40
|
+
def build(allow_filesystem_tools: nil, &block)
|
|
41
|
+
builder = new(allow_filesystem_tools: allow_filesystem_tools)
|
|
42
42
|
builder.instance_eval(&block)
|
|
43
43
|
builder.build_swarm
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
def initialize
|
|
48
|
-
|
|
47
|
+
def initialize(allow_filesystem_tools: nil)
|
|
48
|
+
super
|
|
49
49
|
@lead_agent = nil
|
|
50
|
-
@agents = {}
|
|
51
|
-
@all_agents_config = nil
|
|
52
50
|
@swarm_hooks = []
|
|
53
|
-
@
|
|
54
|
-
@start_node = nil
|
|
55
|
-
@scratchpad_enabled = true # Default: enabled
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Set swarm name
|
|
59
|
-
def name(swarm_name)
|
|
60
|
-
@swarm_name = swarm_name
|
|
51
|
+
@observer_configs = []
|
|
61
52
|
end
|
|
62
53
|
|
|
63
54
|
# Set lead agent
|
|
@@ -65,57 +56,42 @@ module SwarmSDK
|
|
|
65
56
|
@lead_agent = agent_name
|
|
66
57
|
end
|
|
67
58
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# @param enabled [Boolean] Whether to enable scratchpad tools
|
|
71
|
-
def use_scratchpad(enabled)
|
|
72
|
-
@scratchpad_enabled = enabled
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Define an agent with fluent API or load from markdown content
|
|
76
|
-
#
|
|
77
|
-
# Supports two forms:
|
|
78
|
-
# 1. Inline DSL: agent :name do ... end
|
|
79
|
-
# 2. Markdown content: agent :name, <<~MD ... MD
|
|
59
|
+
# Define observer agent behavior
|
|
80
60
|
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
61
|
+
# Configures an agent to run in parallel with main execution,
|
|
62
|
+
# triggered by specific events. The block defines event handlers.
|
|
83
63
|
#
|
|
84
|
-
# @
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
# system_prompt "You build APIs"
|
|
88
|
-
# tools :Read, :Write
|
|
64
|
+
# @param agent_name [Symbol] Name of agent to use as observer (must be defined)
|
|
65
|
+
# @param options [Hash] Optional observer settings (timeout, max_concurrent, etc.)
|
|
66
|
+
# @yield Observer configuration block
|
|
89
67
|
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
68
|
+
# @example Basic observer
|
|
69
|
+
# observer :profiler do
|
|
70
|
+
# on :swarm_start do |event|
|
|
71
|
+
# "Analyze this prompt: #{event[:prompt]}"
|
|
92
72
|
# end
|
|
93
73
|
# end
|
|
94
74
|
#
|
|
95
|
-
# @example
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
load_agent_from_markdown_with_overrides(content, name, &block)
|
|
108
|
-
# Case 2: agent :name, <<~MD (markdown only)
|
|
109
|
-
elsif content.is_a?(String) && !block_given? && markdown_content?(content)
|
|
110
|
-
load_agent_from_markdown(content, name)
|
|
111
|
-
# Case 3: agent :name do ... end (inline DSL)
|
|
112
|
-
elsif block_given?
|
|
113
|
-
builder = Agent::Builder.new(name)
|
|
114
|
-
builder.instance_eval(&block)
|
|
115
|
-
@agents[name] = builder
|
|
116
|
-
else
|
|
117
|
-
raise ArgumentError, "Invalid agent definition. Use: agent :name { ... } OR agent :name, <<~MD ... MD OR agent :name, <<~MD do ... end"
|
|
75
|
+
# @example Observer with options
|
|
76
|
+
# observer :monitor, timeout: 120 do
|
|
77
|
+
# on :tool_call do |event|
|
|
78
|
+
# next unless event[:tool_name] == "Bash"
|
|
79
|
+
# "Check command: #{event[:arguments][:command]}"
|
|
80
|
+
# end
|
|
81
|
+
# end
|
|
82
|
+
def observer(agent_name, **options, &block)
|
|
83
|
+
unless @agents.key?(agent_name)
|
|
84
|
+
raise ConfigurationError,
|
|
85
|
+
"Observer agent '#{agent_name}' not defined. " \
|
|
86
|
+
"Define the agent first with `agent :#{agent_name} do ... end`"
|
|
118
87
|
end
|
|
88
|
+
|
|
89
|
+
config = Observer::Config.new(agent_name)
|
|
90
|
+
config.options.merge!(options) if options.any?
|
|
91
|
+
builder = Observer::Builder.new(agent_name, config)
|
|
92
|
+
builder.instance_eval(&block)
|
|
93
|
+
|
|
94
|
+
@observer_configs << config
|
|
119
95
|
end
|
|
120
96
|
|
|
121
97
|
# Add swarm-level hook (swarm_start, swarm_stop only)
|
|
@@ -136,197 +112,50 @@ module SwarmSDK
|
|
|
136
112
|
@swarm_hooks << { event: event, command: command, timeout: timeout, block: block }
|
|
137
113
|
end
|
|
138
114
|
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
# @example
|
|
142
|
-
# all_agents do
|
|
143
|
-
# tools :Read, :Write
|
|
144
|
-
#
|
|
145
|
-
# hook :pre_tool_use, matcher: "Write" do |ctx|
|
|
146
|
-
# # Validation for all agents
|
|
147
|
-
# end
|
|
148
|
-
# end
|
|
149
|
-
def all_agents(&block)
|
|
150
|
-
builder = AllAgentsBuilder.new
|
|
151
|
-
builder.instance_eval(&block)
|
|
152
|
-
@all_agents_config = builder
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
# Define a node (mini-swarm execution stage)
|
|
156
|
-
#
|
|
157
|
-
# Nodes enable multi-stage workflows where different agent teams
|
|
158
|
-
# collaborate in sequence. Each node is an independent swarm execution.
|
|
159
|
-
#
|
|
160
|
-
# @param name [Symbol] Node name
|
|
161
|
-
# @yield Block for node configuration
|
|
162
|
-
# @return [void]
|
|
163
|
-
#
|
|
164
|
-
# @example Solo agent node
|
|
165
|
-
# node :planning do
|
|
166
|
-
# agent(:architect)
|
|
167
|
-
# end
|
|
168
|
-
#
|
|
169
|
-
# @example Multi-agent node with delegation
|
|
170
|
-
# node :implementation do
|
|
171
|
-
# agent(:backend).delegates_to(:tester, :database)
|
|
172
|
-
# agent(:tester).delegates_to(:database)
|
|
173
|
-
# agent(:database)
|
|
174
|
-
# after :planning
|
|
175
|
-
# end
|
|
176
|
-
def node(name, &block)
|
|
177
|
-
builder = Node::Builder.new(name)
|
|
178
|
-
builder.instance_eval(&block)
|
|
179
|
-
@nodes[name] = builder
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# Set the starting node for workflow execution
|
|
183
|
-
#
|
|
184
|
-
# Required when nodes are defined. Specifies which node to execute first.
|
|
185
|
-
#
|
|
186
|
-
# @param name [Symbol] Name of starting node
|
|
187
|
-
# @return [void]
|
|
188
|
-
#
|
|
189
|
-
# @example
|
|
190
|
-
# start_node :planning
|
|
191
|
-
def start_node(name)
|
|
192
|
-
@start_node = name.to_sym
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Build the actual Swarm instance or NodeOrchestrator
|
|
115
|
+
# Build the actual Swarm instance
|
|
196
116
|
def build_swarm
|
|
197
117
|
raise ConfigurationError, "Swarm name not set. Use: name 'My Swarm'" unless @swarm_name
|
|
118
|
+
raise ConfigurationError, "No agents defined. Use: agent :name { ... }" if @agents.empty?
|
|
119
|
+
raise ConfigurationError, "Lead agent not set. Use: lead :agent_name" unless @lead_agent
|
|
198
120
|
|
|
199
|
-
#
|
|
200
|
-
if @
|
|
201
|
-
|
|
202
|
-
build_node_orchestrator
|
|
203
|
-
else
|
|
204
|
-
# Traditional single-swarm execution (requires agents and lead)
|
|
205
|
-
raise ConfigurationError, "No agents defined. Use: agent :name { ... }" if @agents.empty?
|
|
206
|
-
raise ConfigurationError, "Lead agent not set. Use: lead :agent_name" unless @lead_agent
|
|
121
|
+
# Validate filesystem tools BEFORE building
|
|
122
|
+
validate_all_agents_filesystem_tools if @all_agents_config
|
|
123
|
+
validate_agent_filesystem_tools
|
|
207
124
|
|
|
208
|
-
|
|
209
|
-
end
|
|
125
|
+
build_single_swarm
|
|
210
126
|
end
|
|
211
127
|
|
|
212
128
|
private
|
|
213
129
|
|
|
214
|
-
# Check if a string is markdown content (has frontmatter)
|
|
215
|
-
#
|
|
216
|
-
# @param str [String] String to check
|
|
217
|
-
# @return [Boolean] true if string contains markdown frontmatter
|
|
218
|
-
def markdown_content?(str)
|
|
219
|
-
str.start_with?("---") || str.include?("\n---\n")
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Load an agent from markdown content
|
|
223
|
-
#
|
|
224
|
-
# Returns a hash of the agent config (not a Definition yet) so that
|
|
225
|
-
# all_agents config can be applied later in the build process.
|
|
226
|
-
#
|
|
227
|
-
# @param content [String] Markdown content with frontmatter
|
|
228
|
-
# @param name_override [Symbol, nil] Optional name to override frontmatter name
|
|
229
|
-
# @return [void]
|
|
230
|
-
def load_agent_from_markdown(content, name_override = nil)
|
|
231
|
-
# Parse markdown content - will extract name from frontmatter if not overridden
|
|
232
|
-
definition = MarkdownParser.parse(content, name_override)
|
|
233
|
-
|
|
234
|
-
# Store the config hash (not Definition) so all_agents can be applied
|
|
235
|
-
# We'll wrap this in a special marker so we know it came from markdown
|
|
236
|
-
@agents[definition.name] = { __file_config__: definition.to_h }
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
# Load an agent from markdown content with DSL overrides
|
|
240
|
-
#
|
|
241
|
-
# This allows loading from a file and then overriding specific settings:
|
|
242
|
-
# agent :reviewer, File.read("reviewer.md") do
|
|
243
|
-
# provider :openai
|
|
244
|
-
# model "gpt-4o"
|
|
245
|
-
# end
|
|
246
|
-
#
|
|
247
|
-
# @param content [String] Markdown content with frontmatter
|
|
248
|
-
# @param name_override [Symbol, nil] Optional name to override frontmatter name
|
|
249
|
-
# @yield Block with DSL overrides
|
|
250
|
-
# @return [void]
|
|
251
|
-
def load_agent_from_markdown_with_overrides(content, name_override = nil, &block)
|
|
252
|
-
# Parse markdown content first
|
|
253
|
-
definition = MarkdownParser.parse(content, name_override)
|
|
254
|
-
|
|
255
|
-
# Create a builder with the markdown config
|
|
256
|
-
builder = Agent::Builder.new(definition.name)
|
|
257
|
-
|
|
258
|
-
# Apply markdown settings to builder (these become the base)
|
|
259
|
-
apply_definition_to_builder(builder, definition.to_h)
|
|
260
|
-
|
|
261
|
-
# Apply DSL overrides (these override the markdown settings)
|
|
262
|
-
builder.instance_eval(&block)
|
|
263
|
-
|
|
264
|
-
# Store the builder (not file config) so overrides are preserved
|
|
265
|
-
@agents[definition.name] = builder
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Apply agent definition hash to a builder
|
|
269
|
-
#
|
|
270
|
-
# @param builder [Agent::Builder] Builder to configure
|
|
271
|
-
# @param config [Hash] Configuration hash from definition
|
|
272
|
-
# @return [void]
|
|
273
|
-
def apply_definition_to_builder(builder, config)
|
|
274
|
-
builder.description(config[:description]) if config[:description]
|
|
275
|
-
builder.model(config[:model]) if config[:model]
|
|
276
|
-
builder.provider(config[:provider]) if config[:provider]
|
|
277
|
-
builder.base_url(config[:base_url]) if config[:base_url]
|
|
278
|
-
builder.api_version(config[:api_version]) if config[:api_version]
|
|
279
|
-
builder.context_window(config[:context_window]) if config[:context_window]
|
|
280
|
-
builder.system_prompt(config[:system_prompt]) if config[:system_prompt]
|
|
281
|
-
builder.directory(config[:directory]) if config[:directory]
|
|
282
|
-
builder.timeout(config[:timeout]) if config[:timeout]
|
|
283
|
-
builder.parameters(config[:parameters]) if config[:parameters]
|
|
284
|
-
builder.headers(config[:headers]) if config[:headers]
|
|
285
|
-
builder.coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
|
|
286
|
-
# Don't apply assume_model_exists from markdown - let DSL overrides or auto-enable handle it
|
|
287
|
-
# builder.assume_model_exists(config[:assume_model_exists]) unless config[:assume_model_exists].nil?
|
|
288
|
-
builder.bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
|
|
289
|
-
builder.disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
|
|
290
|
-
|
|
291
|
-
# Add tools from markdown
|
|
292
|
-
if config[:tools]&.any?
|
|
293
|
-
# Extract tool names from the tools array (which may be hashes with permissions)
|
|
294
|
-
tool_names = config[:tools].map do |tool|
|
|
295
|
-
tool.is_a?(Hash) ? tool[:name] : tool
|
|
296
|
-
end
|
|
297
|
-
builder.tools(*tool_names)
|
|
298
|
-
end
|
|
299
|
-
|
|
300
|
-
# Add delegates_to
|
|
301
|
-
builder.delegates_to(*config[:delegates_to]) if config[:delegates_to]&.any?
|
|
302
|
-
|
|
303
|
-
# Add MCP servers
|
|
304
|
-
config[:mcp_servers]&.each do |server|
|
|
305
|
-
builder.mcp_server(server[:name], **server.except(:name))
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
|
|
309
130
|
# Build a traditional single-swarm execution
|
|
310
131
|
#
|
|
311
132
|
# @return [Swarm] Configured swarm instance
|
|
312
133
|
def build_single_swarm
|
|
313
|
-
#
|
|
314
|
-
|
|
134
|
+
# Validate swarm_id is set if external swarms are registered (required for composable swarms)
|
|
135
|
+
if @swarm_registry_config.any? && @swarm_id.nil?
|
|
136
|
+
raise ConfigurationError, "Swarm id must be set using id(...) when using composable swarms"
|
|
137
|
+
end
|
|
315
138
|
|
|
316
|
-
#
|
|
317
|
-
|
|
139
|
+
# Create swarm using SDK (swarm_id auto-generates if nil)
|
|
140
|
+
swarm = Swarm.new(
|
|
141
|
+
name: @swarm_name,
|
|
142
|
+
swarm_id: @swarm_id,
|
|
143
|
+
scratchpad_mode: @scratchpad,
|
|
144
|
+
allow_filesystem_tools: @allow_filesystem_tools,
|
|
145
|
+
)
|
|
318
146
|
|
|
319
|
-
#
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
Agent::Definition.new(agent_name, agent_builder_or_config[:__file_config__])
|
|
325
|
-
else
|
|
326
|
-
# Builder object (from inline DSL) - convert to definition
|
|
327
|
-
agent_builder_or_config.to_definition
|
|
147
|
+
# Setup swarm registry if external swarms are registered
|
|
148
|
+
if @swarm_registry_config.any?
|
|
149
|
+
registry = SwarmRegistry.new(parent_swarm_id: @swarm_id)
|
|
150
|
+
@swarm_registry_config.each do |reg|
|
|
151
|
+
registry.register(reg[:name], source: reg[:source], keep_context: reg[:keep_context])
|
|
328
152
|
end
|
|
153
|
+
swarm.swarm_registry = registry
|
|
154
|
+
end
|
|
329
155
|
|
|
156
|
+
# Build agent definitions and add to swarm
|
|
157
|
+
agent_definitions = build_agent_definitions
|
|
158
|
+
agent_definitions.each_value do |definition|
|
|
330
159
|
swarm.add_agent(definition)
|
|
331
160
|
end
|
|
332
161
|
|
|
@@ -334,191 +163,21 @@ module SwarmSDK
|
|
|
334
163
|
swarm.lead = @lead_agent
|
|
335
164
|
|
|
336
165
|
# Apply swarm hooks (Ruby blocks)
|
|
337
|
-
# These are swarm-level hooks (swarm_start, swarm_stop)
|
|
338
166
|
@swarm_hooks.each do |hook_config|
|
|
339
167
|
apply_swarm_hook(swarm, hook_config)
|
|
340
168
|
end
|
|
341
169
|
|
|
342
170
|
# Apply all_agents hooks (Ruby blocks)
|
|
343
|
-
# These become swarm-level default callbacks that apply to all agents
|
|
344
171
|
@all_agents_config&.hooks&.each do |hook_config|
|
|
345
172
|
apply_all_agents_hook(swarm, hook_config)
|
|
346
173
|
end
|
|
347
174
|
|
|
348
|
-
#
|
|
349
|
-
|
|
350
|
-
# This ensures they're applied at the right time, after LogStream is set up
|
|
175
|
+
# Add observer configurations to swarm
|
|
176
|
+
@observer_configs.each { |c| swarm.add_observer_config(c) }
|
|
351
177
|
|
|
352
178
|
swarm
|
|
353
179
|
end
|
|
354
180
|
|
|
355
|
-
# Build a node-based workflow orchestrator
|
|
356
|
-
#
|
|
357
|
-
# @return [NodeOrchestrator] Configured orchestrator
|
|
358
|
-
def build_node_orchestrator
|
|
359
|
-
raise ConfigurationError, "start_node required when nodes are defined. Use: start_node :name" unless @start_node
|
|
360
|
-
|
|
361
|
-
# Merge all_agents config into each agent (applies to all nodes)
|
|
362
|
-
merge_all_agents_config_into_agents if @all_agents_config
|
|
363
|
-
|
|
364
|
-
# Build agent definitions
|
|
365
|
-
# Handle both Agent::Builder (inline DSL) and file configs (from files)
|
|
366
|
-
agent_definitions = {}
|
|
367
|
-
@agents.each do |agent_name, agent_builder_or_config|
|
|
368
|
-
agent_definitions[agent_name] = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
|
|
369
|
-
# File-loaded agent config (with all_agents merged)
|
|
370
|
-
Agent::Definition.new(agent_name, agent_builder_or_config[:__file_config__])
|
|
371
|
-
else
|
|
372
|
-
# Builder object (from inline DSL) - convert to definition
|
|
373
|
-
agent_builder_or_config.to_definition
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
# Create node orchestrator
|
|
378
|
-
NodeOrchestrator.new(
|
|
379
|
-
swarm_name: @swarm_name,
|
|
380
|
-
agent_definitions: agent_definitions,
|
|
381
|
-
nodes: @nodes,
|
|
382
|
-
start_node: @start_node,
|
|
383
|
-
scratchpad_enabled: @scratchpad_enabled,
|
|
384
|
-
)
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
# Merge all_agents configuration into each agent
|
|
388
|
-
#
|
|
389
|
-
# All_agents values are used as defaults - agent-specific values override.
|
|
390
|
-
# This applies to both inline DSL agents (Builder) and file-loaded agents (config hash).
|
|
391
|
-
#
|
|
392
|
-
# @return [void]
|
|
393
|
-
def merge_all_agents_config_into_agents
|
|
394
|
-
return unless @all_agents_config
|
|
395
|
-
|
|
396
|
-
all_agents_hash = @all_agents_config.to_h
|
|
397
|
-
|
|
398
|
-
@agents.each_value do |agent_builder_or_config|
|
|
399
|
-
if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
|
|
400
|
-
# File-loaded agent - merge into the config hash
|
|
401
|
-
file_config = agent_builder_or_config[:__file_config__]
|
|
402
|
-
|
|
403
|
-
# Merge all_agents into file config (file config overrides)
|
|
404
|
-
# Use same merge strategy as Configuration class
|
|
405
|
-
merged_config = merge_all_agents_into_config(all_agents_hash, file_config)
|
|
406
|
-
|
|
407
|
-
# Update the stored config
|
|
408
|
-
agent_builder_or_config[:__file_config__] = merged_config
|
|
409
|
-
else
|
|
410
|
-
# Builder object (inline DSL agent)
|
|
411
|
-
agent_builder = agent_builder_or_config
|
|
412
|
-
|
|
413
|
-
# Apply all_agents defaults that haven't been set at agent level
|
|
414
|
-
# Agent values override all_agents values
|
|
415
|
-
apply_all_agents_defaults(agent_builder, all_agents_hash)
|
|
416
|
-
|
|
417
|
-
# Merge tools (prepend all_agents tools)
|
|
418
|
-
all_agents_tools = @all_agents_config.tools_list
|
|
419
|
-
agent_builder.prepend_tools(*all_agents_tools) if all_agents_tools.any?
|
|
420
|
-
|
|
421
|
-
# Pass all_agents permissions as default_permissions
|
|
422
|
-
if @all_agents_config.permissions_config.any?
|
|
423
|
-
agent_builder.default_permissions = @all_agents_config.permissions_config
|
|
424
|
-
end
|
|
425
|
-
end
|
|
426
|
-
end
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
# Merge all_agents config into file-loaded agent config
|
|
430
|
-
#
|
|
431
|
-
# Follows same merge strategy as Configuration class:
|
|
432
|
-
# - Arrays (tools, delegates_to): Concatenate (all_agents + file)
|
|
433
|
-
# - Hashes (parameters, headers): Merge (file values override)
|
|
434
|
-
# - Scalars (model, provider, etc.): File overrides
|
|
435
|
-
#
|
|
436
|
-
# @param all_agents_hash [Hash] All_agents configuration
|
|
437
|
-
# @param file_config [Hash] File-loaded agent configuration
|
|
438
|
-
# @return [Hash] Merged configuration
|
|
439
|
-
def merge_all_agents_into_config(all_agents_hash, file_config)
|
|
440
|
-
merged = all_agents_hash.dup
|
|
441
|
-
|
|
442
|
-
file_config.each do |key, value|
|
|
443
|
-
case key
|
|
444
|
-
when :tools
|
|
445
|
-
# Concatenate tools: all_agents.tools + file.tools
|
|
446
|
-
merged[:tools] = Array(merged[:tools]) + Array(value)
|
|
447
|
-
when :delegates_to
|
|
448
|
-
# Concatenate delegates_to
|
|
449
|
-
merged[:delegates_to] = Array(merged[:delegates_to]) + Array(value)
|
|
450
|
-
when :parameters
|
|
451
|
-
# Merge parameters: file values override all_agents
|
|
452
|
-
merged[:parameters] = (merged[:parameters] || {}).merge(value || {})
|
|
453
|
-
when :headers
|
|
454
|
-
# Merge headers: file values override all_agents
|
|
455
|
-
merged[:headers] = (merged[:headers] || {}).merge(value || {})
|
|
456
|
-
else
|
|
457
|
-
# For everything else, file value overrides all_agents value
|
|
458
|
-
merged[key] = value
|
|
459
|
-
end
|
|
460
|
-
end
|
|
461
|
-
|
|
462
|
-
# Pass all_agents permissions as default_permissions
|
|
463
|
-
if all_agents_hash[:permissions] && !merged[:default_permissions]
|
|
464
|
-
merged[:default_permissions] = all_agents_hash[:permissions]
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
merged
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
# Apply all_agents defaults to an agent builder
|
|
471
|
-
#
|
|
472
|
-
# Only sets values that haven't been explicitly set at the agent level.
|
|
473
|
-
# This implements the override semantics: agent values take precedence.
|
|
474
|
-
#
|
|
475
|
-
# @param agent_builder [Agent::Builder] The agent builder to configure
|
|
476
|
-
# @param all_agents_hash [Hash] All_agents configuration
|
|
477
|
-
# @return [void]
|
|
478
|
-
def apply_all_agents_defaults(agent_builder, all_agents_hash)
|
|
479
|
-
# Model: only set if agent hasn't explicitly set it
|
|
480
|
-
if all_agents_hash[:model] && !agent_builder.model_set?
|
|
481
|
-
agent_builder.model(all_agents_hash[:model])
|
|
482
|
-
end
|
|
483
|
-
|
|
484
|
-
# Provider: only set if agent hasn't set it
|
|
485
|
-
if all_agents_hash[:provider] && !agent_builder.provider_set?
|
|
486
|
-
agent_builder.provider(all_agents_hash[:provider])
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
# Base URL: only set if agent hasn't set it
|
|
490
|
-
if all_agents_hash[:base_url] && !agent_builder.base_url_set?
|
|
491
|
-
agent_builder.base_url(all_agents_hash[:base_url])
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# API Version: only set if agent hasn't set it
|
|
495
|
-
if all_agents_hash[:api_version] && !agent_builder.api_version_set?
|
|
496
|
-
agent_builder.api_version(all_agents_hash[:api_version])
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
# Timeout: only set if agent hasn't set it
|
|
500
|
-
if all_agents_hash[:timeout] && !agent_builder.timeout_set?
|
|
501
|
-
agent_builder.timeout(all_agents_hash[:timeout])
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
# Parameters: merge (all_agents + agent, agent values override)
|
|
505
|
-
if all_agents_hash[:parameters]
|
|
506
|
-
merged_params = all_agents_hash[:parameters].merge(agent_builder.parameters)
|
|
507
|
-
agent_builder.parameters(merged_params)
|
|
508
|
-
end
|
|
509
|
-
|
|
510
|
-
# Headers: merge (all_agents + agent, agent values override)
|
|
511
|
-
if all_agents_hash[:headers]
|
|
512
|
-
merged_headers = all_agents_hash[:headers].merge(agent_builder.headers)
|
|
513
|
-
agent_builder.headers(merged_headers)
|
|
514
|
-
end
|
|
515
|
-
|
|
516
|
-
# Coding_agent: only set if agent hasn't set it
|
|
517
|
-
if !all_agents_hash[:coding_agent].nil? && !agent_builder.coding_agent_set?
|
|
518
|
-
agent_builder.coding_agent(all_agents_hash[:coding_agent])
|
|
519
|
-
end
|
|
520
|
-
end
|
|
521
|
-
|
|
522
181
|
def apply_swarm_hook(swarm, config)
|
|
523
182
|
event = config[:event]
|
|
524
183
|
|
|
@@ -564,7 +223,7 @@ module SwarmSDK
|
|
|
564
223
|
end
|
|
565
224
|
|
|
566
225
|
def build_hook_input(context, event)
|
|
567
|
-
# Build JSON input for shell hooks
|
|
226
|
+
# Build JSON input for shell hooks
|
|
568
227
|
base = { event: event.to_s }
|
|
569
228
|
|
|
570
229
|
case event
|
|
@@ -583,5 +242,8 @@ module SwarmSDK
|
|
|
583
242
|
end
|
|
584
243
|
end
|
|
585
244
|
end
|
|
245
|
+
|
|
246
|
+
# Helper class for swarms block in DSL (kept in this file for reference)
|
|
247
|
+
# Actual implementation is in swarm_registry_builder.rb for Zeitwerk
|
|
586
248
|
end
|
|
587
249
|
end
|