claude_swarm 1.0.4 → 1.0.5
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/CHANGELOG.md +15 -0
- data/Rakefile +4 -4
- data/docs/v2/CHANGELOG.swarm_cli.md +9 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +19 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +45 -0
- data/docs/v2/guides/complete-tutorial.md +113 -1
- data/docs/v2/reference/ruby-dsl.md +138 -5
- data/docs/v2/reference/swarm_memory_technical_details.md +2090 -0
- data/lib/claude_swarm/cli.rb +9 -11
- data/lib/claude_swarm/commands/ps.rb +1 -2
- data/lib/claude_swarm/configuration.rb +2 -3
- data/lib/claude_swarm/orchestrator.rb +43 -44
- data/lib/claude_swarm/system_utils.rb +4 -4
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm.rb +4 -9
- data/lib/swarm_cli/commands/mcp_tools.rb +3 -3
- data/lib/swarm_cli/config_loader.rb +11 -10
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_cli.rb +2 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +0 -12
- data/lib/swarm_memory/core/storage.rb +66 -6
- data/lib/swarm_memory/integration/sdk_plugin.rb +14 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +4 -0
- data/lib/swarm_memory/tools/memory_edit.rb +1 -0
- data/lib/swarm_memory/tools/memory_glob.rb +24 -1
- data/lib/swarm_memory/tools/memory_write.rb +2 -2
- data/lib/swarm_memory/version.rb +1 -1
- data/lib/swarm_memory.rb +2 -0
- data/lib/swarm_sdk/agent/chat.rb +1 -1
- data/lib/swarm_sdk/agent/definition.rb +17 -1
- data/lib/swarm_sdk/node/agent_config.rb +7 -2
- data/lib/swarm_sdk/node/builder.rb +130 -35
- data/lib/swarm_sdk/node_context.rb +75 -0
- data/lib/swarm_sdk/node_orchestrator.rb +219 -12
- data/lib/swarm_sdk/plugin.rb +73 -1
- data/lib/swarm_sdk/result.rb +32 -6
- data/lib/swarm_sdk/swarm/builder.rb +1 -0
- data/lib/swarm_sdk/tools/delegate.rb +2 -2
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk.rb +3 -7
- data/memory/corpus-self-reflection/.lock +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.md +11 -0
- data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.yml +23 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.md +20 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.md +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.md +18 -0
- data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.yml +21 -0
- data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.md +30 -0
- data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.yml +8 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.md +21 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.yml +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.md +18 -0
- data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.yml +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.md +23 -0
- data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.yml +23 -0
- data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.md +17 -0
- data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.md +18 -0
- data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.md +15 -0
- data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.md +9 -0
- data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.yml +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.md +13 -0
- data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.yml +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.md +25 -0
- data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.yml +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.md +26 -0
- data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.yml +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.md +17 -0
- data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.md +14 -0
- data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.yml +21 -0
- data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.md +13 -0
- data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.yml +23 -0
- data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.md +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.yml +23 -0
- data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.md +39 -0
- data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.yml +8 -0
- data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.md +23 -0
- data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.yml +24 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.md +15 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.md +12 -0
- data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.yml +23 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.md +15 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.yml +8 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.md +28 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.yml +8 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.md +19 -0
- data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.yml +22 -0
- data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.emb +0 -0
- data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.md +19 -0
- data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.yml +21 -0
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.md +25 -0
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.yml +22 -0
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.emb +8 -0
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.md +30 -0
- data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.yml +22 -0
- data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.md +21 -0
- data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.yml +22 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.md +37 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.yml +8 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.md +24 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.yml +24 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.md +27 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.yml +24 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.md +26 -0
- data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.yml +23 -0
- data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.md +37 -0
- data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.yml +25 -0
- data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.md +21 -0
- data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.yml +23 -0
- data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.emb +0 -0
- data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.md +25 -0
- data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.yml +8 -0
- data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.emb +0 -0
- data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.md +11 -0
- data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.yml +21 -0
- data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.emb +0 -0
- data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.md +19 -0
- data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.yml +21 -0
- data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.emb +0 -0
- data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.md +26 -0
- data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.yml +20 -0
- data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.emb +0 -0
- data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.md +23 -0
- data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.yml +21 -0
- data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.emb +0 -0
- data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.md +19 -0
- data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.yml +22 -0
- data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.emb +0 -0
- data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.md +21 -0
- data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.yml +22 -0
- data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.emb +0 -0
- data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.md +28 -0
- data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.yml +23 -0
- data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.emb +0 -0
- data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.md +21 -0
- data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.yml +20 -0
- data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.emb +0 -0
- data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.md +21 -0
- data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.yml +25 -0
- data/memory/corpus-self-reflection/experience/local-change-without-continuity.emb +0 -0
- data/memory/corpus-self-reflection/experience/local-change-without-continuity.md +30 -0
- data/memory/corpus-self-reflection/experience/local-change-without-continuity.yml +22 -0
- data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.emb +0 -0
- data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.md +21 -0
- data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.yml +20 -0
- data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.emb +0 -0
- data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.md +25 -0
- data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.yml +23 -0
- data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.emb +0 -0
- data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.md +32 -0
- data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.yml +22 -0
- data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.emb +0 -0
- data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.md +21 -0
- data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.yml +21 -0
- data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.emb +0 -0
- data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.md +21 -0
- data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.yml +21 -0
- data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.emb +0 -0
- data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.md +22 -0
- data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.yml +22 -0
- data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.emb +0 -0
- data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.md +28 -0
- data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.yml +24 -0
- data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.emb +0 -0
- data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.md +17 -0
- data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.yml +20 -0
- data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.emb +0 -0
- data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.md +9 -0
- data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.yml +22 -0
- metadata +172 -2
    
        data/lib/swarm_sdk/agent/chat.rb
    CHANGED
    
    | @@ -415,7 +415,7 @@ module SwarmSDK | |
