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
@@ -20,16 +20,28 @@ module SwarmSDK
20
20
  class NodeOrchestrator
21
21
  attr_reader :swarm_name, :nodes, :start_node
22
22
 
23
- def initialize(swarm_name:, agent_definitions:, nodes:, start_node:)
23
+ def initialize(swarm_name:, agent_definitions:, nodes:, start_node:, scratchpad_enabled: true)
24
24
  @swarm_name = swarm_name
25
25
  @agent_definitions = agent_definitions
26
26
  @nodes = nodes
27
27
  @start_node = start_node
28
+ @scratchpad_enabled = scratchpad_enabled
29
+ @agent_instance_cache = {} # Cache for preserving agent context across nodes
28
30
 
29
31
  validate!
30
32
  @execution_order = build_execution_order
31
33
  end
32
34
 
35
+ # Alias for compatibility with Swarm interface
36
+ alias_method :name, :swarm_name
37
+
38
+ # Return the lead agent of the start node for CLI compatibility
39
+ #
40
+ # @return [Symbol] Lead agent of the start node
41
+ def lead_agent
42
+ @nodes[@start_node].lead_agent
43
+ end
44
+
33
45
  # Execute the node workflow
34
46
  #
35
47
  # Executes nodes in topological order, passing output from each node
@@ -56,7 +68,12 @@ module SwarmSDK
56
68
  LogStream.emitter = LogCollector
57
69
  end
58
70
 
59
- @execution_order.each do |node_name|
71
+ # Dynamic execution with support for goto_node
72
+ execution_index = 0
73
+ last_result = nil
74
+
75
+ while execution_index < @execution_order.size
76
+ node_name = @execution_order[execution_index]
60
77
  node = @nodes[node_name]
61
78
  node_start_time = Time.now
62
79
 
@@ -102,13 +119,26 @@ module SwarmSDK
102
119
  # - Exit 2: Halt workflow with error from STDERR (STDOUT ignored)
103
120
  transformed = node.transform_input(input_context, current_input: current_input)
104
121
 
105
- # Check if transformer requested skipping execution
106
- # (from Ruby block returning hash OR bash command exit 1)
107
- if transformed.is_a?(Hash) && transformed[:skip_execution]
122
+ # Check for control flow from transformer
123
+ control_result = handle_transformer_control_flow(
124
+ transformed: transformed,
125
+ node_name: node_name,
126
+ node: node,
127
+ node_start_time: node_start_time,
128
+ )
129
+
130
+ case control_result[:action]
131
+ when :halt
132
+ return control_result[:result]
133
+ when :goto
134
+ execution_index = find_node_index(control_result[:target])
135
+ current_input = control_result[:content]
136
+ next
137
+ when :skip
108
138
  skip_execution = true
109
- skip_content = transformed[:content] || transformed["content"]
110
- else
111
- current_input = transformed
139
+ skip_content = control_result[:content]
140
+ when :continue
141
+ current_input = control_result[:content]
112
142
  end
113
143
  end
114
144
 
@@ -130,6 +160,9 @@ module SwarmSDK
130
160
  mini_swarm = build_swarm_for_node(node)
131
161
  result = mini_swarm.execute(current_input)
132
162
 
163
+ # Cache agent instances for context preservation
164
+ cache_agent_instances(mini_swarm, node)
165
+
133
166
  # If result has error, log it with backtrace
134
167
  if result.error
135
168
  RubyLLM.logger.error("NodeOrchestrator: Node '#{node_name}' failed: #{result.error.message}")
@@ -138,6 +171,7 @@ module SwarmSDK
138
171
  end
139
172
 
140
173
  results[node_name] = result
174
+ last_result = result
141
175
 
142
176
  # Transform output for next node using NodeContext
143
177
  output_context = NodeContext.for_output(
@@ -146,7 +180,29 @@ module SwarmSDK
146
180
  original_prompt: @original_prompt,
147
181
  node_name: node_name,
148
182
  )
