Class: SwarmSDK::Workflow

Inherits:
Object
  • Object
show all
Defined in:
lib/swarm_sdk/workflow.rb,
lib/swarm_sdk/workflow/builder.rb,
lib/swarm_sdk/workflow/executor.rb,
lib/swarm_sdk/workflow/agent_config.rb,
lib/swarm_sdk/workflow/node_builder.rb,
lib/swarm_sdk/workflow/transformer_executor.rb

Overview

Workflow executes a multi-node workflow

Each node represents a mini-swarm execution stage. The workflow:

  • Builds execution order from node dependencies (topological sort)
  • Creates a separate swarm instance for each node
  • Passes output from one node as input to dependent nodes
  • Supports input/output transformers for data flow customization

Examples:

workflow = Workflow.new(
  swarm_name: "Dev Team",
  agent_definitions: { backend: def1, tester: def2 },
  nodes: { planning: node1, implementation: node2 },
  start_node: :planning
)
result = workflow.execute("Build auth system")

Defined Under Namespace

Classes: AgentConfig, Builder, Executor, NodeBuilder, TransformerExecutor

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(swarm_name:, agent_definitions:, nodes:, start_node:, swarm_id: nil, scratchpad: :enabled, allow_filesystem_tools: nil) ⇒ Workflow

Returns a new instance of Workflow.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/swarm_sdk/workflow.rb', line 27

def initialize(swarm_name:, agent_definitions:, nodes:, start_node:, swarm_id: nil, scratchpad: :enabled, allow_filesystem_tools: nil)
  @swarm_name = swarm_name
  @swarm_id = swarm_id || generate_swarm_id(swarm_name)
  @parent_swarm_id = nil # Workflows don't have parent swarms
  @agent_definitions = agent_definitions
  @nodes = nodes
  @start_node = start_node
  @scratchpad = normalize_scratchpad_mode(scratchpad)
  @allow_filesystem_tools = allow_filesystem_tools
  @swarm_registry_config = [] # External swarms config (if using composable swarms)

  # Simplified structure (matches Swarm)
  @agents = {}                    # Cached primary agents from nodes
  @delegation_instances = {}      # Cached delegation instances from nodes

  # MCP clients per agent (for cleanup compatibility)
  @mcp_clients = Hash.new { |h, k| h[k] = [] }

  # Initialize scratchpad storage based on mode
  case @scratchpad
  when :enabled
    # Enabled mode: single scratchpad shared across all nodes
    @shared_scratchpad_storage = Tools::Stores::ScratchpadStorage.new
    @node_scratchpads = nil
  when :per_node
    # Per-node mode: separate scratchpad per node (lazy initialized)
    @shared_scratchpad_storage = nil
    @node_scratchpads = {}
  when :disabled
    # Disabled: no storage at all
    @shared_scratchpad_storage = nil
    @node_scratchpads = nil
  end

  validate!
  @execution_order = build_execution_order
end

Instance Attribute Details

#agent_definitionsObject (readonly)

Returns the value of attribute agent_definitions.



21
22
23
# File 'lib/swarm_sdk/workflow.rb', line 21

def agent_definitions
  @agent_definitions
end

#agentsObject (readonly)

Returns the value of attribute agents.



22
23
24
# File 'lib/swarm_sdk/workflow.rb', line 22

def agents
  @agents
end

#config_for_hooks=(value) ⇒ Object (writeonly)

Sets the attribute config_for_hooks

Parameters:

  • value

    the value to set the attribute config_for_hooks to.



24
25
26
# File 'lib/swarm_sdk/workflow.rb', line 24

def config_for_hooks=(value)
  @config_for_hooks = value
end

#delegation_instancesObject (readonly)

Returns the value of attribute delegation_instances.



22
23
24
# File 'lib/swarm_sdk/workflow.rb', line 22

def delegation_instances
  @delegation_instances
end

#execution_orderObject (readonly)

Returns the value of attribute execution_order.



23
24
25
# File 'lib/swarm_sdk/workflow.rb', line 23

