swarm_sdk 2.2.0 → 2.3.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/agent/builder.rb +58 -0
  3. data/lib/swarm_sdk/agent/chat.rb +527 -1059
  4. data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
  5. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
  6. data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
  7. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
  8. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
  9. data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
  10. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
  11. data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +12 -12
  12. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
  13. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
  14. data/lib/swarm_sdk/agent/context.rb +2 -2
  15. data/lib/swarm_sdk/agent/definition.rb +66 -154
  16. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
  17. data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
  18. data/lib/swarm_sdk/builders/base_builder.rb +409 -0
  19. data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
  20. data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
  21. data/lib/swarm_sdk/concerns/validatable.rb +55 -0
  22. data/lib/swarm_sdk/configuration/parser.rb +353 -0
  23. data/lib/swarm_sdk/configuration/translator.rb +255 -0
  24. data/lib/swarm_sdk/configuration.rb +65 -543
  25. data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
  26. data/lib/swarm_sdk/context_compactor.rb +6 -11
  27. data/lib/swarm_sdk/context_management/builder.rb +128 -0
  28. data/lib/swarm_sdk/context_management/context.rb +328 -0
  29. data/lib/swarm_sdk/defaults.rb +196 -0
  30. data/lib/swarm_sdk/events_to_messages.rb +18 -0
  31. data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
  32. data/lib/swarm_sdk/log_collector.rb +179 -29
  33. data/lib/swarm_sdk/log_stream.rb +29 -0
  34. data/lib/swarm_sdk/node_context.rb +1 -1
  35. data/lib/swarm_sdk/observer/builder.rb +81 -0
  36. data/lib/swarm_sdk/observer/config.rb +45 -0
  37. data/lib/swarm_sdk/observer/manager.rb +236 -0
  38. data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
  39. data/lib/swarm_sdk/plugin.rb +93 -3
  40. data/lib/swarm_sdk/snapshot.rb +6 -6
  41. data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
  42. data/lib/swarm_sdk/state_restorer.rb +136 -151
  43. data/lib/swarm_sdk/state_snapshot.rb +65 -100
  44. data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
  45. data/lib/swarm_sdk/swarm/builder.rb +44 -578
  46. data/lib/swarm_sdk/swarm/executor.rb +213 -0
  47. data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
  48. data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
  49. data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
  50. data/lib/swarm_sdk/swarm/tool_configurator.rb +42 -138
  51. data/lib/swarm_sdk/swarm.rb +137 -679
  52. data/lib/swarm_sdk/tools/bash.rb +11 -3
  53. data/lib/swarm_sdk/tools/delegate.rb +61 -43
  54. data/lib/swarm_sdk/tools/edit.rb +8 -13
  55. data/lib/swarm_sdk/tools/glob.rb +9 -1
  56. data/lib/swarm_sdk/tools/grep.rb +7 -0
  57. data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
  58. data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
  59. data/lib/swarm_sdk/tools/read.rb +11 -13
  60. data/lib/swarm_sdk/tools/registry.rb +122 -10
  61. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +8 -5
  62. data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
  63. data/lib/swarm_sdk/tools/todo_write.rb +7 -0
  64. data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
  65. data/lib/swarm_sdk/tools/write.rb +8 -13
  66. data/lib/swarm_sdk/version.rb +1 -1
  67. data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
  68. data/lib/swarm_sdk/workflow/builder.rb +143 -0
  69. data/lib/swarm_sdk/workflow/executor.rb +497 -0
  70. data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +3 -3
  71. data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
  72. data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
  73. data/lib/swarm_sdk.rb +33 -3
  74. metadata +67 -15
  75. data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Observer