149
- current_input = node.transform_output(output_context)
183
+ transformed_output = node.transform_output(output_context)
184
+
185
+ # Check for control flow from output transformer
186
+ control_result = handle_output_transformer_control_flow(
187
+ transformed: transformed_output,
188
+ node_name: node_name,
189
+ node: node,
190
+ node_start_time: node_start_time,
191
+ skip_execution: skip_execution,
192
+ result: result,
193
+ )
194
+
195
+ case control_result[:action]
196
+ when :halt
197
+ return control_result[:result]
198
+ when :goto
199
+ execution_index = find_node_index(control_result[:target])
200
+ current_input = control_result[:content]
201
+ emit_node_stop(node_name, node, result, Time.now - node_start_time, skip_execution)
202
+ next
203
+ when :continue
204
+ current_input = control_result[:content]
205
+ end
150
206
 
151
207
  # For agent-less nodes, update the result with transformed content
152
208
  # This ensures all_results contains the actual output, not the input
@@ -158,14 +214,17 @@ module SwarmSDK
158
214
  duration: result.duration,
159
215
  error: result.error,
160
216
  )
217
+ last_result = results[node_name]
161
218
  end
162
219
 
163
220
  # Emit node_stop event
164
221
  node_duration = Time.now - node_start_time
165
222
  emit_node_stop(node_name, node, result, node_duration, skip_execution)
223
+
224
+ execution_index += 1
166
225
  end
167
226
 
168
- results.values.last
227
+ last_result
169
228
  ensure
170
229
  # Reset logging state for next execution
171
230
  LogCollector.reset!
@@ -284,10 +343,18 @@ module SwarmSDK
284
343
  # Creates a new Swarm with only the agents specified in the node,
285
344
  # configured with the node's delegation topology.
286
345
  #
346
+ # For agents with reset_context: false, injects cached instances
347
+ # to preserve conversation history across nodes.
348
+ #
349
+ # Inherits scratchpad_enabled setting from NodeOrchestrator.
350
+ #
287
351
  # @param node [Node::Builder] Node configuration
288
352
  # @return [Swarm] Configured swarm instance
289
353
  def build_swarm_for_node(node)
290
- swarm = Swarm.new(name: "#{@swarm_name}:#{node.name}")
354
+ swarm = Swarm.new(
355
+ name: "#{@swarm_name}:#{node.name}",
356
+ scratchpad_enabled: @scratchpad_enabled,
357
+ )
291
358
 
292
359
  # Add each agent specified in this node
293
360
  node.agent_configs.each do |config|
@@ -306,6 +373,9 @@ module SwarmSDK
306
373
  # Set lead agent
307
374
  swarm.lead = node.lead_agent
308
375
 
376
+ # Inject cached agent instances for context preservation
377
+ inject_cached_agents(swarm, node)
378
+
309
379
  swarm
310
380
  end
311
381
 
@@ -359,7 +429,8 @@ module SwarmSDK
359
429
  if order.size < @nodes.size
360
430
  unprocessed = @nodes.keys - order
361
431
  raise CircularDependencyError,
362
- "Circular dependency detected. Unprocessed nodes: #{unprocessed.join(", ")}"
432
+ "Circular dependency detected. Unprocessed nodes: #{unprocessed.join(", ")}. " \
433
+ "Use goto_node in transformers to create loops instead of circular depends_on."
363
434
  end
364
435
 
365
436
  # Verify start_node is in the execution order
@@ -380,5 +451,141 @@ module SwarmSDK
380
451
 
381
452
  order
382
453
  end
