claude_swarm 1.0.6 → 1.0.8

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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +27 -0
  4. data/README.md +336 -1037
  5. data/docs/V1_TO_V2_MIGRATION_GUIDE.md +1120 -0
  6. data/docs/v1/README.md +1197 -0
  7. data/docs/v2/CHANGELOG.swarm_cli.md +22 -0
  8. data/docs/v2/CHANGELOG.swarm_memory.md +20 -0
  9. data/docs/v2/CHANGELOG.swarm_sdk.md +287 -10
  10. data/docs/v2/README.md +32 -6
  11. data/docs/v2/guides/complete-tutorial.md +133 -37
  12. data/docs/v2/guides/composable-swarms.md +1178 -0
  13. data/docs/v2/guides/getting-started.md +42 -1
  14. data/docs/v2/guides/snapshots.md +1498 -0
  15. data/docs/v2/reference/architecture-flow.md +5 -3
  16. data/docs/v2/reference/event_payload_structures.md +249 -12
  17. data/docs/v2/reference/execution-flow.md +1 -1
  18. data/docs/v2/reference/ruby-dsl.md +368 -22
  19. data/docs/v2/reference/yaml.md +314 -63
  20. data/examples/snapshot_demo.rb +119 -0
  21. data/examples/v2/dsl/01_basic.rb +0 -2
  22. data/examples/v2/dsl/02_core_parameters.rb +0 -2
  23. data/examples/v2/dsl/03_capabilities.rb +0 -2
  24. data/examples/v2/dsl/04_llm_parameters.rb +0 -2
  25. data/examples/v2/dsl/05_advanced_flags.rb +0 -3
  26. data/examples/v2/dsl/06_permissions.rb +0 -4
  27. data/examples/v2/dsl/07_mcp_server.rb +0 -2
  28. data/examples/v2/dsl/08_swarm_hooks.rb +0 -2
  29. data/examples/v2/dsl/09_agent_hooks.rb +0 -2
  30. data/examples/v2/dsl/10_all_agents_hooks.rb +0 -3
  31. data/examples/v2/dsl/11_delegation.rb +0 -2
  32. data/examples/v2/dsl/12_complete_integration.rb +2 -6
  33. data/examples/v2/node_context_demo.rb +1 -1
  34. data/examples/v2/node_workflow.rb +2 -4
  35. data/examples/v2/plan_and_execute.rb +157 -0
  36. data/lib/claude_swarm/cli.rb +0 -18
  37. data/lib/claude_swarm/configuration.rb +28 -18
  38. data/lib/claude_swarm/openai/chat_completion.rb +2 -11
  39. data/lib/claude_swarm/openai/responses.rb +2 -11
  40. data/lib/claude_swarm/version.rb +1 -1
  41. data/lib/swarm_cli/formatters/human_formatter.rb +103 -0
  42. data/lib/swarm_cli/interactive_repl.rb +9 -3
  43. data/lib/swarm_cli/version.rb +1 -1
  44. data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
  45. data/lib/swarm_memory/integration/cli_registration.rb +3 -2
  46. data/lib/swarm_memory/integration/sdk_plugin.rb +11 -5
  47. data/lib/swarm_memory/tools/memory_edit.rb +2 -2
  48. data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
  49. data/lib/swarm_memory/tools/memory_read.rb +3 -3
  50. data/lib/swarm_memory/version.rb +1 -1
  51. data/lib/swarm_memory.rb +5 -0
  52. data/lib/swarm_sdk/agent/builder.rb +33 -0
  53. data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
  54. data/lib/swarm_sdk/agent/chat/hook_integration.rb +49 -3
  55. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
  56. data/lib/swarm_sdk/agent/chat.rb +200 -51
  57. data/lib/swarm_sdk/agent/context.rb +6 -2
  58. data/lib/swarm_sdk/agent/context_manager.rb +6 -0
  59. data/lib/swarm_sdk/agent/definition.rb +14 -2
  60. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
  61. data/lib/swarm_sdk/configuration.rb +387 -94
  62. data/lib/swarm_sdk/events_to_messages.rb +181 -0
  63. data/lib/swarm_sdk/log_collector.rb +31 -5
  64. data/lib/swarm_sdk/log_stream.rb +37 -8
  65. data/lib/swarm_sdk/model_aliases.json +4 -1
  66. data/lib/swarm_sdk/node/agent_config.rb +33 -8
  67. data/lib/swarm_sdk/node/builder.rb +39 -18
  68. data/lib/swarm_sdk/node_orchestrator.rb +293 -26
  69. data/lib/swarm_sdk/proc_helpers.rb +53 -0
  70. data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
  71. data/lib/swarm_sdk/restore_result.rb +65 -0
  72. data/lib/swarm_sdk/snapshot.rb +156 -0
  73. data/lib/swarm_sdk/snapshot_from_events.rb +386 -0
  74. data/lib/swarm_sdk/state_restorer.rb +491 -0
  75. data/lib/swarm_sdk/state_snapshot.rb +369 -0
  76. data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
  77. data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
  78. data/lib/swarm_sdk/swarm/builder.rb +208 -12
  79. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
  80. data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
  81. data/lib/swarm_sdk/swarm.rb +338 -42
  82. data/lib/swarm_sdk/swarm_loader.rb +145 -0
  83. data/lib/swarm_sdk/swarm_registry.rb +136 -0
  84. data/lib/swarm_sdk/tools/delegate.rb +92 -7
  85. data/lib/swarm_sdk/tools/read.rb +17 -5
  86. data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
  87. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
  88. data/lib/swarm_sdk/utils.rb +18 -0
  89. data/lib/swarm_sdk/validation_result.rb +33 -0
  90. data/lib/swarm_sdk/version.rb +1 -1
  91. data/lib/swarm_sdk.rb +40 -8
  92. data/swarm_cli.gemspec +1 -1
  93. data/swarm_memory.gemspec +2 -2
  94. data/swarm_sdk.gemspec +2 -2
  95. metadata +21 -13
  96. data/examples/learning-assistant/assistant.md +0 -7
  97. data/examples/learning-assistant/example-memories/concept-example.md +0 -90
  98. data/examples/learning-assistant/example-memories/experience-example.md +0 -66
  99. data/examples/learning-assistant/example-memories/fact-example.md +0 -76
  100. data/examples/learning-assistant/example-memories/memory-index.md +0 -78
  101. data/examples/learning-assistant/example-memories/skill-example.md +0 -168
  102. data/examples/learning-assistant/learning_assistant.rb +0 -34
  103. data/examples/learning-assistant/learning_assistant.yml +0 -20
  104. data/lib/swarm_sdk/mcp.rb +0 -16
  105. data/llm.v2.txt +0 -13407
  106. /data/docs/v2/guides/{MEMORY_DEFRAG_GUIDE.md → memory-defrag-guide.md} +0 -0
  107. /data/{llms.txt → llms.claude-swarm.txt} +0 -0