5
+ # Manages observer agent executions
6
+ #
7
+ # Handles:
8
+ # - Event subscription via LogCollector
9
+ # - Spawning async tasks for observer agents
10
+ # - Self-consumption protection (observers don't trigger themselves)
11
+ # - Task lifecycle and cleanup
12
+ #
13
+ # @example
14
+ # manager = Observer::Manager.new(swarm)
15
+ # manager.add_config(profiler_config)
16
+ # manager.setup
17
+ # # ... main execution happens ...
18
+ # manager.wait_for_completion
19
+ # manager.cleanup
20
+ class Manager
21
+ # Initialize manager with swarm reference
22
+ #
23
+ # @param swarm [Swarm] Parent swarm instance
24
+ def initialize(swarm)
25
+ @swarm = swarm
26
+ @configs = []
27
+ @subscription_ids = []
28
+ @barrier = nil
29
+ @task_ids = {}
30
+ end
31
+
32
+ # Add an observer configuration
33
+ #
34
+ # @param config [Observer::Config] Observer configuration
35
+ # @return [void]
36
+ def add_config(config)
37
+ @configs << config
38
+ end
39
+
40
+ # Setup event subscriptions for all observer configs
41
+ #
42
+ # Creates LogCollector subscriptions for each event type, filtered by type.
43
+ # Must be called after setup_logging() in Swarm.execute().
44
+ #
45
+ # @return [void]
46
+ def setup
47
+ @barrier = Async::Barrier.new
48
+
49
+ @configs.each do |config|
50
+ config.event_handlers.each do |event_type, handler|
51
+ sub_id = LogCollector.subscribe(filter: { type: event_type.to_s }) do |event|
52
+ handle_event(config, handler, event)
53
+ end
54
+ @subscription_ids << sub_id
55
+ end
56
+ end
57
+ end
58
+
59
+ # Wait for all observer tasks to complete
60
+ #
61
+ # Uses Async::Barrier.wait to wait for all spawned tasks.
62
+ # Handles errors gracefully without stopping other observers.
63
+ #
64
+ # @return [void]
65
+ def wait_for_completion
66
+ return unless @barrier
67
+
68
+ # Wait for all tasks, handling errors gracefully
69
+ # Barrier.wait re-raises first exception by default, so we use block form
70
+ @barrier.wait do |task|
71
+ task.wait
72
+ rescue StandardError => error
73
+ # Log but don't stop waiting for other observers
74
+ RubyLLM.logger.error("Observer task failed: #{error.message}")
75
+ end
76
+ end
77
+
78
+ # Cleanup all subscriptions
79
+ #
80
+ # Unsubscribes from LogCollector to prevent memory leaks.
81
+ # Called by Executor.cleanup_after_execution.
82
+ #
83
+ # @return [void]
84
+ def cleanup
85
+ @subscription_ids.each { |id| LogCollector.unsubscribe(id) }
86
+ @subscription_ids.clear
87
+ end
88
+
89
+ private
90
+
91
+ # Handle an incoming event
92
+ #
93
+ # Checks self-consumption protection, calls handler block,
94
+ # and spawns execution if handler returns a prompt.
95
+ #
96
+ # @param config [Observer::Config] Observer configuration
97
+ # @param handler [Proc] Event handler block
98
+ # @param event [Hash] Event data
99
+ # @return [void]
100
+ def handle_event(config, handler, event)
101
+ # CRITICAL: Prevent self-consumption - observer must not consume its own events
102
+ # This prevents infinite loops where an observer triggers itself
103
+ return if event[:agent] == config.agent_name
104
+
105
+ prompt = handler.call(event)
106
+ return unless prompt # nil means skip
107
+
108
+ spawn_execution(config, prompt, event)
109
+ end
110
+
111
+ # Spawn an async task for observer execution
112
+ #
113
+ # Creates a child async task via barrier for the observer agent.
114
+ # Sets observer-specific Fiber context.
115
+ #
116
+ # @param config [Observer::Config] Observer configuration
117
+ # @param prompt [String] Prompt to send to observer agent
118
+ # @param trigger_event [Hash] Event that triggered this execution
119
+ # @return [void]
120
+ def spawn_execution(config, prompt, trigger_event)
121
+ @barrier.async do
122
+ # Set observer-specific context in child fiber
123
+ # No need to restore - child fiber dies when task completes
124
+ Fiber[:swarm_id] = "#{Fiber[:swarm_id]}/observer:#{config.agent_name}"
125
+
126
+ execute_observer_agent(config, prompt, trigger_event)
127
+ end
128
+ end
129
+
130
+ # Execute the observer agent with the prompt
131
+ #
132
+ # Creates an isolated chat instance and sends the prompt.
133
+ # Emits lifecycle events (start, complete, error).
134
+ #
135
+ # @param config [Observer::Config] Observer configuration
136
+ # @param prompt [String] Prompt to execute
137
+ # @param trigger_event [Hash] Event that triggered this execution
138
+ # @return [RubyLLM::Message, nil] Response or nil on error
139
+ def execute_observer_agent(config, prompt, trigger_event)
140
+ agent_chat = create_isolated_chat(config.agent_name)
141
+
142
+ start_time = Time.now
143
+ emit_observer_start(config, trigger_event)
144
+
145
+ result = agent_chat.ask(prompt)
146
+
147
+ emit_observer_complete(config, trigger_event, result, Time.now - start_time)
148
+ result
149
+ rescue StandardError => e
150
+ emit_observer_error(config, trigger_event, e)
151
+ nil
152
+ end
153
+
154
+ # Create an isolated chat instance for the observer agent
155
+ #
156
+ # Uses AgentInitializer to create a fully configured agent chat
157
+ # without delegation tools (observers don't delegate).
158
+ #
159
+ # @param agent_name [Symbol] Name of the observer agent
160
+ # @return [Agent::Chat] Isolated chat instance
161
+ def create_isolated_chat(agent_name)
162
+ initializer = Swarm::AgentInitializer.new(@swarm)
163
+ initializer.initialize_isolated_agent(agent_name)
164
+ end
165
+
166
+ # Emit observer_agent_start event
167
+ #
168
+ # @param config [Observer::Config] Observer configuration
169
+ # @param trigger_event [Hash] Triggering event
170
+ # @return [void]
171
+ def emit_observer_start(config, trigger_event)
172
+ return unless LogStream.emitter
173
+
174
+ LogStream.emit(
175
+ type: "observer_agent_start",
176
+ agent: config.agent_name,
177
+ trigger_event: trigger_event[:type],
178
+ trigger_timestamp: trigger_event[:timestamp],
179
+ task_id: generate_task_id(config),
180
+ timestamp: Time.now.utc.iso8601,
181
+ )
182
+ end
183
+
184
+ # Emit observer_agent_complete event
185
+ #
186
+ # @param config [Observer::Config] Observer configuration
187
+ # @param trigger_event [Hash] Triggering event
188
+ # @param result [RubyLLM::Message] Agent response
189
+ # @param duration [Float] Execution duration in seconds
190
+ # @return [void]
191
+ def emit_observer_complete(config, trigger_event, result, duration)
192
+ return unless LogStream.emitter
193
+
194
+ LogStream.emit(
195
+ type: "observer_agent_complete",
196
+ agent: config.agent_name,
197
+ trigger_event: trigger_event[:type],
198
+ task_id: generate_task_id(config),
199
+ duration: duration.round(3),
200
+ success: true,
201
+ timestamp: Time.now.utc.iso8601,
202
+ )
203
+ end
204
+
205
+ # Emit observer_agent_error event
206
+ #
207
+ # @param config [Observer::Config] Observer configuration
208
+ # @param trigger_event [Hash] Triggering event
209
+ # @param error [StandardError] Error that occurred
210
+ # @return [void]
211
+ def emit_observer_error(config, trigger_event, error)
212
+ return unless LogStream.emitter
213
+
214
+ LogStream.emit(
215
+ type: "observer_agent_error",
216
+ agent: config.agent_name,
217
+ trigger_event: trigger_event[:type],
218
+ task_id: generate_task_id(config),
219
+ error: error.message,
220
+ backtrace: error.backtrace&.first(5),
221
+ timestamp: Time.now.utc.iso8601,
222
+ )
223
+ end
224
+
225
+ # Generate a unique task ID for an observer
226
+ #
227
+ # Cached per observer agent name for correlation.
228
+ #
229
+ # @param config [Observer::Config] Observer configuration
230
+ # @return [String] Task ID
231
+ def generate_task_id(config)
232
+ @task_ids[config.agent_name] ||= "observer_#{SecureRandom.hex(6)}"
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Patterns
5
+ # Observes another agent's actions with optional real-time processing
6
+ #
7
+ # @example Basic observation
8
+ # observer = AgentObserver.new(target: :backend)
9
+ # observer.start
10
+ # swarm.execute("task")
11
+ # observer.stop
12
+ # puts observer.observations
13
+ #
14
+ # @example Real-time analysis
15
+ # observer = AgentObserver.new(
16
+ # target: :backend,
17
+ # on_event: ->(e) { analyze_security(e) }
18
+ # )
19
+ #
20
+ # @example Filter specific event types
21
+ # observer = AgentObserver.new(
22
+ # target: :backend,
23
+ # event_types: ["tool_call", "tool_result"]
24
+ # )
25
+ class AgentObserver
26
+ attr_reader :observations, :target_agent
27
+
28
+ # Initialize observer
29
+ #
30
+ # @param target [Symbol] Agent to observe
31
+ # @param event_types [Array<String>] Event types to capture (default: all)
32
+ # @param on_event [Proc] Optional callback for real-time processing
33
+ def initialize(target:, event_types: nil, on_event: nil)
34
+ @target_agent = target
35
+ @event_types = event_types
36
+ @on_event = on_event
37
+ @observations = []
38
+ @subscription_id = nil
39
+ @started_at = nil
40
+ end
41
+
42
+ # Start observing
43
+ #
44
+ # @return [void]
45
+ def start
46
+ return if @subscription_id
47
+
48
+ @started_at = Time.now
49
+ @observations.clear
50
+
51
+ filter = { agent: @target_agent }
52
+ filter[:type] = @event_types if @event_types
53
+
54
+ @subscription_id = LogCollector.subscribe(filter: filter) do |event|
55
+ @observations << event.merge(observed_at: Time.now)
56
+ @on_event&.call(event)
57
+ end
58
+ end
59
+
60
+ # Stop observing
61
+ #
62
+ # @return [void]
63
+ def stop
64
+ return unless @subscription_id
65
+
66
+ LogCollector.unsubscribe(@subscription_id)
67
+ @subscription_id = nil
68
+ end
69
+
70
+ # Check if currently observing
71
+ #
72
+ # @return [Boolean] true if actively observing
73
+ def observing?
74
+ !@subscription_id.nil?
75
+ end
76
+
77
+ # Get summary of observations
78
+ #
79
+ # @return [Hash] Summary statistics
80
+ def summary
81
+ {
82
+ target: @target_agent,
83
+ started_at: @started_at,
84
+ duration_seconds: @started_at ? (Time.now - @started_at).round(2) : 0,
85
+ total_events: @observations.size,
86
+ event_breakdown: @observations.group_by { |e| e[:type] }.transform_values(&:count),
87
+ tool_calls: @observations.select { |e| e[:type] == "tool_call" }.map { |e| e[:tool_name] },
88
+ errors: @observations.select { |e| e[:type] == "internal_error" },
89
+ }
90
+ end
91
+
92
+ # Format observations for LLM consumption
93
+ #
94
+ # Useful for providing observation data to another agent for analysis
95
+ #
96
+ # @return [String] Formatted observation log
97
+ def to_llm_context
98
+ @observations.map do |event|
99
+ case event[:type]
100
+ when "tool_call"
101
+ "- Called #{event[:tool_name]} with: #{truncate_json(event[:arguments])}"
102
+ when "tool_result"
103
+ "- #{event[:tool_name]} returned: #{truncate(event[:result])}"
104
+ when "agent_step"
105
+ "- Thinking: #{truncate(event[:content])}"
106
+ when "agent_stop"
107
+ "- Final response: #{truncate(event[:content])}"
108
+ else
109
+ "- [#{event[:type]}] #{event.except(:type, :timestamp, :observed_at).to_json}"
110
+ end
111
+ end.join("\n")
112
+ end
113
+
114
+ # Clear collected observations
115
+ #
116
+ # @return [void]
117
+ def clear_observations
118
+ @observations.clear
119
+ end
120
+
121
+ # Execute block while observing
122
+ #
123
+ # Automatically starts and stops observation around the block
124
+ #
125
+ # @example
126
+ # observer = AgentObserver.new(target: :backend)
127
+ # observer.observe do
128
+ # swarm.execute("Build API")
129
+ # end
130
+ # puts observer.summary
131
+ #
132
+ # @yield Block to execute while observing
133
+ # @return [Object] Result from the block
134
+ def observe
135
+ start
136
+ yield
137
+ ensure
138
+ stop
139
+ end
140
+
141
+ private
142
+
143
+ def truncate(text, max_length = 200)
144
+ return "" if text.nil?
145
+
146
+ text = text.to_s
147
+ return text if text.length <= max_length
148
+
149
+ "#{text[0...max_length]}..."
150
+ end
151
+
152
+ def truncate_json(obj, max_length = 100)
153
+ return "{}" if obj.nil?
154
+
155
+ json = obj.to_json
156
+ truncate(json, max_length)
157
+ end
158
+ end
159
+ end
160
+ end
@@ -10,7 +10,7 @@ module SwarmSDK
10
10
  # ## Adding Custom Attributes to Agents