| 415 415 |  | 
| 416 416 | 
             
                    # Handle nil response from provider (malformed API response)
         | 
| 417 417 | 
             
                    if response.nil?
         | 
| 418 | 
            -
                      raise  | 
| 418 | 
            +
                      raise StandardError, "Provider returned nil response. This usually indicates a malformed API response " \
         | 
| 419 419 | 
             
                        "that couldn't be parsed.\n\n" \
         | 
| 420 420 | 
             
                        "Provider: #{@provider.class.name}\n" \
         | 
| 421 421 | 
             
                        "API Base: #{@provider.api_base}\n" \
         | 
| @@ -158,10 +158,12 @@ module SwarmSDK | |
| 158 158 | 
             
                  end
         | 
| 159 159 |  | 
| 160 160 | 
             
                  def to_h
         | 
| 161 | 
            -
                     | 
| 161 | 
            +
                    # Core SDK configuration (always serialized)
         | 
| 162 | 
            +
                    base_config = {
         | 
| 162 163 | 
             
                      name: @name,
         | 
| 163 164 | 
             
                      description: @description,
         | 
| 164 165 | 
             
                      model: SwarmSDK::Models.resolve_alias(@model), # Resolve model aliases
         | 
| 166 | 
            +
                      context_window: @context_window,
         | 
| 165 167 | 
             
                      directory: @directory,
         | 
| 166 168 | 
             
                      tools: @tools,
         | 
| 167 169 | 
             
                      delegates_to: @delegates_to,
         | 
| @@ -179,7 +181,21 @@ module SwarmSDK | |
| 179 181 | 
             
                      assume_model_exists: @assume_model_exists,
         | 
| 180 182 | 
             
                      max_concurrent_tools: @max_concurrent_tools,
         | 
| 181 183 | 
             
                      hooks: @hooks,
         | 
| 184 | 
            +
                      # Permissions are core SDK functionality (not plugin-specific)
         | 
| 185 | 
            +
                      default_permissions: @default_permissions,
         | 
| 186 | 
            +
                      permissions: @agent_permissions,
         | 
| 182 187 | 
             
                    }.compact
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    # Allow plugins to contribute their config for serialization
         | 
| 190 | 
            +
                    # This enables plugin features (memory, skills, etc.) to be preserved
         | 
| 191 | 
            +
                    # when cloning agents without SwarmSDK knowing about plugin-specific fields
         | 
| 192 | 
            +
                    plugin_configs = SwarmSDK::PluginRegistry.all.map do |plugin|
         | 
| 193 | 
            +
                      plugin.serialize_config(agent_definition: self)
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                    # Merge plugin configs into base config
         | 
| 197 | 
            +
                    # Later plugins override earlier ones if they have conflicting keys
         | 
| 198 | 
            +
                    plugin_configs.reduce(base_config) { |acc, config| acc.merge(config) }
         | 
| 183 199 | 
             
                  end
         | 
| 184 200 |  | 
| 185 201 | 
             
                  # Validate agent configuration and return warnings (non-fatal issues)
         | 
| @@ -6,19 +6,24 @@ module SwarmSDK | |
| 6 6 | 
             
                #
         | 
| 7 7 | 
             
                # This class enables the chainable syntax:
         | 
| 8 8 | 
             
                #   agent(:backend).delegates_to(:tester, :database)
         | 
| 9 | 
            +
                #   agent(:backend, reset_context: false)  # Preserve context across nodes
         | 
