swarm_sdk 2.0.3 → 2.0.5

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/agent/builder.rb +41 -0
  3. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +22 -5
  4. data/lib/swarm_sdk/agent/definition.rb +52 -6
  5. data/lib/swarm_sdk/configuration.rb +3 -1
  6. data/lib/swarm_sdk/prompts/memory.md.erb +480 -0
  7. data/lib/swarm_sdk/swarm/agent_initializer.rb +16 -3
  8. data/lib/swarm_sdk/swarm/builder.rb +9 -1
  9. data/lib/swarm_sdk/swarm/tool_configurator.rb +73 -23
  10. data/lib/swarm_sdk/swarm.rb +51 -7
  11. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
  12. data/lib/swarm_sdk/tools/memory/memory_delete.rb +64 -0
  13. data/lib/swarm_sdk/tools/memory/memory_edit.rb +145 -0
  14. data/lib/swarm_sdk/tools/memory/memory_glob.rb +94 -0
  15. data/lib/swarm_sdk/tools/memory/memory_grep.rb +147 -0
  16. data/lib/swarm_sdk/tools/memory/memory_multi_edit.rb +228 -0
  17. data/lib/swarm_sdk/tools/memory/memory_read.rb +82 -0
  18. data/lib/swarm_sdk/tools/memory/memory_write.rb +90 -0
  19. data/lib/swarm_sdk/tools/registry.rb +11 -3
  20. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
  21. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
  22. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
  23. data/lib/swarm_sdk/tools/stores/memory_storage.rb +300 -0
  24. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
  25. data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
  26. data/lib/swarm_sdk/tools/stores/storage_read_tracker.rb +61 -0
  27. data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
  28. data/lib/swarm_sdk/version.rb +1 -1
  29. data/lib/swarm_sdk.rb +39 -0
  30. metadata +18 -5
  31. data/lib/swarm_sdk/tools/scratchpad_list.rb +0 -88
  32. data/lib/swarm_sdk/tools/scratchpad_read.rb +0 -59
  33. data/lib/swarm_sdk/tools/scratchpad_write.rb +0 -88
  34. data/lib/swarm_sdk/tools/stores/scratchpad.rb +0 -153
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aba64a75c41f9957bb3e11afbe0dd5a5fc6422793fda5d3fb829ac0042cbe18
4
- data.tar.gz: 4f403fb7d107fa712a8b847775fea1140a03904cf066b8e848b914f4b3c573e3
3
+ metadata.gz: c2b0a4dbb64785b7fc12ad6a461acfb4502135cbf06a86f08de6ae7b2df05e21
4
+ data.tar.gz: c118e7fb996b6ce679d1feb80e359f246ca5741d4d2ae1dbbece537796502b0c
5
5
  SHA512:
6
- metadata.gz: 204b9d889968b2d582557b06169d02828abd4c365b00e2a6551cc7f4c44a2ec7ae452a55d1e25b59fdc8a0b10c486c365cce2b2332d02f0d54b502ec6667af75
7
- data.tar.gz: ae46da01755ef0c98b162c7c2be3c51ba8831dbbdd10301f937e0eed0260f9694b7514658d437e9d65d9cc58e06d7a877600ec29fd48eb773eec8514d74440b1
6
+ metadata.gz: 9fe12e5956c41623e43b0f7b69d8bce442052c180aaf89e787b1163836f013a45435a8147ce749eaaf2809799cb0c17f3ac2942ed5fdff048805d8ab21d1924d
7
+ data.tar.gz: 526374b16146e8721c2b1a7370bf58843fa7bd03b36b5f1cd6a5a23e263446171005dec50ce5ec979d6aa42f5a07886f27f09939aacd76c45c922478782624bb
@@ -2,6 +2,32 @@
2
2
 
3
3
  module SwarmSDK
4
4
  module Agent
5
+ # Configuration for agent memory
6
+ class MemoryConfig
7
+ def initialize
8
+ @adapter = :filesystem # Default adapter
9
+ @directory = nil
10
+ end
11
+
12
+ # DSL method to set/get adapter
13
+ def adapter(value = nil)
14
+ return @adapter if value.nil?
15
+
16
+ @adapter = value.to_sym
17
+ end
18
+
19
+ # DSL method to set/get directory
20
+ def directory(value = nil)
21
+ return @directory if value.nil?
22
+
23
+ @directory = value
24
+ end
25
+
26
+ def enabled?
27
+ !@directory.nil?
28
+ end
29
+ end
30
+
5
31
  # Builder provides fluent API for configuring agents
6
32
  #
7
33
  # This class offers a Ruby DSL for defining agents with a clean, readable syntax.
@@ -51,6 +77,7 @@ module SwarmSDK
51
77
  @hooks = []