11
11
  #
12
12
  # Plugins can add custom attributes to Agent::Definition that are preserved
13
- # when agents are cloned (e.g., in NodeOrchestrator). To do this:
13
+ # when agents are cloned (e.g., in Workflow). To do this:
14
14
  #
15
15
  # 1. Add attr_reader to Agent::Definition for your attribute
16
16
  # 2. Parse the attribute in Agent::Definition#initialize
@@ -62,7 +62,7 @@ module SwarmSDK
62
62
  # my_custom_config { option: "value" }
63
63
  # end
64
64
  #
65
- # And it will be preserved when NodeOrchestrator clones the agent!
65
+ # And it will be preserved when Workflow clones the agent!
66
66
  #
67
67
  # @example Real-world: SwarmMemory plugin
68
68
  # # SwarmMemory adds 'memory' attribute to agents
@@ -197,7 +197,7 @@ module SwarmSDK
197
197
  # Contribute to agent serialization (optional)
198
198
  #
199
199
  # Called when Agent::Definition.to_h is invoked (e.g., for cloning agents
200
- # in NodeOrchestrator). Plugins can return config keys that should be
200
+ # in Workflow). Plugins can return config keys that should be
201
201
  # included in the serialized hash to preserve their state.
202
202
  #
203
203
  # This allows plugins to maintain their configuration when agents are