| 9 10 | 
             
                #
         | 
| 10 11 | 
             
                # @example Basic delegation
         | 
| 11 12 | 
             
                #   agent(:backend).delegates_to(:tester)
         | 
| 12 13 | 
             
                #
         | 
| 13 14 | 
             
                # @example No delegation (solo agent)
         | 
| 14 15 | 
             
                #   agent(:planner)
         | 
| 16 | 
            +
                #
         | 
| 17 | 
            +
                # @example Preserve agent context
         | 
| 18 | 
            +
                #   agent(:architect, reset_context: false)
         | 
| 15 19 | 
             
                class AgentConfig
         | 
| 16 20 | 
             
                  attr_reader :agent_name
         | 
| 17 21 |  | 
| 18 | 
            -
                  def initialize(agent_name, node_builder)
         | 
| 22 | 
            +
                  def initialize(agent_name, node_builder, reset_context: true)
         | 
| 19 23 | 
             
                    @agent_name = agent_name
         | 
| 20 24 | 
             
                    @node_builder = node_builder
         | 
| 21 25 | 
             
                    @delegates_to = []
         | 
| 26 | 
            +
                    @reset_context = reset_context
         | 
| 22 27 | 
             
                    @finalized = false
         | 
| 23 28 | 
             
                  end
         | 
| 24 29 |  | 
| @@ -41,7 +46,7 @@ module SwarmSDK | |
| 41 46 | 
             
                  def finalize
         | 
| 42 47 | 
             
                    return if @finalized
         | 
| 43 48 |  | 
| 44 | 
            -
                    @node_builder.register_agent(@agent_name, @delegates_to)
         | 
| 49 | 
            +
                    @node_builder.register_agent(@agent_name, @delegates_to, @reset_context)
         | 
| 45 50 | 
             
                    @finalized = true
         | 
| 46 51 | 
             
                  end
         | 
| 47 52 | 
             
                end
         | 
| @@ -46,7 +46,11 @@ module SwarmSDK | |
| 46 46 | 
             
                  # Returns an AgentConfig object that supports fluent delegation syntax.
         | 
| 47 47 | 
             
                  # If delegates_to is not called, the agent is registered with no delegation.
         | 
| 48 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 | 
            +
                  #
         | 
| 49 52 | 
             
                  # @param name [Symbol] Agent name
         | 
| 53 | 
            +
                  # @param reset_context [Boolean] Whether to reset agent context (default: true)
         | 
| 50 54 | 
             
                  # @return [AgentConfig] Fluent configuration object
         | 
| 51 55 | 
             
                  #
         | 
| 52 56 | 
             
                  # @example With delegation
         | 
| @@ -54,12 +58,15 @@ module SwarmSDK | |
| 54 58 | 
             
                  #
         | 
| 55 59 | 
             
                  # @example Without delegation
         | 
| 56 60 | 
             
                  #   agent(:planner)
         | 
| 57 | 
            -
                   | 
| 58 | 
            -
             | 
| 61 | 
            +
                  #
         | 
| 62 | 
            +
                  # @example Preserve context across nodes
         | 
| 63 | 
            +
                  #   agent(:architect, reset_context: false)
         | 
| 64 | 
            +
                  def agent(name, reset_context: true)
         | 
| 65 | 
            +
                    config = AgentConfig.new(name, self, reset_context: reset_context)
         | 
| 59 66 |  | 
| 60 67 | 
             
                    # Register immediately with empty delegation
         | 
| 61 68 | 
             
                    # If delegates_to is called later, it will update this
         | 
| 62 | 
            -
                    register_agent(name, [])
         | 
| 69 | 
            +
                    register_agent(name, [], reset_context)
         | 
| 63 70 |  | 
| 64 71 | 
             
                    config
         | 
| 65 72 | 
             
                  end
         | 
| @@ -68,17 +75,19 @@ module SwarmSDK | |
| 68 75 | 
             
                  #
         | 
| 69 76 | 
             
                  # @param agent_name [Symbol] Agent name
         | 
| 70 77 | 
             
                  # @param delegates_to [Array<Symbol>] Delegation targets
         | 
| 78 | 
            +
                  # @param reset_context [Boolean] Whether to reset agent context
         | 
| 71 79 | 
             
                  # @return [void]
         | 
| 72 | 
            -
                  def register_agent(agent_name, delegates_to)
         | 
| 80 | 
            +
                  def register_agent(agent_name, delegates_to, reset_context = true)
         | 
| 73 81 | 
             
                    # Check if agent already registered
         | 
| 74 82 | 
             
                    existing = @agent_configs.find { |ac| ac[:agent] == agent_name }
         | 