def execution_order
  @execution_order
end

#mcp_clientsObject (readonly)

Returns the value of attribute mcp_clients.



22
23
24
# File 'lib/swarm_sdk/workflow.rb', line 22

def mcp_clients
  @mcp_clients
end

#nodesObject (readonly)

Returns the value of attribute nodes.



21
22
23
# File 'lib/swarm_sdk/workflow.rb', line 21

def nodes
  @nodes
end

#original_promptObject

Returns the value of attribute original_prompt.



25
26
27
# File 'lib/swarm_sdk/workflow.rb', line 25

def original_prompt
  @original_prompt
end

#parent_swarm_idObject (readonly)

Returns the value of attribute parent_swarm_id.



22
23
24
# File 'lib/swarm_sdk/workflow.rb', line 22

def parent_swarm_id
  @parent_swarm_id
end

#scratchpadObject (readonly)

Returns the value of attribute scratchpad.



21
22
23
# File 'lib/swarm_sdk/workflow.rb', line 21

def scratchpad
  @scratchpad
end

#start_nodeObject (readonly)

Returns the value of attribute start_node.



21
22
23
# File 'lib/swarm_sdk/workflow.rb', line 21

def start_node
  @start_node
end

#swarm_idObject

Returns the value of attribute swarm_id.



22
23
24
# File 'lib/swarm_sdk/workflow.rb', line 22

def swarm_id
  @swarm_id
end

#swarm_nameObject (readonly)

Returns the value of attribute swarm_name.



21
22
23
# File 'lib/swarm_sdk/workflow.rb', line 21

def swarm_name
  @swarm_name
end

#swarm_registry_configObject

Returns the value of attribute swarm_registry_config.



25
26
27
# File 'lib/swarm_sdk/workflow.rb', line 25

def swarm_registry_config
  @swarm_registry_config
end

Instance Method Details

#all_scratchpadsHash

Get all scratchpad storages (for snapshot/restore)

Returns:

  • (Hash)

    { :shared => scratchpad } or { node_name => scratchpad }



108
109
110
111
112
113
114
115
116
117
# File 'lib/swarm_sdk/workflow.rb', line 108

def all_scratchpads
  case @scratchpad
  when :enabled
    { shared: @shared_scratchpad_storage }
  when :per_node
    @node_scratchpads.dup
  when :disabled
    {}
  end
end

#build_swarm_for_node(node) ⇒ Swarm

Build a swarm instance for a specific node

Creates a new Swarm with only the agents specified in the node, configured with the node's delegation topology.

For agents with reset_context: false, injects cached instances to preserve conversation history across nodes.

Scratchpad behavior depends on mode:

  • :enabled - all nodes use the same scratchpad instance
  • :per_node - each node gets its own scratchpad instance
  • :disabled - no scratchpad

Parameters:

Returns:

  • (Swarm)

    Configured swarm instance



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/swarm_sdk/workflow.rb', line 250

def build_swarm_for_node(node)
  # Build hierarchical swarm_id if parent has one (nil auto-generates)
  node_swarm_id = @swarm_id ? "#{@swarm_id}/node:#{node.name}" : nil

  swarm = Swarm.new(
    name: "#{@swarm_name}:#{node.name}",
    swarm_id: node_swarm_id,
    parent_swarm_id: @swarm_id,
    scratchpad: scratchpad_for(node.name),
    scratchpad_mode: :enabled, # Mini-swarms always use enabled (scratchpad instance passed in)
    allow_filesystem_tools: @allow_filesystem_tools,
  )

  # Setup swarm registry if external swarms are registered
  if @swarm_registry_config&.any?
    registry = SwarmRegistry.new(parent_swarm_id: node_swarm_id || swarm.swarm_id)
    @swarm_registry_config.each do |reg|
      registry.register(reg[:name], source: reg[:source], keep_context: reg[:keep_context])
    end
    swarm.swarm_registry = registry
  end

  # Add each agent specified in this node
  node.agent_configs.each do |config|
    agent_name = config[:agent]
    delegates_to = config[:delegates_to]
    tools_override = config[:tools]

    # Get global agent definition
    agent_def = @agent_definitions[agent_name]

    # Clone definition with node-specific overrides
    node_specific_def = clone_agent_for_node(agent_def, delegates_to, tools_override)

    swarm.add_agent(node_specific_def)
  end

  # Set lead agent
  swarm.lead = node.lead_agent

  # Inject cached agent instances for context preservation
  inject_cached_agents(swarm, node)

  swarm
