swarm_sdk 2.2.0 → 2.3.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/agent/builder.rb +58 -0
  3. data/lib/swarm_sdk/agent/chat.rb +527 -1059
  4. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
  5. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  6. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
  7. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  8. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  9. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  10. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  11. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +12 -12
  12. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  13. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  14. data/lib/swarm_sdk/agent/context.rb +2 -2
  15. data/lib/swarm_sdk/agent/definition.rb +66 -154
  16. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  17. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  18. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  19. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  20. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  21. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  22. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  23. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  24. data/lib/swarm_sdk/configuration.rb +65 -543
  25. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  26. data/lib/swarm_sdk/context_compactor.rb +6 -11
  27. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  28. data/lib/swarm_sdk/context_management/context.rb +328 -0
  29. data/lib/swarm_sdk/defaults.rb +196 -0
  30. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  31. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  32. data/lib/swarm_sdk/log_collector.rb +179 -29
  33. data/lib/swarm_sdk/log_stream.rb +29 -0
  34. data/lib/swarm_sdk/node_context.rb +1 -1
  35. data/lib/swarm_sdk/observer/builder.rb +81 -0
  36. data/lib/swarm_sdk/observer/config.rb +45 -0
  37. data/lib/swarm_sdk/observer/manager.rb +236 -0
  38. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  39. data/lib/swarm_sdk/plugin.rb +93 -3
  40. data/lib/swarm_sdk/snapshot.rb +6 -6
  41. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  42. data/lib/swarm_sdk/state_restorer.rb +136 -151
  43. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  44. data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
  45. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  46. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  47. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  48. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  49. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  50. data/lib/swarm_sdk/swarm/tool_configurator.rb +42 -138
  51. data/lib/swarm_sdk/swarm.rb +137 -679
  52. data/lib/swarm_sdk/tools/bash.rb +11 -3
  53. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  54. data/lib/swarm_sdk/tools/edit.rb +8 -13
  55. data/lib/swarm_sdk/tools/glob.rb +9 -1
  56. data/lib/swarm_sdk/tools/grep.rb +7 -0
  57. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  58. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  59. data/lib/swarm_sdk/tools/read.rb +11 -13
  60. data/lib/swarm_sdk/tools/registry.rb +122 -10
  61. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +8 -5
  62. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  63. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  64. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  65. data/lib/swarm_sdk/tools/write.rb +8 -13
  66. data/lib/swarm_sdk/version.rb +1 -1
  67. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  68. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  69. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  70. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +3 -3
  71. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  72. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  73. data/lib/swarm_sdk.rb +33 -3
  74. metadata +67 -15
  75. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
@@ -58,7 +58,7 @@ module SwarmSDK
58
58
  # @return [ContextCompactor::Metrics] Compression metrics
59
59
  def compact
60
60
  start_time = Time.now
61
- original_messages = @chat.messages.dup
61
+ original_messages = @chat.messages
62
62
 
63
63
  # Emit compression_started event
