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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +27 -0
- data/README.md +336 -1037
- data/docs/V1_TO_V2_MIGRATION_GUIDE.md +1120 -0
- data/docs/v1/README.md +1197 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +22 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +20 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +287 -10
- data/docs/v2/README.md +32 -6
- data/docs/v2/guides/complete-tutorial.md +133 -37
- data/docs/v2/guides/composable-swarms.md +1178 -0
- data/docs/v2/guides/getting-started.md +42 -1
- data/docs/v2/guides/snapshots.md +1498 -0
- data/docs/v2/reference/architecture-flow.md +5 -3
- data/docs/v2/reference/event_payload_structures.md +249 -12
- data/docs/v2/reference/execution-flow.md +1 -1
- data/docs/v2/reference/ruby-dsl.md +368 -22
- data/docs/v2/reference/yaml.md +314 -63
- data/examples/snapshot_demo.rb +119 -0
- data/examples/v2/dsl/01_basic.rb +0 -2
- data/examples/v2/dsl/02_core_parameters.rb +0 -2
- data/examples/v2/dsl/03_capabilities.rb +0 -2
- data/examples/v2/dsl/04_llm_parameters.rb +0 -2
- data/examples/v2/dsl/05_advanced_flags.rb +0 -3
- data/examples/v2/dsl/06_permissions.rb +0 -4
- data/examples/v2/dsl/07_mcp_server.rb +0 -2
- data/examples/v2/dsl/08_swarm_hooks.rb +0 -2
- data/examples/v2/dsl/09_agent_hooks.rb +0 -2
- data/examples/v2/dsl/10_all_agents_hooks.rb +0 -3
- data/examples/v2/dsl/11_delegation.rb +0 -2
- data/examples/v2/dsl/12_complete_integration.rb +2 -6
- data/examples/v2/node_context_demo.rb +1 -1
- data/examples/v2/node_workflow.rb +2 -4
- data/examples/v2/plan_and_execute.rb +157 -0
- data/lib/claude_swarm/cli.rb +0 -18
- data/lib/claude_swarm/configuration.rb +28 -18
- data/lib/claude_swarm/openai/chat_completion.rb +2 -11
- data/lib/claude_swarm/openai/responses.rb +2 -11
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/formatters/human_formatter.rb +103 -0
- data/lib/swarm_cli/interactive_repl.rb +9 -3
- data/lib/swarm_cli/version.rb +1 -1
- 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 +11 -5
- 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 +5 -0
- data/lib/swarm_sdk/agent/builder.rb +33 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +49 -3
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
- data/lib/swarm_sdk/agent/chat.rb +200 -51
- data/lib/swarm_sdk/agent/context.rb +6 -2
- data/lib/swarm_sdk/agent/context_manager.rb +6 -0
- data/lib/swarm_sdk/agent/definition.rb +14 -2
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
- data/lib/swarm_sdk/configuration.rb +387 -94
- data/lib/swarm_sdk/events_to_messages.rb +181 -0
- data/lib/swarm_sdk/log_collector.rb +31 -5
- data/lib/swarm_sdk/log_stream.rb +37 -8
- data/lib/swarm_sdk/model_aliases.json +4 -1
- data/lib/swarm_sdk/node/agent_config.rb +33 -8
- data/lib/swarm_sdk/node/builder.rb +39 -18
- data/lib/swarm_sdk/node_orchestrator.rb +293 -26
- data/lib/swarm_sdk/proc_helpers.rb +53 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
- 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 +386 -0
- data/lib/swarm_sdk/state_restorer.rb +491 -0
- data/lib/swarm_sdk/state_snapshot.rb +369 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
- data/lib/swarm_sdk/swarm/builder.rb +208 -12
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
- data/lib/swarm_sdk/swarm.rb +338 -42
- data/lib/swarm_sdk/swarm_loader.rb +145 -0
- data/lib/swarm_sdk/swarm_registry.rb +136 -0
- data/lib/swarm_sdk/tools/delegate.rb +92 -7
- data/lib/swarm_sdk/tools/read.rb +17 -5
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
- 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.rb +40 -8
- data/swarm_cli.gemspec +1 -1
- data/swarm_memory.gemspec +2 -2
- data/swarm_sdk.gemspec +2 -2
- metadata +21 -13
- data/examples/learning-assistant/assistant.md +0 -7
- data/examples/learning-assistant/example-memories/concept-example.md +0 -90
- data/examples/learning-assistant/example-memories/experience-example.md +0 -66
- data/examples/learning-assistant/example-memories/fact-example.md +0 -76
- data/examples/learning-assistant/example-memories/memory-index.md +0 -78
- data/examples/learning-assistant/example-memories/skill-example.md +0 -168
- data/examples/learning-assistant/learning_assistant.rb +0 -34
- data/examples/learning-assistant/learning_assistant.yml +0 -20
- data/lib/swarm_sdk/mcp.rb +0 -16
- data/llm.v2.txt +0 -13407
- /data/docs/v2/guides/{MEMORY_DEFRAG_GUIDE.md → memory-defrag-guide.md} +0 -0
- /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,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,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
|
-
|
|
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,
|
|
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"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
#
|
|
14
14
|
# Run: ruby examples/node_workflow.rb
|
|
15
15
|
|
|
16
|
-
|
|
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
|
data/lib/claude_swarm/cli.rb
CHANGED
|
@@ -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
|
-
|
|
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"
|
|
@@ -67,17 +67,8 @@ module ClaudeSwarm
|
|
|
67
67
|
messages: messages,
|
|
68
68
|
}
|
|
69
69
|
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
data/lib/claude_swarm/version.rb
CHANGED
|
@@ -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
|
|
data/lib/swarm_cli/version.rb
CHANGED