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,555 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmSDK
|
|
4
|
-
class Workflow
|
|
5
|
-
# NodeBuilder provides DSL for configuring individual nodes within a workflow
|
|
6
|
-
#
|
|
7
|
-
# A node represents a stage in a multi-step workflow where a specific set
|
|
8
|
-
# of agents collaborate. Each node creates an independent swarm execution.
|
|
9
|
-
#
|
|
10
|
-
# @example Solo agent node
|
|
11
|
-
# node :planning do
|
|
12
|
-
# agent(:architect)
|
|
13
|
-
# end
|
|
14
|
-
#
|
|
15
|
-
# @example Multi-agent node with delegation
|
|
16
|
-
# node :implementation do
|
|
17
|
-
# agent(:backend).delegates_to(:tester, :database)
|
|
18
|
-
# agent(:tester).delegates_to(:database)
|
|
19
|
-
# agent(:database)
|
|
20
|
-
#
|
|
21
|
-
# depends_on :planning
|
|
22
|
-
# end
|
|
23
|
-
class NodeBuilder
|
|
24
|
-
attr_reader :name,
|
|
25
|
-
:agent_configs,
|
|
26
|
-
:dependencies,
|
|
27
|
-
:lead_override,
|
|
28
|
-
:input_transformer,
|
|
29
|
-
:output_transformer,
|
|
30
|
-
:input_transformer_command,
|
|
31
|
-
:output_transformer_command
|
|
32
|
-
|
|
33
|
-
def initialize(name)
|
|
34
|
-
@name = name
|
|
35
|
-
@agent_configs = []
|
|
36
|
-
@dependencies = []
|
|
37
|
-
@lead_override = nil
|
|
38
|
-
@input_transformer = nil # Ruby block
|
|
39
|
-
@output_transformer = nil # Ruby block
|
|
40
|
-
@input_transformer_command = nil # Bash command
|
|
41
|
-
@output_transformer_command = nil # Bash command
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Configure an agent for this node
|
|
45
|
-
#
|
|
46
|
-
# Returns an AgentConfig object that supports fluent delegation and tool override syntax.
|
|
47
|
-
# If delegates_to/tools are not called, the agent uses global configuration.
|
|
48
|
-
#
|
|
49
|
-
# By default, agents get fresh context in each node (reset_context: true).
|
|
50
|
-
# Set reset_context: false to preserve conversation history across nodes.
|
|
51
|
-
#
|
|
52
|
-
# @param name [Symbol] Agent name
|
|
53
|
-
# @param reset_context [Boolean] Whether to reset agent context (default: true)
|
|
54
|
-
# @return [AgentConfig] Fluent configuration object
|
|
55
|
-
#
|
|
56
|
-
# @example With delegation
|
|
57
|
-
# agent(:backend).delegates_to(:tester, :database)
|
|
58
|
-
#
|
|
59
|
-
# @example Without delegation
|
|
60
|
-
# agent(:planner)
|
|
61
|
-
#
|
|
62
|
-
# @example Preserve context across nodes
|
|
63
|
-
# agent(:architect, reset_context: false)
|
|
64
|
-
#
|
|
65
|
-
# @example Override tools for this node
|
|
66
|
-
# agent(:backend).tools(:Read, :Think)
|
|
67
|
-
#
|
|
68
|
-
# @example Combine delegation and tools
|
|
69
|
-
# agent(:backend).delegates_to(:tester).tools(:Read, :Edit, :Write)
|
|
70
|
-
def agent(name, reset_context: true)
|
|
71
|
-
config = AgentConfig.new(name, self, reset_context: reset_context)
|
|
72
|
-
|
|
73
|
-
# Register immediately with empty delegation and no tool override
|
|
74
|
-
# If delegates_to/tools are called later, they will update this
|
|
75
|
-
register_agent(name, [], reset_context, nil)
|
|
76
|
-
|
|
77
|
-
config
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Register an agent configuration (called by AgentConfig)
|
|
81
|
-
#
|
|
82
|
-
# @param agent_name [Symbol] Agent name
|
|
83
|
-
# @param delegates_to [Array<Symbol>] Delegation targets
|
|
84
|
-
# @param reset_context [Boolean] Whether to reset agent context
|
|
85
|
-
# @param tools [Array<Symbol>, nil] Tool override for this node (nil = use global)
|
|
86
|
-
# @return [void]
|
|
87
|
-
def register_agent(agent_name, delegates_to, reset_context = true, tools = nil)
|
|
88
|
-
# Check if agent already registered
|
|
89
|
-
existing = @agent_configs.find { |ac| ac[:agent] == agent_name }
|
|
90
|
-
|
|
91
|
-
if existing
|
|
92
|
-
# Update delegation, reset_context, and tools (happens when methods are called after agent())
|
|
93
|
-
existing[:delegates_to] = delegates_to
|
|
94
|
-
existing[:reset_context] = reset_context
|
|
95
|
-
existing[:tools] = tools unless tools.nil?
|
|
96
|
-
else
|
|
97
|
-
# Add new agent configuration
|
|
98
|
-
@agent_configs << {
|
|
99
|
-
agent: agent_name,
|
|
100
|
-
delegates_to: delegates_to,
|
|
101
|
-
reset_context: reset_context,
|
|
102
|
-
tools: tools,
|
|
103
|
-
}
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Declare dependencies (nodes that must execute before this one)
|
|
108
|
-
#
|
|
109
|
-
# @param node_names [Array<Symbol>] Names of prerequisite nodes
|
|
110
|
-
# @return [void]
|
|
111
|
-
#
|
|
112
|
-
# @example Single dependency
|
|
113
|
-
# depends_on :planning
|
|
114
|
-
#
|
|
115
|
-
# @example Multiple dependencies
|
|
116
|
-
# depends_on :frontend, :backend
|
|
117
|
-
def depends_on(*node_names)
|
|
118
|
-
@dependencies.concat(node_names.map(&:to_sym))
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Override the lead agent (first agent is lead by default)
|
|
122
|
-
#
|
|
123
|
-
# @param agent_name [Symbol] Name of agent to make lead
|
|
124
|
-
# @return [void]
|
|
125
|
-
#
|
|
126
|
-
# @example
|
|
127
|
-
# agent(:backend).delegates_to(:tester)
|
|
128
|
-
# agent(:tester)
|
|
129
|
-
# lead :tester # tester is lead instead of backend
|
|
130
|
-
def lead(agent_name)
|
|
131
|
-
@lead_override = agent_name.to_sym
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Define input transformer for this node
|
|
135
|
-
#
|
|
136
|
-
# The transformer receives a NodeContext object with access to:
|
|
137
|
-
# - Previous node's result (convenience: ctx.content)
|
|
138
|
-
# - Original user prompt (ctx.original_prompt)
|
|
139
|
-
# - All previous node results (ctx.all_results[:node_name])
|
|
140
|
-
# - Current node metadata (ctx.node_name, ctx.dependencies)
|
|
141
|
-
#
|
|
142
|
-
# Can also be used for side effects (logging, file I/O) since the block
|
|
143
|
-
# runs at execution time, not declaration time.
|
|
144
|
-
#
|
|
145
|
-
# **Control Flow**: Return a hash with special keys to control execution:
|
|
146
|
-
# - `skip_execution: true` - Skip node's LLM execution, return content immediately
|
|
147
|
-
# - `halt_workflow: true` - Halt entire workflow with content as final result
|
|
148
|
-
# - `goto_node: :node_name` - Jump to different node with content as input
|
|
149
|
-
#
|
|
150
|
-
# @yield [NodeContext] Context with previous results and metadata
|
|
151
|
-
# @return [String, Hash] Transformed input OR control hash
|
|
152
|
-
#
|
|
153
|
-
# @example Access previous result and original prompt
|
|
154
|
-
# input do |ctx|
|
|
155
|
-
# # Convenience accessor
|
|
156
|
-
# previous_content = ctx.content
|
|
157
|
-
#
|
|
158
|
-
# # Access original prompt
|
|
159
|
-
# "Original: #{ctx.original_prompt}\nPrevious: #{previous_content}"
|
|
160
|
-
# end
|
|
161
|
-
#
|
|
162
|
-
# @example Access results from specific nodes
|
|
163
|
-
# input do |ctx|
|
|
164
|
-
# plan = ctx.all_results[:planning].content
|
|
165
|
-
# design = ctx.all_results[:design].content
|
|
166
|
-
#
|
|
167
|
-
# "Implement based on:\nPlan: #{plan}\nDesign: #{design}"
|
|
168
|
-
# end
|
|
169
|
-
#
|
|
170
|
-
# @example Skip execution (caching) - using return
|
|
171
|
-
# input do |ctx|
|
|
172
|
-
# cached = check_cache(ctx.content)
|
|
173
|
-
# return ctx.skip_execution(content: cached) if cached
|
|
174
|
-
# ctx.content
|
|
175
|
-
# end
|
|
176
|
-
#
|
|
177
|
-
# @example Halt workflow (validation) - using return
|
|
178
|
-
# input do |ctx|
|
|
179
|
-
# if ctx.content.length > 10000
|
|
180
|
-
# # Halt entire workflow - return works safely!
|
|
181
|
-
# return ctx.halt_workflow(content: "ERROR: Input too long")
|
|
182
|
-
# end
|
|
183
|
-
# ctx.content
|
|
184
|
-
# end
|
|
185
|
-
#
|
|
186
|
-
# @example Jump to different node (conditional routing) - using return
|
|
187
|
-
# input do |ctx|
|
|
188
|
-
# if ctx.content.include?("NEEDS_REVIEW")
|
|
189
|
-
# # Jump to review node instead - return works safely!
|
|
190
|
-
# return ctx.goto_node(:review, content: ctx.content)
|
|
191
|
-
# end
|
|
192
|
-
# ctx.content
|
|
193
|
-
# end
|
|
194
|
-
#
|
|
195
|
-
# @note The input block is automatically converted to a lambda, which means
|
|
196
|
-
# return statements work safely and only exit the transformer, not the
|
|
197
|
-
# entire program. This allows natural control flow patterns.
|
|
198
|
-
def input(&block)
|
|
199
|
-
@input_transformer = ProcHelpers.to_lambda(block)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Set input transformer as bash command (YAML API)
|
|
203
|
-
#
|
|
204
|
-
# The command receives NodeContext as JSON on STDIN and outputs transformed content.
|
|
205
|
-
#
|
|
206
|
-
# **Exit codes:**
|
|
207
|
-
# - 0: Success, use STDOUT as transformed content
|
|
208
|
-
# - 1: Skip node execution, use current_input unchanged (STDOUT ignored)
|
|
209
|
-
# - 2: Halt workflow with error, show STDERR (STDOUT ignored)
|
|
210
|
-
#
|
|
211
|
-
# @param command [String] Bash command to execute
|
|
212
|
-
# @param timeout [Integer] Timeout in seconds (default: 60)
|
|
213
|
-
# @return [void]
|
|
214
|
-
#
|
|
215
|
-
# @example
|
|
216
|
-
# input_command("scripts/validate.sh", timeout: 30)
|
|
217
|
-
def input_command(command, timeout: TransformerExecutor::DEFAULT_TIMEOUT)
|
|
218
|
-
@input_transformer_command = { command: command, timeout: timeout }
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# Define output transformer for this node
|
|
222
|
-
#
|
|
223
|
-
# The transformer receives a NodeContext object with access to:
|
|
224
|
-
# - Current node's result (convenience: ctx.content)
|
|
225
|
-
# - Original user prompt (ctx.original_prompt)
|
|
226
|
-
# - All completed node results (ctx.all_results[:node_name])
|
|
227
|
-
# - Current node metadata (ctx.node_name)
|
|
228
|
-
#
|
|
229
|
-
# Can also be used for side effects (logging, file I/O) since the block
|
|
230
|
-
# runs at execution time, not declaration time.
|
|
231
|
-
#
|
|
232
|
-
# **Control Flow**: Return a hash with special keys to control execution:
|
|
233
|
-
# - `halt_workflow: true` - Halt entire workflow with content as final result
|
|
234
|
-
# - `goto_node: :node_name` - Jump to different node with content as input
|
|
235
|
-
#
|
|
236
|
-
# @yield [NodeContext] Context with current result and metadata
|
|
237
|
-
# @return [String, Hash] Transformed output OR control hash
|
|
238
|
-
#
|
|
239
|
-
# @example Transform and save to file
|
|
240
|
-
# output do |ctx|
|
|
241
|
-
# # Side effect: save to file
|
|
242
|
-
# File.write("results/plan.txt", ctx.content)
|
|
243
|
-
#
|
|
244
|
-
# # Return transformed output for next node
|
|
245
|
-
# "Key decisions: #{extract_decisions(ctx.content)}"
|
|
246
|
-
# end
|
|
247
|
-
#
|
|
248
|
-
# @example Access original prompt
|
|
249
|
-
# output do |ctx|
|
|
250
|
-
# # Include original context in output
|
|
251
|
-
# "Task: #{ctx.original_prompt}\nResult: #{ctx.content}"
|
|
252
|
-
# end
|
|
253
|
-
#
|
|
254
|
-
# @example Halt workflow (convergence check) - using return
|
|
255
|
-
# output do |ctx|
|
|
256
|
-
# return ctx.halt_workflow(content: ctx.content) if converged?(ctx.content)
|
|
257
|
-
# ctx.content
|
|
258
|
-
# end
|
|
259
|
-
#
|
|
260
|
-
# @example Jump to different node (conditional routing) - using return
|
|
261
|
-
# output do |ctx|
|
|
262
|
-
# if needs_revision?(ctx.content)
|
|
263
|
-
# # Go back to revision node - return works safely!
|
|
264
|
-
# return ctx.goto_node(:revision, content: ctx.content)
|
|
265
|
-
# end
|
|
266
|
-
# ctx.content
|
|
267
|
-
# end
|
|
268
|
-
#
|
|
269
|
-
# @note The output block is automatically converted to a lambda, which means
|
|
270
|
-
# return statements work safely and only exit the transformer, not the
|
|
271
|
-
# entire program. This allows natural control flow patterns.
|
|
272
|
-
def output(&block)
|
|
273
|
-
@output_transformer = ProcHelpers.to_lambda(block)
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
# Set output transformer as bash command (YAML API)
|
|
277
|
-
#
|
|
278
|
-
# The command receives NodeContext as JSON on STDIN and outputs transformed content.
|
|
279
|
-
#
|
|
280
|
-
# **Exit codes:**
|
|
281
|
-
# - 0: Success, use STDOUT as transformed content
|
|
282
|
-
# - 1: Pass through unchanged, use result.content (STDOUT ignored)
|
|
283
|
-
# - 2: Halt workflow with error, show STDERR (STDOUT ignored)
|
|
284
|
-
#
|
|
285
|
-
# @param command [String] Bash command to execute
|
|
286
|
-
# @param timeout [Integer] Timeout in seconds (default: 60)
|
|
287
|
-
# @return [void]
|
|
288
|
-
#
|
|
289
|
-
# @example
|
|
290
|
-
# output_command("scripts/format.sh", timeout: 30)
|
|
291
|
-
def output_command(command, timeout: TransformerExecutor::DEFAULT_TIMEOUT)
|
|
292
|
-
@output_transformer_command = { command: command, timeout: timeout }
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
# Check if node has any input transformer (block or command)
|
|
296
|
-
#
|
|
297
|
-
# @return [Boolean]
|
|
298
|
-
def has_input_transformer?
|
|
299
|
-
@input_transformer || @input_transformer_command
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# Check if node has any output transformer (block or command)
|
|
303
|
-
#
|
|
304
|
-
# @return [Boolean]
|
|
305
|
-
def has_output_transformer?
|
|
306
|
-
@output_transformer || @output_transformer_command
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# Transform input using configured transformer (block or command)
|
|
310
|
-
#
|
|
311
|
-
# Executes either Ruby block or bash command transformer.
|
|
312
|
-
#
|
|
313
|
-
# **Ruby block return values:**
|
|
314
|
-
# - String: Transformed content
|
|
315
|
-
# - Hash with `skip_execution: true`: Skip node execution
|
|
316
|
-
# - Hash with `halt_workflow: true`: Halt entire workflow
|
|
317
|
-
# - Hash with `goto_node: :name`: Jump to different node
|
|
318
|
-
#
|
|
319
|
-
# **Exit code behavior (bash commands only):**
|
|
320
|
-
# - Exit 0: Use STDOUT as transformed content
|
|
321
|
-
# - Exit 1: Skip node execution, use current_input unchanged (STDOUT ignored)
|
|
322
|
-
# - Exit 2: Halt workflow with error (STDOUT ignored)
|
|
323
|
-
#
|
|
324
|
-
# @param context [NodeContext] Context with previous results and metadata
|
|
325
|
-
# @param current_input [String] Fallback content for exit 1 (skip), also used for halt error context
|
|
326
|
-
# @return [String, Hash] Transformed input OR control hash (skip_execution, halt_workflow, goto_node)
|
|
327
|
-
# @raise [ConfigurationError] If bash transformer halts workflow (exit 2)
|
|
328
|
-
def transform_input(context, current_input:)
|
|
329
|
-
# No transformer configured: return content as-is
|
|
330
|
-
return context.content unless @input_transformer || @input_transformer_command
|
|
331
|
-
|
|
332
|
-
# Ruby block transformer
|
|
333
|
-
# Ruby blocks can return String (transformed content) OR Hash (control flow)
|
|
334
|
-
if @input_transformer
|
|
335
|
-
result = @input_transformer.call(context)
|
|
336
|
-
|
|
337
|
-
# If hash, validate control flow keys
|
|
338
|
-
if result.is_a?(Hash)
|
|
339
|
-
validate_transformer_hash(result, :input)
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
return result
|
|
343
|
-
end
|
|
344
|
-
|
|
345
|
-
# Bash command transformer
|
|
346
|
-
# Bash commands use exit codes to control behavior:
|
|
347
|
-
# - Exit 0: Success, use STDOUT as transformed content
|
|
348
|
-
# - Exit 1: Skip node execution, use current_input unchanged (STDOUT ignored)
|
|
349
|
-
# - Exit 2: Halt workflow with error (STDOUT ignored)
|
|
350
|
-
if @input_transformer_command
|
|
351
|
-
result = TransformerExecutor.execute(
|
|
352
|
-
command: @input_transformer_command[:command],
|
|
353
|
-
context: context,
|
|
354
|
-
event: "input",
|
|
355
|
-
node_name: @name,
|
|
356
|
-
fallback_content: current_input, # Used for exit 1 (skip)
|
|
357
|
-
timeout: @input_transformer_command[:timeout],
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
# Handle transformer result based on exit code
|
|
361
|
-
if result.halt?
|
|
362
|
-
# Exit 2: Halt workflow with error
|
|
363
|
-
raise ConfigurationError,
|
|
364
|
-
"Input transformer halted workflow for node '#{@name}': #{result.error_message}"
|
|
365
|
-
elsif result.skip_execution?
|
|
366
|
-
# Exit 1: Skip node execution, return skip hash
|
|
367
|
-
# Content is current_input unchanged (STDOUT was ignored)
|
|
368
|
-
{ skip_execution: true, content: result.content }
|
|
369
|
-
else
|
|
370
|
-
# Exit 0: Return transformed content from STDOUT
|
|
371
|
-
result.content
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
# Transform output using configured transformer (block or command)
|
|
377
|
-
#
|
|
378
|
-
# Executes either Ruby block or bash command transformer.
|
|
379
|
-
#
|
|
380
|
-
# **Ruby block return values:**
|
|
381
|
-
# - String: Transformed content
|
|
382
|
-
# - Hash with `halt_workflow: true`: Halt entire workflow
|
|
383
|
-
# - Hash with `goto_node: :name`: Jump to different node
|
|
384
|
-
#
|
|
385
|
-
# **Exit code behavior (bash commands only):**
|
|
386
|
-
# - Exit 0: Use STDOUT as transformed content
|
|
387
|
-
# - Exit 1: Pass through unchanged, use result.content (STDOUT ignored)
|
|
388
|
-
# - Exit 2: Halt workflow with error (STDOUT ignored)
|
|
389
|
-
#
|
|
390
|
-
# @param context [NodeContext] Context with current result and metadata
|
|
391
|
-
# @return [String, Hash] Transformed output OR control hash (halt_workflow, goto_node)
|
|
392
|
-
# @raise [ConfigurationError] If bash transformer halts workflow (exit 2)
|
|
393
|
-
def transform_output(context)
|
|
394
|
-
# No transformer configured: return content as-is
|
|
395
|
-
return context.content unless @output_transformer || @output_transformer_command
|
|
396
|
-
|
|
397
|
-
# Ruby block transformer
|
|
398
|
-
# Ruby blocks can return String (transformed content) OR Hash (control flow)
|
|
399
|
-
if @output_transformer
|
|
400
|
-
result = @output_transformer.call(context)
|
|
401
|
-
|
|
402
|
-
# If hash, validate control flow keys
|
|
403
|
-
if result.is_a?(Hash)
|
|
404
|
-
validate_transformer_hash(result, :output)
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
return result
|
|
408
|
-
end
|
|
409
|
-
|
|
410
|
-
# Bash command transformer
|
|
411
|
-
# Bash commands use exit codes to control behavior:
|
|
412
|
-
# - Exit 0: Success, use STDOUT as transformed content
|
|
413
|
-
# - Exit 1: Pass through unchanged, use result.content (STDOUT ignored)
|
|
414
|
-
# - Exit 2: Halt workflow with error from STDERR (STDOUT ignored)
|
|
415
|
-
if @output_transformer_command
|
|
416
|
-
result = TransformerExecutor.execute(
|
|
417
|
-
command: @output_transformer_command[:command],
|
|
418
|
-
context: context,
|
|
419
|
-
event: "output",
|
|
420
|
-
node_name: @name,
|
|
421
|
-
fallback_content: context.content, # result.content for exit 1
|
|
422
|
-
timeout: @output_transformer_command[:timeout],
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
# Handle transformer result based on exit code
|
|
426
|
-
if result.halt?
|
|
427
|
-
# Exit 2: Halt workflow with error
|
|
428
|
-
raise ConfigurationError,
|
|
429
|
-
"Output transformer halted workflow for node '#{@name}': #{result.error_message}"
|
|
430
|
-
else
|
|
431
|
-
# Exit 0: Return transformed content from STDOUT
|
|
432
|
-
# Exit 1: Return fallback (result.content unchanged)
|
|
433
|
-
result.content
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
# Get the lead agent for this node
|
|
439
|
-
#
|
|
440
|
-
# @return [Symbol] Lead agent name
|
|
441
|
-
def lead_agent
|
|
442
|
-
@lead_override || @agent_configs.first&.dig(:agent)
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
# Check if this is an agent-less (computation-only) node
|
|
446
|
-
#
|
|
447
|
-
# Agent-less nodes run pure Ruby code without LLM execution.
|
|
448
|
-
# They must have at least one transformer (input or output).
|
|
449
|
-
#
|
|
450
|
-
# @return [Boolean]
|
|
451
|
-
def agent_less?
|
|
452
|
-
@agent_configs.empty?
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
# Validate node configuration
|
|
456
|
-
#
|
|
457
|
-
# Also auto-adds agents that are referenced in delegates_to but not explicitly declared.
|
|
458
|
-
# This allows writing: agent(:backend).delegates_to(:verifier)
|
|
459
|
-
# without needing: agent(:verifier)
|
|
460
|
-
#
|
|
461
|
-
# @return [void]
|
|
462
|
-
# @raise [ConfigurationError] If configuration is invalid
|
|
463
|
-
def validate!
|
|
464
|
-
# Auto-add agents mentioned in delegates_to but not explicitly declared
|
|
465
|
-
auto_add_delegate_agents
|
|
466
|
-
|
|
467
|
-
# Agent-less nodes (pure computation) are allowed but need transformers
|
|
468
|
-
if @agent_configs.empty?
|
|
469
|
-
unless has_input_transformer? || has_output_transformer?
|
|
470
|
-
raise ConfigurationError,
|
|
471
|
-
"Agent-less node '#{@name}' must have at least one transformer (input or output). " \
|
|
472
|
-
"Either add agents with agent(:name) or add input/output transformers."
|
|
473
|
-
end
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
# If has agents, validate lead override
|
|
477
|
-
if @lead_override && !@agent_configs.any? { |ac| ac[:agent] == @lead_override }
|
|
478
|
-
raise ConfigurationError,
|
|
479
|
-
"Node '#{@name}' lead agent '#{@lead_override}' not found in node's agents"
|
|
480
|
-
end
|
|
481
|
-
end
|
|
482
|
-
|
|
483
|
-
private
|
|
484
|
-
|
|
485
|
-
# Validate transformer hash return value
|
|
486
|
-
#
|
|
487
|
-
# Ensures hash has valid control flow keys and required content field.
|
|
488
|
-
#
|
|
489
|
-
# @param hash [Hash] Hash returned from transformer
|
|
490
|
-
# @param transformer_type [Symbol] :input or :output
|
|
491
|
-
# @return [void]
|
|
492
|
-
# @raise [ConfigurationError] If hash is invalid
|
|
493
|
-
def validate_transformer_hash(hash, transformer_type)
|
|
494
|
-
# Valid control keys
|
|
495
|
-
valid_keys = if transformer_type == :input
|
|
496
|
-
[:skip_execution, :halt_workflow, :goto_node, :content]
|
|
497
|
-
else
|
|
498
|
-
[:halt_workflow, :goto_node, :content]
|
|
499
|
-
end
|
|
500
|
-
|
|
501
|
-
# Check for invalid keys
|
|
502
|
-
invalid_keys = hash.keys - valid_keys
|
|
503
|
-
if invalid_keys.any?
|
|
504
|
-
raise ConfigurationError,
|
|
505
|
-
"Invalid #{transformer_type} transformer hash keys: #{invalid_keys.join(", ")}. " \
|
|
506
|
-
"Valid keys: #{valid_keys.join(", ")}"
|
|
507
|
-
end
|
|
508
|
-
|
|
509
|
-
# Ensure content is present
|
|
510
|
-
unless hash.key?(:content)
|
|
511
|
-
raise ConfigurationError,
|
|
512
|
-
"#{transformer_type.capitalize} transformer hash must include :content key"
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
# Ensure only one control key
|
|
516
|
-
control_keys = hash.keys & [:skip_execution, :halt_workflow, :goto_node]
|
|
517
|
-
if control_keys.size > 1
|
|
518
|
-
raise ConfigurationError,
|
|
519
|
-
"#{transformer_type.capitalize} transformer hash can only have one control key, got: #{control_keys.join(", ")}"
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# Validate goto_node has valid node name
|
|
523
|
-
if hash[:goto_node] && !hash[:goto_node].is_a?(Symbol)
|
|
524
|
-
raise ConfigurationError,
|
|
525
|
-
"goto_node value must be a Symbol, got: #{hash[:goto_node].class}"
|
|
526
|
-
end
|
|
527
|
-
end
|
|
528
|
-
|
|
529
|
-
# Auto-add agents that are mentioned in delegates_to but not explicitly declared
|
|
530
|
-
#
|
|
531
|
-
# This allows:
|
|
532
|
-
# agent(:backend).delegates_to(:tester)
|
|
533
|
-
# Without needing:
|
|
534
|
-
# agent(:tester)
|
|
535
|
-
#
|
|
536
|
-
# The tester agent is automatically added to the node with no delegation
|
|
537
|
-
# and reset_context: true (fresh context by default).
|
|
538
|
-
#
|
|
539
|
-
# @return [void]
|
|
540
|
-
def auto_add_delegate_agents
|
|
541
|
-
# Collect all agents mentioned in delegates_to
|
|
542
|
-
all_delegates = @agent_configs.flat_map { |ac| ac[:delegates_to] }.uniq
|
|
543
|
-
|
|
544
|
-
# Find delegates that aren't explicitly declared
|
|
545
|
-
declared_agents = @agent_configs.map { |ac| ac[:agent] }
|
|
546
|
-
missing_delegates = all_delegates - declared_agents
|
|
547
|
-
|
|
548
|
-
# Auto-add missing delegates with empty delegation and default reset_context
|
|
549
|
-
missing_delegates.each do |delegate_name|
|
|
550
|
-
@agent_configs << { agent: delegate_name, delegates_to: [], reset_context: true }
|
|
551
|
-
end
|
|
552
|
-
end
|
|
553
|
-
end
|
|
554
|
-
end
|
|
555
|
-
end
|