@@ -8,10 +8,6 @@
8
8
  # Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/dsl/06_permissions.rb
9
9
 
10
10
  require "swarm_sdk"
11
- require_relative "../../../swarm_sdk/swarm_builder"
12
- require_relative "../../../swarm_sdk/agent_builder"
13
- require_relative "../../../swarm_sdk/all_agents_builder"
14
- require_relative "../../../swarm_sdk/permissions_builder"
15
11
  require "fileutils"
16
12
 
17
13
  ENV["OPENAI_API_KEY"] = "test-key"
@@ -8,8 +8,6 @@
8
8
  # Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/dsl/07_mcp_server.rb
9
9
 
10
10
  require "swarm_sdk"
11
- require_relative "../../../swarm_sdk/swarm_builder"
12
- require_relative "../../../swarm_sdk/agent_builder"
13
11
 
14
12
  ENV["OPENAI_API_KEY"] = "test-key"
15
13
 
@@ -8,8 +8,6 @@
8
8
  # Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/dsl/08_swarm_hooks.rb
9
9
 
10
10
  require "swarm_sdk"
11
- require_relative "../../../swarm_sdk/swarm_builder"
12
- require_relative "../../../swarm_sdk/agent_builder"
13
11
 
14
12
  ENV["OPENAI_API_KEY"] = "test-key"
15
13
 
