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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/lib/claude_swarm/claude_mcp_server.rb +1 -0
  3. data/lib/claude_swarm/cli.rb +5 -18
  4. data/lib/claude_swarm/configuration.rb +30 -19
  5. data/lib/claude_swarm/mcp_generator.rb +5 -10
  6. data/lib/claude_swarm/openai/chat_completion.rb +4 -12
  7. data/lib/claude_swarm/openai/executor.rb +3 -1
  8. data/lib/claude_swarm/openai/responses.rb +13 -32
  9. data/lib/claude_swarm/version.rb +1 -1
  10. data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
  11. data/lib/swarm_cli/commands/run.rb +2 -2
  12. data/lib/swarm_cli/config_loader.rb +14 -14
  13. data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
  14. data/lib/swarm_cli/interactive_repl.rb +11 -5
  15. data/lib/swarm_cli/ui/icons.rb +0 -23
  16. data/lib/swarm_cli/version.rb +1 -1
  17. data/lib/swarm_memory/adapters/base.rb +4 -4
  18. data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
  19. data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
  20. data/lib/swarm_memory/integration/cli_registration.rb +3 -2
  21. data/lib/swarm_memory/integration/sdk_plugin.rb +98 -12
  22. data/lib/swarm_memory/tools/memory_edit.rb +2 -2
  23. data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
  24. data/lib/swarm_memory/tools/memory_read.rb +3 -3
  25. data/lib/swarm_memory/version.rb +1 -1
  26. data/lib/swarm_memory.rb +6 -1
  27. data/lib/swarm_sdk/agent/builder.rb +91 -0
  28. data/lib/swarm_sdk/agent/chat.rb +540 -925
  29. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +33 -79
  30. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  31. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +147 -39
  32. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  33. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  34. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  35. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  36. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +22 -38
  37. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  38. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  39. data/lib/swarm_sdk/agent/context.rb +8 -4
  40. data/lib/swarm_sdk/agent/context_manager.rb +6 -0
  41. data/lib/swarm_sdk/agent/definition.rb +79 -174
  42. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +182 -0
  43. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  44. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  45. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  46. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  47. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  48. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  49. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  50. data/lib/swarm_sdk/configuration.rb +100 -261
  51. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  52. data/lib/swarm_sdk/context_compactor.rb +6 -11
  53. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  54. data/lib/swarm_sdk/context_management/context.rb +328 -0
  55. data/lib/swarm_sdk/defaults.rb +196 -0
  56. data/lib/swarm_sdk/events_to_messages.rb +199 -0
  57. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  58. data/lib/swarm_sdk/log_collector.rb +192 -16
  59. data/lib/swarm_sdk/log_stream.rb +66 -8
  60. data/lib/swarm_sdk/model_aliases.json +4 -1
  61. data/lib/swarm_sdk/node_context.rb +1 -1
  62. data/lib/swarm_sdk/observer/builder.rb +81 -0
  63. data/lib/swarm_sdk/observer/config.rb +45 -0
  64. data/lib/swarm_sdk/observer/manager.rb +236 -0
  65. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  66. data/lib/swarm_sdk/plugin.rb +93 -3
  67. data/lib/swarm_sdk/proc_helpers.rb +53 -0
  68. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
  69. data/lib/swarm_sdk/restore_result.rb +65 -0
  70. data/lib/swarm_sdk/snapshot.rb +156 -0
  71. data/lib/swarm_sdk/snapshot_from_events.rb +397 -0
  72. data/lib/swarm_sdk/state_restorer.rb +476 -0
  73. data/lib/swarm_sdk/state_snapshot.rb +334 -0
  74. data/lib/swarm_sdk/swarm/agent_initializer.rb +428 -79
  75. data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
  76. data/lib/swarm_sdk/swarm/builder.rb +69 -407
  77. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  78. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  79. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  80. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  81. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
  82. data/lib/swarm_sdk/swarm/tool_configurator.rb +88 -149
  83. data/lib/swarm_sdk/swarm.rb +366 -631
  84. data/lib/swarm_sdk/swarm_loader.rb +145 -0
  85. data/lib/swarm_sdk/swarm_registry.rb +136 -0
  86. data/lib/swarm_sdk/tools/bash.rb +11 -3
  87. data/lib/swarm_sdk/tools/delegate.rb +127 -24
  88. data/lib/swarm_sdk/tools/edit.rb +8 -13
  89. data/lib/swarm_sdk/tools/glob.rb +9 -1
  90. data/lib/swarm_sdk/tools/grep.rb +7 -0
  91. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  92. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  93. data/lib/swarm_sdk/tools/read.rb +28 -18
  94. data/lib/swarm_sdk/tools/registry.rb +122 -10
  95. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
  96. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
  97. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
  98. data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
  99. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +53 -5
  100. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  101. data/lib/swarm_sdk/tools/think.rb +4 -1
  102. data/lib/swarm_sdk/tools/todo_write.rb +27 -8
  103. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  104. data/lib/swarm_sdk/tools/write.rb +8 -13
  105. data/lib/swarm_sdk/utils.rb +18 -0
  106. data/lib/swarm_sdk/validation_result.rb +33 -0
  107. data/lib/swarm_sdk/version.rb +1 -1
  108. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +34 -9
  109. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  110. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  111. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +42 -21
  112. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  113. data/lib/swarm_sdk/workflow.rb +554 -0
  114. data/lib/swarm_sdk.rb +393 -22
  115. metadata +51 -16
  116. data/lib/swarm_memory/chat_extension.rb +0 -34
  117. data/lib/swarm_sdk/node_orchestrator.rb +0 -591
  118. 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: b83e33e8fe0bbfda5910aa6e3cb0da9815166741b6590e22d0321d31d1c4aa39
