agent99 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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,165 @@
1
+ # Agent99 Framework
2
+
3
+ ## Message Processing
4
+
5
+ The Agent99 framework implements a robust message processing system that handles three main types of
6
+ messages: Request, Response, and Control messages. Each message type follows specific processing flows
7
+ and validation rules.
8
+
9
+ ### Message Types
10
+
11
+ 1. **Request Messages**
12
+ - Sent by clients to request services or actions
13
+ - Must include a valid schema definition
14
+ - Processed through the `receive_request` handler
15
+ - Automatically validated against REQUEST_SCHEMA
16
+ - Can trigger responses back to the sender
17
+
18
+ 2. **Response Messages**
19
+ - Sent in reply to requests
20
+ - Contain results or error information
21
+ - Processed through the `receive_response` handler
22
+ - Include original request reference
23
+ - May trigger additional processing flows
24
+
25
+ 3. **Control Messages**
26
+ - Manage agent lifecycle and behavior
27
+ - Include actions like shutdown, pause, resume
28
+ - Processed through dedicated control handlers
29
+ - Support configuration updates
30
+ - Enable status monitoring
31
+
32
+ ### Message Processing Flow
33
+
34
+ #### Request Processing
35
+ 1. Message arrives and is validated against schema
36
+ 2. If validation passes:
37
+ - `receive_request` is called
38
+ - Custom processing occurs
39
+ - Optional response is sent
40
+ 3. If validation fails:
41
+ - Error response is automatically sent
42
+ - Error is logged
43
+ - Request is not processed further
44
+
45
+ ![Request Message Processing Flow](diagrams/request_flow.png)
46
+
47
+ #### Response Processing
48
+ 1. Response message is received
49
+ 2. `receive_response` handler is called
50
+ 3. Response data is processed
51
+ 4. Any follow-up actions are triggered
52
+
53
+ #### Control Processing
54
+ 1. Control message is received
55
+ 2. Action is identified
56
+ 3. Appropriate handler is called:
57
+ - `handle_shutdown`
58
+ - `handle_pause`
59
+ - `handle_resume`
60
+ - `handle_update_config`
61
+ - `handle_status_request`
62
+ 4. Control response is sent if needed
63
+
64
+ ### Error Handling
65
+
66
+ The framework provides comprehensive error handling:
67
+
68
+ 1. **Validation Errors**
69
+ - Schema validation failures
70
+ - Missing required fields
71
+ - Invalid data types
72
+
73
+ 2. **Processing Errors**
74
+ - Handler exceptions
75
+ - Resource unavailability
76
+ - Timeout conditions
77
+
78
+ 3. **System Errors**
79
+ - Connection issues
80
+ - Resource constraints
81
+ - Framework-level problems
82
+
83
+ ### Best Practices
84
+
85
+ 1. **Message Validation**
86
+ - Always define REQUEST_SCHEMA for request-handling agents
87
+ - Include comprehensive examples in schema
88
+ - Validate message structure
89
+
90
+ 2. **Error Handling**
91
+ - Implement robust error handling in handlers
92
+ - Return meaningful error messages
93
+ - Log all significant events
94
+
95
+ 3. **Response Management**
96
+ - Send responses promptly
97
+ - Include relevant context
98
+ - Handle partial success cases
99
+
100
+ 4. **Control Messages**
101
+ - Respond to all control messages
102
+ - Implement graceful shutdown
103
+ - Maintain agent state properly
104
+
105
+ ### Implementation Example
106
+
107
+ ```ruby
108
+ class MyAgent < Agent99::Base
109
+ REQUEST_SCHEMA = {
110
+ type: "object",
111
+ properties: {
112
+ header: HEADER_SCHEMA,
113
+ data: {
114
+ type: "object",
115
+ required: ["action"],
116
+ properties: {
117
+ action: { type: "string" }
118
+ }
119
+ }
120
+ }
121
+ }
122
+
123
+ def receive_request
124
+ action = payload.dig(:data, :action)
125
+ result = process_action(action)
126
+ send_response(result)
127
+ rescue StandardError => e
128
+ send_response(error: e.message)
129
+ end
130
+
131
+ private
132
+
133
+ def process_action(action)
134
+ # Custom processing logic
135
+ { status: "success", result: action }
136
+ end
137
+ end
138
+ ```
139
+
140
+ ### Message Structure
141
+
142
+ All messages must include a header with:
143
+ * `type`: Message type (request/response/control)
144
+ * `to_uuid`: Destination agent ID
145
+ * `from_uuid`: Sender agent ID
146
+ * `event_uuid`: Unique event identifier
147
+ * `timestamp`: Message creation time
148
+
149
+ Example message structure:
150
+ ```ruby
151
+ {
152
+ header: {
153
+ type: "request",
154
+ to_uuid: "agent_123",
155
+ from_uuid: "agent_456",
156
+ event_uuid: "evt_789",
157
+ timestamp: 1638360000000000
158
+ },
159
+ data: {
160
+ # Message-specific payload
161
+ }
162
+ }
163
+ ```
164
+
165
+
@@ -0,0 +1,129 @@
1
+ # Agent99 Framework
2
+
3
+ ## Messaging Systems
4
+
5
+ Agent99 supports both AMQP and NATS messaging systems, allowing you to choose the most appropriate messaging backend for your needs. The framework provides a consistent interface regardless of which messaging system you use.
6
+
7
+ ### Supported Message Brokers
8
+
9
+ #### AMQP (RabbitMQ)
10
+ The AMQP implementation uses RabbitMQ as the message broker and provides:
11
+
12
+ - **Durability**: Messages can persist across broker restarts
13
+ - **Queue TTL**: Queues automatically expire after 60 seconds of inactivity
14
+ - **Automatic Reconnection**: Handles connection drops gracefully
15
+ - **Default Configuration**:
16
+ ```ruby
17
+ {
18
+ host: "127.0.0.1",
19
+ port: 5672,
20
+ ssl: false,
21
+ vhost: "/",
22
+ user: "guest",
23
+ pass: "guest",
24
+ heartbeat: :server,
25
+ frame_max: 131072,
26
+ auth_mechanism: "PLAIN"
27
+ }
28
+ ```
29
+
30
+ #### NATS
31
+ The NATS implementation provides:
32
+
33
+ - **Lightweight**: Simple pub/sub with minimal overhead
34
+ - **Auto-Pruning**: Automatically cleans up unused subjects
35
+ - **High Performance**: Optimized for speed and throughput
36
+ - **Default Configuration**: Uses NATS default connection settings
37
+
38
+ ### Choosing a Message Client
39
+
40
+ You can select your messaging backend when initializing an agent:
41
+
42
+ ```ruby
43
+ # For AMQP
44
+ agent = MyAgent.new(message_client: Agent99::AmqpMessageClient.new)
45
+
46
+ # For NATS
47
+ agent = MyAgent.new(message_client: Agent99::NatsMessageClient.new)
48
+ ```
49
+
50
+ Or configure it via environment variables:
51
+
52
+ TODO: Need to add this to the configuration class which is still TBD
53
+
54
+ ```bash
55
+ # For AMQP
56
+ export MESSAGE_SYSTEM=amqp
57
+ export RABBITMQ_URL=amqp://guest:guest@localhost:5672
58
+
59
+ # For NATS
60
+ export MESSAGE_SYSTEM=nats
61
+ export NATS_URL=nats://localhost:4222
62
+ ```
63
+
64
+ ### Message Handling
65
+
66
+ Both implementations support these core operations:
67
+
68
+ 1. **Queue Setup**:
69
+ ```ruby
70
+ queue = message_client.setup(agent_id: id, logger: logger)
71
+ ```
72
+
73
+ 2. **Message Publishing**:
74
+ ```ruby
75
+ message_client.publish({
76
+ header: {
77
+ type: "request",
78
+ to_uuid: recipient_id,
79
+ from_uuid: sender_id,
80
+ event_uuid: event_id,
81
+ timestamp: Agent99::Timestamp.new.to_i
82
+ },
83
+ data: payload # or whatever for the agent.
84
+ })
85
+ ```
86
+
87
+ 3. **Message Subscription**:
88
+ ```ruby
89
+ message_client.listen_for_messages(
90
+ queue,
91
+ request_handler: ->(msg) { handle_request(msg) },
92
+ response_handler: ->(msg) { handle_response(msg) },
93
+ control_handler: ->(msg) { handle_control(msg) }
94
+ )
95
+ ```
96
+
97
+ ### Key Differences
98
+
99
+ 1. **Queue Management**:
100
+ - AMQP: Explicit queue creation and deletion
101
+ - NATS: Implicit subject-based routing
102
+
103
+ 2. **Message Persistence**:
104
+ - AMQP: Supports persistent messages and queues
105
+ - NATS: Ephemeral messaging by default
106
+
107
+ 3. **Error Handling**:
108
+ - AMQP: Provides detailed connection and channel errors
109
+ - NATS: Simplified error handling with auto-reconnect
110
+
111
+ ### Best Practices
112
+
113
+ 1. **Error Handling**: Always wrap message operations in begin/rescue blocks
114
+ 2. **Logging**: Use the provided logger for debugging and monitoring
115
+ 3. **Configuration**: Use environment variables for deployment flexibility
116
+ 4. **Testing**: Test your agents with both messaging systems to ensure compatibility
117
+
118
+ ### Monitoring
119
+
120
+ Both implementations provide logging for:
121
+ - Message publication success/failure
122
+ - Queue creation and deletion
123
+ - Connection status
124
+ - Error conditions
125
+
126
+ Use the logger to monitor your messaging system:
127
+ ```ruby
128
+ message_client.logger.level = Logger::DEBUG # For detailed logging
129
+ ```
@@ -0,0 +1,9 @@
1
+ # Agent99 Framework
2
+
3
+ ## Performance Considerations
4
+
5
+ To ensure optimal performance:
6
+
7
+ - Optimize message processing by minimizing complex logic in request handlers.
8
+ - Use asynchronous processing wherever possible to prevent blocking operations.
9
+ - Profile agent performance during development to identify bottlenecks.
@@ -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.
data/examples/README.md CHANGED
@@ -1,41 +1,36 @@
1
- # Agent99 Framework Examples
2
-
3
- - TODO
4
- - Review and edit
5
- - Add instructions for brew install rabbitmq nats-server boxes
6
- - review example agents to see code can be tightened
1
+ # Agent99 Framework Examples - 86 and the Chief
7
2
 
