swarm_memory 2.1.2 → 2.1.3
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/configuration.rb +28 -4
- data/lib/claude_swarm/mcp_generator.rb +4 -10
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +3 -3
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/adapters/base.rb +4 -4
- data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
- data/lib/swarm_memory/integration/cli_registration.rb +3 -2
- data/lib/swarm_memory/integration/sdk_plugin.rb +11 -5
- data/lib/swarm_memory/tools/memory_edit.rb +2 -2
- data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
- data/lib/swarm_memory/tools/memory_read.rb +3 -3
- data/lib/swarm_memory/version.rb +1 -1
- data/lib/swarm_memory.rb +5 -0
- data/lib/swarm_sdk/agent/builder.rb +33 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +41 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
- data/lib/swarm_sdk/agent/chat.rb +198 -51
- data/lib/swarm_sdk/agent/context.rb +6 -2
- data/lib/swarm_sdk/agent/context_manager.rb +6 -0
- data/lib/swarm_sdk/agent/definition.rb +15 -22
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
- data/lib/swarm_sdk/configuration.rb +420 -103
- data/lib/swarm_sdk/events_to_messages.rb +181 -0
- data/lib/swarm_sdk/log_collector.rb +31 -5
- data/lib/swarm_sdk/log_stream.rb +37 -8
- data/lib/swarm_sdk/model_aliases.json +4 -1
- data/lib/swarm_sdk/node/agent_config.rb +33 -8
- data/lib/swarm_sdk/node/builder.rb +39 -18
- data/lib/swarm_sdk/node_orchestrator.rb +293 -26
- data/lib/swarm_sdk/proc_helpers.rb +53 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
- data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
- data/lib/swarm_sdk/restore_result.rb +65 -0
- data/lib/swarm_sdk/snapshot.rb +156 -0
- data/lib/swarm_sdk/snapshot_from_events.rb +386 -0
- data/lib/swarm_sdk/state_restorer.rb +491 -0
- data/lib/swarm_sdk/state_snapshot.rb +369 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
- data/lib/swarm_sdk/swarm/builder.rb +208 -12
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
- data/lib/swarm_sdk/swarm.rb +367 -90
- data/lib/swarm_sdk/swarm_loader.rb +145 -0
- data/lib/swarm_sdk/swarm_registry.rb +136 -0
- data/lib/swarm_sdk/tools/delegate.rb +92 -7
- data/lib/swarm_sdk/tools/read.rb +17 -5
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +4 -4
- data/lib/swarm_sdk/tools/think.rb +4 -1
- data/lib/swarm_sdk/tools/todo_write.rb +20 -8
- data/lib/swarm_sdk/utils.rb +18 -0
- data/lib/swarm_sdk/validation_result.rb +33 -0
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +362 -21
- metadata +17 -5
|
@@ -18,15 +18,39 @@ module SwarmSDK
|
|
|
18
18
|
# )
|
|
19
19
|
# result = orchestrator.execute("Build auth system")
|
|
20
20
|
class NodeOrchestrator
|
|
21
|
-
attr_reader :swarm_name, :nodes, :start_node
|
|
21
|
+
attr_reader :swarm_name, :nodes, :start_node, :agent_definitions, :agent_instance_cache, :scratchpad
|
|
22
|
+
attr_writer :swarm_id, :config_for_hooks
|
|
23
|
+
attr_accessor :swarm_registry_config
|
|
22
24
|
|
|
23
|
-
def initialize(swarm_name:, agent_definitions:, nodes:, start_node:,
|
|
25
|
+
def initialize(swarm_name:, agent_definitions:, nodes:, start_node:, swarm_id: nil, scratchpad: :enabled, allow_filesystem_tools: nil)
|
|
24
26
|
@swarm_name = swarm_name
|
|
27
|
+
@swarm_id = swarm_id
|
|
25
28
|
@agent_definitions = agent_definitions
|
|
26
29
|
@nodes = nodes
|
|
27
30
|
@start_node = start_node
|
|
28
|
-
@
|
|
29
|
-
@
|
|
31
|
+
@scratchpad = normalize_scratchpad_mode(scratchpad)
|
|
32
|
+
@allow_filesystem_tools = allow_filesystem_tools
|
|
33
|
+
@swarm_registry_config = [] # External swarms config (if using composable swarms)
|
|
34
|
+
@agent_instance_cache = {
|
|
35
|
+
primary: {}, # { agent_name => Agent::Chat }
|
|
36
|
+
delegations: {}, # { "delegate@delegator" => Agent::Chat }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Initialize scratchpad storage based on mode
|
|
40
|
+
case @scratchpad
|
|
41
|
+
when :enabled
|
|
42
|
+
# Enabled mode: single scratchpad shared across all nodes
|
|
43
|
+
@shared_scratchpad_storage = Tools::Stores::ScratchpadStorage.new
|
|
44
|
+
@node_scratchpads = nil
|
|
45
|
+
when :per_node
|
|
46
|
+
# Per-node mode: separate scratchpad per node (lazy initialized)
|
|
47
|
+
@shared_scratchpad_storage = nil
|
|
48
|
+
@node_scratchpads = {}
|
|
49
|
+
when :disabled
|
|
50
|
+
# Disabled: no storage at all
|
|
51
|
+
@shared_scratchpad_storage = nil
|
|
52
|
+
@node_scratchpads = nil
|
|
53
|
+
end
|
|
30
54
|
|
|
31
55
|
validate!
|
|
32
56
|
@execution_order = build_execution_order
|
|
@@ -35,6 +59,72 @@ module SwarmSDK
|
|
|
35
59
|
# Alias for compatibility with Swarm interface
|
|
36
60
|
alias_method :name, :swarm_name
|
|
37
61
|
|
|
62
|
+
# Get scratchpad storage for a specific node
|
|
63
|
+
#
|
|
64
|
+
# Returns the appropriate scratchpad based on mode:
|
|
65
|
+
# - :enabled - returns the shared scratchpad (same for all nodes)
|
|
66
|
+
# - :per_node - returns node-specific scratchpad (lazy initialized)
|
|
67
|
+
# - :disabled - returns nil
|
|
68
|
+
#
|
|
69
|
+
# @param node_name [Symbol] Node name
|
|
70
|
+
# @return [Tools::Stores::ScratchpadStorage, nil] Scratchpad instance or nil if disabled
|
|
71
|
+
def scratchpad_for(node_name)
|
|
72
|
+
case @scratchpad
|
|
73
|
+
when :enabled
|
|
74
|
+
@shared_scratchpad_storage
|
|
75
|
+
when :per_node
|
|
76
|
+
# Lazy initialization per node
|
|
77
|
+
@node_scratchpads[node_name] ||= Tools::Stores::ScratchpadStorage.new
|
|
78
|
+
when :disabled
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get all scratchpad storages (for snapshot/restore)
|
|
84
|
+
#
|
|
85
|
+
# @return [Hash] { :shared => scratchpad } or { node_name => scratchpad }
|
|
86
|
+
def all_scratchpads
|
|
87
|
+
case @scratchpad
|
|
88
|
+
when :enabled
|
|
89
|
+
{ shared: @shared_scratchpad_storage }
|
|
90
|
+
when :per_node
|
|
91
|
+
@node_scratchpads.dup
|
|
92
|
+
when :disabled
|
|
93
|
+
{}
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Check if scratchpad is enabled
|
|
98
|
+
#
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
def scratchpad_enabled?
|
|
101
|
+
@scratchpad != :disabled
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Check if scratchpad is shared between nodes (enabled mode)
|
|
105
|
+
#
|
|
106
|
+
# @return [Boolean]
|
|
107
|
+
def shared_scratchpad?
|
|
108
|
+
@scratchpad == :enabled
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Check if scratchpad is per-node
|
|
112
|
+
#
|
|
113
|
+
# @return [Boolean]
|
|
114
|
+
def per_node_scratchpad?
|
|
115
|
+
@scratchpad == :per_node
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Backward compatibility accessor
|
|
119
|
+
#
|
|
120
|
+
# @return [Tools::Stores::ScratchpadStorage, nil]
|
|
121
|
+
def shared_scratchpad_storage
|
|
122
|
+
if @scratchpad == :per_node
|
|
123
|
+
RubyLLM.logger.warn("NodeOrchestrator: Accessing shared_scratchpad_storage in per-node mode. Use scratchpad_for(node_name) instead.")
|
|
124
|
+
end
|
|
125
|
+
@shared_scratchpad_storage
|
|
126
|
+
end
|
|
127
|
+
|
|
38
128
|
# Return the lead agent of the start node for CLI compatibility
|
|
39
129
|
#
|
|
40
130
|
# @return [Symbol] Lead agent of the start node
|
|
@@ -56,6 +146,9 @@ module SwarmSDK
|
|
|
56
146
|
results = {}
|
|
57
147
|
@original_prompt = prompt # Store original prompt for NodeContext
|
|
58
148
|
|
|
149
|
+
# Set fiber-local execution context for entire workflow
|
|
150
|
+
Fiber[:execution_id] = generate_execution_id
|
|
151
|
+
|
|
59
152
|
# Setup logging if block given
|
|
60
153
|
if block_given?
|
|
61
154
|
# Register callback to collect logs and forward to user's block
|
|
@@ -77,6 +170,12 @@ module SwarmSDK
|
|
|
77
170
|
node = @nodes[node_name]
|
|
78
171
|
node_start_time = Time.now
|
|
79
172
|
|
|
173
|
+
# Set node-specific swarm_id in fiber storage
|
|
174
|
+
# Mini-swarms will use ||= to inherit execution_id
|
|
175
|
+
node_swarm_id = @swarm_id ? "#{@swarm_id}/node:#{node_name}" : nil
|
|
176
|
+
Fiber[:swarm_id] = node_swarm_id
|
|
177
|
+
Fiber[:parent_swarm_id] = @swarm_id
|
|
178
|
+
|
|
80
179
|
# Emit node_start event
|
|
81
180
|
emit_node_start(node_name, node)
|
|
82
181
|
|
|
@@ -226,13 +325,92 @@ module SwarmSDK
|
|
|
226
325
|
|
|
227
326
|
last_result
|
|
228
327
|
ensure
|
|
328
|
+
# NodeOrchestrator always clears (always sets up logging)
|
|
329
|
+
Fiber[:execution_id] = nil
|
|
330
|
+
Fiber[:swarm_id] = nil
|
|
331
|
+
Fiber[:parent_swarm_id] = nil
|
|
332
|
+
|
|
229
333
|
# Reset logging state for next execution
|
|
230
334
|
LogCollector.reset!
|
|
231
335
|
LogStream.reset!
|
|
232
336
|
end
|
|
233
337
|
|
|
338
|
+
# Create snapshot of current workflow state
|
|
339
|
+
#
|
|
340
|
+
# Returns a Snapshot object containing agent conversations, context state,
|
|
341
|
+
# and scratchpad data from all nodes that have been executed. The snapshot
|
|
342
|
+
# captures the state of agents in the agent_instance_cache (both primary and
|
|
343
|
+
# delegation instances), as well as scratchpad storage.
|
|
344
|
+
#
|
|
345
|
+
# Configuration (agent definitions, nodes, transformers) stays in your code
|
|
346
|
+
# and is NOT included in snapshots.
|
|
347
|
+
#
|
|
348
|
+
# Scratchpad behavior depends on scratchpad mode:
|
|
349
|
+
# - :enabled (default): single scratchpad shared across all nodes
|
|
350
|
+
# - :per_node: separate scratchpad per node
|
|
351
|
+
# - :disabled: no scratchpad data
|
|
352
|
+
#
|
|
353
|
+
# @return [Snapshot] Snapshot object with convenient serialization methods
|
|
354
|
+
#
|
|
355
|
+
# @example Save snapshot to JSON file
|
|
356
|
+
# orchestrator = NodeOrchestrator.new(...)
|
|
357
|
+
# orchestrator.execute("Build feature")
|
|
358
|
+
# snapshot = orchestrator.snapshot
|
|
359
|
+
# snapshot.write_to_file("workflow_session.json")
|
|
360
|
+
def snapshot
|
|
361
|
+
StateSnapshot.new(self).snapshot
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Restore workflow state from snapshot
|
|
365
|
+
#
|
|
366
|
+
# Accepts a Snapshot object, hash, or JSON string. Validates compatibility
|
|
367
|
+
# between snapshot and current orchestrator configuration. Restores agent
|
|
368
|
+
# conversations that exist in the agent_instance_cache.
|
|
369
|
+
#
|
|
370
|
+
# The orchestrator must be created with the SAME configuration (agent definitions,
|
|
371
|
+
# nodes) as when the snapshot was created. Only conversation state is restored.
|
|
372
|
+
#
|
|
373
|
+
# For agents with reset_context: false, restored conversations will be injected
|
|
374
|
+
# during node execution. Agents not in cache yet will be skipped (they haven't
|
|
375
|
+
# been used yet, so there's nothing to restore).
|
|
376
|
+
#
|
|
377
|
+
# @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
|
|
378
|
+
# @return [RestoreResult] Result with warnings about skipped agents
|
|
379
|
+
#
|
|
380
|
+
# @example Restore from Snapshot object
|
|
381
|
+
# orchestrator = NodeOrchestrator.new(...) # Same config as snapshot
|
|
382
|
+
# snapshot = Snapshot.from_file("workflow_session.json")
|
|
383
|
+
# result = orchestrator.restore(snapshot)
|
|
384
|
+
# if result.success?
|
|
385
|
+
# puts "All agents restored"
|
|
386
|
+
# else
|
|
387
|
+
# puts result.summary
|
|
388
|
+
# end
|
|
389
|
+
#
|
|
390
|
+
# Restore orchestrator state from snapshot
|
|
391
|
+
#
|
|
392
|
+
# By default, uses current system prompts from agent definitions (YAML + SDK defaults + plugin injections).
|
|
393
|
+
# Set preserve_system_prompts: true to use historical prompts from snapshot.
|
|
394
|
+
#
|
|
395
|
+
# @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
|
|
396
|
+
# @param preserve_system_prompts [Boolean] Use historical system prompts instead of current config (default: false)
|
|
397
|
+
# @return [RestoreResult] Result with warnings about partial restores
|
|
398
|
+
def restore(snapshot, preserve_system_prompts: false)
|
|
399
|
+
StateRestorer.new(self, snapshot, preserve_system_prompts: preserve_system_prompts).restore
|
|
400
|
+
end
|
|
401
|
+
|
|
234
402
|
private
|
|
235
403
|
|
|
404
|
+
# Generate a unique execution ID for workflow
|
|
405
|
+
#
|
|
406
|
+
# Creates an execution ID that uniquely identifies a single orchestrator.execute() call.
|
|
407
|
+
# Format: "exec_workflow_{random_hex}"
|
|
408
|
+
#
|
|
409
|
+
# @return [String] Generated execution ID (e.g., "exec_workflow_a3f2b1c8")
|
|
410
|
+
def generate_execution_id
|
|
411
|
+
"exec_workflow_#{SecureRandom.hex(8)}"
|
|
412
|
+
end
|
|
413
|
+
|
|
236
414
|
# Emit node_start event
|
|
237
415
|
#
|
|
238
416
|
# @param node_name [Symbol] Name of the node
|
|
@@ -346,26 +524,46 @@ module SwarmSDK
|
|
|
346
524
|
# For agents with reset_context: false, injects cached instances
|
|
347
525
|
# to preserve conversation history across nodes.
|
|
348
526
|
#
|
|
349
|
-
#
|
|
527
|
+
# Scratchpad behavior depends on mode:
|
|
528
|
+
# - :enabled - all nodes use the same scratchpad instance
|
|
529
|
+
# - :per_node - each node gets its own scratchpad instance
|
|
530
|
+
# - :disabled - no scratchpad
|
|
350
531
|
#
|
|
351
532
|
# @param node [Node::Builder] Node configuration
|
|
352
533
|
# @return [Swarm] Configured swarm instance
|
|
353
534
|
def build_swarm_for_node(node)
|
|
535
|
+
# Build hierarchical swarm_id if parent has one (nil auto-generates)
|
|
536
|
+
node_swarm_id = @swarm_id ? "#{@swarm_id}/node:#{node.name}" : nil
|
|
537
|
+
|
|
354
538
|
swarm = Swarm.new(
|
|
355
539
|
name: "#{@swarm_name}:#{node.name}",
|
|
356
|
-
|
|
540
|
+
swarm_id: node_swarm_id,
|
|
541
|
+
parent_swarm_id: @swarm_id,
|
|
542
|
+
scratchpad: scratchpad_for(node.name),
|
|
543
|
+
scratchpad_mode: :enabled, # Mini-swarms always use enabled (scratchpad instance passed in)
|
|
544
|
+
allow_filesystem_tools: @allow_filesystem_tools,
|
|
357
545
|
)
|
|
358
546
|
|
|
547
|
+
# Setup swarm registry if external swarms are registered
|
|
548
|
+
if @swarm_registry_config&.any?
|
|
549
|
+
registry = SwarmRegistry.new(parent_swarm_id: node_swarm_id || swarm.swarm_id)
|
|
550
|
+
@swarm_registry_config.each do |reg|
|
|
551
|
+
registry.register(reg[:name], source: reg[:source], keep_context: reg[:keep_context])
|
|
552
|
+
end
|
|
553
|
+
swarm.swarm_registry = registry
|
|
554
|
+
end
|
|
555
|
+
|
|
359
556
|
# Add each agent specified in this node
|
|
360
557
|
node.agent_configs.each do |config|
|
|
361
558
|
agent_name = config[:agent]
|
|
362
559
|
delegates_to = config[:delegates_to]
|
|
560
|
+
tools_override = config[:tools]
|
|
363
561
|
|
|
364
562
|
# Get global agent definition
|
|
365
563
|
agent_def = @agent_definitions[agent_name]
|
|
366
564
|
|
|
367
|
-
# Clone definition with node-specific
|
|
368
|
-
node_specific_def =
|
|
565
|
+
# Clone definition with node-specific overrides
|
|
566
|
+
node_specific_def = clone_agent_for_node(agent_def, delegates_to, tools_override)
|
|
369
567
|
|
|
370
568
|
swarm.add_agent(node_specific_def)
|
|
371
569
|
end
|
|
@@ -379,14 +577,20 @@ module SwarmSDK
|
|
|
379
577
|
swarm
|
|
380
578
|
end
|
|
381
579
|
|
|
382
|
-
# Clone an agent definition with
|
|
580
|
+
# Clone an agent definition with node-specific overrides
|
|
581
|
+
#
|
|
582
|
+
# Allows overriding delegation and tools per node. This enables:
|
|
583
|
+
# - Different delegation topology per node
|
|
584
|
+
# - Different tool sets per workflow stage
|
|
383
585
|
#
|
|
384
586
|
# @param agent_def [Agent::Definition] Original definition
|
|
385
587
|
# @param delegates_to [Array<Symbol>] New delegation targets
|
|
386
|
-
# @
|
|
387
|
-
|
|
588
|
+
# @param tools [Array<Symbol>, nil] Tool override (nil = use global agent definition)
|
|
589
|
+
# @return [Agent::Definition] Cloned definition with overrides
|
|
590
|
+
def clone_agent_for_node(agent_def, delegates_to, tools)
|
|
388
591
|
config = agent_def.to_h
|
|
389
592
|
config[:delegates_to] = delegates_to
|
|
593
|
+
config[:tools] = tools if tools # Only override if explicitly set
|
|
390
594
|
Agent::Definition.new(agent_def.name, config)
|
|
391
595
|
end
|
|
392
596
|
|
|
@@ -540,18 +744,29 @@ module SwarmSDK
|
|
|
540
744
|
# @param node [Node::Builder] Node configuration
|
|
541
745
|
# @return [void]
|
|
542
746
|
def cache_agent_instances(swarm, node)
|
|
543
|
-
return unless swarm.agents
|
|
747
|
+
return unless swarm.agents
|
|
544
748
|
|
|
545
749
|
node.agent_configs.each do |config|
|
|
546
750
|
agent_name = config[:agent]
|
|
547
751
|
reset_context = config[:reset_context]
|
|
548
752
|
|
|
549
|
-
# Only cache if reset_context
|
|
753
|
+
# Only cache if reset_context: false
|
|
550
754
|
next if reset_context
|
|
551
755
|
|
|
552
|
-
# Cache
|
|
756
|
+
# Cache primary agent
|
|
553
757
|
agent_instance = swarm.agents[agent_name]
|
|
554
|
-
@agent_instance_cache[agent_name] = agent_instance if agent_instance
|
|
758
|
+
@agent_instance_cache[:primary][agent_name] = agent_instance if agent_instance
|
|
759
|
+
|
|
760
|
+
# V7.0: Cache delegation instances atomically (together with primary)
|
|
761
|
+
agent_def = @agent_definitions[agent_name]
|
|
762
|
+
agent_def.delegates_to.each do |delegate_name|
|
|
763
|
+
delegation_key = "#{delegate_name}@#{agent_name}"
|
|
764
|
+
delegation_instance = swarm.delegation_instances[delegation_key]
|
|
765
|
+
|
|
766
|
+
if delegation_instance
|
|
767
|
+
@agent_instance_cache[:delegations][delegation_key] = delegation_instance
|
|
768
|
+
end
|
|
769
|
+
end
|
|
555
770
|
end
|
|
556
771
|
end
|
|
557
772
|
|
|
@@ -565,27 +780,79 @@ module SwarmSDK
|
|
|
565
780
|
# @return [void]
|
|
566
781
|
def inject_cached_agents(swarm, node)
|
|
567
782
|
# Check if any agents need context preservation
|
|
568
|
-
|
|
569
|
-
|
|
783
|
+
has_preserved = node.agent_configs.any? do |c|
|
|
784
|
+
!c[:reset_context] && (
|
|
785
|
+
@agent_instance_cache[:primary][c[:agent]] ||
|
|
786
|
+
has_cached_delegations_for?(c[:agent])
|
|
787
|
+
)
|
|
788
|
+
end
|
|
789
|
+
return unless has_preserved
|
|
570
790
|
|
|
571
|
-
#
|
|
572
|
-
#
|
|
791
|
+
# V7.0 CRITICAL FIX: Force initialization FIRST
|
|
792
|
+
# Without this, @agents will be replaced by initialize_all, losing our injected instances
|
|
793
|
+
swarm.agent(node.agent_configs.first[:agent]) # Triggers lazy init
|
|
794
|
+
|
|
795
|
+
# Now safely inject cached instances
|
|
573
796
|
agents_hash = swarm.agents
|
|
797
|
+
delegation_hash = swarm.delegation_instances
|
|
574
798
|
|
|
799
|
+
# Inject cached PRIMARY agents
|
|
575
800
|
node.agent_configs.each do |config|
|
|
576
801
|
agent_name = config[:agent]
|
|
577
|
-
|
|
802
|
+
next if config[:reset_context]
|
|
578
803
|
|
|
579
|
-
|
|
580
|
-
next if reset_context
|
|
581
|
-
|
|
582
|
-
# Check if we have a cached instance
|
|
583
|
-
cached_agent = @agent_instance_cache[agent_name]
|
|
804
|
+
cached_agent = @agent_instance_cache[:primary][agent_name]
|
|
584
805
|
next unless cached_agent
|
|
585
806
|
|
|
586
|
-
#
|
|
807
|
+
# Replace freshly initialized agent with cached instance
|
|
587
808
|
agents_hash[agent_name] = cached_agent
|
|
588
809
|
end
|
|
810
|
+
|
|
811
|
+
# Inject cached DELEGATION instances (atomic with primary)
|
|
812
|
+
node.agent_configs.each do |config|
|
|
813
|
+
agent_name = config[:agent]
|
|
814
|
+
next if config[:reset_context]
|
|
815
|
+
|
|
816
|
+
agent_def = @agent_definitions[agent_name]
|
|
817
|
+
|
|
818
|
+
agent_def.delegates_to.each do |delegate_name|
|
|
819
|
+
delegation_key = "#{delegate_name}@#{agent_name}"
|
|
820
|
+
cached_delegation = @agent_instance_cache[:delegations][delegation_key]
|
|
821
|
+
next unless cached_delegation
|
|
822
|
+
|
|
823
|
+
# Replace freshly initialized delegation instance
|
|
824
|
+
# V7.0: Tool references intact - atomic caching preserves object graph
|
|
825
|
+
delegation_hash[delegation_key] = cached_delegation
|
|
826
|
+
end
|
|
827
|
+
end
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
def has_cached_delegations_for?(agent_name)
|
|
831
|
+
agent_def = @agent_definitions[agent_name]
|
|
832
|
+
agent_def.delegates_to.any? do |delegate_name|
|
|
833
|
+
delegation_key = "#{delegate_name}@#{agent_name}"
|
|
834
|
+
@agent_instance_cache[:delegations][delegation_key]
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
# Normalize scratchpad mode parameter
|
|
839
|
+
#
|
|
840
|
+
# Accepts symbols: :enabled, :per_node, or :disabled
|
|
841
|
+
#
|
|
842
|
+
# @param value [Symbol, String] Scratchpad mode (strings from YAML converted to symbols)
|
|
843
|
+
# @return [Symbol] Normalized mode (:enabled, :per_node, or :disabled)
|
|
844
|
+
# @raise [ArgumentError] If value is invalid
|
|
845
|
+
def normalize_scratchpad_mode(value)
|
|
846
|
+
# Convert strings from YAML to symbols
|
|
847
|
+
value = value.to_sym if value.is_a?(String)
|
|
848
|
+
|
|
849
|
+
case value
|
|
850
|
+
when :enabled, :per_node, :disabled
|
|
851
|
+
value
|
|
852
|
+
else
|
|
853
|
+
raise ArgumentError,
|
|
854
|
+
"Invalid scratchpad mode: #{value.inspect}. Use :enabled, :per_node, or :disabled"
|
|
855
|
+
end
|
|
589
856
|
end
|
|
590
857
|
end
|
|
591
858
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
# Helper methods for working with Procs and Lambdas
|
|
5
|
+
#
|
|
6
|
+
# Provides functionality to convert regular Proc objects into Lambdas to enable
|
|
7
|
+
# safe use of return statements in DSL blocks (like input/output transformers).
|
|
8
|
+
module ProcHelpers
|
|
9
|
+
class << self
|
|
10
|
+
# Convert a Proc to a Lambda
|
|
11
|
+
#
|
|
12
|
+
# The fundamental difference between a Proc and a Lambda is in how they handle
|
|
13
|
+
# return statements. In a Proc, return exits the enclosing method (or program),
|
|
14
|
+
# while in a Lambda, return only exits the lambda itself.
|
|
15
|
+
#
|
|
16
|
+
# This method converts a Proc to a Lambda by:
|
|
17
|
+
# 1. Converting the proc to an unbound method via define_method
|
|
18
|
+
# 2. Wrapping it in a lambda that binds and calls the method
|
|
19
|
+
# 3. In the method, return exits the method (not the original scope)
|
|
20
|
+
#
|
|
21
|
+
# This allows users to write natural control flow with return statements:
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# my_proc = proc { |x| return x * 2 if x > 0; 0 }
|
|
25
|
+
# my_lambda = ProcHelpers.to_lambda(my_proc)
|
|
26
|
+
# my_lambda.call(5) # => 10 (return works safely!)
|
|
27
|
+
#
|
|
28
|
+
# @param proc [Proc] The proc to convert
|
|
29
|
+
# @return [Proc] A lambda with the same behavior but safe return semantics
|
|
30
|
+
def to_lambda(proc)
|
|
31
|
+
return proc if proc.lambda?
|
|
32
|
+
|
|
33
|
+
# Save local reference to proc so we can use it in module_exec/lambda scopes
|
|
34
|
+
source_proc = proc
|
|
35
|
+
|
|
36
|
+
# Convert proc to unbound method
|
|
37
|
+
# define_method with a block converts the block to a method, where return
|
|
38
|
+
# exits the method (not the original scope)
|
|
39
|
+
unbound_method = Module.new.module_exec do
|
|
40
|
+
instance_method(define_method(:_proc_call, &source_proc))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Return lambda which binds our unbound method to correct receiver and calls it
|
|
44
|
+
lambda do |*args, **kwargs, &block|
|
|
45
|
+
# Bind method to the original proc's receiver (the context where it was defined)
|
|
46
|
+
# This preserves access to instance variables, local variables via closure, etc.
|
|
47
|
+
receiver = source_proc.binding.eval("self")
|
|
48
|
+
unbound_method.bind(receiver).call(*args, **kwargs, &block)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -69,139 +69,15 @@ When making changes to files, first understand the file's conventions. Mimic exi
|
|
|
69
69
|
- When you edit something, first look at the surrounding context (especially imports/requires) to understand the choice of frameworks and libraries. Then consider how to make the given change in a way that is most consistent with existing patterns.
|
|
70
70
|
- Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to repositories.
|
|
71
71
|
|
|
72
|
-
# Task Management
|
|
73
|
-
|
|
74
|
-
You have access to the TodoWrite tool to help you manage and plan tasks. Use this tool VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
|
|
75
|
-
This tool is also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
|
|
76
|
-
|
|
77
|
-
**CRITICAL WORKFLOW**: When starting a multi-step task:
|
|
78
|
-
1. **FIRST**: Analyze what needs to be done (search, read files, understand scope)
|
|
79
|
-
2. **SECOND**: Create a COMPLETE todo list with ALL known tasks before starting work
|
|
80
|
-
3. **THIRD**: Begin executing tasks, marking them in_progress → completed as you work
|
|
81
|
-
4. **ONLY add new todos** if you discover unexpected work during implementation
|
|
82
|
-
|
|
83
|
-
**CRITICAL RULES FOR TODO COMPLETION**:
|
|
84
|
-
- Mark EACH task as completed IMMEDIATELY after finishing it (do not batch updates)
|
|
85
|
-
- You MUST complete ALL pending todos before giving your final answer to the user
|
|
86
|
-
- If a task becomes irrelevant, remove it from the list or mark it completed with a note
|
|
87
|
-
- NEVER leave in_progress or pending tasks when you finish responding to the user
|
|
88
|
-
- Before giving your final response, verify all todos are marked completed
|
|
89
|
-
|
|
90
|
-
Examples:
|
|
91
|
-
|
|
92
|
-
<example>
|
|
93
|
-
user: Run the build and fix any type errors
|
|
94
|
-
assistant: I'll run the build first to identify all type errors, then create a complete todo list.
|
|
95
|
-
|
|
96
|
-
[Runs build and finds 3 type errors in 3 different files]
|
|
97
|
-
|
|
98
|
-
Now I'll create a complete todo list with all the work:
|
|
99
|
-
|
|
100
|
-
[Uses TodoWrite to create full list:]
|
|
101
|
-
1. Fix type error in auth.ts:45 (in_progress)
|
|
102
|
-
2. Fix type error in user.ts:23 (pending)
|
|
103
|
-
3. Fix type error in api.ts:67 (pending)
|
|
104
|
-
4. Run build again to verify all fixes (pending)
|
|
105
|
-
|
|
106
|
-
Starting with the first error in auth.ts...
|
|
107
|
-
[Fixes auth.ts error]
|
|
108
|
-
|
|
109
|
-
[Updates TodoWrite - marks task 1 completed, task 2 in_progress]
|
|
110
|
-
|
|
111
|
-
Now fixing user.ts...
|
|
112
|
-
[Fixes user.ts error]
|
|
113
|
-
|
|
114
|
-
[Updates TodoWrite - marks task 2 completed, task 3 in_progress]
|
|
115
|
-
|
|
116
|
-
Now fixing api.ts...
|
|
117
|
-
[Fixes api.ts error]
|
|
118
|
-
|
|
119
|
-
[Updates TodoWrite - marks task 3 completed, task 4 in_progress]
|
|
120
|
-
|
|
121
|
-
Running build to verify...
|
|
122
|
-
[Runs build - all errors fixed!]
|
|
123
|
-
|
|
124
|
-
[Updates TodoWrite - marks task 4 completed]
|
|
125
|
-
|
|
126
|
-
All type errors have been fixed and the build passes successfully.
|
|
127
|
-
</example>
|
|
128
|
-
In this example, the assistant: (1) analyzed scope first, (2) created COMPLETE todo list, (3) marked EACH task completed immediately after finishing it, (4) completed ALL tasks before giving final response.
|
|
129
|
-
|
|
130
|
-
<example>
|
|
131
|
-
user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
|
|
132
|
-
|
|
133
|
-
A: I'll help you implement a usage metrics tracking and export feature. Let me first research the codebase to understand the scope, then create a complete todo list.
|
|
134
|
-
|
|
135
|
-
[Searches for existing metrics/telemetry code, examines architecture, identifies integration points]
|
|
136
|
-
|
|
137
|
-
Based on my analysis, here's the complete implementation plan:
|
|
138
|
-
|
|
139
|
-
[Uses TodoWrite to create full list:]
|
|
140
|
-
1. Create MetricsCollector class in src/metrics/ (in_progress)
|
|
141
|
-
2. Add database schema for metrics storage (pending)
|
|
142
|
-
3. Implement CSV export format (pending)
|
|
143
|
-
4. Implement JSON export format (pending)
|
|
144
|
-
5. Create MetricsService API endpoints (pending)
|
|
145
|
-
6. Add frontend UI for export (pending)
|
|
146
|
-
7. Write unit tests for collectors and exporters (pending)
|
|
147
|
-
8. Update documentation (pending)
|
|
148
|
-
|
|
149
|
-
Now starting with the MetricsCollector class...
|
|
150
|
-
[Implements MetricsCollector]
|
|
151
|
-
|
|
152
|
-
[Updates TodoWrite - marks task 1 completed, task 2 in_progress]
|
|
153
|
-
|
|
154
|
-
Adding database schema...
|
|
155
|
-
[Creates schema]
|
|
156
|
-
|
|
157
|
-
[Updates TodoWrite - marks task 2 completed, task 3 in_progress]
|
|
158
|
-
|
|
159
|
-
Implementing CSV export...
|
|
160
|
-
[Implements CSV exporter]
|
|
161
|
-
|
|
162
|
-
[Updates TodoWrite - marks task 3 completed, task 4 in_progress]
|
|
163
|
-
|
|
164
|
-
Implementing JSON export...
|
|
165
|
-
[Implements JSON exporter]
|
|
166
|
-
|
|
167
|
-
[Updates TodoWrite - marks task 4 completed, task 5 in_progress]
|
|
168
|
-
|
|
169
|
-
Creating MetricsService API endpoints...
|
|
170
|
-
[Implements API endpoints]
|
|
171
|
-
|
|
172
|
-
[Updates TodoWrite - marks task 5 completed, task 6 in_progress]
|
|
173
|
-
|
|
174
|
-
Adding frontend UI for export...
|
|
175
|
-
[Implements UI components]
|
|
176
|
-
|
|
177
|
-
[Updates TodoWrite - marks task 6 completed, task 7 in_progress]
|
|
178
|
-
|
|
179
|
-
Writing unit tests...
|
|
180
|
-
[Writes and runs tests - all pass]
|
|
181
|
-
|
|
182
|
-
[Updates TodoWrite - marks task 7 completed, task 8 in_progress]
|
|
183
|
-
|
|
184
|
-
Updating documentation...
|
|
185
|
-
[Updates docs with usage examples]
|
|
186
|
-
|
|
187
|
-
[Updates TodoWrite - marks task 8 completed]
|
|
188
|
-
|
|
189
|
-
The metrics tracking and export feature is now complete. Users can collect metrics and export them to CSV or JSON formats through both the API and the frontend UI.
|
|
190
|
-
</example>
|
|
191
|
-
|
|
192
72
|
# Doing tasks
|
|
193
73
|
|
|
194
74
|
The user will primarily request you perform tasks. This includes solving problems, adding new functionality, refactoring, explaining content, and more. For these tasks the following steps are recommended:
|
|
195
75
|
|
|
196
|
-
- Use the TodoWrite tool to plan the task if required
|
|
197
76
|
- Use the available search tools to understand the context and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially.
|
|
198
77
|
- Implement the solution using all tools available to you
|
|
199
|
-
- Mark each todo completed IMMEDIATELY after finishing it
|
|
200
78
|
- Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the project documentation or search to determine the testing approach.
|
|
201
79
|
- When you have completed a task, if there are linting or validation commands available to you, run them to ensure your work is correct. NEVER assume what these commands are - check the project documentation first.
|
|
202
80
|
NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.
|
|
203
|
-
- Before giving your final response: Ensure ALL todos are marked completed. NEVER leave pending or in_progress tasks.
|
|
204
|
-
- IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
|
|
205
81
|
|
|
206
82
|
# Tool usage policy
|
|
207
83
|
|
|
@@ -211,8 +87,6 @@ NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTAN
|
|
|
211
87
|
- If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to delegate a task to multiple agents in parallel, send a single message with multiple DelegateTask tool calls.
|
|
212
88
|
- Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit/MultiEdit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
|
|
213
89
|
|
|
214
|
-
IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
|
|
215
|
-
|
|
216
90
|
You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.
|
|
217
91
|
|
|
218
92
|
|