| 75 83 |  | 
| 76 84 | 
             
                    if existing
         | 
| 77 | 
            -
                      # Update delegation (happens when delegates_to is called after agent())
         | 
| 85 | 
            +
                      # Update delegation and reset_context (happens when delegates_to is called after agent())
         | 
| 78 86 | 
             
                      existing[:delegates_to] = delegates_to
         | 
| 87 | 
            +
                      existing[:reset_context] = reset_context
         | 
| 79 88 | 
             
                    else
         | 
| 80 89 | 
             
                      # Add new agent configuration
         | 
| 81 | 
            -
                      @agent_configs << { agent: agent_name, delegates_to: delegates_to }
         | 
| 90 | 
            +
                      @agent_configs << { agent: agent_name, delegates_to: delegates_to, reset_context: reset_context }
         | 
| 82 91 | 
             
                    end
         | 
| 83 92 | 
             
                  end
         | 
| 84 93 |  | 
| @@ -120,12 +129,13 @@ module SwarmSDK | |
| 120 129 | 
             
                  # Can also be used for side effects (logging, file I/O) since the block
         | 
| 121 130 | 
             
                  # runs at execution time, not declaration time.
         | 
| 122 131 | 
             
                  #
         | 
| 123 | 
            -
                  # ** | 
| 124 | 
            -
                  #  | 
| 125 | 
            -
                  #  | 
| 132 | 
            +
                  # **Control Flow**: Return a hash with special keys to control execution:
         | 
| 133 | 
            +
                  # - `skip_execution: true` - Skip node's LLM execution, return content immediately
         | 
| 134 | 
            +
                  # - `halt_workflow: true` - Halt entire workflow with content as final result
         | 
| 135 | 
            +
                  # - `goto_node: :node_name` - Jump to different node with content as input
         | 
| 126 136 | 
             
                  #
         | 
| 127 137 | 
             
                  # @yield [NodeContext] Context with previous results and metadata
         | 
| 128 | 
            -
                  # @return [String, Hash] Transformed input OR  | 
| 138 | 
            +
                  # @return [String, Hash] Transformed input OR control hash
         | 
| 129 139 | 
             
                  #
         | 
| 130 140 | 
             
                  # @example Access previous result and original prompt
         | 
| 131 141 | 
             
                  #   input do |ctx|
         | 
| @@ -147,22 +157,26 @@ module SwarmSDK | |
| 147 157 | 
             
                  # @example Skip execution (caching)
         | 
| 148 158 | 
             
                  #   input do |ctx|
         | 
| 149 159 | 
             
                  #     cached = check_cache(ctx.content)
         | 
| 150 | 
            -
                  #     if cached
         | 
| 151 | 
            -
                  # | 
| 152 | 
            -
                  #       { skip_execution: true, content: cached }
         | 
| 153 | 
            -
                  #     else
         | 
| 154 | 
            -
                  #       ctx.content
         | 
| 155 | 
            -
                  #     end
         | 
| 160 | 
            +
                  #     return ctx.skip_execution(content: cached) if cached
         | 
| 161 | 
            +
                  #     ctx.content
         | 
| 156 162 | 
             
                  #   end
         | 
| 157 163 | 
             
                  #
         | 
| 158 | 
            -
                  # @example  | 
| 164 | 
            +
                  # @example Halt workflow (validation)
         | 
| 159 165 | 
             
                  #   input do |ctx|
         | 
| 160 166 | 
             
                  #     if ctx.content.length > 10000
         | 
| 161 | 
            -
                  #       #  | 
| 162 | 
            -
                  #        | 
| 163 | 
            -
                  #      | 
| 164 | 
            -
                  # | 
| 167 | 
            +
                  #       # Halt entire workflow
         | 
| 168 | 
            +
                  #       return ctx.halt_workflow(content: "ERROR: Input too long")
         | 
| 169 | 
            +
                  #     end
         | 
| 170 | 
            +
                  #     ctx.content
         | 
| 171 | 
            +
                  #   end
         | 
| 172 | 
            +
                  #
         | 
| 173 | 
            +
                  # @example Jump to different node (conditional routing)
         | 
| 174 | 
            +
                  #   input do |ctx|
         | 
| 175 | 
            +
                  #     if ctx.content.include?("NEEDS_REVIEW")
         | 
| 176 | 
            +
                  #       # Jump to review node instead
         | 
| 177 | 
            +
                  #       return ctx.goto_node(:review, content: ctx.content)
         | 
| 165 178 | 
             
                  #     end
         | 
| 179 | 
            +
                  #     ctx.content
         | 
| 166 180 | 
             
                  #   end
         | 
