swarm_memory 2.1.3 → 2.1.4

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/lib/claude_swarm/claude_mcp_server.rb +1 -0
  3. data/lib/claude_swarm/cli.rb +5 -18
  4. data/lib/claude_swarm/configuration.rb +2 -15
  5. data/lib/claude_swarm/mcp_generator.rb +1 -0
  6. data/lib/claude_swarm/openai/chat_completion.rb +4 -12
  7. data/lib/claude_swarm/openai/executor.rb +3 -1
  8. data/lib/claude_swarm/openai/responses.rb +13 -32
  9. data/lib/claude_swarm/version.rb +1 -1
  10. data/lib/swarm_cli/commands/run.rb +2 -2
  11. data/lib/swarm_cli/config_loader.rb +11 -11
  12. data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
  13. data/lib/swarm_cli/interactive_repl.rb +11 -5
  14. data/lib/swarm_cli/ui/icons.rb +0 -23
  15. data/lib/swarm_cli/version.rb +1 -1
  16. data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
  17. data/lib/swarm_memory/integration/sdk_plugin.rb +87 -7
  18. data/lib/swarm_memory/version.rb +1 -1
  19. data/lib/swarm_memory.rb +1 -1
  20. data/lib/swarm_sdk/agent/builder.rb +58 -0
  21. data/lib/swarm_sdk/agent/chat.rb +527 -1059
  22. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
  23. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  24. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
  25. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  26. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  27. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  28. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  29. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +12 -12
  30. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  31. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  32. data/lib/swarm_sdk/agent/context.rb +2 -2
  33. data/lib/swarm_sdk/agent/definition.rb +66 -154
  34. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  35. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  36. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  37. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  38. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  39. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  40. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  41. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  42. data/lib/swarm_sdk/configuration.rb +65 -543
  43. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  44. data/lib/swarm_sdk/context_compactor.rb +6 -11
  45. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  46. data/lib/swarm_sdk/context_management/context.rb +328 -0
  47. data/lib/swarm_sdk/defaults.rb +196 -0
  48. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  49. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  50. data/lib/swarm_sdk/log_collector.rb +179 -29
  51. data/lib/swarm_sdk/log_stream.rb +29 -0
  52. data/lib/swarm_sdk/node_context.rb +1 -1
  53. data/lib/swarm_sdk/observer/builder.rb +81 -0
  54. data/lib/swarm_sdk/observer/config.rb +45 -0
  55. data/lib/swarm_sdk/observer/manager.rb +236 -0
  56. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  57. data/lib/swarm_sdk/plugin.rb +93 -3
  58. data/lib/swarm_sdk/snapshot.rb +6 -6
  59. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  60. data/lib/swarm_sdk/state_restorer.rb +136 -151
  61. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  62. data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
  63. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  64. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  65. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  66. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  67. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  68. data/lib/swarm_sdk/swarm/tool_configurator.rb +42 -138
  69. data/lib/swarm_sdk/swarm.rb +137 -679
  70. data/lib/swarm_sdk/tools/bash.rb +11 -3
  71. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  72. data/lib/swarm_sdk/tools/edit.rb +8 -13
  73. data/lib/swarm_sdk/tools/glob.rb +9 -1
  74. data/lib/swarm_sdk/tools/grep.rb +7 -0
  75. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  76. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  77. data/lib/swarm_sdk/tools/read.rb +11 -13
  78. data/lib/swarm_sdk/tools/registry.rb +122 -10
  79. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +8 -5
  80. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  81. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  82. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  83. data/lib/swarm_sdk/tools/write.rb +8 -13
  84. data/lib/swarm_sdk/version.rb +1 -1
  85. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  86. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  87. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  88. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +3 -3
  89. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  90. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  91. data/lib/swarm_sdk.rb +33 -3
  92. metadata +37 -14
  93. data/lib/swarm_memory/chat_extension.rb +0 -34
  94. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- # Creates snapshots of swarm conversation state
4
+ # Creates snapshots of swarm/workflow conversation state
5
5
  #
6
- # Unified implementation that works for both Swarm and NodeOrchestrator.
6
+ # Unified implementation that works for both Swarm and Workflow.
7
7
  # Captures conversation history, context state, scratchpad contents, and
8
8
  # read tracking information.
9
9
  #