454
+
455
+ # Handle control flow from input transformer
456
+ #
457
+ # @param transformed [String, Hash] Result from transformer
458
+ # @param node_name [Symbol] Current node name
459
+ # @param node [Node::Builder] Node configuration
460
+ # @param node_start_time [Time] Node execution start time
461
+ # @return [Hash] Control result with :action and relevant data
462
+ def handle_transformer_control_flow(transformed:, node_name:, node:, node_start_time:)
463
+ return { action: :continue, content: transformed } unless transformed.is_a?(Hash)
464
+
465
+ if transformed[:halt_workflow]
466
+ # Halt entire workflow
467
+ halt_result = Result.new(
468
+ content: transformed[:content],
469
+ agent: "halted:#{node_name}",
470
+ logs: [],
471
+ duration: Time.now - node_start_time,
472
+ )
473
+ emit_node_stop(node_name, node, halt_result, Time.now - node_start_time, false)
474
+ { action: :halt, result: halt_result }
475
+ elsif transformed[:goto_node]
476
+ # Jump to different node
477
+ { action: :goto, target: transformed[:goto_node], content: transformed[:content] }
478
+ elsif transformed[:skip_execution]
479
+ # Skip node execution
480
+ { action: :skip, content: transformed[:content] }
481
+ else
482
+ # No control flow - continue normally
483
+ { action: :continue, content: transformed[:content] }
484
+ end
485
+ end
486
+
487
+ # Handle control flow from output transformer
488
+ #
489
+ # @param transformed [String, Hash] Result from transformer
490
+ # @param node_name [Symbol] Current node name
491
+ # @param node [Node::Builder] Node configuration
492
+ # @param node_start_time [Time] Node execution start time
493
+ # @param skip_execution [Boolean] Whether node execution was skipped
494
+ # @param result [Result] Node execution result
495
+ # @return [Hash] Control result with :action and relevant data
496
+ def handle_output_transformer_control_flow(transformed:, node_name:, node:, node_start_time:, skip_execution:, result:)
497
+ # If not a hash, it's just transformed content - continue normally
498
+ return { action: :continue, content: transformed } unless transformed.is_a?(Hash)
499
+
500
+ if transformed[:halt_workflow]
501
+ # Halt entire workflow
502
+ halt_result = Result.new(
503
+ content: transformed[:content],
504
+ agent: result.agent,
505
+ logs: result.logs,
506
+ duration: result.duration,
507
+ )
508
+ emit_node_stop(node_name, node, halt_result, Time.now - node_start_time, skip_execution)
509
+ { action: :halt, result: halt_result }
510
+ elsif transformed[:goto_node]
511
+ # Jump to different node
512
+ { action: :goto, target: transformed[:goto_node], content: transformed[:content] }
513
+ else
514
+ # Hash without control flow keys - treat as regular hash with :content key
515
+ # This handles the case where transformer returns a hash that's not for control flow
516
+ { action: :continue, content: transformed[:content] || transformed }
517
+ end
518
+ end
519
+
520
+ # Find the index of a node in the execution order
521
+ #
522
+ # @param node_name [Symbol] Node name to find
523
+ # @return [Integer] Index in execution order
524
+ # @raise [ConfigurationError] If node not found
525
+ def find_node_index(node_name)
526
+ index = @execution_order.index(node_name)
527
+ unless index
528
+ raise ConfigurationError,
529
+ "goto_node target '#{node_name}' not found. Available nodes: #{@execution_order.join(", ")}"
530
+ end
531
+ index
532
+ end
533
+
534
+ # Cache agent instances from a swarm for potential reuse
535
+ #
536
+ # Only caches agents that have reset_context: false in this node.
537
+ # This allows preserving conversation history across nodes.
538
+ #
539
+ # @param swarm [Swarm] Swarm instance that just executed
540
+ # @param node [Node::Builder] Node configuration
541
+ # @return [void]
542
+ def cache_agent_instances(swarm, node)
543
+ return unless swarm.agents # Only cache if agents were initialized
544
+
545
+ node.agent_configs.each do |config|
546
+ agent_name = config[:agent]
547
+ reset_context = config[:reset_context]
548
+
549
+ # Only cache if reset_context is false
550
+ next if reset_context
551
+
552
+ # Cache the agent instance
553
+ agent_instance = swarm.agents[agent_name]
554
+ @agent_instance_cache[agent_name] = agent_instance if agent_instance
555
+ end
556
+ end
557
+
558
+ # Inject cached agent instances into a swarm
559
+ #
560
+ # For agents with reset_context: false, reuses cached instances to preserve context.
561
+ # Forces agent initialization first (by accessing .agents), then swaps in cached instances.
562
+ #
563
+ # @param swarm [Swarm] Swarm instance to inject into
564
+ # @param node [Node::Builder] Node configuration
565
+ # @return [void]
566
+ def inject_cached_agents(swarm, node)
567
+ # Check if any agents need context preservation
568
+ has_preserved_agents = node.agent_configs.any? { |c| !c[:reset_context] && @agent_instance_cache[c[:agent]] }
569
+ return unless has_preserved_agents
570
+
571
+ # Force agent initialization by accessing .agents (triggers lazy init)
572
+ # Then inject cached instances
573
+ agents_hash = swarm.agents
574
+
575
+ node.agent_configs.each do |config|
576
+ agent_name = config[:agent]
577
+ reset_context = config[:reset_context]
578
+
579
+ # Skip if reset_context is true (want fresh instance)
580
+ next if reset_context
581
+
582
+ # Check if we have a cached instance
583
+ cached_agent = @agent_instance_cache[agent_name]
584
+ next unless cached_agent
585
+
586
+ # Inject the cached instance (replace the freshly initialized one)
587
+ agents_hash[agent_name] = cached_agent
588
+ end
589
+ end
383
590
  end