8
3
  This folder contains example implementations using the Agent99 framework. The framework provides a foundation for building AI agents that can communicate with each other through a message-based system.
9
4
 
10
5
  ## Files
11
6
 
12
- ### 1. hello_world.rb
7
+ ### 1. maxwell_agent86.rb
13
8
 
14
- This file demonstrates a basic AI agent implementation using the Agent99 framework.
9
+ This file demonstrates a basic agent implementation using the Agent99 framework.
15
10
 
16
- - Class: `HelloWorld < Agent99::Base`
11
+ - Class: `MaxwellAgent86 < Agent99::Base`
17
12
  - Functionality: Responds to "hello world" requests
18
13
  - Key methods:
19
14
  - `receive_request`: Handles incoming requests
20
15
  - `validate_request`: Validates the request against a schema
21
16
  - `process`: Generates the response
22
17
 
23
- ### 2. hello_world_client.rb
18
+ ### 2. chief_agent.rb
24
19
 
25
- This file shows how to create a client that interacts with the HelloWorld agent.
20
+ This file shows how to create a client that interacts with the MaxwellAgent86 agent.
26
21
 
27
- - Class: `HelloWorldClient < Agent99::Base`
28
- - Functionality: Sends a request to a HelloWorld agent and processes the response
22
+ - Class: `ChiefAgent < Agent99::Base`
23
+ - Functionality: Sends a request to an agent who can be a greeter and processes the response
29
24
  - Key methods:
30
25
  - `init`: Initiates the request sending process
31
26
  - `send_request`: Builds and sends the request
32
27
  - `receive_response`: Handles the response from the HelloWorld agent
33
28
 
34
- ### 3. hello_world_request.rb
29
+ ### 3. mexwell_request.rb
35
30
 
36
- This file defines the schema for HelloWorld requests using SimpleJsonSchemaBuilder.
31
+ This file defines the schema for MaxwellAgent86 requests using SimpleJsonSchemaBuilder.
37
32
 
38
- - Class: `HelloWorldRequest < SimpleJsonSchemaBuilder::Base`
33
+ - Class: `MaxwellRequest < SimpleJsonSchemaBuilder::Base`
39
34
  - Defines the structure of a valid HelloWorld request
40
35
 
41
36
  ### 4. registry.rb
@@ -50,30 +45,65 @@ This file implements a simple registry service for AI agents using Sinatra.
50
45
  - DELETE `/withdraw/:uuid`: Withdraws an agent from the registry
51
46
  - GET `/`: Lists all registered agents
52
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
+
53
91
  ## Usage
54
92
 
55
- 1. Start the registry service:
56
- ```
57
- ruby registry.rb
58
- ```
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.
94
+
95
+ Start the sample registry first: `./registry.rb`
96
+
97
+ Then start the service agent: `./maxwell_agent86.rb`
98
+ Maxwell will will register itself, get its UUID and setup a message queue to which it will listen for its service requests.
59
99
 
