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.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/Rakefile +4 -4
  4. data/docs/v2/CHANGELOG.swarm_cli.md +9 -0
  5. data/docs/v2/CHANGELOG.swarm_memory.md +19 -0
  6. data/docs/v2/CHANGELOG.swarm_sdk.md +45 -0
  7. data/docs/v2/guides/complete-tutorial.md +113 -1
  8. data/docs/v2/reference/ruby-dsl.md +138 -5
  9. data/docs/v2/reference/swarm_memory_technical_details.md +2090 -0
  10. data/lib/claude_swarm/cli.rb +9 -11
  11. data/lib/claude_swarm/commands/ps.rb +1 -2
  12. data/lib/claude_swarm/configuration.rb +2 -3
  13. data/lib/claude_swarm/orchestrator.rb +43 -44
  14. data/lib/claude_swarm/system_utils.rb +4 -4
  15. data/lib/claude_swarm/version.rb +1 -1
  16. data/lib/claude_swarm.rb +4 -9
  17. data/lib/swarm_cli/commands/mcp_tools.rb +3 -3
  18. data/lib/swarm_cli/config_loader.rb +11 -10
  19. data/lib/swarm_cli/version.rb +1 -1
  20. data/lib/swarm_cli.rb +2 -0
  21. data/lib/swarm_memory/adapters/filesystem_adapter.rb +0 -12
  22. data/lib/swarm_memory/core/storage.rb +66 -6
  23. data/lib/swarm_memory/integration/sdk_plugin.rb +14 -0
  24. data/lib/swarm_memory/optimization/defragmenter.rb +4 -0
  25. data/lib/swarm_memory/tools/memory_edit.rb +1 -0
  26. data/lib/swarm_memory/tools/memory_glob.rb +24 -1
  27. data/lib/swarm_memory/tools/memory_write.rb +2 -2
  28. data/lib/swarm_memory/version.rb +1 -1
  29. data/lib/swarm_memory.rb +2 -0
  30. data/lib/swarm_sdk/agent/chat.rb +1 -1
  31. data/lib/swarm_sdk/agent/definition.rb +17 -1
  32. data/lib/swarm_sdk/node/agent_config.rb +7 -2
  33. data/lib/swarm_sdk/node/builder.rb +130 -35
  34. data/lib/swarm_sdk/node_context.rb +75 -0
  35. data/lib/swarm_sdk/node_orchestrator.rb +219 -12
  36. data/lib/swarm_sdk/plugin.rb +73 -1
  37. data/lib/swarm_sdk/result.rb +32 -6
  38. data/lib/swarm_sdk/swarm/builder.rb +1 -0
  39. data/lib/swarm_sdk/tools/delegate.rb +2 -2
  40. data/lib/swarm_sdk/version.rb +1 -1
  41. data/lib/swarm_sdk.rb +3 -7
  42. data/memory/corpus-self-reflection/.lock +0 -0
  43. data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.emb +0 -0
  44. data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.md +11 -0
  45. data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.yml +23 -0
  46. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.emb +0 -0
  47. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.md +20 -0
  48. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.yml +22 -0
  49. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.emb +0 -0
  50. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.md +24 -0
  51. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.yml +22 -0
  52. data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.emb +0 -0
  53. data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.md +18 -0
  54. data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.yml +21 -0
  55. data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.emb +0 -0
  56. data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.md +30 -0
  57. data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.yml +8 -0
  58. data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.emb +0 -0
  59. data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.md +21 -0
  60. data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.yml +24 -0
  61. data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.emb +0 -0
  62. data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.md +18 -0
  63. data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.yml +24 -0
  64. data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.emb +0 -0
  65. data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.md +23 -0
  66. data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.yml +23 -0
  67. data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.emb +0 -0
  68. data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.md +17 -0
  69. data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.yml +22 -0
  70. data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.emb +0 -0
  71. data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.md +18 -0
  72. data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.yml +22 -0
  73. data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.emb +0 -0
  74. data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.md +15 -0
  75. data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.yml +22 -0
  76. data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.emb +0 -0
  77. data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.md +9 -0
  78. data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.yml +24 -0
  79. data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.emb +0 -0
  80. data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.md +13 -0
  81. data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.yml +24 -0
  82. data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.emb +0 -0
  83. data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.md +25 -0
  84. data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.yml +24 -0
  85. data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.emb +0 -0
  86. data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.md +26 -0
  87. data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.yml +24 -0
  88. data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.emb +0 -0
  89. data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.md +17 -0
  90. data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.yml +22 -0
  91. data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.emb +0 -0
  92. data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.md +14 -0
  93. data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.yml +21 -0
  94. data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.emb +0 -0
  95. data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.md +13 -0
  96. data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.yml +23 -0
  97. data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.emb +0 -0
  98. data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.md +22 -0
  99. data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.yml +23 -0
  100. data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.emb +0 -0
  101. data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.md +39 -0
  102. data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.yml +8 -0
  103. data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.emb +0 -0
  104. data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.md +23 -0
  105. data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.yml +24 -0
  106. data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.emb +0 -0
  107. data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.md +15 -0
  108. data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.yml +22 -0
  109. data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.emb +0 -0
  110. data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.md +12 -0
  111. data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.yml +23 -0
  112. data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.emb +0 -0
  113. data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.md +15 -0
  114. data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.yml +8 -0
  115. data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.emb +0 -0
  116. data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.md +28 -0
  117. data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.yml +8 -0
  118. data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.emb +0 -0
  119. data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.md +19 -0
  120. data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.yml +22 -0
  121. data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.emb +0 -0
  122. data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.md +19 -0
  123. data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.yml +21 -0
  124. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.emb +0 -0
  125. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.md +25 -0
  126. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.yml +22 -0
  127. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.emb +8 -0
  128. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.md +30 -0
  129. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.yml +22 -0
  130. data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.emb +0 -0
  131. data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.md +21 -0
  132. data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.yml +22 -0
  133. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.emb +0 -0
  134. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.md +37 -0
  135. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.yml +8 -0
  136. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.emb +0 -0
  137. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.md +24 -0
  138. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.yml +24 -0
  139. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.emb +0 -0
  140. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.md +27 -0
  141. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.yml +24 -0
  142. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.emb +0 -0
  143. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.md +26 -0
  144. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.yml +23 -0
  145. data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.emb +0 -0
  146. data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.md +37 -0
  147. data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.yml +25 -0
  148. data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.emb +0 -0
  149. data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.md +21 -0
  150. data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.yml +23 -0
  151. data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.emb +0 -0
  152. data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.md +25 -0
  153. data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.yml +8 -0
  154. data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.emb +0 -0
  155. data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.md +11 -0
  156. data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.yml +21 -0
  157. data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.emb +0 -0
  158. data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.md +19 -0
  159. data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.yml +21 -0
  160. data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.emb +0 -0
  161. data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.md +26 -0
  162. data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.yml +20 -0
  163. data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.emb +0 -0
  164. data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.md +23 -0
  165. data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.yml +21 -0
  166. data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.emb +0 -0
  167. data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.md +19 -0
  168. data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.yml +22 -0
  169. data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.emb +0 -0
  170. data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.md +21 -0
  171. data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.yml +22 -0
  172. data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.emb +0 -0
  173. data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.md +28 -0
  174. data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.yml +23 -0
  175. data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.emb +0 -0
  176. data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.md +21 -0
  177. data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.yml +20 -0
  178. data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.emb +0 -0
  179. data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.md +21 -0
  180. data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.yml +25 -0
  181. data/memory/corpus-self-reflection/experience/local-change-without-continuity.emb +0 -0
  182. data/memory/corpus-self-reflection/experience/local-change-without-continuity.md +30 -0
  183. data/memory/corpus-self-reflection/experience/local-change-without-continuity.yml +22 -0
  184. data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.emb +0 -0
  185. data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.md +21 -0
  186. data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.yml +20 -0
  187. data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.emb +0 -0
  188. data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.md +25 -0
  189. data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.yml +23 -0
  190. data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.emb +0 -0
  191. data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.md +32 -0
  192. data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.yml +22 -0
  193. data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.emb +0 -0
  194. data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.md +21 -0
  195. data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.yml +21 -0
  196. data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.emb +0 -0
  197. data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.md +21 -0
  198. data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.yml +21 -0
  199. data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.emb +0 -0
  200. data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.md +22 -0
  201. data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.yml +22 -0
  202. data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.emb +0 -0
  203. data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.md +28 -0
  204. data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.yml +24 -0
  205. data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.emb +0 -0
  206. data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.md +17 -0
  207. data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.yml +20 -0
  208. data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.emb +0 -0
  209. data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.md +9 -0
  210. data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.yml +22 -0
  211. metadata +172 -2
