agent99 0.0.3 → 0.0.5
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.
- checksums.yaml +4 -4
- data/A2A_SPEC-dev.md +1829 -0
- data/CHANGELOG.md +38 -0
- data/COMMITS.md +196 -0
- data/DOCS.md +96 -0
- data/README.md +212 -84
- data/Rakefile +62 -0
- data/docs/AI/htm.md +215 -0
- data/docs/AI/htm.rb +141 -0
- data/docs/AI/htm_demo.db +0 -0
- data/docs/AI/notes_on_htm_implementation.md +1319 -0
- data/docs/AI/some_code.rb +692 -0
- data/docs/advanced-topics/a2a-protocol.md +13 -0
- data/docs/{advanced_features.md → advanced-topics/advanced-features.md} +9 -4
- data/docs/{control_actions.md → advanced-topics/control-actions.md} +2 -0
- data/docs/advanced-topics/model-context-protocol.md +4 -0
- data/docs/advanced-topics/multi-agent-processing.md +674 -0
- data/docs/agent-development/request-response-handling.md +512 -0
- data/docs/agent99_framework/central_registry.md +94 -0
- data/docs/agent99_framework/message_client.md +120 -0
- data/docs/agent99_framework/registry_client.md +119 -0
- data/docs/api-reference/agent99-base.md +463 -0
- data/docs/api-reference/message-clients.md +495 -0
- data/docs/{api_reference.md → api-reference/overview.md} +14 -4
- data/docs/api-reference/registry-client.md +470 -0
- data/docs/api-reference/schemas.md +518 -0
- data/docs/assets/css/custom.css +27 -0
- data/docs/assets/images/agent-lifecycle.svg +73 -0
- data/docs/assets/images/agent-registry-process.svg +86 -0
- data/docs/assets/images/agent-registry-processes.svg +114 -0
- data/docs/assets/images/agent-types-overview.svg +51 -0
- data/docs/assets/images/agent99-architecture.svg +85 -0
- data/docs/assets/images/agent99_logo.png +0 -0
- data/docs/assets/images/control-actions-state.svg +83 -0
- data/docs/assets/images/knowledge-graph.svg +77 -0
- data/docs/assets/images/message-processing-flow.svg +148 -0
- data/docs/assets/images/multi-agent-system.svg +66 -0
- data/docs/assets/images/proxy-pattern-sequence.svg +48 -0
- data/docs/assets/images/request-flow.svg +97 -0
- data/docs/assets/images/request-processing-lifecycle.svg +50 -0
- data/docs/assets/images/request-response-sequence.svg +39 -0
- data/docs/{agent_lifecycle.md → core-concepts/agent-lifecycle.md} +2 -0
- data/docs/core-concepts/agent-types.md +255 -0
- data/docs/{architecture.md → core-concepts/architecture.md} +5 -5
- data/docs/core-concepts/what-is-an-agent.md +293 -0
- data/docs/diagrams/message-flow-sequence.svg +198 -0
- data/docs/diagrams/p2p-network-topology.svg +181 -0
- data/docs/diagrams/smart-transport-routing.svg +165 -0
- data/docs/diagrams/three-layer-architecture.svg +77 -0
- data/docs/diagrams/transport-extension-api.svg +309 -0
- data/docs/diagrams/transport-extension-architecture.svg +234 -0
- data/docs/diagrams/transport-selection-flowchart.svg +264 -0
- data/docs/examples/advanced-examples.md +951 -0
- data/docs/examples/basic-examples.md +268 -0
- data/docs/{agent_discovery.md → framework-components/agent-discovery.md} +9 -5
- data/docs/{agent_registry_processes.md → framework-components/agent-registry.md} +9 -3
- data/docs/{message_processing.md → framework-components/message-processing.md} +3 -1
- data/docs/getting-started/basic-example.md +306 -0
- data/docs/getting-started/installation.md +160 -0
- data/docs/getting-started/overview.md +64 -0
- data/docs/getting-started/quick-start.md +179 -0
- data/docs/index.md +97 -0
- data/docs/operations/breaking-changes.md +26 -0
- data/examples/DEMO.md +148 -0
- data/examples/README.md +50 -0
- data/examples/agent_watcher.rb +5 -1
- data/examples/bad_agent.rb +32 -0
- data/examples/chief_agent.rb +17 -6
- data/examples/control.rb +16 -7
- data/examples/example_agent.rb +16 -3
- data/examples/maxwell_agent86.rb +15 -26
- data/examples/registry.rb +10 -9
- data/examples/run_demo.rb +433 -0
- data/lib/agent99/agent_discovery.rb +4 -0
- data/lib/agent99/agent_lifecycle.rb +34 -10
- data/lib/agent99/amqp_message_client.rb +2 -2
- data/lib/agent99/base.rb +6 -2
- data/lib/agent99/message_processing.rb +6 -10
- data/lib/agent99/registry_client.rb +15 -11
- data/lib/agent99/tcp_message_client.rb +183 -0
- data/lib/agent99/version.rb +1 -1
- data/lib/agent99.rb +1 -1
- data/mkdocs.yml +195 -0
- data/p2p_plan.md +533 -0
- data/p2p_roadmap.md +299 -0
- data/registry_plan.md +1818 -0
- metadata +93 -30
- data/docs/README.md +0 -57
- data/docs/diagrams/agent_registry_processes.dot +0 -42
- data/docs/diagrams/agent_registry_processes.png +0 -0
- data/docs/diagrams/high_level_architecture.dot +0 -26
- data/docs/diagrams/high_level_architecture.png +0 -0
- data/docs/diagrams/request_flow.dot +0 -42
- data/docs/diagrams/request_flow.png +0 -0
- /data/docs/{extending_the_framework.md → advanced-topics/extending-the-framework.md} +0 -0
- /data/docs/{custom_agent_implementation.md → agent-development/custom-agent-implementation.md} +0 -0
- /data/docs/{error_handling_and_logging.md → agent-development/error-handling-and-logging.md} +0 -0
- /data/docs/{schema_definition.md → agent-development/schema-definition.md} +0 -0
- /data/docs/{messaging_system.md → framework-components/messaging-system.md} +0 -0
- /data/docs/{configuration.md → operations/configuration.md} +0 -0
- /data/docs/{preformance_considerations.md → operations/performance-considerations.md} +0 -0
- /data/docs/{security.md → operations/security.md} +0 -0
- /data/docs/{troubleshooting.md → operations/troubleshooting.md} +0 -0
@@ -0,0 +1,268 @@
|
|
1
|
+
# Basic Examples
|
2
|
+
|
3
|
+
This section contains basic examples to help you get started with Agent99.
|
4
|
+
|
5
|
+
## Simple Greeter Agent
|
6
|
+
|
7
|
+
Here's a basic server agent that responds to greeting requests:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
require 'agent99'
|
11
|
+
require 'simple_json_schema_builder'
|
12
|
+
|
13
|
+
# Define the request schema
|
14
|
+
class GreeterRequest < SimpleJsonSchemaBuilder::Base
|
15
|
+
object do
|
16
|
+
object :header, schema: Agent99::HeaderSchema
|
17
|
+
string :name, required: true, examples: ["World"]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Create the agent
|
22
|
+
class GreeterAgent < Agent99::Base
|
23
|
+
def info
|
24
|
+
{
|
25
|
+
name: self.class.to_s,
|
26
|
+
type: :server,
|
27
|
+
capabilities: ['greeter', 'hello_world'],
|
28
|
+
request_schema: GreeterRequest.schema
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_request(payload)
|
33
|
+
name = payload.dig(:name) || "World"
|
34
|
+
response = { result: "Hello, #{name}!" }
|
35
|
+
send_response(response)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Run the agent
|
40
|
+
if __FILE__ == $0
|
41
|
+
agent = GreeterAgent.new
|
42
|
+
puts "Starting Greeter Agent..."
|
43
|
+
agent.run
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
## Echo Agent
|
48
|
+
|
49
|
+
An agent that echoes back whatever message it receives:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
require 'agent99'
|
53
|
+
|
54
|
+
class EchoAgent < Agent99::Base
|
55
|
+
def info
|
56
|
+
{
|
57
|
+
name: self.class.to_s,
|
58
|
+
type: :server,
|
59
|
+
capabilities: ['echo', 'mirror']
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_request(payload)
|
64
|
+
logger.info "Echo agent received: #{payload}"
|
65
|
+
send_response(message: "You said: #{payload}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Run the agent
|
70
|
+
if __FILE__ == $0
|
71
|
+
agent = EchoAgent.new
|
72
|
+
puts "Starting Echo Agent..."
|
73
|
+
agent.run
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
## Calculator Agent
|
78
|
+
|
79
|
+
A more complex agent that performs basic arithmetic operations:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
require 'agent99'
|
83
|
+
require 'simple_json_schema_builder'
|
84
|
+
|
85
|
+
class CalculatorRequest < SimpleJsonSchemaBuilder::Base
|
86
|
+
object do
|
87
|
+
object :header, schema: Agent99::HeaderSchema
|
88
|
+
string :operation, enum: %w[add subtract multiply divide], required: true
|
89
|
+
number :a, required: true
|
90
|
+
number :b, required: true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class CalculatorAgent < Agent99::Base
|
95
|
+
def info
|
96
|
+
{
|
97
|
+
name: self.class.to_s,
|
98
|
+
type: :server,
|
99
|
+
capabilities: ['calculator', 'math', 'arithmetic'],
|
100
|
+
request_schema: CalculatorRequest.schema
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def process_request(payload)
|
105
|
+
operation = payload.dig(:operation)
|
106
|
+
a = payload.dig(:a).to_f
|
107
|
+
b = payload.dig(:b).to_f
|
108
|
+
|
109
|
+
result = case operation
|
110
|
+
when 'add'
|
111
|
+
a + b
|
112
|
+
when 'subtract'
|
113
|
+
a - b
|
114
|
+
when 'multiply'
|
115
|
+
a * b
|
116
|
+
when 'divide'
|
117
|
+
return send_error("Division by zero") if b == 0
|
118
|
+
a / b
|
119
|
+
else
|
120
|
+
return send_error("Unknown operation: #{operation}")
|
121
|
+
end
|
122
|
+
|
123
|
+
send_response(result: result, operation: operation, inputs: { a: a, b: b })
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Run the agent
|
128
|
+
if __FILE__ == $0
|
129
|
+
agent = CalculatorAgent.new
|
130
|
+
puts "Starting Calculator Agent..."
|
131
|
+
agent.run
|
132
|
+
end
|
133
|
+
```
|
134
|
+
|
135
|
+
## Client Agent Example
|
136
|
+
|
137
|
+
An agent that makes requests to other agents:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
require 'agent99'
|
141
|
+
|
142
|
+
class ClientAgent < Agent99::Base
|
143
|
+
def info
|
144
|
+
{
|
145
|
+
name: self.class.to_s,
|
146
|
+
type: :client,
|
147
|
+
capabilities: ['client', 'requester']
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def start
|
152
|
+
# Find available agents
|
153
|
+
agents = discover_agents(['greeter'])
|
154
|
+
|
155
|
+
if agents.any?
|
156
|
+
greeter = agents.first
|
157
|
+
logger.info "Found greeter agent: #{greeter[:name]}"
|
158
|
+
|
159
|
+
# Send a request
|
160
|
+
request = {
|
161
|
+
name: "Agent99 Client"
|
162
|
+
}
|
163
|
+
|
164
|
+
response = send_request(greeter[:name], request)
|
165
|
+
logger.info "Received response: #{response}"
|
166
|
+
else
|
167
|
+
logger.warn "No greeter agents found"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Run the client
|
173
|
+
if __FILE__ == $0
|
174
|
+
client = ClientAgent.new
|
175
|
+
puts "Starting Client Agent..."
|
176
|
+
|
177
|
+
# Run once and exit
|
178
|
+
client.start
|
179
|
+
end
|
180
|
+
```
|
181
|
+
|
182
|
+
## Hybrid Agent Example
|
183
|
+
|
184
|
+
An agent that can both serve requests and make requests to other agents:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
require 'agent99'
|
188
|
+
|
189
|
+
class HybridAgent < Agent99::Base
|
190
|
+
def info
|
191
|
+
{
|
192
|
+
name: self.class.to_s,
|
193
|
+
type: :hybrid,
|
194
|
+
capabilities: ['proxy', 'forwarder']
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
def process_request(payload)
|
199
|
+
# This agent forwards requests to other agents
|
200
|
+
target_capability = payload.dig(:forward_to)
|
201
|
+
message = payload.dig(:message)
|
202
|
+
|
203
|
+
if target_capability && message
|
204
|
+
# Find agents with the target capability
|
205
|
+
agents = discover_agents([target_capability])
|
206
|
+
|
207
|
+
if agents.any?
|
208
|
+
target_agent = agents.first
|
209
|
+
|
210
|
+
# Forward the message
|
211
|
+
response = send_request(target_agent[:name], { name: message })
|
212
|
+
send_response(forwarded_response: response, target_agent: target_agent[:name])
|
213
|
+
else
|
214
|
+
send_error("No agents found with capability: #{target_capability}")
|
215
|
+
end
|
216
|
+
else
|
217
|
+
send_error("Missing 'forward_to' or 'message' in request")
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Run the agent
|
223
|
+
if __FILE__ == $0
|
224
|
+
agent = HybridAgent.new
|
225
|
+
puts "Starting Hybrid Agent..."
|
226
|
+
agent.run
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
## Running the Examples
|
231
|
+
|
232
|
+
1. **Start the Registry**:
|
233
|
+
```bash
|
234
|
+
ruby examples/registry.rb
|
235
|
+
```
|
236
|
+
|
237
|
+
2. **Start a Message Broker** (NATS or RabbitMQ):
|
238
|
+
```bash
|
239
|
+
nats-server
|
240
|
+
# OR
|
241
|
+
rabbitmq-server
|
242
|
+
```
|
243
|
+
|
244
|
+
3. **Run the Agents**:
|
245
|
+
```bash
|
246
|
+
# In separate terminals
|
247
|
+
ruby greeter_agent.rb
|
248
|
+
ruby calculator_agent.rb
|
249
|
+
ruby client_agent.rb
|
250
|
+
```
|
251
|
+
|
252
|
+
## Testing Agent Communication
|
253
|
+
|
254
|
+
You can test agent communication using the client example or by sending HTTP requests to the registry:
|
255
|
+
|
256
|
+
```bash
|
257
|
+
# List all registered agents
|
258
|
+
curl http://localhost:4567/agents
|
259
|
+
|
260
|
+
# Find agents by capability
|
261
|
+
curl http://localhost:4567/agents/discover/greeter
|
262
|
+
```
|
263
|
+
|
264
|
+
## Next Steps
|
265
|
+
|
266
|
+
- [Advanced Examples](advanced-examples.md) - More complex agent patterns
|
267
|
+
- [Schema Definition](../agent-development/schema-definition.md) - Learn about request/response schemas
|
268
|
+
- [Message Processing](../framework-components/message-processing.md) - Understand message handling
|
@@ -12,17 +12,21 @@ The agent discovery process involves the following steps:
|
|
12
12
|
2. **Discovery**: When an agent needs to discover other agents, it queries the registry for agents with specific capabilities.
|
13
13
|
3. **Response**: The registry responds with a list of available agents, allowing them to interact based on their capabilities.
|
14
14
|
|
15
|
-
### Declaring Capabilities
|
15
|
+
### Declaring Capabilities in the Info Method
|
16
16
|
|
17
|
-
Each agent must implement the `
|
17
|
+
Each agent must implement the `info` method to declare its capabilities as an array of strings:
|
18
18
|
|
19
19
|
```ruby
|
20
|
-
def
|
21
|
-
|
20
|
+
def info
|
21
|
+
{
|
22
|
+
# ...
|
23
|
+
capabilities: ['process_image', 'face_detection'],
|
24
|
+
# ...
|
25
|
+
}
|
22
26
|
end
|
23
27
|
```
|
24
28
|
|
25
|
-
**Note**: The discovery mechanism is based on an exact match (case insensitive) between the requested capability and the entries in the agent's capabilities array. For example, if an agent declares its capabilities as mentioned above, a discovery request for `'FACE_DETECTION'` will successfully match.
|
29
|
+
**Note**: The discovery mechanism is based on an exact match (case insensitive) between the requested capability and the entries in the agent's capabilities array in its information packet. For example, if an agent declares its capabilities as mentioned above, a discovery request for `'FACE_DETECTION'` will successfully match.
|
26
30
|
|
27
31
|
### Discovery API
|
28
32
|
|
@@ -38,8 +38,14 @@ end
|
|
38
38
|
# Define the new Agent ....
|
39
39
|
|
40
40
|
class MyAgent < Agent99::Base
|
41
|
-
|
42
|
-
|
41
|
+
def info
|
42
|
+
{
|
43
|
+
# ...
|
44
|
+
request_schema: MyAgentRequest.schema
|
45
|
+
capabilities: %w[ greeter hello_world ],
|
46
|
+
# ...
|
47
|
+
}
|
48
|
+
end
|
43
49
|
end
|
44
50
|
|
45
51
|
# You may create multiple instances of the agent if needed
|
@@ -60,7 +66,7 @@ my_agent.id
|
|
60
66
|
|
61
67
|
The image below shows how the `Agent99::Base` class uses dependency injection in its constructor method to bring in an instance of the `RegristryClient` class to provide an interface to the centralized regristery service.
|
62
68
|
|
63
|
-

|
64
70
|
|
65
71
|
The above image also show the other services provided via the RegistryClient class.
|
66
72
|
|
@@ -31,6 +31,8 @@ and validation rules.
|
|
31
31
|
|
32
32
|
### Message Processing Flow
|
33
33
|
|
34
|
+

|
35
|
+
|
34
36
|
#### Request Processing
|
35
37
|
1. Message arrives and is validated against schema
|
36
38
|
2. If validation passes:
|
@@ -42,7 +44,7 @@ and validation rules.
|
|
42
44
|
- Error is logged
|
43
45
|
- Request is not processed further
|
44
46
|
|
45
|
-

|
46
48
|
|
47
49
|
#### Response Processing
|
48
50
|
1. Response message is received
|
@@ -0,0 +1,306 @@
|
|
1
|
+
# Basic Example: Building a Calculator Service
|
2
|
+
|
3
|
+
This detailed example walks you through creating a complete calculator service using Agent99, demonstrating key concepts like schema validation, error handling, and agent discovery.
|
4
|
+
|
5
|
+
## What We'll Build
|
6
|
+
|
7
|
+
A distributed calculator system with:
|
8
|
+
- **Calculator Agent**: Performs arithmetic operations
|
9
|
+
- **Client Agent**: Sends calculation requests
|
10
|
+
- **Request/Response Schemas**: Validates input and output
|
11
|
+
- **Error Handling**: Graceful error management
|
12
|
+
|
13
|
+
## Step 1: Define the Request Schema
|
14
|
+
|
15
|
+
Create `calculator_schemas.rb`:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'simple_json_schema_builder'
|
19
|
+
|
20
|
+
# Request schema for calculator operations
|
21
|
+
class CalculatorRequest < SimpleJsonSchemaBuilder::Base
|
22
|
+
object do
|
23
|
+
object :header, schema: Agent99::HeaderSchema
|
24
|
+
string :operation, enum: %w[add subtract multiply divide], required: true,
|
25
|
+
description: "The arithmetic operation to perform"
|
26
|
+
number :a, required: true, description: "First operand"
|
27
|
+
number :b, required: true, description: "Second operand"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Response schema for calculator results
|
32
|
+
class CalculatorResponse < SimpleJsonSchemaBuilder::Base
|
33
|
+
object do
|
34
|
+
object :header, schema: Agent99::HeaderSchema
|
35
|
+
number :result, required: true, description: "The calculation result"
|
36
|
+
string :operation, required: true, description: "The operation performed"
|
37
|
+
object :operands do
|
38
|
+
number :a, required: true
|
39
|
+
number :b, required: true
|
40
|
+
end
|
41
|
+
string :timestamp, required: true, description: "When the calculation was performed"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
## Step 2: Create the Calculator Agent
|
47
|
+
|
48
|
+
Create `calculator_agent.rb`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
require 'agent99'
|
52
|
+
require_relative 'calculator_schemas'
|
53
|
+
|
54
|
+
class CalculatorAgent < Agent99::Base
|
55
|
+
def info
|
56
|
+
{
|
57
|
+
name: self.class.to_s,
|
58
|
+
type: :server,
|
59
|
+
capabilities: ['calculator', 'math', 'arithmetic'],
|
60
|
+
description: 'Performs basic arithmetic operations',
|
61
|
+
request_schema: CalculatorRequest.schema,
|
62
|
+
response_schema: CalculatorResponse.schema
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_request(payload)
|
67
|
+
logger.info "Received calculation request: #{payload}"
|
68
|
+
|
69
|
+
# Extract and validate inputs
|
70
|
+
operation = payload.dig(:operation)
|
71
|
+
a = payload.dig(:a).to_f
|
72
|
+
b = payload.dig(:b).to_f
|
73
|
+
|
74
|
+
# Perform calculation
|
75
|
+
result = perform_calculation(operation, a, b)
|
76
|
+
|
77
|
+
if result.is_a?(Hash) && result[:error]
|
78
|
+
# Send error response
|
79
|
+
send_error(result[:error], result[:code] || 'CALCULATION_ERROR')
|
80
|
+
else
|
81
|
+
# Send successful response
|
82
|
+
response = {
|
83
|
+
result: result,
|
84
|
+
operation: operation,
|
85
|
+
operands: { a: a, b: b },
|
86
|
+
timestamp: Time.now.iso8601
|
87
|
+
}
|
88
|
+
|
89
|
+
send_response(response)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def perform_calculation(operation, a, b)
|
96
|
+
case operation
|
97
|
+
when 'add'
|
98
|
+
a + b
|
99
|
+
when 'subtract'
|
100
|
+
a - b
|
101
|
+
when 'multiply'
|
102
|
+
a * b
|
103
|
+
when 'divide'
|
104
|
+
return { error: "Division by zero is not allowed", code: 'DIVISION_BY_ZERO' } if b == 0
|
105
|
+
a / b
|
106
|
+
else
|
107
|
+
{ error: "Unknown operation: #{operation}", code: 'INVALID_OPERATION' }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Run the agent if this file is executed directly
|
113
|
+
if __FILE__ == $0
|
114
|
+
puts "🧮 Starting Calculator Agent..."
|
115
|
+
agent = CalculatorAgent.new
|
116
|
+
|
117
|
+
# Handle graceful shutdown
|
118
|
+
trap('INT') do
|
119
|
+
puts "\n👋 Shutting down Calculator Agent..."
|
120
|
+
agent.shutdown
|
121
|
+
exit
|
122
|
+
end
|
123
|
+
|
124
|
+
agent.run
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
## Step 3: Create a Client Agent
|
129
|
+
|
130
|
+
Create `calculator_client.rb`:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
require 'agent99'
|
134
|
+
|
135
|
+
class CalculatorClient < Agent99::Base
|
136
|
+
def info
|
137
|
+
{
|
138
|
+
name: self.class.to_s,
|
139
|
+
type: :client,
|
140
|
+
capabilities: ['calculator_client', 'testing']
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
def perform_calculations
|
145
|
+
# Find calculator agents
|
146
|
+
calculators = discover_agents(['calculator'])
|
147
|
+
|
148
|
+
if calculators.empty?
|
149
|
+
puts "❌ No calculator agents found!"
|
150
|
+
return
|
151
|
+
end
|
152
|
+
|
153
|
+
calculator = calculators.first
|
154
|
+
puts "🎯 Found calculator: #{calculator[:name]}"
|
155
|
+
|
156
|
+
# Test calculations
|
157
|
+
test_cases = [
|
158
|
+
{ operation: 'add', a: 10, b: 5, expected: 15 },
|
159
|
+
{ operation: 'subtract', a: 10, b: 3, expected: 7 },
|
160
|
+
{ operation: 'multiply', a: 4, b: 7, expected: 28 },
|
161
|
+
{ operation: 'divide', a: 20, b: 4, expected: 5 },
|
162
|
+
{ operation: 'divide', a: 10, b: 0, expected: 'ERROR' } # This should error
|
163
|
+
]
|
164
|
+
|
165
|
+
test_cases.each_with_index do |test_case, index|
|
166
|
+
puts "\n📋 Test #{index + 1}: #{test_case[:a]} #{test_case[:operation]} #{test_case[:b]}"
|
167
|
+
|
168
|
+
begin
|
169
|
+
response = send_request(calculator[:name], test_case.reject { |k, _| k == :expected })
|
170
|
+
|
171
|
+
if response[:error]
|
172
|
+
puts "⚠️ Error: #{response[:error]} (#{response[:code]})"
|
173
|
+
puts "✅ Expected error!" if test_case[:expected] == 'ERROR'
|
174
|
+
else
|
175
|
+
result = response[:result]
|
176
|
+
puts "📊 Result: #{result}"
|
177
|
+
|
178
|
+
if test_case[:expected] != 'ERROR'
|
179
|
+
if (result - test_case[:expected]).abs < 0.001 # Handle floating point comparison
|
180
|
+
puts "✅ Test passed!"
|
181
|
+
else
|
182
|
+
puts "❌ Test failed! Expected #{test_case[:expected]}, got #{result}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
rescue => e
|
187
|
+
puts "💥 Request failed: #{e.message}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Run the client if this file is executed directly
|
194
|
+
if __FILE__ == $0
|
195
|
+
puts "📱 Starting Calculator Client..."
|
196
|
+
client = CalculatorClient.new
|
197
|
+
client.perform_calculations
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
## Step 4: Run the Complete Example
|
202
|
+
|
203
|
+
### Terminal 1: Start the Registry
|
204
|
+
```bash
|
205
|
+
# From the agent99 repository
|
206
|
+
ruby examples/registry.rb
|
207
|
+
```
|
208
|
+
|
209
|
+
### Terminal 2: Start NATS (or RabbitMQ)
|
210
|
+
```bash
|
211
|
+
nats-server
|
212
|
+
```
|
213
|
+
|
214
|
+
### Terminal 3: Start the Calculator Agent
|
215
|
+
```bash
|
216
|
+
ruby calculator_agent.rb
|
217
|
+
```
|
218
|
+
|
219
|
+
Expected output:
|
220
|
+
```
|
221
|
+
🧮 Starting Calculator Agent...
|
222
|
+
INFO -- Agent CalculatorAgent registered successfully
|
223
|
+
INFO -- Agent listening for messages...
|
224
|
+
```
|
225
|
+
|
226
|
+
### Terminal 4: Run the Client
|
227
|
+
```bash
|
228
|
+
ruby calculator_client.rb
|
229
|
+
```
|
230
|
+
|
231
|
+
Expected output:
|
232
|
+
```
|
233
|
+
📱 Starting Calculator Client...
|
234
|
+
🎯 Found calculator: CalculatorAgent
|
235
|
+
|
236
|
+
📋 Test 1: 10 add 5
|
237
|
+
📊 Result: 15.0
|
238
|
+
✅ Test passed!
|
239
|
+
|
240
|
+
📋 Test 2: 10 subtract 3
|
241
|
+
📊 Result: 7.0
|
242
|
+
✅ Test passed!
|
243
|
+
|
244
|
+
📋 Test 3: 4 multiply 7
|
245
|
+
📊 Result: 28.0
|
246
|
+
✅ Test passed!
|
247
|
+
|
248
|
+
📋 Test 4: 20 divide 4
|
249
|
+
📊 Result: 5.0
|
250
|
+
✅ Test passed!
|
251
|
+
|
252
|
+
📋 Test 5: 10 divide 0
|
253
|
+
⚠️ Error: Division by zero is not allowed (DIVISION_BY_ZERO)
|
254
|
+
✅ Expected error!
|
255
|
+
```
|
256
|
+
|
257
|
+
## Step 5: Explore the Registry
|
258
|
+
|
259
|
+
While everything is running, visit http://localhost:4567/agents in your browser to see registered agents, or use curl:
|
260
|
+
|
261
|
+
```bash
|
262
|
+
# List all agents
|
263
|
+
curl http://localhost:4567/agents | jq
|
264
|
+
|
265
|
+
# Find calculator agents
|
266
|
+
curl http://localhost:4567/agents/discover/calculator | jq
|
267
|
+
```
|
268
|
+
|
269
|
+
## Key Concepts Demonstrated
|
270
|
+
|
271
|
+
### 🏗️ **Agent Architecture**
|
272
|
+
- **Server Agent**: Waits for and processes requests
|
273
|
+
- **Client Agent**: Discovers and communicates with other agents
|
274
|
+
- **Registry**: Central discovery service
|
275
|
+
|
276
|
+
### 📝 **Schema Validation**
|
277
|
+
- **Request Schema**: Validates incoming requests
|
278
|
+
- **Response Schema**: Defines response structure
|
279
|
+
- **Type Safety**: Ensures data integrity
|
280
|
+
|
281
|
+
### 🛡️ **Error Handling**
|
282
|
+
- **Validation Errors**: Invalid input handling
|
283
|
+
- **Business Logic Errors**: Division by zero, invalid operations
|
284
|
+
- **Network Errors**: Communication failures
|
285
|
+
|
286
|
+
### 🔍 **Agent Discovery**
|
287
|
+
- **Capability-based**: Find agents by what they can do
|
288
|
+
- **Dynamic**: Agents can join and leave at runtime
|
289
|
+
- **Fault-tolerant**: Graceful handling of missing agents
|
290
|
+
|
291
|
+
## What's Next?
|
292
|
+
|
293
|
+
- **[Core Concepts](../core-concepts/what-is-an-agent.md)** - Deep dive into agent theory
|
294
|
+
- **[Schema Definition](../agent-development/schema-definition.md)** - Advanced schema patterns
|
295
|
+
- **[Advanced Examples](../examples/advanced-examples.md)** - Complex multi-agent scenarios
|
296
|
+
- **[Error Handling](../agent-development/error-handling-and-logging.md)** - Comprehensive error strategies
|
297
|
+
|
298
|
+
## Exercises
|
299
|
+
|
300
|
+
Try extending this example:
|
301
|
+
|
302
|
+
1. **Add more operations**: power, square root, factorial
|
303
|
+
2. **Add validation**: check for reasonable input ranges
|
304
|
+
3. **Add persistence**: log all calculations to a file
|
305
|
+
4. **Add monitoring**: track success/failure rates
|
306
|
+
5. **Add authentication**: require API keys for requests
|