claude_swarm 1.0.0 → 1.0.2
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 +21 -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 -3
- 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 +247 -4
- data/EXAMPLES.md +0 -164
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Production Monitoring with Hooks - Advanced Level
|
|
5
|
+
#
|
|
6
|
+
# This example demonstrates production-ready patterns with the NEW architecture:
|
|
7
|
+
# - Structured logging (JSON format)
|
|
8
|
+
# - Metrics collection (Prometheus/StatsD compatible)
|
|
9
|
+
# - Error tracking and alerting
|
|
10
|
+
# - Performance monitoring
|
|
11
|
+
# - Audit trails
|
|
12
|
+
# - Cost tracking per user/tenant using agent_step
|
|
13
|
+
#
|
|
14
|
+
# NEW ARCHITECTURE: Usage tracking uses agent_step and agent_stop!
|
|
15
|
+
#
|
|
16
|
+
# Run: bundle exec ruby -Ilib lib/swarm_sdk/examples/hooks/03_production_monitoring.rb
|
|
17
|
+
|
|
18
|
+
require "swarm_sdk"
|
|
19
|
+
require "json"
|
|
20
|
+
require "logger"
|
|
21
|
+
|
|
22
|
+
puts "=" * 80
|
|
23
|
+
puts "PRODUCTION MONITORING EXAMPLE"
|
|
24
|
+
puts "=" * 80
|
|
25
|
+
puts ""
|
|
26
|
+
|
|
27
|
+
# Setup structured logging
|
|
28
|
+
@logger = Logger.new($stdout)
|
|
29
|
+
@logger.level = Logger::INFO
|
|
30
|
+
@logger.formatter = proc do |severity, datetime, _progname, msg|
|
|
31
|
+
{
|
|
32
|
+
timestamp: datetime.utc.iso8601,
|
|
33
|
+
severity: severity,
|
|
34
|
+
message: msg,
|
|
35
|
+
}.to_json + "\n"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Metrics collector (simulates StatsD/Prometheus)
|
|
39
|
+
class MetricsCollector
|
|
40
|
+
def initialize
|
|
41
|
+
@metrics = Hash.new { |h, k| h[k] = [] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def increment(metric, value = 1, tags = {})
|
|
45
|
+
@metrics[metric] << { value: value, tags: tags, timestamp: Time.now }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def timing(metric, duration_ms, tags = {})
|
|
49
|
+
@metrics[metric] << { duration_ms: duration_ms, tags: tags, timestamp: Time.now }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def gauge(metric, value, tags = {})
|
|
53
|
+
@metrics[metric] << { value: value, tags: tags, timestamp: Time.now }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def report
|
|
57
|
+
@metrics.each do |metric, values|
|
|
58
|
+
puts "\n#{metric}:"
|
|
59
|
+
if metric.include?("timing")
|
|
60
|
+
durations = values.map { |v| v[:duration_ms] }
|
|
61
|
+
puts " Count: #{durations.size}"
|
|
62
|
+
puts " Min: #{durations.min}ms"
|
|
63
|
+
puts " Max: #{durations.max}ms"
|
|
64
|
+
puts " Avg: #{(durations.sum / durations.size.to_f).round(2)}ms"
|
|
65
|
+
else
|
|
66
|
+
puts " Count: #{values.size}"
|
|
67
|
+
puts " Total: #{values.map { |v| v[:value] }.sum}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@metrics = MetricsCollector.new
|
|
74
|
+
|
|
75
|
+
# Track operation timings
|
|
76
|
+
@operation_start_times = {}
|
|
77
|
+
|
|
78
|
+
# User/tenant context (for multi-tenant systems)
|
|
79
|
+
USER_ID = ENV["USER_ID"] || "user_123"
|
|
80
|
+
TENANT_ID = ENV["TENANT_ID"] || "tenant_abc"
|
|
81
|
+
|
|
82
|
+
# Cost limits per user
|
|
83
|
+
COST_LIMITS = {
|
|
84
|
+
"user_123" => { daily: 10.0, per_request: 1.0 },
|
|
85
|
+
}.freeze
|
|
86
|
+
|
|
87
|
+
# Track costs per user
|
|
88
|
+
@user_costs = Hash.new { |h, k| h[k] = { daily: 0.0, current_request: 0.0 } }
|
|
89
|
+
|
|
90
|
+
swarm = SwarmSDK.build do
|
|
91
|
+
name("Production Monitor")
|
|
92
|
+
lead(:worker)
|
|
93
|
+
|
|
94
|
+
# ============================================================================
|
|
95
|
+
# 1. REQUEST TRACKING
|
|
96
|
+
# ============================================================================
|
|
97
|
+
|
|
98
|
+
hook(:swarm_start) do |context|
|
|
99
|
+
request_id = SecureRandom.uuid
|
|
100
|
+
|
|
101
|
+
@logger.info({
|
|
102
|
+
event: "request_start",
|
|
103
|
+
request_id: request_id,
|
|
104
|
+
user_id: USER_ID,
|
|
105
|
+
tenant_id: TENANT_ID,
|
|
106
|
+
agent: context.agent_name,
|
|
107
|
+
prompt_length: context.metadata[:prompt]&.length,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
# Store request_id in metadata for other hooks
|
|
111
|
+
context.metadata[:request_id] = request_id
|
|
112
|
+
context.metadata[:request_start] = Time.now
|
|
113
|
+
|
|
114
|
+
@metrics.increment("swarm.requests", 1, user_id: USER_ID, tenant_id: TENANT_ID)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
hook(:swarm_stop) do |context|
|
|
118
|
+
duration_ms = ((Time.now - context.metadata[:request_start]) * 1000).round(2)
|
|
119
|
+
|
|
120
|
+
@logger.info({
|
|
121
|
+
event: "request_complete",
|
|
122
|
+
request_id: context.metadata[:request_id],
|
|
123
|
+
user_id: USER_ID,
|
|
124
|
+
tenant_id: TENANT_ID,
|
|
125
|
+
duration_ms: duration_ms,
|
|
126
|
+
success: context.metadata[:success],
|
|
127
|
+
total_cost: context.metadata[:total_cost],
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
@metrics.timing("swarm.request_duration", duration_ms, user_id: USER_ID)
|
|
131
|
+
@metrics.gauge("swarm.request_cost", context.metadata[:total_cost], user_id: USER_ID)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# ============================================================================
|
|
135
|
+
# 2. TOOL MONITORING (NO USAGE HERE!)
|
|
136
|
+
# ============================================================================
|
|
137
|
+
|
|
138
|
+
hook(:pre_tool_use) do |context|
|
|
139
|
+
@operation_start_times[context.tool_call.id] = Time.now
|
|
140
|
+
|
|
141
|
+
@logger.info({
|
|
142
|
+
event: "tool_start",
|
|
143
|
+
tool: context.tool_name,
|
|
144
|
+
tool_call_id: context.tool_call.id,
|
|
145
|
+
agent: context.agent_name,
|
|
146
|
+
user_id: USER_ID,
|
|
147
|
+
parameters: context.tool_call.parameters.keys,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
@metrics.increment("tools.invocations", 1, tool: context.tool_name, agent: context.agent_name)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
hook(:post_tool_use) do |context|
|
|
154
|
+
# Calculate tool execution time
|
|
155
|
+
start_time = @operation_start_times[context.tool_result.tool_call_id]
|
|
156
|
+
duration_ms = start_time ? ((Time.now - start_time) * 1000).round(2) : 0
|
|
157
|
+
|
|
158
|
+
# NO USAGE DATA HERE! (moved to agent_step)
|
|
159
|
+
log_data = {
|
|
160
|
+
event: "tool_complete",
|
|
161
|
+
tool: context.tool_name,
|
|
162
|
+
tool_call_id: context.tool_result.tool_call_id,
|
|
163
|
+
agent: context.agent_name,
|
|
164
|
+
user_id: USER_ID,
|
|
165
|
+
tenant_id: TENANT_ID,
|
|
166
|
+
success: context.tool_result.success?,
|
|
167
|
+
duration_ms: duration_ms,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Log errors
|
|
171
|
+
unless context.tool_result.success?
|
|
172
|
+
log_data[:error] = context.tool_result.error
|
|
173
|
+
@metrics.increment("tools.errors", 1, tool: context.tool_name)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
@logger.info(log_data)
|
|
177
|
+
@metrics.timing("tools.duration", duration_ms, tool: context.tool_name)
|
|
178
|
+
|
|
179
|
+
# Cleanup
|
|
180
|
+
@operation_start_times.delete(context.tool_result.tool_call_id)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# ============================================================================
|
|
184
|
+
# 3. AGENT STEP MONITORING (USAGE IS HERE!)
|
|
185
|
+
# ============================================================================
|
|
186
|
+
|
|
187
|
+
# NEW: agent_step hook for usage tracking!
|
|
188
|
+
hook(:agent_step) do |context|
|
|
189
|
+
usage = context.metadata[:usage]
|
|
190
|
+
|
|
191
|
+
if usage
|
|
192
|
+
cost = usage[:total_cost]
|
|
193
|
+
tool_calls_count = context.metadata[:tool_calls]&.size || 0
|
|
194
|
+
|
|
195
|
+
@logger.info({
|
|
196
|
+
event: "agent_step",
|
|
197
|
+
agent: context.agent_name,
|
|
198
|
+
user_id: USER_ID,
|
|
199
|
+
tenant_id: TENANT_ID,
|
|
200
|
+
model: context.metadata[:model],
|
|
201
|
+
tokens_total: usage[:total_tokens],
|
|
202
|
+
tokens_input: usage[:input_tokens],
|
|
203
|
+
tokens_output: usage[:output_tokens],
|
|
204
|
+
cost: cost,
|
|
205
|
+
context_usage_percent: usage[:tokens_used_percentage],
|
|
206
|
+
tool_calls_count: tool_calls_count,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
# Track costs per user
|
|
210
|
+
@user_costs[USER_ID][:daily] += cost
|
|
211
|
+
@user_costs[USER_ID][:current_request] += cost
|
|
212
|
+
|
|
213
|
+
# Check cost limits
|
|
214
|
+
limits = COST_LIMITS[USER_ID]
|
|
215
|
+
if limits
|
|
216
|
+
if @user_costs[USER_ID][:current_request] > limits[:per_request]
|
|
217
|
+
@logger.error({
|
|
218
|
+
event: "cost_limit_exceeded",
|
|
219
|
+
limit_type: "per_request",
|
|
220
|
+
user_id: USER_ID,
|
|
221
|
+
cost: @user_costs[USER_ID][:current_request],
|
|
222
|
+
limit: limits[:per_request],
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
SwarmSDK::Hooks::Result.halt("Request cost limit exceeded: $#{limits[:per_request]}")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
if @user_costs[USER_ID][:daily] > limits[:daily]
|
|
229
|
+
@logger.error({
|
|
230
|
+
event: "cost_limit_exceeded",
|
|
231
|
+
limit_type: "daily",
|
|
232
|
+
user_id: USER_ID,
|
|
233
|
+
cost: @user_costs[USER_ID][:daily],
|
|
234
|
+
limit: limits[:daily],
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
SwarmSDK::Hooks::Result.halt("Daily cost limit exceeded: $#{limits[:daily]}")
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Metrics
|
|
242
|
+
@metrics.gauge("agent.step_cost", cost, agent: context.agent_name, user_id: USER_ID)
|
|
243
|
+
@metrics.gauge("agent.step_tokens", usage[:total_tokens], agent: context.agent_name)
|
|
244
|
+
@metrics.increment("agent.steps", 1, agent: context.agent_name)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# ============================================================================
|
|
249
|
+
# 4. CONTEXT MONITORING
|
|
250
|
+
# ============================================================================
|
|
251
|
+
|
|
252
|
+
hook(:context_warning) do |context|
|
|
253
|
+
threshold = context.metadata[:threshold]
|
|
254
|
+
percentage = context.metadata[:percentage]
|
|
255
|
+
|
|
256
|
+
@logger.warn({
|
|
257
|
+
event: "context_warning",
|
|
258
|
+
agent: context.agent_name,
|
|
259
|
+
user_id: USER_ID,
|
|
260
|
+
threshold: threshold,
|
|
261
|
+
percentage: percentage,
|
|
262
|
+
tokens_used: context.metadata[:tokens_used],
|
|
263
|
+
tokens_remaining: context.metadata[:tokens_remaining],
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
@metrics.gauge("context.usage_percent", percentage, agent: context.agent_name)
|
|
267
|
+
|
|
268
|
+
# Alert if critical
|
|
269
|
+
if percentage > 95
|
|
270
|
+
@logger.error({
|
|
271
|
+
event: "context_critical",
|
|
272
|
+
agent: context.agent_name,
|
|
273
|
+
percentage: percentage,
|
|
274
|
+
message: "Context window nearly full - execution may fail",
|
|
275
|
+
})
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# ============================================================================
|
|
280
|
+
# 5. AGENT LIFECYCLE
|
|
281
|
+
# ============================================================================
|
|
282
|
+
|
|
283
|
+
hook(:agent_stop) do |context|
|
|
284
|
+
usage = context.metadata[:usage]
|
|
285
|
+
|
|
286
|
+
@logger.info({
|
|
287
|
+
event: "agent_complete",
|
|
288
|
+
agent: context.agent_name,
|
|
289
|
+
user_id: USER_ID,
|
|
290
|
+
model: context.metadata[:model],
|
|
291
|
+
finish_reason: context.metadata[:finish_reason],
|
|
292
|
+
tokens_total: usage[:total_tokens],
|
|
293
|
+
cost: usage[:total_cost],
|
|
294
|
+
context_usage: usage[:tokens_used_percentage],
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
# Track final step cost
|
|
298
|
+
if usage
|
|
299
|
+
cost = usage[:total_cost]
|
|
300
|
+
@user_costs[USER_ID][:daily] += cost
|
|
301
|
+
@user_costs[USER_ID][:current_request] += cost
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
@metrics.increment("agent.completions", 1, agent: context.agent_name)
|
|
305
|
+
@metrics.gauge("agent.final_cost", usage[:total_cost], agent: context.agent_name)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# ============================================================================
|
|
309
|
+
# 6. ERROR TRACKING
|
|
310
|
+
# ============================================================================
|
|
311
|
+
|
|
312
|
+
# NOTE: In production, you'd integrate with Sentry, Rollbar, etc.
|
|
313
|
+
# This is a simplified example.
|
|
314
|
+
|
|
315
|
+
agent(:worker) do
|
|
316
|
+
description("Worker agent with comprehensive monitoring")
|
|
317
|
+
model("gpt-4o-mini")
|
|
318
|
+
system_prompt("You are a helpful worker. Complete tasks efficiently.")
|
|
319
|
+
tools(:Write)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Execute task
|
|
324
|
+
puts "\n--- Executing Monitored Task ---"
|
|
325
|
+
puts "User: #{USER_ID}"
|
|
326
|
+
puts "Tenant: #{TENANT_ID}"
|
|
327
|
+
puts ""
|
|
328
|
+
|
|
329
|
+
begin
|
|
330
|
+
result = swarm.execute("Create a file called report.txt with a brief summary of monitoring best practices")
|
|
331
|
+
|
|
332
|
+
puts "\n--- Execution Complete ---"
|
|
333
|
+
puts "Success: #{result.success?}"
|
|
334
|
+
puts "Cost: $#{format("%.6f", @user_costs[USER_ID][:current_request])}"
|
|
335
|
+
puts ""
|
|
336
|
+
|
|
337
|
+
# Show metrics report
|
|
338
|
+
puts "\n--- Metrics Report ---"
|
|
339
|
+
@metrics.report
|
|
340
|
+
|
|
341
|
+
# Show user cost tracking
|
|
342
|
+
puts "\n--- User Cost Tracking ---"
|
|
343
|
+
@user_costs.each do |user_id, costs|
|
|
344
|
+
puts "#{user_id}:"
|
|
345
|
+
puts " Current request: $#{format("%.6f", costs[:current_request])}"
|
|
346
|
+
puts " Daily total: $#{format("%.6f", costs[:daily])}"
|
|
347
|
+
|
|
348
|
+
next unless (limits = COST_LIMITS[user_id])
|
|
349
|
+
|
|
350
|
+
puts " Limits:"
|
|
351
|
+
puts " Per request: $#{limits[:per_request]} (#{(costs[:current_request] / limits[:per_request] * 100).round}% used)"
|
|
352
|
+
puts " Daily: $#{limits[:daily]} (#{(costs[:daily] / limits[:daily] * 100).round}% used)"
|
|
353
|
+
end
|
|
354
|
+
rescue => e
|
|
355
|
+
@logger.error({
|
|
356
|
+
event: "execution_error",
|
|
357
|
+
user_id: USER_ID,
|
|
358
|
+
error_class: e.class.name,
|
|
359
|
+
error_message: e.message,
|
|
360
|
+
backtrace: e.backtrace[0..5],
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
puts "\nError: #{e.message}"
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
puts "\n" + "=" * 80
|
|
367
|
+
puts "PRODUCTION PATTERNS DEMONSTRATED"
|
|
368
|
+
puts "=" * 80
|
|
369
|
+
puts <<~SUMMARY
|
|
370
|
+
|
|
371
|
+
1. **Structured Logging**
|
|
372
|
+
- JSON format for easy parsing
|
|
373
|
+
- Rich context (user_id, tenant_id, request_id)
|
|
374
|
+
- Event-based structure
|
|
375
|
+
- Integration-ready (Elasticsearch, CloudWatch, etc.)
|
|
376
|
+
|
|
377
|
+
2. **Metrics Collection**
|
|
378
|
+
- Counters (requests, tool invocations, agent steps, errors)
|
|
379
|
+
- Timings (request duration, tool execution time)
|
|
380
|
+
- Gauges (costs, token usage, context percentage)
|
|
381
|
+
- Tags for grouping (user, tenant, tool, agent)
|
|
382
|
+
|
|
383
|
+
3. **Cost Management (NEW ARCHITECTURE!)**
|
|
384
|
+
- Per-user cost tracking in agent_step hook
|
|
385
|
+
- Daily and per-request limits
|
|
386
|
+
- Automatic limit enforcement
|
|
387
|
+
- Cost attribution (which user/tenant spent what)
|
|
388
|
+
|
|
389
|
+
4. **Performance Monitoring**
|
|
390
|
+
- Request duration tracking
|
|
391
|
+
- Tool execution timing
|
|
392
|
+
- Agent step tracking
|
|
393
|
+
- Context usage monitoring
|
|
394
|
+
|
|
395
|
+
5. **Error Tracking**
|
|
396
|
+
- Tool failure logging
|
|
397
|
+
- Context overflow warnings
|
|
398
|
+
- Cost limit violations
|
|
399
|
+
- Integration-ready for Sentry/Rollbar
|
|
400
|
+
|
|
401
|
+
6. **Audit Trail**
|
|
402
|
+
- Complete request lifecycle tracking
|
|
403
|
+
- Tool parameter logging (keys only for security)
|
|
404
|
+
- User/tenant attribution
|
|
405
|
+
- Compliance-ready logging
|
|
406
|
+
|
|
407
|
+
**KEY ARCHITECTURAL CHANGE:**
|
|
408
|
+
- Usage tracking moved from post_tool_use to agent_step!
|
|
409
|
+
- post_tool_use: Tracks tool execution (duration, success/failure)
|
|
410
|
+
- agent_step: Tracks LLM usage (tokens, costs)
|
|
411
|
+
- One agent_step may trigger multiple tool calls
|
|
412
|
+
- Usage reflects the cost of the LLM response, not individual tools
|
|
413
|
+
|
|
414
|
+
**Integration Points:**
|
|
415
|
+
- Logs → Elasticsearch, CloudWatch, Datadog
|
|
416
|
+
- Metrics → Prometheus, StatsD, Datadog
|
|
417
|
+
- Errors → Sentry, Rollbar, Bugsnag
|
|
418
|
+
- Costs → Internal billing systems
|
|
419
|
+
|
|
420
|
+
**Production Considerations:**
|
|
421
|
+
- Implement log sampling for high-volume systems
|
|
422
|
+
- Use async metrics shipping (avoid blocking hooks)
|
|
423
|
+
- Set up alerting on critical thresholds
|
|
424
|
+
- Configure log retention policies
|
|
425
|
+
- Implement PII scrubbing in logs
|
|
426
|
+
|
|
427
|
+
SUMMARY
|
|
428
|
+
|
|
429
|
+
puts "=" * 80
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Test: agent_stop with exit 0
|
|
2
|
+
# Expected: stdout added to log stream, agent stops normally
|
|
3
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/agent_stop_exit_0.yml -p "What is 2+2?"
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
swarm:
|
|
7
|
+
name: "agent_stop Exit 0 Test"
|
|
8
|
+
lead: agent
|
|
9
|
+
|
|
10
|
+
agents:
|
|
11
|
+
agent:
|
|
12
|
+
description: "Test agent"
|
|
13
|
+
model: gpt-5
|
|
14
|
+
provider: openai
|
|
15
|
+
system_prompt: "You are a test agent."
|
|
16
|
+
|
|
17
|
+
hooks:
|
|
18
|
+
agent_stop:
|
|
19
|
+
- type: command
|
|
20
|
+
command: "echo 'Agent completed task successfully - logged for audit' && exit 0"
|
|
21
|
+
timeout: 5
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Test: agent_stop with exit 1
|
|
2
|
+
# Expected: stderr added to log stream, agent stops normally (non-blocking error)
|
|
3
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/agent_stop_exit_1.yml -p "What is 2+2?"
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
swarm:
|
|
7
|
+
name: "agent_stop Exit 1 Test"
|
|
8
|
+
lead: agent
|
|
9
|
+
|
|
10
|
+
agents:
|
|
11
|
+
agent:
|
|
12
|
+
description: "Test agent"
|
|
13
|
+
model: gpt-5
|
|
14
|
+
provider: openai
|
|
15
|
+
system_prompt: "You are a test agent."
|
|
16
|
+
|
|
17
|
+
hooks:
|
|
18
|
+
agent_stop:
|
|
19
|
+
- type: command
|
|
20
|
+
command: "echo 'Warning: Failed to save agent metrics to database' >&2 && exit 1"
|
|
21
|
+
timeout: 5
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Test: agent_stop with exit 2
|
|
2
|
+
# Expected: Blocks stoppage ONCE, sends stderr to agent, agent continues
|
|
3
|
+
# Note: This uses a Ruby callback (not shell hook) to implement "block once" behavior
|
|
4
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/agent_stop_exit_2.yml -p "What is 2+2?"
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
swarm:
|
|
8
|
+
name: "agent_stop Exit 2 Test"
|
|
9
|
+
lead: agent
|
|
10
|
+
|
|
11
|
+
agents:
|
|
12
|
+
agent:
|
|
13
|
+
description: "Test agent"
|
|
14
|
+
model: gpt-5
|
|
15
|
+
provider: openai
|
|
16
|
+
system_prompt: "You are a test agent."
|
|
17
|
+
|
|
18
|
+
# NOTE: agent_stop exit 2 behavior requires stateful "block once" logic
|
|
19
|
+
# This is better implemented via Ruby callbacks, not shell hooks
|
|
20
|
+
# See docs/hooks.md for agent_stop reprompting examples with callbacks
|
|
21
|
+
|
|
22
|
+
hooks:
|
|
23
|
+
agent_stop:
|
|
24
|
+
- type: command
|
|
25
|
+
command: "echo 'Response quality check: Please provide more detail in your answer' >&2 && exit 2"
|
|
26
|
+
timeout: 5
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Test: Multiple hooks for same event - all pass
|
|
2
|
+
# Expected: All three hooks run sequentially, tool executes
|
|
3
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/multiple_hooks_all_pass.yml -p "Run 'ls' command" --verbose
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
swarm:
|
|
7
|
+
name: "Multiple Hooks - All Pass"
|
|
8
|
+
lead: agent
|
|
9
|
+
|
|
10
|
+
agents:
|
|
11
|
+
agent:
|
|
12
|
+
description: "Test agent"
|
|
13
|
+
model: gpt-5
|
|
14
|
+
provider: openai
|
|
15
|
+
system_prompt: "You are a test agent."
|
|
16
|
+
tools:
|
|
17
|
+
- Bash
|
|
18
|
+
|
|
19
|
+
hooks:
|
|
20
|
+
pre_tool_use:
|
|
21
|
+
# Hook 1: Succeeds
|
|
22
|
+
- matcher: "Bash"
|
|
23
|
+
type: command
|
|
24
|
+
command: "echo 'Hook 1: validation passed' && echo 'Hook 1 executed' >> tmp/hooks_log.txt && exit 0"
|
|
25
|
+
timeout: 5
|
|
26
|
+
|
|
27
|
+
# Hook 2: Succeeds
|
|
28
|
+
- matcher: "Bash"
|
|
29
|
+
type: command
|
|
30
|
+
command: "echo 'Hook 2: security check passed' && echo 'Hook 2 executed' >> tmp/hooks_log.txt && exit 0"
|
|
31
|
+
timeout: 5
|
|
32
|
+
|
|
33
|
+
# Hook 3: Succeeds
|
|
34
|
+
- matcher: "Bash"
|
|
35
|
+
type: command
|
|
36
|
+
command: "echo 'Hook 3: audit logged' && echo 'Hook 3 executed' >> tmp/hooks_log.txt && exit 0"
|
|
37
|
+
timeout: 5
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Test: Multiple hooks for same event - first one fails with exit 2
|
|
2
|
+
# Expected: First hook blocks, remaining hooks DON'T run
|
|
3
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/multiple_hooks_first_fails.yml -p "Run 'ls' command" --verbose
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
swarm:
|
|
7
|
+
name: "Multiple Hooks - First Fails"
|
|
8
|
+
lead: agent
|
|
9
|
+
|
|
10
|
+
agents:
|
|
11
|
+
agent:
|
|
12
|
+
description: "Test agent"
|
|
13
|
+
model: gpt-5
|
|
14
|
+
provider: openai
|
|
15
|
+
system_prompt: "You are a test agent."
|
|
16
|
+
tools:
|
|
17
|
+
- Bash
|
|
18
|
+
|
|
19
|
+
hooks:
|
|
20
|
+
pre_tool_use:
|
|
21
|
+
# Hook 1: Fails immediately (exit 2)
|
|
22
|
+
- matcher: "Bash"
|
|
23
|
+
type: command
|
|
24
|
+
command: "echo 'Hook 1: BLOCKING' >&2 && echo 'Hook 1 executed' >> tmp/hooks_log.txt && exit 2"
|
|
25
|
+
timeout: 5
|
|
26
|
+
|
|
27
|
+
# Hook 2: Would succeed (but shouldn't run because hook 1 blocks)
|
|
28
|
+
- matcher: "Bash"
|
|
29
|
+
type: command
|
|
30
|
+
command: "echo 'Hook 2: This should NOT run' && echo 'Hook 2 executed' >> tmp/hooks_log.txt && exit 0"
|
|
31
|
+
timeout: 5
|
|
32
|
+
|
|
33
|
+
# Hook 3: Would succeed (but shouldn't run)
|
|
34
|
+
- matcher: "Bash"
|
|
35
|
+
type: command
|
|
36
|
+
command: "echo 'Hook 3: This should NOT run' && echo 'Hook 3 executed' >> tmp/hooks_log.txt && exit 0"
|
|
37
|
+
timeout: 5
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Test: Multiple hooks for same event - second one fails with exit 2
|
|
2
|
+
# Expected: First hook runs, second blocks, third doesn't run
|
|
3
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/multiple_hooks_second_fails.yml -p "Run 'ls' command" --verbose
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
swarm:
|
|
7
|
+
name: "Multiple Hooks - Second Fails"
|
|
8
|
+
lead: agent
|
|
9
|
+
|
|
10
|
+
agents:
|
|
11
|
+
agent:
|
|
12
|
+
description: "Test agent"
|
|
13
|
+
model: gpt-5
|
|
14
|
+
provider: openai
|
|
15
|
+
system_prompt: "You are a test agent."
|
|
16
|
+
tools:
|
|
17
|
+
- Bash
|
|
18
|
+
|
|
19
|
+
hooks:
|
|
20
|
+
pre_tool_use:
|
|
21
|
+
# Hook 1: Succeeds (exit 0)
|
|
22
|
+
- matcher: "Bash"
|
|
23
|
+
type: command
|
|
24
|
+
command: "echo 'Hook 1: passed' && echo 'Hook 1 executed' >> tmp/hooks_log.txt && exit 0"
|
|
25
|
+
timeout: 5
|
|
26
|
+
|
|
27
|
+
# Hook 2: Fails (exit 2)
|
|
28
|
+
- matcher: "Bash"
|
|
29
|
+
type: command
|
|
30
|
+
command: "echo 'Hook 2: BLOCKING' >&2 && echo 'Hook 2 executed' >> tmp/hooks_log.txt && exit 2"
|
|
31
|
+
timeout: 5
|
|
32
|
+
|
|
33
|
+
# Hook 3: Would succeed (but shouldn't run because hook 2 blocks)
|
|
34
|
+
- matcher: "Bash"
|
|
35
|
+
type: command
|
|
36
|
+
command: "echo 'Hook 3: This should NOT run' && echo 'Hook 3 executed' >> tmp/hooks_log.txt && exit 0"
|
|
37
|
+
timeout: 5
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Test: Multiple hooks with warnings (exit 1)
|
|
2
|
+
# Expected: All hooks run with warnings, execution continues
|
|
3
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/multiple_hooks_warnings.yml -p "Run 'ls' command" --verbose
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
swarm:
|
|
7
|
+
name: "Multiple Hooks - Warnings"
|
|
8
|
+
lead: agent
|
|
9
|
+
|
|
10
|
+
agents:
|
|
11
|
+
agent:
|
|
12
|
+
description: "Test agent"
|
|
13
|
+
model: gpt-5
|
|
14
|
+
provider: openai
|
|
15
|
+
system_prompt: "You are a test agent."
|
|
16
|
+
tools:
|
|
17
|
+
- Bash
|
|
18
|
+
|
|
19
|
+
hooks:
|
|
20
|
+
pre_tool_use:
|
|
21
|
+
# Hook 1: Warning (exit 1)
|
|
22
|
+
- matcher: "Bash"
|
|
23
|
+
type: command
|
|
24
|
+
command: "echo 'Warning 1: Metrics collection unavailable' >&2 && echo 'Hook 1 executed' >> tmp/hooks_log.txt && exit 1"
|
|
25
|
+
timeout: 5
|
|
26
|
+
|
|
27
|
+
# Hook 2: Warning (exit 1)
|
|
28
|
+
- matcher: "Bash"
|
|
29
|
+
type: command
|
|
30
|
+
command: "echo 'Warning 2: Audit log write failed' >&2 && echo 'Hook 2 executed' >> tmp/hooks_log.txt && exit 1"
|
|
31
|
+
timeout: 5
|
|
32
|
+
|
|
33
|
+
# Hook 3: Succeeds (exit 0)
|
|
34
|
+
- matcher: "Bash"
|
|
35
|
+
type: command
|
|
36
|
+
command: "echo 'Hook 3: Final validation passed' && echo 'Hook 3 executed' >> tmp/hooks_log.txt && exit 0"
|
|
37
|
+
timeout: 5
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Test: post_tool_use with exit 0
|
|
2
|
+
# Expected: stdout added to log stream, execution continues
|
|
3
|
+
# Run: bundle exec exe/swarm run lib/swarm_sdk/examples/hooks/post_tool_use_exit_0.yml -p "Run 'ls' command"
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
swarm:
|
|
7
|
+
name: "post_tool_use Exit 0 Test"
|
|
8
|
+
lead: agent
|
|
9
|
+
|
|
10
|
+
agents:
|
|
11
|
+
agent:
|
|
12
|
+
description: "Test agent"
|
|
13
|
+
model: gpt-5
|
|
14
|
+
provider: openai
|
|
15
|
+
system_prompt: "You are a test agent."
|
|
16
|
+
tools:
|
|
17
|
+
- Bash
|
|
18
|
+
|
|
19
|
+
hooks:
|
|
20
|
+
post_tool_use:
|
|
21
|
+
- matcher: "Bash"
|
|
22
|
+
type: command
|
|
23
|
+
command: "echo 'Bash tool completed successfully - logged for audit' && exit 0"
|
|
24
|
+
timeout: 5
|