@@ -215,5 +215,95 @@ module SwarmSDK
215
215
  def serialize_config(agent_definition:)
216
216
  {}
217
217
  end
218
+
219
+ # Snapshot plugin-specific state for an agent
220
+ #
221
+ # Called during state snapshot creation (e.g., session persistence).
222
+ # Return any state your plugin needs to persist for this agent.
223
+ # The returned hash will be JSON serialized.
224
+ #
225
+ # @param agent_name [Symbol] Agent identifier
226
+ # @return [Hash] Plugin-specific state (empty hash if nothing to snapshot)
227
+ #
228
+ # @example Memory read tracking
229
+ # def snapshot_agent_state(agent_name)
230
+ # entries = StorageReadTracker.get_read_entries(agent_name)
231
+ # return {} if entries.empty?
232
+ #
233
+ # { read_entries: entries }
234
+ # end
235
+ def snapshot_agent_state(agent_name)
236
+ {}
237
+ end
238
+
239
+ # Restore plugin-specific state for an agent
240
+ #
241
+ # Called during state restoration. Restore any persisted state.
242
+ # This method is idempotent - calling it multiple times with
243
+ # the same state should produce the same result.
244
+ #
245
+ # @param agent_name [Symbol] Agent identifier
246
+ # @param state [Hash] Previously snapshotted state (with symbol keys)
247
+ # @return [void]
248
+ #
249
+ # @example Memory read tracking
250
+ # def restore_agent_state(agent_name, state)
251
+ # entries = state[:read_entries] || state["read_entries"]
252
+ # return unless entries
253
+ #
254
+ # StorageReadTracker.restore_read_entries(agent_name, entries)
255
+ # end
256
+ def restore_agent_state(agent_name, state)
257
+ # Override if needed
258
+ end
259
+
260
+ # Get digest for a tool result (e.g., file hash, memory entry hash)
261
+ #
262
+ # Called during tool result metadata collection. Returns a digest
263
+ # that can be used to detect if the resource has changed since
264
+ # it was last read. This enables change detection hooks.
265
+ #
266
+ # @param agent_name [Symbol] Agent identifier
267
+ # @param tool_name [String] Name of the tool (e.g., "MemoryRead")
268
+ # @param path [String] Path or identifier of the resource
269
+ # @return [String, nil] Digest string or nil if not tracked by this plugin
270
+ #
271
+ # @example Memory read tracking
272
+ # def get_tool_result_digest(agent_name:, tool_name:, path:)
273
+ # return unless tool_name == "MemoryRead"
274
+ #
275
+ # StorageReadTracker.get_read_entries(agent_name)[path]
276
+ # end
277
+ def get_tool_result_digest(agent_name:, tool_name:, path:)
278
+ nil
279
+ end
280
+
281
+ # Translate YAML configuration into DSL calls
282
+ #
283
+ # Called during YAML-to-DSL translation. Plugins can translate their
284
+ # specific YAML configuration keys into DSL method calls on the builder.
285
+ # This allows SDK to remain plugin-agnostic while plugins can add
286
+ # YAML configuration support.
287
+ #
288
+ # @param builder [Agent::Builder] Builder instance (self in DSL context)
289
+ # @param agent_config [Hash] Full agent config from YAML
290
+ # @return [void]
291
+ #
292
+ # @example Memory plugin YAML translation
293
+ # def translate_yaml_config(builder, agent_config)
294
+ # memory_config = agent_config[:memory]
295
+ # return unless memory_config
296
+ #
297
+ # builder.instance_eval do
298
+ # memory do
299
+ # directory(memory_config[:directory])
300
+ # adapter(memory_config[:adapter]) if memory_config[:adapter]
301
+ # mode(memory_config[:mode]) if memory_config[:mode]
302
+ # end
303
+ # end
304
+ # end
305
+ def translate_yaml_config(builder, agent_config)
306
+ # Override if plugin needs YAML configuration support
307
+ end
218
308
  end
