swarm_sdk 2.7.13 → 3.0.0.alpha1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -1,680 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- # Builder provides fluent API for configuring agents
6
- #
7
- # This class offers a Ruby DSL for defining agents with a clean, readable syntax.
8
- # It collects configuration and then adds the agent to the swarm.
9
- #
10
- # @example
11
- # agent :backend do
12
- # model "gpt-5"
13
- # prompt "You build APIs"
14
- # tools :Read, :Write, :Bash
15
- #
16
- # hook :pre_tool_use, matcher: "Bash" do |ctx|
17
- # SwarmSDK::Hooks::Result.halt("Blocked!") if dangerous?(ctx)
18
- # end
19
- # end
20
- class Builder
21
- # Expose default_permissions for Swarm::Builder to set from all_agents
22
- attr_writer :default_permissions
23
-
24
- # Expose mcp_servers for tests
25
- attr_reader :mcp_servers
26
-
27
- # Get tools list as array for validation
28
- #
29
- # @return [Array<Symbol>] List of tools
30
- def tools_list
31
- @tools.to_a
32
- end
33
-
34
- def initialize(name)
35
- @name = name
36
- @description = nil
37
- @model = "gpt-5"
38
- @provider = nil
39
- @base_url = nil
40
- @api_version = nil
41
- @context_window = nil
42
- @system_prompt = nil
43
- # Use Set for tools to automatically handle duplicates when tools() is called multiple times.
44
- # This ensures that if someone does: tools :Read; tools :Write; tools :Read
45
- # the final set contains only [:Read, :Write] without duplicates.
46
- # We convert to Array in to_definition for compatibility with Agent::Definition.
47
- @tools = Set.new
48
- @delegates_to = []
49
- @directory = "."
50
- @parameters = {}
51
- @headers = {}
52
- @request_timeout = nil
53
- @turn_timeout = nil
54
- @mcp_servers = []
55
- @disable_default_tools = nil # nil = include all default tools
56
- @bypass_permissions = false
57
- @coding_agent = nil # nil = not set (will default to false in Definition)
58
- @assume_model_exists = nil
59
- @hooks = []
60
- @permissions_config = {}
61
- @default_permissions = {} # Set by SwarmBuilder from all_agents
62
- @memory_config = nil
63
- @shared_across_delegations = nil # nil = not set (will default to false in Definition)
64
- @streaming = nil # nil = not set (will use global config default)
65
- @thinking = nil # nil = not set (extended thinking disabled)
66
- @context_management_config = nil # Context management DSL hooks
67
- end
68
-
69
- # Set/get agent model
70
- def model(model_name = :__not_provided__)
71
- return @model if model_name == :__not_provided__
72
-
73
- @model = model_name
74
- end
75
-
76
- # Set/get provider
77
- def provider(provider_name = :__not_provided__)
78
- return @provider if provider_name == :__not_provided__
79
-
80
- @provider = provider_name
81
- end
82
-
83
- # Set/get base URL
84
- def base_url(url = :__not_provided__)
85
- return @base_url if url == :__not_provided__
86
-
87
- @base_url = url
88
- end
89
-
90
- # Set/get API version (OpenAI-compatible providers only)
91
- def api_version(version = :__not_provided__)
92
- return @api_version if version == :__not_provided__
93
-
94
- @api_version = version
95
- end
96
-
97
- # Set/get explicit context window override
98
- def context_window(tokens = :__not_provided__)
99
- return @context_window if tokens == :__not_provided__
100
-
101
- @context_window = tokens
102
- end
103
-
104
- # Set/get LLM parameters
105
- def parameters(params = :__not_provided__)
106
- return @parameters if params == :__not_provided__
107
-
108
- @parameters = params
109
- end
110
-
111
- # Set/get custom HTTP headers
112
- def headers(header_hash = :__not_provided__)
113
- return @headers if header_hash == :__not_provided__
114
-
115
- @headers = header_hash
116
- end
117
-
118
- # Set/get request timeout
119
- def request_timeout(seconds = :__not_provided__)
120
- return @request_timeout if seconds == :__not_provided__
121
-
122
- @request_timeout = seconds
123
- end
124
-
125
- # Set/get turn timeout
126
- def turn_timeout(seconds = :__not_provided__)
127
- return @turn_timeout if seconds == :__not_provided__
128
-
129
- @turn_timeout = seconds
130
- end
131
-
132
- # Add an MCP server configuration
133
- #
134
- # @param name [Symbol] Server name
135
- # @param type [Symbol] Transport type (:stdio, :sse, :http)
136
- # @param tools [Array<Symbol>, nil] Tool names to expose (nil = discover all tools)
137
- # @param options [Hash] Transport-specific options
138
- #
139
- # @example stdio transport with discovery
140
- # mcp_server :filesystem, type: :stdio, command: "npx", args: ["-y", "@modelcontextprotocol/server-filesystem"]
141
- #
142
- # @example stdio transport with filtered tools (faster boot)
143
- # mcp_server :codebase, type: :stdio, command: "mcp-server-codebase", tools: [:search_code, :list_files]
144
- #
145
- # @example SSE transport
146
- # mcp_server :web, type: :sse, url: "https://example.com/mcp", headers: { authorization: "Bearer token" }
147
- #
148
- # @example HTTP/streamable transport
149
- # mcp_server :api, type: :http, url: "https://api.example.com/mcp", timeout: 60
150
- def mcp_server(name, **options)
151
- server_config = { name: name }.merge(options)
152
- @mcp_servers << server_config
153
- end
154
-
155
- # Disable default tools
156
- #
157
- # @param value [Boolean, Array<Symbol>]
158
- # - true: Disable ALL default tools
159
- # - Array of symbols: Disable specific tools (e.g., [:Think, :TodoWrite])
160
- #
161
- # @example Disable all default tools
162
- # disable_default_tools true
163
- #
164
- # @example Disable specific tools (array)
165
- # disable_default_tools [:Think, :TodoWrite]
166
- #
167
- # @example Disable specific tools (separate arguments)
168
- # disable_default_tools :Think, :TodoWrite
169
- def disable_default_tools(*tools)
170
- # Handle different argument forms
171
- @disable_default_tools = case tools.size
172
- when 0
173
- nil
174
- when 1
175
- # Single argument: could be true/false/array
176
- tools.first
177
- else
178
- # Multiple arguments: treat as array of tool names
179
- tools.map(&:to_sym)
180
- end
181
- end
182
-
183
- # Set bypass_permissions flag
184
- def bypass_permissions(enabled)
185
- @bypass_permissions = enabled
186
- end
187
-
188
- # Set coding_agent flag
189
- #
190
- # When true, includes the base system prompt for coding tasks.
191
- # When false (default), uses only the custom system prompt.
192
- #
193
- # @param enabled [Boolean] Whether to include base coding prompt
194
- # @return [void]
195
- #
196
- # @example
197
- # coding_agent true # Include base prompt for coding tasks
198
- def coding_agent(enabled)
199
- @coding_agent = enabled
200
- end
201
-
202
- # Set assume_model_exists flag
203
- def assume_model_exists(enabled)
204
- @assume_model_exists = enabled
205
- end
206
-
207
- # Set system prompt (matches YAML key)
208
- def system_prompt(text)
209
- @system_prompt = text
210
- end
211
-
212
- # Set description
213
- def description(text)
214
- @description = text
215
- end
216
-
217
- # Set or add tools
218
- #
219
- # Uses Set internally to automatically deduplicate tool names across multiple calls.
220
- # This allows calling tools() multiple times without worrying about duplicates.
221
- #
222
- # @param tool_names [Array<Symbol>] Tool names to add
223
- # @param include_default [Boolean] Whether to include default tools (Read, Grep, etc.)
224
- # @param replace [Boolean] If true, replaces existing tools instead of merging (default: false)
225
- #
226
- # @example Basic usage with defaults
227
- # tools :Grep, :Read # include_default: true is implicit
228
- #
229
- # @example Explicit tools only, no defaults
230
- # tools :Grep, :Read, include_default: false
231
- #
232
- # @example Multiple calls (cumulative, automatic deduplication)
233
- # tools :Read
234
- # tools :Write, :Edit # @tools now contains Set[:Read, :Write, :Edit]
235
- # tools :Read # Still Set[:Read, :Write, :Edit] - no duplicate
236
- #
237
- # @example Replace tools (for markdown overrides)
238
- # tools :Read, :Write, replace: true # Replaces all existing tools
239
- def tools(*tool_names, include_default: true, replace: false)
240
- @tools = Set.new if replace
241
- @tools.merge(tool_names.map(&:to_sym))
242
- # When include_default is false, disable all default tools
243
- @disable_default_tools = true unless include_default
244
- end
245
-
246
- # Add tools from all_agents configuration
247
- #
248
- # Used by Swarm::Builder to add all_agents tools.
249
- # Since we use Set, order doesn't matter and duplicates are handled automatically.
250
- #
251
- # @param tool_names [Array] Tool names to add
252
- # @return [void]
253
- def prepend_tools(*tool_names)
254
- @tools.merge(tool_names.map(&:to_sym))
255
- end
256
-
257
- # Set directory
258
- def directory(dir)
259
- @directory = dir
260
- end
261
-
262
- # Set delegation targets
263
- #
264
- # Supports multiple formats for flexibility:
265
- #
266
- # @example Simple array (backwards compatible)
267
- # delegates_to :frontend, :backend, :qa
268
- #
269
- # @example Hash with custom tool names
270
- # delegates_to frontend: "AskFrontend",
271
- # backend: "GetBackendHelp",
272
- # qa: "RequestReview"
273
- #
274
- # @example Mixed - some auto, some custom
275
- # delegates_to :frontend,
276
- # backend: "GetBackendHelp",
277
- # :qa
278
- #
279
- # @example With delegation options (preserve_context controls context persistence)
280
- # delegates_to :frontend,
281
- # { agent: :backend, tool_name: "AskBackend", preserve_context: false }
282
- #
283
- # @param agent_names_and_options [Array<Symbol, Hash>] Agent names and/or hash with custom tool names
284
- # @return [void]
285
- def delegates_to(*agent_names_and_options)
286
- agent_names_and_options.each do |item|
287
- case item
288
- when Symbol, String
289
- # Simple format: :frontend
290
- @delegates_to << { agent: item.to_sym, tool_name: nil, preserve_context: true }
291
- when Hash
292
- if item.key?(:agent)
293
- # Full config format: { agent: :backend, tool_name: "Custom", preserve_context: false }
294
- @delegates_to << {
295
- agent: item[:agent].to_sym,
296
- tool_name: item[:tool_name],
297
- preserve_context: item.fetch(:preserve_context, true),
298
- }
299
- else
300
- # Hash format: { frontend: "AskFrontend", backend: nil }
301
- item.each do |agent, tool_name|
302
- @delegates_to << { agent: agent.to_sym, tool_name: tool_name, preserve_context: true }
303
- end
304
- end
305
- else
306
- raise ConfigurationError, "delegates_to accepts Symbols or Hashes, got #{item.class}"
307
- end
308
- end
309
- end
310
-
311
- # Add a hook (Ruby block OR shell command)
312
- #
313
- # @example Ruby block
314
- # hook :pre_tool_use, matcher: "Bash" do |ctx|
315
- # HookResult.halt("Blocked") if dangerous?(ctx)
316
- # end
317
- #
318
- # @example Shell command
319
- # hook :pre_tool_use, matcher: "Bash", command: "validate.sh"
320
- def hook(event, matcher: nil, command: nil, timeout: nil, &block)
321
- @hooks << {
322
- event: event,
323
- matcher: matcher,
324
- command: command,
325
- timeout: timeout,
326
- block: block,
327
- }
328
- end
329
-
330
- # Configure permissions for this agent
331
- #
332
- # @example
333
- # permissions do
334
- # Write.allow_paths "backend/**/*"
335
- # Write.deny_paths "backend/secrets/**"
336
- # end
337
- def permissions(&block)
338
- @permissions_config = PermissionsBuilder.build(&block)
339
- end
340
-
341
- # Configure delegation isolation mode
342
- #
343
- # @param enabled [Boolean] If true, allows sharing instances across delegations (old behavior)
344
- # If false (default), creates isolated instances per delegation
345
- # @return [self] Returns self for method chaining
346
- #
347
- # @example
348
- # shared_across_delegations true # Allow sharing (old behavior)
349
- def shared_across_delegations(enabled)
350
- @shared_across_delegations = enabled
351
- self
352
- end
353
-
354
- # Enable or disable streaming for LLM API responses
355
- #
356
- # @param value [Boolean] If true (default), enables streaming; if false, disables it
357
- # @return [self] Returns self for method chaining
358
- #
359
- # @example Enable streaming (default)
360
- # streaming true
361
- #
362
- # @example Disable streaming
363
- # streaming false
364
- def streaming(value = true)
365
- @streaming = value
366
- self
367
- end
368
-
369
- # Check if streaming has been explicitly set
370
- #
371
- # @return [Boolean] true if streaming was explicitly set, false otherwise
372
- def streaming_set?
373
- !@streaming.nil?
374
- end
375
-
376
- # Configure extended thinking for this agent
377
- #
378
- # Extended thinking allows models to reason through complex problems before responding.
379
- # For Anthropic models, specify a budget (token count). For OpenAI models, specify effort.
380
- # Both can be specified for cross-provider compatibility.
381
- #
382
- # @param effort [Symbol, String, nil] Reasoning effort level (:low, :medium, :high) — used by OpenAI
383
- # @param budget [Integer, nil] Token budget for thinking — used by Anthropic
384
- # @return [self] Returns self for method chaining
385
- #
386
- # @example Anthropic thinking with budget
387
- # thinking budget: 10_000
388
- #
389
- # @example OpenAI reasoning effort
390
- # thinking effort: :high
391
- #
392
- # @example Cross-provider (both)
393
- # thinking effort: :high, budget: 10_000
394
- def thinking(effort: nil, budget: nil)
395
- raise ArgumentError, "thinking requires :effort or :budget" if effort.nil? && budget.nil?
396
-
397
- @thinking = { effort: effort, budget: budget }.compact
398
- self
399
- end
400
-
401
- # Check if thinking has been explicitly set
402
- #
403
- # @return [Boolean] true if thinking was explicitly configured
404
- def thinking_set?
405
- !@thinking.nil?
406
- end
407
-
408
- # Configure context management handlers
409
- #
410
- # Define custom handlers for context warning thresholds (60%, 80%, 90%).
411
- # Handlers receive a rich context object with message manipulation methods.
412
- # When a custom handler is registered, automatic compression is disabled
413
- # for that threshold, giving full control to the handler.
414
- #
415
- # @yield Context management DSL block
416
- # @return [void]
417
- #
418
- # @example Basic compression at 60%
419
- # context_management do
420
- # on :warning_60 do |ctx|
421
- # ctx.compress_tool_results(keep_recent: 10)
422
- # end
423
- # end
424
- #
425
- # @example Multiple thresholds with different strategies
426
- # context_management do
427
- # on :warning_60 do |ctx|
428
- # ctx.compress_tool_results(keep_recent: 15, truncate_to: 500)
429
- # end
430
- #
431
- # on :warning_80 do |ctx|
432
- # ctx.prune_old_messages(keep_recent: 30)
433
- # ctx.compress_tool_results(keep_recent: 5, truncate_to: 200)
434
- # end
435
- #
436
- # on :warning_90 do |ctx|
437
- # ctx.log_action("emergency_pruning", remaining: ctx.tokens_remaining)
438
- # ctx.prune_old_messages(keep_recent: 15)
439
- # end
440
- # end
441
- #
442
- # @example Conditional logic based on metrics
443
- # context_management do
444
- # on :warning_80 do |ctx|
445
- # if ctx.usage_percentage > 85
446
- # ctx.prune_old_messages(keep_recent: 10)
447
- # else
448
- # ctx.compress_tool_results(keep_recent: 5)
449
- # end
450
- # end
451
- # end
452
- def context_management(&block)
453
- builder = ContextManagement::Builder.new
454
- builder.instance_eval(&block)
455
- @context_management_config = builder.build
456
- end
457
-
458
- # Set permissions directly from hash (for YAML translation)
459
- #
460
- # This is intentionally separate from permissions() to keep the DSL clean.
461
- # Called by Configuration when translating YAML permissions.
462
- #
463
- # @param hash [Hash] Permissions configuration hash
464
- # @return [void]
465
- def permissions_hash=(hash)
466
- @permissions_config = hash || {}
467
- end
468
-
469
- # Check if model has been explicitly set (not default)
470
- #
471
- # Used by Swarm::Builder to determine if all_agents model should apply.
472
- #
473
- # @return [Boolean] true if model was explicitly set
474
- def model_set?
475
- @model != "gpt-5"
476
- end
477
-
478
- # Check if provider has been explicitly set
479
- #
480
- # Used by Swarm::Builder to determine if all_agents provider should apply.
481
- #
482
- # @return [Boolean] true if provider was explicitly set
483
- def provider_set?
484
- !@provider.nil?
485
- end
486
-
487
- # Check if base_url has been explicitly set
488
- #
489
- # Used by Swarm::Builder to determine if all_agents base_url should apply.
490
- #
491
- # @return [Boolean] true if base_url was explicitly set
492
- def base_url_set?
493
- !@base_url.nil?
494
- end
495
-
496
- # Check if api_version has been explicitly set
497
- #
498
- # Used by Swarm::Builder to determine if all_agents api_version should apply.
499
- #
500
- # @return [Boolean] true if api_version was explicitly set
501
- def api_version_set?
502
- !@api_version.nil?
503
- end
504
-
505
- # Check if request_timeout has been explicitly set
506
- #
507
- # Used by Swarm::Builder to determine if all_agents request_timeout should apply.
508
- #
509
- # @return [Boolean] true if request_timeout was explicitly set
510
- def request_timeout_set?
511
- !@request_timeout.nil?
512
- end
513
-
514
- # Check if turn_timeout has been explicitly set
515
- #
516
- # Used by Swarm::Builder to determine if all_agents turn_timeout should apply.
517
- #
518
- # @return [Boolean] true if turn_timeout was explicitly set
519
- def turn_timeout_set?
520
- !@turn_timeout.nil?
521
- end
522
-
523
- # Check if coding_agent has been explicitly set
524
- #
525
- # Used by Swarm::Builder to determine if all_agents coding_agent should apply.
526
- #
527
- # @return [Boolean] true if coding_agent was explicitly set
528
- def coding_agent_set?
529
- !@coding_agent.nil?
530
- end
531
-
532
- # Check if parameters have been set
533
- #
534
- # Used by Swarm::Builder for merging all_agents parameters.
535
- #
536
- # @return [Boolean] true if parameters were set
537
- def parameters_set?
538
- @parameters.any?
539
- end
540
-
541
- # Check if headers have been set
542
- #
543
- # Used by Swarm::Builder for merging all_agents headers.
544
- #
545
- # @return [Boolean] true if headers were set
546
- def headers_set?
547
- @headers.any?
548
- end
549
-
550
- # Build and return an Agent::Definition
551
- #
552
- # This method converts the builder's configuration into a validated
553
- # Agent::Definition object. The caller is responsible for adding it to a swarm.
554
- #
555
- # Converts @tools Set to Array here because Agent::Definition expects an array.
556
- # The Set was only used during building to handle duplicates efficiently.
557
- #
558
- # @return [Agent::Definition] Fully configured and validated agent definition
559
- def to_definition
560
- agent_config = {
561
- description: @description || "Agent #{@name}",
562
- model: @model,
563
- system_prompt: @system_prompt,
564
- tools: @tools.to_a, # Convert Set to Array for Agent::Definition compatibility
565
- delegates_to: @delegates_to,
566
- directory: @directory,
567
- }
568
-
569
- # Add optional fields
570
- agent_config[:provider] = @provider if @provider
571
- agent_config[:base_url] = @base_url if @base_url
572
- agent_config[:api_version] = @api_version if @api_version
573
- agent_config[:context_window] = @context_window if @context_window
574
- agent_config[:parameters] = @parameters if @parameters.any?
575
- agent_config[:headers] = @headers if @headers.any?
576
- agent_config[:request_timeout] = @request_timeout if @request_timeout
577
- agent_config[:turn_timeout] = @turn_timeout if @turn_timeout
578
- agent_config[:mcp_servers] = @mcp_servers if @mcp_servers.any?
579
- agent_config[:disable_default_tools] = @disable_default_tools unless @disable_default_tools.nil?
580
- agent_config[:bypass_permissions] = @bypass_permissions
581
- agent_config[:coding_agent] = @coding_agent
582
- agent_config[:assume_model_exists] = @assume_model_exists unless @assume_model_exists.nil?
583
- agent_config[:permissions] = @permissions_config if @permissions_config.any?
584
- agent_config[:default_permissions] = @default_permissions if @default_permissions.any?
585
- agent_config[:memory] = @memory_config if @memory_config
586
- agent_config[:shared_across_delegations] = @shared_across_delegations unless @shared_across_delegations.nil?
587
- agent_config[:streaming] = @streaming unless @streaming.nil?
588
- agent_config[:thinking] = @thinking if @thinking
589
-
590
- # Convert DSL hooks to HookDefinition format
591
- agent_config[:hooks] = convert_hooks_to_definitions if @hooks.any?
592
-
593
- # Merge context management hooks into agent hooks
594
- if @context_management_config
595
- agent_config[:hooks] ||= {}
596
- agent_config[:hooks][:context_warning] ||= []
597
- agent_config[:hooks][:context_warning].concat(@context_management_config)
598
- end
599
-
600
- Agent::Definition.new(@name, agent_config)
601
- end
602
-
603
- private
604
-
605
- # Convert DSL hooks to HookDefinition objects for Agent::Definition
606
- #
607
- # This converts the builder's hook configuration (Ruby blocks and shell commands)
608
- # into HookDefinition objects that will be applied during agent initialization.
609
- #
610
- # @return [Hash] Hooks grouped by event type { event: [HookDefinition, ...] }
611
- def convert_hooks_to_definitions
612
- result = Hash.new { |h, k| h[k] = [] }
613
-
614
- @hooks.each do |hook_config|
615
- event = hook_config[:event]
616
-
617
- # Create HookDefinition with proc or command
618
- if hook_config[:block]
619
- # Ruby block hook
620
- hook_def = Hooks::Definition.new(
621
- event: event,
622
- matcher: hook_config[:matcher],
623
- priority: 0,
624
- proc: hook_config[:block],
625
- )
626
- elsif hook_config[:command]
627
- # Shell command hook - wrap in a block that calls ShellExecutor
628
- hook_def = Hooks::Definition.new(
629
- event: event,
630
- matcher: hook_config[:matcher],
631
- priority: 0,
632
- proc: create_shell_hook_proc(hook_config),
633
- )
634
- else
635
- raise ConfigurationError, "Hook must have either :block or :command"
636
- end
637
-
638
- result[event] << hook_def
639
- end
640
-
641
- result
642
- end
643
-
644
- # Create a proc that executes a shell command hook
645
- def create_shell_hook_proc(config)
646
- command = config[:command]
647
- timeout = config[:timeout] || 60
648
- agent_name = @name
649
-
650
- proc do |context|
651
- input_json = build_hook_input(context, config[:event])
652
- Hooks::ShellExecutor.execute(
653
- command: command,
654
- input_json: input_json,
655
- timeout: timeout,
656
- agent_name: agent_name,
657
- swarm_name: context.swarm&.name,
658
- event: config[:event],
659
- )
660
- end
661
- end
662
-
663
- # Build hook input JSON for shell command hooks
664
- def build_hook_input(context, event)
665
- base = { event: event.to_s, agent: @name.to_s }
666
-
667
- case event
668
- when :pre_tool_use
669
- base.merge(tool: context.tool_call.name, parameters: context.tool_call.parameters)
670
- when :post_tool_use
671
- base.merge(result: context.tool_result.content, success: context.tool_result.success?)
672
- when :user_prompt
673
- base.merge(prompt: context.metadata[:prompt])
674
- else
675
- base
676
- end
677
- end
678
- end
679
- end
680
- end