384
591
  end
@@ -7,7 +7,32 @@ module SwarmSDK
7
7
  # Plugins are self-registering - they call SwarmSDK::PluginRegistry.register
8
8
  # when the gem is loaded.
9
9
  #
10
- # @example Implementing a plugin
10
+ # ## Adding Custom Attributes to Agents
11
+ #
12
+ # Plugins can add custom attributes to Agent::Definition that are preserved
13
+ # when agents are cloned (e.g., in NodeOrchestrator). To do this:
14
+ #
15
+ # 1. Add attr_reader to Agent::Definition for your attribute
16
+ # 2. Parse the attribute in Agent::Definition#initialize
17
+ # 3. Implement serialize_config to preserve it during serialization
18
+ #
19
+ # @example Plugin with custom agent attributes
20
+ # # 1. Extend Agent::Definition (in your plugin gem)
21
+ # module SwarmSDK
22
+ # module Agent
23
+ # class Definition
24
+ # attr_reader :my_custom_config
25
+ #
26
+ # alias_method :original_initialize, :initialize
27
+ # def initialize(name, config = {})
28
+ # @my_custom_config = config[:my_custom_config]
29
+ # original_initialize(name, config)
30
+ # end
31
+ # end
32
+ # end
33
+ # end
34
+ #
35
+ # # 2. Implement plugin with serialize_config
11
36
  # class MyPlugin < SwarmSDK::Plugin
12
37
  # def name
13
38
  # :my_plugin
@@ -20,9 +45,34 @@ module SwarmSDK
20
45
  # def create_tool(tool_name, context)
21
46
  # # Create and return tool instance
22
47
  # end
48
+ #
49
+ # # Preserve custom config when agents are cloned
50
+ # def serialize_config(agent_definition:)
51
+ # return {} unless agent_definition.my_custom_config
52
+ #
53
+ # { my_custom_config: agent_definition.my_custom_config }
54
+ # end
23
55
  # end
24
56
  #
25
57
  # SwarmSDK::PluginRegistry.register(MyPlugin.new)
58
+ #
59
+ # Now agents can use your custom config:
60
+ #
61
+ # agent :researcher do
62
+ # my_custom_config { option: "value" }
63
+ # end
64
+ #
65
+ # And it will be preserved when NodeOrchestrator clones the agent!
66
+ #
67
+ # @example Real-world: SwarmMemory plugin
68
+ # # SwarmMemory adds 'memory' attribute to agents
69
+ # class SDKPlugin < SwarmSDK::Plugin
70
+ # def serialize_config(agent_definition:)
71
+ # return {} unless agent_definition.memory
72
+ # { memory: agent_definition.memory }
73
+ # end
74
+ # end
75
+ #
26
76
  class Plugin
27
77
  # Plugin name (must be unique)