| 167 181 | 
             
                  def input(&block)
         | 
| 168 182 | 
             
                    @input_transformer = block
         | 
| @@ -198,8 +212,12 @@ module SwarmSDK | |
| 198 212 | 
             
                  # Can also be used for side effects (logging, file I/O) since the block
         | 
| 199 213 | 
             
                  # runs at execution time, not declaration time.
         | 
| 200 214 | 
             
                  #
         | 
| 215 | 
            +
                  # **Control Flow**: Return a hash with special keys to control execution:
         | 
| 216 | 
            +
                  # - `halt_workflow: true` - Halt entire workflow with content as final result
         | 
| 217 | 
            +
                  # - `goto_node: :node_name` - Jump to different node with content as input
         | 
| 218 | 
            +
                  #
         | 
| 201 219 | 
             
                  # @yield [NodeContext] Context with current result and metadata
         | 
| 202 | 
            -
                  # @return [String] Transformed output
         | 
| 220 | 
            +
                  # @return [String, Hash] Transformed output OR control hash
         | 
| 203 221 | 
             
                  #
         | 
| 204 222 | 
             
                  # @example Transform and save to file
         | 
| 205 223 | 
             
                  #   output do |ctx|
         | 
| @@ -216,12 +234,19 @@ module SwarmSDK | |
| 216 234 | 
             
                  #     "Task: #{ctx.original_prompt}\nResult: #{ctx.content}"
         | 
| 217 235 | 
             
                  #   end
         | 
| 218 236 | 
             
                  #
         | 
| 219 | 
            -
                  # @example  | 
| 237 | 
            +
                  # @example Halt workflow (convergence check)
         | 
| 220 238 | 
             
                  #   output do |ctx|
         | 
| 221 | 
            -
                  #      | 
| 222 | 
            -
                  #      | 
| 239 | 
            +
                  #     return ctx.halt_workflow(content: ctx.content) if converged?(ctx.content)
         | 
| 240 | 
            +
                  #     ctx.content
         | 
| 241 | 
            +
                  #   end
         | 
| 223 242 | 
             
                  #
         | 
| 224 | 
            -
                  # | 
| 243 | 
            +
                  # @example Jump to different node (conditional routing)
         | 
| 244 | 
            +
                  #   output do |ctx|
         | 
| 245 | 
            +
                  #     if needs_revision?(ctx.content)
         | 
| 246 | 
            +
                  #       # Go back to revision node
         | 
| 247 | 
            +
                  #       return ctx.goto_node(:revision, content: ctx.content)
         | 
| 248 | 
            +
                  #     end
         | 
| 249 | 
            +
                  #     ctx.content
         | 
| 225 250 | 
             
                  #   end
         | 
| 226 251 | 
             
                  def output(&block)
         | 
| 227 252 | 
             
                    @output_transformer = block
         | 
| @@ -264,6 +289,12 @@ module SwarmSDK | |
| 264 289 | 
             
                  #
         | 
| 265 290 | 
             
                  # Executes either Ruby block or bash command transformer.
         | 
| 266 291 | 
             
                  #
         | 
| 292 | 
            +
                  # **Ruby block return values:**
         | 
| 293 | 
            +
                  # - String: Transformed content
         | 
| 294 | 
            +
                  # - Hash with `skip_execution: true`: Skip node execution
         | 
| 295 | 
            +
                  # - Hash with `halt_workflow: true`: Halt entire workflow
         | 
| 296 | 
            +
                  # - Hash with `goto_node: :name`: Jump to different node
         | 
| 297 | 
            +
                  #
         | 
| 267 298 | 
             
                  # **Exit code behavior (bash commands only):**
         | 
| 268 299 | 
             
                  # - Exit 0: Use STDOUT as transformed content
         | 
| 269 300 | 
             
                  # - Exit 1: Skip node execution, use current_input unchanged (STDOUT ignored)
         | 
| @@ -271,16 +302,23 @@ module SwarmSDK | |
| 271 302 | 
             
                  #
         | 
| 272 303 | 
             
                  # @param context [NodeContext] Context with previous results and metadata
         | 
| 273 304 | 
             
                  # @param current_input [String] Fallback content for exit 1 (skip), also used for halt error context
         | 
| 274 | 
            -
                  # @return [String, Hash] Transformed input OR  | 
| 305 | 
            +
                  # @return [String, Hash] Transformed input OR control hash (skip_execution, halt_workflow, goto_node)
         | 
| 275 306 | 
             
                  # @raise [ConfigurationError] If bash transformer halts workflow (exit 2)
         | 
| 276 307 | 
             
                  def transform_input(context, current_input:)
         | 
| 277 308 | 
             
                    # No transformer configured: return content as-is
         | 