219
309
  end
@@ -102,7 +102,7 @@ module SwarmSDK
102
102
  @data[:version] || @data["version"]
103
103
  end
104
104
 
105
- # Get snapshot type (swarm or node_orchestrator)
105
+ # Get snapshot type (swarm or workflow)
106
106
  #
107
107
  # @return [String] Snapshot type
108
108
  def type
@@ -139,18 +139,18 @@ module SwarmSDK
139
139
  delegations ? delegations.keys.map(&:to_s) : []
140
140
  end
141
141
 
142
- # Check if snapshot is for a swarm (vs node_orchestrator)
142
+ # Check if snapshot is for a swarm (vs workflow)
143
143
  #
144
144
  # @return [Boolean] true if swarm snapshot
145
145
  def swarm?
146
146
  type == "swarm"
147
147
  end
148
148
 
149
- # Check if snapshot is for a node orchestrator
149
+ # Check if snapshot is for a workflow
150
150
  #
151
- # @return [Boolean] true if node orchestrator snapshot
152
- def node_orchestrator?
153
- type == "node_orchestrator"
151
+ # @return [Boolean] true if workflow snapshot
152
+ def workflow?
153
+ type == "workflow"
154
154
  end
155
155
  end
156
156
  end
@@ -69,16 +69,17 @@ module SwarmSDK
69
69
  # @return [Hash] StateSnapshot hash
