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
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
class Swarm
|
|
5
|
-
# AllAgentsBuilder for configuring settings that apply to all agents
|
|
6
|
-
#
|
|
7
|
-
# Settings configured here are applied to ALL agents, but can be overridden
|
|
8
|
-
# at the agent level. This is useful for shared configuration like:
|
|
9
|
-
# - Common provider/base_url (all agents use same proxy)
|
|
10
|
-
# - Shared timeout settings
|
|
11
|
-
# - Global permissions
|
|
12
|
-
#
|
|
13
|
-
# @example
|
|
14
|
-
# all_agents do
|
|
15
|
-
# provider :openai
|
|
16
|
-
# base_url "http://proxy.com/v1"
|
|
17
|
-
# timeout 180
|
|
18
|
-
# tools :Read, :Write
|
|
19
|
-
# coding_agent false
|
|
20
|
-
# end
|
|
21
|
-
class AllAgentsBuilder
|
|
22
|
-
attr_reader :hooks, :permissions_config, :tools_list
|
|
23
|
-
|
|
24
|
-
def initialize
|
|
25
|
-
@tools_list = []
|
|
26
|
-
@hooks = []
|
|
27
|
-
@permissions_config = {}
|
|
28
|
-
@model = nil
|
|
29
|
-
@provider = nil
|
|
30
|
-
@base_url = nil
|
|
31
|
-
@api_version = nil
|
|
32
|
-
@timeout = nil
|
|
33
|
-
@parameters = nil
|
|
34
|
-
@headers = nil
|
|
35
|
-
@coding_agent = nil
|
|
36
|
-
@disable_default_tools = nil
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Set model for all agents
|
|
40
|
-
def model(model_name)
|
|
41
|
-
@model = model_name
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Set provider for all agents
|
|
45
|
-
def provider(provider_name)
|
|
46
|
-
@provider = provider_name
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Set base URL for all agents
|
|
50
|
-
def base_url(url)
|
|
51
|
-
@base_url = url
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Set API version for all agents
|
|
55
|
-
def api_version(version)
|
|
56
|
-
@api_version = version
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Set timeout for all agents
|
|
60
|
-
def timeout(seconds)
|
|
61
|
-
@timeout = seconds
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Set parameters for all agents
|
|
65
|
-
def parameters(params)
|
|
66
|
-
@parameters = params
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Set headers for all agents
|
|
70
|
-
def headers(header_hash)
|
|
71
|
-
@headers = header_hash
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Set coding_agent flag for all agents
|
|
75
|
-
def coding_agent(enabled)
|
|
76
|
-
@coding_agent = enabled
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Disable default tools for all agents
|
|
80
|
-
#
|
|
81
|
-
# @param value [Boolean, Array<Symbol>]
|
|
82
|
-
# - true: Disable ALL default tools
|
|
83
|
-
# - Array of symbols: Disable specific tools (e.g., [:Think, :TodoWrite])
|
|
84
|
-
def disable_default_tools(value)
|
|
85
|
-
@disable_default_tools = value
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Add tools that all agents will have
|
|
89
|
-
def tools(*tool_names)
|
|
90
|
-
@tools_list.concat(tool_names)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Add hook for all agents (agent-level events only)
|
|
94
|
-
#
|
|
95
|
-
# @example
|
|
96
|
-
# hook :pre_tool_use, matcher: "Write" do |ctx|
|
|
97
|
-
# # Applies to all agents
|
|
98
|
-
# end
|
|
99
|
-
def hook(event, matcher: nil, command: nil, timeout: nil, &block)
|
|
100
|
-
# Validate agent-level events
|
|
101
|
-
agent_events = [
|
|
102
|
-
:pre_tool_use,
|
|
103
|
-
:post_tool_use,
|
|
104
|
-
:user_prompt,
|
|
105
|
-
:agent_step,
|
|
106
|
-
:agent_stop,
|
|
107
|
-
:first_message,
|
|
108
|
-
:pre_delegation,
|
|
109
|
-
:post_delegation,
|
|
110
|
-
:context_warning,
|
|
111
|
-
]
|
|
112
|
-
|
|
113
|
-
unless agent_events.include?(event)
|
|
114
|
-
raise ArgumentError, "Invalid all_agents hook: #{event}. Swarm-level events (:swarm_start, :swarm_stop) cannot be used in all_agents block."
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
@hooks << { event: event, matcher: matcher, command: command, timeout: timeout, block: block }
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Configure permissions for all agents
|
|
121
|
-
#
|
|
122
|
-
# Supports two forms:
|
|
123
|
-
# 1. Block form (DSL): permissions do ... end
|
|
124
|
-
# 2. Direct hash (internal/YAML): set_permissions_hash(hash)
|
|
125
|
-
#
|
|
126
|
-
# @example Block form
|
|
127
|
-
# permissions do
|
|
128
|
-
# Write.allow_paths "tmp/**/*"
|
|
129
|
-
# Write.deny_paths "tmp/secrets/**"
|
|
130
|
-
# Bash.allow_commands "^git status$"
|
|
131
|
-
# end
|
|
132
|
-
def permissions(&block)
|
|
133
|
-
@permissions_config = PermissionsBuilder.build(&block)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Set permissions directly from hash (for YAML translation)
|
|
137
|
-
#
|
|
138
|
-
# This is intentionally separate from permissions() to keep the DSL clean.
|
|
139
|
-
# Called by Configuration when translating YAML permissions.
|
|
140
|
-
#
|
|
141
|
-
# @param hash [Hash] Permissions configuration hash
|
|
142
|
-
# @return [void]
|
|
143
|
-
def permissions_hash=(hash)
|
|
144
|
-
@permissions_config = hash || {}
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Convert to hash for merging with agent configs
|
|
148
|
-
#
|
|
149
|
-
# @return [Hash] Configuration hash
|
|
150
|
-
def to_h
|
|
151
|
-
{
|
|
152
|
-
model: @model,
|
|
153
|
-
provider: @provider,
|
|
154
|
-
base_url: @base_url,
|
|
155
|
-
api_version: @api_version,
|
|
156
|
-
timeout: @timeout,
|
|
157
|
-
parameters: @parameters,
|
|
158
|
-
headers: @headers,
|
|
159
|
-
coding_agent: @coding_agent,
|
|
160
|
-
disable_default_tools: @disable_default_tools,
|
|
161
|
-
tools: @tools_list,
|
|
162
|
-
permissions: @permissions_config,
|
|
163
|
-
}.compact
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
class Swarm
|
|
5
|
-
# Builder provides a beautiful Ruby DSL for building swarms
|
|
6
|
-
#
|
|
7
|
-
# The DSL combines YAML simplicity with Ruby power, enabling:
|
|
8
|
-
# - Fluent, chainable configuration
|
|
9
|
-
# - Hooks as Ruby blocks OR shell commands
|
|
10
|
-
# - Full Ruby language features (variables, conditionals, loops)
|
|
11
|
-
# - Type-safe, IDE-friendly API
|
|
12
|
-
#
|
|
13
|
-
# @example Basic usage
|
|
14
|
-
# swarm = SwarmSDK.build do
|
|
15
|
-
# name "Dev Team"
|
|
16
|
-
# lead :backend
|
|
17
|
-
#
|
|
18
|
-
# agent :backend do
|
|
19
|
-
# model "gpt-5"
|
|
20
|
-
# prompt "You build APIs"
|
|
21
|
-
# tools :Read, :Write, :Bash
|
|
22
|
-
#
|
|
23
|
-
# # Hook as Ruby block - inline logic!
|
|
24
|
-
# hook :pre_tool_use, matcher: "Bash" do |ctx|
|
|
25
|
-
# SwarmSDK::Hooks::Result.halt("Blocked!") if ctx.tool_call.parameters[:command].include?("rm -rf")
|
|
26
|
-
# end
|
|
27
|
-
# end
|
|
28
|
-
# end
|
|
29
|
-
#
|
|
30
|
-
# swarm.execute("Build auth API")
|
|
31
|
-
class Builder < Builders::BaseBuilder
|
|
32
|
-
# Main entry point for DSL
|
|
33
|
-
#
|
|
34
|
-
# @example
|
|
35
|
-
# swarm = SwarmSDK.build do
|
|
36
|
-
# name "Team"
|
|
37
|
-
# agent :backend { ... }
|
|
38
|
-
# end
|
|
39
|
-
class << self
|
|
40
|
-
def build(allow_filesystem_tools: nil, &block)
|
|
41
|
-
builder = new(allow_filesystem_tools: allow_filesystem_tools)
|
|
42
|
-
builder.instance_eval(&block)
|
|
43
|
-
builder.build_swarm
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def initialize(allow_filesystem_tools: nil)
|
|
48
|
-
super
|
|
49
|
-
@lead_agent = nil
|
|
50
|
-
@swarm_hooks = []
|
|
51
|
-
@observer_configs = []
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Set lead agent
|
|
55
|
-
def lead(agent_name)
|
|
56
|
-
@lead_agent = agent_name
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Define observer agent behavior
|
|
60
|
-
#
|
|
61
|
-
# Configures an agent to run in parallel with main execution,
|
|
62
|
-
# triggered by specific events. The block defines event handlers.
|
|
63
|
-
#
|
|
64
|
-
# @param agent_name [Symbol] Name of agent to use as observer (must be defined)
|
|
65
|
-
# @param options [Hash] Optional observer settings (timeout, max_concurrent, etc.)
|
|
66
|
-
# @yield Observer configuration block
|
|
67
|
-
#
|
|
68
|
-
# @example Basic observer
|
|
69
|
-
# observer :profiler do
|
|
70
|
-
# on :swarm_start do |event|
|
|
71
|
-
# "Analyze this prompt: #{event[:prompt]}"
|
|
72
|
-
# end
|
|
73
|
-
# end
|
|
74
|
-
#
|
|
75
|
-
# @example Observer with options
|
|
76
|
-
# observer :monitor, timeout: 120 do
|
|
77
|
-
# on :tool_call do |event|
|
|
78
|
-
# next unless event[:tool_name] == "Bash"
|
|
79
|
-
# "Check command: #{event[:arguments][:command]}"
|
|
80
|
-
# end
|
|
81
|
-
# end
|
|
82
|
-
def observer(agent_name, **options, &block)
|
|
83
|
-
unless @agents.key?(agent_name)
|
|
84
|
-
raise ConfigurationError,
|
|
85
|
-
"Observer agent '#{agent_name}' not defined. " \
|
|
86
|
-
"Define the agent first with `agent :#{agent_name} do ... end`"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
config = Observer::Config.new(agent_name)
|
|
90
|
-
config.options.merge!(options) if options.any?
|
|
91
|
-
builder = Observer::Builder.new(agent_name, config)
|
|
92
|
-
builder.instance_eval(&block)
|
|
93
|
-
|
|
94
|
-
@observer_configs << config
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Add swarm-level hook (swarm_start, swarm_stop only)
|
|
98
|
-
#
|
|
99
|
-
# @example Shell command
|
|
100
|
-
# hook :swarm_start, command: "echo 'Starting' >> log.txt"
|
|
101
|
-
#
|
|
102
|
-
# @example Ruby block
|
|
103
|
-
# hook :swarm_start do |ctx|
|
|
104
|
-
# puts "Swarm starting: #{ctx.metadata[:prompt]}"
|
|
105
|
-
# end
|
|
106
|
-
def hook(event, command: nil, timeout: nil, &block)
|
|
107
|
-
# Validate swarm-level events
|
|
108
|
-
unless [:swarm_start, :swarm_stop].include?(event)
|
|
109
|
-
raise ArgumentError, "Invalid swarm-level hook: #{event}. Only :swarm_start and :swarm_stop allowed at swarm level. Use all_agents { hook ... } or agent { hook ... } for other events."
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
@swarm_hooks << { event: event, command: command, timeout: timeout, block: block }
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Build the actual Swarm instance
|
|
116
|
-
def build_swarm
|
|
117
|
-
raise ConfigurationError, "Swarm name not set. Use: name 'My Swarm'" unless @swarm_name
|
|
118
|
-
raise ConfigurationError, "No agents defined. Use: agent :name { ... }" if @agents.empty?
|
|
119
|
-
raise ConfigurationError, "Lead agent not set. Use: lead :agent_name" unless @lead_agent
|
|
120
|
-
|
|
121
|
-
# Validate filesystem tools BEFORE building
|
|
122
|
-
validate_all_agents_filesystem_tools if @all_agents_config
|
|
123
|
-
validate_agent_filesystem_tools
|
|
124
|
-
|
|
125
|
-
build_single_swarm
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
private
|
|
129
|
-
|
|
130
|
-
# Build a traditional single-swarm execution
|
|
131
|
-
#
|
|
132
|
-
# @return [Swarm] Configured swarm instance
|
|
133
|
-
def build_single_swarm
|
|
134
|
-
# Validate swarm_id is set if external swarms are registered (required for composable swarms)
|
|
135
|
-
if @swarm_registry_config.any? && @swarm_id.nil?
|
|
136
|
-
raise ConfigurationError, "Swarm id must be set using id(...) when using composable swarms"
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# Create swarm using SDK (swarm_id auto-generates if nil)
|
|
140
|
-
swarm = Swarm.new(
|
|
141
|
-
name: @swarm_name,
|
|
142
|
-
swarm_id: @swarm_id,
|
|
143
|
-
scratchpad_mode: @scratchpad,
|
|
144
|
-
allow_filesystem_tools: @allow_filesystem_tools,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
# Setup swarm registry if external swarms are registered
|
|
148
|
-
if @swarm_registry_config.any?
|
|
149
|
-
registry = SwarmRegistry.new(parent_swarm_id: @swarm_id)
|
|
150
|
-
@swarm_registry_config.each do |reg|
|
|
151
|
-
registry.register(reg[:name], source: reg[:source], keep_context: reg[:keep_context])
|
|
152
|
-
end
|
|
153
|
-
swarm.swarm_registry = registry
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# Build agent definitions and add to swarm
|
|
157
|
-
agent_definitions = build_agent_definitions
|
|
158
|
-
agent_definitions.each_value do |definition|
|
|
159
|
-
swarm.add_agent(definition)
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Set lead
|
|
163
|
-
swarm.lead = @lead_agent
|
|
164
|
-
|
|
165
|
-
# Apply swarm hooks (Ruby blocks)
|
|
166
|
-
@swarm_hooks.each do |hook_config|
|
|
167
|
-
apply_swarm_hook(swarm, hook_config)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Apply all_agents hooks (Ruby blocks)
|
|
171
|
-
@all_agents_config&.hooks&.each do |hook_config|
|
|
172
|
-
apply_all_agents_hook(swarm, hook_config)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# Add observer configurations to swarm
|
|
176
|
-
@observer_configs.each { |c| swarm.add_observer_config(c) }
|
|
177
|
-
|
|
178
|
-
swarm
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def apply_swarm_hook(swarm, config)
|
|
182
|
-
event = config[:event]
|
|
183
|
-
|
|
184
|
-
if config[:block]
|
|
185
|
-
# Ruby block hook - register directly
|
|
186
|
-
swarm.add_default_callback(event, &config[:block])
|
|
187
|
-
elsif config[:command]
|
|
188
|
-
# Shell command hook - use ShellExecutor
|
|
189
|
-
swarm.add_default_callback(event) do |context|
|
|
190
|
-
input_json = build_hook_input(context, event)
|
|
191
|
-
Hooks::ShellExecutor.execute(
|
|
192
|
-
command: config[:command],
|
|
193
|
-
input_json: input_json,
|
|
194
|
-
timeout: config[:timeout] || 60,
|
|
195
|
-
swarm_name: swarm.name,
|
|
196
|
-
event: event,
|
|
197
|
-
)
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def apply_all_agents_hook(swarm, config)
|
|
203
|
-
event = config[:event]
|
|
204
|
-
matcher = config[:matcher]
|
|
205
|
-
|
|
206
|
-
if config[:block]
|
|
207
|
-
# Ruby block hook
|
|
208
|
-
swarm.add_default_callback(event, matcher: matcher, &config[:block])
|
|
209
|
-
elsif config[:command]
|
|
210
|
-
# Shell command hook
|
|
211
|
-
swarm.add_default_callback(event, matcher: matcher) do |context|
|
|
212
|
-
input_json = build_hook_input(context, event)
|
|
213
|
-
Hooks::ShellExecutor.execute(
|
|
214
|
-
command: config[:command],
|
|
215
|
-
input_json: input_json,
|
|
216
|
-
timeout: config[:timeout] || 60,
|
|
217
|
-
agent_name: context.agent_name,
|
|
218
|
-
swarm_name: swarm.name,
|
|
219
|
-
event: event,
|
|
220
|
-
)
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def build_hook_input(context, event)
|
|
226
|
-
# Build JSON input for shell hooks
|
|
227
|
-
base = { event: event.to_s }
|
|
228
|
-
|
|
229
|
-
case event
|
|
230
|
-
when :pre_tool_use
|
|
231
|
-
base.merge(tool: context.tool_call.name, parameters: context.tool_call.parameters)
|
|
232
|
-
when :post_tool_use
|
|
233
|
-
base.merge(result: context.tool_result.content, success: context.tool_result.success?)
|
|
234
|
-
when :user_prompt
|
|
235
|
-
base.merge(prompt: context.metadata[:prompt])
|
|
236
|
-
when :swarm_start
|
|
237
|
-
base.merge(prompt: context.metadata[:prompt])
|
|
238
|
-
when :swarm_stop
|
|
239
|
-
base.merge(success: context.metadata[:success], duration: context.metadata[:duration])
|
|
240
|
-
else
|
|
241
|
-
base
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Helper class for swarms block in DSL (kept in this file for reference)
|
|
247
|
-
# Actual implementation is in swarm_registry_builder.rb for Zeitwerk
|
|
248
|
-
end
|
|
249
|
-
end
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
class Swarm
|
|
5
|
-
# Handles swarm execution orchestration
|
|
6
|
-
#
|
|
7
|
-
# Extracted from Swarm#execute to reduce complexity and eliminate code duplication.
|
|
8
|
-
# The core execution loop, error handling, and cleanup logic are unified here.
|
|
9
|
-
class Executor
|
|
10
|
-
def initialize(swarm)
|
|
11
|
-
@swarm = swarm
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# Execute the swarm with a prompt
|
|
15
|
-
#
|
|
16
|
-
# @param prompt [String] User prompt
|
|
17
|
-
# @param wait [Boolean] Block until completion (true) or return task (false)
|
|
18
|
-
# @param logs [Array] Log collection array
|
|
19
|
-
# @param has_logging [Boolean] Whether logging is enabled
|
|
20
|
-
# @param original_fiber_storage [Hash] Original Fiber storage values to restore
|
|
21
|
-
# @return [Async::Task] The execution task
|
|
22
|
-
def run(prompt, wait:, logs:, has_logging:, original_fiber_storage:)
|
|
23
|
-
@original_fiber_storage = original_fiber_storage
|
|
24
|
-
if wait
|
|
25
|
-
run_blocking(prompt, logs: logs, has_logging: has_logging)
|
|
26
|
-
else
|
|
27
|
-
run_async(prompt, logs: logs, has_logging: has_logging)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
# Blocking execution using Sync
|
|
34
|
-
def run_blocking(prompt, logs:, has_logging:)
|
|
35
|
-
Sync do |task|
|
|
36
|
-
execute_in_task(prompt, logs: logs, has_logging: has_logging) do |lead, current_prompt|
|
|
37
|
-
task.async(finished: false) { lead.ask(current_prompt) }.wait
|
|
38
|
-
end
|
|
39
|
-
ensure
|
|
40
|
-
# Always wait for observer tasks, even if main execution raises
|
|
41
|
-
# This is INSIDE Sync block, so async tasks can still complete
|
|
42
|
-
@swarm.wait_for_observers
|
|
43
|
-
end
|
|
44
|
-
ensure
|
|
45
|
-
# Restore original fiber storage (preserves parent context for nested swarms)
|
|
46
|
-
restore_fiber_storage
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Non-blocking execution using parent async task
|
|
50
|
-
def run_async(prompt, logs:, has_logging:)
|
|
51
|
-
parent = Async::Task.current
|
|
52
|
-
raise ConfigurationError, "wait: false requires an async context. Use Sync { swarm.execute(..., wait: false) }" unless parent
|
|
53
|
-
|
|
54
|
-
parent.async(finished: false) do
|
|
55
|
-
execute_in_task(prompt, logs: logs, has_logging: has_logging) do |lead, current_prompt|
|
|
56
|
-
Async(finished: false) { lead.ask(current_prompt) }.wait
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Core execution logic (unified, no duplication)
|
|
62
|
-
#
|
|
63
|
-
# @param prompt [String] Initial prompt
|
|
64
|
-
# @param logs [Array] Log collection
|
|
65
|
-
# @param has_logging [Boolean] Whether logging is enabled
|
|
66
|
-
# @yield [lead, current_prompt] Block to execute LLM call
|
|
67
|
-
# @return [Result] Execution result
|
|
68
|
-
def execute_in_task(prompt, logs:, has_logging:, &block)
|
|
69
|
-
start_time = Time.now
|
|
70
|
-
result = nil
|
|
71
|
-
swarm_stop_triggered = false
|
|
72
|
-
current_prompt = prompt
|
|
73
|
-
|
|
74
|
-
begin
|
|
75
|
-
# Notify plugins that swarm is starting
|
|
76
|
-
PluginRegistry.emit_event(:on_swarm_started, swarm: @swarm)
|
|
77
|
-
|
|
78
|
-
result = execution_loop(current_prompt, logs, start_time, &block)
|
|
79
|
-
swarm_stop_triggered = true
|
|
80
|
-
rescue ConfigurationError, AgentNotFoundError
|
|
81
|
-
# Re-raise configuration errors - these should be fixed, not caught
|
|
82
|
-
raise
|
|
83
|
-
rescue TypeError => e
|
|
84
|
-
result = handle_type_error(e, logs, start_time)
|
|
85
|
-
rescue StandardError => e
|
|
86
|
-
result = handle_standard_error(e, logs, start_time)
|
|
87
|
-
ensure
|
|
88
|
-
# Notify plugins that swarm is stopping (called even on error)
|
|
89
|
-
PluginRegistry.emit_event(:on_swarm_stopped, swarm: @swarm)
|
|
90
|
-
|
|
91
|
-
cleanup_after_execution(result, start_time, logs, swarm_stop_triggered, has_logging)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
result
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
# Main execution loop with reprompting support
|
|
98
|
-
def execution_loop(initial_prompt, logs, start_time)
|
|
99
|
-
current_prompt = initial_prompt
|
|
100
|
-
|
|
101
|
-
loop do
|
|
102
|
-
lead = @swarm.agents[@swarm.lead_agent]
|
|
103
|
-
response = yield(lead, current_prompt)
|
|
104
|
-
|
|
105
|
-
# Check if swarm was finished by a hook (finish_swarm)
|
|
106
|
-
if response.is_a?(Hash) && response[:__finish_swarm__]
|
|
107
|
-
result = build_result(response[:message], logs, start_time)
|
|
108
|
-
@swarm.trigger_swarm_stop(result)
|
|
109
|
-
return result
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
result = build_result(response.content, logs, start_time)
|
|
113
|
-
|
|
114
|
-
# Trigger swarm_stop hooks (for reprompt check and event emission)
|
|
115
|
-
hook_result = @swarm.trigger_swarm_stop(result)
|
|
116
|
-
|
|
117
|
-
# Check if hook requests reprompting
|
|
118
|
-
if hook_result&.reprompt?
|
|
119
|
-
current_prompt = hook_result.value
|
|
120
|
-
# Continue loop with new prompt
|
|
121
|
-
else
|
|
122
|
-
# Exit loop - execution complete
|
|
123
|
-
return result
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
# Build a Result object
|
|
129
|
-
def build_result(content, logs, start_time)
|
|
130
|
-
Result.new(
|
|
131
|
-
content: content,
|
|
132
|
-
agent: @swarm.lead_agent.to_s,
|
|
133
|
-
logs: logs,
|
|
134
|
-
duration: Time.now - start_time,
|
|
135
|
-
)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# Handle TypeError (e.g., "String does not have #dig method")
|
|
139
|
-
def handle_type_error(error, logs, start_time)
|
|
140
|
-
if error.message.include?("does not have #dig method")
|
|
141
|
-
agent_definition = @swarm.agent_definitions[@swarm.lead_agent]
|
|
142
|
-
error_msg = if agent_definition.base_url
|
|
143
|
-
"LLM API request failed: The proxy/server at '#{agent_definition.base_url}' returned an invalid response. " \
|
|
144
|
-
"This usually means the proxy is unreachable, requires authentication, or returned an error in non-JSON format. " \
|
|
145
|
-
"Original error: #{error.message}"
|
|
146
|
-
else
|
|
147
|
-
"LLM API request failed with unexpected response format. Original error: #{error.message}"
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
Result.new(
|
|
151
|
-
content: nil,
|
|
152
|
-
agent: @swarm.lead_agent.to_s,
|
|
153
|
-
error: LLMError.new(error_msg),
|
|
154
|
-
logs: logs,
|
|
155
|
-
duration: Time.now - start_time,
|
|
156
|
-
)
|
|
157
|
-
else
|
|
158
|
-
Result.new(
|
|
159
|
-
content: nil,
|
|
160
|
-
agent: @swarm.lead_agent.to_s,
|
|
161
|
-
error: error,
|
|
162
|
-
logs: logs,
|
|
163
|
-
duration: Time.now - start_time,
|
|
164
|
-
)
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Handle StandardError
|
|
169
|
-
def handle_standard_error(error, logs, start_time)
|
|
170
|
-
Result.new(
|
|
171
|
-
content: nil,
|
|
172
|
-
agent: @swarm.lead_agent&.to_s || "unknown",
|
|
173
|
-
error: error,
|
|
174
|
-
logs: logs,
|
|
175
|
-
duration: Time.now - start_time,
|
|
176
|
-
)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Cleanup after execution (ensure block logic)
|
|
180
|
-
def cleanup_after_execution(result, start_time, logs, swarm_stop_triggered, has_logging)
|
|
181
|
-
# Trigger swarm_stop if not already triggered (handles error cases)
|
|
182
|
-
unless swarm_stop_triggered
|
|
183
|
-
@swarm.trigger_swarm_stop_final(result, start_time, logs)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# Cleanup MCP clients after execution
|
|
187
|
-
@swarm.cleanup
|
|
188
|
-
|
|
189
|
-
# Cleanup observer subscriptions (matches MCP cleanup pattern)
|
|
190
|
-
@swarm.cleanup_observers
|
|
191
|
-
|
|
192
|
-
# Restore original Fiber storage (preserves parent context for nested swarms)
|
|
193
|
-
restore_fiber_storage
|
|
194
|
-
|
|
195
|
-
# Reset logging state for next execution if we set it up
|
|
196
|
-
reset_logging if has_logging
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
# Restore Fiber-local storage to original values (preserves parent context)
|
|
200
|
-
def restore_fiber_storage
|
|
201
|
-
Fiber[:execution_id] = @original_fiber_storage[:execution_id]
|
|
202
|
-
Fiber[:swarm_id] = @original_fiber_storage[:swarm_id]
|
|
203
|
-
Fiber[:parent_swarm_id] = @original_fiber_storage[:parent_swarm_id]
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Reset logging state
|
|
207
|
-
def reset_logging
|
|
208
|
-
LogCollector.reset!
|
|
209
|
-
LogStream.reset!
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|