60
- 2. Run the HelloWorld agent:
61
- ```
62
- ruby hello_world.rb
63
- ```
100
+ Finally start the chief agent in charge: `./chief_agent.rb`
101
+ The Chief also registers itself but no other agent can give the Chief missions. That's his job. The chief gets his mission UUID, sets up a message queue to listen for the reports from the field after he sends out his request (aka order)
64
102
 
65
- 3. Run the HelloWorld client:
66
- ```
67
- ruby hello_world_client.rb
68
- ```
103
+ But first the Chief asks the registry for the UUIDs of all agents who can handle a "greeter" request. The Chief selects one of those agents and sends the agent a greet request. The Chief then waits for a response to the request. When it comes in, the chiefs displays the response and terminates.
69
104
 
70
- ## Dependencies
105
+ Run the chief a few times in a roll. Some times the agent to whom the Chief issues his requests does not always respond the you would expect.
71
106
 
72
- - Ruby 3.3+
73
- - Gems: json, json_schema, sinatra, bunny, securerandom
107
+ ![Agent99 Framework Diagram](diagram.png)
74
108
 
75
- ## Notes
76
109
 
77
- - The framework uses modern Ruby 3.3 syntax, especially for hashes and method signatures with named parameters.
78
- - The examples demonstrate basic usage of the Agent99 framework, including request/response handling, validation, and agent discovery.
79
- - The registry service uses an in-memory store (AGENT_REGISTRY) for simplicity, but it's recommended to use a more robust solution like SQLite for production use.
@@ -0,0 +1,102 @@
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 = %w[ launch_agents watcher launcher ]
32
+
33
+ def init
34
+ @watch_path = ENV.fetch('AGENT_WATCH_PATH', './agents')
35
+ setup_watcher
36
+ end
37
+
38
+ private
39
+
40
+ def setup_watcher
41
+ @listener = Listen.to(@watch_path) do |modified, added, removed|
42
+ added.each do |file|
43
+ handle_new_agent(file)
44
+ end
45
+ end
46
+
47
+ # Start listening in a separate thread
48
+ @listener.start
49
+ end
50
+
51
+ def handle_new_agent(file)
52
+ return unless File.extname(file) == '.rb'
53
+
54
+ begin
55
+ # Load the new agent file
56
+ require file
57
+
58
+ # Extract the class name from the file name
59
+ class_name = File.basename(file, '.rb')
60
+ .split('_')
61
+ .map(&:capitalize)
62
+ .join
63
+
64
+ # Get the class object
65
+ agent_class = Object.const_get(class_name)
66
+
67
+ # Verify it's an Agent99::Base subclass
68
+ return unless agent_class < Agent99::Base
69
+
70
+ # Create and run the new agent in a thread
71
+ Thread.new do
72
+ begin
73
+ agent = agent_class.new
74
+ agent.run
75
+ rescue StandardError => e
76
+ logger.error "Error running agent #{class_name}: #{e.message}"
77
+ logger.debug e.backtrace.join("\n")
78
+ end
79
+ end
80
+
81
+ logger.info "Successfully launched agent: #{class_name}"
82
+
83
+ rescue LoadError => e
84
+ logger.error "Failed to load agent file #{file}: #{e.message}"
85
+
86
+ rescue NameError => e
87
+ logger.error "Failed to instantiate agent class from #{file}: #{e.message}"
88
+
89
+ rescue StandardError => e
90
+ logger.error "Unexpected error handling #{file}: #{e.message}"
91
+ logger.debug e.backtrace.join("\n")
92
+ end
93
+ end
94
+
95
+ def fini
96
+ @listener&.stop
97
+ super
98
+ end
99
+ end
100
+
101
+ watcher = AgentWatcher.new
102
+ watcher.run
File without changes