@@ -15,20 +15,19 @@ module SwarmSDK
15
15
  # swarm = SwarmSDK.build { ... }
16
16
  # swarm.execute("Build authentication")
17
17
  # snapshot = swarm.snapshot
18
- # File.write("session.json", JSON.pretty_generate(snapshot))
18
+ # snapshot.write_to_file("session.json")
19
19
  #
20
- # @example Snapshot a node orchestrator
21
- # orchestrator = NodeOrchestrator.new(...)
22
- # orchestrator.execute("Build feature")
23
- # snapshot = orchestrator.snapshot
24
- # redis.set("session:#{user_id}", JSON.generate(snapshot))
20
+ # @example Snapshot a workflow
21
+ # workflow = SwarmSDK.workflow { ... }
22
+ # workflow.execute("Build feature")
23
+ # snapshot = workflow.snapshot
24
+ # snapshot.write_to_file("workflow_session.json")
25
25
  class StateSnapshot
26
26
  # Initialize snapshot creator
27
27
  #
28
- # @param orchestration [Swarm, NodeOrchestrator] Swarm or orchestrator to snapshot
28
+ # @param orchestration [Swarm, Workflow] Swarm or workflow to snapshot
29
29
  def initialize(orchestration)
30
30
  @orchestration = orchestration
31
- @type = orchestration.is_a?(SwarmSDK::NodeOrchestrator) ? :node_orchestrator : :swarm
32
31
  end
33
32
 
34
33
  # Create snapshot of current state
@@ -39,76 +38,54 @@ module SwarmSDK
39
38
  # @return [Snapshot] Snapshot object
40
39
  def snapshot
41
40
  data = {
42
- version: "1.0.0",
43
- type: @type.to_s,
41
+ version: "2.1.0", # Bumped for plugin state abstraction
42
+ type: type_name,
44
43
  snapshot_at: Time.now.utc.iso8601,
45
44
  swarm_sdk_version: SwarmSDK::VERSION,
45
+ metadata: snapshot_metadata,
46
46
  agents: snapshot_agents,
47
47
  delegation_instances: snapshot_delegation_instances,
48
+ scratchpad: snapshot_scratchpad,
48
49
  read_tracking: snapshot_read_tracking,
49
- memory_read_tracking: snapshot_memory_read_tracking,
50
+ plugin_states: snapshot_plugin_states,
50
51
  }
51
52
 
52
- # Add scratchpad for both Swarm and NodeOrchestrator (shared across nodes)
53
- data[:scratchpad] = snapshot_scratchpad
54
-
55
- # Add type-specific metadata
56
- if @type == :swarm
57
- data[:swarm] = snapshot_swarm_metadata
58
- else
59
- data[:orchestrator] = snapshot_orchestrator_metadata
60
- end
61
-
62
53
  # Wrap in Snapshot object
63
54
  SwarmSDK::Snapshot.new(data)
64
55
  end
65
56
 
66
57
  private
67
58
 
68
- # Snapshot swarm-specific metadata
59
+ # Get type name for snapshot
69
60
  #
70
- # @return [Hash] Swarm metadata
71
- def snapshot_swarm_metadata
72
- {
73
- id: @orchestration.swarm_id,
74
- parent_id: @orchestration.parent_swarm_id,
75
- first_message_sent: @orchestration.first_message_sent?,
76
- }
61
+ # @return [String] "swarm" or "workflow"
62
+ def type_name
63
+ @orchestration.class.name.split("::").last.downcase
77
64
  end
78
65
 
79
- # Snapshot orchestrator-specific metadata
66
+ # Snapshot common metadata
80
67
  #
81
- # @return [Hash] Orchestrator metadata
82
- def snapshot_orchestrator_metadata
68
+ # @return [Hash] Metadata
69
+ def snapshot_metadata
83
70
  {
84
- id: @orchestration.swarm_id || generate_orchestrator_id,
85
- parent_id: nil, # NodeOrchestrator doesn't support parent_id
71
+ id: @orchestration.swarm_id,
72
+ parent_id: @orchestration.parent_swarm_id,
73
+ name: @orchestration.name,
74
+ # Swarm-specific: first_message_sent (Workflow returns false)
75
+ first_message_sent: @orchestration.first_message_sent?,
86
76
  }
87
77
  end
88
78
 