52
78
  @permissions_config = {}
53
79
  @default_permissions = {} # Set by SwarmBuilder from all_agents
80
+ @memory_config = nil
54
81
  end
55
82
 
56
83
  # Set/get agent model
@@ -218,6 +245,19 @@ module SwarmSDK
218
245
  @directory = dir
219
246
  end
220
247
 
248
+ # Configure persistent memory for this agent
249
+ #
250
+ # @example
251
+ # memory do
252
+ # adapter :filesystem # default
253
+ # directory ".swarm/agent-memory"
254
+ # end
255
+ def memory(&block)
256
+ @memory_config = MemoryConfig.new
257
+ @memory_config.instance_eval(&block) if block_given?
258
+ @memory_config
259
+ end
260
+
221
261
  # Set delegation targets
222
262
  def delegates_to(*agent_names)
223
263
  @delegates_to.concat(agent_names)
@@ -359,6 +399,7 @@ module SwarmSDK
359
399
  agent_config[:assume_model_exists] = @assume_model_exists unless @assume_model_exists.nil?
360
400
  agent_config[:permissions] = @permissions_config if @permissions_config.any?
361
401
  agent_config[:default_permissions] = @default_permissions if @default_permissions.any?
402
+ agent_config[:memory] = @memory_config if @memory_config
362
403
 
363
404
  # Convert DSL hooks to HookDefinition format
364
405
  agent_config[:hooks] = convert_hooks_to_definitions if @hooks.any?
@@ -70,20 +70,37 @@ module SwarmSDK
70
70
  def calculate_cost(message)
71
71
  return zero_cost unless message.input_tokens && message.output_tokens
72
72
 
73
- model_info = RubyLLM.models.find(message.model_id)
73
+ # Use SwarmSDK's model registry (not RubyLLM's) for up-to-date pricing
74
+ model_info = SwarmSDK::Models.find(message.model_id)
74
75
  return zero_cost unless model_info
75
76
 
76
- # Prices are per million tokens (USD)
77
- input_cost = (message.input_tokens / 1_000_000.0) * model_info.input_price_per_million
78
- output_cost = (message.output_tokens / 1_000_000.0) * model_info.output_price_per_million
77
+ # Extract pricing from SwarmSDK's models.json structure
78
+ pricing = model_info["pricing"] || model_info[:pricing]
79
+ return zero_cost unless pricing
80
+
81
+ text_pricing = pricing["text_tokens"] || pricing[:text_tokens]
82
+ return zero_cost unless text_pricing
83
+
84
+ standard_pricing = text_pricing["standard"] || text_pricing[:standard]
85
+ return zero_cost unless standard_pricing
86
+
87
+ input_price = standard_pricing["input_per_million"] || standard_pricing[:input_per_million]
88
+ output_price = standard_pricing["output_per_million"] || standard_pricing[:output_per_million]
89
+
90
+ return zero_cost unless input_price && output_price
91
+
92
+ # Calculate costs (prices are per million tokens in USD)
93
+ input_cost = (message.input_tokens / 1_000_000.0) * input_price
94
+ output_cost = (message.output_tokens / 1_000_000.0) * output_price
79
95
 
80
96
  {
81
97
  input_cost: input_cost,
82
98
  output_cost: output_cost,
83
99
  total_cost: input_cost + output_cost,
84
100
  }
85
- rescue StandardError
101
+ rescue StandardError => e
86
102
  # Model not found in registry or pricing not available
103
+ RubyLLM.logger.debug("Cost calculation failed for #{message.model_id}: #{e.message}")
87
104
  zero_cost
88
105
  end
89
106
 
@@ -43,7 +43,8 @@ module SwarmSDK
43
43
  :default_permissions,
44
44
  :agent_permissions,
45
45
  :assume_model_exists,
46
- :hooks
46
+ :hooks,
47
+ :memory
47
48
 
48
49
  attr_accessor :bypass_permissions, :max_concurrent_tools
49
50
 
@@ -91,7 +92,11 @@ module SwarmSDK
91
92
  # Parse directory first so it can be used in system prompt rendering
92
93
  @directory = parse_directory(config[:directory])
93
94
 
94
- # Build system prompt after directory is set
95
+ # Parse memory configuration BEFORE building system prompt
96
+ # (memory prompt needs to be appended if memory is enabled)
97
+ @memory = parse_memory_config(config[:memory])
98
+
99
+ # Build system prompt after directory and memory are set
95
100
  @system_prompt = build_full_system_prompt(config[:system_prompt])
96
101
 
97
102
  # Parse tools with permissions support
@@ -116,6 +121,30 @@ module SwarmSDK
116
121
  validate!
117
122
  end
