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/claude_swarm/cli.rb
CHANGED
|
@@ -43,9 +43,8 @@ module ClaudeSwarm
|
|
|
43
43
|
type: :string,
|
|
44
44
|
desc: "Root directory for resolving relative paths (defaults to current directory)"
|
|
45
45
|
def start(config_file = nil)
|
|
46
|
-
#
|
|
47
|
-
root_dir = options[:root_dir] || Dir.pwd
|
|
48
|
-
ENV["CLAUDE_SWARM_ROOT_DIR"] = File.expand_path(root_dir)
|
|
46
|
+
# Determine root directory for this session
|
|
47
|
+
root_dir = File.expand_path(options[:root_dir] || Dir.pwd)
|
|
49
48
|
|
|
50
49
|
# Resolve config path relative to root directory
|
|
51
50
|
config_path = config_file || "claude-swarm.yml"
|
|
@@ -71,7 +70,7 @@ module ClaudeSwarm
|
|
|
71
70
|
end
|
|
72
71
|
|
|
73
72
|
begin
|
|
74
|
-
config = Configuration.new(config_path, base_dir:
|
|
73
|
+
config = Configuration.new(config_path, base_dir: root_dir, options: options)
|
|
75
74
|
generator = McpGenerator.new(config, vibe: options[:vibe])
|
|
76
75
|
orchestrator = Orchestrator.new(
|
|
77
76
|
config,
|
|
@@ -547,24 +546,23 @@ module ClaudeSwarm
|
|
|
547
546
|
exit(1)
|
|
548
547
|
end
|
|
549
548
|
|
|
550
|
-
#
|
|
549
|
+
# Load the original root directory from session
|
|
551
550
|
root_dir_file = File.join(session_path, "root_directory")
|
|
552
|
-
if File.exist?(root_dir_file)
|
|
551
|
+
root_dir = if File.exist?(root_dir_file)
|
|
553
552
|
original_dir = File.read(root_dir_file).strip
|
|
554
553
|
if Dir.exist?(original_dir)
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
say("Changed to original directory: #{original_dir}", :green) unless options[:prompt]
|
|
554
|
+
say("Using original directory: #{original_dir}", :green) unless options[:prompt]
|
|
555
|
+
original_dir
|
|
558
556
|
else
|
|
559
557
|
error("Original directory no longer exists: #{original_dir}")
|
|
560
558
|
exit(1)
|
|
561
559
|
end
|
|
562
560
|
else
|
|
563
561
|
# If no root_directory file, use current directory
|
|
564
|
-
|
|
562
|
+
Dir.pwd
|
|
565
563
|
end
|
|
566
564
|
|
|
567
|
-
config = Configuration.new(config_file, base_dir:
|
|
565
|
+
config = Configuration.new(config_file, base_dir: root_dir)
|
|
568
566
|
|
|
569
567
|
# Load session metadata if it exists to check for worktree info
|
|
570
568
|
session_metadata_file = File.join(session_path, "session_metadata.json")
|
|
@@ -96,9 +96,8 @@ module ClaudeSwarm
|
|
|
96
96
|
main_instance = config.dig("swarm", "main")
|
|
97
97
|
|
|
98
98
|
# Get base directory from session metadata or root_directory file
|
|
99
|
-
base_dir = ClaudeSwarm.root_dir
|
|
100
99
|
root_dir_file = File.join(session_dir, "root_directory")
|
|
101
|
-
base_dir = File.
|
|
100
|
+
base_dir = File.exist?(root_dir_file) ? File.read(root_dir_file).strip : Dir.pwd
|
|
102
101
|
|
|
103
102
|
# Get all directories - handle both string and array formats
|
|
104
103
|
dir_config = config.dig("swarm", "instances", main_instance, "directory")
|
|
@@ -13,13 +13,12 @@ module ClaudeSwarm
|
|
|
13
13
|
ENV_VAR_WITH_DEFAULT_PATTERN = /\$\{([^:}]+)(:=([^}]*))?\}/
|
|
14
14
|
O_SERIES_MODEL_PATTERN = /^(o\d+(\s+(Preview|preview))?(-pro|-mini|-deep-research|-mini-deep-research)?|gpt-5(-mini|-nano)?)$/
|
|
15
15
|
|
|
16
|
-
attr_reader :config, :config_path, :swarm, :swarm_name, :main_instance, :instances, :
|
|
16
|
+
attr_reader :config, :config_path, :swarm, :swarm_name, :main_instance, :instances, :base_dir
|
|
17
17
|
|
|
18
18
|
def initialize(config_path, base_dir: nil, options: {})
|
|
19
19
|
@config_path = Pathname.new(config_path).expand_path
|
|
20
20
|
@config_dir = @config_path.dirname
|
|
21
|
-
@base_dir = base_dir || @config_dir
|
|
22
|
-
@root_directory = @base_dir
|
|
21
|
+
@base_dir = base_dir || @config_dir.to_s
|
|
23
22
|
@options = options
|
|
24
23
|
load_and_validate
|
|
25
24
|
end
|
|
@@ -38,7 +38,7 @@ module ClaudeSwarm
|
|
|
38
38
|
@session_log_path = File.join(@session_path, "session.log")
|
|
39
39
|
else
|
|
40
40
|
# Generate new session path
|
|
41
|
-
session_params = { working_dir:
|
|
41
|
+
session_params = { working_dir: @config.base_dir }
|
|
42
42
|
session_params[:session_id] = @provided_session_id if @provided_session_id
|
|
43
43
|
@session_path = SessionPath.generate(**session_params)
|
|
44
44
|
SessionPath.ensure_directory(@session_path)
|
|
@@ -49,7 +49,6 @@ module ClaudeSwarm
|
|
|
49
49
|
|
|
50
50
|
end
|
|
51
51
|
ENV["CLAUDE_SWARM_SESSION_PATH"] = @session_path
|
|
52
|
-
ENV["CLAUDE_SWARM_ROOT_DIR"] = ClaudeSwarm.root_dir
|
|
53
52
|
|
|
54
53
|
# Initialize components that depend on session path
|
|
55
54
|
@process_tracker = ProcessTracker.new(@session_path)
|
|
@@ -235,13 +234,11 @@ module ClaudeSwarm
|
|
|
235
234
|
before_commands_dir = parent_dir
|
|
236
235
|
end
|
|
237
236
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
exit(1)
|
|
244
|
-
end
|
|
237
|
+
success = execute_before_commands?(before_commands, chdir: before_commands_dir)
|
|
238
|
+
unless success
|
|
239
|
+
non_interactive_output { print("❌ Before commands failed. Aborting swarm launch.") }
|
|
240
|
+
cleanup_all
|
|
241
|
+
exit(1)
|
|
245
242
|
end
|
|
246
243
|
|
|
247
244
|
non_interactive_output do
|
|
@@ -262,19 +259,18 @@ module ClaudeSwarm
|
|
|
262
259
|
end
|
|
263
260
|
|
|
264
261
|
# Execute the main instance - this will cascade to other instances via MCP
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
end
|
|
262
|
+
# Execute main Claude instance with unbundled environment to avoid bundler conflicts
|
|
263
|
+
# This ensures the main instance runs in a clean environment without inheriting
|
|
264
|
+
# Claude Swarm's BUNDLE_* environment variables
|
|
265
|
+
main_dir = main_instance[:directory]
|
|
266
|
+
Bundler.with_unbundled_env do
|
|
267
|
+
if @non_interactive_prompt
|
|
268
|
+
stream_to_session_log(*command, chdir: main_dir)
|
|
269
|
+
else
|
|
270
|
+
system_with_pid!(*command, chdir: main_dir) do |pid|
|
|
271
|
+
@process_tracker.track_pid(pid, "claude_#{@config.main_instance}")
|
|
272
|
+
non_interactive_output do
|
|
273
|
+
puts "✓ Claude instance started with PID: #{pid}"
|
|
278
274
|
end
|
|
279
275
|
end
|
|
280
276
|
end
|
|
@@ -306,12 +302,12 @@ module ClaudeSwarm
|
|
|
306
302
|
puts
|
|
307
303
|
end
|
|
308
304
|
|
|
309
|
-
def execute_before_commands?(commands)
|
|
310
|
-
execute_commands(commands, phase: "before", fail_fast: true)
|
|
305
|
+
def execute_before_commands?(commands, chdir:)
|
|
306
|
+
execute_commands(commands, phase: "before", fail_fast: true, chdir: chdir)
|
|
311
307
|
end
|
|
312
308
|
|
|
313
|
-
def execute_after_commands?(commands)
|
|
314
|
-
execute_commands(commands, phase: "after", fail_fast: false)
|
|
309
|
+
def execute_after_commands?(commands, chdir:)
|
|
310
|
+
execute_commands(commands, phase: "after", fail_fast: false, chdir: chdir)
|
|
315
311
|
end
|
|
316
312
|
|
|
317
313
|
def execute_after_commands_once
|
|
@@ -336,16 +332,14 @@ module ClaudeSwarm
|
|
|
336
332
|
after_commands_dir = parent_dir
|
|
337
333
|
end
|
|
338
334
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
end
|
|
335
|
+
non_interactive_output do
|
|
336
|
+
print("⚙️ Executing after commands...")
|
|
337
|
+
end
|
|
343
338
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
end
|
|
339
|
+
success = execute_after_commands?(after_commands, chdir: after_commands_dir)
|
|
340
|
+
unless success
|
|
341
|
+
non_interactive_output do
|
|
342
|
+
puts "⚠️ Some after commands failed"
|
|
349
343
|
end
|
|
350
344
|
end
|
|
351
345
|
end
|
|
@@ -357,7 +351,7 @@ module ClaudeSwarm
|
|
|
357
351
|
|
|
358
352
|
# Save the root directory
|
|
359
353
|
root_dir_file = File.join(session_path, "root_directory")
|
|
360
|
-
File.write(root_dir_file,
|
|
354
|
+
File.write(root_dir_file, @config.base_dir)
|
|
361
355
|
|
|
362
356
|
# Save session metadata
|
|
363
357
|
metadata_file = File.join(session_path, "session_metadata.json")
|
|
@@ -366,7 +360,7 @@ module ClaudeSwarm
|
|
|
366
360
|
|
|
367
361
|
def build_session_metadata
|
|
368
362
|
{
|
|
369
|
-
"root_directory" =>
|
|
363
|
+
"root_directory" => @config.base_dir,
|
|
370
364
|
"timestamp" => Time.now.utc.iso8601,
|
|
371
365
|
"start_time" => @start_time.utc.iso8601,
|
|
372
366
|
"swarm_name" => @config.swarm_name,
|
|
@@ -642,12 +636,12 @@ module ClaudeSwarm
|
|
|
642
636
|
end
|
|
643
637
|
end
|
|
644
638
|
|
|
645
|
-
def stream_to_session_log(*command)
|
|
639
|
+
def stream_to_session_log(*command, chdir:)
|
|
646
640
|
# Setup logger for session logging
|
|
647
641
|
logger = Logger.new(@session_log_path, level: :info, progname: @config.main_instance)
|
|
648
642
|
|
|
649
643
|
# Use Open3.popen2e to capture stdout and stderr merged for formatting
|
|
650
|
-
Open3.popen2e(*command) do |stdin, stdout_and_stderr, wait_thr|
|
|
644
|
+
Open3.popen2e(*command, chdir: chdir) do |stdin, stdout_and_stderr, wait_thr|
|
|
651
645
|
stdin.close
|
|
652
646
|
|
|
653
647
|
# Read and process the merged output
|
|
@@ -819,7 +813,10 @@ module ClaudeSwarm
|
|
|
819
813
|
@logger ||= Logger.new(File.join(@session_path, "session.log"), level: :info, progname: "orchestrator")
|
|
820
814
|
end
|
|
821
815
|
|
|
822
|
-
def execute_commands(commands, phase:, fail_fast:)
|
|
816
|
+
def execute_commands(commands, phase:, fail_fast:, chdir:)
|
|
817
|
+
raise ArgumentError, "chdir parameter is required" if chdir.nil?
|
|
818
|
+
raise ArgumentError, "chdir must be a valid directory: #{chdir}" unless File.directory?(chdir)
|
|
819
|
+
|
|
823
820
|
all_succeeded = true
|
|
824
821
|
|
|
825
822
|
# Setup logger for session logging if we have a session path
|
|
@@ -839,13 +836,15 @@ module ClaudeSwarm
|
|
|
839
836
|
end
|
|
840
837
|
end
|
|
841
838
|
|
|
842
|
-
|
|
843
|
-
|
|
839
|
+
# Use Open3.capture2e with chdir option to execute the command
|
|
840
|
+
# This allows setting the working directory without changing the process directory
|
|
841
|
+
output, status = Open3.capture2e(command, chdir: chdir)
|
|
842
|
+
success = status.success?
|
|
844
843
|
output_separator = "-" * 80
|
|
845
844
|
|
|
846
845
|
logger.info { "Command output:" }
|
|
847
846
|
logger.info { output }
|
|
848
|
-
logger.info { "Exit status: #{
|
|
847
|
+
logger.info { "Exit status: #{status.exitstatus}" }
|
|
849
848
|
logger.info { output_separator }
|
|
850
849
|
|
|
851
850
|
# Show output if in debug mode or if command failed
|
|
@@ -854,7 +853,7 @@ module ClaudeSwarm
|
|
|
854
853
|
output_prefix = phase == "after" ? "After command" : "Command"
|
|
855
854
|
puts "#{output_prefix} #{index + 1} output:"
|
|
856
855
|
puts output
|
|
857
|
-
print("Exit status: #{
|
|
856
|
+
print("Exit status: #{status.exitstatus}")
|
|
858
857
|
end
|
|
859
858
|
end
|
|
860
859
|
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module ClaudeSwarm
|
|
4
4
|
module SystemUtils
|
|
5
|
-
def system!(*args)
|
|
6
|
-
system(*args)
|
|
5
|
+
def system!(*args, **options)
|
|
6
|
+
system(*args, **options)
|
|
7
7
|
handle_command_failure(last_status, args)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def system_with_pid!(*args)
|
|
10
|
+
def system_with_pid!(*args, **options)
|
|
11
11
|
# Spawn the process - by default, inherits the parent's I/O
|
|
12
|
-
pid = Process.spawn(*args)
|
|
12
|
+
pid = Process.spawn(*args, **options)
|
|
13
13
|
|
|
14
14
|
# Yield the PID to the block if given
|
|
15
15
|
yield(pid) if block_given?
|
data/lib/claude_swarm/version.rb
CHANGED
data/lib/claude_swarm.rb
CHANGED
|
@@ -26,25 +26,24 @@ require "fast_mcp"
|
|
|
26
26
|
require "mcp_client"
|
|
27
27
|
require "thor"
|
|
28
28
|
|
|
29
|
+
require_relative "claude_swarm/version"
|
|
29
30
|
# Zeitwerk setup
|
|
30
31
|
require "zeitwerk"
|
|
31
32
|
loader = Zeitwerk::Loader.new
|
|
32
|
-
loader.tag = "
|
|
33
|
-
|
|
33
|
+
loader.tag = File.basename(__FILE__, ".rb")
|
|
34
34
|
loader.ignore("#{__dir__}/claude_swarm/templates")
|
|
35
|
+
loader.push_dir("#{__dir__}/claude_swarm", namespace: ClaudeSwarm)
|
|
36
|
+
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
|
35
37
|
loader.inflector.inflect(
|
|
36
38
|
"cli" => "CLI",
|
|
37
39
|
"openai" => "OpenAI",
|
|
38
40
|
)
|
|
41
|
+
loader.setup
|
|
39
42
|
|
|
40
43
|
module ClaudeSwarm
|
|
41
44
|
class Error < StandardError; end
|
|
42
45
|
|
|
43
46
|
class << self
|
|
44
|
-
def root_dir
|
|
45
|
-
ENV.fetch("CLAUDE_SWARM_ROOT_DIR") { Dir.pwd }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
47
|
def home_dir
|
|
49
48
|
ENV.fetch("CLAUDE_SWARM_HOME") { File.expand_path("~/.claude-swarm") }
|
|
50
49
|
end
|
|
@@ -66,6 +65,3 @@ module ClaudeSwarm
|
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
67
|
end
|
|
69
|
-
|
|
70
|
-
loader.push_dir("#{__dir__}/claude_swarm", namespace: ClaudeSwarm)
|
|
71
|
-
loader.setup
|
|
@@ -14,9 +14,9 @@ module SwarmCLI
|
|
|
14
14
|
|
|
15
15
|
def initialize(options)
|
|
16
16
|
@options = options
|
|
17
|
-
# Create scratchpad
|
|
18
|
-
|
|
19
|
-
@scratchpad = SwarmSDK::
|
|
17
|
+
# Create volatile scratchpad for MCP server
|
|
18
|
+
# Note: Scratchpad is always volatile - data is not persisted between sessions
|
|
19
|
+
@scratchpad = SwarmSDK::Tools::Stores::ScratchpadStorage.new
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def execute
|
|
@@ -5,7 +5,7 @@ module SwarmCLI
|
|
|
5
5
|
#
|
|
6
6
|
# Supports:
|
|
7
7
|
# - YAML files (.yml, .yaml) - loaded via SwarmSDK::Swarm.load
|
|
8
|
-
# - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm instance
|
|
8
|
+
# - Ruby DSL files (.rb) - executed and expected to return a SwarmSDK::Swarm or SwarmSDK::NodeOrchestrator instance
|
|
9
9
|
#
|
|
10
10
|
# @example Load YAML config
|
|
11
11
|
# swarm = ConfigLoader.load("config.yml")
|
|
@@ -19,10 +19,10 @@ module SwarmCLI
|
|
|
19
19
|
#
|
|
20
20
|
# Detects file type by extension:
|
|
21
21
|
# - .yml, .yaml -> Load as YAML using SwarmSDK::Swarm.load
|
|
22
|
-
# - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm instance
|
|
22
|
+
# - .rb -> Execute as Ruby DSL and expect SwarmSDK::Swarm or SwarmSDK::NodeOrchestrator instance
|
|
23
23
|
#
|
|
24
24
|
# @param path [String, Pathname] Path to configuration file
|
|
25
|
-
# @return [SwarmSDK::Swarm] Configured swarm instance
|
|
25
|
+
# @return [SwarmSDK::Swarm, SwarmSDK::NodeOrchestrator] Configured swarm or orchestrator instance
|
|
26
26
|
# @raise [SwarmCLI::ConfigurationError] If file not found or invalid format
|
|
27
27
|
def load(path)
|
|
28
28
|
path = Pathname.new(path).expand_path
|
|
@@ -59,12 +59,12 @@ module SwarmCLI
|
|
|
59
59
|
# Load Ruby DSL configuration file
|
|
60
60
|
#
|
|
61
61
|
# Executes the Ruby file in a clean binding and expects it to return
|
|
62
|
-
# a SwarmSDK::Swarm instance. The file should
|
|
63
|
-
# create a Swarm instance directly.
|
|
62
|
+
# a SwarmSDK::Swarm or SwarmSDK::NodeOrchestrator instance. The file should
|
|
63
|
+
# use SwarmSDK.build or create a Swarm/NodeOrchestrator instance directly.
|
|
64
64
|
#
|
|
65
65
|
# @param path [Pathname] Path to Ruby DSL file
|
|
66
|
-
# @return [SwarmSDK::Swarm] Configured swarm instance
|
|
67
|
-
# @raise [ConfigurationError] If file doesn't return a
|
|
66
|
+
# @return [SwarmSDK::Swarm, SwarmSDK::NodeOrchestrator] Configured swarm or orchestrator instance
|
|
67
|
+
# @raise [ConfigurationError] If file doesn't return a valid instance
|
|
68
68
|
def load_ruby_dsl(path)
|
|
69
69
|
# Read the file content
|
|
70
70
|
content = path.read
|
|
@@ -73,10 +73,11 @@ module SwarmCLI
|
|
|
73
73
|
# This allows the DSL file to use SwarmSDK.build directly
|
|
74
74
|
result = eval(content, binding, path.to_s, 1) # rubocop:disable Security/Eval
|
|
75
75
|
|
|
76
|
-
# Validate result is a Swarm instance
|
|
77
|
-
|
|
76
|
+
# Validate result is a Swarm or NodeOrchestrator instance
|
|
77
|
+
# Both have the same execute(prompt) interface
|
|
78
|
+
unless result.is_a?(SwarmSDK::Swarm) || result.is_a?(SwarmSDK::NodeOrchestrator)
|
|
78
79
|
raise ConfigurationError,
|
|
79
|
-
"Ruby DSL file must return a SwarmSDK::Swarm instance. " \
|
|
80
|
+
"Ruby DSL file must return a SwarmSDK::Swarm or SwarmSDK::NodeOrchestrator instance. " \
|
|
80
81
|
"Got: #{result.class}. " \
|
|
81
82
|
"Use: SwarmSDK.build { ... } or Swarm.new(...)"
|
|
82
83
|
end
|
data/lib/swarm_cli/version.rb
CHANGED
data/lib/swarm_cli.rb
CHANGED
|
@@ -22,7 +22,9 @@ require_relative "swarm_cli/version"
|
|
|
22
22
|
|
|
23
23
|
require "zeitwerk"
|
|
24
24
|
loader = Zeitwerk::Loader.new
|
|
25
|
+
loader.tag = File.basename(__FILE__, ".rb")
|
|
25
26
|
loader.push_dir("#{__dir__}/swarm_cli", namespace: SwarmCLI)
|
|
27
|
+
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
|
26
28
|
loader.inflector.inflect(
|
|
27
29
|
"cli" => "CLI",
|
|
28
30
|
"ui" => "UI",
|
|
@@ -171,12 +171,6 @@ module SwarmMemory
|
|
|
171
171
|
|
|
172
172
|
content = File.read(md_file)
|
|
173
173
|
|
|
174
|
-
# Check if it's a stub (redirect)
|
|
175
|
-
if stub_content?(content)
|
|
176
|
-
target_path = extract_redirect_target(content)
|
|
177
|
-
return read(file_path: target_path) if target_path
|
|
178
|
-
end
|
|
179
|
-
|
|
180
174
|
# Increment hit counter
|
|
181
175
|
increment_hits(file_path)
|
|
182
176
|
|
|
@@ -205,12 +199,6 @@ module SwarmMemory
|
|
|
205
199
|
|
|
206
200
|
content = File.read(md_file)
|
|
207
201
|
|
|
208
|
-
# Follow stub redirect if applicable
|
|
209
|
-
if stub_content?(content)
|
|
210
|
-
target_path = extract_redirect_target(content)
|
|
211
|
-
return read_entry(file_path: target_path) if target_path
|
|
212
|
-
end
|
|
213
|
-
|
|
214
202
|
# Read metadata
|
|
215
203
|
yaml_data = File.exist?(yaml_file) ? YAML.load_file(yaml_file, permitted_classes: [Time, Date, Symbol]) : {}
|
|
216
204
|
|
|
@@ -95,22 +95,82 @@ module SwarmMemory
|
|
|
95
95
|
)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
-
# Read content from storage
|
|
98
|
+
# Read content from storage, automatically following stub redirects
|
|
99
99
|
#
|
|
100
100
|
# @param file_path [String] Path to read from
|
|
101
101
|
# @return [String] Content at the path
|
|
102
102
|
def read(file_path:)
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
entry = read_entry(file_path: file_path)
|
|
104
|
+
entry.content
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
-
# Read full entry with metadata
|
|
107
|
+
# Read full entry with metadata, automatically following stub redirects
|
|
108
|
+
#
|
|
109
|
+
# Stub redirects are created by MemoryDefrag when merging/moving entries.
|
|
110
|
+
# This method transparently follows redirect chains up to 5 levels deep.
|
|
108
111
|
#
|
|
109
112
|
# @param file_path [String] Path to read from
|
|
113
|
+
# @param visited [Array<String>] Internal: tracks visited paths to detect circular redirects
|
|
110
114
|
# @return [Entry] Full entry object
|
|
111
|
-
|
|
115
|
+
# @raise [ArgumentError] If path not found, circular redirect detected, or too many redirects
|
|
116
|
+
def read_entry(file_path:, visited: [])
|
|
112
117
|
normalized_path = PathNormalizer.normalize(file_path)
|
|
113
|
-
|
|
118
|
+
|
|
119
|
+
# Detect circular redirects immediately
|
|
120
|
+
if visited.include?(normalized_path)
|
|
121
|
+
cycle = visited + [normalized_path]
|
|
122
|
+
raise ArgumentError,
|
|
123
|
+
"Circular redirect detected in memory storage: #{cycle.join(" → ")}\n\n" \
|
|
124
|
+
"This indicates corrupted stub files. Please run MemoryDefrag to repair:\n " \
|
|
125
|
+
"MemoryDefrag(action: \"analyze\")"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Check depth limit (prevent infinite chains)
|
|
129
|
+
if visited.size >= 5
|
|
130
|
+
chain = visited + [normalized_path]
|
|
131
|
+
raise ArgumentError,
|
|
132
|
+
"Memory redirect chain too deep (>5 redirects): #{chain.join(" → ")}\n\n" \
|
|
133
|
+
"This indicates fragmented memory storage. Please run maintenance:\n " \
|
|
134
|
+
"MemoryDefrag(action: \"full\", dry_run: true) # Preview first\n " \
|
|
135
|
+
"MemoryDefrag(action: \"full\", dry_run: false) # Execute"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Read entry from adapter
|
|
139
|
+
begin
|
|
140
|
+
entry = @adapter.read_entry(file_path: normalized_path)
|
|
141
|
+
rescue ArgumentError
|
|
142
|
+
# If this is a redirect target that doesn't exist, provide helpful error
|
|
143
|
+
if visited.empty?
|
|
144
|
+
# Not a redirect, just re-raise original error
|
|
145
|
+
raise
|
|
146
|
+
else
|
|
147
|
+
original_path = visited.first
|
|
148
|
+
raise ArgumentError,
|
|
149
|
+
"memory://#{original_path} was redirected to memory://#{normalized_path}, but the target was not found.\n\n" \
|
|
150
|
+
"The original entry may have been merged or moved incorrectly. " \
|
|
151
|
+
"Run MemoryDefrag to identify and fix broken redirects:\n " \
|
|
152
|
+
"MemoryDefrag(action: \"analyze\")"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Check if this is a stub redirect
|
|
157
|
+
if entry.metadata && entry.metadata["stub"] == true
|
|
158
|
+
redirect_target = entry.metadata["redirect_to"]
|
|
159
|
+
|
|
160
|
+
# Validate redirect target exists
|
|
161
|
+
if redirect_target.nil? || redirect_target.strip.empty?
|
|
162
|
+
raise ArgumentError,
|
|
163
|
+
"memory://#{normalized_path} is a stub with invalid redirect metadata.\n\n" \
|
|
164
|
+
"This should never happen (stubs are created by MemoryDefrag). " \
|
|
165
|
+
"The stub file may be corrupted. Please report this as a bug."
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Follow redirect recursively, tracking visited paths
|
|
169
|
+
return read_entry(file_path: redirect_target, visited: visited + [normalized_path])
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Not a stub, return the entry
|
|
173
|
+
entry
|
|
114
174
|
end
|
|
115
175
|
|
|
116
176
|
# Delete an entry
|
|
@@ -207,6 +207,19 @@ module SwarmMemory
|
|
|
207
207
|
agent_definition.memory_enabled?
|
|
208
208
|
end
|
|
209
209
|
|
|
210
|
+
# Contribute to agent serialization
|
|
211
|
+
#
|
|
212
|
+
# Preserves memory configuration when agents are cloned (e.g., in NodeOrchestrator).
|
|
213
|
+
# This allows memory configuration to persist across node transitions.
|
|
214
|
+
#
|
|
215
|
+
# @param agent_definition [Agent::Definition] Agent definition
|
|
216
|
+
# @return [Hash] Memory config to include in to_h
|
|
217
|
+
def serialize_config(agent_definition:)
|
|
218
|
+
return {} unless agent_definition.memory
|
|
219
|
+
|
|
220
|
+
{ memory: agent_definition.memory }
|
|
221
|
+
end
|
|
222
|
+
|
|
210
223
|
# Lifecycle: Agent initialized
|
|
211
224
|
#
|
|
212
225
|
# Filters tools by mode (removing non-mode tools), registers LoadSkill,
|
|
@@ -287,6 +300,7 @@ module SwarmMemory
|
|
|
287
300
|
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
288
301
|
storage = @storages[agent_name]
|
|
289
302
|
return [] unless storage&.semantic_index
|
|
303
|
+
return [] if prompt.empty?
|
|
290
304
|
|
|
291
305
|
# Adaptive threshold based on query length
|
|
292
306
|
# Short queries use lower threshold as they have less semantic richness
|
|
@@ -747,7 +747,11 @@ module SwarmMemory
|
|
|
747
747
|
# @param to [String] Target path
|
|
748
748
|
# @param reason [String] Reason (merged, moved)
|
|
749
749
|
# @return [void]
|
|
750
|
+
# @raise [ArgumentError] If target path or reason is nil/empty
|
|
750
751
|
def create_stub(from:, to:, reason:)
|
|
752
|
+
raise ArgumentError, "Cannot create stub without target path" if to.nil? || to.strip.empty?
|
|
753
|
+
raise ArgumentError, "Cannot create stub without reason" if reason.nil? || reason.strip.empty?
|
|
754
|
+
|
|
751
755
|
stub_content = "# #{reason} → #{to}\n\nThis entry was #{reason} into #{to}."
|
|
752
756
|
|
|
753
757
|
@adapter.write(
|
|
@@ -100,6 +100,8 @@ module SwarmMemory
|
|
|
100
100
|
desc: "Glob pattern - target concept/, fact/, skill/, or experience/ only (e.g., 'skill/**', 'concept/ruby/*', 'fact/people/*.md')",
|
|
101
101
|
required: true
|
|
102
102
|
|
|
103
|
+
MAX_RESULTS = 500 # Limit results to prevent overwhelming output
|
|
104
|
+
|
|
103
105
|
# Initialize with storage instance
|
|
104
106
|
#
|
|
105
107
|
# @param storage [Core::Storage] Storage instance
|
|
@@ -124,6 +126,14 @@ module SwarmMemory
|
|
|
124
126
|
return "No entries found matching pattern '#{pattern}'"
|
|
125
127
|
end
|
|
126
128
|
|
|
129
|
+
# Limit results
|
|
130
|
+
if entries.count > MAX_RESULTS
|
|
131
|
+
entries = entries.take(MAX_RESULTS)
|
|
132
|
+
truncated = true
|
|
133
|
+
else
|
|
134
|
+
truncated = false
|
|
135
|
+
end
|
|
136
|
+
|
|
127
137
|
result = []
|
|
128
138
|
result << "Memory entries matching '#{pattern}' (#{entries.size} #{entries.size == 1 ? "entry" : "entries"}):"
|
|
129
139
|
|
|
@@ -131,7 +141,20 @@ module SwarmMemory
|
|
|
131
141
|
result << " memory://#{entry[:path]} - \"#{entry[:title]}\" (#{format_bytes(entry[:size])})"
|
|
132
142
|
end
|
|
133
143
|
|
|
134
|
-
result.join("\n")
|
|
144
|
+
output = result.join("\n")
|
|
145
|
+
|
|
146
|
+
# Add system reminder if truncated
|
|
147
|
+
if truncated
|
|
148
|
+
output += <<~REMINDER
|
|
149
|
+
|
|
150
|
+
<system-reminder>
|
|
151
|
+
Results limited to first #{MAX_RESULTS} matches (sorted by most recently modified).
|
|
152
|
+
Consider using a more specific pattern to narrow your search.
|
|
153
|
+
</system-reminder>
|
|
154
|
+
REMINDER
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
output
|
|
135
158
|
rescue ArgumentError => e
|
|
136
159
|
validation_error(e.message)
|
|
137
160
|
end
|
|
@@ -45,8 +45,8 @@ module SwarmMemory
|
|
|
45
45
|
TAGS ARE CRITICAL: Think "What would I search for in 6 months?" For skills especially, be VERY comprehensive with tags - they're your search index.
|
|
46
46
|
|
|
47
47
|
EXAMPLES:
|
|
48
|
-
- For concept: tags: ['ruby', 'oop', 'classes', 'inheritance', 'methods']
|
|
49
|
-
- For skill: tags: ['debugging', 'api', 'http', 'errors', 'trace', 'network', 'rest']
|
|
48
|
+
- For concept: tags: (JSON) "['ruby', 'oop', 'classes', 'inheritance', 'methods']"
|
|
49
|
+
- For skill: tags: (JSON) "['debugging', 'api', 'http', 'errors', 'trace', 'network', 'rest']"
|
|
50
50
|
DESC
|
|
51
51
|
|
|
52
52
|
param :file_path,
|
data/lib/swarm_memory/version.rb
CHANGED
data/lib/swarm_memory.rb
CHANGED
|
@@ -28,7 +28,9 @@ require_relative "swarm_memory/version"
|
|
|
28
28
|
# Setup Zeitwerk loader
|
|
29
29
|
require "zeitwerk"
|
|
30
30
|
loader = Zeitwerk::Loader.new
|
|
31
|
+
loader.tag = File.basename(__FILE__, ".rb")
|
|
31
32
|
loader.push_dir("#{__dir__}/swarm_memory", namespace: SwarmMemory)
|
|
33
|
+
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
|
32
34
|
loader.setup
|
|
33
35
|
|
|
34
36
|
# Explicitly load DSL components and extensions to inject into SwarmSDK
|