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,816 @@
|
|
|
1
|
+
# Plugin System Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to SwarmSDK's plugin architecture - how it works, how to write plugins, and what's possible.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The **plugin system** allows gems to extend SwarmSDK without creating tight coupling. Plugins can provide:
|
|
10
|
+
|
|
11
|
+
- Custom tools
|
|
12
|
+
- Persistent storage
|
|
13
|
+
- Configuration options
|
|
14
|
+
- System prompt contributions
|
|
15
|
+
- Lifecycle hooks (agent init, swarm start/stop, user messages)
|
|
16
|
+
|
|
17
|
+
**Key Design Principles:**
|
|
18
|
+
- 🔌 **Zero Coupling** - SwarmSDK has no knowledge of plugin classes
|
|
19
|
+
- 🤖 **Auto-Registration** - Plugins register themselves when loaded
|
|
20
|
+
- 🎯 **Lifecycle Hooks** - Plugins participate in agent/swarm lifecycle
|
|
21
|
+
- 🛠️ **Tool Provider** - Plugins create and manage their own tools
|
|
22
|
+
- 📦 **Storage Provider** - Plugins handle their own data persistence
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Architecture
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
┌─────────────────────────────────────────┐
|
|
30
|
+
│ SwarmSDK (Core) │
|
|
31
|
+
│ - Plugin base class │
|
|
32
|
+
│ - PluginRegistry │
|
|
33
|
+
│ - Lifecycle hooks │
|
|
34
|
+
│ - No plugin dependencies │
|
|
35
|
+
└─────────────┬───────────────────────────┘
|
|
36
|
+
│
|
|
37
|
+
│ (auto-registers)
|
|
38
|
+
│
|
|
39
|
+
┌─────────────▼───────────────────────────┐
|
|
40
|
+
│ Plugin Gem (e.g., SwarmMemory) │
|
|
41
|
+
│ - Inherits from SwarmSDK::Plugin │
|
|
42
|
+
│ - Implements required methods │
|
|
43
|
+
│ - Registers on load │
|
|
44
|
+
│ - Provides tools & storage │
|
|
45
|
+
└──────────────────────────────────────────┘
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Writing a Plugin
|
|
51
|
+
|
|
52
|
+
### Step 1: Create Plugin Class
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# lib/my_plugin/integration/sdk_plugin.rb
|
|
56
|
+
|
|
57
|
+
module MyPlugin
|
|
58
|
+
module Integration
|
|
59
|
+
class SDKPlugin < SwarmSDK::Plugin
|
|
60
|
+
def initialize
|
|
61
|
+
super
|
|
62
|
+
@storages = {} # Track per-agent storage
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Plugin identifier (must be unique)
|
|
66
|
+
def name
|
|
67
|
+
:my_plugin
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Tools provided by this plugin
|
|
71
|
+
def tools
|
|
72
|
+
[:MyTool1, :MyTool2]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Create a tool instance
|
|
76
|
+
def create_tool(tool_name, context)
|
|
77
|
+
storage = context[:storage]
|
|
78
|
+
agent_name = context[:agent_name]
|
|
79
|
+
|
|
80
|
+
case tool_name
|
|
81
|
+
when :MyTool1
|
|
82
|
+
Tools::MyTool1.new(storage: storage, agent_name: agent_name)
|
|
83
|
+
when :MyTool2
|
|
84
|
+
Tools::MyTool2.new(storage: storage)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if storage should be created for an agent
|
|
89
|
+
def storage_enabled?(agent_definition)
|
|
90
|
+
agent_definition.respond_to?(:my_plugin) && agent_definition.my_plugin
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Create storage for an agent
|
|
94
|
+
def create_storage(agent_name:, config:)
|
|
95
|
+
directory = config.respond_to?(:directory) ? config.directory : config[:directory]
|
|
96
|
+
|
|
97
|
+
MyPlugin::Storage.new(directory: directory)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Contribute to system prompt
|
|
101
|
+
def system_prompt_contribution(agent_definition:, storage:)
|
|
102
|
+
# Load your prompt template
|
|
103
|
+
File.read(File.expand_path("../prompts/my_plugin.md", __dir__))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Tools that should be immutable (can't be removed)
|
|
107
|
+
def immutable_tools
|
|
108
|
+
[:MyTool1]
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Called when agent is initialized
|
|
112
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
113
|
+
storage = context[:storage]
|
|
114
|
+
return unless storage
|
|
115
|
+
|
|
116
|
+
# Store for later use
|
|
117
|
+
@storages[agent_name] = storage
|
|
118
|
+
|
|
119
|
+
# Mark tools as immutable
|
|
120
|
+
agent.mark_tools_immutable(immutable_tools.map(&:to_s))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Called on every user message
|
|
124
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
125
|
+
storage = @storages[agent_name]
|
|
126
|
+
return [] unless storage
|
|
127
|
+
|
|
128
|
+
# Perform some analysis and return system reminders
|
|
129
|
+
if should_suggest_something?(prompt)
|
|
130
|
+
["<system-reminder>Helpful suggestion here</system-reminder>"]
|
|
131
|
+
else
|
|
132
|
+
[]
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Step 2: Auto-Register Plugin
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# lib/my_plugin.rb
|
|
144
|
+
|
|
145
|
+
require 'swarm_sdk'
|
|
146
|
+
require_relative 'my_plugin/integration/sdk_plugin'
|
|
147
|
+
|
|
148
|
+
module MyPlugin
|
|
149
|
+
# ... your gem code ...
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Auto-register with SwarmSDK when loaded
|
|
153
|
+
if defined?(SwarmSDK) && defined?(SwarmSDK::PluginRegistry)
|
|
154
|
+
SwarmSDK::PluginRegistry.register(MyPlugin::Integration::SDKPlugin.new)
|
|
155
|
+
end
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Step 3: Add DSL Support (Optional)
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Add configuration method to Agent::Builder
|
|
162
|
+
module SwarmSDK
|
|
163
|
+
module Agent
|
|
164
|
+
class Builder
|
|
165
|
+
def my_plugin(&block)
|
|
166
|
+
@my_plugin_config = MyPluginConfig.new
|
|
167
|
+
@my_plugin_config.instance_eval(&block) if block_given?
|
|
168
|
+
@my_plugin_config
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Usage:**
|
|
176
|
+
```ruby
|
|
177
|
+
agent :assistant do
|
|
178
|
+
my_plugin do
|
|
179
|
+
directory ".swarm/my-plugin-data"
|
|
180
|
+
option "value"
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Plugin Interface Reference
|
|
188
|
+
|
|
189
|
+
### Required Methods
|
|
190
|
+
|
|
191
|
+
```ruby
|
|
192
|
+
class MyPlugin < SwarmSDK::Plugin
|
|
193
|
+
# Plugin identifier (Symbol) - REQUIRED
|
|
194
|
+
def name
|
|
195
|
+
:my_plugin
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Optional Methods
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
# List of tools provided
|
|
204
|
+
def tools
|
|
205
|
+
[:Tool1, :Tool2]
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Create a tool instance
|
|
209
|
+
# context = {agent_name:, storage:, agent_definition:, chat:, tool_configurator:}
|
|
210
|
+
def create_tool(tool_name, context)
|
|
211
|
+
MyTool.new(...)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Create plugin storage for an agent
|
|
215
|
+
def create_storage(agent_name:, config:)
|
|
216
|
+
MyStorage.new(...)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Check if storage should be created
|
|
220
|
+
def storage_enabled?(agent_definition)
|
|
221
|
+
agent_definition.respond_to?(:my_plugin)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Parse configuration from agent definition
|
|
225
|
+
def parse_config(raw_config)
|
|
226
|
+
raw_config # Or transform as needed
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Contribute to agent system prompt
|
|
230
|
+
def system_prompt_contribution(agent_definition:, storage:)
|
|
231
|
+
"# My Plugin Guidance\n..."
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Tools that can't be removed
|
|
235
|
+
def immutable_tools
|
|
236
|
+
[:MyTool1]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Lifecycle: Agent initialized
|
|
240
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
241
|
+
# Setup, register tools, mark immutable, etc.
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Lifecycle: Swarm started
|
|
245
|
+
def on_swarm_started(swarm:)
|
|
246
|
+
# Global initialization
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Lifecycle: Swarm stopped
|
|
250
|
+
def on_swarm_stopped(swarm:)
|
|
251
|
+
# Cleanup, save state, etc.
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Lifecycle: User message (EVERY message)
|
|
255
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
256
|
+
# Analyze prompt, return system reminders
|
|
257
|
+
[] # Array of reminder strings
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Lifecycle Hooks
|
|
264
|
+
|
|
265
|
+
### Hook Execution Order
|
|
266
|
+
|
|
267
|
+
```
|
|
268
|
+
1. Swarm created
|
|
269
|
+
↓
|
|
270
|
+
2. Agents initialized
|
|
271
|
+
→ Plugin.create_storage()
|
|
272
|
+
→ Plugin.on_agent_initialized()
|
|
273
|
+
↓
|
|
274
|
+
3. Swarm.execute() called
|
|
275
|
+
→ Plugin.on_swarm_started()
|
|
276
|
+
↓
|
|
277
|
+
4. User message
|
|
278
|
+
→ Plugin.on_user_message() [Returns reminders]
|
|
279
|
+
→ Agent sees reminders
|
|
280
|
+
→ Agent responds
|
|
281
|
+
↓
|
|
282
|
+
5. Swarm stops
|
|
283
|
+
→ Plugin.on_swarm_stopped()
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### on_agent_initialized
|
|
287
|
+
|
|
288
|
+
**Purpose:** Setup plugin-specific agent configuration
|
|
289
|
+
|
|
290
|
+
**Use cases:**
|
|
291
|
+
- Register additional tools (like LoadSkill that needs chat reference)
|
|
292
|
+
- Mark tools as immutable
|
|
293
|
+
- Store agent storage reference for later use
|
|
294
|
+
- Configure agent-specific settings
|
|
295
|
+
|
|
296
|
+
**Example:**
|
|
297
|
+
```ruby
|
|
298
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
299
|
+
@storages[agent_name] = context[:storage]
|
|
300
|
+
|
|
301
|
+
# Register special tool
|
|
302
|
+
special_tool = create_special_tool(context)
|
|
303
|
+
agent.with_tool(special_tool)
|
|
304
|
+
|
|
305
|
+
# Mark tools as immutable
|
|
306
|
+
agent.mark_tools_immutable(["MyTool1", "MyTool2"])
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### on_user_message
|
|
311
|
+
|
|
312
|
+
**Purpose:** Inject context-aware system reminders
|
|
313
|
+
|
|
314
|
+
**Use cases:**
|
|
315
|
+
- Semantic skill discovery
|
|
316
|
+
- Context injection based on prompt
|
|
317
|
+
- Suggested actions
|
|
318
|
+
- Warning/guidance
|
|
319
|
+
|
|
320
|
+
**Example:**
|
|
321
|
+
```ruby
|
|
322
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
323
|
+
storage = @storages[agent_name]
|
|
324
|
+
return [] unless storage
|
|
325
|
+
|
|
326
|
+
# Search for relevant knowledge
|
|
327
|
+
results = storage.search(prompt, threshold: 0.65)
|
|
328
|
+
return [] if results.empty?
|
|
329
|
+
|
|
330
|
+
# Return system reminder
|
|
331
|
+
["<system-reminder>Found #{results.size} relevant entries...</system-reminder>"]
|
|
332
|
+
end
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Important:** Reminders are **ephemeral** - sent to LLM but not persisted in conversation.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Real-World Example: SwarmMemory
|
|
340
|
+
|
|
341
|
+
See how SwarmMemory implements the plugin interface:
|
|
342
|
+
|
|
343
|
+
**File:** `lib/swarm_memory/integration/sdk_plugin.rb`
|
|
344
|
+
|
|
345
|
+
**Provides:**
|
|
346
|
+
- 9 tools (MemoryWrite, MemoryRead, ..., LoadSkill)
|
|
347
|
+
- Filesystem storage with embeddings
|
|
348
|
+
- Memory system prompt
|
|
349
|
+
- Dual semantic search (skills + memories)
|
|
350
|
+
- Relationship discovery
|
|
351
|
+
|
|
352
|
+
**Key Implementation Details:**
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
class SDKPlugin < SwarmSDK::Plugin
|
|
356
|
+
def initialize
|
|
357
|
+
super
|
|
358
|
+
@storages = {} # Track storages per agent
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def tools
|
|
362
|
+
# LoadSkill NOT here - registered in on_agent_initialized
|
|
363
|
+
[:MemoryWrite, :MemoryRead, :MemoryEdit, ...]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def create_storage(agent_name:, config:)
|
|
367
|
+
directory = extract_directory(config)
|
|
368
|
+
embedder = Embeddings::InformersEmbedder.new
|
|
369
|
+
adapter = Adapters::FilesystemAdapter.new(directory: directory)
|
|
370
|
+
|
|
371
|
+
Storage.new(adapter: adapter, embedder: embedder)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
375
|
+
# Store for later
|
|
376
|
+
@storages[agent_name] = context[:storage]
|
|
377
|
+
|
|
378
|
+
# Register LoadSkill (needs chat + tool_configurator)
|
|
379
|
+
load_skill = create_load_skill_tool(context)
|
|
380
|
+
agent.with_tool(load_skill)
|
|
381
|
+
|
|
382
|
+
# Mark all memory tools immutable
|
|
383
|
+
agent.mark_tools_immutable(immutable_tools)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
387
|
+
storage = @storages[agent_name]
|
|
388
|
+
return [] unless storage&.semantic_index
|
|
389
|
+
|
|
390
|
+
# Parallel search for skills AND memories
|
|
391
|
+
Async do |task|
|
|
392
|
+
skills = task.async { search_skills(prompt) }
|
|
393
|
+
memories = task.async { search_memories(prompt) }
|
|
394
|
+
|
|
395
|
+
# Build reminders for both
|
|
396
|
+
reminders = []
|
|
397
|
+
reminders << build_skill_reminder(skills.wait) if skills.wait.any?
|
|
398
|
+
reminders << build_memory_reminder(memories.wait) if memories.wait.any?
|
|
399
|
+
reminders
|
|
400
|
+
end.wait
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Testing Plugins
|
|
408
|
+
|
|
409
|
+
### Unit Tests
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
class MyPluginTest < Minitest::Test
|
|
413
|
+
def test_plugin_registration
|
|
414
|
+
assert SwarmSDK::PluginRegistry.registered?(:my_plugin)
|
|
415
|
+
|
|
416
|
+
plugin = SwarmSDK::PluginRegistry.get(:my_plugin)
|
|
417
|
+
assert_instance_of MyPlugin::Integration::SDKPlugin, plugin
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def test_plugin_provides_tools
|
|
421
|
+
plugin = SwarmSDK::PluginRegistry.get(:my_plugin)
|
|
422
|
+
|
|
423
|
+
assert_includes plugin.tools, :MyTool1
|
|
424
|
+
assert_includes plugin.tools, :MyTool2
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def test_tool_creation
|
|
428
|
+
plugin = SwarmSDK::PluginRegistry.get(:my_plugin)
|
|
429
|
+
storage = MyPlugin::Storage.new(directory: "/tmp/test")
|
|
430
|
+
|
|
431
|
+
context = { agent_name: :test, storage: storage }
|
|
432
|
+
tool = plugin.create_tool(:MyTool1, context)
|
|
433
|
+
|
|
434
|
+
assert_instance_of MyPlugin::Tools::MyTool1, tool
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Integration Tests
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
def test_plugin_tools_available_to_agent
|
|
443
|
+
swarm = SwarmSDK.build do
|
|
444
|
+
agent :test do
|
|
445
|
+
my_plugin { directory "/tmp/test" }
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
agent = swarm.agent(:test)
|
|
450
|
+
|
|
451
|
+
# Plugin tools should be registered
|
|
452
|
+
assert agent.tools.key?(:MyTool1)
|
|
453
|
+
assert agent.tools.key?(:MyTool2)
|
|
454
|
+
end
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## Plugin Registry API
|
|
460
|
+
|
|
461
|
+
### Registration
|
|
462
|
+
|
|
463
|
+
```ruby
|
|
464
|
+
# Auto-registration (recommended)
|
|
465
|
+
SwarmSDK::PluginRegistry.register(MyPlugin.new)
|
|
466
|
+
|
|
467
|
+
# Check registration
|
|
468
|
+
SwarmSDK::PluginRegistry.registered?(:my_plugin) # => true
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### Lookup
|
|
472
|
+
|
|
473
|
+
```ruby
|
|
474
|
+
# Get plugin by name
|
|
475
|
+
plugin = SwarmSDK::PluginRegistry.get(:my_plugin)
|
|
476
|
+
|
|
477
|
+
# Check if tool is provided by plugin
|
|
478
|
+
SwarmSDK::PluginRegistry.plugin_tool?(:MyTool1) # => true
|
|
479
|
+
|
|
480
|
+
# Get plugin for a tool
|
|
481
|
+
plugin = SwarmSDK::PluginRegistry.plugin_for_tool(:MyTool1)
|
|
482
|
+
plugin.name # => :my_plugin
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### All Plugins
|
|
486
|
+
|
|
487
|
+
```ruby
|
|
488
|
+
# Get all registered plugins
|
|
489
|
+
plugins = SwarmSDK::PluginRegistry.all # => [plugin1, plugin2]
|
|
490
|
+
|
|
491
|
+
# Get all plugin tools
|
|
492
|
+
tools = SwarmSDK::PluginRegistry.tools # => {MyTool1: plugin1, MyTool2: plugin1}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Best Practices
|
|
498
|
+
|
|
499
|
+
### 1. Single Responsibility
|
|
500
|
+
|
|
501
|
+
Each plugin should have a **focused purpose**:
|
|
502
|
+
|
|
503
|
+
✅ **Good:** Memory storage plugin, Database plugin, Analytics plugin
|
|
504
|
+
❌ **Bad:** "Utilities" plugin with unrelated tools
|
|
505
|
+
|
|
506
|
+
### 2. Minimal Dependencies
|
|
507
|
+
|
|
508
|
+
Plugins should have **minimal external dependencies**:
|
|
509
|
+
|
|
510
|
+
```ruby
|
|
511
|
+
# Check if dependency available
|
|
512
|
+
def create_storage(...)
|
|
513
|
+
unless defined?(SomeDependency)
|
|
514
|
+
raise PluginError, "my_plugin requires 'some_gem'. Install: gem install some_gem"
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
SomeDependency.new(...)
|
|
518
|
+
end
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### 3. Graceful Degradation
|
|
522
|
+
|
|
523
|
+
Plugins should **degrade gracefully** if features unavailable:
|
|
524
|
+
|
|
525
|
+
```ruby
|
|
526
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
527
|
+
storage = @storages[agent_name]
|
|
528
|
+
|
|
529
|
+
# No storage? Return empty (no crash)
|
|
530
|
+
return [] unless storage
|
|
531
|
+
|
|
532
|
+
# Feature unavailable? Silent degradation
|
|
533
|
+
return [] unless storage.respond_to?(:search)
|
|
534
|
+
|
|
535
|
+
# Feature available - use it
|
|
536
|
+
results = storage.search(prompt)
|
|
537
|
+
results.any? ? [build_reminder(results)] : []
|
|
538
|
+
end
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### 4. Namespace Your Tools
|
|
542
|
+
|
|
543
|
+
Use descriptive, namespaced tool names:
|
|
544
|
+
|
|
545
|
+
✅ **Good:** `MemoryWrite`, `DatabaseQuery`, `AnalyticsTrack`
|
|
546
|
+
❌ **Bad:** `Write`, `Query`, `Track` (conflicts with built-ins)
|
|
547
|
+
|
|
548
|
+
### 5. Document Everything
|
|
549
|
+
|
|
550
|
+
Plugins should have:
|
|
551
|
+
- Comprehensive README
|
|
552
|
+
- Tool descriptions (in tool classes)
|
|
553
|
+
- Configuration examples
|
|
554
|
+
- Troubleshooting guide
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Advanced Patterns
|
|
559
|
+
|
|
560
|
+
### Conditional Tool Registration
|
|
561
|
+
|
|
562
|
+
Register different tools based on configuration:
|
|
563
|
+
|
|
564
|
+
```ruby
|
|
565
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
566
|
+
config = context[:agent_definition].my_plugin
|
|
567
|
+
|
|
568
|
+
# Conditionally register tools
|
|
569
|
+
if config.experimental_features
|
|
570
|
+
experimental_tool = create_experimental_tool(context)
|
|
571
|
+
agent.with_tool(experimental_tool)
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Tool Swapping
|
|
577
|
+
|
|
578
|
+
Dynamically change available tools:
|
|
579
|
+
|
|
580
|
+
```ruby
|
|
581
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
582
|
+
# Detect if user wants specialized mode
|
|
583
|
+
if prompt.include?("debug mode")
|
|
584
|
+
# Agent can swap tools based on this hint
|
|
585
|
+
["<system-reminder>Debug mode detected. Load debug skill for specialized tools.</system-reminder>"]
|
|
586
|
+
else
|
|
587
|
+
[]
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Multi-Agent Coordination
|
|
593
|
+
|
|
594
|
+
Plugins can coordinate across agents:
|
|
595
|
+
|
|
596
|
+
```ruby
|
|
597
|
+
class CoordinationPlugin < SwarmSDK::Plugin
|
|
598
|
+
def initialize
|
|
599
|
+
super
|
|
600
|
+
@shared_state = {}
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
604
|
+
@shared_state[agent_name] = { initialized_at: Time.now }
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
608
|
+
# Check what other agents are doing
|
|
609
|
+
other_agents = @shared_state.keys - [agent_name]
|
|
610
|
+
|
|
611
|
+
if other_agents.any?
|
|
612
|
+
["<system-reminder>Other active agents: #{other_agents.join(", ")}</system-reminder>"]
|
|
613
|
+
else
|
|
614
|
+
[]
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## Plugin Development Checklist
|
|
623
|
+
|
|
624
|
+
### Before Publishing
|
|
625
|
+
|
|
626
|
+
- [ ] Plugin class inherits from `SwarmSDK::Plugin`
|
|
627
|
+
- [ ] `name` method returns unique symbol
|
|
628
|
+
- [ ] Auto-registration code in main gem file
|
|
629
|
+
- [ ] All tools have comprehensive descriptions
|
|
630
|
+
- [ ] Storage adapter (if any) handles errors gracefully
|
|
631
|
+
- [ ] Lifecycle hooks implemented as needed
|
|
632
|
+
- [ ] Unit tests for plugin class
|
|
633
|
+
- [ ] Integration tests with SwarmSDK
|
|
634
|
+
- [ ] README with installation and usage
|
|
635
|
+
- [ ] Example swarm configuration
|
|
636
|
+
- [ ] Troubleshooting section
|
|
637
|
+
|
|
638
|
+
### Quality Checklist
|
|
639
|
+
|
|
640
|
+
- [ ] Plugin works when SwarmSDK is standalone (no crashes)
|
|
641
|
+
- [ ] Graceful error messages if dependencies missing
|
|
642
|
+
- [ ] No global state pollution
|
|
643
|
+
- [ ] Thread-safe (if using shared state)
|
|
644
|
+
- [ ] Fiber-safe (if using Async operations)
|
|
645
|
+
- [ ] Tools have `name` method returning simple strings
|
|
646
|
+
- [ ] Configuration validated with helpful errors
|
|
647
|
+
- [ ] Works with both Ruby DSL and YAML
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## Common Pitfalls
|
|
652
|
+
|
|
653
|
+
### ❌ Pitfall 1: Circular Dependencies
|
|
654
|
+
|
|
655
|
+
```ruby
|
|
656
|
+
# BAD - SwarmSDK would depend on MyPlugin
|
|
657
|
+
require 'swarm_sdk'
|
|
658
|
+
require 'my_plugin' # SwarmSDK has: require 'my_plugin'
|
|
659
|
+
|
|
660
|
+
# GOOD - No circular dependency
|
|
661
|
+
require 'swarm_sdk' # No my_plugin references
|
|
662
|
+
require 'my_plugin' # Requires swarm_sdk, registers plugin
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### ❌ Pitfall 2: Hardcoding in SDK
|
|
666
|
+
|
|
667
|
+
```ruby
|
|
668
|
+
# BAD - SDK knows about plugin
|
|
669
|
+
if defined?(MyPlugin)
|
|
670
|
+
do_something_with_my_plugin
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# GOOD - SDK uses plugin interface
|
|
674
|
+
SwarmSDK::PluginRegistry.all.each do |plugin|
|
|
675
|
+
plugin.on_agent_initialized(...)
|
|
676
|
+
end
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### ❌ Pitfall 3: Not Handling Missing Storage
|
|
680
|
+
|
|
681
|
+
```ruby
|
|
682
|
+
# BAD - Crashes if storage nil
|
|
683
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
684
|
+
storage = @storages[agent_name]
|
|
685
|
+
results = storage.search(prompt) # BOOM if storage is nil
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# GOOD - Graceful handling
|
|
689
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
690
|
+
storage = @storages[agent_name]
|
|
691
|
+
return [] unless storage # Early return
|
|
692
|
+
|
|
693
|
+
results = storage.search(prompt)
|
|
694
|
+
# ...
|
|
695
|
+
end
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### ❌ Pitfall 4: Forgetting to Track Storage
|
|
699
|
+
|
|
700
|
+
```ruby
|
|
701
|
+
# BAD - Storage created but not tracked
|
|
702
|
+
def create_storage(...)
|
|
703
|
+
MyStorage.new(...)
|
|
704
|
+
# Forgot to store it!
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
# In on_user_message:
|
|
708
|
+
storage = @storages[agent_name] # nil!
|
|
709
|
+
|
|
710
|
+
# GOOD - Track in on_agent_initialized
|
|
711
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
712
|
+
@storages[agent_name] = context[:storage]
|
|
713
|
+
end
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Examples
|
|
719
|
+
|
|
720
|
+
### Minimal Plugin
|
|
721
|
+
|
|
722
|
+
```ruby
|
|
723
|
+
class MinimalPlugin < SwarmSDK::Plugin
|
|
724
|
+
def name
|
|
725
|
+
:minimal
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
def tools
|
|
729
|
+
[:Echo]
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
def create_tool(tool_name, context)
|
|
733
|
+
Tools::Echo.new
|
|
734
|
+
end
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
SwarmSDK::PluginRegistry.register(MinimalPlugin.new)
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Storage Plugin
|
|
741
|
+
|
|
742
|
+
```ruby
|
|
743
|
+
class StoragePlugin < SwarmSDK::Plugin
|
|
744
|
+
def initialize
|
|
745
|
+
super
|
|
746
|
+
@databases = {}
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
def name
|
|
750
|
+
:database
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
def storage_enabled?(agent_definition)
|
|
754
|
+
agent_definition.respond_to?(:database)
|
|
755
|
+
end
|
|
756
|
+
|
|
757
|
+
def create_storage(agent_name:, config:)
|
|
758
|
+
Database.connect(config.url)
|
|
759
|
+
end
|
|
760
|
+
|
|
761
|
+
def on_agent_initialized(agent_name:, agent:, context:)
|
|
762
|
+
@databases[agent_name] = context[:storage]
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
def tools
|
|
766
|
+
[:DbQuery, :DbInsert]
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def create_tool(tool_name, context)
|
|
770
|
+
db = context[:storage]
|
|
771
|
+
|
|
772
|
+
case tool_name
|
|
773
|
+
when :DbQuery
|
|
774
|
+
Tools::DbQuery.new(db: db)
|
|
775
|
+
when :DbInsert
|
|
776
|
+
Tools::DbInsert.new(db: db)
|
|
777
|
+
end
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
### Analytics Plugin
|
|
783
|
+
|
|
784
|
+
```ruby
|
|
785
|
+
class AnalyticsPlugin < SwarmSDK::Plugin
|
|
786
|
+
def name
|
|
787
|
+
:analytics
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
def on_user_message(agent_name:, prompt:, is_first_message:)
|
|
791
|
+
# Track every user message
|
|
792
|
+
Analytics.track(
|
|
793
|
+
event: "user_message",
|
|
794
|
+
agent: agent_name,
|
|
795
|
+
prompt_length: prompt.length,
|
|
796
|
+
is_first: is_first_message
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
[] # No reminders
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
def on_swarm_stopped(swarm:)
|
|
803
|
+
# Flush analytics on shutdown
|
|
804
|
+
Analytics.flush
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## See Also
|
|
812
|
+
|
|
813
|
+
- **SwarmMemory Plugin:** `lib/swarm_memory/integration/sdk_plugin.rb` - Real-world example
|
|
814
|
+
- **Plugin Base Class:** `lib/swarm_sdk/plugin.rb` - Interface definition
|
|
815
|
+
- **PluginRegistry:** `lib/swarm_sdk/plugin_registry.rb` - Registry implementation
|
|
816
|
+
- **Writing Adapters:** `docs/v2/guides/memory-adapters.md` - Storage adapters
|