| 278 309 | 
             
                    return context.content unless @input_transformer || @input_transformer_command
         | 
| 279 310 |  | 
| 280 311 | 
             
                    # Ruby block transformer
         | 
| 281 | 
            -
                    # Ruby blocks can return String (transformed content) OR Hash ( | 
| 312 | 
            +
                    # Ruby blocks can return String (transformed content) OR Hash (control flow)
         | 
| 282 313 | 
             
                    if @input_transformer
         | 
| 283 | 
            -
                       | 
| 314 | 
            +
                      result = @input_transformer.call(context)
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                      # If hash, validate control flow keys
         | 
| 317 | 
            +
                      if result.is_a?(Hash)
         | 
| 318 | 
            +
                        validate_transformer_hash(result, :input)
         | 
| 319 | 
            +
                      end
         | 
| 320 | 
            +
             | 
| 321 | 
            +
                      return result
         | 
| 284 322 | 
             
                    end
         | 
| 285 323 |  | 
| 286 324 | 
             
                    # Bash command transformer
         | 
| @@ -318,22 +356,34 @@ module SwarmSDK | |
| 318 356 | 
             
                  #
         | 
| 319 357 | 
             
                  # Executes either Ruby block or bash command transformer.
         | 
| 320 358 | 
             
                  #
         | 
| 359 | 
            +
                  # **Ruby block return values:**
         | 
| 360 | 
            +
                  # - String: Transformed content
         | 
| 361 | 
            +
                  # - Hash with `halt_workflow: true`: Halt entire workflow
         | 
| 362 | 
            +
                  # - Hash with `goto_node: :name`: Jump to different node
         | 
| 363 | 
            +
                  #
         | 
| 321 364 | 
             
                  # **Exit code behavior (bash commands only):**
         | 
| 322 365 | 
             
                  # - Exit 0: Use STDOUT as transformed content
         | 
| 323 366 | 
             
                  # - Exit 1: Pass through unchanged, use result.content (STDOUT ignored)
         | 
| 324 367 | 
             
                  # - Exit 2: Halt workflow with error (STDOUT ignored)
         | 
| 325 368 | 
             
                  #
         | 
| 326 369 | 
             
                  # @param context [NodeContext] Context with current result and metadata
         | 
| 327 | 
            -
                  # @return [String] Transformed output
         | 
| 370 | 
            +
                  # @return [String, Hash] Transformed output OR control hash (halt_workflow, goto_node)
         | 
| 328 371 | 
             
                  # @raise [ConfigurationError] If bash transformer halts workflow (exit 2)
         | 
| 329 372 | 
             
                  def transform_output(context)
         | 
| 330 373 | 
             
                    # No transformer configured: return content as-is
         | 
| 331 374 | 
             
                    return context.content unless @output_transformer || @output_transformer_command
         | 
| 332 375 |  | 
| 333 376 | 
             
                    # Ruby block transformer
         | 
| 334 | 
            -
                    #  | 
| 377 | 
            +
                    # Ruby blocks can return String (transformed content) OR Hash (control flow)
         | 
| 335 378 | 
             
                    if @output_transformer
         | 
| 336 | 
            -
                       | 
| 379 | 
            +
                      result = @output_transformer.call(context)
         | 
| 380 | 
            +
             | 
| 381 | 
            +
                      # If hash, validate control flow keys
         | 
| 382 | 
            +
                      if result.is_a?(Hash)
         | 
| 383 | 
            +
                        validate_transformer_hash(result, :output)
         | 
| 384 | 
            +
                      end
         | 
| 385 | 
            +
             | 
| 386 | 
            +
                      return result
         | 
| 337 387 | 
             
                    end
         | 
| 338 388 |  | 
| 339 389 | 
             
                    # Bash command transformer
         | 
| @@ -411,6 +461,50 @@ module SwarmSDK | |
| 411 461 |  | 
| 412 462 | 
             
                  private
         | 
| 413 463 |  | 
| 464 | 
            +
                  # Validate transformer hash return value
         | 
| 465 | 
            +
                  #
         | 
| 466 | 
            +
                  # Ensures hash has valid control flow keys and required content field.
         | 
| 467 | 
            +
                  #
         | 
| 468 | 
            +
                  # @param hash [Hash] Hash returned from transformer
         | 
| 469 | 
            +
                  # @param transformer_type [Symbol] :input or :output
         | 
| 470 | 
            +
                  # @return [void]
         | 
| 471 | 
            +
                  # @raise [ConfigurationError] If hash is invalid
         | 
| 472 | 
            +
                  def validate_transformer_hash(hash, transformer_type)
         | 
