swarm_sdk 2.7.13 → 2.7.15

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.
@@ -72,7 +72,7 @@ module SwarmSDK
72
72
  # Default tools available to all agents
73
73
  DEFAULT_TOOLS = ToolConfigurator::DEFAULT_TOOLS
74
74
 
75
- attr_reader :name, :agents, :lead_agent, :mcp_clients, :delegation_instances, :agent_definitions, :swarm_id, :parent_swarm_id, :swarm_registry, :scratchpad_storage, :allow_filesystem_tools, :hook_registry, :global_semaphore, :plugin_storages, :config_for_hooks, :observer_configs, :execution_timeout
75
+ attr_reader :name, :agents, :lead_agent, :mcp_clients, :delegation_instances, :agent_definitions, :swarm_id, :parent_swarm_id, :swarm_registry, :scratchpad_storage, :allow_filesystem_tools, :hook_registry, :global_semaphore, :plugin_storages, :config_for_hooks, :observer_configs, :execution_timeout, :stop_signal_read
76
76
 
77
77
  # Check if scratchpad tools are enabled
78
78
  #
@@ -217,6 +217,13 @@ module SwarmSDK
217
217
  # Observer agent configurations
218
218
  @observer_configs = []
219
219
  @observer_manager = nil
220
+
221
+ # Stop mechanism state
222
+ @stop_requested = false
223
+ @execution_barrier = nil
224
+ @stop_signal_read = nil
225
+ @stop_signal_write = nil
226
+ @active_agent_chats = {}
220
227
  end
221
228
 
222
229
  # Add an agent to the swarm
@@ -471,12 +478,17 @@ module SwarmSDK
471
478
 
472
479
  # Wait for all observer tasks to complete
473
480
  #
481
+ # If a stop was requested, stops observer tasks immediately instead of waiting.
474
482
  # Called by Executor to wait for observer agents before cleanup.
475
483
  # Safe to call even if no observers are configured.
476
484
  #
477
485
  # @return [void]
478
486
  def wait_for_observers
479
- @observer_manager&.wait_for_completion
487
+ if @stop_requested
488
+ stop_observers
489
+ else
490
+ @observer_manager&.wait_for_completion
491
+ end
480
492
  end
481
493
 
482
494
  # Cleanup observer subscriptions
@@ -490,6 +502,124 @@ module SwarmSDK
490
502
  @observer_manager = nil
491
503
  end
492
504
 