118
123
 
124
+ # Check if memory is enabled for this agent
125
+ #
126
+ # @return [Boolean]
127
+ def memory_enabled?
128
+ @memory&.respond_to?(:enabled?) && @memory.enabled?
129
+ end
130
+
131
+ # Parse memory configuration from Hash or MemoryConfig object
132
+ #
133
+ # @param memory_config [Hash, Agent::MemoryConfig, nil] Memory configuration
134
+ # @return [Agent::MemoryConfig, nil]
135
+ def parse_memory_config(memory_config)
136
+ return if memory_config.nil?
137
+ return memory_config if memory_config.is_a?(Agent::MemoryConfig)
138
+
139
+ # Convert hash (from YAML) to MemoryConfig object
140
+ config = Agent::MemoryConfig.new
141
+ adapter_value = memory_config[:adapter] || memory_config["adapter"] || :filesystem
142
+ config.adapter(adapter_value.to_sym)
143
+ directory_value = memory_config[:directory] || memory_config["directory"]
144
+ config.directory(directory_value) if directory_value
145
+ config
146
+ end
147
+
119
148
  def to_h
120
149
  {
121
150
  name: @name,
@@ -216,9 +245,8 @@ module SwarmSDK
216
245
  end
217
246
 
218
247
  def build_full_system_prompt(custom_prompt)
219
- # If coding_agent is false (default), return custom prompt with optional TODO/Scratchpad info
220
- # If coding_agent is true, include full base prompt for coding tasks
221
- if @coding_agent
248
+ # Build the base prompt based on coding_agent setting
249
+ prompt = if @coding_agent
222
250
  # Coding agent: include full base prompt
223
251
  rendered_base = render_base_system_prompt
224
252
 
@@ -238,11 +266,22 @@ module SwarmSDK
238
266
  # No custom prompt: just return TODO/Scratchpad info
239
267
  non_coding_base
240
268
  end
241
- # Default tools available: include TODO/Scratchpad instructions
242
269
  else
243
270
  # No default tools: return only custom prompt
244
271
  (custom_prompt || "").to_s
245
272
  end
273
+
274
+ # Append memory instructions if memory is enabled
275
+ if memory_enabled?
276
+ memory_prompt = render_memory_prompt
277
+ prompt = if prompt && !prompt.strip.empty?
278
+ "#{prompt}\n\n#{memory_prompt}"
279
+ else
280
+ memory_prompt
281
+ end
282
+ end
283
+
284
+ prompt
246
285
  end
247
286
 
248
287
  # Check if default tools are enabled (i.e., not disabled)
@@ -266,6 +305,13 @@ module SwarmSDK
266
305
  ERB.new(template_content).result(binding)
267
306
  end
268
307
 
308
+ def render_memory_prompt
309
+ # Load and render the memory system prompt
310
+ memory_prompt_path = File.expand_path("../prompts/memory.md.erb", __dir__)
311
+ template_content = File.read(memory_prompt_path)
312
+ ERB.new(template_content).result(binding)
313
+ end
314
+
269
315
  def render_non_coding_base_prompt
270
316
  # Simplified base prompt for non-coding agents
271
317
  # Includes environment info, TODO, and Scratchpad tool information
@@ -4,7 +4,7 @@ module SwarmSDK
4
4
  class Configuration
5
5
  ENV_VAR_WITH_DEFAULT_PATTERN = /\$\{([^:}]+)(:=([^}]*))?\}/
6
6
 
7
- attr_reader :config_path, :swarm_name, :lead_agent, :agents, :all_agents_config, :swarm_hooks, :all_agents_hooks
7
+ attr_reader :config_path, :swarm_name, :lead_agent, :agents, :all_agents_config, :swarm_hooks, :all_agents_hooks, :scratchpad_enabled
8
8
 
9
9
  class << self
10
10
  def load(path)
@@ -62,6 +62,7 @@ module SwarmSDK
62
62
  name: @swarm_name,
63
63
  global_concurrency: Swarm::DEFAULT_GLOBAL_CONCURRENCY,
64
64
  default_local_concurrency: Swarm::DEFAULT_LOCAL_CONCURRENCY,
65
+ scratchpad_enabled: @scratchpad_enabled,
65
66
  )
66
67
 
67
68
  # Add all agents - pass definitions directly
@@ -146,6 +147,7 @@ module SwarmSDK
146
147
 
147
148
  @swarm_name = swarm[:name]
148
149
  @lead_agent = swarm[:lead].to_sym # Convert to symbol for consistency
150
+ @scratchpad_enabled = swarm[:use_scratchpad].nil? ? true : swarm[:use_scratchpad] # Default: enabled
149
151
  end
150
152
 
151
153
  def load_agents