@@ -8,8 +8,6 @@
8
8
  # Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/dsl/09_agent_hooks.rb
9
9
 
10
10
  require "swarm_sdk"
11
- require_relative "../../../swarm_sdk/swarm_builder"
12
- require_relative "../../../swarm_sdk/agent_builder"
13
11
 
14
12
  ENV["OPENAI_API_KEY"] = "test-key"
15
13
 
@@ -8,9 +8,6 @@
8
8
  # Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/dsl/10_all_agents_hooks.rb
9
9
 
10
10
  require "swarm_sdk"
11
- require_relative "../../../swarm_sdk/swarm_builder"
12
- require_relative "../../../swarm_sdk/agent_builder"
13
- require_relative "../../../swarm_sdk/all_agents_builder"
14
11
 
15
12
  ENV["OPENAI_API_KEY"] = "test-key"
16
13
 
@@ -8,8 +8,6 @@
8
8
  # Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/dsl/11_delegation.rb
9
9
 
10
10
  require "swarm_sdk"
11
- require_relative "../../../swarm_sdk/swarm_builder"
12
- require_relative "../../../swarm_sdk/agent_builder"
13
11
 
14
12
  ENV["OPENAI_API_KEY"] = "test-key"
15
13
 
@@ -8,10 +8,6 @@
8
8
  # Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/dsl/12_complete_integration.rb
9
9
 
10
10
  require "swarm_sdk"
11
- require_relative "../../../swarm_sdk/swarm_builder"
12
- require_relative "../../../swarm_sdk/agent_builder"
13
- require_relative "../../../swarm_sdk/all_agents_builder"
14
- require_relative "../../../swarm_sdk/permissions_builder"
15
11
 
16
12
  ENV["OPENAI_API_KEY"] = "test-key"
17
13
 
@@ -76,7 +72,7 @@ swarm = SwarmSDK.build do
76
72
 
77
73
  # Advanced flags
78
74
  bypass_permissions(false)
79
- skip_base_prompt(false)
75
+ coding_agent(false)
80
76
  assume_model_exists(true)
81
77
  timeout(120)
82
78
 
@@ -113,7 +109,7 @@ puts " ✓ Agent identity (system_prompt, description)"
113
109
  puts " ✓ Capabilities (tools, delegates_to, directory)"
114
110
  puts " ✓ MCP servers (filesystem via stdio)"
115
111
  puts " ✓ LLM params (parameters, timeout)"
116
- puts " ✓ Advanced flags (disable_default_tools, bypass_permissions, skip_base_prompt, assume_model_exists)"
112
+ puts " ✓ Advanced flags (disable_default_tools, bypass_permissions, coding_agent, assume_model_exists)"
117
113
  puts " ✓ Permissions (all_agents and agent-level)"
118
114
  puts " ✓ Hooks (swarm-level, agent-level, all_agents)"
119
115
  puts " ✓ Delegation"
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # Run: ruby examples/node_context_demo.rb
12
12
 
13
- require_relative "../lib/swarm_sdk"
13
+ require "swarm_sdk"
14
14
 
15
15
  swarm = SwarmSDK.build do
16
16
  name("NodeContext Demo")
@@ -13,7 +13,7 @@
13
13
  #
14
14
  # Run: ruby examples/node_workflow.rb
15
15
 
16
- require_relative "../lib/swarm_sdk"
16
+ require "swarm_sdk"
17
17
 
18
18
  swarm = SwarmSDK.build do
19
19
  name("Haiku Workflow")
@@ -27,7 +27,6 @@ swarm = SwarmSDK.build do
27
27
  Your job is to break down tasks into smaller subtasks. Extract the intent of the user's prompt and break it down into smaller subtasks.
28
28
  Return a list of subtasks.
29
29
  PROMPT
30
- tools(include_default: false)
31
30
  end
32
31
 
33
32
  agent(:implementer) do
@@ -37,7 +36,6 @@ swarm = SwarmSDK.build do
37
36
  system_prompt(<<~PROMPT)
38
37
  Your job is to execute the subtasks given to you.
39
38
  PROMPT
40
- tools(include_default: false)
41
39
  end
42
40
 
43
41
  agent(:verifier) do
