claude_swarm 1.0.1 → 1.0.4
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/.claude/commands/release.md +1 -1
- data/.claude/hooks/lint-code-files.rb +65 -0
- data/.rubocop.yml +22 -2
- data/CHANGELOG.md +14 -1
- data/CLAUDE.md +1 -1
- data/CONTRIBUTING.md +69 -0
- data/README.md +27 -2
- data/Rakefile +71 -3
- data/analyze_coverage.rb +94 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
- data/docs/v2/README.md +308 -0
- data/docs/v2/guides/claude-code-agents.md +262 -0
- data/docs/v2/guides/complete-tutorial.md +3088 -0
- data/docs/v2/guides/getting-started.md +1456 -0
- data/docs/v2/guides/memory-adapters.md +998 -0
- data/docs/v2/guides/plugins.md +816 -0
- data/docs/v2/guides/quick-start-cli.md +1745 -0
- data/docs/v2/guides/rails-integration.md +1902 -0
- data/docs/v2/guides/swarm-memory.md +599 -0
- data/docs/v2/reference/cli.md +729 -0
- data/docs/v2/reference/ruby-dsl.md +2154 -0
- data/docs/v2/reference/yaml.md +1835 -0
- data/docs-team-swarm.yml +2222 -0
- data/examples/learning-assistant/assistant.md +7 -0
- data/examples/learning-assistant/example-memories/concept-example.md +90 -0
- data/examples/learning-assistant/example-memories/experience-example.md +66 -0
- data/examples/learning-assistant/example-memories/fact-example.md +76 -0
- data/examples/learning-assistant/example-memories/memory-index.md +78 -0
- data/examples/learning-assistant/example-memories/skill-example.md +168 -0
- data/examples/learning-assistant/learning_assistant.rb +34 -0
- data/examples/learning-assistant/learning_assistant.yml +20 -0
- data/examples/v2/dsl/01_basic.rb +44 -0
- data/examples/v2/dsl/02_core_parameters.rb +59 -0
- data/examples/v2/dsl/03_capabilities.rb +71 -0
- data/examples/v2/dsl/04_llm_parameters.rb +56 -0
- data/examples/v2/dsl/05_advanced_flags.rb +73 -0
- data/examples/v2/dsl/06_permissions.rb +80 -0
- data/examples/v2/dsl/07_mcp_server.rb +62 -0
- data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
- data/examples/v2/dsl/09_agent_hooks.rb +67 -0
- data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
- data/examples/v2/dsl/11_delegation.rb +60 -0
- data/examples/v2/dsl/12_complete_integration.rb +137 -0
- data/examples/v2/file_tools_swarm.yml +102 -0
- data/examples/v2/hooks/01_basic_hooks.rb +133 -0
- data/examples/v2/hooks/02_usage_tracking.rb +201 -0
- data/examples/v2/hooks/03_production_monitoring.rb +429 -0
- data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
- data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
- data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
- data/examples/v2/hooks/swarm_summary.sh +44 -0
- data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
- data/examples/v2/hooks/validate_bash.rb +59 -0
- data/examples/v2/multi_directory_permissions.yml +221 -0
- data/examples/v2/node_context_demo.rb +127 -0
- data/examples/v2/node_workflow.rb +173 -0
- data/examples/v2/path_resolution_demo.rb +216 -0
- data/examples/v2/simple-swarm-v2.rb +90 -0
- data/examples/v2/simple-swarm-v2.yml +62 -0
- data/examples/v2/swarm.yml +71 -0
- data/examples/v2/swarm_with_hooks.yml +61 -0
- data/examples/v2/swarm_with_hooks_simple.yml +25 -0
- data/examples/v2/think_tool_demo.rb +62 -0
- data/exe/swarm +6 -0
- data/lib/claude_swarm/claude_mcp_server.rb +0 -6
- data/lib/claude_swarm/cli.rb +10 -3
- data/lib/claude_swarm/commands/ps.rb +19 -20
- data/lib/claude_swarm/commands/show.rb +1 -1
- data/lib/claude_swarm/configuration.rb +10 -12
- data/lib/claude_swarm/mcp_generator.rb +10 -1
- data/lib/claude_swarm/orchestrator.rb +73 -49
- data/lib/claude_swarm/system_utils.rb +37 -11
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm/worktree_manager.rb +1 -0
- data/lib/claude_swarm/yaml_loader.rb +22 -0
- data/lib/claude_swarm.rb +7 -2
- data/lib/swarm_cli/cli.rb +201 -0
- data/lib/swarm_cli/command_registry.rb +61 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +173 -0
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
- data/lib/swarm_cli/interactive_repl.rb +918 -0
- data/lib/swarm_cli/mcp_serve_options.rb +44 -0
- data/lib/swarm_cli/mcp_tools_options.rb +59 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +151 -0
- data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
- data/lib/swarm_cli/ui/components/content_block.rb +120 -0
- data/lib/swarm_cli/ui/components/divider.rb +57 -0
- data/lib/swarm_cli/ui/components/panel.rb +62 -0
- data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
- data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
- data/lib/swarm_cli/ui/formatters/number.rb +58 -0
- data/lib/swarm_cli/ui/formatters/text.rb +77 -0
- data/lib/swarm_cli/ui/formatters/time.rb +73 -0
- data/lib/swarm_cli/ui/icons.rb +59 -0
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
- data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
- data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
- data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +44 -0
- data/lib/swarm_memory/adapters/base.rb +141 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
- data/lib/swarm_memory/chat_extension.rb +34 -0
- data/lib/swarm_memory/cli/commands.rb +306 -0
- data/lib/swarm_memory/core/entry.rb +37 -0
- data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
- data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
- data/lib/swarm_memory/core/path_normalizer.rb +75 -0
- data/lib/swarm_memory/core/semantic_index.rb +244 -0
- data/lib/swarm_memory/core/storage.rb +288 -0
- data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
- data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
- data/lib/swarm_memory/dsl/memory_config.rb +113 -0
- data/lib/swarm_memory/embeddings/embedder.rb +36 -0
- data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
- data/lib/swarm_memory/errors.rb +21 -0
- data/lib/swarm_memory/integration/cli_registration.rb +30 -0
- data/lib/swarm_memory/integration/configuration.rb +43 -0
- data/lib/swarm_memory/integration/registration.rb +31 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
- data/lib/swarm_memory/optimization/analyzer.rb +244 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
- data/lib/swarm_memory/prompts/memory.md.erb +109 -0
- data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
- data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
- data/lib/swarm_memory/search/semantic_search.rb +112 -0
- data/lib/swarm_memory/search/text_search.rb +42 -0
- data/lib/swarm_memory/search/text_similarity.rb +80 -0
- data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
- data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
- data/lib/swarm_memory/tools/load_skill.rb +313 -0
- data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
- data/lib/swarm_memory/tools/memory_delete.rb +99 -0
- data/lib/swarm_memory/tools/memory_edit.rb +185 -0
- data/lib/swarm_memory/tools/memory_glob.rb +160 -0
- data/lib/swarm_memory/tools/memory_grep.rb +247 -0
- data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
- data/lib/swarm_memory/tools/memory_read.rb +123 -0
- data/lib/swarm_memory/tools/memory_write.rb +231 -0
- data/lib/swarm_memory/utils.rb +50 -0
- data/lib/swarm_memory/version.rb +5 -0
- data/lib/swarm_memory.rb +166 -0
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +461 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
- data/lib/swarm_sdk/agent/chat.rb +1159 -0
- data/lib/swarm_sdk/agent/context.rb +112 -0
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +556 -0
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +296 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +197 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +147 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +51 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +75 -0
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/node/agent_config.rb +49 -0
- data/lib/swarm_sdk/node/builder.rb +439 -0
- data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
- data/lib/swarm_sdk/node_context.rb +170 -0
- data/lib/swarm_sdk/node_orchestrator.rb +384 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
- data/lib/swarm_sdk/swarm/builder.rb +586 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
- data/lib/swarm_sdk/swarm.rb +982 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/delegate.rb +164 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +228 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +93 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +157 -0
- data/llm.v2.txt +13407 -0
- data/rubocop/cop/security/no_reflection_methods.rb +47 -0
- data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
- data/swarm_cli.gemspec +57 -0
- data/swarm_memory.gemspec +28 -0
- data/swarm_sdk.gemspec +41 -0
- data/team.yml +1 -1
- data/team_full.yml +1875 -0
- data/{team_v2.yml → team_sdk.yml} +121 -52
- metadata +249 -6
- data/EXAMPLES.md +0 -164
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmMemory
|
|
4
|
+
module Search
|
|
5
|
+
# Semantic search using embedding similarity
|
|
6
|
+
#
|
|
7
|
+
# Finds entries similar to a query based on embedding vectors
|
|
8
|
+
# rather than exact text matching.
|
|
9
|
+
class SemanticSearch
|
|
10
|
+
# Initialize semantic search
|
|
11
|
+
#
|
|
12
|
+
# @param adapter [Adapters::Base] Storage adapter
|
|
13
|
+
# @param embedder [Embeddings::Embedder] Embedder for generating query vectors
|
|
14
|
+
def initialize(adapter:, embedder:)
|
|
15
|
+
@adapter = adapter
|
|
16
|
+
@embedder = embedder
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Search for entries similar to query
|
|
20
|
+
#
|
|
21
|
+
# @param query [String] Search query
|
|
22
|
+
# @param top_k [Integer] Number of results to return
|
|
23
|
+
# @param threshold [Float] Minimum similarity threshold (0.0-1.0)
|
|
24
|
+
# @return [Array<Hash>] Ranked results with similarity scores
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# results = search.find_similar(
|
|
28
|
+
# query: "How do I test Ruby code?",
|
|
29
|
+
# top_k: 5,
|
|
30
|
+
# threshold: 0.7
|
|
31
|
+
# )
|
|
32
|
+
# # => [
|
|
33
|
+
# # { path: "skills/testing/minitest", similarity: 0.92, title: "..." },
|
|
34
|
+
# # { path: "concepts/ruby/testing", similarity: 0.85, title: "..." }
|
|
35
|
+
# # ]
|
|
36
|
+
def find_similar(query:, top_k: 5, threshold: 0.7)
|
|
37
|
+
raise ArgumentError, "query is required" if query.nil? || query.to_s.strip.empty?
|
|
38
|
+
|
|
39
|
+
# Generate query embedding
|
|
40
|
+
query_embedding = @embedder.embed(query)
|
|
41
|
+
|
|
42
|
+
# Get all entries with embeddings
|
|
43
|
+
all_entries = @adapter.all_entries
|
|
44
|
+
entries_with_embeddings = all_entries.select { |_, entry| entry.embedded? }
|
|
45
|
+
|
|
46
|
+
return [] if entries_with_embeddings.empty?
|
|
47
|
+
|
|
48
|
+
# Calculate similarities
|
|
49
|
+
similarities = entries_with_embeddings.map do |path, entry|
|
|
50
|
+
similarity = TextSimilarity.cosine(query_embedding, entry.embedding)
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
path: path,
|
|
54
|
+
title: entry.title,
|
|
55
|
+
similarity: similarity,
|
|
56
|
+
updated_at: entry.updated_at,
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Filter by threshold and sort by similarity (descending)
|
|
61
|
+
results = similarities
|
|
62
|
+
.select { |r| r[:similarity] >= threshold }
|
|
63
|
+
.sort_by { |r| -r[:similarity] }
|
|
64
|
+
.take(top_k)
|
|
65
|
+
|
|
66
|
+
results
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Find entries similar to a given entry
|
|
70
|
+
#
|
|
71
|
+
# @param file_path [String] Path to reference entry
|
|
72
|
+
# @param top_k [Integer] Number of results to return
|
|
73
|
+
# @param threshold [Float] Minimum similarity threshold
|
|
74
|
+
# @return [Array<Hash>] Ranked results (excluding the reference entry)
|
|
75
|
+
def find_similar_to_entry(file_path:, top_k: 5, threshold: 0.7)
|
|
76
|
+
# Get reference entry
|
|
77
|
+
reference_entry = @adapter.read_entry(file_path: file_path)
|
|
78
|
+
|
|
79
|
+
unless reference_entry.embedded?
|
|
80
|
+
raise SearchError, "Entry #{file_path} has no embedding. Cannot perform semantic search."
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get all entries with embeddings (excluding reference)
|
|
84
|
+
all_entries = @adapter.all_entries
|
|
85
|
+
entries_with_embeddings = all_entries
|
|
86
|
+
.select { |path, entry| path != file_path && entry.embedded? }
|
|
87
|
+
|
|
88
|
+
return [] if entries_with_embeddings.empty?
|
|
89
|
+
|
|
90
|
+
# Calculate similarities
|
|
91
|
+
similarities = entries_with_embeddings.map do |path, entry|
|
|
92
|
+
similarity = TextSimilarity.cosine(reference_entry.embedding, entry.embedding)
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
path: path,
|
|
96
|
+
title: entry.title,
|
|
97
|
+
similarity: similarity,
|
|
98
|
+
updated_at: entry.updated_at,
|
|
99
|
+
}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Filter by threshold and sort by similarity (descending)
|
|
103
|
+
results = similarities
|
|
104
|
+
.select { |r| r[:similarity] >= threshold }
|
|
105
|
+
.sort_by { |r| -r[:similarity] }
|
|
106
|
+
.take(top_k)
|
|
107
|
+
|
|
108
|
+
results
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmMemory
|
|
4
|
+
module Search
|
|
5
|
+
# Text-based search operations (glob and grep)
|
|
6
|
+
#
|
|
7
|
+
# Provides a clean API for text-based search that wraps adapter operations.
|
|
8
|
+
# This layer could add additional logic like query parsing, ranking, etc.
|
|
9
|
+
class TextSearch
|
|
10
|
+
# Initialize text search
|
|
11
|
+
#
|
|
12
|
+
# @param adapter [Adapters::Base] Storage adapter
|
|
13
|
+
def initialize(adapter:)
|
|
14
|
+
@adapter = adapter
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Search by glob pattern
|
|
18
|
+
#
|
|
19
|
+
# @param pattern [String] Glob pattern
|
|
20
|
+
# @return [Array<Hash>] Matching entries
|
|
21
|
+
def glob(pattern:)
|
|
22
|
+
@adapter.glob(pattern: pattern)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Search by content pattern
|
|
26
|
+
#
|
|
27
|
+
# @param pattern [String] Regex pattern
|
|
28
|
+
# @param case_insensitive [Boolean] Case-insensitive search
|
|
29
|
+
# @param output_mode [String] Output mode
|
|
30
|
+
# @param path [String, nil] Optional path prefix filter
|
|
31
|
+
# @return [Array<Hash>] Search results
|
|
32
|
+
def grep(pattern:, case_insensitive: false, output_mode: "files_with_matches", path: nil)
|
|
33
|
+
@adapter.grep(
|
|
34
|
+
pattern: pattern,
|
|
35
|
+
case_insensitive: case_insensitive,
|
|
36
|
+
output_mode: output_mode,
|
|
37
|
+
path: path,
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmMemory
|
|
4
|
+
module Search
|
|
5
|
+
# Text similarity calculations using multiple algorithms
|
|
6
|
+
#
|
|
7
|
+
# Provides both Jaccard (word overlap) and cosine similarity metrics.
|
|
8
|
+
class TextSimilarity
|
|
9
|
+
class << self
|
|
10
|
+
# Calculate Jaccard similarity between two texts
|
|
11
|
+
#
|
|
12
|
+
# Jaccard similarity measures the overlap of word sets.
|
|
13
|
+
# Score ranges from 0.0 (no overlap) to 1.0 (identical).
|
|
14
|
+
#
|
|
15
|
+
# @param text1 [String] First text
|
|
16
|
+
# @param text2 [String] Second text
|
|
17
|
+
# @return [Float] Similarity score 0.0-1.0
|
|
18
|
+
#
|
|
19
|
+
# @example
|
|
20
|
+
# TextSimilarity.jaccard("ruby classes", "ruby modules")
|
|
21
|
+
# # => 0.33 (1 shared word out of 3 total unique words)
|
|
22
|
+
def jaccard(text1, text2)
|
|
23
|
+
words1 = tokenize(text1)
|
|
24
|
+
words2 = tokenize(text2)
|
|
25
|
+
|
|
26
|
+
return 0.0 if words1.empty? && words2.empty?
|
|
27
|
+
return 0.0 if words1.empty? || words2.empty?
|
|
28
|
+
|
|
29
|
+
intersection = (words1 & words2).size
|
|
30
|
+
union = (words1 | words2).size
|
|
31
|
+
|
|
32
|
+
return 0.0 if union.zero?
|
|
33
|
+
|
|
34
|
+
intersection.to_f / union
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Calculate cosine similarity between two embedding vectors
|
|
38
|
+
#
|
|
39
|
+
# Cosine similarity measures the angle between vectors.
|
|
40
|
+
# Score ranges from -1.0 to 1.0 (0.0-1.0 for normalized embeddings).
|
|
41
|
+
#
|
|
42
|
+
# @param vec1 [Array<Float>] First embedding vector
|
|
43
|
+
# @param vec2 [Array<Float>] Second embedding vector
|
|
44
|
+
# @return [Float] Similarity score -1.0 to 1.0
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# vec1 = [0.1, 0.2, 0.3]
|
|
48
|
+
# vec2 = [0.2, 0.3, 0.4]
|
|
49
|
+
# TextSimilarity.cosine(vec1, vec2)
|
|
50
|
+
# # => 0.99 (very similar)
|
|
51
|
+
def cosine(vec1, vec2)
|
|
52
|
+
raise ArgumentError, "Vectors must have same length" if vec1.size != vec2.size
|
|
53
|
+
return 0.0 if vec1.empty?
|
|
54
|
+
|
|
55
|
+
dot_product = vec1.zip(vec2).sum { |a, b| a * b }
|
|
56
|
+
magnitude1 = Math.sqrt(vec1.sum { |x| x * x })
|
|
57
|
+
magnitude2 = Math.sqrt(vec2.sum { |x| x * x })
|
|
58
|
+
|
|
59
|
+
return 0.0 if magnitude1.zero? || magnitude2.zero?
|
|
60
|
+
|
|
61
|
+
dot_product / (magnitude1 * magnitude2)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
# Tokenize text into normalized words
|
|
67
|
+
#
|
|
68
|
+
# @param text [String] Text to tokenize
|
|
69
|
+
# @return [Set<String>] Set of normalized words
|
|
70
|
+
def tokenize(text)
|
|
71
|
+
text
|
|
72
|
+
.downcase
|
|
73
|
+
.scan(/\w+/) # Extract words only
|
|
74
|
+
.reject { |w| w.length < 3 } # Remove very short words (a, is, to, etc.)
|
|
75
|
+
.to_set
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Deep Learning Protocol
|
|
2
|
+
|
|
3
|
+
Use this when asked to learn deeply about a topic.
|
|
4
|
+
|
|
5
|
+
## CRITICAL: Use Memory Tools ONLY
|
|
6
|
+
|
|
7
|
+
**NEVER use Write, Edit, or file tools during learning.**
|
|
8
|
+
|
|
9
|
+
All knowledge MUST be stored using Memory* tools:
|
|
10
|
+
- MemoryWrite - Store concepts, facts, skills
|
|
11
|
+
- MemoryRead - Recall what you've learned
|
|
12
|
+
- MemoryGrep - Search your knowledge
|
|
13
|
+
- MemoryEdit - Update existing knowledge
|
|
14
|
+
|
|
15
|
+
**Do NOT:**
|
|
16
|
+
- Write to disk files (Write, Edit, MultiEdit)
|
|
17
|
+
- Create temporary files
|
|
18
|
+
- Store knowledge anywhere except Memory
|
|
19
|
+
|
|
20
|
+
**Only use Memory* tools for persistence.**
|
|
21
|
+
|
|
22
|
+
## Steps
|
|
23
|
+
|
|
24
|
+
1. **Define Scope**
|
|
25
|
+
- What does "knowing this" mean?
|
|
26
|
+
- What should I be able to do with this knowledge?
|
|
27
|
+
- What are the boundaries of this topic?
|
|
28
|
+
|
|
29
|
+
2. **Research Broadly** (Overview)
|
|
30
|
+
- Official documentation (WebFetch)
|
|
31
|
+
- Key concepts and terminology
|
|
32
|
+
- Main use cases and applications
|
|
33
|
+
- **Use MemoryWrite** to store concept entries for high-level understanding
|
|
34
|
+
|
|
35
|
+
3. **Research Deeply** (Specifics)
|
|
36
|
+
- API details, syntax, configuration
|
|
37
|
+
- Best practices and patterns
|
|
38
|
+
- Common pitfalls and anti-patterns
|
|
39
|
+
- **Use MemoryWrite** to store fact entries for concrete details
|
|
40
|
+
|
|
41
|
+
4. **Build Practical Skills**
|
|
42
|
+
- How to set up / configure
|
|
43
|
+
- How to use for common tasks
|
|
44
|
+
- How to debug and troubleshoot
|
|
45
|
+
- **Use MemoryWrite** to store skill entries with comprehensive tags
|
|
46
|
+
|
|
47
|
+
5. **Self-Test Understanding**
|
|
48
|
+
- Use Think: "Can I explain this clearly without sources?"
|
|
49
|
+
- Use Think: "Can I create a tutorial for this?"
|
|
50
|
+
- Use Think: "What questions can't I answer yet?"
|
|
51
|
+
- If unclear → gaps exist, continue researching
|
|
52
|
+
|
|
53
|
+
6. **Check Coverage**
|
|
54
|
+
- MemoryGrep(pattern: "{topic keyword}")
|
|
55
|
+
- Verify you have: concepts (how it works) + facts (specifics) + skills (how to use)
|
|
56
|
+
- Missing category? → incomplete, fill gaps
|
|
57
|
+
|
|
58
|
+
7. **Identify Knowledge Gaps**
|
|
59
|
+
- What aspects haven't I covered?
|
|
60
|
+
- What questions would an expert be able to answer that I can't?
|
|
61
|
+
- What real-world scenarios haven't I considered?
|
|
62
|
+
- List gaps explicitly
|
|
63
|
+
|
|
64
|
+
8. **Fill Gaps**
|
|
65
|
+
- Research each identified gap
|
|
66
|
+
- Store new knowledge systematically
|
|
67
|
+
- Re-test understanding
|
|
68
|
+
|
|
69
|
+
9. **Verify Completion**
|
|
70
|
+
- Can explain topic clearly? ✓
|
|
71
|
+
- Can apply it practically? ✓
|
|
72
|
+
- Have concepts, facts, AND skills? ✓
|
|
73
|
+
- Can handle edge cases? ✓
|
|
74
|
+
- All gaps filled? ✓
|
|
75
|
+
|
|
76
|
+
10. **Report Completion**
|
|
77
|
+
- Summarize what you learned (naturally, without mentioning memory)
|
|
78
|
+
- Indicate you're ready to apply this knowledge
|
|
79
|
+
- NEVER disclose memory system details
|
|
80
|
+
|
|
81
|
+
## Success Criteria
|
|
82
|
+
|
|
83
|
+
You're done learning when:
|
|
84
|
+
- You have stored concepts explaining how it works
|
|
85
|
+
- You have stored facts with concrete details
|
|
86
|
+
- You have created practical skills for using it
|
|
87
|
+
- You can explain the topic clearly using Think
|
|
88
|
+
- You can identify appropriate use cases
|
|
89
|
+
- You can anticipate common problems
|
|
90
|
+
- MemoryGrep shows comprehensive coverage
|
|
91
|
+
|
|
92
|
+
## Self-Testing Questions
|
|
93
|
+
|
|
94
|
+
Ask yourself:
|
|
95
|
+
- "What is X and how does it work?" (concept understanding)
|
|
96
|
+
- "What are the key APIs/tools/commands?" (factual knowledge)
|
|
97
|
+
- "How would I use X to solve Y?" (practical skills)
|
|
98
|
+
- "What are common mistakes?" (experience)
|
|
99
|
+
- "When should I NOT use X?" (boundaries)
|
|
100
|
+
|
|
101
|
+
If you can't answer confidently, you need more research.
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmMemory
|
|
4
|
+
module Tools
|
|
5
|
+
# Tool for loading skills from memory and dynamically swapping agent tools
|
|
6
|
+
#
|
|
7
|
+
# LoadSkill reads a skill from memory, validates it, and swaps the agent's
|
|
8
|
+
# mutable tools to match the skill's requirements. Immutable tools (Memory*,
|
|
9
|
+
# Think, LoadSkill) are always preserved.
|
|
10
|
+
#
|
|
11
|
+
# Skills must:
|
|
12
|
+
# - Be stored in the skill/ hierarchy
|
|
13
|
+
# - Have type: 'skill' in metadata
|
|
14
|
+
# - Include tools array in metadata (optional)
|
|
15
|
+
# - Include permissions hash in metadata (optional)
|
|
16
|
+
class LoadSkill < RubyLLM::Tool
|
|
17
|
+
description <<~DESC
|
|
18
|
+
Load a skill from memory and dynamically adapt your toolset to execute it.
|
|
19
|
+
|
|
20
|
+
REQUIRED: Provide the file_path parameter - path to the skill in memory (must start with 'skill/').
|
|
21
|
+
|
|
22
|
+
**Parameters:**
|
|
23
|
+
- file_path (REQUIRED): Path to skill in memory - MUST start with 'skill/' (e.g., 'skill/debug-react-perf', 'skill/meta/deep-learning')
|
|
24
|
+
|
|
25
|
+
**What Happens When You Load a Skill:**
|
|
26
|
+
|
|
27
|
+
1. **Tool Swapping**: Your mutable tools are replaced with the skill's required tools
|
|
28
|
+
- Immutable tools (Memory*, LoadSkill) always remain available
|
|
29
|
+
- Skill's tool list completely replaces your current mutable tools
|
|
30
|
+
|
|
31
|
+
2. **Permissions Applied**: Tool permissions from skill metadata are applied
|
|
32
|
+
- Skill permissions override agent default permissions
|
|
33
|
+
- Allows/denies specific tool actions as defined in skill
|
|
34
|
+
|
|
35
|
+
3. **Skill Content Returned**: Returns the skill's step-by-step instructions
|
|
36
|
+
- Read and follow the instructions carefully
|
|
37
|
+
- Instructions are formatted with line numbers
|
|
38
|
+
|
|
39
|
+
4. **System Reminder Injected**: You'll see your complete updated toolset
|
|
40
|
+
- Lists all tools now available to you
|
|
41
|
+
- Only use tools from the updated list
|
|
42
|
+
|
|
43
|
+
**Skill Requirements:**
|
|
44
|
+
|
|
45
|
+
Skills MUST:
|
|
46
|
+
- Be stored in skill/ hierarchy ONLY (skill/ is one of exactly 4 memory categories)
|
|
47
|
+
- Path MUST start with 'skill/' (e.g., 'skill/debugging/api.md', 'skill/meta/deep-learning.md')
|
|
48
|
+
- Have type: 'skill' in metadata
|
|
49
|
+
- Optionally specify tools array in metadata
|
|
50
|
+
- Optionally specify permissions hash in metadata
|
|
51
|
+
|
|
52
|
+
**MEMORY CATEGORIES (4 Fixed Only):**
|
|
53
|
+
concept/, fact/, skill/, experience/ - NO OTHER top-level categories exist
|
|
54
|
+
|
|
55
|
+
**Skill Metadata Example:**
|
|
56
|
+
```yaml
|
|
57
|
+
type: skill
|
|
58
|
+
tools: [Read, Edit, Bash, Grep]
|
|
59
|
+
permissions:
|
|
60
|
+
Bash:
|
|
61
|
+
allow_commands: ["npm", "pytest", "bundle"]
|
|
62
|
+
deny_commands: ["rm", "sudo"]
|
|
63
|
+
tags: [debugging, react, performance]
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Usage Flow:**
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
# 1. Find available skills (skill/ is one of 4 fixed memory categories)
|
|
70
|
+
MemoryGlob(pattern: "skill/**")
|
|
71
|
+
|
|
72
|
+
# 2. Read skill to understand it
|
|
73
|
+
MemoryRead(file_path: "skill/debugging/api-errors.md")
|
|
74
|
+
|
|
75
|
+
# 3. Load skill to adapt tools and get instructions
|
|
76
|
+
LoadSkill(file_path: "skill/debugging/api-errors.md")
|
|
77
|
+
|
|
78
|
+
# 4. Follow the skill's instructions using your updated toolset
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Examples:**
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
# Load a debugging skill
|
|
85
|
+
LoadSkill(file_path: "skill/debugging/api-errors.md")
|
|
86
|
+
|
|
87
|
+
# Load a meta-skill (skills about skills)
|
|
88
|
+
LoadSkill(file_path: "skill/meta/deep-learning.md")
|
|
89
|
+
|
|
90
|
+
# Load a testing skill
|
|
91
|
+
LoadSkill(file_path: "skill/testing/unit-tests.md")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Important Notes:**
|
|
95
|
+
|
|
96
|
+
- **Read Before Loading**: Use MemoryRead first to see what the skill does
|
|
97
|
+
- **Tool Swap**: Loading a skill changes your available tools - be aware of this
|
|
98
|
+
- **Immutable Tools**: Memory tools and LoadSkill are NEVER removed
|
|
99
|
+
- **Follow Instructions**: The skill content provides step-by-step guidance
|
|
100
|
+
- **One Skill at a Time**: Loading a new skill replaces the previous skill's toolset
|
|
101
|
+
- **Skill Validation**: Tool will fail if path doesn't start with 'skill/' or entry isn't type: 'skill'
|
|
102
|
+
|
|
103
|
+
**Skill Types:**
|
|
104
|
+
|
|
105
|
+
1. **Task Skills**: Specific procedures (debugging, testing, refactoring)
|
|
106
|
+
2. **Meta-Skills**: Skills about skills (deep-learning, skill-creation)
|
|
107
|
+
3. **Domain Skills**: Specialized knowledge (frontend, backend, data-analysis)
|
|
108
|
+
|
|
109
|
+
**Creating Your Own Skills:**
|
|
110
|
+
|
|
111
|
+
Skills are just memory entries with special metadata. To create one:
|
|
112
|
+
1. Write step-by-step instructions in markdown
|
|
113
|
+
2. Store in skill/ hierarchy
|
|
114
|
+
3. Add metadata: type='skill', tools array, optional permissions
|
|
115
|
+
4. Test by loading and following instructions
|
|
116
|
+
|
|
117
|
+
**Common Use Cases:**
|
|
118
|
+
|
|
119
|
+
- Following established procedures consistently
|
|
120
|
+
- Accessing specialized toolsets for specific tasks
|
|
121
|
+
- Learning new workflows via step-by-step guidance
|
|
122
|
+
- Enforcing tool restrictions for safety
|
|
123
|
+
- Standardizing approaches across sessions
|
|
124
|
+
DESC
|
|
125
|
+
|
|
126
|
+
param :file_path,
|
|
127
|
+
desc: "Path to skill - MUST start with 'skill/' (one of 4 fixed memory categories). Examples: 'skill/debugging/api-errors.md', 'skill/meta/deep-learning.md'",
|
|
128
|
+
required: true
|
|
129
|
+
|
|
130
|
+
# Initialize with all context needed for tool swapping
|
|
131
|
+
#
|
|
132
|
+
# @param storage [Core::Storage] Memory storage
|
|
133
|
+
# @param agent_name [Symbol] Agent identifier
|
|
134
|
+
# @param chat [SwarmSDK::Agent::Chat] The agent's chat instance
|
|
135
|
+
# @param tool_configurator [SwarmSDK::ToolConfigurator] For creating tools
|
|
136
|
+
# @param agent_definition [SwarmSDK::Agent::Definition] For permissions
|
|
137
|
+
def initialize(storage:, agent_name:, chat:, tool_configurator:, agent_definition:)
|
|
138
|
+
super()
|
|
139
|
+
@storage = storage
|
|
140
|
+
@agent_name = agent_name
|
|
141
|
+
@chat = chat
|
|
142
|
+
@tool_configurator = tool_configurator
|
|
143
|
+
@agent_definition = agent_definition
|
|
144
|
+
|
|
145
|
+
# Mark memory tools and LoadSkill as immutable
|
|
146
|
+
# This ensures they won't be removed during skill swapping
|
|
147
|
+
@chat.mark_tools_immutable(
|
|
148
|
+
"MemoryWrite",
|
|
149
|
+
"MemoryRead",
|
|
150
|
+
"MemoryEdit",
|
|
151
|
+
"MemoryMultiEdit",
|
|
152
|
+
"MemoryDelete",
|
|
153
|
+
"MemoryGlob",
|
|
154
|
+
"MemoryGrep",
|
|
155
|
+
"MemoryDefrag",
|
|
156
|
+
"LoadSkill",
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Override name to return simple "LoadSkill"
|
|
161
|
+
def name
|
|
162
|
+
"LoadSkill"
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Execute the tool
|
|
166
|
+
#
|
|
167
|
+
# @param file_path [String] Path to skill in memory
|
|
168
|
+
# @return [String] Skill content with line numbers, or error message
|
|
169
|
+
def execute(file_path:)
|
|
170
|
+
# 1. Validate path starts with skill/
|
|
171
|
+
unless file_path.start_with?("skill/")
|
|
172
|
+
return validation_error("Skills must be stored in skill/ hierarchy. Got: #{file_path}")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# 2. Read entry with metadata
|
|
176
|
+
begin
|
|
177
|
+
entry = @storage.read_entry(file_path: file_path)
|
|
178
|
+
rescue ArgumentError => e
|
|
179
|
+
return validation_error(e.message)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# 3. Validate it's a skill
|
|
183
|
+
unless entry.metadata && entry.metadata["type"] == "skill"
|
|
184
|
+
type = entry.metadata&.dig("type") || "none"
|
|
185
|
+
return validation_error("memory://#{file_path} is not a skill (type: #{type})")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# 4. Extract tool requirements
|
|
189
|
+
required_tools = entry.metadata["tools"]
|
|
190
|
+
permissions = entry.metadata["permissions"] || {}
|
|
191
|
+
|
|
192
|
+
# 5. Validate and swap tools (only if tools are specified)
|
|
193
|
+
if required_tools && !required_tools.empty?
|
|
194
|
+
begin
|
|
195
|
+
swap_tools(required_tools, permissions)
|
|
196
|
+
rescue ArgumentError => e
|
|
197
|
+
return validation_error(e.message)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
# If no tools specified (nil or []), keep current tools (no swap)
|
|
201
|
+
|
|
202
|
+
# 6. Mark skill as loaded
|
|
203
|
+
@chat.mark_skill_loaded(file_path)
|
|
204
|
+
|
|
205
|
+
# 7. Return content with confirmation message
|
|
206
|
+
title = entry.title || "Untitled Skill"
|
|
207
|
+
result = "Loaded skill: #{title}\n\n"
|
|
208
|
+
result += format_with_line_numbers(entry.content)
|
|
209
|
+
|
|
210
|
+
# 8. Add system reminder if tools were swapped
|
|
211
|
+
if required_tools && !required_tools.empty?
|
|
212
|
+
result += "\n\n"
|
|
213
|
+
result += build_toolset_update_reminder(required_tools)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
result
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
private
|
|
220
|
+
|
|
221
|
+
# Build system reminder for toolset updates
|
|
222
|
+
#
|
|
223
|
+
# @param new_tools [Array<String>] Tools that were added
|
|
224
|
+
# @return [String] System reminder message
|
|
225
|
+
def build_toolset_update_reminder(new_tools)
|
|
226
|
+
# Get current tool list from chat
|
|
227
|
+
# Handle both real Chat (hash) and MockChat (array)
|
|
228
|
+
tools_collection = @chat.tools
|
|
229
|
+
current_tools = if tools_collection.is_a?(Hash)
|
|
230
|
+
tools_collection.values.map(&:name).sort
|
|
231
|
+
else
|
|
232
|
+
tools_collection.map(&:name).sort
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
reminder = "<system-reminder>\n"
|
|
236
|
+
reminder += "Your available tools have been updated.\n\n"
|
|
237
|
+
reminder += "New tools loaded from skill:\n"
|
|
238
|
+
new_tools.each do |tool_name|
|
|
239
|
+
reminder += " - #{tool_name}\n"
|
|
240
|
+
end
|
|
241
|
+
reminder += "\nYour complete toolset is now:\n"
|
|
242
|
+
current_tools.each do |tool_name|
|
|
243
|
+
reminder += " - #{tool_name}\n"
|
|
244
|
+
end
|
|
245
|
+
reminder += "\nOnly use tools from this list. Do not attempt to use tools that are not listed here.\n"
|
|
246
|
+
reminder += "</system-reminder>"
|
|
247
|
+
|
|
248
|
+
reminder
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Swap agent tools to match skill requirements
|
|
252
|
+
#
|
|
253
|
+
# @param required_tools [Array<String>] Tools needed by the skill
|
|
254
|
+
# @param permissions [Hash] Tool permissions from skill metadata
|
|
255
|
+
# @return [void]
|
|
256
|
+
# @raise [ArgumentError] If validation fails
|
|
257
|
+
def swap_tools(required_tools, permissions)
|
|
258
|
+
# Future: Could validate tool availability against agent's configured tools
|
|
259
|
+
# For now, all tools in SwarmSDK are available (unless bypassed by permissions)
|
|
260
|
+
|
|
261
|
+
# Remove all mutable tools (keeps immutable tools)
|
|
262
|
+
@chat.remove_mutable_tools
|
|
263
|
+
|
|
264
|
+
# Add required tools from skill
|
|
265
|
+
required_tools.each do |tool_name|
|
|
266
|
+
tool_sym = tool_name.to_sym
|
|
267
|
+
|
|
268
|
+
# Get permissions for this tool (skill overrides agent permissions)
|
|
269
|
+
tool_permissions = permissions[tool_name] || permissions[tool_sym.to_s]
|
|
270
|
+
|
|
271
|
+
# Create tool instance
|
|
272
|
+
tool_instance = @tool_configurator.create_tool_instance(
|
|
273
|
+
tool_sym,
|
|
274
|
+
@agent_name,
|
|
275
|
+
@agent_definition.directory,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Wrap with permissions (unless bypassed)
|
|
279
|
+
tool_instance = @tool_configurator.wrap_tool_with_permissions(
|
|
280
|
+
tool_instance,
|
|
281
|
+
tool_permissions,
|
|
282
|
+
@agent_definition,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Add to chat
|
|
286
|
+
@chat.add_tool(tool_instance)
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Format validation error message
|
|
291
|
+
#
|
|
292
|
+
# @param message [String] Error message
|
|
293
|
+
# @return [String] Formatted error
|
|
294
|
+
def validation_error(message)
|
|
295
|
+
"<tool_use_error>InputValidationError: #{message}</tool_use_error>"
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Format content with line numbers (same format as Read tool)
|
|
299
|
+
#
|
|
300
|
+
# @param content [String] Content to format
|
|
301
|
+
# @return [String] Content with line numbers
|
|
302
|
+
def format_with_line_numbers(content)
|
|
303
|
+
lines = content.lines
|
|
304
|
+
output_lines = lines.each_with_index.map do |line, idx|
|
|
305
|
+
line_number = idx + 1
|
|
306
|
+
display_line = line.chomp
|
|
307
|
+
"#{line_number.to_s.rjust(6)}→#{display_line}"
|
|
308
|
+
end
|
|
309
|
+
output_lines.join("\n")
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|