70
70
  def reconstruct
71
71
  {
72
- version: "1.0.0",
72
+ version: "2.1.0",
73
73
  type: "swarm",
74
74
  snapshot_at: @events.last&.fetch(:timestamp, Time.now.utc.iso8601),
75
75
  swarm_sdk_version: SwarmSDK::VERSION,
76
- swarm: reconstruct_swarm_metadata,
76
+ metadata: reconstruct_swarm_metadata,
77
77
  agents: reconstruct_all_agents,
78
78
  delegation_instances: reconstruct_all_delegations,
79
79
  scratchpad: reconstruct_scratchpad,
80
80
  read_tracking: reconstruct_read_tracking,
81
81
  memory_read_tracking: reconstruct_memory_read_tracking,
82
+ plugin_states: reconstruct_plugin_states,
82
83
  }
83
84
  end
84
85
 
@@ -363,6 +364,16 @@ module SwarmSDK
363
364
  tracking
364
365
  end
365
366
 
367
+ # Reconstruct plugin states
368
+ #
369
+ # Plugin states cannot be fully reconstructed from events alone as they
370
+ # contain internal plugin data. Returns empty hash for compatibility.
371
+ #
372
+ # @return [Hash] Empty plugin states hash
373
+ def reconstruct_plugin_states
374
+ {}
375
+ end
376
+
366
377
  # Parse timestamp string to Time object
367
378
  #
368
379
  # @param timestamp [String, nil] ISO 8601 timestamp