505
+ # Stop all swarm execution immediately
506
+ #
507
+ # Thread-safe method that signals the execution to stop. Uses IO.pipe
508
+ # for cross-thread signaling, which wakes the Async scheduler from any
509
+ # thread. The stop listener task then calls barrier.stop within the
510
+ # reactor to cancel all executing tasks.
511
+ #
512
+ # Safe to call from event callbacks, other threads, or signal handlers.
513
+ # No-op if no execution is in progress or stop was already requested.
514
+ #
515
+ # @return [void]
516
+ #
517
+ # @example Stop from event callback
518
+ # swarm.execute("Build auth") do |event|
519
+ # swarm.stop if event[:type] == "tool_call" && event[:tool] == "Dangerous"
520
+ # end
521
+ #
522
+ # @example Stop from another thread
523
+ # Thread.new { swarm.execute("Build auth") }
524
+ # sleep 10
525
+ # swarm.stop
526
+ def stop
527
+ return if @stop_requested
528
+
529
+ @stop_requested = true
530
+ begin
531
+ @stop_signal_write&.write("x") unless @stop_signal_write&.closed?
532
+ @stop_signal_write&.close unless @stop_signal_write&.closed?
533
+ rescue IOError, Errno::EPIPE
534
+ # Pipe already closed - normal during cleanup
535
+ end
536
+ end
537
+
538
+ # Check if a stop has been requested
539
+ #
540
+ # @return [Boolean] true if stop was requested
541
+ def stop_requested?
542
+ @stop_requested
543
+ end
544
+
545
+ # Prepare stop signaling for a new execution
546
+ #
547
+ # Resets the stop flag and creates a new IO.pipe for signaling.
548
+ # Called by Executor at the start of each execution.
549
+ #
550
+ # @return [void]
551
+ def prepare_for_execution
552
+ @stop_requested = false
553
+ @stop_signal_read, @stop_signal_write = IO.pipe
554
+ @active_agent_chats = {}
555
+ end
556
+
557
+ # Close the stop signal pipe
558
+ #
559
+ # Called by Executor after execution completes.
560
+ #
561
+ # @return [void]
562
+ def cleanup_stop_signal
563
+ @stop_signal_read&.close unless @stop_signal_read&.closed?
564
+ @stop_signal_write&.close unless @stop_signal_write&.closed?
565
+ @stop_signal_read = nil
566
+ @stop_signal_write = nil
567
+ end
568
+
569
+ # Register the execution barrier for stop cancellation
570
+ #
571
+ # @param barrier [Async::Barrier] The barrier wrapping execution tasks
572
+ # @return [void]
573
+ def register_execution_barrier(barrier)
574
+ @execution_barrier = barrier
575
+ end
576
+
577
+ # Clear the execution barrier reference
578
+ #
579
+ # @return [void]
580
+ def clear_execution_barrier
581
+ @execution_barrier = nil
582
+ end
583
+
584
+ # Mark an agent as actively executing an LLM call
585
+ #
586
+ # Called by Agent::Chat#execute_ask to track which agents are mid-execution.
587
+ # Used during interruption to emit agent_stop events for active agents.
588
+ #
589
+ # @param name [Symbol] Agent name
590
+ # @param chat [Agent::Chat] Agent chat instance
591
+ # @return [void]
592
+ def mark_agent_active(name, chat)
593
+ @active_agent_chats[name] = chat
594
+ end
595
+
596
+ # Mark an agent as no longer actively executing
597
+ #
598
+ # @param name [Symbol] Agent name
599
+ # @return [void]
600
+ def mark_agent_inactive(name)
601
+ @active_agent_chats.delete(name)
602
+ end
603
+
604
+ # Get a snapshot of currently active agent chats
605
+ #
606
+ # Returns a copy to avoid concurrent modification issues.
607
+ #
608
+ # @return [Hash{Symbol => Agent::Chat}] Copy of active agent chats
609
+ def active_agent_chats
610
+ @active_agent_chats.dup
611
+ end
612
+
613
+ # Stop all observer tasks immediately
614
+ #
615
+ # Interrupts in-flight observer LLM calls.
616
+ # Called during swarm interruption instead of wait_for_completion.
617
+ #
618
+ # @return [void]
619
+ def stop_observers
620
+ @observer_manager&.stop
621
+ end
622
+
493
623
  # Create snapshot of current conversation state
494
624
  #
495
625
  # Returns a Snapshot object containing:
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.7.13"
4
+ VERSION = "2.7.15"
5
5
  end
data/lib/swarm_sdk.rb CHANGED
@@ -91,6 +91,9 @@ module SwarmSDK
91
91
  # Raised when agent turn exceeds turn_timeout
92
92
  class TurnTimeoutError < TimeoutError; end
93
93
 
94
+ # Raised when swarm execution is interrupted via swarm.stop
95
+ class InterruptedError < Error; end
96
+
94
97
  # Base class for MCP-related errors (provides context about server/tool)
95
98
  class MCPError < Error; end
96
99
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swarm_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.13
4
+ version: 2.7.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Arruda
@@ -181,8 +181,11 @@ files:
181
181
  - lib/swarm_sdk/ruby_llm_patches/connection_patch.rb
182
182
  - lib/swarm_sdk/ruby_llm_patches/init.rb
183
183
  - lib/swarm_sdk/ruby_llm_patches/io_endpoint_patch.rb
184
+ - lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb
184
185
  - lib/swarm_sdk/ruby_llm_patches/message_management_patch.rb
186
+ - lib/swarm_sdk/ruby_llm_patches/openai_thought_signature_patch.rb
185
187
  - lib/swarm_sdk/ruby_llm_patches/responses_api_patch.rb
188
+ - lib/swarm_sdk/ruby_llm_patches/streaming_error_patch.rb
186
189
  - lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb
187
190
  - lib/swarm_sdk/snapshot.rb
188
191
  - lib/swarm_sdk/snapshot_from_events.rb