swarm_memory 2.1.1 → 2.1.3

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/claude_swarm/cli.rb +9 -11
  3. data/lib/claude_swarm/commands/ps.rb +1 -2
  4. data/lib/claude_swarm/configuration.rb +30 -7
  5. data/lib/claude_swarm/mcp_generator.rb +4 -10
  6. data/lib/claude_swarm/orchestrator.rb +43 -44
  7. data/lib/claude_swarm/system_utils.rb +4 -4
  8. data/lib/claude_swarm/version.rb +1 -1
  9. data/lib/claude_swarm.rb +5 -9
  10. data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
  11. data/lib/swarm_cli/commands/mcp_tools.rb +3 -3
  12. data/lib/swarm_cli/config_loader.rb +14 -13
  13. data/lib/swarm_cli/version.rb +1 -1
  14. data/lib/swarm_cli.rb +2 -0
  15. data/lib/swarm_memory/adapters/base.rb +4 -4
  16. data/lib/swarm_memory/adapters/filesystem_adapter.rb +0 -12
  17. data/lib/swarm_memory/core/storage.rb +66 -6
  18. data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
  19. data/lib/swarm_memory/integration/cli_registration.rb +3 -2
  20. data/lib/swarm_memory/integration/sdk_plugin.rb +24 -4
  21. data/lib/swarm_memory/optimization/defragmenter.rb +4 -0
  22. data/lib/swarm_memory/tools/memory_edit.rb +3 -2
  23. data/lib/swarm_memory/tools/memory_glob.rb +24 -1
  24. data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
  25. data/lib/swarm_memory/tools/memory_read.rb +3 -3
  26. data/lib/swarm_memory/tools/memory_write.rb +2 -2
  27. data/lib/swarm_memory/version.rb +1 -1
  28. data/lib/swarm_memory.rb +7 -0
  29. data/lib/swarm_sdk/agent/builder.rb +33 -0
  30. data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
  31. data/lib/swarm_sdk/agent/chat/hook_integration.rb +41 -0
  32. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
  33. data/lib/swarm_sdk/agent/chat.rb +199 -52
  34. data/lib/swarm_sdk/agent/context.rb +6 -2
  35. data/lib/swarm_sdk/agent/context_manager.rb +6 -0
  36. data/lib/swarm_sdk/agent/definition.rb +32 -23
  37. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
  38. data/lib/swarm_sdk/configuration.rb +420 -103
  39. data/lib/swarm_sdk/events_to_messages.rb +181 -0
  40. data/lib/swarm_sdk/log_collector.rb +31 -5
  41. data/lib/swarm_sdk/log_stream.rb +37 -8
  42. data/lib/swarm_sdk/model_aliases.json +4 -1
  43. data/lib/swarm_sdk/node/agent_config.rb +39 -9
  44. data/lib/swarm_sdk/node/builder.rb +158 -42
  45. data/lib/swarm_sdk/node_context.rb +75 -0
  46. data/lib/swarm_sdk/node_orchestrator.rb +492 -18
  47. data/lib/swarm_sdk/plugin.rb +73 -1
  48. data/lib/swarm_sdk/proc_helpers.rb +53 -0
  49. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
  50. data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
  51. data/lib/swarm_sdk/restore_result.rb +65 -0
  52. data/lib/swarm_sdk/result.rb +32 -6
  53. data/lib/swarm_sdk/snapshot.rb +156 -0
  54. data/lib/swarm_sdk/snapshot_from_events.rb +386 -0
  55. data/lib/swarm_sdk/state_restorer.rb +491 -0
  56. data/lib/swarm_sdk/state_snapshot.rb +369 -0
  57. data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
  58. data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
  59. data/lib/swarm_sdk/swarm/builder.rb +208 -11
  60. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
  61. data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
  62. data/lib/swarm_sdk/swarm.rb +367 -90
  63. data/lib/swarm_sdk/swarm_loader.rb +145 -0
  64. data/lib/swarm_sdk/swarm_registry.rb +136 -0
  65. data/lib/swarm_sdk/tools/delegate.rb +94 -9
  66. data/lib/swarm_sdk/tools/read.rb +17 -5
  67. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
  68. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
  69. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
  70. data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
  71. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
  72. data/lib/swarm_sdk/tools/stores/storage.rb +4 -4
  73. data/lib/swarm_sdk/tools/think.rb +4 -1
  74. data/lib/swarm_sdk/tools/todo_write.rb +20 -8
  75. data/lib/swarm_sdk/utils.rb +18 -0
  76. data/lib/swarm_sdk/validation_result.rb +33 -0
  77. data/lib/swarm_sdk/version.rb +1 -1
  78. data/lib/swarm_sdk.rb +365 -28
  79. metadata +17 -5
