claude_swarm 1.0.2 → 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 +5 -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 +173 -3
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
|