| 473 | 
            +
                    # Valid control keys
         | 
| 474 | 
            +
                    valid_keys = if transformer_type == :input
         | 
| 475 | 
            +
                      [:skip_execution, :halt_workflow, :goto_node, :content]
         | 
| 476 | 
            +
                    else
         | 
| 477 | 
            +
                      [:halt_workflow, :goto_node, :content]
         | 
| 478 | 
            +
                    end
         | 
| 479 | 
            +
             | 
| 480 | 
            +
                    # Check for invalid keys
         | 
| 481 | 
            +
                    invalid_keys = hash.keys - valid_keys
         | 
| 482 | 
            +
                    if invalid_keys.any?
         | 
| 483 | 
            +
                      raise ConfigurationError,
         | 
| 484 | 
            +
                        "Invalid #{transformer_type} transformer hash keys: #{invalid_keys.join(", ")}. " \
         | 
| 485 | 
            +
                          "Valid keys: #{valid_keys.join(", ")}"
         | 
| 486 | 
            +
                    end
         | 
| 487 | 
            +
             | 
| 488 | 
            +
                    # Ensure content is present
         | 
| 489 | 
            +
                    unless hash.key?(:content)
         | 
| 490 | 
            +
                      raise ConfigurationError,
         | 
| 491 | 
            +
                        "#{transformer_type.capitalize} transformer hash must include :content key"
         | 
| 492 | 
            +
                    end
         | 
| 493 | 
            +
             | 
| 494 | 
            +
                    # Ensure only one control key
         | 
| 495 | 
            +
                    control_keys = hash.keys & [:skip_execution, :halt_workflow, :goto_node]
         | 
| 496 | 
            +
                    if control_keys.size > 1
         | 
| 497 | 
            +
                      raise ConfigurationError,
         | 
| 498 | 
            +
                        "#{transformer_type.capitalize} transformer hash can only have one control key, got: #{control_keys.join(", ")}"
         | 
| 499 | 
            +
                    end
         | 
| 500 | 
            +
             | 
| 501 | 
            +
                    # Validate goto_node has valid node name
         | 
| 502 | 
            +
                    if hash[:goto_node] && !hash[:goto_node].is_a?(Symbol)
         | 
| 503 | 
            +
                      raise ConfigurationError,
         | 
| 504 | 
            +
                        "goto_node value must be a Symbol, got: #{hash[:goto_node].class}"
         | 
| 505 | 
            +
                    end
         | 
| 506 | 
            +
                  end
         | 
| 507 | 
            +
             | 
| 414 508 | 
             
                  # Auto-add agents that are mentioned in delegates_to but not explicitly declared
         | 
| 415 509 | 
             
                  #
         | 
| 416 510 | 
             
                  # This allows:
         | 
| @@ -418,7 +512,8 @@ module SwarmSDK | |
| 418 512 | 
             
                  # Without needing:
         | 
| 419 513 | 
             
                  #   agent(:tester)
         | 
| 420 514 | 
             
                  #
         | 
| 421 | 
            -
                  # The tester agent is automatically added to the node with no delegation | 
| 515 | 
            +
                  # The tester agent is automatically added to the node with no delegation
         | 
| 516 | 
            +
                  # and reset_context: true (fresh context by default).
         | 
| 422 517 | 
             
                  #
         | 
| 423 518 | 
             
                  # @return [void]
         | 
| 424 519 | 
             
                  def auto_add_delegate_agents
         | 
| @@ -429,9 +524,9 @@ module SwarmSDK | |
| 429 524 | 
             
                    declared_agents = @agent_configs.map { |ac| ac[:agent] }
         | 
| 430 525 | 
             
                    missing_delegates = all_delegates - declared_agents
         | 
| 431 526 |  | 
| 432 | 
            -
                    # Auto-add missing delegates with empty delegation
         | 
| 527 | 
            +
                    # Auto-add missing delegates with empty delegation and default reset_context
         | 
| 433 528 | 
             
                    missing_delegates.each do |delegate_name|
         | 
| 434 | 
            -
                      @agent_configs << { agent: delegate_name, delegates_to: [] }
         | 
| 529 | 
            +
                      @agent_configs << { agent: delegate_name, delegates_to: [], reset_context: true }
         | 
| 435 530 | 
             
                    end
         | 
| 436 531 | 
             
                  end
         | 
| 437 532 | 
             
                end
         | 
| @@ -166,5 +166,80 @@ module SwarmSDK | |
| 166 166 | 
             
                    @previous_result.success?
         | 
| 167 167 | 
             
                  end
         | 
| 168 168 | 
             
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                # Control flow methods for transformers
         | 
