agent99 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -1
  3. data/README.md +34 -26
  4. data/docs/README.md +57 -0
  5. data/docs/advanced_features.md +115 -0
  6. data/docs/agent99_framework/central_registry.md +94 -0
  7. data/docs/agent99_framework/message_client.md +120 -0
  8. data/docs/agent99_framework/registry_client.md +119 -0
  9. data/docs/agent_discovery.md +66 -0
  10. data/docs/agent_lifecycle.md +137 -0
  11. data/docs/agent_registry_processes.md +108 -0
  12. data/docs/api_reference.md +146 -0
  13. data/docs/architecture.md +77 -0
  14. data/docs/breaking_change_v0.0.4.md +26 -0
  15. data/docs/configuration.md +17 -0
  16. data/docs/control_actions.md +179 -0
  17. data/docs/custom_agent_implementation.md +30 -0
  18. data/docs/diagrams/agent_registry_processes.dot +42 -0
  19. data/docs/diagrams/agent_registry_processes.png +0 -0
  20. data/docs/diagrams/high_level_architecture.dot +26 -0
  21. data/docs/diagrams/high_level_architecture.png +0 -0
  22. data/docs/diagrams/request_flow.dot +42 -0
  23. data/docs/diagrams/request_flow.png +0 -0
  24. data/docs/error_handling_and_logging.md +13 -0
  25. data/docs/extending_the_framework.md +11 -0
  26. data/docs/message_processing.md +165 -0
  27. data/docs/messaging_system.md +129 -0
  28. data/docs/preformance_considerations.md +9 -0
  29. data/docs/schema_definition.md +78 -0
  30. data/docs/security.md +9 -0
  31. data/docs/troubleshooting.md +11 -0
  32. data/docs/what_is_an_agent.md +293 -0
  33. data/examples/README.md +43 -0
  34. data/examples/agent_watcher.rb +106 -0
  35. data/examples/agents/.keep +0 -0
  36. data/examples/chief_agent.rb +17 -6
  37. data/examples/control.rb +16 -7
  38. data/examples/example_agent.rb +39 -0
  39. data/examples/kaos_spy.rb +63 -0
  40. data/examples/maxwell_agent86.rb +15 -26
  41. data/examples/registry.rb +24 -13
  42. data/lib/agent99/agent_discovery.rb +4 -0
  43. data/lib/agent99/agent_lifecycle.rb +34 -10
  44. data/lib/agent99/base.rb +5 -1
  45. data/lib/agent99/header_schema.rb +5 -0
  46. data/lib/agent99/message_processing.rb +3 -1
  47. data/lib/agent99/registry_client.rb +12 -11
  48. data/lib/agent99/tcp_message_client.rb +183 -0
  49. data/lib/agent99/version.rb +1 -1
  50. metadata +50 -3
  51. data/docs/todo.md +0 -66