end

#cache_agent_instances(swarm, node) ⇒ void

This method returns an undefined value.

Cache agent instances from a swarm for potential reuse

Only caches agents that have reset_context: false in this node. This allows preserving conversation history across nodes.

Parameters:



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/swarm_sdk/workflow.rb', line 304

def cache_agent_instances(swarm, node)
  return unless swarm.agents

  node.agent_configs.each do |config|
    agent_name = config[:agent]
    reset_context = config[:reset_context]

    # Only cache if reset_context: false
    next if reset_context

    # Cache primary agent
    agent_instance = swarm.agents[agent_name]
    @agents[agent_name] = agent_instance if agent_instance

    # Cache delegation instances atomically (together with primary)
    agent_def = @agent_definitions[agent_name]
    agent_def.delegates_to.each do |delegate_name|
      delegation_key = "#{delegate_name}@#{agent_name}"
      delegation_instance = swarm.delegation_instances[delegation_key]

      if delegation_instance
        @delegation_instances[delegation_key] = delegation_instance
      end
    end
  end
end

#delegation_instances_hashObject



75
76
77
# File 'lib/swarm_sdk/workflow.rb', line 75

def delegation_instances_hash
  @delegation_instances
end

#execute(prompt, inherit_subscriptions: true) {|Hash| ... } ⇒ Result

Execute the node workflow

Executes nodes in topological order, passing output from each node to its dependents. Supports streaming logs if block given.

Parameters:

  • prompt (String)

    Initial prompt for the workflow

  • inherit_subscriptions (Boolean) (defaults to: true)

    Whether to inherit parent log subscriptions (default: true). Set to false to isolate child workflow from parent's event stream.

Yields:

  • (Hash)

    Log entry if block given (for streaming)

Returns:

  • (Result)

    Final result from last node execution



167
168
169
# File 'lib/swarm_sdk/workflow.rb', line 167

def execute(prompt, inherit_subscriptions: true, &block)
  Executor.new(self).run(prompt, inherit_subscriptions: inherit_subscriptions, &block)
end

#first_message_sent?Boolean