@@ -47,12 +45,12 @@ swarm = SwarmSDK.build do
47
45
  system_prompt(<<~PROMPT)
48
46
  Your job is to verify work given to you and return a summary of your findings
49
47
  PROMPT
50
- tools(include_default: false)
51
48
  end
52
49
 
53
50
  # Stage 1: Planning
54
51
  node(:planning) do
55
52
  # Input transformer - ctx.content is the initial prompt
53
+ # Demonstrates using return for early exit with skip_execution
56
54
  input do |ctx|
57
55
  <<~PROMPT
58
56
  Please break down the following prompt into smaller subtasks:
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "swarm_sdk"
5
+
6
+ # Example: Plan and Execute Pattern
7
+ # Two nodes, one agent - first node plans, second node executes the plan
8
+
9
+ swarm = SwarmSDK.build do
10
+ name("Plan and Execute")
11
+
12
+ # Define a single agent that will be used in both nodes
13
+ agent(:assistant) do
14
+ description("A versatile assistant that can plan and execute tasks")
15
+ provider(:openai)
16
+ model("gpt-5")
17
+ coding_agent(false)
18
+
19
+ system_prompt(<<~PROMPT)
20
+ You are a helpful assistant who can both plan and execute tasks.
21
+
22
+ When planning, you should:
23
+ - Break down the task into clear, actionable steps
24
+ - Identify any dependencies or prerequisites
25
+ - Consider potential challenges
26
+
27
+ When executing, you should:
28
+ - Follow the plan carefully
29
+ - Use available tools effectively
30
+ - Report on progress and completion
31
+ PROMPT
32
+
33
+ # Give the assistant tools for execution
34
+ disable_default_tools(true)
35
+ end
36
+
37
+ # Node 1: Planning stage
38
+ # The agent receives the original input and creates a plan
39
+ node(:planning) do
40
+ # Use the assistant agent in planning mode (fresh context)
41
+ agent(:assistant)
42
+
43
+ # Transform input for planning
44
+ #
45
+ # NOTE: Input/output blocks are automatically converted to lambdas,
46
+ # which means you can use `return` safely for early exits!
47
+ #
48
+ # Example of using return for conditional skip:
49
+ # return ctx.skip_execution(content: "cached result") if cached
50
+ input do |ctx|
51
+ <<~INPUT
52
+ Please create a detailed plan for the following task:
53
+
54
+ #{ctx.original_prompt}
55
+
56
+ Your plan should:
57
+ 1. Break down the task into specific steps
58
+ 2. Identify what needs to be done first
59
+ 3. List any tools or resources needed
60
+ 4. Be clear and actionable
61
+
62
+ Output your plan in a structured format.
63
+ INPUT
64
+ end
65
+
66
+ # Transform output to extract key information
67
+ output do |ctx|
68
+ # Save the plan to a file for reference
69
+ File.write("plan.txt", ctx.content)
70
+
71
+ # Pass the plan to the next node
72
+ <<~OUTPUT
73
+ PLAN CREATED:
74
+ #{ctx.content}
75
+
76
+ Now execute this plan step by step.
77
+ OUTPUT
78
+ end
79
+ end
80
+
81
+ # Node 2: Execution stage
82
+ # The agent receives the plan and executes it
83
+ node(:implementation) do
84
+ # Depends on planning node
85
+ depends_on(:planning)
86
+
87
+ # Use the assistant agent in execution mode (fresh context)
88
+ agent(:assistant)
89
+
90
+ # Transform input to provide context from planning
91
+ input do |ctx|
92
+ plan = ctx.all_results[:planning].content
93
+
94
+ <<~INPUT
95
+ You previously created the following plan:
96
+
97
+ #{plan}
98
+
99
+ Now execute this plan. Use the available tools (Write, Edit, Bash) to complete each step.
100
+ Report on what you accomplished.
101
+ INPUT
102
+ end
103
+
104
+ # Output transformer for final results
105
+ output do |ctx|
106
+ <<~OUTPUT
107
+ EXECUTION COMPLETE
108
+
109
+ #{ctx.content}
110
+
111
+ Plan reference: #{File.exist?("plan.txt") ? "Saved in plan.txt" : "Not saved"}
112
+ OUTPUT
113
+ end
114
+ end
115
+
116
+ # Set the starting node
117
+ start_node(:planning)
118
+ end
119
+
120
+ # Execute the swarm
121
+ if __FILE__ == $PROGRAM_NAME
122
+ # Example task
123
+ task = "Create a simple Ruby script that reads a CSV file and outputs a summary"
124
+
125
+ puts "Starting Plan and Execute swarm..."
126
+ puts "Task: #{task}"
127
+ puts "\n" + "=" * 80 + "\n"
128
+
129
+ result = swarm.execute(task) do |log|
130
+ # Optional: Log events as they happen
131
+ case log[:type]
132
+ when "node_start"
133
+ puts "\n🔵 Starting node: #{log[:node]}"
134
+ when "node_complete"
135
+ puts "✅ Completed node: #{log[:node]} (#{log[:duration].round(2)}s)"
136
+ when "tool_call"
137
+ puts " 🔧 Tool: #{log[:tool]}"
138
+ end
139
+ end
140
+
141
+ puts "\n" + "=" * 80 + "\n"
142
+
143
+ if result.success?
144
+ puts "✅ Swarm execution successful!\n\n"
145
+ puts result.content
146
+ puts "\n" + "-" * 80
147
+ puts "Stats:"
148
+ puts " Duration: #{result.duration.round(2)}s"
149
+ puts " Total cost: $#{result.total_cost.round(4)}"
150
+ puts " Total tokens: #{result.total_tokens}"
151
+ puts " Agents involved: #{result.agents_involved.join(", ")}"
152
+ else
153
+ puts "❌ Swarm execution failed!"
154
+ puts "Error: #{result.error.message}"
155
+ puts result.error.backtrace.first(5).join("\n")
156
+ end
157
+ end
@@ -181,14 +181,6 @@ module ClaudeSwarm
181
181
  exit(1)