@@ -0,0 +1,78 @@
1
+ # Agent99 Framework
2
+
3
+ ## Schema Definition
4
+
5
+ Agent99 uses `SimpleJsonSchemaBuilder` for defining message schemas. This gem was chosen for its Ruby-like DSL that makes schema definitions readable and maintainable, while still producing JSON Schema compatible output.
6
+
7
+ ### Header Schema
8
+
9
+ All messages in Agent99 must include a header that conforms to this schema:
10
+
11
+ ```ruby
12
+ class Agent99::HeaderSchema < SimpleJsonSchemaBuilder::Base
13
+ object do
14
+ string :type, required: true,
15
+ enum: %w[request response control]
16
+ string :to_uuid, required: true
17
+ string :from_uuid, required: true
18
+ string :event_uuid,required: true
19
+ integer :timestamp, required: true
20
+ end
21
+ end
22
+ ```
23
+
24
+ ### Request Schema Example
25
+
26
+ Define your agent's request schema by inheriting from SimpleJsonSchemaBuilder::Base:
27
+
28
+ ```ruby
29
+ class MyAgentRequest < SimpleJsonSchemaBuilder::Base
30
+ object do
31
+ object :header, schema: Agent99::HeaderSchema
32
+ string :greeting, required: false, examples: ["Hello"]
33
+ string :name, required: true, examples: ["World"]
34
+ end
35
+ end
36
+ ```
37
+
38
+ In this example, the agent has two parameters that it uses from the request message: greeting and name; however, greeting is not required. **The Agent99 framework will use the first item in the examples array as a default for optional parameters.**
39
+
40
+ ### Automatic Schema Validation
41
+
42
+ Agent99 automatically validates incoming request messages against your agent's REQUEST_SCHEMA:
43
+
44
+ 1. When a request arrives, the framework checks if your agent class defines REQUEST_SCHEMA
45
+ 2. If defined, the message is validated before reaching your receive_request method
46
+ 3. If validation fails:
47
+ - An error response is automatically sent back to the requester
48
+ - Your receive_request method is not called
49
+ - The validation errors are logged
50
+
51
+ Example validation error response:
52
+
53
+ ```ruby
54
+ {
55
+ header: {
56
+ type: 'error',
57
+ to_uuid: original_from_uuid,
58
+ from_uuid: agent_id,
59
+ event_uuid: original_event_uuid,
60
+ timestamp: current_timestamp
61
+ },
62
+ errors: ['Required property "name" not found in request']
63
+ }
64
+ ```
65
+
66
+ ### Why SimpleJsonSchemaBuilder?
67
+
68
+ SimpleJsonSchemaBuilder was chosen for Agent99 because it:
69
+
70
+ 1. Provides a Ruby-native DSL for schema definition
71
+ 2. Generates standard JSON Schema output
72
+ 3. Supports schema composition and reuse
73
+ 4. Includes built-in validation
74
+ 5. Has excellent performance characteristics
75
+ 6. Maintains type safety through static analysis
76
+
77
+ The gem allows us to define schemas that are both human-readable and machine-validatable, while staying within the Ruby ecosystem.
78
+
data/docs/security.md ADDED
@@ -0,0 +1,9 @@
1
+ # Agent99 Framework
2
+
3
+ ## Security
4
+
5
+ While Agent99 focuses on communication efficiency, it is recommended to implement security measures like:
6
+
7
+ - Encrypting messages in transit.
8
+ - Using secure protocols for messaging (e.g., AMQPS or NATS with TLS).
9
+ - Implementing access control mechanisms for sensitive operations.
@@ -0,0 +1,11 @@
1
+ # Agent99 Framework
2
+
3
+ ## Troubleshooting
4
+
5
+ Common issues may include:
6
+
7
+ - Failed registrations: Ensure that your registry URL is correctly configured.
8
+ - Message routing issues: Verify routing keys and message schemata.
9
+ - Unhandled exceptions: Check logs for details.
10
+
11
+ For effective debugging, use the logging facilities provided and monitor agent activities.
@@ -0,0 +1,293 @@
1
+ # What is an Agent?
2
+
3
+ An agent is a self-contained piece of code designed to perform a specific function or service. Unlike general-purpose classes or modules, agents can be autonomous entities that focus on doing one thing well. They embody the Single Responsibility Principle (SRP) by design.
4
+
5
+ I will be using the Ruby programming language and the agent99 gem specifically to illustrate aspects of what I think good software agents should look like. I choose Ruby because:
6
+
7
+ - Ruby allows developers to create elegant and expressive AI applications.
8
+ - It promotes paradigm shifts, challenging the Python status quo in AI development.
9
+ - Developers who value clarity can find their niche in Ruby's design.
10
+ - Ruby's metaprogramming capabilities pave the way for creative AI solutions.
11
+ - Greater integration of Ruby-based frameworks makes them viable choices in the AI landscape.
12
+ - Ruby's object-oriented style leads to more modular and testable code.
13
+ - Ruby inspires a community that often prefers its syntax over Python's.
14
+
15
+ ## Getting Started with Agent99
16
+
17
+ The `agent99` is a Ruby gem that serves as a framework for developing and managing software agents, allowing for the execution and communication of these agents in a distributed environment. It implements a reference protocol that supports agent registration, discovery, and messaging through a centralized registry and a messaging system like AMQP or NATS. Each agent, derived from the `Agent99::Base` class, is designed to perform specific tasks as defined by its capabilities, adhering to the Single Responsibility Principle (SRP) for enhanced maintainability and testability. The framework facilitates modular agent interactions, enabling developers to build innovative applications while taking advantage of Ruby’s expressive syntax and metaprogramming features. The library emphasizes best practices in software design, including error handling and lifecycle management for robust agent operations.
18
+
19
+ To install the Agent99 gem, simply run:
20
+
21
+ ```bash
22
+ gem install agent99
23
+ ```
24
+
25
+ The [documentation](https://github.com/MadBomber/agent99/blob/main/docs/README.md) provides a comprehensive overview of the framework, but here, we will explore definitions of software agents and the Single Responsibility Principle (SRP), along with how Agent99 distinguishes itself in agent management and description.
26
+
27
+ ## What Is a Reference Implementation?
28
+
29
+ The Agent99 gem implements a protocol in Ruby that can be replicated in other programming languages. This interoperability allows software agents, given they support the Agent99 protocol, to mix and match regardless of the language they were built in.
30
+
31
+ ## Understanding Software Agents and the Single Responsibility Principle
32
+
33
+ Software agents and the Single Responsibility Principle (SRP) are crucial in contemporary software development. They decompose complex systems into manageable, autonomous components, while SRP promotes the creation of maintainable, testable, and adaptable systems. Utilizing both can boost code quality and nurture agility in development teams, particularly in AI, automation, and microservices contexts.
34
+
35
+ ## What Are Software Agents?
36
+
37
+ In simple terms, a software agent is a designated piece of code that performs a single function effectively. Within the Agent99 framework, agents are instances of subclasses derived from the **Agent99::Base** class. These instances can be running in their own separate process or groups of instances of different Agent99 instances can run within separate Threads in a single process.
38
+
39
+ Here's a simple example of an Agent99 agent class running in an independent process:
40
+
41
+ ```ruby
42
+ # File: example_agent.rb
43
+
44
+ require 'agent99'
45
+
46
+ class ExampleAgent < Agent99::Base
47
+ def info
48
+ {
49
+ name: self.class.to_s,
50
+ type: :server,
51
+ capabilities: %w[ rubber_stamp yes_man example ],
52
+ # request_schema: {}, # ExampleRequest.schema,
53
+ # response_schema: {}, # Agent99::RESPONSE.schema
54
+ # control_schema: {}, # Agent99::CONTROL.schema
55
+ # error_schema: {}, # Agent99::ERROR.schema
56
+ }
57
+ end
58
+
59
+ def receive_request
60
+ logger.info "Example agent received request: #{payload}"
61
+ send_response(status: 'success')
62
+ end
63
+ end
64
+
65
+ ExampleAgent.new.run
66
+ ```
67
+
68
+ ```base
69
+ ruby example_agent.rb
70
+ ```
71
+
72
+ Each agent subclass is responsible for specific methods that define its unique capabilities and how it handles requests. The `info` method provides a comprehensive information packet about the agent. It returns a hash containing key details that are crucial for agent registration and discovery within the system. The **:capabilities** entry in the `info` packet in an Array of Strings - synonyms - for the thing that the agent does.
73
+
74
+ For a server type agent, the only methods that are required to be defined, as in the ExampleAgent class above, are its **info** and its **receive_request** methods. Everything else from initialization, registration, message dispatching, and graceful shutdown are handled by the default methods within the **Agent99::Base** class.
75
+
76
+ More complex agents will require methods like **receive_response** and **receive_control** and potentially custom implementations of **init**, **initialize**, or **fini** may be necessary for managing state or resources.
77
+
78
+ > **RoadMap:** Currently, the Agent99 implementation defines the **capabilities** value as an `Array(String)`, with plans to enhance this functionality into descriptive unstructured text akin to defining tools for functional callbacks in LLM processing using semantic search.
79
+
80
+ ## The Single Responsibility Principle (SRP)
81
+
82
+ The Single Responsibility Principle, part of the SOLID principles of object-oriented design, asserts that a class or module should have only one reason to change. This means it should fulfill a single job or responsibility effectively.
83
+
84
+ ### Why SRP Matters
85
+
86
+ 1. **Maintainability:** Code is easier to read and modify, leading to more maintainable systems.
87
+ 2. **Testability:** Isolated responsibilities facilitate independent unit testing.
88
+ 3. **Flexibility:** Minimal impact on other parts of the system when modifying one responsibility, reducing the risk of bugs.
89
+
90
+ ### Applying SRP in Software Development
91
+
92
+ Implementing SRP involves:
93
+
94
+ - **Identifying Responsibilities:** Break down functionalities into specific tasks; each class or module should focus on a particular duty.
95
+ - **Modular Design:** Create a loosely coupled system to enhance separation of concerns.
96
+ - **Utilizing Design Patterns:** Harness design patterns like Observer, Strategy, and Factory to ensure clear interfaces and responsibilities.
97
+
98
+ ## Alignment of Agents and SRP
99
+
100
+ The notion of software agents naturally corresponds with the SRP. Each agent can be a distinct class or module that encapsulates a specific functionality. For instance:
101
+
102
+ - An order processing agent focuses solely on order management.
103
+ - A notification agent manages the sending of alerts or messages without getting involved in order processing logic.
104
+
105
+ Designing agents with SRP in mind fosters modularity and reusability, allowing changes to one agent without affecting others and supporting more robust architecture.
106
+
107
+ ## Agent99 as a Reference Framework
108
+
109
+ In its current iteration, the Agent99 Framework does not differ conceptually from other microservice architecture implementations. It enables centralized registration where agents list their capabilities for other agents or applications to discover. Agent communications occur via a distributed messaging system. The agent99 Ruby gem currently uses AMQP (via the Bunny gem and the RabbitMQ broker) and the NATS-server.
110
+
111
+ ### Agent Structure
112
+
113
+ Agents in Agent99 inherit from **Agent99::Base**, which offers core functionality through crucial modules:
114
+
115
+ - **HeaderManagement:** Handles message header processing.
116
+ - **AgentDiscovery:** Facilitates capability advertisement and discovery.
117
+ - **ControlActions:** Manages control messages.
118
+ - **AgentLifecycle:** Oversees agent startup and shutdown functionality.
119
+ - **MessageProcessing:** Manages message dispatch and handling.
120
+
121
+ Every agent must define its type (server, client, or hybrid) and capabilities. The framework supports three message types: requests, responses, and control messages.
122
+
123
+ ```ruby
124
+ class Agent99::Base
125
+ include Agent99::HeaderManagement
126
+ include Agent99::AgentDiscovery
127
+ include Agent99::ControlActions
128
+ include Agent99::AgentLifecycle
129
+ include Agent99::MessageProcessing
130
+
131
+ MESSAGE_TYPES = %w[request response control]
132
+
133
+ attr_reader :id, :capabilities, :name, :payload, :header, :logger, :queue
134
+ attr_accessor :registry_client, :message_client
135
+
136
+ ###################################################
137
+ private
138
+
139
+ def handle_error(message, error)
140
+ logger.error "#{message}: #{error.message}"
141
+ logger.debug error.backtrace.join("\n")
142
+ end
143
+
144
+ # the final rescue block
145
+ rescue StandardError => e
146
+ handle_error("Unhandled error in Agent99::Base", e)
147
+ exit(2)
148
+ end
149
+ ```
150
+
151
+ ### Centralized Registry
152
+
153
+ The registry service tracks agent availability and capabilities through a **RegistryClient**. A web-based application serves as the central registry, with a Sinatra implementation found in the `examples/registry.rb` file. Its primary function is to maintain a data store of registered agents.
154
+
155
+ It supports three core operations:
156
+
157
+ #### 1. Register
158
+
159
+ ![Central Registry Process](/assets/images/agent_registry_process.png)
160
+
161
+ Agents register by providing their information (e.g., name and capabilities) to the registry service. Here's how registration works in practice:
162
+
163
+ ```ruby
164
+ class WeatherAgent < Agent99::Base
165
+ TYPE = :server
166
+
167
+ def capabilities
168
+ %w[get_temperature get_forecast]
169
+ end
170
+
171
+ def receive_request(message)
172
+ case message.payload[:action]
173
+ when 'get_temperature'
174
+ send_response({ temperature: 72, unit: 'F' })
175
+ when 'get_forecast'
176
+ send_response({ forecast: 'Sunny with a chance of rain' })
177
+ end
178
+ end
179
+ end
180
+
181
+ # Start the agent
182
+ WeatherAgent.new.run
183
+ ```
184
+
185
+ Upon successful registration, agents receive a universally unique ID (UUID) that identifies them in the system. The registration process is handled automatically by the Agent99 framework when you call `run`.
186
+
187
+ #### 2. Discover
188
+
189
+ Agents can query the registry to discover capabilities. The discovery operation retrieves information about agents offering specific capabilities via an HTTP GET request.
190
+
191
+ #### 3. Withdraw
192
+
193
+ When an agent needs to exit the system, it withdraws its registration using its UUID, removing it from the available agents list through an HTTP DELETE request.
194
+
195
+ ### Messaging Network
196
+
197
+ The Ruby implementation of Agent99 currently focuses on AMQP messaging systems. Messages are formatted as JSON structures that adhere to defined schemas, allowing the **MessageClient** to validate messages effortlessly.
198
+
199
+ Messages are validated against defined schemas, and invalid messages return to the sender without invoking agent-specific processes.
200
+
201
+ Message types within the framework include:
202
+
203
+ #### Request Messages
204
+
205
+ These messages are validated against an agent-defined schema and include:
206
+
207
+ - A header with routing information.
208
+ - Agent-specific elements with their types and examples.
209
+
210
+ Requests are handled by the `receive_request` handler in target agents.
211
+
212
+ Here's an example of a request message schema using the [SimpleJsonSchemaBuilder gem](https://github.com/mooktakim/simple_json_schema_builder)
213
+
214
+ ```ruby
215
+ # examples/maxwell_request.rb
216
+
217
+ require 'agent99/header_schema'
218
+
219
+ class MaxwellRequest < SimpleJsonSchemaBuilder::Base
220
+ object do
221
+ object :header, schema: Agent99::HeaderSchema
222
+
223
+ string :greeting, required: false, examples: ["Hello"]
224
+ string :name, required: true, examples: ["World"]
225
+ end
226
+ end
227
+ ```
228
+
229
+ This schema defines a `MaxwellRequest` with a header (using the `Agent99::HeaderSchema`), an optional greeting, and a required name. A valid JSON message conforming to this schema might look like:
230
+
231
+ ```json
232
+ {
233
+ "header": {
234
+ "from_uuid": "123e4567-e89b-12d3-a456-426614174000",
235
+ "to_uuid": "987e6543-e21b-12d3-a456-426614174000",
236
+ "message_id": "msg-001",
237
+ "correlation_id": "corr-001",
238
+ "timestamp": "2023-04-01T12:00:00Z"
239
+ },
240
+ "greeting": "Hello",
241
+ "name": "Agent99"
242
+ }
243
+ ```
244
+
245
+ Using such schemas ensures that messages are well-structured and contain all necessary information before being processed by agents.
246
+
247
+ Here is how the **MaxwellAgent** associates itself with its specific request schema:
248
+
249
+ ```ruby
250
+ # examples/maxwell_agent86.rb
251
+
252
+ require 'agent99'
253
+ require_relative 'maxwell_request'
254
+
255
+ class MaxwellAgent86 < Agent99::Base
256
+ def info
257
+ {
258
+ # ...
259
+ request_schema: MaxwellRequest.schema,
260
+ # ...
261
+ }
262
+ end
263
+ ```
264
+
265
+ When an agent subclass defines a **:request_schema** in its `info`, the message processing of the **Agent99::Base** validates all incoming requests against the schema. If there are errors, those errors are returned to the sender without presenting the request message to the agent's custom **receive_request** method.
266
+
267
+ #### Response Messages
268
+
269
+ Responses are routed back to the requesting agent and include:
270
+
271
+ - The original message header in reverse.
272
+ - The response payload.
273
+ - Status information.
274
+
275
+ Responses are processed by the `receive_response` method.
276
+
277
+ #### Control Messages
278
+
279
+ Control messages manage agent lifecycles and configurations and include commands such as:
280
+
281
+ - **shutdown:** Stop the agent.
282
+ - **pause/resume:** Temporarily suspend or resume operations.
283
+ - **update_config:** Modify agent configurations.
284
+ - **status:** Query agent state.
285
+ - **response:** Handle control operation results.
286
+
287
+ Messages are queued with a 60-second TTL (Time To Live) to prevent buildup from inactive agents.
288
+
289
+ ## Additional Resources
290
+
291
+ For further exploration, check out the documentation of the current Ruby implementation at [GitHub](https://github.com/MadBomber/agent99).
292
+
293
+ Contributions to this initial Ruby reference implementation are welcome! It would be exciting to see additional language implementations.
data/examples/README.md CHANGED
@@ -45,6 +45,49 @@ This file implements a simple registry service for AI agents using Sinatra.
45
45
  - DELETE `/withdraw/:uuid`: Withdraws an agent from the registry
46
46
  - GET `/`: Lists all registered agents
47
47
 
48
+ ### 5. control_agent.rb
49
+
50
+ Example use of control messages.
51
+
52
+ ### 6. kaos_spy.rb
53
+ - Agent 99 was kinnapped by KAOS and forced to reveal the secrets of Control's centralized registry and communications network.
54
+ - The KAOS spy raided hacked the registry, stole the records for all of Control's agents in the field and DOX'ed them on social media.
55
+ - That was not enough for KAOS. Knowing the secret UUID for each agent, KAOS proceeded to turn off the communication network one queue at a time.
56
+ - Get Smart -- Get Security
57
+
58
+ ### 7. agent_watcher.rb
59
+
60
+ This file implements an agent watcher that dynamically loads and runs new agents.
61
+
62
+ - Class: `AgentWatcher < Agent99::Base`
63
+ - Functionality: Monitors a specified directory for new Ruby files and loads them as agents
64
+ - Key features:
65
+ - Watches a configurable directory (default: './agents')
66
+ - Detects new .rb files added to the watched directory
67
+ - Dynamically loads new files as Ruby agents
68
+ - Instantiates and runs each new agent in a separate thread
69
+ - Handles errors during the loading and running process
70
+ - Terminates all loaded agents when the watcher is stopped
71
+ - Key methods:
72
+ - `init`: Sets up the file watcher
73
+ - `setup_watcher`: Configures the directory listener
74
+ - `handle_new_agent`: Processes newly detected agent files
75
+
76
+ ### 8. example_agent.rb
77
+
78
+ This file provides a simple example agent that can be dynamically loaded by the AgentWatcher.
79
+
80
+ - Class: `ExampleAgent < Agent99::Base`
81
+ - Functionality: Demonstrates a basic agent that can be dynamically loaded
82
+ - Key features:
83
+ - Defines capabilities as a rubber stamp and yes-man
84
+ - Responds to all requests with a success status
85
+ - Key methods:
86
+ - `capabilities`: Defines the agent's capabilities
87
+ - `receive_request`: Handles incoming requests and sends a response
88
+
89
+ Note: To use the example_agent.rb, first run the AgentWatcher, then copy example_agent.rb into the 'agents' directory. The AgentWatcher will automatically detect, load, and run the new agent.
90
+
48
91
  ## Usage
49
92
 
50
93
  From the examples directory you will need to start three different processes. You will want to keep them all in the forgound so it would be best to start them in different terminal windows.
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/agent_watcher.rb
3
+ #
4
+ # This file defines an AgentWatcher class that monitors a specified directory
5
+ # for new Ruby files and dynamically loads and runs them as agents.
6
+
7
+ # When running, the AgentWatcher does the following:
8
+ # 1. Watches the directory specified by AGENT_WATCH_PATH (default: './agents')
9
+ # 2. Detects when new .rb files are added to the watched directory
10
+ # 3. Attempts to load each new file as a Ruby agent
11
+ # 4. If successful, instantiates the agent and runs it in a separate thread
12
+ #
13
+ # When example_agent.rb is copied into the agents directory:
14
+ # 1. The AgentWatcher detects the new file
15
+ # 2. It attempts to load the file and extract the agent class
16
+ # 3. If the class is a subclass of Agent99::Base, it instantiates the agent
17
+ # 4. The new agent is then run in a separate thread
18
+ # 5. Any errors during this process are logged for debugging
19
+ #
20
+ # When AgentWatcher is terminated, it first terminates all of the
21
+ # agents that it has previously loaded and then terminates itself.
22
+
23
+
24
+ require 'listen'
25
+
26
+ require_relative '../lib/agent99'
27
+
28
+ class AgentWatcher < Agent99::Base
29
+ TYPE = :client
30
+
31
+ def capabilities = {
32
+ info: {
33
+ capabilities: %w[ launch_agents watcher launcher ]
34
+ }
35
+ }
36
+
37
+ def init
38
+ @watch_path = ENV.fetch('AGENT_WATCH_PATH', './agents')
39
+ setup_watcher
40
+ end
41
+
42
+ private
43
+
44
+ def setup_watcher
45
+ @listener = Listen.to(@watch_path) do |modified, added, removed|
46
+ added.each do |file|
47
+ handle_new_agent(file)
48
+ end
49
+ end
50
+
51
+ # Start listening in a separate thread
52
+ @listener.start
53
+ end
54
+
55
+ def handle_new_agent(file)
56
+ return unless File.extname(file) == '.rb'
57
+
58
+ begin
59
+ # Load the new agent file
60
+ require file
61
+
62
+ # Extract the class name from the file name
63
+ class_name = File.basename(file, '.rb')
64
+ .split('_')
65
+ .map(&:capitalize)
66
+ .join
67
+
68
+ # Get the class object
69
+ agent_class = Object.const_get(class_name)
70
+
71
+ # Verify it's an Agent99::Base subclass
72
+ return unless agent_class < Agent99::Base
73
+
74
+ # Create and run the new agent in a thread
75
+ Thread.new do
76
+ begin
77
+ agent = agent_class.new
78
+ agent.run
79
+ rescue StandardError => e
80
+ logger.error "Error running agent #{class_name}: #{e.message}"
81
+ logger.debug e.backtrace.join("\n")
82
+ end
83
+ end
84
+
85
+ logger.info "Successfully launched agent: #{class_name}"
86
+
87
+ rescue LoadError => e
88
+ logger.error "Failed to load agent file #{file}: #{e.message}"
89
+
90
+ rescue NameError => e
91
+ logger.error "Failed to instantiate agent class from #{file}: #{e.message}"
92
+
93
+ rescue StandardError => e
94
+ logger.error "Unexpected error handling #{file}: #{e.message}"
95
+ logger.debug e.backtrace.join("\n")
96
+ end
97
+ end
98
+
99
+ def fini
100
+ @listener&.stop
101
+ super
102
+ end
103
+ end
104
+
105
+ watcher = AgentWatcher.new
106
+ watcher.run
File without changes
@@ -14,7 +14,23 @@
14
14
  require_relative '../lib/agent99'
15
15
 
16
16
  class ChiefAgent < Agent99::Base
17
- TYPE = :client
17
+ # this information is made available when the agent
18
+ # registers with the central registry service. It is
19
+ # made available during the discovery process.
20
+ #
21
+ def info
22
+ {
23
+ name: self.class.to_s,
24
+ type: :client,
25
+ capabilities: ['Chief of Control'],
26
+ # request_schema: ChiefRequest.schema,
27
+ # response_schema: {}, # Agent99::RESPONSE.schema
28
+ # control_schema: {}, # Agent99::CONTROL.schema
29
+ # error_schema: {}, # Agent99::ERROR.schema
30
+ }
31
+ end
32
+
33
+
18
34
 
19
35
  # init is called at the end of the initialization process.
20
36
  # It may be only something that a :client type agent would do.
@@ -82,11 +98,6 @@ class ChiefAgent < Agent99::Base
82
98
 
83
99
  exit(0)
84
100
  end
85
-
86
-
87
- def capabilities
88
- ['Chief of Control']
89
- end
90
101
  end
91
102
 
92
103
 
data/examples/control.rb CHANGED
@@ -4,7 +4,21 @@
4
4
  require_relative '../lib/agent99'
5
5
 
6
6
  class Control < Agent99::Base
7
- TYPE = :hybrid
7
+ # this information is made available when the agent
8
+ # registers with the central registry service. It is
9
+ # made available during the discovery process.
10
+ #
11
+ def info
12
+ {
13
+ name: self.class.to_s,
14
+ type: :hybrid,
15
+ capabilities: ['control', 'headquarters', 'secret underground base'],
16
+ # request_schema: ControlRequest.schema,
17
+ # response_schema: {}, # Agent99::RESPONSE.schema
18
+ # control_schema: {}, # Agent99::CONTROL.schema
19
+ # error_schema: {}, # Agent99::ERROR.schema
20
+ }
21
+ end
8
22
 
9
23
  attr_accessor :statuses
10
24
 
@@ -14,11 +28,6 @@ class Control < Agent99::Base
14
28
  end
15
29
 
16
30
 
17
- def capabilities
18
- ['control', 'headquarters', 'secret underground base']
19
- end
20
-
21
-
22
31
  def send_control_message(message:, payload: {})
23
32
  @agents.each do |agent|
24
33
  response = @message_client.publish(
@@ -48,7 +57,7 @@ class Control < Agent99::Base
48
57
 
49
58
 
50
59
  def stop_all
51
- send_control_message(message: 'stop')
60
+ send_control_message(message: 'shutdown')
52
61
  end
53
62
 
54
63
  def get_all_status
@@ -0,0 +1,39 @@
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
+ # this information is made available when the agent
19
+ # registers with the central registry service. It is
20
+ # made available during the discovery process.
21
+ #
22
+ def info
23
+ {
24
+ name: self.class.to_s,
25
+ type: :server,
26
+ capabilities: %w[ rubber_stamp yes_man example ],
27
+ # request_schema: {}, # ExampleRequest.schema,
28
+ # response_schema: {}, # Agent99::RESPONSE.schema
29
+ # control_schema: {}, # Agent99::CONTROL.schema
30
+ # error_schema: {}, # Agent99::ERROR.schema
31
+ }
32
+ end
33
+
34
+
35
+ def receive_request
36
+ logger.info "Example agent received request: #{payload}"
37
+ send_response(status: 'success')
38
+ end
39
+ end