agent99 0.0.1 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -1
- data/README.md +46 -2
- data/docs/README.md +57 -0
- data/docs/advanced_features.md +110 -0
- data/docs/agent_discovery.md +62 -0
- data/docs/agent_lifecycle.md +137 -0
- data/docs/agent_registry_processes.md +102 -0
- data/docs/api_reference.md +136 -0
- data/docs/architecture.md +77 -0
- data/docs/configuration.md +17 -0
- data/docs/control_actions.md +179 -0
- data/docs/custom_agent_implementation.md +30 -0
- data/docs/diagrams/agent_registry_processes.dot +42 -0
- data/docs/diagrams/agent_registry_processes.png +0 -0
- data/docs/diagrams/high_level_architecture.dot +26 -0
- data/docs/diagrams/high_level_architecture.png +0 -0
- data/docs/diagrams/request_flow.dot +42 -0
- data/docs/diagrams/request_flow.png +0 -0
- data/docs/error_handling_and_logging.md +13 -0
- data/docs/extending_the_framework.md +11 -0
- data/docs/message_processing.md +165 -0
- data/docs/messaging_system.md +129 -0
- data/docs/preformance_considerations.md +9 -0
- data/docs/schema_definition.md +78 -0
- data/docs/security.md +9 -0
- data/docs/troubleshooting.md +11 -0
- data/examples/README.md +65 -35
- data/examples/agent_watcher.rb +102 -0
- data/examples/agents/.keep +0 -0
- data/examples/chief_agent.rb +96 -0
- data/examples/control.rb +136 -0
- data/examples/diagram.dot +22 -0
- data/examples/diagram.png +0 -0
- data/examples/example_agent.rb +26 -0
- data/examples/kaos_spy.rb +63 -0
- data/examples/{hello_world.rb → maxwell_agent86.rb} +38 -18
- data/examples/{hello_world_request.rb → maxwell_request.rb} +2 -2
- data/examples/registry.rb +8 -7
- data/examples/start_rabbitmq_and_registry.sh +29 -0
- data/lib/agent99/agent_lifecycle.rb +14 -15
- data/lib/agent99/amqp_message_client.rb +41 -10
- data/lib/agent99/base.rb +6 -8
- data/lib/agent99/control_actions.rb +80 -47
- data/lib/agent99/header_management.rb +6 -4
- data/lib/agent99/header_schema.rb +5 -0
- data/lib/agent99/message_processing.rb +21 -13
- data/lib/agent99/registry_client.rb +8 -0
- data/lib/agent99/version.rb +1 -1
- data/lib/agent99.rb +4 -0
- metadata +41 -11
- data/docs/todo.md +0 -66
- data/examples/hello_world_client.rb +0 -70
- data/examples/start_agents.sh +0 -20
@@ -0,0 +1,96 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/chief_agent.rb
|
3
|
+
#
|
4
|
+
# This program is a type :client agent.
|
5
|
+
#
|
6
|
+
# This program calls a CLI program named boxes to highlight
|
7
|
+
# the response received to the request that it sent.
|
8
|
+
#
|
9
|
+
# brew install boxes
|
10
|
+
#
|
11
|
+
# Run this program several times to see if Maxwell Agent86
|
12
|
+
# messes up his mission which he is prone to do half the time.
|
13
|
+
|
14
|
+
require_relative '../lib/agent99'
|
15
|
+
|
16
|
+
class ChiefAgent < Agent99::Base
|
17
|
+
TYPE = :client
|
18
|
+
|
19
|
+
# init is called at the end of the initialization process.
|
20
|
+
# It may be only something that a :client type agent would do.
|
21
|
+
#
|
22
|
+
# For this client it sends out a request as its first order of
|
23
|
+
# business and expects to receive a response.
|
24
|
+
#
|
25
|
+
def init
|
26
|
+
action = 'greeter'
|
27
|
+
agent = discover_agent(
|
28
|
+
capability: action,
|
29
|
+
how_many: 1
|
30
|
+
).first[:uuid]
|
31
|
+
|
32
|
+
send_request(agent:)
|
33
|
+
|
34
|
+
rescue Exception => e
|
35
|
+
logger.warn "No Agents are available as #{action}"
|
36
|
+
exit(1)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
##################################################
|
41
|
+
private
|
42
|
+
|
43
|
+
def send_request(agent:)
|
44
|
+
request = build_request(
|
45
|
+
to_uuid: agent,
|
46
|
+
greeting: 'Hey',
|
47
|
+
name: 'MadBomber'
|
48
|
+
)
|
49
|
+
|
50
|
+
result = @message_client.publish(request)
|
51
|
+
logger.info "Sent request: #{request.inspect}; status? #{result.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def build_request(
|
56
|
+
to_uuid:,
|
57
|
+
greeting: 'Hello',
|
58
|
+
name: 'World'
|
59
|
+
)
|
60
|
+
|
61
|
+
{
|
62
|
+
header: {
|
63
|
+
type: 'request',
|
64
|
+
from_uuid: @id,
|
65
|
+
to_uuid: ,
|
66
|
+
event_uuid: SecureRandom.uuid,
|
67
|
+
timestamp: Agent99::Timestamp.new.to_i
|
68
|
+
},
|
69
|
+
greeting:,
|
70
|
+
name:
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def receive_response
|
76
|
+
logger.info "Received response: #{payload.inspect}"
|
77
|
+
result = payload[:result]
|
78
|
+
|
79
|
+
puts
|
80
|
+
puts `echo "#{result}" | boxes -d info`
|
81
|
+
puts
|
82
|
+
|
83
|
+
exit(0)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def capabilities
|
88
|
+
['Chief of Control']
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
# Example usage
|
94
|
+
client = ChiefAgent.new
|
95
|
+
client.run
|
96
|
+
|
data/examples/control.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# control.rb
|
3
|
+
|
4
|
+
require_relative '../lib/agent99'
|
5
|
+
|
6
|
+
class Control < Agent99::Base
|
7
|
+
TYPE = :hybrid
|
8
|
+
|
9
|
+
attr_accessor :statuses
|
10
|
+
|
11
|
+
def init
|
12
|
+
@agents = @registry_client.fetch_all_agents
|
13
|
+
@statuses = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def capabilities
|
18
|
+
['control', 'headquarters', 'secret underground base']
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def send_control_message(message:, payload: {})
|
23
|
+
@agents.each do |agent|
|
24
|
+
response = @message_client.publish(
|
25
|
+
header: {
|
26
|
+
to_uuid: agent[:uuid],
|
27
|
+
from_uuid: @id,
|
28
|
+
event_uuid: SecureRandom.uuid,
|
29
|
+
type: 'control',
|
30
|
+
timestamp: Agent99::Timestamp.new.to_i
|
31
|
+
},
|
32
|
+
action: message,
|
33
|
+
payload: payload
|
34
|
+
)
|
35
|
+
puts "Sent #{message} to #{agent[:name]}: #{response[:success] ? 'Success' : 'Failed'}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def pause_all
|
41
|
+
send_control_message(message: 'pause')
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def resume_all
|
46
|
+
send_control_message(message: 'resume')
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def stop_all
|
51
|
+
send_control_message(message: 'stop')
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_all_status
|
55
|
+
@statuses.clear # Reset statuses before new request
|
56
|
+
|
57
|
+
@agents.each do |agent|
|
58
|
+
@message_client.publish(
|
59
|
+
header: {
|
60
|
+
to_uuid: agent[:uuid],
|
61
|
+
from_uuid: @id,
|
62
|
+
event_uuid: SecureRandom.uuid,
|
63
|
+
type: 'control',
|
64
|
+
timestamp: Agent99::Timestamp.new.to_i
|
65
|
+
},
|
66
|
+
action: 'status'
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Wait for responses (with timeout)
|
71
|
+
sleep 2 # Give agents time to respond
|
72
|
+
@statuses
|
73
|
+
end
|
74
|
+
|
75
|
+
def receive_response
|
76
|
+
if payload[:action] == 'response' && payload[:data][:type] == 'status'
|
77
|
+
agent_name = payload[:header][:from_uuid]
|
78
|
+
@statuses[agent_name] = payload[:data]
|
79
|
+
logger.info "Received status from #{agent_name}: #{payload[:data]}"
|
80
|
+
elsif payload[:action] == 'status' && payload[:header][:from_uuid] == @id
|
81
|
+
# Handle our own status request
|
82
|
+
handle_status_request
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
if __FILE__ == $PROGRAM_NAME
|
91
|
+
control = Control.new
|
92
|
+
|
93
|
+
# Start the message processing in a separate thread
|
94
|
+
dispatcher_thread = Thread.new do
|
95
|
+
control.run
|
96
|
+
end
|
97
|
+
|
98
|
+
# UI thread
|
99
|
+
begin
|
100
|
+
loop do
|
101
|
+
puts "\n1. Pause all agents"
|
102
|
+
puts "2. Resume all agents"
|
103
|
+
puts "3. Stop all agents"
|
104
|
+
puts "4. Get all agents status"
|
105
|
+
puts "5. Exit"
|
106
|
+
print "\nEnter your choice: "
|
107
|
+
|
108
|
+
choice = gets.chomp.to_i
|
109
|
+
|
110
|
+
case choice
|
111
|
+
when 1
|
112
|
+
control.pause_all
|
113
|
+
when 2
|
114
|
+
control.resume_all
|
115
|
+
when 3
|
116
|
+
control.stop_all
|
117
|
+
when 4
|
118
|
+
statuses = control.get_all_status
|
119
|
+
sleep 2 # Give time for responses to arrive
|
120
|
+
puts JSON.pretty_generate(control.statuses)
|
121
|
+
when 5
|
122
|
+
puts "Exiting..."
|
123
|
+
Thread.exit
|
124
|
+
break
|
125
|
+
else
|
126
|
+
puts "Invalid choice. Please try again."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
rescue Interrupt
|
130
|
+
puts "\nShutting down..."
|
131
|
+
ensure
|
132
|
+
dispatcher_thread.exit
|
133
|
+
control.fini
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
digraph agent_interaction {
|
2
|
+
rankdir=LR; // Left to right layout
|
3
|
+
|
4
|
+
// Define nodes for each component
|
5
|
+
node [shape=box];
|
6
|
+
ChiefAgent [label="ChiefAgent"];
|
7
|
+
MaxwellAgent86 [label="MaxwellAgent86"];
|
8
|
+
Registry [label="Registry Service"];
|
9
|
+
|
10
|
+
// Define edges for interactions
|
11
|
+
ChiefAgent -> Registry [label="GET /discover (greeter)"];
|
12
|
+
Registry -> ChiefAgent [label="Agent UUIDs"];
|
13
|
+
|
14
|
+
ChiefAgent -> MaxwellAgent86 [label="Send greet request"];
|
15
|
+
MaxwellAgent86 -> ChiefAgent [label="Response (Hello World)"];
|
16
|
+
|
17
|
+
MaxwellAgent86 -> Registry [label="POST /register"];
|
18
|
+
Registry -> MaxwellAgent86 [label="ACK (registration)"];
|
19
|
+
|
20
|
+
ChiefAgent -> Registry [label="POST /register"];
|
21
|
+
Registry -> ChiefAgent [label="ACK (registration)"];
|
22
|
+
}
|
Binary file
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# examples/example_agent.rb
|
2
|
+
#
|
3
|
+
# NOTE: This agent is meant to be loaded
|
4
|
+
# by the agent_watcher.rb be file.
|
5
|
+
# To do that first have AgentWatcher running
|
6
|
+
# then `cp example_agent.rb agents`
|
7
|
+
# AgentWatcher will see the new file arrive
|
8
|
+
# in the `agents` folder, will determine that the
|
9
|
+
# new file contains an Agent99 subclass, will
|
10
|
+
# load it, create a new instance of the class and
|
11
|
+
# finally run the new instance within its own
|
12
|
+
# thread as part of the AgentWatcher process.
|
13
|
+
#
|
14
|
+
|
15
|
+
require_relative '../../lib/agent99'
|
16
|
+
|
17
|
+
class ExampleAgent < Agent99::Base
|
18
|
+
TYPE = :server
|
19
|
+
|
20
|
+
def capabilities = %w[ rubber_stamp yes_man example ]
|
21
|
+
|
22
|
+
def receive_request
|
23
|
+
logger.info "Example agent received request: #{payload}"
|
24
|
+
send_response(status: 'success')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# examples/kaos_spy.rb
|
3
|
+
#
|
4
|
+
# KAOS stood for "Kreatively Akting Out Simultaneously."
|
5
|
+
|
6
|
+
require 'agent99'
|
7
|
+
|
8
|
+
# Kaos captured Agent99 and forced her to reveal the
|
9
|
+
# secrets of the centralized registry and the communication
|
10
|
+
# network used by Control. Max was not there to save her.
|
11
|
+
|
12
|
+
require 'tty-table'
|
13
|
+
|
14
|
+
class KaosSpy
|
15
|
+
# TODO: spread some choas!
|
16
|
+
|
17
|
+
attr_reader :registry, :comms, :agents
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@registry = Agent99::RegistryClient.new
|
21
|
+
@agents = registry.fetch_all_agents
|
22
|
+
dox_control_agents
|
23
|
+
|
24
|
+
@comms = Agent99::AmqpMessageClient.new
|
25
|
+
take_out_communications
|
26
|
+
end
|
27
|
+
|
28
|
+
def dox_control_agents
|
29
|
+
if agents.empty?
|
30
|
+
puts "\nKAOS won! There are no Control agents in the field."
|
31
|
+
else
|
32
|
+
report = [ %w[Name Address Capabilities] ]
|
33
|
+
|
34
|
+
agents.each{|agent|
|
35
|
+
report << [
|
36
|
+
agent[:name],
|
37
|
+
agent[:uuid],
|
38
|
+
agent[:capabilities].join(', ')
|
39
|
+
]
|
40
|
+
}
|
41
|
+
|
42
|
+
table = TTY::Table.new(report[0], report[1..])
|
43
|
+
puts table.render(:unicode)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def take_out_communications
|
48
|
+
puts
|
49
|
+
puts "Destroy Control's Comms Network ..."
|
50
|
+
puts
|
51
|
+
|
52
|
+
agents.each do |agent|
|
53
|
+
comms.delete_queue(agent[:uuid])
|
54
|
+
puts " Agent #{agent[:name]} cannot make or receive calls@"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
KaosSpy.new
|
60
|
+
puts
|
61
|
+
puts "That's all it takes - Get Smart; get security!"
|
62
|
+
puts
|
63
|
+
|
@@ -1,22 +1,31 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
# examples/
|
2
|
+
# examples/maxwell_agent86.rb
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
# There are three types of agents: Server, Client and Hybrid.
|
5
|
+
# A Server receives a requests and _may_ send a response.
|
6
|
+
# A Client sends a request and _may_ expect a response.
|
7
|
+
# A Hybrid _may_ act like a Server or a Client
|
6
8
|
|
7
9
|
require_relative '../lib/agent99'
|
8
|
-
require_relative '
|
10
|
+
require_relative 'maxwell_request'
|
11
|
+
|
12
|
+
class MaxwellAgent86 < Agent99::Base
|
13
|
+
REQUEST_SCHEMA = MaxwellRequest.schema
|
14
|
+
TYPE = :server
|
9
15
|
|
10
|
-
class HelloWorld < Agent99::Base
|
11
|
-
REQUEST_SCHEMA = HelloWorldRequest.schema
|
12
16
|
# RESPONSE_SCHEMA = Agent99::RESPONSE.schema
|
13
17
|
# ERROR_SCHEMA = Agent99::ERROR.schema
|
14
18
|
|
19
|
+
|
20
|
+
#######################################
|
21
|
+
private
|
22
|
+
|
15
23
|
# The request is in @payload
|
16
24
|
def receive_request
|
17
25
|
send_response( validate_request || process )
|
18
26
|
end
|
19
27
|
|
28
|
+
|
20
29
|
# This method validates the incoming request and returns any errors found
|
21
30
|
# or nil if there are no errors.
|
22
31
|
# It allows for returning an array of errors.
|
@@ -44,6 +53,7 @@ class HelloWorld < Agent99::Base
|
|
44
53
|
}
|
45
54
|
end
|
46
55
|
|
56
|
+
|
47
57
|
# Validate the incoming request body against the schema
|
48
58
|
validation_errors = validate_schema
|
49
59
|
unless validation_errors.empty?
|
@@ -57,6 +67,7 @@ class HelloWorld < Agent99::Base
|
|
57
67
|
responses.empty? ? nil : responses
|
58
68
|
end
|
59
69
|
|
70
|
+
|
60
71
|
# Returns the response value
|
61
72
|
# All response message have the same schema in that
|
62
73
|
# they have a header (all messages have headers) and
|
@@ -64,9 +75,13 @@ class HelloWorld < Agent99::Base
|
|
64
75
|
# a JSON string, sure but then we would need a
|
65
76
|
# RESPONSE_SCHEMA constant for the class.
|
66
77
|
def process
|
67
|
-
|
68
|
-
|
69
|
-
|
78
|
+
result = if 50 <= rand(100)
|
79
|
+
get(:greeting) + ' ' + get(:name)
|
80
|
+
else
|
81
|
+
'Missed it by that >< much.'
|
82
|
+
end
|
83
|
+
|
84
|
+
{ result: result }
|
70
85
|
end
|
71
86
|
|
72
87
|
|
@@ -76,16 +91,21 @@ class HelloWorld < Agent99::Base
|
|
76
91
|
loger.warn("Unexpected response type message: response.inspect")
|
77
92
|
end
|
78
93
|
|
79
|
-
private
|
80
94
|
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
95
|
+
# Phase One Implementation is to do a search
|
96
|
+
# using the String#include? and the Array#include?
|
97
|
+
# methods. If you want discrete word-based selection
|
98
|
+
# then use an Array of Strings to define the different
|
99
|
+
# things this agent can do.
|
100
|
+
#
|
101
|
+
# If you want to match on sub-strings then define the
|
102
|
+
# the capabilities as a String.
|
103
|
+
#
|
104
|
+
# Subsequent implementations may use a semantic search
|
105
|
+
# to find the agents to use in which case capabilities may
|
106
|
+
# be constrained to be a String.
|
85
107
|
#
|
86
|
-
#
|
87
|
-
# until the registry program gets more
|
88
|
-
# stuff added to its discovery process.
|
108
|
+
# For now, lets just go with the Array of Strings.
|
89
109
|
#
|
90
110
|
def capabilities
|
91
111
|
%w[ greeter hello_world hello-world hello]
|
@@ -93,5 +113,5 @@ class HelloWorld < Agent99::Base
|
|
93
113
|
end
|
94
114
|
|
95
115
|
# Example usage
|
96
|
-
agent =
|
116
|
+
agent = MaxwellAgent86.new
|
97
117
|
agent.run # Starts listening for messages
|
@@ -1,8 +1,8 @@
|
|
1
|
-
# examples/
|
1
|
+
# examples/maxwell_request.rb
|
2
2
|
|
3
3
|
require_relative '../lib/agent99/header_schema'
|
4
4
|
|
5
|
-
class
|
5
|
+
class MaxwellRequest < SimpleJsonSchemaBuilder::Base
|
6
6
|
object do
|
7
7
|
object :header, schema: Agent99::HeaderSchema
|
8
8
|
|
data/examples/registry.rb
CHANGED
@@ -6,11 +6,13 @@ include DebugMe
|
|
6
6
|
|
7
7
|
require 'sinatra'
|
8
8
|
require 'json'
|
9
|
-
require 'bunny'
|
10
9
|
require 'securerandom'
|
11
10
|
|
12
|
-
# In-memory registry to store agent
|
13
|
-
#
|
11
|
+
# In-memory registry to store agent Array(Hash)
|
12
|
+
#
|
13
|
+
# Agent capabilities are save as lower case. The
|
14
|
+
# discovery process also compares content as lower case.
|
15
|
+
#
|
14
16
|
# TODO: change this data store to a sqlite database
|
15
17
|
# maybe with a vector search capability.
|
16
18
|
#
|
@@ -26,12 +28,11 @@ end
|
|
26
28
|
post '/register' do
|
27
29
|
request.body.rewind
|
28
30
|
agent_info = JSON.parse(request.body.read, symbolize_names: true)
|
29
|
-
|
30
31
|
agent_name = agent_info[:name]
|
31
|
-
capabilities = agent_info[:capabilities]
|
32
|
-
|
33
32
|
agent_uuid = SecureRandom.uuid
|
34
33
|
|
34
|
+
agent_info[:capabilities].map!{|c| c.downcase}
|
35
|
+
|
35
36
|
AGENT_REGISTRY << agent_info.merge({uuid: agent_uuid})
|
36
37
|
|
37
38
|
status 201
|
@@ -43,7 +44,7 @@ end
|
|
43
44
|
# TODO: This is a simple keyword matcher. Looking
|
44
45
|
# => for a semantic match process.
|
45
46
|
get '/discover' do
|
46
|
-
capability = params['capability']
|
47
|
+
capability = params['capability'].downcase
|
47
48
|
|
48
49
|
matching_agents = AGENT_REGISTRY.select do |agent|
|
49
50
|
agent[:capabilities].include?(capability)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
# examples/start_rabbitmq_and_registry.sh
|
3
|
+
#
|
4
|
+
# brew install rabbitmq-server
|
5
|
+
#
|
6
|
+
# Start up the AMQP message broker (RabbitM@) in the background
|
7
|
+
|
8
|
+
echo "Starting rabbitmq-server in background ..."
|
9
|
+
rabbitmq-server &
|
10
|
+
sleep 2
|
11
|
+
open http://localhost:15672/#/queues
|
12
|
+
|
13
|
+
echo
|
14
|
+
echo
|
15
|
+
echo "Starting example registry in forground ..."
|
16
|
+
echo "http://localhost:4567"
|
17
|
+
echo
|
18
|
+
ruby registry.rb # blocks until control-C
|
19
|
+
|
20
|
+
# You may have to do control-c twice to stop both
|
21
|
+
|
22
|
+
echo "#"
|
23
|
+
echo "##"
|
24
|
+
echo "###"
|
25
|
+
echo "####"
|
26
|
+
echo "#####"
|
27
|
+
echo "registry is stopped"
|
28
|
+
echo "stopping the rabbitmq server ...."
|
29
|
+
rabbitmqctl stop
|
@@ -48,9 +48,21 @@ module Agent99::AgentLifecycle
|
|
48
48
|
end
|
49
49
|
|
50
50
|
|
51
|
+
# Performs cleanup operations when the agent is shutting down.
|
52
|
+
#
|
53
|
+
def fini
|
54
|
+
if id
|
55
|
+
queue_name = id
|
56
|
+
withdraw
|
57
|
+
@message_client&.delete_queue(queue_name)
|
58
|
+
else
|
59
|
+
logger.warn('fini called with a nil id')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
51
63
|
################################################
|
52
64
|
private
|
53
|
-
|
65
|
+
|
54
66
|
# Checks if the agent is currently paused.
|
55
67
|
#
|
56
68
|
# @return [Boolean] True if the agent is paused, false otherwise
|
@@ -71,17 +83,4 @@ module Agent99::AgentLifecycle
|
|
71
83
|
end
|
72
84
|
end
|
73
85
|
end
|
74
|
-
|
75
|
-
|
76
|
-
# Performs cleanup operations when the agent is shutting down.
|
77
|
-
#
|
78
|
-
def fini
|
79
|
-
if id
|
80
|
-
queue_name = id
|
81
|
-
withdraw
|
82
|
-
@message_client&.delete_queue(queue_name)
|
83
|
-
else
|
84
|
-
logger.warn('fini called with a nil id')
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
86
|
+
end
|
@@ -6,6 +6,19 @@ require 'json_schema'
|
|
6
6
|
require 'logger'
|
7
7
|
|
8
8
|
class Agent99::AmqpMessageClient
|
9
|
+
CONFIG = {
|
10
|
+
host: "127.0.0.1",
|
11
|
+
port: 5672,
|
12
|
+
ssl: false,
|
13
|
+
vhost: "/",
|
14
|
+
user: "guest",
|
15
|
+
pass: "guest",
|
16
|
+
heartbeat: :server, # will use RabbitMQ setting
|
17
|
+
frame_max: 131072,
|
18
|
+
auth_mechanism: "PLAIN"
|
19
|
+
}
|
20
|
+
|
21
|
+
|
9
22
|
QUEUE_TTL = 60_000 # 60 seconds TTL
|
10
23
|
@instance = nil
|
11
24
|
|
@@ -17,16 +30,21 @@ class Agent99::AmqpMessageClient
|
|
17
30
|
|
18
31
|
attr_accessor :logger, :channel, :exchange
|
19
32
|
|
20
|
-
def initialize(
|
33
|
+
def initialize(
|
34
|
+
config: CONFIG,
|
35
|
+
logger: Logger.new($stdout))
|
36
|
+
@config = config
|
21
37
|
@connection = create_amqp_connection
|
22
|
-
@channel
|
23
|
-
@exchange
|
24
|
-
@logger
|
38
|
+
@channel = @connection.create_channel
|
39
|
+
@exchange = @channel.default_exchange
|
40
|
+
@logger = logger
|
25
41
|
end
|
26
42
|
|
27
43
|
def setup(agent_id:, logger:)
|
28
44
|
queue = create_queue(agent_id)
|
29
45
|
|
46
|
+
logger.info "Created queue for agent_id: #{agent_id}"
|
47
|
+
|
30
48
|
# Returning the queue to be used in the Base class
|
31
49
|
queue
|
32
50
|
end
|
@@ -66,26 +84,39 @@ class Agent99::AmqpMessageClient
|
|
66
84
|
queue_name = message.dig(:header, :to_uuid)
|
67
85
|
|
68
86
|
begin
|
87
|
+
# FIXME: message.to_json
|
69
88
|
json_payload = JSON.generate(message)
|
70
89
|
|
71
90
|
exchange.publish(json_payload, routing_key: queue_name)
|
72
91
|
|
73
|
-
logger.info "
|
92
|
+
logger.info "#{message.dig(:header,:type).to_s.upcase} message published successfully to queue: #{queue_name}"
|
74
93
|
|
75
94
|
# Return a success status
|
76
|
-
{
|
95
|
+
{
|
96
|
+
success: true,
|
97
|
+
message: "Message published successfully"
|
98
|
+
}
|
77
99
|
|
78
100
|
rescue JSON::GeneratorError => e
|
79
101
|
logger.error "Failed to convert payload to JSON: #{e.message}"
|
80
|
-
{
|
102
|
+
{
|
103
|
+
success: false,
|
104
|
+
error: "JSON conversion error: #{e.message}"
|
105
|
+
}
|
81
106
|
|
82
107
|
rescue Bunny::ConnectionClosedError, Bunny::ChannelAlreadyClosed => e
|
83
108
|
logger.error "Failed to publish message: #{e.message}"
|
84
|
-
{
|
109
|
+
{
|
110
|
+
success: false,
|
111
|
+
error: "Publishing error: #{e.message}"
|
112
|
+
}
|
85
113
|
|
86
114
|
rescue StandardError => e
|
87
115
|
logger.error "Unexpected error while publishing message: #{e.message}"
|
88
|
-
{
|
116
|
+
{
|
117
|
+
success: false,
|
118
|
+
error: "Unexpected error: #{e.message}"
|
119
|
+
}
|
89
120
|
end
|
90
121
|
end
|
91
122
|
|
@@ -109,7 +140,7 @@ class Agent99::AmqpMessageClient
|
|
109
140
|
private
|
110
141
|
|
111
142
|
def create_amqp_connection
|
112
|
-
Bunny.new.tap(&:start)
|
143
|
+
Bunny.new(@config).tap(&:start)
|
113
144
|
rescue Bunny::TCPConnectionFailed, StandardError => e
|
114
145
|
logger.error "Failed to connect to AMQP: #{e.message}"
|
115
146
|
raise "AMQP Connection Error: #{e.message}. Please check your AMQP server and try again."
|