agent99 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/A2A_SPEC-dev.md +1829 -0
- data/CHANGELOG.md +38 -0
- data/COMMITS.md +196 -0
- data/DOCS.md +96 -0
- data/README.md +212 -84
- data/Rakefile +62 -0
- data/docs/AI/htm.md +215 -0
- data/docs/AI/htm.rb +141 -0
- data/docs/AI/htm_demo.db +0 -0
- data/docs/AI/notes_on_htm_implementation.md +1319 -0
- data/docs/AI/some_code.rb +692 -0
- data/docs/advanced-topics/a2a-protocol.md +13 -0
- data/docs/{advanced_features.md → advanced-topics/advanced-features.md} +9 -4
- data/docs/{control_actions.md → advanced-topics/control-actions.md} +2 -0
- data/docs/advanced-topics/model-context-protocol.md +4 -0
- data/docs/advanced-topics/multi-agent-processing.md +674 -0
- data/docs/agent-development/request-response-handling.md +512 -0
- data/docs/agent99_framework/central_registry.md +94 -0
- data/docs/agent99_framework/message_client.md +120 -0
- data/docs/agent99_framework/registry_client.md +119 -0
- data/docs/api-reference/agent99-base.md +463 -0
- data/docs/api-reference/message-clients.md +495 -0
- data/docs/{api_reference.md → api-reference/overview.md} +14 -4
- data/docs/api-reference/registry-client.md +470 -0
- data/docs/api-reference/schemas.md +518 -0
- data/docs/assets/css/custom.css +27 -0
- data/docs/assets/images/agent-lifecycle.svg +73 -0
- data/docs/assets/images/agent-registry-process.svg +86 -0
- data/docs/assets/images/agent-registry-processes.svg +114 -0
- data/docs/assets/images/agent-types-overview.svg +51 -0
- data/docs/assets/images/agent99-architecture.svg +85 -0
- data/docs/assets/images/agent99_logo.png +0 -0
- data/docs/assets/images/control-actions-state.svg +83 -0
- data/docs/assets/images/knowledge-graph.svg +77 -0
- data/docs/assets/images/message-processing-flow.svg +148 -0
- data/docs/assets/images/multi-agent-system.svg +66 -0
- data/docs/assets/images/proxy-pattern-sequence.svg +48 -0
- data/docs/assets/images/request-flow.svg +97 -0
- data/docs/assets/images/request-processing-lifecycle.svg +50 -0
- data/docs/assets/images/request-response-sequence.svg +39 -0
- data/docs/{agent_lifecycle.md → core-concepts/agent-lifecycle.md} +2 -0
- data/docs/core-concepts/agent-types.md +255 -0
- data/docs/{architecture.md → core-concepts/architecture.md} +5 -5
- data/docs/core-concepts/what-is-an-agent.md +293 -0
- data/docs/diagrams/message-flow-sequence.svg +198 -0
- data/docs/diagrams/p2p-network-topology.svg +181 -0
- data/docs/diagrams/smart-transport-routing.svg +165 -0
- data/docs/diagrams/three-layer-architecture.svg +77 -0
- data/docs/diagrams/transport-extension-api.svg +309 -0
- data/docs/diagrams/transport-extension-architecture.svg +234 -0
- data/docs/diagrams/transport-selection-flowchart.svg +264 -0
- data/docs/examples/advanced-examples.md +951 -0
- data/docs/examples/basic-examples.md +268 -0
- data/docs/{agent_discovery.md → framework-components/agent-discovery.md} +9 -5
- data/docs/{agent_registry_processes.md → framework-components/agent-registry.md} +9 -3
- data/docs/{message_processing.md → framework-components/message-processing.md} +3 -1
- data/docs/getting-started/basic-example.md +306 -0
- data/docs/getting-started/installation.md +160 -0
- data/docs/getting-started/overview.md +64 -0
- data/docs/getting-started/quick-start.md +179 -0
- data/docs/index.md +97 -0
- data/docs/operations/breaking-changes.md +26 -0
- data/examples/DEMO.md +148 -0
- data/examples/README.md +50 -0
- data/examples/agent_watcher.rb +5 -1
- data/examples/bad_agent.rb +32 -0
- data/examples/chief_agent.rb +17 -6
- data/examples/control.rb +16 -7
- data/examples/example_agent.rb +16 -3
- data/examples/maxwell_agent86.rb +15 -26
- data/examples/registry.rb +10 -9
- data/examples/run_demo.rb +433 -0
- data/lib/agent99/agent_discovery.rb +4 -0
- data/lib/agent99/agent_lifecycle.rb +34 -10
- data/lib/agent99/amqp_message_client.rb +2 -2
- data/lib/agent99/base.rb +6 -2
- data/lib/agent99/message_processing.rb +6 -10
- data/lib/agent99/registry_client.rb +15 -11
- data/lib/agent99/tcp_message_client.rb +183 -0
- data/lib/agent99/version.rb +1 -1
- data/lib/agent99.rb +1 -1
- data/mkdocs.yml +195 -0
- data/p2p_plan.md +533 -0
- data/p2p_roadmap.md +299 -0
- data/registry_plan.md +1818 -0
- metadata +93 -30
- data/docs/README.md +0 -57
- data/docs/diagrams/agent_registry_processes.dot +0 -42
- data/docs/diagrams/agent_registry_processes.png +0 -0
- data/docs/diagrams/high_level_architecture.dot +0 -26
- data/docs/diagrams/high_level_architecture.png +0 -0
- data/docs/diagrams/request_flow.dot +0 -42
- data/docs/diagrams/request_flow.png +0 -0
- /data/docs/{extending_the_framework.md → advanced-topics/extending-the-framework.md} +0 -0
- /data/docs/{custom_agent_implementation.md → agent-development/custom-agent-implementation.md} +0 -0
- /data/docs/{error_handling_and_logging.md → agent-development/error-handling-and-logging.md} +0 -0
- /data/docs/{schema_definition.md → agent-development/schema-definition.md} +0 -0
- /data/docs/{messaging_system.md → framework-components/messaging-system.md} +0 -0
- /data/docs/{configuration.md → operations/configuration.md} +0 -0
- /data/docs/{preformance_considerations.md → operations/performance-considerations.md} +0 -0
- /data/docs/{security.md → operations/security.md} +0 -0
- /data/docs/{troubleshooting.md → operations/troubleshooting.md} +0 -0
@@ -0,0 +1,433 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/run_demo.rb
|
3
|
+
#
|
4
|
+
# Comprehensive demo script for Agent99 framework examples
|
5
|
+
# This script orchestrates running multiple examples automatically
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'optparse'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'timeout'
|
11
|
+
|
12
|
+
class Agent99Demo
|
13
|
+
EXAMPLES_DIR = File.dirname(__FILE__)
|
14
|
+
|
15
|
+
# Available demo scenarios
|
16
|
+
SCENARIOS = {
|
17
|
+
'basic' => {
|
18
|
+
description: 'Basic Maxwell Agent86 and Chief interaction',
|
19
|
+
agents: ['maxwell_agent86.rb', 'chief_agent.rb'],
|
20
|
+
duration: 10
|
21
|
+
},
|
22
|
+
'control' => {
|
23
|
+
description: 'Control agent managing other agents',
|
24
|
+
agents: ['maxwell_agent86.rb', 'control.rb'],
|
25
|
+
duration: 15
|
26
|
+
},
|
27
|
+
'watcher' => {
|
28
|
+
description: 'Agent watcher dynamically loading new agents',
|
29
|
+
agents: ['agent_watcher.rb'],
|
30
|
+
duration: 20,
|
31
|
+
special: :watcher_demo
|
32
|
+
},
|
33
|
+
'security' => {
|
34
|
+
description: 'KAOS spy demonstration (security example)',
|
35
|
+
agents: ['maxwell_agent86.rb', 'kaos_spy.rb'],
|
36
|
+
duration: 10,
|
37
|
+
warning: 'This demonstrates a malicious agent for educational purposes'
|
38
|
+
},
|
39
|
+
'all' => {
|
40
|
+
description: 'Run multiple scenarios in sequence',
|
41
|
+
agents: [],
|
42
|
+
duration: 60,
|
43
|
+
special: :run_all_scenarios
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@pids = []
|
49
|
+
@registry_pid = nil
|
50
|
+
@rabbitmq_pid = nil
|
51
|
+
@options = {
|
52
|
+
scenario: 'basic',
|
53
|
+
verbose: false,
|
54
|
+
no_cleanup: false,
|
55
|
+
list_only: false
|
56
|
+
}
|
57
|
+
setup_signal_handlers
|
58
|
+
end
|
59
|
+
|
60
|
+
def run(args = ARGV)
|
61
|
+
parse_options(args)
|
62
|
+
|
63
|
+
if @options[:list_only]
|
64
|
+
list_scenarios
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
puts "🚀 Starting Agent99 Demo: #{@options[:scenario]}"
|
69
|
+
puts "📁 Working directory: #{EXAMPLES_DIR}"
|
70
|
+
puts
|
71
|
+
|
72
|
+
scenario = SCENARIOS[@options[:scenario]]
|
73
|
+
unless scenario
|
74
|
+
puts "❌ Unknown scenario: #{@options[:scenario]}"
|
75
|
+
list_scenarios
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
|
79
|
+
if scenario[:warning]
|
80
|
+
puts "⚠️ WARNING: #{scenario[:warning]}"
|
81
|
+
puts "Continue? (y/N): "
|
82
|
+
response = gets.chomp.downcase
|
83
|
+
unless response == 'y' || response == 'yes'
|
84
|
+
puts "Demo cancelled."
|
85
|
+
exit 0
|
86
|
+
end
|
87
|
+
puts
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
check_dependencies
|
92
|
+
start_infrastructure
|
93
|
+
|
94
|
+
case scenario[:special]
|
95
|
+
when :run_all_scenarios
|
96
|
+
run_all_scenarios
|
97
|
+
when :watcher_demo
|
98
|
+
run_watcher_demo
|
99
|
+
else
|
100
|
+
run_scenario(scenario)
|
101
|
+
end
|
102
|
+
|
103
|
+
rescue Interrupt
|
104
|
+
puts "\n🛑 Demo interrupted by user"
|
105
|
+
rescue => e
|
106
|
+
puts "❌ Error running demo: #{e.message}"
|
107
|
+
puts e.backtrace.first(5) if @options[:verbose]
|
108
|
+
ensure
|
109
|
+
cleanup unless @options[:no_cleanup]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def parse_options(args)
|
116
|
+
OptionParser.new do |opts|
|
117
|
+
opts.banner = "Usage: #{$0} [options]"
|
118
|
+
opts.separator ""
|
119
|
+
opts.separator "Agent99 Framework Demo Runner"
|
120
|
+
opts.separator ""
|
121
|
+
opts.separator "Options:"
|
122
|
+
|
123
|
+
opts.on("-s", "--scenario SCENARIO", "Demo scenario to run (#{SCENARIOS.keys.join(', ')})") do |s|
|
124
|
+
@options[:scenario] = s
|
125
|
+
end
|
126
|
+
|
127
|
+
opts.on("-l", "--list", "List available scenarios") do
|
128
|
+
@options[:list_only] = true
|
129
|
+
end
|
130
|
+
|
131
|
+
opts.on("-v", "--verbose", "Verbose output") do
|
132
|
+
@options[:verbose] = true
|
133
|
+
end
|
134
|
+
|
135
|
+
opts.on("--no-cleanup", "Don't cleanup processes on exit (for debugging)") do
|
136
|
+
@options[:no_cleanup] = true
|
137
|
+
end
|
138
|
+
|
139
|
+
opts.on("-h", "--help", "Show this help") do
|
140
|
+
puts opts
|
141
|
+
exit
|
142
|
+
end
|
143
|
+
|
144
|
+
opts.separator ""
|
145
|
+
opts.separator "Examples:"
|
146
|
+
opts.separator " #{$0} -s basic # Run basic Maxwell/Chief demo"
|
147
|
+
opts.separator " #{$0} -s security # Run security demonstration"
|
148
|
+
opts.separator " #{$0} -l # List all available scenarios"
|
149
|
+
end.parse!(args)
|
150
|
+
end
|
151
|
+
|
152
|
+
def list_scenarios
|
153
|
+
puts "Available demo scenarios:"
|
154
|
+
puts
|
155
|
+
SCENARIOS.each do |name, config|
|
156
|
+
puts " #{name.ljust(12)} - #{config[:description]}"
|
157
|
+
puts "#{' ' * 17}Duration: ~#{config[:duration]}s"
|
158
|
+
puts "#{' ' * 17}Warning: #{config[:warning]}" if config[:warning]
|
159
|
+
puts
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def check_dependencies
|
164
|
+
puts "🔍 Checking dependencies..."
|
165
|
+
|
166
|
+
# Check if Ruby scripts exist
|
167
|
+
required_files = %w[registry.rb]
|
168
|
+
scenario = SCENARIOS[@options[:scenario]]
|
169
|
+
required_files += scenario[:agents] if scenario[:agents]
|
170
|
+
|
171
|
+
required_files.each do |file|
|
172
|
+
path = File.join(EXAMPLES_DIR, file)
|
173
|
+
unless File.exist?(path)
|
174
|
+
puts "❌ Missing required file: #{file}"
|
175
|
+
exit 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Check for rabbitmq (optional warning)
|
180
|
+
begin
|
181
|
+
system("which rabbitmq-server > /dev/null 2>&1")
|
182
|
+
unless $?.success?
|
183
|
+
puts "⚠️ RabbitMQ not found. Install with: brew install rabbitmq-server"
|
184
|
+
puts " Continuing anyway - agents will use fallback message client"
|
185
|
+
end
|
186
|
+
rescue
|
187
|
+
puts "⚠️ Could not check for RabbitMQ"
|
188
|
+
end
|
189
|
+
|
190
|
+
# Check for boxes command (used by chief_agent)
|
191
|
+
begin
|
192
|
+
system("boxes --help > /dev/null 2>&1")
|
193
|
+
unless $?.success?
|
194
|
+
puts "ℹ️ 'boxes' command not found. Install with: brew install boxes"
|
195
|
+
puts " Chief agent output will be plain text"
|
196
|
+
end
|
197
|
+
rescue
|
198
|
+
end
|
199
|
+
|
200
|
+
puts "✅ Dependencies check complete"
|
201
|
+
puts
|
202
|
+
end
|
203
|
+
|
204
|
+
def start_infrastructure
|
205
|
+
puts "🏗️ Starting infrastructure..."
|
206
|
+
|
207
|
+
Dir.chdir(EXAMPLES_DIR) do
|
208
|
+
# Start RabbitMQ in background (if available)
|
209
|
+
if system("which rabbitmq-server > /dev/null 2>&1")
|
210
|
+
puts " Starting RabbitMQ server..."
|
211
|
+
@rabbitmq_pid = spawn("rabbitmq-server", out: "/dev/null", err: "/dev/null")
|
212
|
+
sleep 3 # Give RabbitMQ time to start
|
213
|
+
end
|
214
|
+
|
215
|
+
# Start registry
|
216
|
+
puts " Starting registry service on http://localhost:4567..."
|
217
|
+
@registry_pid = spawn("ruby registry.rb", out: @options[:verbose] ? $stdout : "/dev/null")
|
218
|
+
sleep 2 # Give registry time to start
|
219
|
+
|
220
|
+
# Test registry connection
|
221
|
+
begin
|
222
|
+
require 'net/http'
|
223
|
+
response = Net::HTTP.get_response(URI('http://localhost:4567/healthcheck'))
|
224
|
+
if response.code == '200'
|
225
|
+
puts "✅ Registry service started successfully"
|
226
|
+
else
|
227
|
+
raise "Registry returned status #{response.code}"
|
228
|
+
end
|
229
|
+
rescue => e
|
230
|
+
puts "❌ Failed to connect to registry: #{e.message}"
|
231
|
+
exit 1
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
puts
|
236
|
+
end
|
237
|
+
|
238
|
+
def run_scenario(scenario)
|
239
|
+
puts "🎬 Running scenario: #{scenario[:description]}"
|
240
|
+
puts " Duration: ~#{scenario[:duration]} seconds"
|
241
|
+
puts
|
242
|
+
|
243
|
+
Dir.chdir(EXAMPLES_DIR) do
|
244
|
+
agent_pids = []
|
245
|
+
|
246
|
+
# Start each agent
|
247
|
+
scenario[:agents].each_with_index do |agent, index|
|
248
|
+
puts " Starting agent: #{agent}"
|
249
|
+
|
250
|
+
if agent == 'chief_agent.rb'
|
251
|
+
# Chief agent runs once and exits, so we handle it specially
|
252
|
+
sleep 1 # Give other agents time to register
|
253
|
+
puts " 🎯 Running Chief Agent (one-shot)..."
|
254
|
+
system("ruby #{agent}")
|
255
|
+
else
|
256
|
+
# Regular agents run continuously
|
257
|
+
pid = spawn("ruby #{agent}",
|
258
|
+
out: @options[:verbose] ? $stdout : "/dev/null",
|
259
|
+
err: @options[:verbose] ? $stderr : "/dev/null")
|
260
|
+
agent_pids << pid
|
261
|
+
@pids << pid
|
262
|
+
sleep 1 # Stagger startup
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
unless scenario[:agents].include?('chief_agent.rb')
|
267
|
+
# For continuous scenarios, run for specified duration
|
268
|
+
puts " ⏱️ Running for #{scenario[:duration]} seconds..."
|
269
|
+
puts " Press Ctrl+C to stop early"
|
270
|
+
sleep scenario[:duration]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
puts "✅ Scenario completed"
|
275
|
+
end
|
276
|
+
|
277
|
+
def run_watcher_demo
|
278
|
+
puts "🎬 Running Agent Watcher Demo"
|
279
|
+
puts " This demo shows dynamic agent loading"
|
280
|
+
puts
|
281
|
+
|
282
|
+
Dir.chdir(EXAMPLES_DIR) do
|
283
|
+
# Ensure agents directory exists
|
284
|
+
FileUtils.mkdir_p('agents')
|
285
|
+
|
286
|
+
# Start the agent watcher
|
287
|
+
puts " Starting Agent Watcher..."
|
288
|
+
watcher_pid = spawn("ruby agent_watcher.rb",
|
289
|
+
out: @options[:verbose] ? $stdout : "/dev/null")
|
290
|
+
@pids << watcher_pid
|
291
|
+
|
292
|
+
sleep 3
|
293
|
+
|
294
|
+
puts " 📂 Copying example_agent.rb to agents/ directory..."
|
295
|
+
FileUtils.cp('example_agent.rb', 'agents/example_agent_demo.rb')
|
296
|
+
|
297
|
+
sleep 5
|
298
|
+
|
299
|
+
puts " 📂 Adding second agent..."
|
300
|
+
# Create a simple second agent
|
301
|
+
File.write('agents/demo_agent2.rb', generate_demo_agent_code('DemoAgent2', ['demo', 'test2']))
|
302
|
+
|
303
|
+
sleep 5
|
304
|
+
|
305
|
+
puts " 📂 Adding third agent..."
|
306
|
+
File.write('agents/demo_agent3.rb', generate_demo_agent_code('DemoAgent3', ['demo', 'test3']))
|
307
|
+
|
308
|
+
puts " ⏱️ Letting agents run for 15 seconds..."
|
309
|
+
sleep 15
|
310
|
+
|
311
|
+
# Cleanup created files
|
312
|
+
FileUtils.rm_rf('agents/example_agent_demo.rb') rescue nil
|
313
|
+
FileUtils.rm_rf('agents/demo_agent2.rb') rescue nil
|
314
|
+
FileUtils.rm_rf('agents/demo_agent3.rb') rescue nil
|
315
|
+
end
|
316
|
+
|
317
|
+
puts "✅ Agent Watcher demo completed"
|
318
|
+
end
|
319
|
+
|
320
|
+
def run_all_scenarios
|
321
|
+
puts "🎬 Running All Scenarios"
|
322
|
+
puts
|
323
|
+
|
324
|
+
%w[basic control security].each do |scenario_name|
|
325
|
+
puts "=" * 60
|
326
|
+
scenario = SCENARIOS[scenario_name]
|
327
|
+
|
328
|
+
if scenario[:warning]
|
329
|
+
puts "⚠️ Skipping #{scenario_name}: #{scenario[:warning]}"
|
330
|
+
puts " Run with -s #{scenario_name} to run individually"
|
331
|
+
next
|
332
|
+
end
|
333
|
+
|
334
|
+
run_scenario(scenario)
|
335
|
+
puts
|
336
|
+
|
337
|
+
# Brief pause between scenarios
|
338
|
+
sleep 3
|
339
|
+
cleanup_agents
|
340
|
+
end
|
341
|
+
|
342
|
+
puts "=" * 60
|
343
|
+
puts "✅ All scenarios completed"
|
344
|
+
end
|
345
|
+
|
346
|
+
def generate_demo_agent_code(class_name, capabilities)
|
347
|
+
<<~RUBY
|
348
|
+
require_relative '../lib/agent99'
|
349
|
+
|
350
|
+
class #{class_name} < Agent99::Base
|
351
|
+
def info
|
352
|
+
{
|
353
|
+
name: self.class.to_s,
|
354
|
+
type: :server,
|
355
|
+
capabilities: #{capabilities.inspect}
|
356
|
+
}
|
357
|
+
end
|
358
|
+
|
359
|
+
private
|
360
|
+
|
361
|
+
def receive_request
|
362
|
+
send_response({ result: "Hello from \#{self.class.name}!" })
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Auto-start when loaded
|
367
|
+
agent = #{class_name}.new
|
368
|
+
agent.run
|
369
|
+
RUBY
|
370
|
+
end
|
371
|
+
|
372
|
+
def setup_signal_handlers
|
373
|
+
%w[INT TERM QUIT].each do |signal|
|
374
|
+
Signal.trap(signal) do
|
375
|
+
puts "\n🛑 Received #{signal} signal, cleaning up..."
|
376
|
+
cleanup
|
377
|
+
exit 0
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def cleanup
|
383
|
+
puts "🧹 Cleaning up processes..."
|
384
|
+
cleanup_agents
|
385
|
+
cleanup_infrastructure
|
386
|
+
puts "✅ Cleanup complete"
|
387
|
+
end
|
388
|
+
|
389
|
+
def cleanup_agents
|
390
|
+
@pids.each do |pid|
|
391
|
+
begin
|
392
|
+
Process.kill('TERM', pid)
|
393
|
+
Process.wait(pid, Process::WNOHANG)
|
394
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
395
|
+
# Process already terminated
|
396
|
+
rescue => e
|
397
|
+
puts "⚠️ Error terminating process #{pid}: #{e.message}" if @options[:verbose]
|
398
|
+
end
|
399
|
+
end
|
400
|
+
@pids.clear
|
401
|
+
end
|
402
|
+
|
403
|
+
def cleanup_infrastructure
|
404
|
+
if @registry_pid
|
405
|
+
begin
|
406
|
+
Process.kill('TERM', @registry_pid)
|
407
|
+
Process.wait(@registry_pid, Process::WNOHANG)
|
408
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
409
|
+
rescue => e
|
410
|
+
puts "⚠️ Error terminating registry: #{e.message}" if @options[:verbose]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
if @rabbitmq_pid
|
415
|
+
begin
|
416
|
+
Process.kill('TERM', @rabbitmq_pid)
|
417
|
+
Process.wait(@rabbitmq_pid, Process::WNOHANG)
|
418
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
419
|
+
rescue => e
|
420
|
+
puts "⚠️ Error terminating RabbitMQ: #{e.message}" if @options[:verbose]
|
421
|
+
end
|
422
|
+
|
423
|
+
# Also try rabbitmqctl stop as backup
|
424
|
+
system("rabbitmqctl stop > /dev/null 2>&1") rescue nil
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Main execution
|
430
|
+
if __FILE__ == $PROGRAM_NAME
|
431
|
+
demo = Agent99Demo.new
|
432
|
+
demo.run
|
433
|
+
end
|
@@ -11,16 +11,19 @@ module Agent99::AgentLifecycle
|
|
11
11
|
def initialize(registry_client: Agent99::RegistryClient.new,
|
12
12
|
message_client: Agent99::AmqpMessageClient.new,
|
13
13
|
logger: Logger.new($stdout))
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
14
|
+
@agents = {}
|
15
|
+
@payload = nil
|
16
|
+
@name = self.class.name
|
17
|
+
@capabilities = capabilities
|
18
|
+
@id = nil
|
19
|
+
@registry_client = registry_client
|
20
|
+
@message_client = message_client
|
21
|
+
@logger = logger
|
22
|
+
|
23
|
+
validate_info_keys
|
21
24
|
|
22
25
|
@registry_client.logger = logger
|
23
|
-
register
|
26
|
+
register(info)
|
24
27
|
|
25
28
|
@queue = message_client.setup(agent_id: id, logger:)
|
26
29
|
|
@@ -29,12 +32,33 @@ module Agent99::AgentLifecycle
|
|
29
32
|
setup_signal_handlers
|
30
33
|
end
|
31
34
|
|
35
|
+
|
36
|
+
def validate_info_keys
|
37
|
+
required_keys = [:name, :capabilities]
|
38
|
+
if respond_to? :info
|
39
|
+
missing_keys = required_keys - info.keys
|
40
|
+
unless missing_keys.empty?
|
41
|
+
logger.error <<~MESSAGE
|
42
|
+
This agent's info method is missing
|
43
|
+
#{1 == missing_keys.size ? 'a required key' : 'some required keys'}:
|
44
|
+
#{missing_keys}
|
45
|
+
MESSAGE
|
46
|
+
.split("\n").join
|
47
|
+
exit(1)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
logger.error "An agent must implement the info method"
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
32
56
|
# Registers the agent with the registry service.
|
33
57
|
#
|
34
58
|
# @raise [StandardError] If registration fails
|
35
59
|
#
|
36
|
-
def register
|
37
|
-
@id = registry_client.register(
|
60
|
+
def register(agent_info)
|
61
|
+
@id = registry_client.register(info: agent_info)
|
38
62
|
logger.info "Registered Agent #{name} with ID: #{id}"
|
39
63
|
rescue StandardError => e
|
40
64
|
handle_error("Error during registration", e)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'bunny'
|
4
4
|
require 'json'
|
5
|
-
require '
|
5
|
+
require 'simple_json_schema_builder'
|
6
6
|
require 'logger'
|
7
7
|
|
8
8
|
class Agent99::AmqpMessageClient
|
@@ -34,10 +34,10 @@ class Agent99::AmqpMessageClient
|
|
34
34
|
config: CONFIG,
|
35
35
|
logger: Logger.new($stdout))
|
36
36
|
@config = config
|
37
|
+
@logger = logger
|
37
38
|
@connection = create_amqp_connection
|
38
39
|
@channel = @connection.create_channel
|
39
40
|
@exchange = @channel.default_exchange
|
40
|
-
@logger = logger
|
41
41
|
end
|
42
42
|
|
43
43
|
def setup(agent_id:, logger:)
|
data/lib/agent99/base.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'logger'
|
4
4
|
require 'json'
|
5
|
-
require '
|
5
|
+
require 'simple_json_schema_builder'
|
6
6
|
|
7
7
|
require_relative 'timestamp'
|
8
8
|
require_relative 'registry_client'
|
@@ -37,7 +37,11 @@ class Agent99::Base
|
|
37
37
|
|
38
38
|
MESSAGE_TYPES = %w[request response control]
|
39
39
|
|
40
|
-
attr_reader :id, :capabilities, :name
|
40
|
+
attr_reader :id, :capabilities, :name
|
41
|
+
attr_reader :payload, :header, :queue
|
42
|
+
attr_reader :logger
|
43
|
+
attr_reader :agents
|
44
|
+
|
41
45
|
attr_accessor :registry_client, :message_client
|
42
46
|
|
43
47
|
|
@@ -128,17 +128,13 @@ module Agent99::MessageProcessing
|
|
128
128
|
# @return [Array] An array of validation errors, empty if validation succeeds
|
129
129
|
#
|
130
130
|
def validate_schema
|
131
|
-
schema
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
validator.validate(@payload)
|
136
|
-
[]
|
137
|
-
|
138
|
-
rescue JsonSchema::ValidationError => e
|
131
|
+
# TODO: Implement proper JSON schema validation
|
132
|
+
# For now, skip validation to avoid JsonSchema dependency issues
|
133
|
+
return []
|
134
|
+
rescue => e
|
139
135
|
handle_error("Validation error", e)
|
140
|
-
send_response(type: 'error', errors: e.
|
141
|
-
e.
|
136
|
+
send_response(type: 'error', errors: [e.message])
|
137
|
+
[e.message]
|
142
138
|
end
|
143
139
|
|
144
140
|
|
@@ -8,17 +8,19 @@ class Agent99::RegistryClient
|
|
8
8
|
attr_accessor :logger
|
9
9
|
|
10
10
|
def initialize(
|
11
|
-
base_url: ENV.fetch('
|
11
|
+
base_url: ENV.fetch('AGENT99_REGISTRY_URL', 'http://localhost:4567'),
|
12
12
|
logger: Logger.new($stdout)
|
13
13
|
)
|
14
|
-
@base_url
|
15
|
-
@logger
|
16
|
-
|
14
|
+
@base_url = base_url
|
15
|
+
@logger = logger
|
16
|
+
uri = URI.parse(base_url)
|
17
|
+
@http_client = Net::HTTP.new(uri.host, uri.port)
|
17
18
|
end
|
18
19
|
|
19
|
-
def register(
|
20
|
-
|
21
|
-
|
20
|
+
def register(info:)
|
21
|
+
payload = info
|
22
|
+
request = create_request(:post, "/register", payload)
|
23
|
+
@id = send_request(request)
|
22
24
|
end
|
23
25
|
|
24
26
|
def withdraw(id)
|
@@ -37,16 +39,16 @@ class Agent99::RegistryClient
|
|
37
39
|
|
38
40
|
|
39
41
|
def fetch_all_agents
|
40
|
-
request
|
41
|
-
response
|
42
|
+
request = create_request(:get, "/")
|
43
|
+
response = send_request(request)
|
42
44
|
end
|
43
45
|
|
44
46
|
################################################
|
45
47
|
private
|
46
48
|
|
47
49
|
def create_request(method, path, body = nil)
|
48
|
-
request
|
49
|
-
request.body
|
50
|
+
request = Object.const_get("Net::HTTP::#{method.capitalize}").new(path, { "Content-Type" => "application/json" })
|
51
|
+
request.body = body.to_json if body
|
50
52
|
request
|
51
53
|
end
|
52
54
|
|
@@ -57,10 +59,12 @@ class Agent99::RegistryClient
|
|
57
59
|
|
58
60
|
rescue JSON::ParserError => e
|
59
61
|
logger.error "JSON parsing error: #{e.message}"
|
62
|
+
logger.debug "Response body that failed parsing: #{response&.body}"
|
60
63
|
nil
|
61
64
|
|
62
65
|
rescue StandardError => e
|
63
66
|
logger.error "Request error: #{e.message}"
|
67
|
+
logger.debug "Error details: #{e.class} - #{e.backtrace&.first(5)}"
|
64
68
|
nil
|
65
69
|
end
|
66
70
|
|