4
- data.tar.gz: 6c72c5174ec20553d1db8c9677b8a48e7bf475b0a689ccdea4c8755eedf8971e
3
+ metadata.gz: ebb25752a42e174408e197c5b24d94ebfbd2a57cfd5a26101e839f2375e54b8f
4
+ data.tar.gz: 9558c9e19cfb486872d6cee1b445506e9c599f8d4a767670743f3a3f1481fb49
5
5
  SHA512:
6
- metadata.gz: 53ce80b29bad1e1d3c8e86a629d87f092fb005508127b57e90a90acc9e3fa4130bdd8b7946810aa0801a1cfef014d6a789e5458fab2e9034aa589cc768df2d0e
7
- data.tar.gz: f2c7a903a638748eee87f40a05bad00d8bcd0fd267bc2f1ca2cad667f489d9fec6887934373b29505f97b97cc46d9eba6ab20c9de23d6522d86a38a595d9265a
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
@@ -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
- interpolate_env_string(obj)
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.transform_values! { |v| interpolate_env_vars!(v) }
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! { |v| interpolate_env_vars!(v) }
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 Ruby and Bundler to work properly
187
- # This includes both BUNDLE_* variables and Ruby-specific variables
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
- # Bundle-specific variables
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
- # Only add temperature for non-o-series models
71
- # O-series models don't support temperature parameter
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
- # Only add temperature for non-o-series models
50
- # O-series models don't support temperature parameter
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] = previous_response_id if 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) && !output.empty?
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
- # Check if we already have a conversation going
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeSwarm
4
- VERSION = "1.0.5"
4
+ VERSION = "1.0.9"
5
5
  end
@@ -29,7 +29,7 @@ module SwarmCLI
29
29
 
30
30
  # Validate the swarm configuration
31
31
  begin
32
- SwarmSDK::Swarm.load(config_path)
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::Swarm.load(self.class.config_path)
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.on_log do |log_entry|
138
- formatter.on_log(log_entry) if log_entry[:type] == "model_lookup_warning"
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::Swarm.load
8
- # - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm or SwarmSDK::NodeOrchestrator instance
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::Swarm.load
22
- # - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm or SwarmSDK::NodeOrchestrator instance
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::NodeOrchestrator] Configured swarm or orchestrator instance
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::Swarm.load(path.to_s)
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::NodeOrchestrator instance. The file should
63
- # use SwarmSDK.build or create a Swarm/NodeOrchestrator instance directly.
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::NodeOrchestrator] Configured swarm or orchestrator instance
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 NodeOrchestrator instance
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::NodeOrchestrator)
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::NodeOrchestrator instance. " \
80
+ "Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::Workflow instance. " \
81
81
  "Got: #{result.class}. " \
82
- "Use: SwarmSDK.build { ... } or Swarm.new(...)"
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.reset_messages!
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 /^DelegateTaskTo/
578
+ when /^WorkWith/
573
579
  delegation_tools << tool_name
574
580
  when /^mcp__/
575
581
  mcp_tools << tool_name