swarm_memory 2.1.5 → 2.1.6
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/swarm_memory/version.rb +1 -1
- metadata +5 -184
- data/lib/claude_swarm/base_executor.rb +0 -133
- data/lib/claude_swarm/claude_code_executor.rb +0 -349
- data/lib/claude_swarm/claude_mcp_server.rb +0 -78
- data/lib/claude_swarm/cli.rb +0 -697
- data/lib/claude_swarm/commands/ps.rb +0 -215
- data/lib/claude_swarm/commands/show.rb +0 -139
- data/lib/claude_swarm/configuration.rb +0 -373
- data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
- data/lib/claude_swarm/json_handler.rb +0 -91
- data/lib/claude_swarm/mcp_generator.rb +0 -230
- data/lib/claude_swarm/openai/chat_completion.rb +0 -256
- data/lib/claude_swarm/openai/executor.rb +0 -256
- data/lib/claude_swarm/openai/responses.rb +0 -319
- data/lib/claude_swarm/orchestrator.rb +0 -878
- data/lib/claude_swarm/process_tracker.rb +0 -78
- data/lib/claude_swarm/session_cost_calculator.rb +0 -209
- data/lib/claude_swarm/session_path.rb +0 -42
- data/lib/claude_swarm/settings_generator.rb +0 -77
- data/lib/claude_swarm/system_utils.rb +0 -46
- data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
- data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
- data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
- data/lib/claude_swarm/tools/task_tool.rb +0 -63
- data/lib/claude_swarm/version.rb +0 -5
- data/lib/claude_swarm/worktree_manager.rb +0 -475
- data/lib/claude_swarm/yaml_loader.rb +0 -22
- data/lib/claude_swarm.rb +0 -67
- data/lib/swarm_cli/cli.rb +0 -201
- data/lib/swarm_cli/command_registry.rb +0 -61
- data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
- data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
- data/lib/swarm_cli/commands/migrate.rb +0 -55
- data/lib/swarm_cli/commands/run.rb +0 -173
- data/lib/swarm_cli/config_loader.rb +0 -98
- data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
- data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
- data/lib/swarm_cli/interactive_repl.rb +0 -924
- data/lib/swarm_cli/mcp_serve_options.rb +0 -44
- data/lib/swarm_cli/mcp_tools_options.rb +0 -59
- data/lib/swarm_cli/migrate_options.rb +0 -54
- data/lib/swarm_cli/migrator.rb +0 -132
- data/lib/swarm_cli/options.rb +0 -151
- data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
- data/lib/swarm_cli/ui/components/content_block.rb +0 -120
- data/lib/swarm_cli/ui/components/divider.rb +0 -57
- data/lib/swarm_cli/ui/components/panel.rb +0 -62
- data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
- data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
- data/lib/swarm_cli/ui/formatters/number.rb +0 -58
- data/lib/swarm_cli/ui/formatters/text.rb +0 -77
- data/lib/swarm_cli/ui/formatters/time.rb +0 -73
- data/lib/swarm_cli/ui/icons.rb +0 -36
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
- data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
- data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
- data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
- data/lib/swarm_cli/version.rb +0 -5
- data/lib/swarm_cli.rb +0 -46
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
- data/lib/swarm_sdk/agent/builder.rb +0 -552
- data/lib/swarm_sdk/agent/chat.rb +0 -774
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
- data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
- data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
- data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
- data/lib/swarm_sdk/agent/context.rb +0 -116
- data/lib/swarm_sdk/agent/context_manager.rb +0 -315
- data/lib/swarm_sdk/agent/definition.rb +0 -477
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
- data/lib/swarm_sdk/builders/base_builder.rb +0 -409
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
- data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
- data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
- data/lib/swarm_sdk/concerns/validatable.rb +0 -55
- data/lib/swarm_sdk/configuration/parser.rb +0 -353
- data/lib/swarm_sdk/configuration/translator.rb +0 -255
- data/lib/swarm_sdk/configuration.rb +0 -135
- data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
- data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
- data/lib/swarm_sdk/context_compactor.rb +0 -335
- data/lib/swarm_sdk/context_management/builder.rb +0 -128
- data/lib/swarm_sdk/context_management/context.rb +0 -328
- data/lib/swarm_sdk/defaults.rb +0 -196
- data/lib/swarm_sdk/events_to_messages.rb +0 -199
- data/lib/swarm_sdk/hooks/adapter.rb +0 -359
- data/lib/swarm_sdk/hooks/context.rb +0 -197
- data/lib/swarm_sdk/hooks/definition.rb +0 -80
- data/lib/swarm_sdk/hooks/error.rb +0 -29
- data/lib/swarm_sdk/hooks/executor.rb +0 -146
- data/lib/swarm_sdk/hooks/registry.rb +0 -147
- data/lib/swarm_sdk/hooks/result.rb +0 -150
- data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
- data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
- data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
- data/lib/swarm_sdk/log_collector.rb +0 -227
- data/lib/swarm_sdk/log_stream.rb +0 -127
- data/lib/swarm_sdk/markdown_parser.rb +0 -75
- data/lib/swarm_sdk/model_aliases.json +0 -8
- data/lib/swarm_sdk/models.json +0 -1
- data/lib/swarm_sdk/models.rb +0 -120
- data/lib/swarm_sdk/node_context.rb +0 -245
- data/lib/swarm_sdk/observer/builder.rb +0 -81
- data/lib/swarm_sdk/observer/config.rb +0 -45
- data/lib/swarm_sdk/observer/manager.rb +0 -236
- data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
- data/lib/swarm_sdk/permissions/config.rb +0 -239
- data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
- data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
- data/lib/swarm_sdk/permissions/validator.rb +0 -173
- data/lib/swarm_sdk/permissions_builder.rb +0 -122
- data/lib/swarm_sdk/plugin.rb +0 -309
- data/lib/swarm_sdk/plugin_registry.rb +0 -101
- data/lib/swarm_sdk/proc_helpers.rb +0 -53
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
- data/lib/swarm_sdk/restore_result.rb +0 -65
- data/lib/swarm_sdk/result.rb +0 -123
- data/lib/swarm_sdk/snapshot.rb +0 -156
- data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
- data/lib/swarm_sdk/state_restorer.rb +0 -476
- data/lib/swarm_sdk/state_snapshot.rb +0 -334
- data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
- data/lib/swarm_sdk/swarm/builder.rb +0 -249
- data/lib/swarm_sdk/swarm/executor.rb +0 -213
- data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
- data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
- data/lib/swarm_sdk/swarm.rb +0 -717
- data/lib/swarm_sdk/swarm_loader.rb +0 -145
- data/lib/swarm_sdk/swarm_registry.rb +0 -136
- data/lib/swarm_sdk/tools/bash.rb +0 -282
- data/lib/swarm_sdk/tools/clock.rb +0 -44
- data/lib/swarm_sdk/tools/delegate.rb +0 -267
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
- data/lib/swarm_sdk/tools/edit.rb +0 -145
- data/lib/swarm_sdk/tools/glob.rb +0 -166
- data/lib/swarm_sdk/tools/grep.rb +0 -235
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
- data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
- data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
- data/lib/swarm_sdk/tools/read.rb +0 -261
- data/lib/swarm_sdk/tools/registry.rb +0 -205
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
- data/lib/swarm_sdk/tools/think.rb +0 -98
- data/lib/swarm_sdk/tools/todo_write.rb +0 -235
- data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
- data/lib/swarm_sdk/tools/write.rb +0 -112
- data/lib/swarm_sdk/utils.rb +0 -68
- data/lib/swarm_sdk/validation_result.rb +0 -33
- data/lib/swarm_sdk/version.rb +0 -5
- data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
- data/lib/swarm_sdk/workflow/builder.rb +0 -143
- data/lib/swarm_sdk/workflow/executor.rb +0 -497
- data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
- data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
- data/lib/swarm_sdk/workflow.rb +0 -554
- data/lib/swarm_sdk.rb +0 -524
data/lib/swarm_sdk/workflow.rb
DELETED
|
@@ -1,554 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
# Workflow executes a multi-node workflow
|
|
5
|
-
#
|
|
6
|
-
# Each node represents a mini-swarm execution stage. The workflow:
|
|
7
|
-
# - Builds execution order from node dependencies (topological sort)
|
|
8
|
-
# - Creates a separate swarm instance for each node
|
|
9
|
-
# - Passes output from one node as input to dependent nodes
|
|
10
|
-
# - Supports input/output transformers for data flow customization
|
|
11
|
-
#
|
|
12
|
-
# @example
|
|
13
|
-
# workflow = Workflow.new(
|
|
14
|
-
# swarm_name: "Dev Team",
|
|
15
|
-
# agent_definitions: { backend: def1, tester: def2 },
|
|
16
|
-
# nodes: { planning: node1, implementation: node2 },
|
|
17
|
-
# start_node: :planning
|
|
18
|
-
# )
|
|
19
|
-
# result = workflow.execute("Build auth system")
|
|
20
|
-
class Workflow
|
|
21
|
-
attr_reader :swarm_name, :nodes, :start_node, :agent_definitions, :scratchpad
|
|
22
|
-
attr_reader :agents, :delegation_instances, :swarm_id, :parent_swarm_id, :mcp_clients
|
|
23
|
-
attr_reader :execution_order
|
|
24
|
-
attr_writer :swarm_id, :config_for_hooks
|
|
25
|
-
attr_accessor :swarm_registry_config, :original_prompt
|
|
26
|
-
|
|
27
|
-
def initialize(swarm_name:, agent_definitions:, nodes:, start_node:, swarm_id: nil, scratchpad: :enabled, allow_filesystem_tools: nil)
|
|
28
|
-
@swarm_name = swarm_name
|
|
29
|
-
@swarm_id = swarm_id || generate_swarm_id(swarm_name)
|
|
30
|
-
@parent_swarm_id = nil # Workflows don't have parent swarms
|
|
31
|
-
@agent_definitions = agent_definitions
|
|
32
|
-
@nodes = nodes
|
|
33
|
-
@start_node = start_node
|
|
34
|
-
@scratchpad = normalize_scratchpad_mode(scratchpad)
|
|
35
|
-
@allow_filesystem_tools = allow_filesystem_tools
|
|
36
|
-
@swarm_registry_config = [] # External swarms config (if using composable swarms)
|
|
37
|
-
|
|
38
|
-
# Simplified structure (matches Swarm)
|
|
39
|
-
@agents = {} # Cached primary agents from nodes
|
|
40
|
-
@delegation_instances = {} # Cached delegation instances from nodes
|
|
41
|
-
|
|
42
|
-
# MCP clients per agent (for cleanup compatibility)
|
|
43
|
-
@mcp_clients = Hash.new { |h, k| h[k] = [] }
|
|
44
|
-
|
|
45
|
-
# Initialize scratchpad storage based on mode
|
|
46
|
-
case @scratchpad
|
|
47
|
-
when :enabled
|
|
48
|
-
# Enabled mode: single scratchpad shared across all nodes
|
|
49
|
-
@shared_scratchpad_storage = Tools::Stores::ScratchpadStorage.new
|
|
50
|
-
@node_scratchpads = nil
|
|
51
|
-
when :per_node
|
|
52
|
-
# Per-node mode: separate scratchpad per node (lazy initialized)
|
|
53
|
-
@shared_scratchpad_storage = nil
|
|
54
|
-
@node_scratchpads = {}
|
|
55
|
-
when :disabled
|
|
56
|
-
# Disabled: no storage at all
|
|
57
|
-
@shared_scratchpad_storage = nil
|
|
58
|
-
@node_scratchpads = nil
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
validate!
|
|
62
|
-
@execution_order = build_execution_order
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Provide name method for interface compatibility
|
|
66
|
-
def name
|
|
67
|
-
@swarm_name
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Implement Snapshotable interface
|
|
71
|
-
def primary_agents
|
|
72
|
-
@agents
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def delegation_instances_hash
|
|
76
|
-
@delegation_instances
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# No-op for Swarm compatibility (Workflow doesn't track first message)
|
|
80
|
-
def first_message_sent?
|
|
81
|
-
false
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Get scratchpad storage for a specific node
|
|
85
|
-
#
|
|
86
|
-
# Returns the appropriate scratchpad based on mode:
|
|
87
|
-
# - :enabled - returns the shared scratchpad (same for all nodes)
|
|
88
|
-
# - :per_node - returns node-specific scratchpad (lazy initialized)
|
|
89
|
-
# - :disabled - returns nil
|
|
90
|
-
#
|
|
91
|
-
# @param node_name [Symbol] Node name
|
|
92
|
-
# @return [Tools::Stores::ScratchpadStorage, nil] Scratchpad instance or nil if disabled
|
|
93
|
-
def scratchpad_for(node_name)
|
|
94
|
-
case @scratchpad
|
|
95
|
-
when :enabled
|
|
96
|
-
@shared_scratchpad_storage
|
|
97
|
-
when :per_node
|
|
98
|
-
# Lazy initialization per node
|
|
99
|
-
@node_scratchpads[node_name] ||= Tools::Stores::ScratchpadStorage.new
|
|
100
|
-
when :disabled
|
|
101
|
-
nil
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Get all scratchpad storages (for snapshot/restore)
|
|
106
|
-
#
|
|
107
|
-
# @return [Hash] { :shared => scratchpad } or { node_name => scratchpad }
|
|
108
|
-
def all_scratchpads
|
|
109
|
-
case @scratchpad
|
|
110
|
-
when :enabled
|
|
111
|
-
{ shared: @shared_scratchpad_storage }
|
|
112
|
-
when :per_node
|
|
113
|
-
@node_scratchpads.dup
|
|
114
|
-
when :disabled
|
|
115
|
-
{}
|
|
116
|
-
end
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Check if scratchpad is enabled
|
|
120
|
-
#
|
|
121
|
-
# @return [Boolean]
|
|
122
|
-
def scratchpad_enabled?
|
|
123
|
-
@scratchpad != :disabled
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# Check if scratchpad is shared between nodes (enabled mode)
|
|
127
|
-
#
|
|
128
|
-
# @return [Boolean]
|
|
129
|
-
def shared_scratchpad?
|
|
130
|
-
@scratchpad == :enabled
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Check if scratchpad is per-node
|
|
134
|
-
#
|
|
135
|
-
# @return [Boolean]
|
|
136
|
-
def per_node_scratchpad?
|
|
137
|
-
@scratchpad == :per_node
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
# Backward compatibility accessor
|
|
141
|
-
#
|
|
142
|
-
# @return [Tools::Stores::ScratchpadStorage, nil]
|
|
143
|
-
def shared_scratchpad_storage
|
|
144
|
-
if @scratchpad == :per_node
|
|
145
|
-
RubyLLM.logger.warn("Workflow: Accessing shared_scratchpad_storage in per-node mode. Use scratchpad_for(node_name) instead.")
|
|
146
|
-
end
|
|
147
|
-
@shared_scratchpad_storage
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Return the lead agent of the start node for CLI compatibility
|
|
151
|
-
#
|
|
152
|
-
# @return [Symbol] Lead agent of the start node
|
|
153
|
-
def lead_agent
|
|
154
|
-
@nodes[@start_node].lead_agent
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
# Execute the node workflow
|
|
158
|
-
#
|
|
159
|
-
# Executes nodes in topological order, passing output from each node
|
|
160
|
-
# to its dependents. Supports streaming logs if block given.
|
|
161
|
-
#
|
|
162
|
-
# @param prompt [String] Initial prompt for the workflow
|
|
163
|
-
# @param inherit_subscriptions [Boolean] Whether to inherit parent log subscriptions
|
|
164
|
-
# (default: true). Set to false to isolate child workflow from parent's event stream.
|
|
165
|
-
# @yield [Hash] Log entry if block given (for streaming)
|
|
166
|
-
# @return [Result] Final result from last node execution
|
|
167
|
-
def execute(prompt, inherit_subscriptions: true, &block)
|
|
168
|
-
Executor.new(self).run(prompt, inherit_subscriptions: inherit_subscriptions, &block)
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Create snapshot of current workflow state
|
|
172
|
-
#
|
|
173
|
-
# Returns a Snapshot object containing agent conversations, context state,
|
|
174
|
-
# and scratchpad data from all nodes that have been executed. The snapshot
|
|
175
|
-
# captures the state of agents in the agent_instance_cache (both primary and
|
|
176
|
-
# delegation instances), as well as scratchpad storage.
|
|
177
|
-
#
|
|
178
|
-
# Configuration (agent definitions, nodes, transformers) stays in your code
|
|
179
|
-
# and is NOT included in snapshots.
|
|
180
|
-
#
|
|
181
|
-
# Scratchpad behavior depends on scratchpad mode:
|
|
182
|
-
# - :enabled (default): single scratchpad shared across all nodes
|
|
183
|
-
# - :per_node: separate scratchpad per node
|
|
184
|
-
# - :disabled: no scratchpad data
|
|
185
|
-
#
|
|
186
|
-
# @return [Snapshot] Snapshot object with convenient serialization methods
|
|
187
|
-
#
|
|
188
|
-
# @example Save snapshot to JSON file
|
|
189
|
-
# workflow = Workflow.new(...)
|
|
190
|
-
# workflow.execute("Build feature")
|
|
191
|
-
# snapshot = workflow.snapshot
|
|
192
|
-
# snapshot.write_to_file("workflow_session.json")
|
|
193
|
-
def snapshot
|
|
194
|
-
StateSnapshot.new(self).snapshot
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
# Restore workflow state from snapshot
|
|
198
|
-
#
|
|
199
|
-
# Accepts a Snapshot object, hash, or JSON string. Validates compatibility
|
|
200
|
-
# between snapshot and current workflow configuration. Restores agent
|
|
201
|
-
# conversations that exist in the cached agents.
|
|
202
|
-
#
|
|
203
|
-
# The workflow must be created with the SAME configuration (agent definitions,
|
|
204
|
-
# nodes) as when the snapshot was created. Only conversation state is restored.
|
|
205
|
-
#
|
|
206
|
-
# For agents with reset_context: false, restored conversations will be injected
|
|
207
|
-
# during node execution. Agents not in cache yet will be skipped (they haven't
|
|
208
|
-
# been used yet, so there's nothing to restore).
|
|
209
|
-
#
|
|
210
|
-
# @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
|
|
211
|
-
# @return [RestoreResult] Result with warnings about skipped agents
|
|
212
|
-
#
|
|
213
|
-
# @example Restore from Snapshot object
|
|
214
|
-
# workflow = Workflow.new(...) # Same config as snapshot
|
|
215
|
-
# snapshot = Snapshot.from_file("workflow_session.json")
|
|
216
|
-
# result = workflow.restore(snapshot)
|
|
217
|
-
# if result.success?
|
|
218
|
-
# puts "All agents restored"
|
|
219
|
-
# else
|
|
220
|
-
# puts result.summary
|
|
221
|
-
# end
|
|
222
|
-
#
|
|
223
|
-
# Restore workflow state from snapshot
|
|
224
|
-
#
|
|
225
|
-
# By default, uses current system prompts from agent definitions (YAML + SDK defaults + plugin injections).
|
|
226
|
-
# Set preserve_system_prompts: true to use historical prompts from snapshot.
|
|
227
|
-
#
|
|
228
|
-
# @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
|
|
229
|
-
# @param preserve_system_prompts [Boolean] Use historical system prompts instead of current config (default: false)
|
|
230
|
-
# @return [RestoreResult] Result with warnings about partial restores
|
|
231
|
-
def restore(snapshot, preserve_system_prompts: false)
|
|
232
|
-
StateRestorer.new(self, snapshot, preserve_system_prompts: preserve_system_prompts).restore
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Build a swarm instance for a specific node
|
|
236
|
-
#
|
|
237
|
-
# Creates a new Swarm with only the agents specified in the node,
|
|
238
|
-
# configured with the node's delegation topology.
|
|
239
|
-
#
|
|
240
|
-
# For agents with reset_context: false, injects cached instances
|
|
241
|
-
# to preserve conversation history across nodes.
|
|
242
|
-
#
|
|
243
|
-
# Scratchpad behavior depends on mode:
|
|
244
|
-
# - :enabled - all nodes use the same scratchpad instance
|
|
245
|
-
# - :per_node - each node gets its own scratchpad instance
|
|
246
|
-
# - :disabled - no scratchpad
|
|
247
|
-
#
|
|
248
|
-
# @param node [Workflow::NodeBuilder] Node configuration
|
|
249
|
-
# @return [Swarm] Configured swarm instance
|
|
250
|
-
def build_swarm_for_node(node)
|
|
251
|
-
# Build hierarchical swarm_id if parent has one (nil auto-generates)
|
|
252
|
-
node_swarm_id = @swarm_id ? "#{@swarm_id}/node:#{node.name}" : nil
|
|
253
|
-
|
|
254
|
-
swarm = Swarm.new(
|
|
255
|
-
name: "#{@swarm_name}:#{node.name}",
|
|
256
|
-
swarm_id: node_swarm_id,
|
|
257
|
-
parent_swarm_id: @swarm_id,
|
|
258
|
-
scratchpad: scratchpad_for(node.name),
|
|
259
|
-
scratchpad_mode: :enabled, # Mini-swarms always use enabled (scratchpad instance passed in)
|
|
260
|
-
allow_filesystem_tools: @allow_filesystem_tools,
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
# Setup swarm registry if external swarms are registered
|
|
264
|
-
if @swarm_registry_config&.any?
|
|
265
|
-
registry = SwarmRegistry.new(parent_swarm_id: node_swarm_id || swarm.swarm_id)
|
|
266
|
-
@swarm_registry_config.each do |reg|
|
|
267
|
-
registry.register(reg[:name], source: reg[:source], keep_context: reg[:keep_context])
|
|
268
|
-
end
|
|
269
|
-
swarm.swarm_registry = registry
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
# Add each agent specified in this node
|
|
273
|
-
node.agent_configs.each do |config|
|
|
274
|
-
agent_name = config[:agent]
|
|
275
|
-
delegates_to = config[:delegates_to]
|
|
276
|
-
tools_override = config[:tools]
|
|
277
|
-
|
|
278
|
-
# Get global agent definition
|
|
279
|
-
agent_def = @agent_definitions[agent_name]
|
|
280
|
-
|
|
281
|
-
# Clone definition with node-specific overrides
|
|
282
|
-
node_specific_def = clone_agent_for_node(agent_def, delegates_to, tools_override)
|
|
283
|
-
|
|
284
|
-
swarm.add_agent(node_specific_def)
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
# Set lead agent
|
|
288
|
-
swarm.lead = node.lead_agent
|
|
289
|
-
|
|
290
|
-
# Inject cached agent instances for context preservation
|
|
291
|
-
inject_cached_agents(swarm, node)
|
|
292
|
-
|
|
293
|
-
swarm
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# Cache agent instances from a swarm for potential reuse
|
|
297
|
-
#
|
|
298
|
-
# Only caches agents that have reset_context: false in this node.
|
|
299
|
-
# This allows preserving conversation history across nodes.
|
|
300
|
-
#
|
|
301
|
-
# @param swarm [Swarm] Swarm instance that just executed
|
|
302
|
-
# @param node [Workflow::Builder] Node configuration
|
|
303
|
-
# @return [void]
|
|
304
|
-
def cache_agent_instances(swarm, node)
|
|
305
|
-
return unless swarm.agents
|
|
306
|
-
|
|
307
|
-
node.agent_configs.each do |config|
|
|
308
|
-
agent_name = config[:agent]
|
|
309
|
-
reset_context = config[:reset_context]
|
|
310
|
-
|
|
311
|
-
# Only cache if reset_context: false
|
|
312
|
-
next if reset_context
|
|
313
|
-
|
|
314
|
-
# Cache primary agent
|
|
315
|
-
agent_instance = swarm.agents[agent_name]
|
|
316
|
-
@agents[agent_name] = agent_instance if agent_instance
|
|
317
|
-
|
|
318
|
-
# Cache delegation instances atomically (together with primary)
|
|
319
|
-
agent_def = @agent_definitions[agent_name]
|
|
320
|
-
agent_def.delegates_to.each do |delegate_name|
|
|
321
|
-
delegation_key = "#{delegate_name}@#{agent_name}"
|
|
322
|
-
delegation_instance = swarm.delegation_instances[delegation_key]
|
|
323
|
-
|
|
324
|
-
if delegation_instance
|
|
325
|
-
@delegation_instances[delegation_key] = delegation_instance
|
|
326
|
-
end
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
private
|
|
332
|
-
|
|
333
|
-
# Generate a unique execution ID for workflow
|
|
334
|
-
#
|
|
335
|
-
# Creates an execution ID that uniquely identifies a single workflow.execute() call.
|
|
336
|
-
# Format: "exec_workflow_{random_hex}"
|
|
337
|
-
#
|
|
338
|
-
# @return [String] Generated execution ID (e.g., "exec_workflow_a3f2b1c8")
|
|
339
|
-
def generate_swarm_id(name)
|
|
340
|
-
sanitized = name.to_s.gsub(/[^a-z0-9_-]/i, "_").downcase
|
|
341
|
-
"#{sanitized}_#{SecureRandom.hex(4)}"
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
# Validate workflow configuration
|
|
345
|
-
#
|
|
346
|
-
# @return [void]
|
|
347
|
-
# @raise [ConfigurationError] If configuration is invalid
|
|
348
|
-
def validate!
|
|
349
|
-
# Validate start_node exists
|
|
350
|
-
unless @nodes.key?(@start_node)
|
|
351
|
-
raise ConfigurationError,
|
|
352
|
-
"start_node '#{@start_node}' not found. Available nodes: #{@nodes.keys.join(", ")}"
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
# Validate all nodes
|
|
356
|
-
@nodes.each_value(&:validate!)
|
|
357
|
-
|
|
358
|
-
# Validate node dependencies reference existing nodes
|
|
359
|
-
@nodes.each do |node_name, node|
|
|
360
|
-
node.dependencies.each do |dep|
|
|
361
|
-
unless @nodes.key?(dep)
|
|
362
|
-
raise ConfigurationError,
|
|
363
|
-
"Node '#{node_name}' depends on unknown node '#{dep}'"
|
|
364
|
-
end
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
# Validate all agents referenced in nodes exist (skip agent-less nodes)
|
|
369
|
-
@nodes.each do |node_name, node|
|
|
370
|
-
next if node.agent_less? # Skip validation for agent-less nodes
|
|
371
|
-
|
|
372
|
-
node.agent_configs.each do |config|
|
|
373
|
-
agent_name = config[:agent]
|
|
374
|
-
unless @agent_definitions.key?(agent_name)
|
|
375
|
-
raise ConfigurationError,
|
|
376
|
-
"Node '#{node_name}' references undefined agent '#{agent_name}'"
|
|
377
|
-
end
|
|
378
|
-
|
|
379
|
-
# Validate delegation targets exist
|
|
380
|
-
config[:delegates_to].each do |delegate|
|
|
381
|
-
unless @agent_definitions.key?(delegate)
|
|
382
|
-
raise ConfigurationError,
|
|
383
|
-
"Node '#{node_name}' agent '#{agent_name}' delegates to undefined agent '#{delegate}'"
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
end
|
|
388
|
-
end
|
|
389
|
-
|
|
390
|
-
# Clone an agent definition with node-specific overrides
|
|
391
|
-
#
|
|
392
|
-
# Allows overriding delegation and tools per node. This enables:
|
|
393
|
-
# - Different delegation topology per node
|
|
394
|
-
# - Different tool sets per workflow stage
|
|
395
|
-
#
|
|
396
|
-
# @param agent_def [Agent::Definition] Original definition
|
|
397
|
-
# @param delegates_to [Array<Symbol>] New delegation targets
|
|
398
|
-
# @param tools [Array<Symbol>, nil] Tool override (nil = use global agent definition)
|
|
399
|
-
# @return [Agent::Definition] Cloned definition with overrides
|
|
400
|
-
def clone_agent_for_node(agent_def, delegates_to, tools)
|
|
401
|
-
config = agent_def.to_h
|
|
402
|
-
config[:delegates_to] = delegates_to
|
|
403
|
-
config[:tools] = tools if tools # Only override if explicitly set
|
|
404
|
-
Agent::Definition.new(agent_def.name, config)
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
# Build execution order using topological sort (Kahn's algorithm)
|
|
408
|
-
#
|
|
409
|
-
# Processes all nodes in dependency order, starting from start_node.
|
|
410
|
-
# Ensures all nodes are reachable from start_node.
|
|
411
|
-
#
|
|
412
|
-
# @return [Array<Symbol>] Ordered list of node names
|
|
413
|
-
# @raise [CircularDependencyError] If circular dependency detected
|
|
414
|
-
def build_execution_order
|
|
415
|
-
# Build in-degree map and adjacency list
|
|
416
|
-
in_degree = {}
|
|
417
|
-
adjacency = Hash.new { |h, k| h[k] = [] }
|
|
418
|
-
|
|
419
|
-
@nodes.each do |node_name, node|
|
|
420
|
-
in_degree[node_name] = node.dependencies.size
|
|
421
|
-
node.dependencies.each do |dep|
|
|
422
|
-
adjacency[dep] << node_name
|
|
423
|
-
end
|
|
424
|
-
end
|
|
425
|
-
|
|
426
|
-
# Start with nodes that have no dependencies
|
|
427
|
-
queue = in_degree.select { |_, degree| degree == 0 }.keys
|
|
428
|
-
order = []
|
|
429
|
-
|
|
430
|
-
while queue.any?
|
|
431
|
-
# Process nodes with all dependencies satisfied
|
|
432
|
-
node_name = queue.shift
|
|
433
|
-
order << node_name
|
|
434
|
-
|
|
435
|
-
# Reduce in-degree for dependent nodes
|
|
436
|
-
adjacency[node_name].each do |dependent|
|
|
437
|
-
in_degree[dependent] -= 1
|
|
438
|
-
queue << dependent if in_degree[dependent] == 0
|
|
439
|
-
end
|
|
440
|
-
end
|
|
441
|
-
|
|
442
|
-
# Check for circular dependencies
|
|
443
|
-
if order.size < @nodes.size
|
|
444
|
-
unprocessed = @nodes.keys - order
|
|
445
|
-
raise CircularDependencyError,
|
|
446
|
-
"Circular dependency detected. Unprocessed nodes: #{unprocessed.join(", ")}. " \
|
|
447
|
-
"Use goto_node in transformers to create loops instead of circular depends_on."
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
# Verify start_node is in the execution order
|
|
451
|
-
unless order.include?(@start_node)
|
|
452
|
-
raise ConfigurationError,
|
|
453
|
-
"start_node '#{@start_node}' is not reachable in the dependency graph"
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
# Verify start_node is actually first (or rearrange to make it first)
|
|
457
|
-
# This ensures we start from the declared start_node
|
|
458
|
-
start_index = order.index(@start_node)
|
|
459
|
-
if start_index && start_index > 0
|
|
460
|
-
# start_node has dependencies - this violates the assumption
|
|
461
|
-
raise ConfigurationError,
|
|
462
|
-
"start_node '#{@start_node}' has dependencies: #{@nodes[@start_node].dependencies.join(", ")}. " \
|
|
463
|
-
"start_node must have no dependencies."
|
|
464
|
-
end
|
|
465
|
-
|
|
466
|
-
order
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# Inject cached agent instances into a swarm
|
|
470
|
-
#
|
|
471
|
-
# For agents with reset_context: false, reuses cached instances to preserve context.
|
|
472
|
-
# Forces agent initialization first (by accessing .agents), then swaps in cached instances.
|
|
473
|
-
#
|
|
474
|
-
# @param swarm [Swarm] Swarm instance to inject into
|
|
475
|
-
# @param node [Workflow::Builder] Node configuration
|
|
476
|
-
# @return [void]
|
|
477
|
-
def inject_cached_agents(swarm, node)
|
|
478
|
-
# Check if any agents need context preservation
|
|
479
|
-
has_preserved = node.agent_configs.any? do |c|
|
|
480
|
-
!c[:reset_context] && (
|
|
481
|
-
@agents[c[:agent]] ||
|
|
482
|
-
has_cached_delegations_for?(c[:agent])
|
|
483
|
-
)
|
|
484
|
-
end
|
|
485
|
-
return unless has_preserved
|
|
486
|
-
|
|
487
|
-
# Force initialization FIRST
|
|
488
|
-
# Without this, @agents will be replaced by initialize_all, losing our injected instances
|
|
489
|
-
swarm.agent(node.agent_configs.first[:agent]) # Triggers lazy init
|
|
490
|
-
|
|
491
|
-
# Now safely inject cached instances
|
|
492
|
-
agents_hash = swarm.agents
|
|
493
|
-
delegation_hash = swarm.delegation_instances
|
|
494
|
-
|
|
495
|
-
# Inject cached PRIMARY agents
|
|
496
|
-
node.agent_configs.each do |config|
|
|
497
|
-
agent_name = config[:agent]
|
|
498
|
-
next if config[:reset_context]
|
|
499
|
-
|
|
500
|
-
cached_agent = @agents[agent_name]
|
|
501
|
-
next unless cached_agent
|
|
502
|
-
|
|
503
|
-
# Replace freshly initialized agent with cached instance
|
|
504
|
-
agents_hash[agent_name] = cached_agent
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
# Inject cached DELEGATION instances (atomic with primary)
|
|
508
|
-
node.agent_configs.each do |config|
|
|
509
|
-
agent_name = config[:agent]
|
|
510
|
-
next if config[:reset_context]
|
|
511
|
-
|
|
512
|
-
agent_def = @agent_definitions[agent_name]
|
|
513
|
-
|
|
514
|
-
agent_def.delegates_to.each do |delegate_name|
|
|
515
|
-
delegation_key = "#{delegate_name}@#{agent_name}"
|
|
516
|
-
cached_delegation = @delegation_instances[delegation_key]
|
|
517
|
-
next unless cached_delegation
|
|
518
|
-
|
|
519
|
-
# Replace freshly initialized delegation instance
|
|
520
|
-
# Tool references intact - atomic caching preserves object graph
|
|
521
|
-
delegation_hash[delegation_key] = cached_delegation
|
|
522
|
-
end
|
|
523
|
-
end
|
|
524
|
-
end
|
|
525
|
-
|
|
526
|
-
def has_cached_delegations_for?(agent_name)
|
|
527
|
-
agent_def = @agent_definitions[agent_name]
|
|
528
|
-
agent_def.delegates_to.any? do |delegate_name|
|
|
529
|
-
delegation_key = "#{delegate_name}@#{agent_name}"
|
|
530
|
-
@delegation_instances[delegation_key]
|
|
531
|
-
end
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
# Normalize scratchpad mode parameter
|
|
535
|
-
#
|
|
536
|
-
# Accepts symbols: :enabled, :per_node, or :disabled
|
|
537
|
-
#
|
|
538
|
-
# @param value [Symbol, String] Scratchpad mode (strings from YAML converted to symbols)
|
|
539
|
-
# @return [Symbol] Normalized mode (:enabled, :per_node, or :disabled)
|
|
540
|
-
# @raise [ArgumentError] If value is invalid
|
|
541
|
-
def normalize_scratchpad_mode(value)
|
|
542
|
-
# Convert strings from YAML to symbols
|
|
543
|
-
value = value.to_sym if value.is_a?(String)
|
|
544
|
-
|
|
545
|
-
case value
|
|
546
|
-
when :enabled, :per_node, :disabled
|
|
547
|
-
value
|
|
548
|
-
else
|
|
549
|
-
raise ArgumentError,
|
|
550
|
-
"Invalid scratchpad mode: #{value.inspect}. Use :enabled, :per_node, or :disabled"
|
|
551
|
-
end
|
|
552
|
-
end
|
|
553
|
-
end
|
|
554
|
-
end
|