agent99 0.0.1 → 0.0.3

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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -1
  3. data/README.md +46 -2
  4. data/docs/README.md +57 -0
  5. data/docs/advanced_features.md +110 -0
  6. data/docs/agent_discovery.md +62 -0
  7. data/docs/agent_lifecycle.md +137 -0
  8. data/docs/agent_registry_processes.md +102 -0
  9. data/docs/api_reference.md +136 -0
  10. data/docs/architecture.md +77 -0
  11. data/docs/configuration.md +17 -0
  12. data/docs/control_actions.md +179 -0
  13. data/docs/custom_agent_implementation.md +30 -0
  14. data/docs/diagrams/agent_registry_processes.dot +42 -0
  15. data/docs/diagrams/agent_registry_processes.png +0 -0
  16. data/docs/diagrams/high_level_architecture.dot +26 -0
  17. data/docs/diagrams/high_level_architecture.png +0 -0
  18. data/docs/diagrams/request_flow.dot +42 -0
  19. data/docs/diagrams/request_flow.png +0 -0
  20. data/docs/error_handling_and_logging.md +13 -0
  21. data/docs/extending_the_framework.md +11 -0
  22. data/docs/message_processing.md +165 -0
  23. data/docs/messaging_system.md +129 -0
  24. data/docs/preformance_considerations.md +9 -0
  25. data/docs/schema_definition.md +78 -0
  26. data/docs/security.md +9 -0
  27. data/docs/troubleshooting.md +11 -0
  28. data/examples/README.md +65 -35
  29. data/examples/agent_watcher.rb +102 -0
  30. data/examples/agents/.keep +0 -0
  31. data/examples/chief_agent.rb +96 -0
  32. data/examples/control.rb +136 -0
  33. data/examples/diagram.dot +22 -0
  34. data/examples/diagram.png +0 -0
  35. data/examples/example_agent.rb +26 -0
  36. data/examples/kaos_spy.rb +63 -0
  37. data/examples/{hello_world.rb → maxwell_agent86.rb} +38 -18
  38. data/examples/{hello_world_request.rb → maxwell_request.rb} +2 -2
  39. data/examples/registry.rb +8 -7
  40. data/examples/start_rabbitmq_and_registry.sh +29 -0
  41. data/lib/agent99/agent_lifecycle.rb +14 -15
  42. data/lib/agent99/amqp_message_client.rb +41 -10
  43. data/lib/agent99/base.rb +6 -8
  44. data/lib/agent99/control_actions.rb +80 -47
  45. data/lib/agent99/header_management.rb +6 -4
  46. data/lib/agent99/header_schema.rb +5 -0
  47. data/lib/agent99/message_processing.rb +21 -13
  48. data/lib/agent99/registry_client.rb +8 -0
  49. data/lib/agent99/version.rb +1 -1
  50. data/lib/agent99.rb +4 -0
  51. metadata +41 -11
  52. data/docs/todo.md +0 -66
  53. data/examples/hello_world_client.rb +0 -70
  54. 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
+
@@ -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/hello_world.rb
2
+ # examples/maxwell_agent86.rb
3
3
 
4
- require 'json'
5
- require 'json_schema'
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 'hello_world_request'
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
- result: get(:greeting) + ' ' + get(:name)
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
- # NOTE: what I'm thinking about here is similar to the
82
- # prompt tool (aka function) callback facility
83
- # where descriptive text is used to describe
84
- # what the tool does.
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
- # TODO: scale this idea back to just keywords
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 = HelloWorld.new
116
+ agent = MaxwellAgent86.new
97
117
  agent.run # Starts listening for messages
@@ -1,8 +1,8 @@
1
- # examples/hello_world_request.rb
1
+ # examples/maxwell_request.rb
2
2
 
3
3
  require_relative '../lib/agent99/header_schema'
4
4
 
5
- class HelloWorldRequest < SimpleJsonSchemaBuilder::Base
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 capabilities
13
- # Array(Hash)
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(logger: Logger.new($stdout))
33
+ def initialize(
34
+ config: CONFIG,
35
+ logger: Logger.new($stdout))
36
+ @config = config
21
37
  @connection = create_amqp_connection
22
- @channel = @connection.create_channel
23
- @exchange = @channel.default_exchange
24
- @logger = 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 "Message published successfully to queue: #{queue_name}"
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
- { success: true, message: "Message published successfully" }
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
- { success: false, error: "JSON conversion error: #{e.message}" }
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
- { success: false, error: "Publishing error: #{e.message}" }
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
- { success: false, error: "Unexpected error: #{e.message}" }
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."