@@ -3,39 +3,74 @@
3
3
  module SwarmSDK
4
4
  module Tools
5
5
  module Stores
6
- # ReadTracker manages read-file tracking for all agents
6
+ # ReadTracker manages read-file tracking for all agents with content digest verification
7
7
  #
8
8
  # This module maintains a global registry of which files each agent has read
9
- # during their conversation. This enables enforcement of the "read-before-write"
10
- # and "read-before-edit" rules that ensure agents have context before modifying files.
9
+ # during their conversation along with SHA256 digests of the content. This enables
10
+ # enforcement of the "read-before-write" and "read-before-edit" rules that ensure
11
+ # agents have context before modifying files, AND prevents editing files that have
12
+ # changed externally since being read.
11
13
  #
12
- # Each agent maintains an independent set of read files, keyed by agent identifier.
14
+ # Each agent maintains an independent map of read files to content digests.
13
15
  module ReadTracker
14
- @read_files = {}
16
+ @read_files = {} # { agent_id => { file_path => sha256_digest } }
15
17
  @mutex = Mutex.new
16
18
 
17
19
  class << self
18
- # Register that an agent has read a file
20
+ # Register that an agent has read a file with content digest
19
21
  #
20
22
  # @param agent_id [Symbol] The agent identifier
21
23
  # @param file_path [String] The absolute path to the file
22
- def register_read(agent_id, file_path)
24
+ # @param content [String] File content (for digest calculation)
25
+ # @return [String] The calculated SHA256 digest
26
+ def register_read(agent_id, file_path, content)
23
27
  @mutex.synchronize do
24
- @read_files[agent_id] ||= Set.new
25
- @read_files[agent_id] << File.expand_path(file_path)
28
+ @read_files[agent_id] ||= {}
29
+ digest = Digest::SHA256.hexdigest(content)
30
+ @read_files[agent_id][File.expand_path(file_path)] = digest
31
+ digest
26
32
  end
27
33
  end
28
34
 
29
- # Check if an agent has read a file
35
+ # Check if an agent has read a file AND content hasn't changed
30
36
  #
31
37
  # @param agent_id [Symbol] The agent identifier
32
38
  # @param file_path [String] The absolute path to the file
33
- # @return [Boolean] true if the agent has read this file
39
+ # @return [Boolean] true if agent read file and content matches
34
40
  def file_read?(agent_id, file_path)
35
41
  @mutex.synchronize do
36
42
  return false unless @read_files[agent_id]
37
43
 
38
- @read_files[agent_id].include?(File.expand_path(file_path))
44
+ expanded_path = File.expand_path(file_path)
45
+ stored_digest = @read_files[agent_id][expanded_path]
46
+ return false unless stored_digest
47
+
48
+ # Check if file still exists and matches stored digest
49
+ return false unless File.exist?(expanded_path)
50
+
51
+ current_digest = Digest::SHA256.hexdigest(File.read(expanded_path))
52
+ current_digest == stored_digest
53
+ end
54
+ end
55
+
56
+ # Get all read files with digests for snapshot
57
+ #
58
+ # @param agent_id [Symbol] The agent identifier
59
+ # @return [Hash] { file_path => digest }
60
+ def get_read_files(agent_id)
61
+ @mutex.synchronize do
62
+ @read_files[agent_id]&.dup || {}
63
+ end
64
+ end
65
+
66
+ # Restore read files with digests from snapshot
67
+ #
68
+ # @param agent_id [Symbol] The agent identifier
69
+ # @param files_with_digests [Hash] { file_path => digest }
70
+ # @return [void]
71
+ def restore_read_files(agent_id, files_with_digests)
72
+ @mutex.synchronize do
73
+ @read_files[agent_id] = files_with_digests.dup
39
74
  end