182
182
  end
183
183
 
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
184
  # Validate the value
193
185
  unless ClaudeSwarm::Configuration::VALID_REASONING_EFFORTS.include?(options[:reasoning_effort])
194
186
  error("reasoning_effort must be 'low', 'medium', or 'high'")
@@ -196,16 +188,6 @@ module ClaudeSwarm
196
188
  end
197
189
  end
198
190
 
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
191
  instance_config = {
210
192
  name: options[:name],
211
193
  directory: options[:directory],
@@ -11,8 +11,6 @@ module ClaudeSwarm
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"
@@ -67,17 +67,8 @@ module ClaudeSwarm
67
67
  messages: messages,
68
68
  }
69
69
 
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
70
+ parameters[:temperature] = @temperature if @temperature
71
+ parameters[:reasoning_effort] = @reasoning_effort if @reasoning_effort
81
72
 
82
73
  # Add tools if available
83
74
  parameters[:tools] = @mcp_client.to_openai_tools if @available_tools&.any? && @mcp_client
@@ -46,17 +46,8 @@ module ClaudeSwarm
46
46
  model: @model,
47
47
  }
48
48
 
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
49
+ parameters[:temperature] = @temperature if @temperature
50
+ parameters[:reasoning] = { effort: @reasoning_effort } if @reasoning_effort
60
51
 
61
52
  # On first call, use string input (can include system prompt)
62
53
  # On subsequent calls with function results, use array input
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeSwarm
4
- VERSION = "1.0.6"
4
+ VERSION = "1.0.8"
5
5
  end
@@ -95,11 +95,20 @@ 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)
102
+ when "response_parse_error"
103
+ handle_response_parse_error(entry)
98
104
  end
99
105
  end
100
106
 
101
107
  # Called when swarm execution completes successfully
102
108
  def on_success(result:)
109
+ # Defensive: ensure all spinners are stopped before showing result
110
+ @spinner_manager.stop_all
111
+
103
112
  if @mode == :non_interactive
104
113
  # Full result display with summary
105
114
  @output.puts
@@ -115,6 +124,9 @@ module SwarmCLI
115
124
 
116
125
  # Called when swarm execution fails
117
126
  def on_error(error:, duration: nil)
127
+ # Defensive: ensure all spinners are stopped before showing error
128
+ @spinner_manager.stop_all
129
+
118
130
  @output.puts