@@ -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 RubyLLM::Error, "Provider returned nil response. This usually indicates a malformed API response " \
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
- def agent(name)
58
- config = AgentConfig.new(name, self)
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
- # **Skip Execution**: Return a hash with `skip_execution: true` to skip
124
- # the node's swarm execution and immediately return the provided content.
125
- # Useful for caching, validation, or conditional execution.
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 skip hash
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
- # # Skip LLM call, return cached result
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 Skip execution (validation)
164
+ # @example Halt workflow (validation)
159
165
  # input do |ctx|
160
166
  # if ctx.content.length > 10000
161
- # # Fail early without LLM call
162
- # { skip_execution: true, content: "ERROR: Input too long" }
163
- # else
164
- # ctx.content
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 Access multiple node results
237
+ # @example Halt workflow (convergence check)
220
238
  # output do |ctx|
221
- # plan = ctx.all_results[:planning].content
222
- # impl = ctx.content
239
+ # return ctx.halt_workflow(content: ctx.content) if converged?(ctx.content)
240
+ # ctx.content
241
+ # end
223
242
  #
224
- # "Completed:\nPlan: #{plan}\nImpl: #{impl}"
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 skip hash `{ skip_execution: true, content: "..." }`
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 (skip_execution)
312
+ # Ruby blocks can return String (transformed content) OR Hash (control flow)
282
313
  if @input_transformer
283
- return @input_transformer.call(context)
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
- # Simply calls the block with context and returns result
377
+ # Ruby blocks can return String (transformed content) OR Hash (control flow)
335
378
  if @output_transformer
336
- return @output_transformer.call(context)
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