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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ebb25752a42e174408e197c5b24d94ebfbd2a57cfd5a26101e839f2375e54b8f
|
|
4
|
+
data.tar.gz: 9558c9e19cfb486872d6cee1b445506e9c599f8d4a767670743f3a3f1481fb49
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a5d16f99c9aeb5fca71dfed900a8e44337d2fa5a1ee58fbec71e25f6825091973c0ef849a9c28cd88eb3bfdac9a481bbe743de2fd034fe3f124c8ea0aa23c5f1
|
|
7
|
+
data.tar.gz: b2ce4c330d45730c723a51786301d39dcc8c3b57eb19f65f6510b5b3027620d3ab214df201e8237b2b0e36f82c726b04e233cbb0266187aa7743446e1215b854
|
|
@@ -36,6 +36,7 @@ module ClaudeSwarm
|
|
|
36
36
|
openai_token_env: instance_config[:openai_token_env],
|
|
37
37
|
base_url: instance_config[:base_url],
|
|
38
38
|
reasoning_effort: instance_config[:reasoning_effort],
|
|
39
|
+
zdr: instance_config[:zdr],
|
|
39
40
|
)
|
|
40
41
|
else
|
|
41
42
|
# Default Claude behavior - always use SDK
|
data/lib/claude_swarm/cli.rb
CHANGED
|
@@ -172,6 +172,10 @@ module ClaudeSwarm
|
|
|
172
172
|
method_option :reasoning_effort,
|
|
173
173
|
type: :string,
|
|
174
174
|
desc: "Reasoning effort for OpenAI models"
|
|
175
|
+
method_option :zdr,
|
|
176
|
+
type: :boolean,
|
|
177
|
+
default: false,
|
|
178
|
+
desc: "Enable ZDR for OpenAI models"
|
|
175
179
|
def mcp_serve
|
|
176
180
|
# Validate reasoning_effort if provided
|
|
177
181
|
if options[:reasoning_effort]
|
|
@@ -181,14 +185,6 @@ module ClaudeSwarm
|
|
|
181
185
|
exit(1)
|
|
182
186
|
end
|
|
183
187
|
|
|
184
|
-
# Validate it's used with an o-series model
|
|
185
|
-
model = options[:model]
|
|
186
|
-
unless model&.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
187
|
-
error("reasoning_effort is only supported for o-series models (o1, o1 Preview, o1-mini, o1-pro, o3, o3-mini, o3-pro, o3-deep-research, o4-mini, o4-mini-deep-research, etc.)")
|
|
188
|
-
error("Current model: #{model}")
|
|
189
|
-
exit(1)
|
|
190
|
-
end
|
|
191
|
-
|
|
192
188
|
# Validate the value
|
|
193
189
|
unless ClaudeSwarm::Configuration::VALID_REASONING_EFFORTS.include?(options[:reasoning_effort])
|
|
194
190
|
error("reasoning_effort must be 'low', 'medium', or 'high'")
|
|
@@ -196,16 +192,6 @@ module ClaudeSwarm
|
|
|
196
192
|
end
|
|
197
193
|
end
|
|
198
194
|
|
|
199
|
-
# Validate temperature is not used with o-series models
|
|
200
|
-
if options[:temperature] && options[:provider] == "openai"
|
|
201
|
-
model = options[:model]
|
|
202
|
-
if model&.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
203
|
-
error("temperature parameter is not supported for o-series models (#{model})")
|
|
204
|
-
error("O-series models use deterministic reasoning and don't accept temperature settings")
|
|
205
|
-
exit(1)
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
|
|
209
195
|
instance_config = {
|
|
210
196
|
name: options[:name],
|
|
211
197
|
directory: options[:directory],
|
|
@@ -226,6 +212,7 @@ module ClaudeSwarm
|
|
|
226
212
|
openai_token_env: options[:openai_token_env],
|
|
227
213
|
base_url: options[:base_url],
|
|
228
214
|
reasoning_effort: options[:reasoning_effort],
|
|
215
|
+
zdr: options[:zdr],
|
|
229
216
|
}
|
|
230
217
|
|
|
231
218
|
begin
|
|
@@ -4,15 +4,13 @@ module ClaudeSwarm
|
|
|
4
4
|
class Configuration
|
|
5
5
|
# Frozen constants for validation
|
|
6
6
|
VALID_PROVIDERS = ["claude", "openai"].freeze
|
|
7
|
-
OPENAI_SPECIFIC_FIELDS = ["temperature", "api_version", "openai_token_env", "base_url", "reasoning_effort"].freeze
|
|
7
|
+
OPENAI_SPECIFIC_FIELDS = ["temperature", "api_version", "openai_token_env", "base_url", "reasoning_effort", "zdr"].freeze
|
|
8
8
|
VALID_API_VERSIONS = ["chat_completion", "responses"].freeze
|
|
9
9
|
VALID_REASONING_EFFORTS = ["low", "medium", "high"].freeze
|
|
10
10
|
|
|
11
11
|
# Regex patterns
|
|
12
12
|
ENV_VAR_PATTERN = /\$\{([^}]+)\}/
|
|
13
13
|
ENV_VAR_WITH_DEFAULT_PATTERN = /\$\{([^:}]+)(:=([^}]*))?\}/
|
|
14
|
-
O_SERIES_MODEL_PATTERN = /^(o\d+(\s+(Preview|preview))?(-pro|-mini|-deep-research|-mini-deep-research)?|gpt-5(-mini|-nano)?)$/
|
|
15
|
-
|
|
16
14
|
attr_reader :config, :config_path, :swarm, :swarm_name, :main_instance, :instances, :base_dir
|
|
17
15
|
|
|
18
16
|
def initialize(config_path, base_dir: nil, options: {})
|
|
@@ -69,19 +67,43 @@ module ClaudeSwarm
|
|
|
69
67
|
validate_directories unless has_before_commands?
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
def interpolate_env_vars!(obj)
|
|
70
|
+
def interpolate_env_vars!(obj, path = [])
|
|
73
71
|
case obj
|
|
74
72
|
when String
|
|
75
|
-
|
|
73
|
+
# Skip interpolation for any values inside MCP configurations
|
|
74
|
+
# Check if we're inside an mcps array element (path like: [..., "instances", <name>, "mcps", <index>, ...])
|
|
75
|
+
if in_mcp_config?(path)
|
|
76
|
+
obj
|
|
77
|
+
else
|
|
78
|
+
interpolate_env_string(obj)
|
|
79
|
+
end
|
|
76
80
|
when Hash
|
|
77
|
-
obj.
|
|
81
|
+
obj.each do |key, value|
|
|
82
|
+
obj[key] = interpolate_env_vars!(value, path + [key])
|
|
83
|
+
end
|
|
84
|
+
obj
|
|
78
85
|
when Array
|
|
79
|
-
obj.map
|
|
86
|
+
obj.map!.with_index { |v, i| interpolate_env_vars!(v, path + [i]) }
|
|
80
87
|
else
|
|
81
88
|
obj
|
|
82
89
|
end
|
|
83
90
|
end
|
|
84
91
|
|
|
92
|
+
def in_mcp_config?(path)
|
|
93
|
+
# Check if we're inside an MCP configuration
|
|
94
|
+
# Pattern: [..., "instances", instance_name, "mcps", index, ...]
|
|
95
|
+
return false if path.size < 4
|
|
96
|
+
|
|
97
|
+
# Find the position of "mcps" in the path
|
|
98
|
+
mcps_index = path.rindex("mcps")
|
|
99
|
+
return false unless mcps_index
|
|
100
|
+
|
|
101
|
+
# Check if this is under instances and followed by an array index
|
|
102
|
+
return false if mcps_index < 2
|
|
103
|
+
|
|
104
|
+
path[mcps_index - 2] == "instances" && path[mcps_index + 1].is_a?(Integer)
|
|
105
|
+
end
|
|
106
|
+
|
|
85
107
|
def interpolate_env_string(str)
|
|
86
108
|
str.gsub(ENV_VAR_WITH_DEFAULT_PATTERN) do |_match|
|
|
87
109
|
env_var = Regexp.last_match(1)
|
|
@@ -141,7 +163,6 @@ module ClaudeSwarm
|
|
|
141
163
|
|
|
142
164
|
# Parse provider (optional, defaults to claude)
|
|
143
165
|
provider = config["provider"]
|
|
144
|
-
model = config["model"]
|
|
145
166
|
|
|
146
167
|
# Validate provider value if specified
|
|
147
168
|
if provider && !VALID_PROVIDERS.include?(provider)
|
|
@@ -159,17 +180,6 @@ module ClaudeSwarm
|
|
|
159
180
|
unless VALID_REASONING_EFFORTS.include?(config["reasoning_effort"])
|
|
160
181
|
raise Error, "Instance '#{name}' has invalid reasoning_effort '#{config["reasoning_effort"]}'. Must be 'low', 'medium', or 'high'"
|
|
161
182
|
end
|
|
162
|
-
|
|
163
|
-
# Validate it's only used with o-series or gpt-5 models
|
|
164
|
-
# Support patterns like: o1, o1-mini, o1-pro, o1 Preview, o3-deep-research, o4-mini-deep-research, gpt-5, gpt-5-mini, gpt-5-nano, etc.
|
|
165
|
-
unless model&.match?(O_SERIES_MODEL_PATTERN)
|
|
166
|
-
raise Error, "Instance '#{name}' has reasoning_effort but model '#{model}' is not an o-series or gpt-5 model (o1, o1 Preview, o1-mini, o1-pro, o3, o3-mini, o3-pro, o3-deep-research, o4-mini, o4-mini-deep-research, gpt-5, gpt-5-mini, gpt-5-nano, etc.)"
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Validate temperature is not used with o-series or gpt-5 models when provider is openai
|
|
171
|
-
if provider == "openai" && config["temperature"] && model&.match?(O_SERIES_MODEL_PATTERN)
|
|
172
|
-
raise Error, "Instance '#{name}' has temperature parameter but model '#{model}' is an o-series or gpt-5 model. O-series and gpt-5 models use deterministic reasoning and don't accept temperature settings"
|
|
173
183
|
end
|
|
174
184
|
|
|
175
185
|
# Validate OpenAI-specific fields only when provider is not "openai"
|
|
@@ -222,6 +232,7 @@ module ClaudeSwarm
|
|
|
222
232
|
instance_config[:openai_token_env] = config["openai_token_env"] || "OPENAI_API_KEY"
|
|
223
233
|
instance_config[:base_url] = config["base_url"]
|
|
224
234
|
instance_config[:reasoning_effort] = config["reasoning_effort"] if config["reasoning_effort"]
|
|
235
|
+
instance_config[:zdr] = config["zdr"] if config.key?("zdr")
|
|
225
236
|
# Default vibe to true for OpenAI instances if not specified
|
|
226
237
|
instance_config[:vibe] = true if config["vibe"].nil?
|
|
227
238
|
elsif config["vibe"].nil?
|
|
@@ -174,6 +174,7 @@ module ClaudeSwarm
|
|
|
174
174
|
args.push("--api-version", instance[:api_version]) if instance[:api_version]
|
|
175
175
|
args.push("--openai-token-env", instance[:openai_token_env]) if instance[:openai_token_env]
|
|
176
176
|
args.push("--base-url", instance[:base_url]) if instance[:base_url]
|
|
177
|
+
args.push("--zdr", instance[:zdr].to_s) if instance.key?(:zdr)
|
|
177
178
|
end
|
|
178
179
|
end
|
|
179
180
|
|
|
@@ -183,21 +184,17 @@ module ClaudeSwarm
|
|
|
183
184
|
args.push("--claude-session-id", claude_session_id) if claude_session_id
|
|
184
185
|
end
|
|
185
186
|
|
|
186
|
-
# Capture environment variables needed for
|
|
187
|
-
#
|
|
187
|
+
# Capture environment variables needed for running claude-swarm
|
|
188
|
+
# We intentionally exclude Bundler variables to ensure we use the system-installed gem
|
|
188
189
|
required_env = {}
|
|
189
190
|
|
|
190
|
-
#
|
|
191
|
-
ENV.each do |k, v|
|
|
192
|
-
required_env[k] = v if k.start_with?("BUNDLE_")
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Claude Swarm-specific variables
|
|
191
|
+
# Claude Swarm-specific variables (always needed)
|
|
196
192
|
ENV.each do |k, v|
|
|
197
193
|
required_env[k] = v if k.start_with?("CLAUDE_SWARM_")
|
|
198
194
|
end
|
|
199
195
|
|
|
200
196
|
# Ruby-specific variables that MCP servers need
|
|
197
|
+
# Exclude RUBYOPT and RUBYLIB to avoid Bundler interference
|
|
201
198
|
[
|
|
202
199
|
"RUBY_ROOT",
|
|
203
200
|
"RUBY_ENGINE",
|
|
@@ -205,8 +202,6 @@ module ClaudeSwarm
|
|
|
205
202
|
"GEM_ROOT",
|
|
206
203
|
"GEM_HOME",
|
|
207
204
|
"GEM_PATH",
|
|
208
|
-
"RUBYOPT",
|
|
209
|
-
"RUBYLIB",
|
|
210
205
|
"PATH",
|
|
211
206
|
].each do |key|
|
|
212
207
|
required_env[key] = ENV[key] if ENV[key]
|
|
@@ -5,7 +5,7 @@ module ClaudeSwarm
|
|
|
5
5
|
class ChatCompletion
|
|
6
6
|
MAX_TURNS_WITH_TOOLS = 100_000 # virtually infinite
|
|
7
7
|
|
|
8
|
-
def initialize(openai_client:, mcp_client:, available_tools:, executor:, instance_name:, model:, temperature: nil, reasoning_effort: nil)
|
|
8
|
+
def initialize(openai_client:, mcp_client:, available_tools:, executor:, instance_name:, model:, temperature: nil, reasoning_effort: nil, zdr: false)
|
|
9
9
|
@openai_client = openai_client
|
|
10
10
|
@mcp_client = mcp_client
|
|
11
11
|
@available_tools = available_tools
|
|
@@ -14,6 +14,7 @@ module ClaudeSwarm
|
|
|
14
14
|
@model = model
|
|
15
15
|
@temperature = temperature
|
|
16
16
|
@reasoning_effort = reasoning_effort
|
|
17
|
+
@zdr = zdr # Not used in chat_completion API, but kept for compatibility
|
|
17
18
|
@conversation_messages = []
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -67,17 +68,8 @@ module ClaudeSwarm
|
|
|
67
68
|
messages: messages,
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if @temperature && !@model.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
73
|
-
parameters[:temperature] = @temperature
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Only add reasoning_effort for o-series models
|
|
77
|
-
# reasoning_effort is only supported by o-series models: o1, o1 Preview, o1-mini, o1-pro, o3, o3-mini, o3-pro, o3-deep-research, o4-mini, o4-mini-deep-research, etc.
|
|
78
|
-
if @reasoning_effort && @model.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
79
|
-
parameters[:reasoning_effort] = @reasoning_effort
|
|
80
|
-
end
|
|
71
|
+
parameters[:temperature] = @temperature if @temperature
|
|
72
|
+
parameters[:reasoning_effort] = @reasoning_effort if @reasoning_effort
|
|
81
73
|
|
|
82
74
|
# Add tools if available
|
|
83
75
|
parameters[:tools] = @mcp_client.to_openai_tools if @available_tools&.any? && @mcp_client
|
|
@@ -31,7 +31,7 @@ module ClaudeSwarm
|
|
|
31
31
|
instance_name: nil, instance_id: nil, calling_instance: nil, calling_instance_id: nil,
|
|
32
32
|
claude_session_id: nil, additional_directories: [], debug: false,
|
|
33
33
|
temperature: nil, api_version: "chat_completion", openai_token_env: "OPENAI_API_KEY",
|
|
34
|
-
base_url: nil, reasoning_effort: nil)
|
|
34
|
+
base_url: nil, reasoning_effort: nil, zdr: false)
|
|
35
35
|
# Call parent initializer for common attributes
|
|
36
36
|
super(
|
|
37
37
|
working_directory: working_directory,
|
|
@@ -52,6 +52,7 @@ module ClaudeSwarm
|
|
|
52
52
|
@api_version = api_version
|
|
53
53
|
@base_url = base_url
|
|
54
54
|
@reasoning_effort = reasoning_effort
|
|
55
|
+
@zdr = zdr
|
|
55
56
|
|
|
56
57
|
# Conversation state for maintaining context
|
|
57
58
|
@conversation_messages = []
|
|
@@ -162,6 +163,7 @@ module ClaudeSwarm
|
|
|
162
163
|
model: @model,
|
|
163
164
|
temperature: @temperature,
|
|
164
165
|
reasoning_effort: @reasoning_effort,
|
|
166
|
+
zdr: @zdr,
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
if @api_version == "responses"
|
|
@@ -5,7 +5,7 @@ module ClaudeSwarm
|
|
|
5
5
|
class Responses
|
|
6
6
|
MAX_TURNS_WITH_TOOLS = 100_000 # virtually infinite
|
|
7
7
|
|
|
8
|
-
def initialize(openai_client:, mcp_client:, available_tools:, executor:, instance_name:, model:, temperature: nil, reasoning_effort: nil)
|
|
8
|
+
def initialize(openai_client:, mcp_client:, available_tools:, executor:, instance_name:, model:, temperature: nil, reasoning_effort: nil, zdr: false)
|
|
9
9
|
@openai_client = openai_client
|
|
10
10
|
@mcp_client = mcp_client
|
|
11
11
|
@available_tools = available_tools
|
|
@@ -14,6 +14,7 @@ module ClaudeSwarm
|
|
|
14
14
|
@model = model
|
|
15
15
|
@temperature = temperature
|
|
16
16
|
@reasoning_effort = reasoning_effort
|
|
17
|
+
@zdr = zdr
|
|
17
18
|
@system_prompt = nil
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -46,17 +47,8 @@ module ClaudeSwarm
|
|
|
46
47
|
model: @model,
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
unless @model.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
52
|
-
parameters[:temperature] = @temperature
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Only add reasoning effort for o-series models
|
|
56
|
-
# reasoning is only supported by o-series models: o1, o1 Preview, o1-mini, o1-pro, o3, o3-mini, o3-pro, o3-deep-research, o4-mini, o4-mini-deep-research, etc.
|
|
57
|
-
if @reasoning_effort && @model.match?(ClaudeSwarm::Configuration::O_SERIES_MODEL_PATTERN)
|
|
58
|
-
parameters[:reasoning] = { effort: @reasoning_effort }
|
|
59
|
-
end
|
|
50
|
+
parameters[:temperature] = @temperature if @temperature
|
|
51
|
+
parameters[:reasoning] = { effort: @reasoning_effort } if @reasoning_effort
|
|
60
52
|
|
|
61
53
|
# On first call, use string input (can include system prompt)
|
|
62
54
|
# On subsequent calls with function results, use array input
|
|
@@ -67,6 +59,7 @@ module ClaudeSwarm
|
|
|
67
59
|
else
|
|
68
60
|
input
|
|
69
61
|
end
|
|
62
|
+
conversation_array << { role: "user", content: parameters[:input] }
|
|
70
63
|
else
|
|
71
64
|
# Follow-up call with conversation array (function calls + outputs)
|
|
72
65
|
parameters[:input] = conversation_array
|
|
@@ -79,8 +72,8 @@ module ClaudeSwarm
|
|
|
79
72
|
@executor.logger.info { "Conversation item IDs: #{conversation_ids.inspect}" }
|
|
80
73
|
end
|
|
81
74
|
|
|
82
|
-
# Add previous response ID for conversation continuity
|
|
83
|
-
parameters[:previous_response_id] =
|
|
75
|
+
# Add previous response ID for conversation continuity (unless zdr is enabled)
|
|
76
|
+
parameters[:previous_response_id] = @zdr ? nil : previous_response_id
|
|
84
77
|
|
|
85
78
|
# Add tools if available
|
|
86
79
|
if @available_tools&.any?
|
|
@@ -115,7 +108,7 @@ module ClaudeSwarm
|
|
|
115
108
|
@executor.logger.error { "Request parameters: #{JsonHandler.pretty_generate!(parameters)}" }
|
|
116
109
|
|
|
117
110
|
# Try to extract and log the response body for better debugging
|
|
118
|
-
if e.respond_to?(:response)
|
|
111
|
+
if e.respond_to?(:response) && e.response
|
|
119
112
|
begin
|
|
120
113
|
error_body = e.response[:body]
|
|
121
114
|
@executor.logger.error { "Error response body: #{error_body}" }
|
|
@@ -131,7 +124,7 @@ module ClaudeSwarm
|
|
|
131
124
|
error: {
|
|
132
125
|
class: e.class.to_s,
|
|
133
126
|
message: e.message,
|
|
134
|
-
response_body: e.respond_to?(:response) ? e.response[:body] : nil,
|
|
127
|
+
response_body: e.respond_to?(:response) && e.response ? e.response[:body] : nil,
|
|
135
128
|
backtrace: e.backtrace.first(5),
|
|
136
129
|
},
|
|
137
130
|
})
|
|
@@ -155,33 +148,21 @@ module ClaudeSwarm
|
|
|
155
148
|
|
|
156
149
|
# Handle response based on output structure
|
|
157
150
|
output = response["output"]
|
|
158
|
-
|
|
159
151
|
if output.nil?
|
|
160
152
|
@executor.logger.error { "No output in response" }
|
|
161
153
|
return "Error: No output in OpenAI response"
|
|
162
154
|
end
|
|
163
155
|
|
|
164
156
|
# Check if output is an array (as per documentation)
|
|
165
|
-
if output.is_a?(Array) &&
|
|
157
|
+
if output.is_a?(Array) && output.any?
|
|
158
|
+
new_conversation = conversation_array.dup
|
|
159
|
+
new_conversation.concat(output)
|
|
166
160
|
# Check if there are function calls
|
|
167
161
|
function_calls = output.select { |item| item["type"] == "function_call" }
|
|
168
|
-
|
|
169
162
|
if function_calls.any?
|
|
170
|
-
|
|
171
|
-
if conversation_array.empty?
|
|
172
|
-
# First depth - build new conversation
|
|
173
|
-
new_conversation = build_conversation_with_outputs(function_calls)
|
|
174
|
-
else
|
|
175
|
-
# Subsequent depth - append to existing conversation
|
|
176
|
-
# Don't re-add function calls, just add the new ones and their outputs
|
|
177
|
-
new_conversation = conversation_array.dup
|
|
178
|
-
append_new_outputs(function_calls, new_conversation)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Recursively process with updated conversation
|
|
163
|
+
append_new_outputs(function_calls, new_conversation)
|
|
182
164
|
process_responses_api(nil, new_conversation, response_id, depth + 1)
|
|
183
165
|
else
|
|
184
|
-
# Look for text response
|
|
185
166
|
extract_text_response(output)
|
|
186
167
|
end
|
|
187
168
|
else
|
data/lib/claude_swarm/version.rb
CHANGED
|
@@ -29,7 +29,7 @@ module SwarmCLI
|
|
|
29
29
|
|
|
30
30
|
# Validate the swarm configuration
|
|
31
31
|
begin
|
|
32
|
-
SwarmSDK
|
|
32
|
+
SwarmSDK.load_file(config_path)
|
|
33
33
|
rescue SwarmSDK::ConfigurationError => e
|
|
34
34
|
$stderr.puts "Error: Invalid swarm configuration: #{e.message}"
|
|
35
35
|
exit(1)
|
|
@@ -92,7 +92,7 @@ module SwarmCLI
|
|
|
92
92
|
|
|
93
93
|
define_method(:call) do |task:, description: nil, thinking_budget: nil|
|
|
94
94
|
# Load swarm for each execution (ensures fresh state)
|
|
95
|
-
swarm = SwarmSDK
|
|
95
|
+
swarm = SwarmSDK.load_file(self.class.config_path)
|
|
96
96
|
|
|
97
97
|
# Build prompt with thinking budget if provided
|
|
98
98
|
prompt = task
|
|
@@ -134,8 +134,8 @@ module SwarmCLI
|
|
|
134
134
|
|
|
135
135
|
def emit_validation_warnings(swarm, formatter)
|
|
136
136
|
# Setup temporary logging to capture and emit warnings
|
|
137
|
-
SwarmSDK::LogCollector.
|
|
138
|
-
formatter.on_log(log_entry)
|
|
137
|
+
SwarmSDK::LogCollector.subscribe(filter: { type: "model_lookup_warning" }) do |log_entry|
|
|
138
|
+
formatter.on_log(log_entry)
|
|
139
139
|
end
|
|
140
140
|
|
|
141
141
|
SwarmSDK::LogStream.emitter = SwarmSDK::LogCollector
|
|
@@ -4,8 +4,8 @@ module SwarmCLI
|
|
|
4
4
|
# ConfigLoader handles loading swarm configurations from both YAML and Ruby DSL files.
|
|
5
5
|
#
|
|
6
6
|
# Supports:
|
|
7
|
-
# - YAML files (.yml, .yaml) - loaded via SwarmSDK
|
|
8
|
-
# - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm or SwarmSDK::
|
|
7
|
+
# - YAML files (.yml, .yaml) - loaded via SwarmSDK.load_file
|
|
8
|
+
# - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm or SwarmSDK::Workflow instance
|
|
9
9
|
#
|
|
10
10
|
# @example Load YAML config
|
|
11
11
|
# swarm = ConfigLoader.load("config.yml")
|
|
@@ -18,11 +18,11 @@ module SwarmCLI
|
|
|
18
18
|
# Load a swarm configuration from file (YAML or Ruby DSL)
|
|
19
19
|
#
|
|
20
20
|
# Detects file type by extension:
|
|
21
|
-
# - .yml, .yaml -> Load as YAML using SwarmSDK
|
|
22
|
-
# - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm or SwarmSDK::
|
|
21
|
+
# - .yml, .yaml -> Load as YAML using SwarmSDK.load_file
|
|
22
|
+
# - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm or SwarmSDK::Workflow instance
|
|
23
23
|
#
|
|
24
24
|
# @param path [String, Pathname] Path to configuration file
|
|
25
|
-
# @return [SwarmSDK::Swarm, SwarmSDK::
|
|
25
|
+
# @return [SwarmSDK::Swarm, SwarmSDK::Workflow] Configured swarm or workflow instance
|
|
26
26
|
# @raise [SwarmCLI::ConfigurationError] If file not found or invalid format
|
|
27
27
|
def load(path)
|
|
28
28
|
path = Pathname.new(path).expand_path
|
|
@@ -50,7 +50,7 @@ module SwarmCLI
|
|
|
50
50
|
# @param path [Pathname] Path to YAML file
|
|
51
51
|
# @return [SwarmSDK::Swarm] Configured swarm instance
|
|
52
52
|
def load_yaml(path)
|
|
53
|
-
SwarmSDK
|
|
53
|
+
SwarmSDK.load_file(path.to_s)
|
|
54
54
|
rescue SwarmSDK::ConfigurationError => e
|
|
55
55
|
# Re-raise with CLI context
|
|
56
56
|
raise ConfigurationError, "Configuration error in #{path}: #{e.message}"
|
|
@@ -59,27 +59,27 @@ module SwarmCLI
|
|
|
59
59
|
# Load Ruby DSL configuration file
|
|
60
60
|
#
|
|
61
61
|
# Executes the Ruby file in a clean binding and expects it to return
|
|
62
|
-
# a SwarmSDK::Swarm or SwarmSDK::
|
|
63
|
-
# use SwarmSDK.build or create a Swarm/
|
|
62
|
+
# a SwarmSDK::Swarm or SwarmSDK::Workflow instance. The file should
|
|
63
|
+
# use SwarmSDK.build or SwarmSDK.workflow or create a Swarm/Workflow instance directly.
|
|
64
64
|
#
|
|
65
65
|
# @param path [Pathname] Path to Ruby DSL file
|
|
66
|
-
# @return [SwarmSDK::Swarm, SwarmSDK::
|
|
66
|
+
# @return [SwarmSDK::Swarm, SwarmSDK::Workflow] Configured swarm or workflow instance
|
|
67
67
|
# @raise [ConfigurationError] If file doesn't return a valid instance
|
|
68
68
|
def load_ruby_dsl(path)
|
|
69
69
|
# Read the file content
|
|
70
70
|
content = path.read
|
|
71
71
|
|
|
72
72
|
# Execute in a clean binding with SwarmSDK available
|
|
73
|
-
# This allows the DSL file to use SwarmSDK.build directly
|
|
73
|
+
# This allows the DSL file to use SwarmSDK.build or SwarmSDK.workflow directly
|
|
74
74
|
result = eval(content, binding, path.to_s, 1) # rubocop:disable Security/Eval
|
|
75
75
|
|
|
76
|
-
# Validate result is a Swarm or
|
|
76
|
+
# Validate result is a Swarm or Workflow instance
|
|
77
77
|
# Both have the same execute(prompt) interface
|
|
78
|
-
unless result.is_a?(SwarmSDK::Swarm) || result.is_a?(SwarmSDK::
|
|
78
|
+
unless result.is_a?(SwarmSDK::Swarm) || result.is_a?(SwarmSDK::Workflow)
|
|
79
79
|
raise ConfigurationError,
|
|
80
|
-
"Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::
|
|
80
|
+
"Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::Workflow instance. " \
|
|
81
81
|
"Got: #{result.class}. " \
|
|
82
|
-
"Use: SwarmSDK.build { ... } or
|
|
82
|
+
"Use: SwarmSDK.build { ... } or SwarmSDK.workflow { ... }"
|
|
83
83
|
end
|
|
84
84
|
|
|
85
85
|
result
|
|
@@ -95,11 +95,18 @@ module SwarmCLI
|
|
|
95
95
|
handle_breakpoint_enter(entry)
|
|
96
96
|
when "breakpoint_exit"
|
|
97
97
|
handle_breakpoint_exit(entry)
|
|
98
|
+
when "llm_retry_attempt"
|
|
99
|
+
handle_llm_retry_attempt(entry)
|
|
100
|
+
when "llm_retry_exhausted"
|
|
101
|
+
handle_llm_retry_exhausted(entry)
|
|
98
102
|
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
# Called when swarm execution completes successfully
|
|
102
106
|
def on_success(result:)
|
|
107
|
+
# Defensive: ensure all spinners are stopped before showing result
|
|
108
|
+
@spinner_manager.stop_all
|
|
109
|
+
|
|
103
110
|
if @mode == :non_interactive
|
|
104
111
|
# Full result display with summary
|
|
105
112
|
@output.puts
|
|
@@ -115,6 +122,9 @@ module SwarmCLI
|
|
|
115
122
|
|
|
116
123
|
# Called when swarm execution fails
|
|
117
124
|
def on_error(error:, duration: nil)
|
|
125
|
+
# Defensive: ensure all spinners are stopped before showing error
|
|
126
|
+
@spinner_manager.stop_all
|
|
127
|
+
|
|
118
128
|
@output.puts
|
|
119
129
|
@output.puts @divider.full
|
|
120
130
|
print_error(error)
|
|
@@ -575,6 +585,66 @@ module SwarmCLI
|
|
|
575
585
|
@output.puts
|
|
576
586
|
end
|
|
577
587
|
|
|
588
|
+
def handle_llm_retry_attempt(entry)
|
|
589
|
+
agent = entry[:agent]
|
|
590
|
+
attempt = entry[:attempt]
|
|
591
|
+
max_retries = entry[:max_retries]
|
|
592
|
+
error_class = entry[:error_class]
|
|
593
|
+
error_message = entry[:error_message]
|
|
594
|
+
retry_delay = entry[:retry_delay]
|
|
595
|
+
|
|
596
|
+
# Stop agent thinking spinner (if active)
|
|
597
|
+
unless @quiet
|
|
598
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
599
|
+
@spinner_manager.stop(spinner_key) if @spinner_manager.active?(spinner_key)
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
lines = [
|
|
603
|
+
@pastel.yellow("LLM API request failed (attempt #{attempt}/#{max_retries})"),
|
|
604
|
+
@pastel.dim("Error: #{error_class}: #{error_message}"),
|
|
605
|
+
@pastel.dim("Retrying in #{retry_delay}s..."),
|
|
606
|
+
]
|
|
607
|
+
|
|
608
|
+
@output.puts @panel.render(
|
|
609
|
+
type: :warning,
|
|
610
|
+
title: "RETRY #{@agent_badge.render(agent)}",
|
|
611
|
+
lines: lines,
|
|
612
|
+
indent: @depth_tracker.get(agent),
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
# Restart spinner for next attempt
|
|
616
|
+
unless @quiet
|
|
617
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
618
|
+
@spinner_manager.start(spinner_key, "#{agent} is retrying...")
|
|
619
|
+
end
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
def handle_llm_retry_exhausted(entry)
|
|
623
|
+
agent = entry[:agent]
|
|
624
|
+
attempts = entry[:attempts]
|
|
625
|
+
error_class = entry[:error_class]
|
|
626
|
+
error_message = entry[:error_message]
|
|
627
|
+
|
|
628
|
+
# Stop agent thinking spinner (if active)
|
|
629
|
+
unless @quiet
|
|
630
|
+
spinner_key = "agent_#{agent}".to_sym
|
|
631
|
+
@spinner_manager.stop(spinner_key) if @spinner_manager.active?(spinner_key)
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
lines = [
|
|
635
|
+
@pastel.red("LLM API request failed after #{attempts} attempts"),
|
|
636
|
+
@pastel.dim("Error: #{error_class}: #{error_message}"),
|
|
637
|
+
@pastel.dim("No more retries available"),
|
|
638
|
+
]
|
|
639
|
+
|
|
640
|
+
@output.puts @panel.render(
|
|
641
|
+
type: :error,
|
|
642
|
+
title: "RETRY EXHAUSTED #{@agent_badge.render(agent)}",
|
|
643
|
+
lines: lines,
|
|
644
|
+
indent: @depth_tracker.get(agent),
|
|
645
|
+
)
|
|
646
|
+
end
|
|
647
|
+
|
|
578
648
|
def display_todo_list(agent, timestamp)
|
|
579
649
|
todos = SwarmSDK::Tools::Stores::TodoManager.get_todos(agent.to_sym)
|
|
580
650
|
indent = @depth_tracker.indent(agent)
|
|
@@ -81,6 +81,9 @@ module SwarmCLI
|
|
|
81
81
|
display_session_summary
|
|
82
82
|
exit(130)
|
|
83
83
|
ensure
|
|
84
|
+
# Defensive: ensure all spinners are stopped on exit
|
|
85
|
+
@formatter&.spinner_manager&.stop_all
|
|
86
|
+
|
|
84
87
|
# Save history on exit
|
|
85
88
|
save_persistent_history
|
|
86
89
|
end
|
|
@@ -432,11 +435,12 @@ module SwarmCLI
|
|
|
432
435
|
end
|
|
433
436
|
end
|
|
434
437
|
|
|
438
|
+
# CRITICAL: Stop all spinners after execution completes
|
|
439
|
+
# This ensures spinner doesn't interfere with error/success display or REPL prompt
|
|
440
|
+
@formatter.spinner_manager.stop_all
|
|
441
|
+
|
|
435
442
|
# Handle cancellation (result is nil when cancelled)
|
|
436
443
|
if result.nil?
|
|
437
|
-
# Stop all active spinners
|
|
438
|
-
@formatter.spinner_manager.stop_all
|
|
439
|
-
|
|
440
444
|
puts ""
|
|
441
445
|
puts @colors[:warning].call("✗ Request cancelled by user")
|
|
442
446
|
puts ""
|
|
@@ -459,6 +463,8 @@ module SwarmCLI
|
|
|
459
463
|
# Add response to history
|
|
460
464
|
@conversation_history << { role: "agent", content: result.content }
|
|
461
465
|
rescue StandardError => e
|
|
466
|
+
# Defensive: ensure spinners are stopped on exception
|
|
467
|
+
@formatter.spinner_manager.stop_all
|
|
462
468
|
@formatter.on_error(error: e)
|
|
463
469
|
end
|
|
464
470
|
|
|
@@ -528,7 +534,7 @@ module SwarmCLI
|
|
|
528
534
|
lead = @swarm.agent(@swarm.lead_agent)
|
|
529
535
|
|
|
530
536
|
# Clear the agent's conversation history
|
|
531
|
-
lead.
|
|
537
|
+
lead.replace_messages([])
|
|
532
538
|
|
|
533
539
|
# Clear REPL conversation history
|
|
534
540
|
@conversation_history.clear
|
|
@@ -569,7 +575,7 @@ module SwarmCLI
|
|
|
569
575
|
case tool_name
|
|
570
576
|
when /^Memory/, "LoadSkill"
|
|
571
577
|
memory_tools << tool_name
|
|
572
|
-
when /^
|
|
578
|
+
when /^WorkWith/
|
|
573
579
|
delegation_tools << tool_name
|
|
574
580
|
when /^mcp__/
|
|
575
581
|
mcp_tools << tool_name
|