swarm_sdk 2.7.14 → 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 (181) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  42. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  43. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  44. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  45. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  46. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  47. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  48. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  49. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  50. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  51. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  52. data/lib/swarm_sdk/v3.rb +145 -0
  53. metadata +83 -148
  54. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  55. data/lib/swarm_sdk/agent/builder.rb +0 -705
  56. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  57. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  58. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  59. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  60. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  61. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  62. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  63. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  64. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  65. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  66. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  67. data/lib/swarm_sdk/agent/context.rb +0 -115
  68. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  69. data/lib/swarm_sdk/agent/definition.rb +0 -588
  70. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  71. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  72. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  73. data/lib/swarm_sdk/agent_registry.rb +0 -146
  74. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  75. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  76. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  77. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  78. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  79. data/lib/swarm_sdk/config.rb +0 -368
  80. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  81. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  82. data/lib/swarm_sdk/configuration.rb +0 -165
  83. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  84. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  85. data/lib/swarm_sdk/context_compactor.rb +0 -335
  86. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  87. data/lib/swarm_sdk/context_management/context.rb +0 -328
  88. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  89. data/lib/swarm_sdk/defaults.rb +0 -251
  90. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  91. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  92. data/lib/swarm_sdk/hooks/context.rb +0 -197
  93. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  94. data/lib/swarm_sdk/hooks/error.rb +0 -29
  95. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  96. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  97. data/lib/swarm_sdk/hooks/result.rb +0 -150
  98. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  99. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  100. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  101. data/lib/swarm_sdk/log_collector.rb +0 -227
  102. data/lib/swarm_sdk/log_stream.rb +0 -127
  103. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  104. data/lib/swarm_sdk/model_aliases.json +0 -8
  105. data/lib/swarm_sdk/models.json +0 -44002
  106. data/lib/swarm_sdk/models.rb +0 -161
  107. data/lib/swarm_sdk/node_context.rb +0 -245
  108. data/lib/swarm_sdk/observer/builder.rb +0 -81
  109. data/lib/swarm_sdk/observer/config.rb +0 -45
  110. data/lib/swarm_sdk/observer/manager.rb +0 -248
  111. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  112. data/lib/swarm_sdk/permissions/config.rb +0 -239
  113. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  114. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  115. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  116. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  117. data/lib/swarm_sdk/plugin.rb +0 -309
  118. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  119. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  120. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  121. data/lib/swarm_sdk/restore_result.rb +0 -65
  122. data/lib/swarm_sdk/result.rb +0 -241
  123. data/lib/swarm_sdk/snapshot.rb +0 -156
  124. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  125. data/lib/swarm_sdk/state_restorer.rb +0 -476
  126. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  127. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  128. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  129. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  130. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  131. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  132. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  133. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  134. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  135. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  136. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  137. data/lib/swarm_sdk/swarm.rb +0 -973
  138. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  139. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  140. data/lib/swarm_sdk/tools/base.rb +0 -63
  141. data/lib/swarm_sdk/tools/bash.rb +0 -280
  142. data/lib/swarm_sdk/tools/clock.rb +0 -46
  143. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  144. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  145. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  146. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  147. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  148. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  149. data/lib/swarm_sdk/tools/edit.rb +0 -145
  150. data/lib/swarm_sdk/tools/glob.rb +0 -166
  151. data/lib/swarm_sdk/tools/grep.rb +0 -235
  152. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  153. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  154. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  155. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  156. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  157. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  158. data/lib/swarm_sdk/tools/read.rb +0 -261
  159. data/lib/swarm_sdk/tools/registry.rb +0 -205
  160. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  161. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  163. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  164. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  165. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  166. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  167. data/lib/swarm_sdk/tools/think.rb +0 -100
  168. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  169. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  170. data/lib/swarm_sdk/tools/write.rb +0 -112
  171. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  172. data/lib/swarm_sdk/utils.rb +0 -68
  173. data/lib/swarm_sdk/validation_result.rb +0 -33
  174. data/lib/swarm_sdk/version.rb +0 -5
  175. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  176. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  177. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  178. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  179. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  180. data/lib/swarm_sdk/workflow.rb +0 -589
  181. data/lib/swarm_sdk.rb +0 -721
