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,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
|
+

|
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.
|
7
|
+
### 1. maxwell_agent86.rb
|
13
8
|
|
14
|
-
This file demonstrates a basic
|
9
|
+
This file demonstrates a basic agent implementation using the Agent99 framework.
|
15
10
|
|
16
|
-
- Class: `
|
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.
|
18
|
+
### 2. chief_agent.rb
|
24
19
|
|
25
|
-
This file shows how to create a client that interacts with the
|
20
|
+
This file shows how to create a client that interacts with the MaxwellAgent86 agent.
|
26
21
|
|
27
|
-
- Class: `
|
28
|
-
- Functionality: Sends a request to a
|
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.
|
29
|
+
### 3. mexwell_request.rb
|
35
30
|
|
36
|
-
This file defines the schema for
|
31
|
+
This file defines the schema for MaxwellAgent86 requests using SimpleJsonSchemaBuilder.
|
37
32
|
|
38
|
-
- Class: `
|
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
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
73
|
-
- Gems: json, json_schema, sinatra, bunny, securerandom
|
107
|
+

|
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
|