No-op for Swarm compatibility (Workflow doesn't track first message)

Returns:

  • (Boolean)


80
81
82
# File 'lib/swarm_sdk/workflow.rb', line 80

def first_message_sent?
  false
end

#lead_agentSymbol

Return the lead agent of the start node for CLI compatibility

Returns:

  • (Symbol)

    Lead agent of the start node



153
154
155
# File 'lib/swarm_sdk/workflow.rb', line 153

def lead_agent
  @nodes[@start_node].lead_agent
end

#nameObject

Provide name method for interface compatibility



66
67
68
# File 'lib/swarm_sdk/workflow.rb', line 66

def name
  @swarm_name
end

#per_node_scratchpad?Boolean

Check if scratchpad is per-node

Returns:

  • (Boolean)


136
137
138
# File 'lib/swarm_sdk/workflow.rb', line 136

def per_node_scratchpad?
  @scratchpad == :per_node
end

#primary_agentsObject

Implement Snapshotable interface



71
72
73
# File 'lib/swarm_sdk/workflow.rb', line 71

def primary_agents
  @agents
end

#restore(snapshot, preserve_system_prompts: false) ⇒ RestoreResult

Restore workflow state from snapshot

Accepts a Snapshot object, hash, or JSON string. Validates compatibility between snapshot and current workflow configuration. Restores agent conversations that exist in the cached agents.

The workflow must be created with the SAME configuration (agent definitions, nodes) as when the snapshot was created. Only conversation state is restored.

For agents with reset_context: false, restored conversations will be injected during node execution. Agents not in cache yet will be skipped (they haven't been used yet, so there's nothing to restore).

Restore workflow state from snapshot

By default, uses current system prompts from agent definitions (YAML + SDK defaults + plugin injections). Set preserve_system_prompts: true to use historical prompts from snapshot.

Examples:

Restore from Snapshot object

workflow = Workflow.new(...)  # Same config as snapshot
snapshot = Snapshot.from_file("workflow_session.json")
result = workflow.restore(snapshot)
if result.success?
  puts "All agents restored"
else
  puts result.summary
end

Parameters:

  • snapshot (Snapshot, Hash, String)

    Snapshot object, hash, or JSON string

  • snapshot (Snapshot, Hash, String)

    Snapshot object, hash, or JSON string

  • preserve_system_prompts (Boolean) (defaults to: false)

    Use historical system prompts instead of current config (default: false)

Returns:



231
232
233
# File 'lib/swarm_sdk/workflow.rb', line 231

def restore(snapshot, preserve_system_prompts: false)
  StateRestorer.new(self, snapshot, preserve_system_prompts: preserve_system_prompts).restore
end

#scratchpad_enabled?Boolean

Check if scratchpad is enabled

Returns:

  • (Boolean)


122
123
124
# File 'lib/swarm_sdk/workflow.rb', line 122

def scratchpad_enabled?
  @scratchpad != :disabled
end

#scratchpad_for(node_name) ⇒ Tools::Stores::ScratchpadStorage?

Get scratchpad storage for a specific node

Returns the appropriate scratchpad based on mode:

  • :enabled - returns the shared scratchpad (same for all nodes)
  • :per_node - returns node-specific scratchpad (lazy initialized)
  • :disabled - returns nil

Parameters:

  • node_name (Symbol)

    Node name

Returns:



93
94
95
96
97
98
99
100
101
102
103
# File 'lib/swarm_sdk/workflow.rb', line 93

def scratchpad_for(node_name)
  case @scratchpad
  when :enabled
    @shared_scratchpad_storage
  when :per_node
    # Lazy initialization per node
    @node_scratchpads[node_name] ||= Tools::Stores::ScratchpadStorage.new
  when :disabled
    nil
  end
end

#shared_scratchpad?Boolean

Check if scratchpad is shared between nodes (enabled mode)

Returns:

  • (Boolean)


129
130
131
# File 'lib/swarm_sdk/workflow.rb', line 129

def shared_scratchpad?
  @scratchpad == :enabled
end

#shared_scratchpad_storageTools::Stores::ScratchpadStorage?

Backward compatibility accessor



143
144
145
146
147
148
# File 'lib/swarm_sdk/workflow.rb', line 143

def shared_scratchpad_storage
  if @scratchpad == :per_node
    RubyLLM.logger.warn("Workflow: Accessing shared_scratchpad_storage in per-node mode. Use scratchpad_for(node_name) instead.")
  end
  @shared_scratchpad_storage
end

#snapshotSnapshot

Create snapshot of current workflow state

Returns a Snapshot object containing agent conversations, context state, and scratchpad data from all nodes that have been executed. The snapshot captures the state of agents in the agent_instance_cache (both primary and delegation instances), as well as scratchpad storage.

Configuration (agent definitions, nodes, transformers) stays in your code and is NOT included in snapshots.

Scratchpad behavior depends on scratchpad mode:

  • :enabled (default): single scratchpad shared across all nodes
  • :per_node: separate scratchpad per node
  • :disabled: no scratchpad data

Examples:

Save snapshot to JSON file

workflow = Workflow.new(...)
workflow.execute("Build feature")
snapshot = workflow.snapshot
snapshot.write_to_file("workflow_session.json")

Returns:

  • (Snapshot)

    Snapshot object with convenient serialization methods



193
194
195
# File 'lib/swarm_sdk/workflow.rb', line 193

def snapshot
  StateSnapshot.new(self).snapshot
end