119
131
  @output.puts @divider.full
120
132
  print_error(error)
@@ -575,6 +587,97 @@ module SwarmCLI
575
587
  @output.puts
576
588
  end
577
589
 
590
+ def handle_llm_retry_attempt(entry)
591
+ agent = entry[:agent]
592
+ attempt = entry[:attempt]
593
+ max_retries = entry[:max_retries]
594
+ error_class = entry[:error_class]
595
+ error_message = entry[:error_message]
596
+ retry_delay = entry[:retry_delay]
597
+
598
+ # Stop agent thinking spinner (if active)
599
+ unless @quiet
600
+ spinner_key = "agent_#{agent}".to_sym
601
+ @spinner_manager.stop(spinner_key) if @spinner_manager.active?(spinner_key)
602
+ end
603
+
604
+ lines = [
605
+ @pastel.yellow("LLM API request failed (attempt #{attempt}/#{max_retries})"),
606
+ @pastel.dim("Error: #{error_class}: #{error_message}"),
607
+ @pastel.dim("Retrying in #{retry_delay}s..."),
608
+ ]
609
+
610
+ @output.puts @panel.render(
611
+ type: :warning,
612
+ title: "RETRY #{@agent_badge.render(agent)}",
613
+ lines: lines,
614
+ indent: @depth_tracker.get(agent),
615
+ )
616
+
617
+ # Restart spinner for next attempt
618
+ unless @quiet
619
+ spinner_key = "agent_#{agent}".to_sym
620
+ @spinner_manager.start(spinner_key, "#{agent} is retrying...")
621
+ end
622
+ end
623
+
624
+ def handle_llm_retry_exhausted(entry)
625
+ agent = entry[:agent]
626
+ attempts = entry[:attempts]
627
+ error_class = entry[:error_class]
628
+ error_message = entry[:error_message]
629
+
630
+ # Stop agent thinking spinner (if active)
631
+ unless @quiet
632
+ spinner_key = "agent_#{agent}".to_sym
633
+ @spinner_manager.stop(spinner_key) if @spinner_manager.active?(spinner_key)
634
+ end
635
+
636
+ lines = [
637
+ @pastel.red("LLM API request failed after #{attempts} attempts"),
638
+ @pastel.dim("Error: #{error_class}: #{error_message}"),
639
+ @pastel.dim("No more retries available"),
640
+ ]
641
+
642
+ @output.puts @panel.render(
643
+ type: :error,
644
+ title: "RETRY EXHAUSTED #{@agent_badge.render(agent)}",
645
+ lines: lines,
646
+ indent: @depth_tracker.get(agent),
647
+ )
648
+ end
649
+
650
+ def handle_response_parse_error(entry)
651
+ agent = entry[:agent]
652
+ error_class = entry[:error_class]
653
+ error_message = entry[:error_message]
654
+
655
+ # Stop agent thinking spinner (if active)
656
+ unless @quiet
657
+ spinner_key = "agent_#{agent}".to_sym
658
+ @spinner_manager.stop(spinner_key) if @spinner_manager.active?(spinner_key)
659
+ end
660
+
661
+ lines = [
662
+ @pastel.red("Failed to parse LLM API response"),
663
+ @pastel.dim("Error: #{error_class}: #{error_message}"),
664
+ ]
665
+
666
+ # Add response body preview if available (truncated)
667
+ if entry[:response_body]
668
+ body_preview = entry[:response_body].to_s[0..200]
669
+ body_preview += "..." if entry[:response_body].to_s.length > 200
670
+ lines << @pastel.dim("Response: #{body_preview}")
671
+ end
672
+
673
+ @output.puts @panel.render(
674
+ type: :error,
675
+ title: "PARSE ERROR #{@agent_badge.render(agent)}",
676
+ lines: lines,
677
+ indent: @depth_tracker.get(agent),
678
+ )
679
+ end
680
+
578
681
  def display_todo_list(agent, timestamp)
579
682
  todos = SwarmSDK::Tools::Stores::TodoManager.get_todos(agent.to_sym)
580
683
  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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmCLI
4
- VERSION = "2.1.2"
4
+ VERSION = "2.1.3"
5
5
  end