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.
Files changed (134) hide show
  1. checksums.yaml +4 -4
  2. data/{CHANGELOG.md → CHANGELOG.claude-swarm.md} +10 -0
  3. data/CLAUDE.md +346 -191
  4. data/decisions/2025-11-22-001-global-agent-registry.md +172 -0
  5. data/docs/v2/CHANGELOG.swarm_cli.md +20 -0
  6. data/docs/v2/CHANGELOG.swarm_memory.md +146 -1
  7. data/docs/v2/CHANGELOG.swarm_sdk.md +433 -10
  8. data/docs/v2/README.md +20 -5
  9. data/docs/v2/guides/complete-tutorial.md +95 -9
  10. data/docs/v2/guides/getting-started.md +10 -8
  11. data/docs/v2/guides/memory-adapters.md +41 -0
  12. data/docs/v2/guides/migrating-to-2.x.md +746 -0
  13. data/docs/v2/guides/plugins.md +52 -5
  14. data/docs/v2/guides/rails-integration.md +6 -0
  15. data/docs/v2/guides/snapshots.md +14 -14
  16. data/docs/v2/guides/swarm-memory.md +2 -13
  17. data/docs/v2/reference/architecture-flow.md +3 -3
  18. data/docs/v2/reference/cli.md +0 -1
  19. data/docs/v2/reference/configuration_reference.md +300 -0
  20. data/docs/v2/reference/event_payload_structures.md +27 -5
  21. data/docs/v2/reference/ruby-dsl.md +614 -18
  22. data/docs/v2/reference/swarm_memory_technical_details.md +7 -29
  23. data/docs/v2/reference/yaml.md +172 -54
  24. data/examples/snapshot_demo.rb +2 -2
  25. data/lib/claude_swarm/mcp_generator.rb +8 -21
  26. data/lib/claude_swarm/orchestrator.rb +8 -1
  27. data/lib/claude_swarm/version.rb +1 -1
  28. data/lib/swarm_cli/commands/run.rb +2 -2
  29. data/lib/swarm_cli/config_loader.rb +11 -11
  30. data/lib/swarm_cli/formatters/human_formatter.rb +0 -33
  31. data/lib/swarm_cli/interactive_repl.rb +2 -2
  32. data/lib/swarm_cli/ui/icons.rb +0 -23
  33. data/lib/swarm_cli/version.rb +1 -1
  34. data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
  35. data/lib/swarm_memory/core/semantic_index.rb +10 -2
  36. data/lib/swarm_memory/core/storage.rb +7 -2
  37. data/lib/swarm_memory/dsl/memory_config.rb +37 -0
  38. data/lib/swarm_memory/integration/sdk_plugin.rb +201 -28
  39. data/lib/swarm_memory/optimization/defragmenter.rb +1 -1
  40. data/lib/swarm_memory/prompts/memory_researcher.md.erb +0 -1
  41. data/lib/swarm_memory/tools/load_skill.rb +0 -1
  42. data/lib/swarm_memory/tools/memory_edit.rb +2 -1
  43. data/lib/swarm_memory/tools/memory_read.rb +1 -1
  44. data/lib/swarm_memory/version.rb +1 -1
  45. data/lib/swarm_memory.rb +8 -6
  46. data/lib/swarm_sdk/agent/builder.rb +58 -0
  47. data/lib/swarm_sdk/agent/chat.rb +527 -1061
  48. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +13 -88
  49. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  50. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +108 -46
  51. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  52. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +267 -0
  53. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +3 -3
  54. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  55. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +11 -13
  56. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  57. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +146 -0
  58. data/lib/swarm_sdk/agent/context.rb +1 -2
  59. data/lib/swarm_sdk/agent/definition.rb +66 -154
  60. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  61. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  62. data/lib/swarm_sdk/agent_registry.rb +146 -0
  63. data/lib/swarm_sdk/builders/base_builder.rb +488 -0
  64. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  65. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  66. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  67. data/lib/swarm_sdk/config.rb +302 -0
  68. data/lib/swarm_sdk/configuration/parser.rb +373 -0
  69. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  70. data/lib/swarm_sdk/configuration.rb +77 -546
  71. data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
  72. data/lib/swarm_sdk/context_compactor.rb +6 -11
  73. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  74. data/lib/swarm_sdk/context_management/context.rb +328 -0
  75. data/lib/swarm_sdk/custom_tool_registry.rb +226 -0
  76. data/lib/swarm_sdk/defaults.rb +196 -0
  77. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  78. data/lib/swarm_sdk/hooks/adapter.rb +3 -3
  79. data/lib/swarm_sdk/hooks/shell_executor.rb +4 -2
  80. data/lib/swarm_sdk/log_collector.rb +179 -29
  81. data/lib/swarm_sdk/log_stream.rb +29 -0
  82. data/lib/swarm_sdk/models.json +4333 -1
  83. data/lib/swarm_sdk/models.rb +43 -2
  84. data/lib/swarm_sdk/node_context.rb +1 -1
  85. data/lib/swarm_sdk/observer/builder.rb +81 -0
  86. data/lib/swarm_sdk/observer/config.rb +45 -0
  87. data/lib/swarm_sdk/observer/manager.rb +236 -0
  88. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  89. data/lib/swarm_sdk/plugin.rb +95 -5
  90. data/lib/swarm_sdk/result.rb +52 -0
  91. data/lib/swarm_sdk/snapshot.rb +6 -6
  92. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  93. data/lib/swarm_sdk/state_restorer.rb +136 -151
  94. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  95. data/lib/swarm_sdk/swarm/agent_initializer.rb +181 -137
  96. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  97. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  98. data/lib/swarm_sdk/swarm/hook_triggers.rb +151 -0
  99. data/lib/swarm_sdk/swarm/logging_callbacks.rb +341 -0
  100. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  101. data/lib/swarm_sdk/swarm/tool_configurator.rb +58 -140
  102. data/lib/swarm_sdk/swarm.rb +203 -683
  103. data/lib/swarm_sdk/tools/bash.rb +14 -8
  104. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  105. data/lib/swarm_sdk/tools/edit.rb +8 -13
  106. data/lib/swarm_sdk/tools/glob.rb +12 -4
  107. data/lib/swarm_sdk/tools/grep.rb +7 -0
  108. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  109. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  110. data/lib/swarm_sdk/tools/read.rb +16 -18
  111. data/lib/swarm_sdk/tools/registry.rb +122 -10
  112. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +9 -5
  113. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  114. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  115. data/lib/swarm_sdk/tools/web_fetch.rb +20 -17
  116. data/lib/swarm_sdk/tools/write.rb +8 -13
  117. data/lib/swarm_sdk/version.rb +1 -1
  118. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  119. data/lib/swarm_sdk/workflow/builder.rb +192 -0
  120. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  121. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +7 -5
  122. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +5 -3
  123. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  124. data/lib/swarm_sdk.rb +294 -108
  125. data/rubocop/cop/security/no_reflection_methods.rb +1 -1
  126. data/swarm_cli.gemspec +1 -1
  127. data/swarm_memory.gemspec +8 -3
  128. data/swarm_sdk.gemspec +6 -4
  129. data/team_full.yml +124 -320
  130. metadata +42 -14
  131. data/lib/swarm_memory/chat_extension.rb +0 -34
  132. data/lib/swarm_memory/tools/memory_multi_edit.rb +0 -281
  133. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
  134. /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"] || ShellExecutor::DEFAULT_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"] || ShellExecutor::DEFAULT_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"] || ShellExecutor::DEFAULT_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
- DEFAULT_TIMEOUT = 60
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: DEFAULT_TIMEOUT, agent_name: nil, swarm_name: nil, event: nil)
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