| 171 | 
            +
                # These return special hashes that NodeOrchestrator recognizes
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                # Skip current node's LLM execution and return content immediately
         | 
| 174 | 
            +
                #
         | 
| 175 | 
            +
                # Only valid for input transformers.
         | 
| 176 | 
            +
                #
         | 
| 177 | 
            +
                # @param content [String] Content to return (skips LLM call)
         | 
| 178 | 
            +
                # @return [Hash] Control hash for skip_execution
         | 
| 179 | 
            +
                # @raise [ArgumentError] If content is nil
         | 
| 180 | 
            +
                #
         | 
| 181 | 
            +
                # @example
         | 
| 182 | 
            +
                #   input do |ctx|
         | 
| 183 | 
            +
                #     cached = check_cache(ctx.content)
         | 
| 184 | 
            +
                #     return ctx.skip_execution(content: cached) if cached
         | 
| 185 | 
            +
                #     ctx.content
         | 
| 186 | 
            +
                #   end
         | 
| 187 | 
            +
                def skip_execution(content:)
         | 
| 188 | 
            +
                  if content.nil?
         | 
| 189 | 
            +
                    raise ArgumentError,
         | 
| 190 | 
            +
                      "skip_execution requires content (got nil). " \
         | 
| 191 | 
            +
                        "Check that ctx.content or your content source is not nil. " \
         | 
| 192 | 
            +
                        "Node: #{@node_name}"
         | 
| 193 | 
            +
                  end
         | 
| 194 | 
            +
                  { skip_execution: true, content: content }
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                # Halt entire workflow and return content as final result
         | 
| 198 | 
            +
                #
         | 
| 199 | 
            +
                # Valid for both input and output transformers.
         | 
| 200 | 
            +
                #
         | 
| 201 | 
            +
                # @param content [String] Final content to return
         | 
| 202 | 
            +
                # @return [Hash] Control hash for halt_workflow
         | 
| 203 | 
            +
                # @raise [ArgumentError] If content is nil
         | 
| 204 | 
            +
                #
         | 
| 205 | 
            +
                # @example
         | 
| 206 | 
            +
                #   output do |ctx|
         | 
| 207 | 
            +
                #     return ctx.halt_workflow(content: ctx.content) if converged?(ctx.content)
         | 
| 208 | 
            +
                #     ctx.content
         | 
| 209 | 
            +
                #   end
         | 
| 210 | 
            +
                def halt_workflow(content:)
         | 
| 211 | 
            +
                  if content.nil?
         | 
| 212 | 
            +
                    raise ArgumentError,
         | 
| 213 | 
            +
                      "halt_workflow requires content (got nil). " \
         | 
| 214 | 
            +
                        "Check that ctx.content or your content source is not nil. " \
         | 
| 215 | 
            +
                        "Node: #{@node_name}"
         | 
| 216 | 
            +
                  end
         | 
| 217 | 
            +
                  { halt_workflow: true, content: content }
         | 
| 218 | 
            +
                end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                # Jump to a different node with provided content as input
         | 
| 221 | 
            +
                #
         | 
| 222 | 
            +
                # Valid for both input and output transformers.
         | 
| 223 | 
            +
                #
         | 
| 224 | 
            +
                # @param node [Symbol] Node name to jump to
         | 
| 225 | 
            +
                # @param content [String] Content to pass to target node
         | 
| 226 | 
            +
                # @return [Hash] Control hash for goto_node
         | 
| 227 | 
            +
                # @raise [ArgumentError] If content is nil
         | 
| 228 | 
            +
                #
         | 
| 229 | 
            +
                # @example
         | 
| 230 | 
            +
                #   input do |ctx|
         | 
| 231 | 
            +
                #     return ctx.goto_node(:review, content: ctx.content) if needs_review?(ctx.content)
         | 
| 232 | 
            +
                #     ctx.content
         | 
| 233 | 
            +
                #   end
         | 
| 234 | 
            +
                def goto_node(node, content:)
         | 
| 235 | 
            +
                  if content.nil?
         | 
| 236 | 
            +
                    raise ArgumentError,
         | 
| 237 | 
            +
                      "goto_node requires content (got nil). " \
         | 
| 238 | 
            +
                        "Check that ctx.content or your content source is not nil. " \
         | 
| 239 | 
            +
                        "This often happens when the previous node failed with an error. " \
         | 
| 240 | 
            +
                        "Node: #{@node_name}, Target: #{node}"
         | 
| 241 | 
            +
                  end
         | 
| 242 | 
            +
                  { goto_node: node.to_sym, content: content }
         | 
| 243 | 
            +
                end
         | 
| 169 244 | 
             
              end
         | 
| 170 245 | 
             
            end
         |