64
64
  LogStream.emit(
@@ -308,7 +308,8 @@ module SwarmSDK
308
308
  response.content
309
309
  rescue StandardError => e
310
310
  # If summarization fails, create a simple fallback summary
311
- RubyLLM.logger.warn("ContextCompactor: Summarization failed: #{e.message}")
311
+ LogStream.emit_error(e, source: "context_compactor", context: "generate_summary", agent: @agent_name)
312
+ RubyLLM.logger.debug("ContextCompactor: Summarization failed: #{e.message}")
312
313
 
313
314
  <<~FALLBACK
314
315
  ## Summary
@@ -322,19 +323,13 @@ module SwarmSDK
322
323
 
323
324
  # Replace messages in the chat
324
325
  #
325
- # RubyLLM::Chat doesn't have a public API for replacing all messages,
326
- # so we need to work with the internal messages array.
326
+ # Delegates to the Chat's replace_messages method which provides
327
+ # a safe abstraction over the internal message array.
327
328
  #
328
329
  # @param new_messages [Array<RubyLLM::Message>] New message array
329
330
  # @return [void]
330
331
  def replace_messages(new_messages)
331
- # Clear existing messages
332
- @chat.messages.clear
333
-
334
- # Add new messages
335
- new_messages.each do |msg|
336
- @chat.messages << msg
337
- end
332
+ @chat.replace_messages(new_messages)
338
333
  end
339
334
  end
340
335
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module ContextManagement
5
+ # DSL for defining context management handlers
6
+ #
7
+ # This builder provides a clean, idiomatic way to register handlers for
8
+ # context warning thresholds. Handlers receive a rich context object
9
+ # with message manipulation methods.
10
+ #
11
+ # @example Basic usage
12
+ # context_management do
13
+ # on :warning_60 do |ctx|
14
+ # ctx.compress_tool_results(keep_recent: 10)
15
+ # end
16
+ #
17
+ # on :warning_80 do |ctx|
18
+ # ctx.prune_old_messages(keep_recent: 20)
19
+ # end
20
+ # end
21
+ #
22
+ # @example Progressive compression
23
+ # context_management do
24
+ # on :warning_60 do |ctx|
25
+ # ctx.compress_tool_results(keep_recent: 15, truncate_to: 500)
26
+ # end
27
+ #
28
+ # on :warning_80 do |ctx|
29
+ # ctx.prune_old_messages(keep_recent: 30)
30
+ # ctx.compress_tool_results(keep_recent: 5, truncate_to: 200)
31
+ # end
32
+ #
33
+ # on :warning_90 do |ctx|
34
+ # ctx.log_action("emergency_pruning", tokens_remaining: ctx.tokens_remaining)
35
+ # ctx.prune_old_messages(keep_recent: 15)
36
+ # end
37
+ # end
38
+ class Builder
39
+ # Map semantic event names to threshold percentages
40
+ EVENT_MAP = {
41
+ warning_60: 60,
42
+ warning_80: 80,
43
+ warning_90: 90,
44
+ }.freeze
45
+
46
+ def initialize
47
+ @handlers = {} # { threshold => block }
48
+ end
49
+
50
+ # Register a handler for a context warning threshold
51
+ #
52
+ # Handlers take full responsibility for managing context at their threshold.
53
+ # When a handler is registered for a threshold, automatic compression is disabled
54
+ # for that threshold.
55
+ #
56
+ # @param event [Symbol] Event name (:warning_60, :warning_80, :warning_90)
57
+ # @yield [ContextManagement::Context] Context with message manipulation methods
58
+ # @return [void]
59
+ #
60
+ # @raise [ArgumentError] If event is unknown or block is missing
61
+ #
62
+ # @example Compress tool results at 60%
63
+ # on :warning_60 do |ctx|
64
+ # ctx.compress_tool_results(keep_recent: 10)
65
+ # end
66
+ #
67
+ # @example Custom logic at 80%
68
+ # on :warning_80 do |ctx|
69
+ # if ctx.usage_percentage > 85
70
+ # ctx.prune_old_messages(keep_recent: 10)
71
+ # else
72
+ # ctx.summarize_old_exchanges(older_than: 20)
73
+ # end
74
+ # end
75
+ #
76
+ # @example Log and prune at 90%
77
+ # on :warning_90 do |ctx|
78
+ # ctx.log_action("critical_threshold", remaining: ctx.tokens_remaining)
79
+ # ctx.prune_old_messages(keep_recent: 10)
80
+ # end
81
+ def on(event, &block)
82
+ threshold = EVENT_MAP[event]
83
+ raise ArgumentError, "Unknown event: #{event}. Valid events: #{EVENT_MAP.keys.join(", ")}" unless threshold
84
+ raise ArgumentError, "Block required for #{event}" unless block
85
+
86
+ @handlers[threshold] = block
87
+ end
88
+
89
+ # Build hook definitions from handlers
90
+ #
91
+ # Creates Hooks::Definition objects that wrap user blocks to provide
92
+ # rich context objects instead of raw Hooks::Context. Each handler
93
+ # becomes a hook for the :context_warning event.
94
+ #
95
+ # @return [Array<Hooks::Definition>] Hook definitions for :context_warning event
96
+ def build
97
+ @handlers.map do |threshold, user_block|
98
+ # Create a hook that filters by threshold and wraps context
99
+ Hooks::Definition.new(
100
+ event: :context_warning,
101
+ matcher: nil, # No tool matching needed
102
+ priority: 0,
103
+ proc: create_threshold_matcher(threshold, user_block),
104
+ )
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ # Create a proc that matches threshold and wraps context
111
+ #
112
+ # @param target_threshold [Integer] Threshold to match (60, 80, 90)
113
+ # @param user_block [Proc] User's handler block
114
+ # @return [Proc] Hook proc
115
+ def create_threshold_matcher(target_threshold, user_block)
116
+ proc do |hooks_context|
117
+ # Only execute for matching threshold
118
+ current_threshold = hooks_context.metadata[:threshold]
119
+ next unless current_threshold == target_threshold
120
+
121
+ # Wrap in rich context object
122
+ rich_context = Context.new(hooks_context)
123
+ user_block.call(rich_context)
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,328 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module ContextManagement
5
+ # Rich context wrapper for context management handlers
6
+ #
7
+ # Provides a clean, developer-friendly API for manipulating the conversation
8
+ # context when warning thresholds are triggered. Wraps the lower-level
9
+ # Hooks::Context with message manipulation helpers.
10
+ #
11
+ # @example Basic usage in handler
12
+ # on :warning_60 do |ctx|
13
+ # ctx.compress_tool_results(keep_recent: 10)
14
+ # end
15
+ #
16
+ # @example Advanced usage with metrics
17
+ # on :warning_80 do |ctx|
18
+ # if ctx.usage_percentage > 85
19
+ # ctx.prune_old_messages(keep_recent: 10)
20
+ # ctx.log_action("aggressive_pruning", remaining: ctx.tokens_remaining)
21
+ # else
22
+ # ctx.compress_tool_results(keep_recent: 5, truncate_to: 100)
23
+ # end
24
+ # end
25
+ class Context
26
+ # Create a new context wrapper
27
+ #
28
+ # @param hooks_context [Hooks::Context] Lower-level hook context with metadata
29
+ def initialize(hooks_context)
30
+ @hooks_context = hooks_context
31
+ @chat = hooks_context.metadata[:chat]
32
+ end
33
+
34
+ # --- Context Metrics ---
35
+
36
+ # Current context usage percentage
37
+ #
38
+ # @return [Float] Usage percentage (0.0 to 100.0)
39
+ #
40
+ # @example
41
+ # if ctx.usage_percentage > 85
42
+ # ctx.prune_old_messages(keep_recent: 10)
43
+ # end
44
+ def usage_percentage
45
+ @hooks_context.metadata[:percentage]
46
+ end
47
+
48
+ # Threshold that triggered this handler
49
+ #
50
+ # @return [Integer] Threshold (60, 80, or 90)
51
+ #
52
+ # @example
53
+ # ctx.log_action("threshold_hit", threshold: ctx.threshold)
54
+ def threshold
55
+ @hooks_context.metadata[:threshold]
56
+ end
57
+
58
+ # Total tokens used so far
59
+ #
60
+ # @return [Integer] Token count
61
+ #
62
+ # @example
63
+ # ctx.log_action("usage", tokens: ctx.tokens_used)
64
+ def tokens_used
65
+ @hooks_context.metadata[:tokens_used]
66
+ end
67
+
68
+ # Tokens remaining in context window
69
+ #
70
+ # @return [Integer] Token count
71
+ #
72
+ # @example
73
+ # if ctx.tokens_remaining < 10000
74
+ # ctx.prune_old_messages(keep_recent: 5)
75
+ # end
76
+ def tokens_remaining
77
+ @hooks_context.metadata[:tokens_remaining]
78
+ end
79
+
80
+ # Total context window size
81
+ #
82
+ # @return [Integer] Token count
83
+ #
84
+ # @example
85
+ # buffer = ctx.context_limit * 0.1 # 10% buffer
86
+ def context_limit
87
+ @hooks_context.metadata[:context_limit]
88
+ end
89
+
90
+ # Agent name
91
+ #
92
+ # @return [Symbol] Agent identifier
93
+ #
94
+ # @example
95
+ # ctx.log_action("agent_context", agent: ctx.agent_name)
96
+ def agent_name
97
+ @hooks_context.agent_name
98
+ end
99
+
100
+ # --- Message Access ---
101
+
102
+ # Get all messages (copy for manipulation)
103
+ #
104
+ # @return [Array<RubyLLM::Message>] Message array
105
+ #
106
+ # @example
107
+ # ctx.messages.each do |msg|
108
+ # puts "#{msg.role}: #{msg.content.length} chars"
109
+ # end
110
+ def messages
111
+ @chat.messages
112
+ end
113
+
114
+ # Number of messages
115
+ #
116
+ # @return [Integer] Message count
117
+ #
118
+ # @example
119
+ # if ctx.message_count > 100
120
+ # ctx.prune_old_messages(keep_recent: 50)
121
+ # end
122
+ def message_count
123
+ @chat.message_count
124
+ end
125
+
126
+ # --- Message Manipulation ---
127
+
128
+ # Replace all messages with new array
129
+ #
130
+ # @param new_messages [Array<RubyLLM::Message>] New message array
131
+ # @return [void]
132
+ #
133
+ # @example
134
+ # new_msgs = ctx.messages.reject { |m| m.role == :tool }
135
+ # ctx.replace_messages(new_msgs)
136
+ def replace_messages(new_messages)
137
+ @chat.replace_messages(new_messages)
138
+ end
139
+
140
+ # Compress tool result messages to save context space
141
+ #
142
+ # Creates NEW message objects with truncated content (follows RubyLLM patterns).
143
+ # Truncates old tool results while keeping recent ones intact.
144
+ # Automatically marks compression as applied to prevent double compression.
145
+ #
146
+ # @param keep_recent [Integer] Number of recent tool results to preserve (default: 10)
147
+ # @param truncate_to [Integer] Max characters for truncated results (default: 200)
148
+ # @return [Integer] Number of messages compressed
149
+ #
150
+ # @example Light compression at 60%
151
+ # ctx.compress_tool_results(keep_recent: 15, truncate_to: 500)
152
+ #
153
+ # @example Aggressive compression at 80%
154
+ # ctx.compress_tool_results(keep_recent: 5, truncate_to: 100)
155
+ def compress_tool_results(keep_recent: 10, truncate_to: 200)
156
+ msgs = messages.dup
157
+ compressed_count = 0
158
+
159
+ # Find tool result messages (skip recent ones)
160
+ tool_indices = []
161
+ msgs.each_with_index do |msg, idx|
162
+ tool_indices << idx if msg.role == :tool
163
+ end
164
+
165
+ # Keep recent tool results, compress older ones
166
+ indices_to_compress = tool_indices[0...-keep_recent] || []
167
+
168
+ indices_to_compress.each do |idx|
169
+ msg = msgs[idx]
170
+ content = msg.content.to_s
171
+ next if content.length <= truncate_to
172
+
173
+ # Create NEW message with truncated content (NO instance_variable_set!)
174
+ truncated_content = "#{content[0...truncate_to]}... [truncated for context management]"
175
+
176
+ # Create new message object following RubyLLM patterns
177
+ msgs[idx] = RubyLLM::Message.new(
178
+ role: :tool,
179
+ content: truncated_content,
180
+ tool_call_id: msg.tool_call_id,
181
+ )
182
+ compressed_count += 1
183
+ end
184
+
185
+ replace_messages(msgs)
186
+
187
+ # Mark compression as applied to coordinate with ContextManager
188
+ mark_compression_applied
189
+
190
+ compressed_count
191
+ end
192
+
193
+ # Mark compression as applied in ContextManager
194
+ #
195
+ # Call this when your handler performs compression to prevent
196
+ # double compression from auto-compression logic.
197
+ #
198
+ # @return [void]
199
+ #
200
+ # @example Custom compression
201
+ # msgs = ctx.messages.map { |m| ... } # custom logic
202
+ # ctx.replace_messages(msgs)
203
+ # ctx.mark_compression_applied
204
+ def mark_compression_applied
205
+ return unless @chat.respond_to?(:context_manager)
206
+
207
+ @chat.context_manager.compression_applied = true
208
+ end
209
+
210
+ # Check if compression has already been applied
211
+ #
212
+ # @return [Boolean] True if compression was already applied
213
+ #
214
+ # @example Conditional compression
215
+ # unless ctx.compression_applied?
216
+ # ctx.compress_tool_results(keep_recent: 10)
217
+ # end
218
+ def compression_applied?
219
+ return false unless @chat.respond_to?(:context_manager)
220
+
221
+ !!@chat.context_manager.compression_applied
222
+ end
223
+
224
+ # Remove old messages from history
225
+ #
226
+ # Keeps system message (if any) and recent exchanges.
227
+ # This is more aggressive than compression and loses context.
228
+ #
229
+ # @param keep_recent [Integer] Number of recent messages to keep (default: 20)
230
+ # @return [Integer] Number of messages removed
231
+ #
232
+ # @example Prune at 80% threshold
233
+ # ctx.prune_old_messages(keep_recent: 30)
234
+ #
235
+ # @example Emergency pruning at 90%
236
+ # ctx.prune_old_messages(keep_recent: 10)
237
+ def prune_old_messages(keep_recent: 20)
238
+ msgs = messages.dup
239
+ original_count = msgs.size
240
+
241
+ # Always keep system message if present
242
+ system_msg = msgs.first if msgs.first&.role == :system
243
+ non_system = system_msg ? msgs[1..] : msgs
244
+
245
+ # Keep only recent messages
246
+ if non_system.size > keep_recent
247
+ kept = non_system.last(keep_recent)
248
+ new_msgs = system_msg ? [system_msg] + kept : kept
249
+ replace_messages(new_msgs)
250
+ original_count - new_msgs.size
251
+ else
252
+ 0
253
+ end
254
+ end
255
+
256
+ # Summarize old message exchanges
257
+ #
258
+ # Groups old user/assistant pairs and replaces with summary.
259
+ # This is a placeholder - actual implementation would use LLM.
260
+ #
261
+ # @param older_than [Integer] Messages older than this index get summarized
262
+ # @return [Integer] Number of exchanges summarized
263
+ #
264
+ # @example
265
+ # ctx.summarize_old_exchanges(older_than: 10)
266
+ def summarize_old_exchanges(older_than: 10)
267
+ # For now, this is a marker - full implementation would call LLM
268
+ # to summarize exchanges. We provide the API for developers to
269
+ # implement their own summarization logic.
270
+ 0
271
+ end
272
+
273
+ # Custom message transformation
274
+ #
275
+ # Apply a block to transform messages. This gives full control
276
+ # over message manipulation for custom strategies.
277
+ #
278
+ # @yield [Array<RubyLLM::Message>] Current messages
279
+ # @yieldreturn [Array<RubyLLM::Message>] Transformed messages
280
+ # @return [void]
281
+ #
282
+ # @example Remove specific tool results
283
+ # ctx.transform_messages do |msgs|
284
+ # msgs.reject { |m| m.role == :tool && m.content.include?("verbose output") }
285
+ # end
286
+ #
287
+ # @example Custom compression logic
288
+ # ctx.transform_messages do |msgs|
289
+ # msgs.map do |m|
290
+ # if m.role == :tool && m.content.length > 1000
291
+ # RubyLLM::Message.new(role: :tool, content: m.content[0..500], tool_call_id: m.tool_call_id)
292
+ # else
293
+ # m
294
+ # end
295
+ # end
296
+ # end
297
+ def transform_messages
298
+ new_msgs = yield(messages.dup)
299
+ replace_messages(new_msgs)
300
+ end
301
+
302
+ # Log a context management action
303
+ #
304
+ # Emits a log event for tracking what actions were taken.
305
+ # Useful for debugging and monitoring context management strategies.
306
+ #
307
+ # @param action [String] Description of action taken
308
+ # @param details [Hash] Additional details
309
+ # @return [void]
310
+ #
311
+ # @example Log compression action
312
+ # ctx.log_action("compressed_tool_results", count: 5)
313
+ #
314
+ # @example Log emergency action
315
+ # ctx.log_action("emergency_pruning", remaining: ctx.tokens_remaining)
316
+ def log_action(action, details = {})
317
+ LogStream.emit(
318
+ type: "context_management_action",
319
+ agent: agent_name,
320
+ threshold: threshold,
321
+ action: action,
322
+ usage_percentage: usage_percentage,
323
+ **details,
324
+ )
325
+ end
326
+ end
327
+ end
328
+ 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