claude_swarm 1.0.9 → 1.0.11
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/{CHANGELOG.md → CHANGELOG.claude-swarm.md} +10 -0
- data/CLAUDE.md +346 -191
- data/decisions/2025-11-22-001-global-agent-registry.md +172 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +20 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +146 -1
- data/docs/v2/CHANGELOG.swarm_sdk.md +433 -10
- data/docs/v2/README.md +20 -5
- data/docs/v2/guides/complete-tutorial.md +95 -9
- data/docs/v2/guides/getting-started.md +10 -8
- data/docs/v2/guides/memory-adapters.md +41 -0
- data/docs/v2/guides/migrating-to-2.x.md +746 -0
- data/docs/v2/guides/plugins.md +52 -5
- data/docs/v2/guides/rails-integration.md +6 -0
- data/docs/v2/guides/snapshots.md +14 -14
- data/docs/v2/guides/swarm-memory.md +2 -13
- data/docs/v2/reference/architecture-flow.md +3 -3
- data/docs/v2/reference/cli.md +0 -1
- data/docs/v2/reference/configuration_reference.md +300 -0
- data/docs/v2/reference/event_payload_structures.md +27 -5
- data/docs/v2/reference/ruby-dsl.md +614 -18
- data/docs/v2/reference/swarm_memory_technical_details.md +7 -29
- data/docs/v2/reference/yaml.md +172 -54
- data/examples/snapshot_demo.rb +2 -2
- data/lib/claude_swarm/mcp_generator.rb +8 -21
- data/lib/claude_swarm/orchestrator.rb +8 -1
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/commands/run.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +11 -11
- data/lib/swarm_cli/formatters/human_formatter.rb +0 -33
- data/lib/swarm_cli/interactive_repl.rb +2 -2
- data/lib/swarm_cli/ui/icons.rb +0 -23
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
- data/lib/swarm_memory/core/semantic_index.rb +10 -2
- data/lib/swarm_memory/core/storage.rb +7 -2
- data/lib/swarm_memory/dsl/memory_config.rb +37 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +201 -28
- data/lib/swarm_memory/optimization/defragmenter.rb +1 -1
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +0 -1
- data/lib/swarm_memory/tools/load_skill.rb +0 -1
- data/lib/swarm_memory/tools/memory_edit.rb +2 -1
- data/lib/swarm_memory/tools/memory_read.rb +1 -1
- data/lib/swarm_memory/version.rb +1 -1
- data/lib/swarm_memory.rb +8 -6
- data/lib/swarm_sdk/agent/builder.rb +58 -0
- data/lib/swarm_sdk/agent/chat.rb +527 -1061
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +13 -88
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +108 -46
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +267 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +3 -3
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +11 -13
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +146 -0
- data/lib/swarm_sdk/agent/context.rb +1 -2
- data/lib/swarm_sdk/agent/definition.rb +66 -154
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
- data/lib/swarm_sdk/agent_registry.rb +146 -0
- data/lib/swarm_sdk/builders/base_builder.rb +488 -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/config.rb +302 -0
- data/lib/swarm_sdk/configuration/parser.rb +373 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +77 -546
- data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
- 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/custom_tool_registry.rb +226 -0
- data/lib/swarm_sdk/defaults.rb +196 -0
- data/lib/swarm_sdk/events_to_messages.rb +18 -0
- data/lib/swarm_sdk/hooks/adapter.rb +3 -3
- data/lib/swarm_sdk/hooks/shell_executor.rb +4 -2
- data/lib/swarm_sdk/log_collector.rb +179 -29
- data/lib/swarm_sdk/log_stream.rb +29 -0
- data/lib/swarm_sdk/models.json +4333 -1
- data/lib/swarm_sdk/models.rb +43 -2
- 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 +95 -5
- data/lib/swarm_sdk/result.rb +52 -0
- data/lib/swarm_sdk/snapshot.rb +6 -6
- data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
- data/lib/swarm_sdk/state_restorer.rb +136 -151
- data/lib/swarm_sdk/state_snapshot.rb +65 -100
- data/lib/swarm_sdk/swarm/agent_initializer.rb +181 -137
- data/lib/swarm_sdk/swarm/builder.rb +44 -578
- data/lib/swarm_sdk/swarm/executor.rb +213 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +151 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +341 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
- data/lib/swarm_sdk/swarm/tool_configurator.rb +58 -140
- data/lib/swarm_sdk/swarm.rb +203 -683
- data/lib/swarm_sdk/tools/bash.rb +14 -8
- data/lib/swarm_sdk/tools/delegate.rb +61 -43
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +12 -4
- 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 +16 -18
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +9 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/todo_write.rb +7 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +20 -17
- data/lib/swarm_sdk/tools/write.rb +8 -13
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +192 -0
- data/lib/swarm_sdk/workflow/executor.rb +497 -0
- data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +7 -5
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +5 -3
- data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
- data/lib/swarm_sdk.rb +294 -108
- data/rubocop/cop/security/no_reflection_methods.rb +1 -1
- data/swarm_cli.gemspec +1 -1
- data/swarm_memory.gemspec +8 -3
- data/swarm_sdk.gemspec +6 -4
- data/team_full.yml +124 -320
- metadata +42 -14
- data/lib/swarm_memory/chat_extension.rb +0 -34
- data/lib/swarm_memory/tools/memory_multi_edit.rb +0 -281
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
- /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "delegate"
|
|
4
|
+
|
|
5
|
+
module SwarmSDK
|
|
6
|
+
# Registry for user-defined custom tools
|
|
7
|
+
#
|
|
8
|
+
# Provides a simple way to register custom tools without creating a full plugin.
|
|
9
|
+
# Custom tools are registered globally and available to all agents that request them.
|
|
10
|
+
#
|
|
11
|
+
# ## When to Use Custom Tools vs Plugins
|
|
12
|
+
#
|
|
13
|
+
# **Use Custom Tools when:**
|
|
14
|
+
# - You have simple, stateless tools
|
|
15
|
+
# - Tools don't need persistent storage
|
|
16
|
+
# - Tools don't need lifecycle hooks
|
|
17
|
+
# - Tools don't need system prompt contributions
|
|
18
|
+
#
|
|
19
|
+
# **Use Plugins when:**
|
|
20
|
+
# - Tools need persistent storage per agent
|
|
21
|
+
# - Tools need lifecycle hooks (on_agent_initialized, on_user_message, etc.)
|
|
22
|
+
# - Tools need to contribute to system prompts
|
|
23
|
+
# - You have a suite of related tools that share configuration
|
|
24
|
+
#
|
|
25
|
+
# @example Register a simple tool
|
|
26
|
+
# class WeatherTool < RubyLLM::Tool
|
|
27
|
+
# description "Get weather for a city"
|
|
28
|
+
# param :city, type: "string", required: true
|
|
29
|
+
#
|
|
30
|
+
# def execute(city:)
|
|
31
|
+
# "Weather in #{city}: Sunny"
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# SwarmSDK.register_tool(WeatherTool)
|
|
36
|
+
#
|
|
37
|
+
# @example Register with explicit name
|
|
38
|
+
# SwarmSDK.register_tool(:Weather, WeatherTool)
|
|
39
|
+
#
|
|
40
|
+
# @example Tool with creation requirements
|
|
41
|
+
# class AgentAwareTool < RubyLLM::Tool
|
|
42
|
+
# def self.creation_requirements
|
|
43
|
+
# [:agent_name, :directory]
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# def initialize(agent_name:, directory:)
|
|
47
|
+
# super()
|
|
48
|
+
# @agent_name = agent_name
|
|
49
|
+
# @directory = directory
|
|
50
|
+
# end
|
|
51
|
+
#
|
|
52
|
+
# def execute
|
|
53
|
+
# "Agent: #{@agent_name}, Dir: #{@directory}"
|
|
54
|
+
# end
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# SwarmSDK.register_tool(AgentAwareTool)
|
|
58
|
+
#
|
|
59
|
+
module CustomToolRegistry
|
|
60
|
+
# Wrapper that overrides the tool's name to match the registered name
|
|
61
|
+
#
|
|
62
|
+
# This ensures that when a user registers a tool with a specific name,
|
|
63
|
+
# that name is what gets used for tool lookup (has_tool?) and LLM tool calls.
|
|
64
|
+
class NamedToolWrapper < SimpleDelegator
|
|
65
|
+
def initialize(tool, registered_name)
|
|
66
|
+
super(tool)
|
|
67
|
+
@registered_name = registered_name.to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Override name to return the registered name
|
|
71
|
+
def name
|
|
72
|
+
@registered_name
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
@tools = {}
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
# Register a custom tool
|
|
80
|
+
#
|
|
81
|
+
# @param name [Symbol] Tool name
|
|
82
|
+
# @param tool_class [Class] Tool class (must be a RubyLLM::Tool subclass)
|
|
83
|
+
# @raise [ArgumentError] If tool_class is not a RubyLLM::Tool subclass
|
|
84
|
+
# @raise [ArgumentError] If a tool with the same name is already registered
|
|
85
|
+
# @return [void]
|
|
86
|
+
def register(name, tool_class)
|
|
87
|
+
name = name.to_sym
|
|
88
|
+
|
|
89
|
+
unless tool_class.is_a?(Class) && tool_class < RubyLLM::Tool
|
|
90
|
+
raise ArgumentError, "Tool class must inherit from RubyLLM::Tool"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if @tools.key?(name)
|
|
94
|
+
raise ArgumentError, "Custom tool '#{name}' is already registered"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if PluginRegistry.plugin_tool?(name)
|
|
98
|
+
raise ArgumentError, "Tool '#{name}' is already provided by a plugin"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if Tools::Registry.exists?(name)
|
|
102
|
+
raise ArgumentError, "Tool '#{name}' is a built-in tool and cannot be overridden"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
@tools[name] = tool_class
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check if a custom tool is registered
|
|
109
|
+
#
|
|
110
|
+
# @param name [Symbol, String] Tool name
|
|
111
|
+
# @return [Boolean]
|
|
112
|
+
def registered?(name)
|
|
113
|
+
@tools.key?(name.to_sym)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Get a registered tool class
|
|
117
|
+
#
|
|
118
|
+
# @param name [Symbol, String] Tool name
|
|
119
|
+
# @return [Class, nil] Tool class or nil if not found
|
|
120
|
+
def get(name)
|
|
121
|
+
@tools[name.to_sym]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Get all registered custom tool names
|
|
125
|
+
#
|
|
126
|
+
# @return [Array<Symbol>]
|
|
127
|
+
def tool_names
|
|
128
|
+
@tools.keys
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Create a tool instance
|
|
132
|
+
#
|
|
133
|
+
# Uses the tool's `creation_requirements` class method (if defined) to determine
|
|
134
|
+
# what parameters to pass to the constructor. The created tool is wrapped with
|
|
135
|
+
# NamedToolWrapper to ensure the registered name is used for tool lookup.
|
|
136
|
+
#
|
|
137
|
+
# @param name [Symbol, String] Tool name
|
|
138
|
+
# @param context [Hash] Available context for tool creation
|
|
139
|
+
# @option context [Symbol] :agent_name Agent identifier
|
|
140
|
+
# @option context [String] :directory Agent's working directory
|
|
141
|
+
# @return [RubyLLM::Tool] Instantiated tool (wrapped with registered name)
|
|
142
|
+
# @raise [ConfigurationError] If tool is unknown or has unmet requirements
|
|
143
|
+
def create(name, context = {})
|
|
144
|
+
name_sym = name.to_sym
|
|
145
|
+
tool_class = @tools[name_sym]
|
|
146
|
+
|
|
147
|
+
raise ConfigurationError, "Unknown custom tool: #{name}" unless tool_class
|
|
148
|
+
|
|
149
|
+
# Create the tool instance
|
|
150
|
+
tool = if tool_class.respond_to?(:creation_requirements)
|
|
151
|
+
requirements = tool_class.creation_requirements
|
|
152
|
+
params = extract_params(requirements, context, name)
|
|
153
|
+
tool_class.new(**params)
|
|
154
|
+
else
|
|
155
|
+
# No requirements - simple instantiation
|
|
156
|
+
tool_class.new
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Wrap with NamedToolWrapper to ensure registered name is used
|
|
160
|
+
NamedToolWrapper.new(tool, name_sym)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Unregister a custom tool
|
|
164
|
+
#
|
|
165
|
+
# @param name [Symbol, String] Tool name
|
|
166
|
+
# @return [Class, nil] The unregistered tool class, or nil if not found
|
|
167
|
+
def unregister(name)
|
|
168
|
+
@tools.delete(name.to_sym)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Clear all registered custom tools
|
|
172
|
+
#
|
|
173
|
+
# Primarily useful for testing.
|
|
174
|
+
#
|
|
175
|
+
# @return [void]
|
|
176
|
+
def clear
|
|
177
|
+
@tools.clear
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Infer tool name from class name
|
|
181
|
+
#
|
|
182
|
+
# @param tool_class [Class] Tool class
|
|
183
|
+
# @return [Symbol] Inferred tool name
|
|
184
|
+
#
|
|
185
|
+
# @example
|
|
186
|
+
# infer_name(WeatherTool) #=> :Weather
|
|
187
|
+
# infer_name(MyApp::Tools::StockPrice) #=> :StockPrice
|
|
188
|
+
# infer_name(MyApp::Tools::StockPriceTool) #=> :StockPrice
|
|
189
|
+
def infer_name(tool_class)
|
|
190
|
+
# Get the class name without module prefix
|
|
191
|
+
class_name = tool_class.name.split("::").last
|
|
192
|
+
|
|
193
|
+
# Remove "Tool" suffix if present
|
|
194
|
+
name = class_name.sub(/Tool\z/, "")
|
|
195
|
+
|
|
196
|
+
name.to_sym
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
# Extract required parameters from context
|
|
202
|
+
#
|
|
203
|
+
# @param requirements [Array<Symbol>] Required parameter names
|
|
204
|
+
# @param context [Hash] Available context
|
|
205
|
+
# @param tool_name [Symbol] Tool name for error messages
|
|
206
|
+
# @return [Hash] Parameters to pass to tool constructor
|
|
207
|
+
# @raise [ConfigurationError] If required parameter is missing
|
|
208
|
+
def extract_params(requirements, context, tool_name)
|
|
209
|
+
params = {}
|
|
210
|
+
|
|
211
|
+
requirements.each do |req|
|
|
212
|
+
unless context.key?(req)
|
|
213
|
+
raise ConfigurationError,
|
|
214
|
+
"Custom tool '#{tool_name}' requires '#{req}' but it was not provided. " \
|
|
215
|
+
"Ensure the tool's `creation_requirements` only includes supported keys: " \
|
|
216
|
+
":agent_name, :directory"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
params[req] = context[req]
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
params
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
# Centralized configuration defaults for SwarmSDK
|
|
5
|
+
#
|
|
6
|
+
# This module provides well-documented default values for all configurable
|
|
7
|
+
# aspects of the SDK. Values are organized by category and include explanations
|
|
8
|
+
# for their purpose and rationale.
|
|
9
|
+
#
|
|
10
|
+
# @example Accessing defaults
|
|
11
|
+
# SwarmSDK::Defaults::Timeouts::AGENT_REQUEST_SECONDS
|
|
12
|
+
# SwarmSDK::Defaults::Concurrency::GLOBAL_LIMIT
|
|
13
|
+
# SwarmSDK::Defaults::Limits::OUTPUT_CHARACTERS
|
|
14
|
+
module Defaults
|
|
15
|
+
# Concurrency limits for parallel execution
|
|
16
|
+
#
|
|
17
|
+
# These limits prevent overwhelming external services and ensure
|
|
18
|
+
# fair resource usage across the system.
|
|
19
|
+
module Concurrency
|
|
20
|
+
# Maximum concurrent API calls across entire swarm
|
|
21
|
+
#
|
|
22
|
+
# This limits total parallel LLM API requests to prevent rate limiting
|
|
23
|
+
# and excessive resource consumption. 50 is a balanced value that allows
|
|
24
|
+
# good parallelism while respecting API rate limits.
|
|
25
|
+
GLOBAL_LIMIT = 50
|
|
26
|
+
|
|
27
|
+
# Maximum parallel tool executions per agent
|
|
28
|
+
#
|
|
29
|
+
# Limits concurrent tool calls within a single agent. 10 allows
|
|
30
|
+
# meaningful parallelism (e.g., reading multiple files) without
|
|
31
|
+
# overwhelming the system.
|
|
32
|
+
LOCAL_LIMIT = 10
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Timeout values for various operations
|
|
36
|
+
#
|
|
37
|
+
# All timeouts are in seconds unless explicitly marked as milliseconds.
|
|
38
|
+
# Timeouts balance responsiveness with allowing enough time for operations
|
|
39
|
+
# to complete successfully.
|
|
40
|
+
module Timeouts
|
|
41
|
+
# LLM API request timeout (seconds)
|
|
42
|
+
#
|
|
43
|
+
# Default timeout for Claude/GPT API calls. 5 minutes accommodates
|
|
44
|
+
# reasoning models (o1, Claude with extended thinking) which can take
|
|
45
|
+
# longer to process complex queries.
|
|
46
|
+
AGENT_REQUEST_SECONDS = 300
|
|
47
|
+
|
|
48
|
+
# Bash command execution timeout (milliseconds)
|
|
49
|
+
#
|
|
50
|
+
# Default timeout for shell commands. 2 minutes balances allowing
|
|
51
|
+
# build/test commands while preventing runaway processes.
|
|
52
|
+
BASH_COMMAND_MS = 120_000
|
|
53
|
+
|
|
54
|
+
# Maximum Bash command timeout (milliseconds)
|
|
55
|
+
#
|
|
56
|
+
# Hard upper limit for bash commands. 10 minutes prevents indefinitely
|
|
57
|
+
# running commands while allowing long builds/tests.
|
|
58
|
+
BASH_COMMAND_MAX_MS = 600_000
|
|
59
|
+
|
|
60
|
+
# Web fetch timeout (seconds)
|
|
61
|
+
#
|
|
62
|
+
# Timeout for HTTP requests in WebFetch tool. 30 seconds is standard
|
|
63
|
+
# for web requests, allowing slow servers while timing out unresponsive ones.
|
|
64
|
+
WEB_FETCH_SECONDS = 30
|
|
65
|
+
|
|
66
|
+
# Shell hook executor timeout (seconds)
|
|
67
|
+
#
|
|
68
|
+
# Default timeout for hook shell commands. 60 seconds allows complex
|
|
69
|
+
# pre/post hooks while preventing indefinite blocking.
|
|
70
|
+
HOOK_SHELL_SECONDS = 60
|
|
71
|
+
|
|
72
|
+
# Workflow transformer command timeout (seconds)
|
|
73
|
+
#
|
|
74
|
+
# Timeout for input/output transformer bash commands. 60 seconds allows
|
|
75
|
+
# data transformation operations while preventing stalls.
|
|
76
|
+
TRANSFORMER_COMMAND_SECONDS = 60
|
|
77
|
+
|
|
78
|
+
# OpenAI responses API ID TTL (seconds)
|
|
79
|
+
#
|
|
80
|
+
# Time-to-live for cached response IDs. 5 minutes allows conversation
|
|
81
|
+
# continuity while preventing stale cache issues.
|
|
82
|
+
RESPONSES_API_TTL_SECONDS = 300
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Output and content size limits
|
|
86
|
+
#
|
|
87
|
+
# These limits prevent overwhelming context windows and ensure
|
|
88
|
+
# reasonable memory usage.
|
|
89
|
+
module Limits
|
|
90
|
+
# Maximum Bash output characters
|
|
91
|
+
#
|
|
92
|
+
# Truncates command output to prevent overwhelming agent context.
|
|
93
|
+
# 30,000 characters balances useful information with context constraints.
|
|
94
|
+
OUTPUT_CHARACTERS = 30_000
|
|
95
|
+
|
|
96
|
+
# Default lines to read from files
|
|
97
|
+
#
|
|
98
|
+
# When no explicit limit is set, Read tool returns first 2000 lines.
|
|
99
|
+
# This provides substantial file content while preventing huge files
|
|
100
|
+
# from overwhelming context.
|
|
101
|
+
READ_LINES = 2000
|
|
102
|
+
|
|
103
|
+
# Maximum characters per line in Read output
|
|
104
|
+
#
|
|
105
|
+
# Truncates very long lines to prevent single lines from consuming
|
|
106
|
+
# excessive context. 2000 characters per line is generous while
|
|
107
|
+
# protecting against minified files.
|
|
108
|
+
LINE_CHARACTERS = 2000
|
|
109
|
+
|
|
110
|
+
# Maximum WebFetch content length
|
|
111
|
+
#
|
|
112
|
+
# Limits web content fetched from URLs. 100,000 characters provides
|
|
113
|
+
# substantial page content while preventing huge pages from overwhelming context.
|
|
114
|
+
WEB_FETCH_CHARACTERS = 100_000
|
|
115
|
+
|
|
116
|
+
# Maximum Glob search results
|
|
117
|
+
#
|
|
118
|
+
# Limits number of file paths returned by Glob tool. 1000 results
|
|
119
|
+
# provides comprehensive search while preventing overwhelming output.
|
|
120
|
+
GLOB_RESULTS = 1000
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Storage limits for persistent data
|
|
124
|
+
module Storage
|
|
125
|
+
# Maximum size for single scratchpad entry (bytes)
|
|
126
|
+
#
|
|
127
|
+
# 3MB per entry prevents individual entries from consuming excessive storage
|
|
128
|
+
# while allowing substantial content (code, large texts).
|
|
129
|
+
ENTRY_SIZE_BYTES = 3_000_000
|
|
130
|
+
|
|
131
|
+
# Maximum total scratchpad storage (bytes)
|
|
132
|
+
#
|
|
133
|
+
# 100GB total storage provides ample room for extensive projects
|
|
134
|
+
# while preventing unbounded growth.
|
|
135
|
+
TOTAL_SIZE_BYTES = 100_000_000_000
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Context management settings
|
|
139
|
+
module Context
|
|
140
|
+
# Context usage percentage triggering compression warning
|
|
141
|
+
#
|
|
142
|
+
# When context usage reaches 60%, agents should consider compaction.
|
|
143
|
+
# This threshold provides buffer before hitting limits.
|
|
144
|
+
COMPRESSION_THRESHOLD_PERCENT = 60
|
|
145
|
+
|
|
146
|
+
# Message count between TodoWrite reminders
|
|
147
|
+
#
|
|
148
|
+
# After 8 messages without using TodoWrite, a gentle reminder is injected.
|
|
149
|
+
# Balances helpfulness without being annoying.
|
|
150
|
+
TODOWRITE_REMINDER_INTERVAL = 8
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Token estimation factors
|
|
154
|
+
#
|
|
155
|
+
# Used for approximate token counting when precise counts aren't available.
|
|
156
|
+
module TokenEstimation
|
|
157
|
+
# Characters per token for prose text
|
|
158
|
+
#
|
|
159
|
+
# Average of ~4 characters per token for natural language text.
|
|
160
|
+
# Based on empirical analysis of tokenization patterns.
|
|
161
|
+
CHARS_PER_TOKEN_PROSE = 4.0
|
|
162
|
+
|
|
163
|
+
# Characters per token for code
|
|
164
|
+
#
|
|
165
|
+
# Code tends to have shorter tokens due to symbols and operators.
|
|
166
|
+
# ~3.5 characters per token accounts for this density.
|
|
167
|
+
CHARS_PER_TOKEN_CODE = 3.5
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Logging configuration
|
|
171
|
+
module Logging
|
|
172
|
+
# Default MCP client log level
|
|
173
|
+
#
|
|
174
|
+
# WARN level suppresses verbose MCP client logs while still
|
|
175
|
+
# reporting important issues.
|
|
176
|
+
MCP_LOG_LEVEL = Logger::WARN
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Agent configuration defaults
|
|
180
|
+
#
|
|
181
|
+
# Default values for agent configuration when not explicitly specified.
|
|
182
|
+
module Agent
|
|
183
|
+
# Default LLM model identifier
|
|
184
|
+
#
|
|
185
|
+
# OpenAI's GPT-5 is used as the default model. This can be overridden
|
|
186
|
+
# per-agent or globally via all_agents configuration.
|
|
187
|
+
MODEL = "gpt-5"
|
|
188
|
+
|
|
189
|
+
# Default LLM provider
|
|
190
|
+
#
|
|
191
|
+
# OpenAI is the default provider. Supported providers include:
|
|
192
|
+
# openai, anthropic, gemini, deepseek, openrouter, bedrock, etc.
|
|
193
|
+
PROVIDER = "openai"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -44,6 +44,7 @@ module SwarmSDK
|
|
|
44
44
|
# - `agent_step`: Reconstructs assistant message with tool calls
|
|
45
45
|
# - `agent_stop`: Reconstructs final assistant message
|
|
46
46
|
# - `tool_result`: Reconstructs tool result message
|
|
47
|
+
# - `delegation_result`: Reconstructs tool result message from delegation
|
|
47
48
|
class EventsToMessages
|
|
48
49
|
class << self
|
|
49
50
|
# Reconstruct messages for an agent from event stream
|
|
@@ -90,6 +91,8 @@ module SwarmSDK
|
|
|
90
91
|
reconstruct_assistant_message(event)
|
|
91
92
|
when "tool_result"
|
|
92
93
|
reconstruct_tool_result_message(event)
|
|
94
|
+
when "delegation_result"
|
|
95
|
+
reconstruct_delegation_result_message(event)
|
|
93
96
|
end
|
|
94
97
|
|
|
95
98
|
messages << message if message
|
|
@@ -158,6 +161,21 @@ module SwarmSDK
|
|
|
158
161
|
)
|
|
159
162
|
end
|
|
160
163
|
|
|
164
|
+
# Reconstruct tool result message from delegation_result event
|
|
165
|
+
#
|
|
166
|
+
# delegation_result events are emitted when a delegation completes,
|
|
167
|
+
# and they should be converted to tool result messages in the conversation.
|
|
168
|
+
#
|
|
169
|
+
# @param event [Hash] delegation_result event
|
|
170
|
+
# @return [RubyLLM::Message] Tool result message
|
|
171
|
+
def reconstruct_delegation_result_message(event)
|
|
172
|
+
RubyLLM::Message.new(
|
|
173
|
+
role: :tool,
|
|
174
|
+
content: event[:result].to_s,
|
|
175
|
+
tool_call_id: event[:tool_call_id],
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
|
|
161
179
|
# Parse timestamp string to Time object
|
|
162
180
|
#
|
|
163
181
|
# @param timestamp [String, nil] ISO 8601 timestamp
|
|
@@ -167,7 +167,7 @@ module SwarmSDK
|
|
|
167
167
|
def create_hook_callback(hook_def, event_symbol, agent_name, swarm_name)
|
|
168
168
|
# Support both string and symbol keys (YAML may be symbolized)
|
|
169
169
|
command = hook_def[:command] || hook_def["command"]
|
|
170
|
-
timeout = hook_def[:timeout] || hook_def["timeout"] ||
|
|
170
|
+
timeout = hook_def[:timeout] || hook_def["timeout"] || SwarmSDK.config.hook_shell_timeout
|
|
171
171
|
|
|
172
172
|
lambda do |context|
|
|
173
173
|
input_json = build_input_json(context, event_symbol, agent_name)
|
|
@@ -191,7 +191,7 @@ module SwarmSDK
|
|
|
191
191
|
def create_all_agents_hook_callback(hook_def, event_symbol, swarm_name)
|
|
192
192
|
# Support both string and symbol keys (YAML may be symbolized)
|
|
193
193
|
command = hook_def[:command] || hook_def["command"]
|
|
194
|
-
timeout = hook_def[:timeout] || hook_def["timeout"] ||
|
|
194
|
+
timeout = hook_def[:timeout] || hook_def["timeout"] || SwarmSDK.config.hook_shell_timeout
|
|
195
195
|
|
|
196
196
|
lambda do |context|
|
|
197
197
|
# Agent name comes from context
|
|
@@ -217,7 +217,7 @@ module SwarmSDK
|
|
|
217
217
|
def create_swarm_hook_callback(hook_def, event_symbol, swarm_name)
|
|
218
218
|
# Support both string and symbol keys (YAML may be symbolized)
|
|
219
219
|
command = hook_def[:command] || hook_def["command"]
|
|
220
|
-
timeout = hook_def[:timeout] || hook_def["timeout"] ||
|
|
220
|
+
timeout = hook_def[:timeout] || hook_def["timeout"] || SwarmSDK.config.hook_shell_timeout
|
|
221
221
|
|
|
222
222
|
lambda do |context|
|
|
223
223
|
input_json = build_swarm_input_json(context, event_symbol, swarm_name)
|
|
@@ -47,7 +47,7 @@ module SwarmSDK
|
|
|
47
47
|
# )
|
|
48
48
|
# # => Result (continue or halt based on exit code)
|
|
49
49
|
class ShellExecutor
|
|
50
|
-
|
|
50
|
+
# NOTE: Timeout now accessed via SwarmSDK.config.hook_shell_timeout
|
|
51
51
|
|
|
52
52
|
class << self
|
|
53
53
|
# Execute a shell command hook
|
|
@@ -59,7 +59,9 @@ module SwarmSDK
|
|
|
59
59
|
# @param swarm_name [String, nil] Swarm name for environment variables
|
|
60
60
|
# @param event [Symbol] Event type for context-aware behavior
|
|
61
61
|
# @return [Result] Result based on exit code (continue or halt)
|
|
62
|
-
def execute(command:, input_json:, timeout:
|
|
62
|
+
def execute(command:, input_json:, timeout: nil, agent_name: nil, swarm_name: nil, event: nil)
|
|
63
|
+
timeout ||= SwarmSDK.config.hook_shell_timeout
|
|
64
|
+
|
|
63
65
|
# Build environment variables
|
|
64
66
|
env = build_environment(agent_name: agent_name, swarm_name: swarm_name)
|
|
65
67
|
|