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.
- checksums.yaml +4 -4
- data/lib/claude_swarm/claude_mcp_server.rb +1 -0
- data/lib/claude_swarm/cli.rb +5 -18
- data/lib/claude_swarm/configuration.rb +2 -15
- data/lib/claude_swarm/mcp_generator.rb +1 -0
- data/lib/claude_swarm/openai/chat_completion.rb +4 -12
- data/lib/claude_swarm/openai/executor.rb +3 -1
- data/lib/claude_swarm/openai/responses.rb +13 -32
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/commands/run.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +11 -11
- data/lib/swarm_cli/formatters/human_formatter.rb +70 -0
- data/lib/swarm_cli/interactive_repl.rb +11 -5
- data/lib/swarm_cli/ui/icons.rb +0 -23
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
- data/lib/swarm_memory/integration/sdk_plugin.rb +87 -7
- data/lib/swarm_memory/version.rb +1 -1
- data/lib/swarm_memory.rb +1 -1
- data/lib/swarm_sdk/agent/builder.rb +58 -0
- data/lib/swarm_sdk/agent/chat.rb +527 -1059
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +9 -88
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +111 -44
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +233 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +1 -1
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +12 -12
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +98 -0
- data/lib/swarm_sdk/agent/context.rb +2 -2
- data/lib/swarm_sdk/agent/definition.rb +66 -154
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
- data/lib/swarm_sdk/builders/base_builder.rb +409 -0
- data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
- data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
- data/lib/swarm_sdk/concerns/validatable.rb +55 -0
- data/lib/swarm_sdk/configuration/parser.rb +353 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +65 -543
- data/lib/swarm_sdk/context_compactor/token_counter.rb +3 -3
- data/lib/swarm_sdk/context_compactor.rb +6 -11
- data/lib/swarm_sdk/context_management/builder.rb +128 -0
- data/lib/swarm_sdk/context_management/context.rb +328 -0
- data/lib/swarm_sdk/defaults.rb +196 -0
- data/lib/swarm_sdk/events_to_messages.rb +18 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +2 -1
- data/lib/swarm_sdk/log_collector.rb +179 -29
- data/lib/swarm_sdk/log_stream.rb +29 -0
- data/lib/swarm_sdk/node_context.rb +1 -1
- data/lib/swarm_sdk/observer/builder.rb +81 -0
- data/lib/swarm_sdk/observer/config.rb +45 -0
- data/lib/swarm_sdk/observer/manager.rb +236 -0
- data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
- data/lib/swarm_sdk/plugin.rb +93 -3
- data/lib/swarm_sdk/snapshot.rb +6 -6
- data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
- data/lib/swarm_sdk/state_restorer.rb +136 -151
- data/lib/swarm_sdk/state_snapshot.rb +65 -100
- data/lib/swarm_sdk/swarm/agent_initializer.rb +180 -136
- data/lib/swarm_sdk/swarm/builder.rb +44 -578
- data/lib/swarm_sdk/swarm/executor.rb +213 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +150 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +340 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
- data/lib/swarm_sdk/swarm/tool_configurator.rb +42 -138
- data/lib/swarm_sdk/swarm.rb +137 -679
- data/lib/swarm_sdk/tools/bash.rb +11 -3
- data/lib/swarm_sdk/tools/delegate.rb +61 -43
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +9 -1
- data/lib/swarm_sdk/tools/grep.rb +7 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
- data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
- data/lib/swarm_sdk/tools/read.rb +11 -13
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +8 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/todo_write.rb +7 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +3 -2
- data/lib/swarm_sdk/tools/write.rb +8 -13
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +143 -0
- data/lib/swarm_sdk/workflow/executor.rb +497 -0
- data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +3 -3
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +3 -2
- data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
- data/lib/swarm_sdk.rb +33 -3
- metadata +37 -14
- data/lib/swarm_memory/chat_extension.rb +0 -34
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SwarmSDK
|
|
4
|
-
# Restores swarm conversation state from snapshots
|
|
4
|
+
# Restores swarm/workflow conversation state from snapshots
|
|
5
5
|
#
|
|
6
|
-
# Unified implementation that works for both Swarm and
|
|
6
|
+
# Unified implementation that works for both Swarm and Workflow.
|
|
7
7
|
# Validates compatibility between snapshot and current configuration,
|
|
8
8
|
# restores conversation history, context state, scratchpad contents, and
|
|
9
9
|
# read tracking information.
|
|
10
10
|
#
|
|
11
11
|
# Handles configuration mismatches gracefully by skipping agents that
|
|
12
|
-
# don't exist in the current swarm and returning warnings in RestoreResult.
|
|
12
|
+
# don't exist in the current swarm/workflow and returning warnings in RestoreResult.
|
|
13
13
|
#
|
|
14
14
|
# ## System Prompt Handling
|
|
15
15
|
#
|
|
@@ -32,12 +32,11 @@ module SwarmSDK
|
|
|
32
32
|
class StateRestorer
|
|
33
33
|
# Initialize state restorer
|
|
34
34
|
#
|
|
35
|
-
# @param orchestration [Swarm,
|
|
35
|
+
# @param orchestration [Swarm, Workflow] Swarm or workflow to restore into
|
|
36
36
|
# @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
|
|
37
37
|
# @param preserve_system_prompts [Boolean] If true, use system prompts from snapshot instead of current config (default: false)
|
|
38
38
|
def initialize(orchestration, snapshot, preserve_system_prompts: false)
|
|
39
39
|
@orchestration = orchestration
|
|
40
|
-
@type = orchestration.is_a?(SwarmSDK::NodeOrchestrator) ? :node_orchestrator : :swarm
|
|
41
40
|
@preserve_system_prompts = preserve_system_prompts
|
|
42
41
|
|
|
43
42
|
# Handle different input types
|
|
@@ -74,7 +73,7 @@ module SwarmSDK
|
|
|
74
73
|
restore_delegation_conversations(validation.restorable_delegations)
|
|
75
74
|
restore_scratchpad
|
|
76
75
|
restore_read_tracking
|
|
77
|
-
|
|
76
|
+
restore_plugin_states
|
|
78
77
|
|
|
79
78
|
# Phase 3: Return result with warnings
|
|
80
79
|
SwarmSDK::RestoreResult.new(
|
|
@@ -91,8 +90,8 @@ module SwarmSDK
|
|
|
91
90
|
# @raise [StateError] if version is unsupported
|
|
92
91
|
def validate_version!
|
|
93
92
|
version = @snapshot_data[:version] || @snapshot_data["version"]
|
|
94
|
-
unless version == "1.0
|
|
95
|
-
raise StateError, "Unsupported snapshot version: #{version}"
|
|
93
|
+
unless version == "2.1.0"
|
|
94
|
+
raise StateError, "Unsupported snapshot version: #{version}. Expected: 2.1.0"
|
|
96
95
|
end
|
|
97
96
|
end
|
|
98
97
|
|
|
@@ -100,17 +99,16 @@ module SwarmSDK
|
|
|
100
99
|
#
|
|
101
100
|
# @raise [StateError] if types don't match
|
|
102
101
|
def validate_type_match!
|
|
103
|
-
snapshot_type = (@snapshot_data[:type] || @snapshot_data["type"]).
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
snapshot_type = (@snapshot_data[:type] || @snapshot_data["type"]).to_s.downcase
|
|
103
|
+
actual_type = @orchestration.class.name.split("::").last.downcase
|
|
104
|
+
|
|
105
|
+
unless snapshot_type == actual_type
|
|
106
|
+
raise StateError, "Snapshot type '#{snapshot_type}' doesn't match orchestration type '#{actual_type}'"
|
|
106
107
|
end
|
|
107
108
|
end
|
|
108
109
|
|
|
109
110
|
# Validate compatibility between snapshot and current configuration
|
|
110
111
|
#
|
|
111
|
-
# Checks which agents from the snapshot exist in current configuration
|
|
112
|
-
# and generates warnings for any that don't match.
|
|
113
|
-
#
|
|
114
112
|
# @return [ValidationResult] Validation results
|
|
115
113
|
def validate_compatibility
|
|
116
114
|
warnings = []
|
|
@@ -145,9 +143,7 @@ module SwarmSDK
|
|
|
145
143
|
delegation_instances&.each do |instance_name, _data|
|
|
146
144
|
base_name, delegator_name = instance_name.split("@")
|
|
147
145
|
|
|
148
|
-
# Delegation can be restored if
|
|
149
|
-
# 1. The base agent exists in current configuration (may not be in snapshot as primary agent)
|
|
150
|
-
# 2. The delegator was a restorable primary agent from the snapshot
|
|
146
|
+
# Delegation can be restored if both agents exist in current configuration
|
|
151
147
|
if current_agents.include?(base_name.to_sym) &&
|
|
152
148
|
restorable_agents.include?(delegator_name.to_sym)
|
|
153
149
|
restorable_delegations << instance_name
|
|
@@ -157,7 +153,7 @@ module SwarmSDK
|
|
|
157
153
|
type: :delegation_instance_not_restorable,
|
|
158
154
|
instance: instance_name,
|
|
159
155
|
message: "Delegation instance '#{instance_name}' cannot be restored " \
|
|
160
|
-
"(base agent or delegator not in current swarm).",
|
|
156
|
+
"(base agent or delegator not in current swarm/workflow).",
|
|
161
157
|
}
|
|
162
158
|
end
|
|
163
159
|
end
|
|
@@ -173,116 +169,107 @@ module SwarmSDK
|
|
|
173
169
|
|
|
174
170
|
# Restore orchestration metadata
|
|
175
171
|
#
|
|
176
|
-
# For Swarm: restores first_message_sent flag
|
|
177
|
-
# For NodeOrchestrator: no additional metadata to restore
|
|
178
|
-
#
|
|
179
172
|
# @return [void]
|
|
180
173
|
def restore_metadata
|
|
181
|
-
# Restore
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
174
|
+
# Restore metadata
|
|
175
|
+
metadata = @snapshot_data[:metadata] || @snapshot_data["metadata"]
|
|
176
|
+
return unless metadata
|
|
177
|
+
|
|
178
|
+
# Restore first_message_sent flag (Swarm only, no-op for Workflow)
|
|
179
|
+
if @orchestration.respond_to?(:first_message_sent=)
|
|
180
|
+
first_sent = metadata[:first_message_sent] || metadata["first_message_sent"]
|
|
186
181
|
@orchestration.first_message_sent = first_sent
|
|
187
182
|
end
|
|
188
|
-
# NodeOrchestrator has no additional metadata to restore
|
|
189
183
|
end
|
|
190
184
|
|
|
191
185
|
# Restore agent conversations
|
|
192
186
|
#
|
|
187
|
+
# Uses interface methods - no type checking!
|
|
188
|
+
#
|
|
193
189
|
# @param restorable_agents [Array<Symbol>] Agents that can be restored
|
|
194
190
|
# @return [void]
|
|
195
191
|
def restore_agent_conversations(restorable_agents)
|
|
196
192
|
restorable_agents.each do |agent_name|
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
|
|
193
|
+
# For Swarm: lazy initialization triggers when we call agent()
|
|
194
|
+
# For Workflow: agents are in cache if already used, otherwise skip
|
|
195
|
+
agent_chat = if @orchestration.is_a?(Swarm)
|
|
200
196
|
@orchestration.agent(agent_name)
|
|
201
197
|
else
|
|
202
|
-
#
|
|
203
|
-
|
|
204
|
-
# We need to create agents now so they can be injected later
|
|
205
|
-
cache = @orchestration.agent_instance_cache[:primary]
|
|
206
|
-
unless cache[agent_name]
|
|
207
|
-
# For NodeOrchestrator, we can't easily create agents here
|
|
208
|
-
# because we'd need the full swarm setup (initializer, etc.)
|
|
209
|
-
# Skip this agent if it's not in cache yet
|
|
210
|
-
next
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
cache[agent_name]
|
|
198
|
+
# Workflow: only restore if agent is already in cache
|
|
199
|
+
@orchestration.primary_agents[agent_name]
|
|
214
200
|
end
|
|
215
201
|
|
|
216
|
-
|
|
202
|
+
next unless agent_chat
|
|
203
|
+
|
|
204
|
+
# Get agent snapshot data
|
|
217
205
|
agents_data = @snapshot_data[:agents] || @snapshot_data["agents"]
|
|
218
206
|
snapshot_data = agents_data[agent_name] || agents_data[agent_name.to_s]
|
|
219
|
-
next unless snapshot_data
|
|
220
|
-
|
|
221
|
-
#
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# Determine which system prompt to use
|
|
226
|
-
# By default, use current prompt from YAML config (allows prompt iteration)
|
|
227
|
-
# With preserve_system_prompts: true, use historical prompt from snapshot
|
|
228
|
-
system_prompt = if @preserve_system_prompts
|
|
229
|
-
# Historical: Use prompt that was active when snapshot was created
|
|
230
|
-
snapshot_data[:system_prompt] || snapshot_data["system_prompt"]
|
|
231
|
-
else
|
|
232
|
-
# Current: Use prompt from current agent definition (default)
|
|
233
|
-
agent_definition = @orchestration.agent_definitions[agent_name]
|
|
234
|
-
agent_definition&.system_prompt
|
|
235
|
-
end
|
|
207
|
+
next unless snapshot_data
|
|
208
|
+
|
|
209
|
+
# Restore conversation
|
|
210
|
+
restore_agent_conversation(agent_chat, agent_name, snapshot_data)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
236
213
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
214
|
+
# Restore a single agent's conversation
|
|
215
|
+
#
|
|
216
|
+
# @param agent_chat [Agent::Chat] Chat instance
|
|
217
|
+
# @param agent_name [Symbol] Agent name
|
|
218
|
+
# @param snapshot_data [Hash] Snapshot data for this agent
|
|
219
|
+
# @return [void]
|
|
220
|
+
def restore_agent_conversation(agent_chat, agent_name, snapshot_data)
|
|
221
|
+
# Determine which system prompt to use
|
|
222
|
+
system_prompt = if @preserve_system_prompts
|
|
223
|
+
snapshot_data[:system_prompt] || snapshot_data["system_prompt"]
|
|
224
|
+
else
|
|
225
|
+
agent_definition = @orchestration.agent_definitions[agent_name]
|
|
226
|
+
agent_definition&.system_prompt
|
|
227
|
+
end
|
|
240
228
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
conversation.each do |msg_data|
|
|
244
|
-
message = deserialize_message(msg_data)
|
|
245
|
-
messages << message
|
|
246
|
-
end
|
|
229
|
+
# Build complete message list including system message
|
|
230
|
+
all_messages = []
|
|
247
231
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
232
|
+
# Add system message first if we have a system prompt
|
|
233
|
+
if system_prompt
|
|
234
|
+
all_messages << RubyLLM::Message.new(role: :system, content: system_prompt)
|
|
251
235
|
end
|
|
236
|
+
|
|
237
|
+
# Add conversation messages
|
|
238
|
+
conversation = snapshot_data[:conversation] || snapshot_data["conversation"]
|
|
239
|
+
restored_messages = conversation.map { |msg_data| deserialize_message(msg_data) }
|
|
240
|
+
all_messages.concat(restored_messages)
|
|
241
|
+
|
|
242
|
+
# Replace all messages using proper abstraction
|
|
243
|
+
agent_chat.replace_messages(all_messages)
|
|
244
|
+
|
|
245
|
+
# Restore context state
|
|
246
|
+
context_state = snapshot_data[:context_state] || snapshot_data["context_state"]
|
|
247
|
+
restore_context_state(agent_chat, context_state)
|
|
252
248
|
end
|
|
253
249
|
|
|
254
250
|
# Deserialize a message from snapshot data
|
|
255
251
|
#
|
|
256
|
-
# Handles Content objects and tool calls properly.
|
|
257
|
-
#
|
|
258
252
|
# @param msg_data [Hash] Message data from snapshot
|
|
259
253
|
# @return [RubyLLM::Message] Deserialized message
|
|
260
254
|
def deserialize_message(msg_data)
|
|
261
255
|
# Handle Content objects
|
|
262
256
|
content = if msg_data[:content].is_a?(Hash) && (msg_data[:content].key?(:text) || msg_data[:content].key?("text"))
|
|
263
257
|
content_data = msg_data[:content]
|
|
264
|
-
# Handle both symbol and string keys from JSON
|
|
265
258
|
text = content_data[:text] || content_data["text"]
|
|
266
259
|
attachments = content_data[:attachments] || content_data["attachments"] || []
|
|
267
260
|
|
|
268
|
-
# Recreate Content object
|
|
269
|
-
# NOTE: Attachments are hashes from JSON - RubyLLM::Content constructor handles this
|
|
270
261
|
RubyLLM::Content.new(text, attachments)
|
|
271
262
|
else
|
|
272
|
-
# Plain string content
|
|
273
263
|
msg_data[:content]
|
|
274
264
|
end
|
|
275
265
|
|
|
276
|
-
# Handle tool calls
|
|
277
|
-
# IMPORTANT: RubyLLM expects tool_calls to be Hash<String, ToolCall>, not Array!
|
|
266
|
+
# Handle tool calls
|
|
278
267
|
tool_calls_hash = if msg_data[:tool_calls] && !msg_data[:tool_calls].empty?
|
|
279
268
|
msg_data[:tool_calls].each_with_object({}) do |tc_data, hash|
|
|
280
|
-
# Handle both symbol and string keys from JSON
|
|
281
269
|
id = tc_data[:id] || tc_data["id"]
|
|
282
270
|
name = tc_data[:name] || tc_data["name"]
|
|
283
271
|
arguments = tc_data[:arguments] || tc_data["arguments"] || {}
|
|
284
272
|
|
|
285
|
-
# Use ID as hash key (convert to string for consistency)
|
|
286
273
|
hash[id.to_s] = RubyLLM::ToolCall.new(
|
|
287
274
|
id: id,
|
|
288
275
|
name: name,
|
|
@@ -308,115 +295,109 @@ module SwarmSDK
|
|
|
308
295
|
# @param context_state [Hash] Context state data
|
|
309
296
|
# @return [void]
|
|
310
297
|
def restore_context_state(agent_chat, context_state)
|
|
311
|
-
# Access via public accessors
|
|
312
298
|
context_manager = agent_chat.context_manager
|
|
313
299
|
agent_context = agent_chat.agent_context
|
|
314
300
|
|
|
315
|
-
# Restore warning thresholds
|
|
301
|
+
# Restore warning thresholds
|
|
316
302
|
if context_state[:warning_thresholds_hit] || context_state["warning_thresholds_hit"]
|
|
317
303
|
thresholds_array = context_state[:warning_thresholds_hit] || context_state["warning_thresholds_hit"]
|
|
318
304
|
thresholds_set = agent_context.warning_thresholds_hit
|
|
319
305
|
thresholds_array.each { |t| thresholds_set.add(t) }
|
|
320
306
|
end
|
|
321
307
|
|
|
322
|
-
# Restore compression flag
|
|
308
|
+
# Restore compression flag
|
|
323
309
|
compression = context_state[:compression_applied] || context_state["compression_applied"]
|
|
324
310
|
context_manager.compression_applied = compression
|
|
325
311
|
|
|
326
|
-
# Restore TodoWrite tracking
|
|
312
|
+
# Restore TodoWrite tracking
|
|
327
313
|
todowrite_index = context_state[:last_todowrite_message_index] || context_state["last_todowrite_message_index"]
|
|
328
314
|
agent_chat.last_todowrite_message_index = todowrite_index
|
|
329
315
|
|
|
330
|
-
# Restore active skill path
|
|
316
|
+
# Restore active skill path
|
|
331
317
|
skill_path = context_state[:active_skill_path] || context_state["active_skill_path"]
|
|
332
318
|
agent_chat.active_skill_path = skill_path
|
|
333
319
|
end
|
|
334
320
|
|
|
335
321
|
# Restore delegation instance conversations
|
|
336
322
|
#
|
|
323
|
+
# Uses interface methods - no type checking!
|
|
324
|
+
#
|
|
337
325
|
# @param restorable_delegations [Array<String>] Delegation instances that can be restored
|
|
338
326
|
# @return [void]
|
|
339
327
|
def restore_delegation_conversations(restorable_delegations)
|
|
340
328
|
restorable_delegations.each do |instance_name|
|
|
341
|
-
#
|
|
342
|
-
delegation_chat =
|
|
343
|
-
@orchestration.delegation_instances[instance_name]
|
|
344
|
-
else
|
|
345
|
-
cache = @orchestration.agent_instance_cache[:delegations]
|
|
346
|
-
unless cache[instance_name]
|
|
347
|
-
# Skip if delegation not in cache yet (NodeOrchestrator)
|
|
348
|
-
next
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
cache[instance_name]
|
|
352
|
-
end
|
|
329
|
+
# Use interface method - works for both!
|
|
330
|
+
delegation_chat = @orchestration.delegation_instances_hash[instance_name]
|
|
353
331
|
next unless delegation_chat
|
|
354
332
|
|
|
355
|
-
# Get delegation snapshot data
|
|
333
|
+
# Get delegation snapshot data
|
|
356
334
|
delegations_data = @snapshot_data[:delegation_instances] || @snapshot_data["delegation_instances"]
|
|
357
335
|
snapshot_data = delegations_data[instance_name.to_sym] || delegations_data[instance_name.to_s] || delegations_data[instance_name]
|
|
358
|
-
next unless snapshot_data
|
|
359
|
-
|
|
360
|
-
# Clear existing messages FIRST (before adding system prompt)
|
|
361
|
-
messages = delegation_chat.messages
|
|
362
|
-
messages.clear
|
|
336
|
+
next unless snapshot_data
|
|
363
337
|
|
|
364
|
-
#
|
|
365
|
-
# Extract base agent name from delegation instance (e.g., "bob@jarvis" -> "bob")
|
|
338
|
+
# Extract base agent name
|
|
366
339
|
base_name = instance_name.to_s.split("@").first.to_sym
|
|
367
340
|
|
|
368
|
-
#
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
snapshot_data[:system_prompt] || snapshot_data["system_prompt"]
|
|
373
|
-
else
|
|
374
|
-
# Current: Use prompt from current base agent definition (default)
|
|
375
|
-
agent_definition = @orchestration.agent_definitions[base_name]
|
|
376
|
-
agent_definition&.system_prompt
|
|
377
|
-
end
|
|
341
|
+
# Restore conversation
|
|
342
|
+
restore_delegation_conversation(delegation_chat, base_name, snapshot_data)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
378
345
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
346
|
+
# Restore a single delegation's conversation
|
|
347
|
+
#
|
|
348
|
+
# @param delegation_chat [Agent::Chat] Chat instance
|
|
349
|
+
# @param base_name [Symbol] Base agent name
|
|
350
|
+
# @param snapshot_data [Hash] Snapshot data
|
|
351
|
+
# @return [void]
|
|
352
|
+
def restore_delegation_conversation(delegation_chat, base_name, snapshot_data)
|
|
353
|
+
# Determine which system prompt to use
|
|
354
|
+
system_prompt = if @preserve_system_prompts
|
|
355
|
+
snapshot_data[:system_prompt] || snapshot_data["system_prompt"]
|
|
356
|
+
else
|
|
357
|
+
agent_definition = @orchestration.agent_definitions[base_name]
|
|
358
|
+
agent_definition&.system_prompt
|
|
359
|
+
end
|
|
382
360
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
conversation.each do |msg_data|
|
|
386
|
-
message = deserialize_message(msg_data)
|
|
387
|
-
messages << message
|
|
388
|
-
end
|
|
361
|
+
# Build complete message list including system message
|
|
362
|
+
all_messages = []
|
|
389
363
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
364
|
+
# Add system message first if we have a system prompt
|
|
365
|
+
if system_prompt
|
|
366
|
+
all_messages << RubyLLM::Message.new(role: :system, content: system_prompt)
|
|
393
367
|
end
|
|
368
|
+
|
|
369
|
+
# Restore conversation messages
|
|
370
|
+
conversation = snapshot_data[:conversation] || snapshot_data["conversation"]
|
|
371
|
+
restored_messages = conversation.map { |msg_data| deserialize_message(msg_data) }
|
|
372
|
+
all_messages.concat(restored_messages)
|
|
373
|
+
|
|
374
|
+
# Replace all messages using proper abstraction
|
|
375
|
+
delegation_chat.replace_messages(all_messages)
|
|
376
|
+
|
|
377
|
+
# Restore context state
|
|
378
|
+
context_state = snapshot_data[:context_state] || snapshot_data["context_state"]
|
|
379
|
+
restore_context_state(delegation_chat, context_state)
|
|
394
380
|
end
|
|
395
381
|
|
|
396
382
|
# Restore scratchpad contents
|
|
397
383
|
#
|
|
398
|
-
# For Swarm: uses scratchpad_storage (flat format)
|
|
399
|
-
# For NodeOrchestrator: { shared: bool, data: ... }
|
|
400
|
-
# - shared: true → :enabled mode (shared across nodes)
|
|
401
|
-
# - shared: false → :per_node mode (isolated per node)
|
|
402
|
-
#
|
|
403
384
|
# @return [void]
|
|
404
385
|
def restore_scratchpad
|
|
405
386
|
scratchpad_data = @snapshot_data[:scratchpad] || @snapshot_data["scratchpad"]
|
|
406
387
|
return unless scratchpad_data&.any?
|
|
407
388
|
|
|
408
|
-
if @
|
|
409
|
-
|
|
389
|
+
if @orchestration.is_a?(Workflow)
|
|
390
|
+
restore_workflow_scratchpad(scratchpad_data)
|
|
410
391
|
else
|
|
411
392
|
restore_swarm_scratchpad(scratchpad_data)
|
|
412
393
|
end
|
|
413
394
|
end
|
|
414
395
|
|
|
415
|
-
# Restore scratchpad for
|
|
396
|
+
# Restore scratchpad for Workflow
|
|
416
397
|
#
|
|
417
398
|
# @param scratchpad_data [Hash] { shared: bool, data: ... }
|
|
418
399
|
# @return [void]
|
|
419
|
-
def
|
|
400
|
+
def restore_workflow_scratchpad(scratchpad_data)
|
|
420
401
|
snapshot_shared_mode = scratchpad_data[:shared] || scratchpad_data["shared"]
|
|
421
402
|
data = scratchpad_data[:data] || scratchpad_data["data"]
|
|
422
403
|
|
|
@@ -464,27 +445,31 @@ module SwarmSDK
|
|
|
464
445
|
read_tracking_data = @snapshot_data[:read_tracking] || @snapshot_data["read_tracking"]
|
|
465
446
|
return unless read_tracking_data
|
|
466
447
|
|
|
467
|
-
# Restore tracking for each agent using new API
|
|
468
|
-
# read_tracking_data format: { agent_name => { file_path => digest } }
|
|
469
448
|
read_tracking_data.each do |agent_name, files_with_digests|
|
|
470
449
|
agent_sym = agent_name.to_sym
|
|
471
450
|
Tools::Stores::ReadTracker.restore_read_files(agent_sym, files_with_digests)
|
|
472
451
|
end
|
|
473
452
|
end
|
|
474
453
|
|
|
475
|
-
# Restore
|
|
454
|
+
# Restore plugin-specific state for all plugins
|
|
476
455
|
#
|
|
477
456
|
# @return [void]
|
|
478
|
-
def
|
|
479
|
-
|
|
480
|
-
return unless
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
457
|
+
def restore_plugin_states
|
|
458
|
+
plugin_states_data = @snapshot_data[:plugin_states] || @snapshot_data["plugin_states"]
|
|
459
|
+
return unless plugin_states_data
|
|
460
|
+
|
|
461
|
+
plugin_states_data.each do |plugin_name, agents_state|
|
|
462
|
+
# Find plugin by name
|
|
463
|
+
plugin = PluginRegistry.all.find { |p| p.name.to_s == plugin_name.to_s }
|
|
464
|
+
next unless plugin
|
|
465
|
+
|
|
466
|
+
# Restore state for each agent
|
|
467
|
+
agents_state.each do |agent_name, state|
|
|
468
|
+
agent_sym = agent_name.to_sym
|
|
469
|
+
# Symbolize keys for consistent access
|
|
470
|
+
symbolized_state = state.is_a?(Hash) ? state.transform_keys(&:to_sym) : state
|
|
471
|
+
plugin.restore_agent_state(agent_sym, symbolized_state)
|
|
472
|
+
end
|
|
488
473
|
end
|
|
489
474
|
end
|
|
490
475
|
end
|