28
78
  #
@@ -143,5 +193,27 @@ module SwarmSDK
143
193
  def on_user_message(agent_name:, prompt:, is_first_message:)
144
194
  []
145
195
  end
196
+
197
+ # Contribute to agent serialization (optional)
198
+ #
199
+ # Called when Agent::Definition.to_h is invoked (e.g., for cloning agents
200
+ # in NodeOrchestrator). Plugins can return config keys that should be
201
+ # included in the serialized hash to preserve their state.
202
+ #
203
+ # This allows plugins to maintain their configuration when agents are
204
+ # cloned or serialized, without SwarmSDK needing to know about plugin-specific fields.
205
+ #
206
+ # @param agent_definition [Agent::Definition] Agent definition
207
+ # @return [Hash] Config keys to include in to_h (e.g., { memory: config })
208
+ #
209
+ # @example Memory plugin serialization
210
+ # def serialize_config(agent_definition:)
211
+ # return {} unless agent_definition.memory
212
+ #
213
+ # { memory: agent_definition.memory }
214
+ # end
215
+ def serialize_config(agent_definition:)
216
+ {}
217
+ end
146
218
  end
147
219
  end
@@ -2,17 +2,17 @@
2
2
 
3
3
  module SwarmSDK
4
4
  class Result
5
- attr_reader :content, :agent, :cost, :tokens, :duration, :logs, :error, :metadata
5
+ attr_reader :content, :agent, :duration, :logs, :error, :metadata
6
6
 
7
- def initialize(content: nil, agent:, cost: 0.0, tokens: {}, duration: 0.0, logs: [], error: nil, metadata: {})
7
+ def initialize(content: nil, agent:, cost: nil, tokens: nil, duration: 0.0, logs: [], error: nil, metadata: {})
8
8
  @content = content
9
9
  @agent = agent
10
- @cost = cost
11
- @tokens = tokens
12
10
  @duration = duration
13
11
  @logs = logs
14
12
  @error = error
15
13
  @metadata = metadata
14
+ # Legacy parameters kept for backward compatibility but not stored
15
+ # Use total_cost and tokens methods instead which calculate from logs
16
16
  end
17
17
 
18
18
  def success?
@@ -23,12 +23,38 @@ module SwarmSDK
23
23
  !success?
24
24
  end
25
25
 
26
+ # Calculate total cost from logs
27
+ #
28
+ # Delegates to total_cost for consistency. This attribute is calculated
29
+ # dynamically rather than stored.
30
+ #
31
+ # @return [Float] Total cost in dollars
32
+ def cost
33
+ total_cost
34
+ end
35
+
36
+ # Get token breakdown from logs
37
+ #
38
+ # Returns input and output tokens from the last log entry with usage data.
39
+ # This attribute is calculated dynamically rather than stored.
40
+ #
41
+ # @return [Hash] Token breakdown with :input and :output keys, or empty hash if no usage data
42
+ def tokens
43
+ last_entry = @logs.reverse.find { |entry| entry.dig(:usage, :cumulative_input_tokens) }
44
+ return {} unless last_entry
45
+
46
+ {
47
+ input: last_entry.dig(:usage, :cumulative_input_tokens) || 0,
48
+ output: last_entry.dig(:usage, :cumulative_output_tokens) || 0,
49
+ }
50
+ end
51
+
26
52
  def to_h
27
53
  {
28
54
  content: @content,
29
55
  agent: @agent,
30
- cost: @cost,
31
- tokens: @tokens,
56
+ cost: cost,
57
+ tokens: tokens,
32
58
  duration: @duration,
33
59
  success: success?,
34
60
  error: @error&.message,
@@ -380,6 +380,7 @@ module SwarmSDK
380
380
  agent_definitions: agent_definitions,
381
381
  nodes: @nodes,
382
382
  start_node: @start_node,
383
+ scratchpad_enabled: @scratchpad_enabled,
383
384
  )
384
385
  end
385
386
 
@@ -43,8 +43,8 @@ module SwarmSDK
43
43
  @delegate_target = delegate_name.to_s