40
75
  end
41
76
 
@@ -218,6 +218,51 @@ module SwarmSDK
218
218
  def size
219
219
  @entries.size
220
220
  end
221
+
222
+ # Get all entries with content for snapshot
223
+ #
224
+ # Thread-safe method that returns a copy of all entries.
225
+ # Used by snapshot/restore functionality.
226
+ #
227
+ # @return [Hash] { path => Entry }
228
+ def all_entries
229
+ @mutex.synchronize do
230
+ @entries.dup
231
+ end
232
+ end
233
+
234
+ # Restore entries from snapshot
235
+ #
236
+ # Restores entries directly without using write() to preserve timestamps.
237
+ # This ensures entry ordering and metadata accuracy after restore.
238
+ #
239
+ # @param entries_data [Hash] { path => { content:, title:, updated_at:, size: } }
240
+ # @return [void]
241
+ def restore_entries(entries_data)
242
+ @mutex.synchronize do
243
+ entries_data.each do |path, data|
244
+ # Handle both symbol and string keys from JSON
245
+ content = data[:content] || data["content"]
246
+ title = data[:title] || data["title"]
247
+ updated_at_str = data[:updated_at] || data["updated_at"]
248
+
249
+ # Parse timestamp from ISO8601 string
250
+ updated_at = Time.parse(updated_at_str)
251
+
252
+ # Create entry with preserved timestamp
253
+ entry = Entry.new(
254
+ content: content,
255
+ title: title,
256
+ updated_at: updated_at,
257
+ size: content.bytesize,
258
+ )
259
+
260
+ # Update storage
261
+ @entries[path] = entry
262
+ @total_size += entry.size
263
+ end
264
+ end
265
+ end
221
266
  end
222
267
  end
223
268
  end
@@ -14,11 +14,11 @@ module SwarmSDK
14
14
  # - Search capabilities: Glob patterns and grep-style content search
15
15
  # - Thread-safe: Mutex-protected operations
16
16
  class Storage
17
- # Maximum size per entry (1MB)
18
- MAX_ENTRY_SIZE = 1_000_000
17
+ # Maximum size per entry (3MB)
18
+ MAX_ENTRY_SIZE = 3_000_000
19
19
 
20
- # Maximum total storage size (100MB)
21
- MAX_TOTAL_SIZE = 100_000_000
20
+ # Maximum total storage size (100GB)
21
+ MAX_TOTAL_SIZE = 100_000_000_000
22
22
 
23
23
  # Represents a single storage entry with metadata
24
24
  Entry = Struct.new(:content, :title, :updated_at, :size, keyword_init: true)
@@ -82,7 +82,10 @@ module SwarmSDK
82
82
  required: true
83
83
 
84
84
  def execute(**kwargs)
85
- "Thought noted."
85
+ <<~RESP
86
+ Thought noted.
87
+ RESP
88
+ # <system-reminder>The user cannot see your thoughts. You MUST NOT stop without giving the user a response.</system-reminder>
86
89
  end
87
90
 
88
91
  private
@@ -8,17 +8,17 @@ module SwarmSDK
8
8
  # Each agent maintains its own independent todo list.
9
9
  class TodoWrite < RubyLLM::Tool
10
10
  description <<~DESC
11
- Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
11
+ Use this tool to create and manage a structured task list for your current work session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
12
12
  It also helps the user understand the progress of the task and overall progress of their requests.
13
13
 
14
14
  ## When to Use This Tool
15
15
  Use this tool proactively in these scenarios:
16
16
 
17
17
  **CRITICAL**: Follow this workflow for multi-step tasks:
18
- 1. FIRST: Analyze the task scope (search files, read code, understand requirements)
19
- 2. SECOND: Create a COMPLETE todo list with ALL known tasks BEFORE starting implementation
18
+ 1. FIRST: Analyze the task scope (gather information, understand requirements)
19
+ 2. SECOND: Create a COMPLETE todo list with ALL known tasks BEFORE starting work
20
20
  3. THIRD: Execute tasks, marking in_progress → completed as you work
21
- 4. ONLY add new todos if unexpected work is discovered during implementation
21
+ 4. ONLY add new todos if unexpected work is discovered during execution
22
22
 
23
23
  Use the todo list when:
24
24
  1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
@@ -27,7 +27,7 @@ module SwarmSDK
27
27
  4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
28
28
  5. After receiving new instructions - After analyzing scope, create complete todo list before starting work
29
29
  6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
30
- 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
30
+ 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during execution
31
31
 
32
32
  ## When NOT to Use This Tool
33
33
 
@@ -73,9 +73,21 @@ module SwarmSDK
73
73
  - Create specific, actionable items
74
74
  - Break complex tasks into smaller, manageable steps
75
75
  - Use clear, descriptive task names
76
- - Always provide both forms:
77
- - content: "Fix authentication bug"
78
- - activeForm: "Fixing authentication bug"
76
+ - Always provide both forms (content and activeForm)
77
+
78
+ ## Examples
79
+
80
+ **Coding Tasks**:
81
+ - content: "Fix authentication bug in login handler"
82
+ - activeForm: "Fixing authentication bug in login handler"
83
+
84
+ **Non-Coding Tasks**:
85
+ - content: "Analyze customer feedback from Q4 survey"
86
+ - activeForm: "Analyzing customer feedback from Q4 survey"
87
+
88
+ **Research Tasks**:
89
+ - content: "Research best practices for API rate limiting"
90
+ - activeForm: "Researching best practices for API rate limiting"
79
91
 
80
92
  When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
81
93
  DESC
@@ -45,6 +45,24 @@ module SwarmSDK
45
45
  obj
46
46
  end
47
47
  end
48
+
49
+ # Convert hash to YAML string
50
+ #
51
+ # Converts a Ruby hash to a YAML string. Useful for creating inline
52
+ # swarm definitions from hash configurations.
53
+ #
54
+ # @param hash [Hash] Hash to convert
55
+ # @return [String] YAML string representation
56
+ #
57
+ # @example
58
+ # config = { version: 2, swarm: { name: "Test" } }
59
+ # Utils.hash_to_yaml(config)
60
+ # # => "---\nversion: 2\nswarm:\n name: Test\n"
61
+ def hash_to_yaml(hash)
62
+ # Convert symbols to strings for valid YAML
63
+ stringified = stringify_keys(hash)
64
+ stringified.to_yaml
65
+ end
48
66
  end
49
67
  end
50
68
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ # Internal result object for validation phase during snapshot restore
5
+ #
6
+ # Used during restore to track which agents can be restored and which
7
+ # need to be skipped due to configuration mismatches.
8
+ #
9
+ # @api private
10
+ class ValidationResult
11
+ attr_reader :warnings,
12
+ :skipped_agents,
13
+ :restorable_agents,
14
+ :skipped_delegations,
15
+ :restorable_delegations
16
+
17
+ # Initialize validation result
18
+ #
19
+ # @param warnings [Array<Hash>] Warning messages with details
20
+ # @param skipped_agents [Array<Symbol>] Names of agents that can't be restored
21
+ # @param restorable_agents [Array<Symbol>] Names of agents that can be restored
22
+ # @param skipped_delegations [Array<String>] Names of delegations that can't be restored
23
+ # @param restorable_delegations [Array<String>] Names of delegations that can be restored
24
+ def initialize(warnings:, skipped_agents:, restorable_agents:,
25
+ skipped_delegations:, restorable_delegations:)
26
+ @warnings = warnings
27
+ @skipped_agents = skipped_agents
28
+ @restorable_agents = restorable_agents
29
+ @skipped_delegations = skipped_delegations
30
+ @restorable_delegations = restorable_delegations
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.1.1"
4
+ VERSION = "2.2.0"
5
5
  end