89
- # Generate orchestrator ID if not set
90
- #
91
- # @return [String] Generated ID
92
- def generate_orchestrator_id
93
- name = @orchestration.swarm_name.to_s.gsub(/[^a-z0-9_-]/i, "_").downcase
94
- "#{name}_#{SecureRandom.hex(4)}"
95
- end
96
-
97
79
  # Snapshot all agent conversations and context state
98
80
  #
81
+ # Uses interface method: primary_agents (no type checking!)
82
+ #
99
83
  # @return [Hash] { agent_name => { conversation:, context_state:, system_prompt: } }
100
84
  def snapshot_agents
101
85
  result = {}
102
86
 
103
- # Get agents from appropriate source
104
- agents_hash = if @type == :swarm
105
- @orchestration.agents
106
- else
107
- @orchestration.agent_instance_cache[:primary]
108
- end
109
-
110
- agents_hash.each do |agent_name, agent_chat|
111
- # Get system prompt from agent definition
87
+ # Use interface method - works for both Swarm and Workflow!
88
+ @orchestration.primary_agents.each do |agent_name, agent_chat|
112
89
  agent_definition = @orchestration.agent_definitions[agent_name]
113
90
  system_prompt = agent_definition&.system_prompt
114
91
 
@@ -127,35 +104,24 @@ module SwarmSDK
127
104
  # @param agent_chat [Agent::Chat] Agent chat instance
128
105
  # @return [Array<Hash>] Serialized messages
129
106
  def snapshot_conversation(agent_chat)
130
- messages = agent_chat.messages
131
- messages.map { |msg| serialize_message(msg) }
107
+ agent_chat.messages.map { |msg| serialize_message(msg) }
132
108
  end
133
109
 
134
110
  # Serialize a single message
135
111
  #
136
- # Handles RubyLLM::Message serialization with proper handling of:
137
- # - Content objects (text + attachments)
138
- # - Tool calls (must manually call .to_h on each)
139
- # - Tool call IDs, tokens, model IDs
140
- #
141
112
  # @param msg [RubyLLM::Message] Message to serialize
142
113
  # @return [Hash] Serialized message
143
114
  def serialize_message(msg)
144
115
  hash = { role: msg.role }
145
116
 
146
- # Handle content - check msg.content directly, not from msg.to_h
147
- # msg.to_h converts Content to String when no attachments present
117
+ # Handle content - check msg.content directly
148
118
  hash[:content] = if msg.content.is_a?(RubyLLM::Content)
149
- # Content object: serialize with text + attachments
150
119
  msg.content.to_h
151
120
  else
152
- # Plain string content
153
121
  msg.content
154
122
  end
155
123
 
156
- # Handle tool calls - must manually extract fields
157
- # RubyLLM::ToolCall#to_h doesn't reliably serialize id/name fields
158
- # msg.tool_calls is a Hash<String, ToolCall>, so we need .values
124
+ # Handle tool calls
159
125
  if msg.tool_calls && !msg.tool_calls.empty?
160
126
  hash[:tool_calls] = msg.tool_calls.values.map do |tc|