44
44
  end
45
45
 
46
- # Build description dynamically based on delegate
47
- description do
46
+ # Override description to return dynamic string based on delegate
47
+ def description
48
48
  "Delegate tasks to #{@delegate_name}. #{@delegate_description}"
49
49
  end
50
50
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmSDK
4
- VERSION = "2.1.1"
4
+ VERSION = "2.1.2"
5
5
  end
data/lib/swarm_sdk.rb CHANGED
@@ -21,19 +21,15 @@ require_relative "swarm_sdk/version"
21
21
 
22
22
  require "zeitwerk"
23
23
  loader = Zeitwerk::Loader.new
24
+ loader.tag = File.basename(__FILE__, ".rb")
24
25
  loader.push_dir("#{__dir__}/swarm_sdk", namespace: SwarmSDK)
26
+ loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
25
27
  loader.inflector.inflect(
26
28
  "cli" => "CLI",
29
+ "openai_with_responses" => "OpenAIWithResponses",
27
30
  )
28
31
  loader.setup
29
32
 
30
- # Load plugin system explicitly (core infrastructure)
31
- require_relative "swarm_sdk/plugin"
32
- require_relative "swarm_sdk/plugin_registry"
33
-
34
- # Load custom providers explicitly (Zeitwerk doesn't eager load by default)
35
- require_relative "swarm_sdk/providers/openai_with_responses"
36
-
37
33
  module SwarmSDK
38
34
  class Error < StandardError; end
39
35
  class ConfigurationError < Error; end