@@ -0,0 +1,533 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module V3
5
+ # DSL builder for creating a single V3 Agent
6
+ #
7
+ # Provides a clean, block-based interface for defining an agent without
8
+ # needing to construct AgentDefinition kwargs directly. Unlike the V2
9
+ # builder which manages multi-agent swarms, this creates a single agent.
10
+ #
11
+ # @example Minimal agent
12
+ # agent = SwarmSDK::V3.agent do
13
+ # name :assistant
14
+ # description "A helpful assistant"
15
+ # end
16
+ #
17
+ # @example Full agent with memory
18
+ # agent = SwarmSDK::V3.agent do
19
+ # name :researcher
20
+ # description "Research specialist"
21
+ # model "claude-sonnet-4"
22
+ # provider "anthropic"
23
+ # system_prompt "You are an expert researcher."
24
+ # tools :Read, :Write, :Edit, :Bash, :Grep, :Glob
25
+ # directory "/path/to/project"
26
+ #
27
+ # memory do
28
+ # directory ".swarm/memory"
29
+ # stm_turns 8
30
+ # retrieval_top_k 15
31
+ # end
32
+ # end
33
+ #
34
+ # agent.ask("Hello!")
35
+ class AgentBuilder
36
+ class << self
37
+ # Build an Agent from a DSL block
38
+ #
39
+ # @yield DSL block evaluated via instance_eval
40
+ # @return [Agent] Initialized agent ready for use
41
+ #
42
+ # @example
43
+ # agent = AgentBuilder.build do
44
+ # name :coder
45
+ # description "Code writer"
46
+ # tools :Read, :Edit
47
+ # end
48
+ def build(&block)
49
+ builder = new
50
+ builder.instance_eval(&block)
51
+ builder.to_agent
52
+ end
53
+ end
54
+
55
+ def initialize
56
+ @config = {}
57
+ @tools = []
58
+ @skills = []
59
+ @mcp_servers = []
60
+ @hooks = []
61
+ @memory_config = nil
62
+ end
63
+
64
+ # @!group DSL Methods
65
+
66
+ # Set agent name
67
+ #
68
+ # @param value [Symbol, String] Agent identifier
69
+ # @return [void]
70
+ #
71
+ # @example
72
+ # name :researcher
73
+ def name(value)
74
+ @config[:name] = value
75
+ end
76
+
77
+ # Set agent description
78
+ #
79
+ # @param value [String] Agent role description
80
+ # @return [void]
81
+ #
82
+ # @example
83
+ # description "Research specialist"
84
+ def description(value)
85
+ @config[:description] = value
86
+ end
87
+
88
+ # Set LLM model
89
+ #
90
+ # @param value [String] Model identifier
91
+ # @return [void]
92
+ #
93
+ # @example
94
+ # model "claude-sonnet-4"
95
+ def model(value)
96
+ @config[:model] = value
97
+ end
98
+
99
+ # Set LLM provider
100
+ #
101
+ # @param value [String] Provider name
102
+ # @return [void]
103
+ #
104
+ # @example
105
+ # provider "anthropic"
106
+ def provider(value)
107
+ @config[:provider] = value
108
+ end
109
+
110
+ # Set system prompt instructions
111
+ #
112
+ # @param value [String] System prompt text
113
+ # @return [void]
114
+ #
115
+ # @example
116
+ # system_prompt "You are an expert researcher."
117
+ def system_prompt(value)
118
+ @config[:system_prompt] = value
119
+ end
120
+
121
+ # Add tools to the agent
122
+ #
123
+ # Can be called multiple times; tools accumulate across calls.
124
+ #
125
+ # @param names [Array<Symbol, String>] Tool names
126
+ # @return [void]
127
+ #
128
+ # @example Single call
129
+ # tools :Read, :Write, :Edit
130
+ #
131
+ # @example Multiple calls accumulate
132
+ # tools :Read, :Write
133
+ # tools :Bash, :Grep
134
+ # # => [:Read, :Write, :Bash, :Grep]
135
+ def tools(*names)
136
+ @tools.concat(names.flatten)
137
+ end
138
+
139
+ # Add skill directories to the agent
140
+ #
141
+ # Can be called multiple times; paths accumulate across calls.
142
+ # Each path should be a directory containing a SKILL.md file,
143
+ # or a parent directory whose children contain SKILL.md files.
144
+ #
145
+ # @param paths [Array<String>] Skill directory paths
146
+ # @return [void]
147
+ #
148
+ # @example Single call
149
+ # skills "/path/to/skills"
150
+ #
151
+ # @example Multiple calls accumulate
152
+ # skills "/skills/pdf"
153
+ # skills "/skills/csv"
154
+ def skills(*paths)
155
+ @skills.concat(paths.flatten)
156
+ end
157
+
158
+ # Set working directory
159
+ #
160
+ # @param value [String] Directory path
161
+ # @return [void]
162
+ #
163
+ # @example
164
+ # directory "/path/to/project"
165
+ def directory(value)
166
+ @config[:directory] = value
167
+ end
168
+
169
+ # Set custom API endpoint
170
+ #
171
+ # @param value [String] Base URL
172
+ # @return [void]
173
+ #
174
+ # @example
175
+ # base_url "https://api.example.com"
176
+ def base_url(value)
177
+ @config[:base_url] = value
178
+ end
179
+
180
+ # Set maximum concurrent tool executions
181
+ #
182
+ # @param value [Integer] Max concurrent tools
183
+ # @return [void]
184
+ #
185
+ # @example
186
+ # max_concurrent_tools 5
187
+ def max_concurrent_tools(value)
188
+ @config[:max_concurrent_tools] = value
189
+ end
190
+
191
+ # Set raw API body parameters
192
+ #
193
+ # @param value [Hash] Parameters hash
194
+ # @return [void]
195
+ #
196
+ # @example
197
+ # parameters(temperature: 0.7, thinking: { type: "enabled", budget_tokens: 10_000 })
198
+ def parameters(value)
199
+ @config[:parameters] = value
200
+ end
201
+
202
+ # Set raw HTTP headers
203
+ #
204
+ # @param value [Hash] Headers hash
205
+ # @return [void]
206
+ #
207
+ # @example
208
+ # headers("anthropic-beta" => "some-feature")
209
+ def headers(value)
210
+ @config[:headers] = value
211
+ end
212
+
213
+ # Set output schema for structured output
214
+ #
215
+ # The schema is passed through to RubyLLM's Chat#with_schema.
216
+ # Can be a Hash (raw JSON Schema) or any object responding to #to_json_schema.
217
+ #
218
+ # @param value [Hash, Object] Schema definition
219
+ # @return [void]
220
+ #
221
+ # @example Raw JSON Schema
222
+ # output_schema({ type: "object", properties: { name: { type: "string" } } })
223
+ def output_schema(value)
224
+ @config[:output_schema] = value
225
+ end
226
+
227
+ # Set API version for OpenAI provider
228
+ #
229
+ # Controls which OpenAI API endpoint to use. Only applicable when
230
+ # provider is "openai". Defaults to "v1/responses" for OpenAI agents.
231
+ #
232
+ # @param value [String] API version ("v1/responses" or "v1/chat/completions")
233
+ # @return [void]
234
+ #
235
+ # @example Use Chat Completions API
236
+ # api_version "v1/chat/completions"
237
+ #
238
+ # @example Use Responses API (default for OpenAI)
239
+ # api_version "v1/responses"
240
+ def api_version(value)
241
+ @config[:api_version] = value
242
+ end
243
+
244
+ # Configure memory via a sub-block
245
+ #
246
+ # @yield DSL block evaluated on a MemoryBuilder
247
+ # @return [void]
248
+ #
249
+ # @example
250
+ # memory do
251
+ # directory ".swarm/memory"
252
+ # stm_turns 8
253
+ # retrieval_top_k 15
254
+ # end
255
+ def memory(&block)
256
+ @memory_config = MemoryBuilder.build(&block)
257
+ end
258
+
259
+ # Register a before_ask hook
260
+ #
261
+ # Fires before {Agent#execute_turn}. Can halt (returns nil from ask)
262
+ # or replace the prompt.
263
+ #
264
+ # @yield [ctx] Hook block receiving a {Hooks::Context}
265
+ # @return [void]
266
+ #
267
+ # @example Log every prompt
268
+ # before_ask { |ctx| puts "Processing: #{ctx.prompt}" }
269
+ #
270
+ # @example Block certain prompts
271
+ # before_ask { |ctx| ctx.halt("Blocked") if ctx.prompt.include?("secret") }
272
+ def before_ask(&block)
273
+ @hooks << { event: :before_ask, block: block }
274
+ end
275
+
276
+ # Register an after_ask hook
277
+ #
278
+ # Fires after {Agent#execute_turn} completes. Observation only —
279
+ # halt and replace have no effect.
280
+ #
281
+ # @yield [ctx] Hook block receiving a {Hooks::Context}
282
+ # @return [void]
283
+ #
284
+ # @example Log responses
285
+ # after_ask { |ctx| log_response(ctx.response) }
286
+ def after_ask(&block)
287
+ @hooks << { event: :after_ask, block: block }
288
+ end
289
+
290
+ # Register a before_tool hook
291
+ #
292
+ # Fires before tool execution. Can halt (returns error string to LLM
293
+ # without executing the tool). Supports match filtering.
294
+ #
295
+ # @param match [Symbol, String, Regexp, Array, nil] Tool name matcher
296
+ # @yield [ctx] Hook block receiving a {Hooks::Context}
297
+ # @return [void]
298
+ #
299
+ # @example Block Bash tool
300
+ # before_tool(match: :Bash) { |ctx| ctx.halt("Bash disabled") }
301
+ #
302
+ # @example Validate Write/Edit arguments
303
+ # before_tool(match: [:Write, :Edit]) { |ctx| validate(ctx.tool_arguments) }
304
+ def before_tool(match: nil, &block)
305
+ @hooks << { event: :before_tool, match: compile_matcher(match), block: block }
306
+ end
307
+
308
+ # Register an after_tool hook
309
+ #
310
+ # Fires after tool execution. Can replace the tool result.
311
+ # Supports match filtering.
312
+ #
313
+ # @param match [Symbol, String, Regexp, Array, nil] Tool name matcher
314
+ # @yield [ctx] Hook block receiving a {Hooks::Context}
315
+ # @return [void]
316
+ #
317
+ # @example Sanitize Bash output
318
+ # after_tool(match: /Bash/) { |ctx| ctx.replace(sanitize(ctx.tool_result)) }
319
+ def after_tool(match: nil, &block)
320
+ @hooks << { event: :after_tool, match: compile_matcher(match), block: block }
321
+ end
322
+
323
+ # Register an on_stop hook
324
+ #
325
+ # Fires after ask() completes successfully. Observation only —
326
+ # the response has already been produced. Useful for logging,
327
+ # metrics, or cleanup.
328
+ #
329
+ # @yield [ctx] Hook block receiving a {Hooks::Context}
330
+ # @return [void]
331
+ #
332
+ # @example Track completions
333
+ # on_stop { |ctx| metrics.increment("agent.#{ctx.agent_name}.completed") }
334
+ def on_stop(&block)
335
+ @hooks << { event: :on_stop, block: block }
336
+ end
337
+
338
+ # Add an MCP server connection
339
+ #
340
+ # Can be called multiple times; servers accumulate across calls.
341
+ # Each server provides tools that the agent can invoke via MCP protocol.
342
+ #
343
+ # @param name [Symbol, String] Server identifier
344
+ # @param options [Hash] Server configuration options
345
+ # @option options [Symbol] :type Transport type (:stdio or :http)
346
+ # @option options [String] :command Subprocess command (stdio)
347
+ # @option options [Array<String>] :args Subprocess arguments (stdio)
348
+ # @option options [Hash] :env Subprocess environment (stdio)
349
+ # @option options [String] :url HTTP endpoint URL (http)
350
+ # @option options [Hash] :headers HTTP headers (http)
351
+ # @option options [Array<Symbol>] :tools Tool names to expose (nil = all)
352
+ # @return [void]
353
+ #
354
+ # @example HTTP server
355
+ # mcp_server :api,
356
+ # type: :http,
357
+ # url: "https://example.com/mcp",
358
+ # headers: { "Authorization" => "Bearer token" }
359
+ #
360
+ # @example Stdio server with tool filtering
361
+ # mcp_server :filesystem,
362
+ # type: :stdio,
363
+ # command: "npx",
364
+ # args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
365
+ # tools: [:read_file, :list_directory]
366
+ def mcp_server(name, **options)
367
+ @mcp_servers << options.merge(name: name)
368
+ end
369
+
370
+ # @!endgroup
371
+
372
+ # Build the Agent from collected configuration
373
+ #
374
+ # @return [Agent] Initialized agent
375
+ # @raise [ConfigurationError] If required fields are missing
376
+ def to_agent
377
+ definition = AgentDefinition.new(**definition_kwargs)
378
+ Agent.new(definition)
379
+ end
380
+
381
+ private
382
+
383
+ # Assemble keyword arguments for AgentDefinition
384
+ #
385
+ # @return [Hash] Keyword arguments
386
+ def definition_kwargs
387
+ kwargs = @config.dup
388
+ kwargs[:tools] = @tools unless @tools.empty?
389
+ kwargs[:skills] = @skills unless @skills.empty?
390
+ kwargs[:mcp_servers] = @mcp_servers unless @mcp_servers.empty?
391
+ kwargs[:hooks] = @hooks unless @hooks.empty?
392
+ kwargs.merge!(@memory_config) if @memory_config
393
+ kwargs
394
+ end
395
+
396
+ # Compile a match specification into a normalized matcher
397
+ #
398
+ # Handles Symbol, String, Regexp, Array, and nil inputs.
399
+ # Arrays are recursively compiled so nested values are normalized.
400
+ #
401
+ # @param match [Symbol, String, Regexp, Array, nil] Raw match input
402
+ # @return [Symbol, String, Regexp, Array, nil] Compiled matcher
403
+ def compile_matcher(match)
404
+ case match
405
+ when nil, Symbol, String, Regexp then match
406
+ when Array then match.map { |m| compile_matcher(m) }
407
+ else
408
+ raise ArgumentError, "Invalid match type: #{match.class}. Use Symbol, String, Regexp, Array, or nil."
409
+ end
410
+ end
411
+ end
412
+
413
+ # Sub-builder for memory configuration within AgentBuilder
414
+ #
415
+ # Collects memory-related settings and returns them as a hash
416
+ # with the `memory_` prefix expected by AgentDefinition.
417
+ #
418
+ # @example
419
+ # config = MemoryBuilder.build do
420
+ # directory ".swarm/memory"
421
+ # stm_turns 8
422
+ # end
423
+ # # => { memory_directory: ".swarm/memory", memory_stm_turns: 8 }
424
+ class MemoryBuilder
425
+ class << self
426
+ # Build memory config from a DSL block
427
+ #
428
+ # @yield DSL block evaluated via instance_eval
429
+ # @return [Hash] Memory configuration with `memory_` prefixed keys
430
+ def build(&block)
431
+ builder = new
432
+ builder.instance_eval(&block)
433
+ builder.to_h
434
+ end
435
+ end
436
+
437
+ def initialize
438
+ @config = {}
439
+ end
440
+
441
+ # Set memory storage directory
442
+ #
443
+ # @param value [String] Directory path
444
+ # @return [void]
445
+ def directory(value)
446
+ @config[:memory_directory] = value
447
+ end
448
+
449
+ # Set number of recent turns to keep in short-term memory
450
+ #
451
+ # @param value [Integer] STM turn count
452
+ # @return [void]
453
+ def stm_turns(value)
454
+ @config[:memory_stm_turns] = value
455
+ end
456
+
457
+ # Set number of memory cards to retrieve per turn
458
+ #
459
+ # @param value [Integer] Top-k retrieval count
460
+ # @return [void]
461
+ def retrieval_top_k(value)
462
+ @config[:memory_retrieval_top_k] = value
463
+ end
464
+
465
+ # Set semantic search weight for hybrid retrieval
466
+ #
467
+ # @param value [Float] Weight between 0.0 and 1.0
468
+ # @return [void]
469
+ def semantic_weight(value)
470
+ @config[:memory_semantic_weight] = value
471
+ end
472
+
473
+ # Set keyword search weight for hybrid retrieval
474
+ #
475
+ # @param value [Float] Weight between 0.0 and 1.0
476
+ # @return [void]
477
+ def keyword_weight(value)
478
+ @config[:memory_keyword_weight] = value
479
+ end
480
+
481
+ # Valid adapter type symbols
482
+ VALID_ADAPTER_TYPES = [:sqlite, :filesystem].freeze
483
+
484
+ # Set the memory adapter
485
+ #
486
+ # @param value [Symbol, Memory::Adapters::Base] Adapter type or instance
487
+ # @return [void]
488
+ # @raise [ArgumentError] If value is not a valid adapter type or instance
489
+ #
490
+ # @example Using a symbol
491
+ # adapter :sqlite
492
+ # adapter :filesystem
493
+ #
494
+ # @example Using an adapter instance
495
+ # adapter MyCustomAdapter.new("/path/to/storage")
496
+ def adapter(value)
497
+ case value
498
+ when Symbol
499
+ unless VALID_ADAPTER_TYPES.include?(value)
500
+ raise ArgumentError, "Unknown memory adapter type: #{value.inspect}. " \
501
+ "Valid types are: #{VALID_ADAPTER_TYPES.map(&:inspect).join(", ")}"
502
+ end
503
+ when Memory::Adapters::Base
504
+ # Valid adapter instance
505
+ else
506
+ raise ArgumentError, "Memory adapter must be a Symbol (#{VALID_ADAPTER_TYPES.map(&:inspect).join(", ")}) " \
507
+ "or an instance of Memory::Adapters::Base, got: #{value.class}"
508
+ end
509
+ @config[:memory_adapter] = value
510
+ end
511
+
512
+ # Enable associative memory
513
+ #
514
+ # When enabled, the agent naturally surfaces tangential memories
515
+ # in conversation, like a person who brings up related topics.
516
+ # Exploration cards get a distinct "YOU ALSO REMEMBER" section
517
+ # and guidance is injected into the system prompt.
518
+ #
519
+ # @param value [Boolean] Whether to enable associative memory
520
+ # @return [void]
521
+ def associative(value)
522
+ @config[:memory_associative] = value
523
+ end
524
+
525
+ # Return collected memory configuration
526
+ #
527
+ # @return [Hash] Memory config with `memory_` prefixed keys
528
+ def to_h
529
+ @config.dup
530
+ end
531
+ end
532
+ end
533
+ end