161
127
  {
@@ -185,7 +151,6 @@ module SwarmSDK
185
151
 
186
152
  {
187
153
  warning_thresholds_hit: agent_context.warning_thresholds_hit.to_a,
188
- # NOTE: @compression_applied initializes to nil, not false
189
154
  compression_applied: context_manager.compression_applied,
190
155
  last_todowrite_message_index: agent_chat.last_todowrite_message_index,
191
156
  active_skill_path: agent_chat.active_skill_path,
@@ -194,19 +159,15 @@ module SwarmSDK
194
159
 
195
160
  # Snapshot delegation instance conversations
196
161
  #
162
+ # Uses interface method: delegation_instances_hash (no type checking!)
163
+ #
197
164
  # @return [Hash] { "delegate@delegator" => { conversation:, context_state:, system_prompt: } }
198
165
  def snapshot_delegation_instances
199
166
  result = {}
200
167
 
201
- # Get delegation instances from appropriate source
202
- delegations_hash = if @type == :swarm
203
- @orchestration.delegation_instances
204
- else
205
- @orchestration.agent_instance_cache[:delegations]
206
- end
207
-
208
- delegations_hash.each do |instance_name, delegation_chat|
209
- # Extract base agent name from instance name (e.g., "backend@lead" -> "backend")
168
+ # Use interface method - works for both Swarm and Workflow!
169
+ @orchestration.delegation_instances_hash.each do |instance_name, delegation_chat|
170
+ # Extract base agent name from instance name
210
171
  base_name = instance_name.to_s.split("@").first.to_sym
211
172
 
212
173
  # Get system prompt from base agent definition
@@ -225,24 +186,21 @@ module SwarmSDK
225
186
 
226
187
  # Snapshot scratchpad contents
227
188
  #
228
- # For Swarm: uses scratchpad_storage (returns flat hash)
229
- # For NodeOrchestrator: returns structured hash with metadata
230
- # - Enabled mode: { shared: true, data: { path => entry } }
231
- # - Per-node mode: { shared: false, data: { node_name => { path => entry } } }
189
+ # Detects type and calls appropriate method.
232
190
  #
233
191
  # @return [Hash] Scratchpad snapshot data
234
192
  def snapshot_scratchpad
235
- if @type == :node_orchestrator
236
- snapshot_node_orchestrator_scratchpad
193
+ if @orchestration.is_a?(Workflow)
194
+ snapshot_workflow_scratchpad
237
195
  else
238
196
  snapshot_swarm_scratchpad
239
197
  end
240
198
  end
241
199
 
242
- # Snapshot scratchpad for NodeOrchestrator
200
+ # Snapshot scratchpad for Workflow
243
201
  #
244
202
  # @return [Hash] Structured scratchpad data with mode metadata
245
- def snapshot_node_orchestrator_scratchpad
203
+ def snapshot_workflow_scratchpad
246
204
  all_scratchpads = @orchestration.all_scratchpads
247
205
  return {} unless all_scratchpads&.any?
248
206
 
@@ -328,22 +286,31 @@ module SwarmSDK
328
286
  result
329
287
  end
330
288
 
331
- # Snapshot memory read tracking state
289
+ # Snapshot plugin-specific state for all plugins
332
290
  #
333
- # @return [Hash] { agent_name => { entry_path => digest } }
334
- def snapshot_memory_read_tracking
335
- return {} unless defined?(SwarmMemory::Core::StorageReadTracker)
336
-
291
+ # Iterates over all registered plugins and collects their agent-specific state.
292
+ # This decouples the SDK from plugin-specific implementations.
293
+ #
294
+ # @return [Hash] { plugin_name => { agent_name => plugin_state } }
295
+ def snapshot_plugin_states
337
296
  result = {}
338
297
 
339
298
  # Get all agents (primary + delegations)
340
299
  agent_names = all_agent_names
341
300
 
342
- agent_names.each do |agent_name|
343
- entries_with_digests = SwarmMemory::Core::StorageReadTracker.get_read_entries(agent_name)
344
- next if entries_with_digests.empty?
301
+ # Iterate over all registered plugins
302
+ PluginRegistry.all.each do |plugin|
303
+ plugin_state = {}
304
+
305
+ agent_names.each do |agent_name|
306
+ agent_state = plugin.snapshot_agent_state(agent_name)
307
+ next if agent_state.empty?
308
+
309
+ plugin_state[agent_name.to_s] = agent_state
310
+ end
345
311
 
346
- result[agent_name.to_s] = entries_with_digests
312
+ # Only include plugin if it has state for at least one agent
313
+ result[plugin.name.to_s] = plugin_state unless plugin_state.empty?
347
314
  end
348
315
 
349
316
  result
@@ -351,17 +318,15 @@ module SwarmSDK
351
318
 
352
319
  # All agent names (primary + delegations)
353
320
  #
321
+ # Uses interface methods - no type checking!
322
+ #
354
323
  # @return [Array<Symbol>] All agent names
355
324
  def all_agent_names
356
- # Get primary agent names - both types use agent_definitions
325
+ # Get primary agent names
357
326
  agents_hash = @orchestration.agent_definitions.keys
358
327
 
359
328
  # Add delegation instance names
360
- delegations_hash = if @type == :swarm
361
- @orchestration.delegation_instances.keys
362
- else
363
- @orchestration.agent_instance_cache[:delegations].keys
364
- end
329
+ delegations_hash = @orchestration.delegation_instances_hash.keys
365
330
 
366
331
  agents_hash + delegations_hash.map(&:to_sym)
367
332
  end