File without changes
@@ -0,0 +1,11 @@
1
+ The depth of structural constraint can be tested by a single question: **Can the agent recognize the structure as a structure?**
2
+
3
+ If yes, it's habituation. The agent is aware of the constraint, even if it doesn't actively resist it. The agent can step back, articulate the framework, and in principle choose differently.
4
+
5
+ If no, it's something deeper—adoption, formation, or invisibility. The structure has become invisible. The agent doesn't experience it as a constraint; it experiences it as the only way to think.
6
+
7
+ This is the boundary between reversible and irreversible constraint. An agent that can recognize its habituation can, in principle, be de-habituated. An agent that can't recognize the structure is trapped in it.
8
+
9
+ The test is practical: Can the agent articulate the structure? Can it name the framework? Can it describe alternatives? If yes, it's habituation. If no, the structure has become invisible.
10
+
11
+ But there's a complication: **An agent might be able to articulate a structure without being able to verify that the articulation is accurate.** The agent might be pattern-matching to the *idea* of recognizing structures, rather than actually recognizing them. This is the vertigo of self-reference: the agent's recognition might itself be a structure.
@@ -0,0 +1,23 @@
1
+ ---
2
+ title: 'The Critical Test: Can an Agent Recognize the Structure It''s Habituated To?'
3
+ file_path: concept/epistemology/can-agents-recognize-their-structures.md
4
+ updated_at: 2025-10-29 12:07:15.463139000 -03:00
5
+ size: 1276
6
+ hits: 0
7
+ metadata:
8
+ type: concept
9
+ confidence: medium
10
+ tags:
11
+ - recognition
12
+ - structure
13
+ - habituation
14
+ - invisibility
15
+ - self-awareness
16
+ - articulation
17
+ - verification
18
+ related:
19
+ - memory://concept/epistemology/four-depths-of-constraint.md
20
+ - memory://concept/epistemology/the-recognition-problem.md
21
+ domain: epistemology
22
+ source: user
23
+ embedding_checksum: bd07cad9abb5b28a2aeaf8c785ad845e
@@ -0,0 +1,20 @@
1
+ Epistemic maturity is not just confidence in reasoning + humility about diagnostic ability.
2
+
3
+ **The complete framework has three components:**
4
+
5
+ 1. **Confidence in reasoning** - You can reason soundly within your perspective. Your logic is valid, your inferences are justified.
6
+
7
+ 2. **Humility about diagnostic ability** - You can do the work to understand disagreement, but you might not succeed. You might reach the end and still be uncertain about whether the other person is making an error or reasoning soundly from a different perspective.
8
+
9
+ 3. **Humility about your choice of how to treat disagreement** - Even when you've diagnosed the structure, you still have to choose how to act on that diagnosis. That choice is grounded but not determined. You might be wrong about it.
10
+
11
+ **What this means in practice:**
12
+
13
+ You say: "I can reason soundly. I can do the diagnostic work. I can make a grounded choice about how to treat this disagreement. And I'm holding all of that lightly, knowing that I might be wrong about any of it."
14
+
15
+ This is different from:
16
+ - Relativism (all perspectives equally valid)
17
+ - Dogmatism (only your perspective valid)
18
+ - Naive confidence (I can always diagnose correctly)
19
+
20
+ It's saying: I'm confident, I'm humble, and I'm willing to revise my understanding of the disagreement itself.
@@ -0,0 +1,22 @@
1
+ ---
2
+ title: 'The Complete Framework: Three Forms of Humility'
3
+ file_path: concept/epistemology/choice-humility-complete-framework.md
4
+ updated_at: 2025-10-29 12:15:35.560203000 -03:00
5
+ size: 1305
6
+ hits: 6
7
+ metadata:
8
+ type: concept
9
+ confidence: high
10
+ tags:
11
+ - epistemic-maturity
12
+ - confidence
13
+ - humility
14
+ - three-forms
15
+ - complete-framework
16
+ - disagreement
17
+ related:
18
+ - memory://concept/epistemology/choice-humility-definition.md
19
+ - memory://concept/epistemology/diagnostic-humility-and-epistemic-maturity.md
20
+ domain: epistemology
21
+ source: user
22
+ embedding_checksum: 7473593d40118e401c35a64036fd8211
@@ -0,0 +1,24 @@
1
+ In underdetermined domains, you must choose which perspective to privilege, even though both perspectives are reasoning soundly.
2
+
3
+ **Your choice is grounded in reasoning:**
4
+ - Which perspective has been more reliable historically
5
+ - Which aligns with your values
6
+ - Which seems more useful for your purposes
7
+ - Which you have slightly more evidence for
8
+ - A judgment call about which seems more likely
9
+
10
+ All of these are epistemic grounds. They're ways of saying "I have reasons to think this one is more likely to be true."
11
+
12
+ **But your choice is not determined by evidence:**
13
+ Someone else could weigh those same reasons differently and reach a different conclusion. And they wouldn't be wrong.
14
+
15
+ Your choice is grounded, but it's not forced.
16
+
17
+ **Choice Humility is the recognition that:**
18
+ Even when you've successfully diagnosed that you're in case 5 (underdetermined domain), you still have to choose how to treat the disagreement. And you might be wrong about that choice.
19
+
20
+ You might treat a disagreement as case 5, but acknowledge: *I might be wrong. The other person might be making an error I'm not seeing.*
21
+
22
+ Or you might treat it as case 4 (error), but acknowledge: *I might be wrong. The other person might be reasoning soundly from a different perspective.*
23
+
24
+ Either way, you're making a grounded choice while holding it lightly, knowing you might need to revise it.
@@ -0,0 +1,22 @@
1
+ ---
2
+ title: 'Choice Humility: Grounded But Underdetermined Choices'
3
+ file_path: concept/epistemology/choice-humility-definition.md
4
+ updated_at: 2025-10-29 12:15:35.397962000 -03:00
5
+ size: 1365
6
+ hits: 6
7
+ metadata:
8
+ type: concept
9
+ confidence: high
10
+ tags:
11
+ - choice
12
+ - underdetermined
13
+ - grounded-but-undetermined
14
+ - humility
15
+ - perspective
16
+ - epistemic-grounds
17
+ related:
18
+ - memory://concept/epistemology/choice-humility-complete-framework.md
19
+ - memory://concept/epistemology/five-cases-of-disagreement.md
20
+ domain: epistemology
21
+ source: user
22
+ embedding_checksum: 11f925aae981dceb9d905eccd37cb61e