activematrix 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +218 -51
- data/lib/active_matrix/agent_manager.rb +275 -0
- data/lib/active_matrix/agent_registry.rb +154 -0
- data/lib/active_matrix/bot/multi_instance_base.rb +189 -0
- data/lib/active_matrix/client.rb +5 -15
- data/lib/active_matrix/client_pool.rb +194 -0
- data/lib/active_matrix/event_router.rb +215 -0
- data/lib/active_matrix/memory/agent_memory.rb +128 -0
- data/lib/active_matrix/memory/base.rb +101 -0
- data/lib/active_matrix/memory/conversation_memory.rb +161 -0
- data/lib/active_matrix/memory/global_memory.rb +153 -0
- data/lib/active_matrix/memory.rb +28 -0
- data/lib/active_matrix/room.rb +131 -51
- data/lib/active_matrix/rooms/space.rb +1 -5
- data/lib/active_matrix/user.rb +10 -0
- data/lib/active_matrix/util/account_data_cache.rb +62 -24
- data/lib/active_matrix/util/cacheable.rb +73 -0
- data/lib/active_matrix/util/extensions.rb +4 -0
- data/lib/active_matrix/util/state_event_cache.rb +106 -31
- data/lib/active_matrix/version.rb +1 -1
- data/lib/active_matrix.rb +53 -2
- data/lib/generators/active_matrix/bot/bot_generator.rb +38 -0
- data/lib/generators/active_matrix/bot/templates/bot.rb.erb +111 -0
- data/lib/generators/active_matrix/bot/templates/bot_spec.rb.erb +68 -0
- data/lib/generators/active_matrix/install/install_generator.rb +44 -0
- data/lib/generators/active_matrix/install/templates/README +30 -0
- data/lib/generators/active_matrix/install/templates/active_matrix.rb +33 -0
- data/lib/generators/active_matrix/install/templates/agent_memory.rb +47 -0
- data/lib/generators/active_matrix/install/templates/conversation_context.rb +72 -0
- data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +17 -0
- data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +21 -0
- data/lib/generators/active_matrix/install/templates/create_global_memories.rb +20 -0
- data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +26 -0
- data/lib/generators/active_matrix/install/templates/global_memory.rb +70 -0
- data/lib/generators/active_matrix/install/templates/matrix_agent.rb +127 -0
- metadata +110 -4
- data/lib/active_matrix/util/rails_cache_adapter.rb +0 -37
- data/lib/active_matrix/util/tinycache.rb +0 -145
- data/lib/active_matrix/util/tinycache_adapter.rb +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1f24e67640b140aef30d970e2ca17366a860914fa34dedb4f6879b84259fb43
|
4
|
+
data.tar.gz: ab62b7257d5bbb8c29738005ffd2443e96f5837a7a07f0b7263a1c9942bad6ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 01c30a4813a0ba61fc39ce2dd920dce535ca0de65154f48e635598268ad9480857376363b63441203c7e0d6abe62af7b2580ea74dff8b5853a3a5afc4712381c
|
7
|
+
data.tar.gz: 96f2c51f3f6b70df680ca90a8f120abe15675f341c706e99286e728d910c9db8fd4c84a53880cfe178cc794d0472ab3bfca856e2a8caa6c1d3112767dd63b1d4
|
data/README.md
CHANGED
@@ -1,82 +1,249 @@
|
|
1
1
|
# ActiveMatrix
|
2
2
|
|
3
|
-
A Rails
|
3
|
+
A Rails-native Matrix SDK for building multi-agent bot systems and real-time communication features. This gem is a fork of the [matrix-sdk](https://github.com/ananace/ruby-matrix-sdk) gem, extensively enhanced with Rails integration, multi-agent architecture, and persistent state management.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Features
|
6
6
|
|
7
|
-
|
7
|
+
- **Multi-Agent Architecture**: Run multiple bots concurrently with lifecycle management
|
8
|
+
- **Rails Integration**: Deep integration with ActiveRecord, Rails.cache, and Rails.logger
|
9
|
+
- **State Machines**: state_machines-powered state management for bot lifecycle
|
10
|
+
- **Memory System**: Three-tier memory architecture (agent, conversation, global)
|
11
|
+
- **Event Routing**: Intelligent event distribution to appropriate agents
|
12
|
+
- **Client Pooling**: Efficient connection management for multiple bots
|
13
|
+
- **Generators**: Rails generators for quick bot creation
|
14
|
+
- **Inter-Agent Communication**: Built-in messaging between bots
|
8
15
|
|
9
|
-
##
|
16
|
+
## Installation
|
10
17
|
|
11
|
-
|
18
|
+
Add this line to your application's Gemfile:
|
12
19
|
|
13
20
|
```ruby
|
14
|
-
|
15
|
-
|
21
|
+
gem 'activematrix', '~> 0.0.3'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
```bash
|
27
|
+
$ bundle install
|
28
|
+
$ rails generate active_matrix:install
|
29
|
+
$ rails db:migrate
|
30
|
+
```
|
31
|
+
|
32
|
+
## Multi-Agent System Usage
|
33
|
+
|
34
|
+
### Creating Your First Bot
|
16
35
|
|
17
|
-
|
36
|
+
```bash
|
37
|
+
$ rails generate active_matrix:bot captain
|
38
|
+
```
|
18
39
|
|
19
|
-
|
20
|
-
api.whoami?
|
21
|
-
# => {:user_id=>"@example:matrix.org"}
|
40
|
+
This creates a bot class in `app/bots/captain_bot.rb`:
|
22
41
|
|
23
|
-
|
24
|
-
|
25
|
-
|
42
|
+
```ruby
|
43
|
+
class CaptainBot < ActiveMatrix::Bot::MultiInstanceBase
|
44
|
+
set :accept_invites, true
|
45
|
+
set :command_prefix, '!'
|
46
|
+
|
47
|
+
command :status,
|
48
|
+
desc: 'Get system status',
|
49
|
+
args: '[component]' do |component = nil|
|
50
|
+
if component
|
51
|
+
# Check specific component
|
52
|
+
status = memory.get("status_#{component}") || 'unknown'
|
53
|
+
room.send_notice("#{component}: #{status}")
|
54
|
+
else
|
55
|
+
# Overall status
|
56
|
+
room.send_notice("All systems operational!")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
command :deploy,
|
61
|
+
desc: 'Deploy to production',
|
62
|
+
args: 'target' do |target|
|
63
|
+
# Use conversation memory to track deployments
|
64
|
+
deployments = conversation_memory.remember(:deployments) { [] }
|
65
|
+
deployments << { target: target, time: Time.current }
|
66
|
+
conversation_memory[:deployments] = deployments
|
67
|
+
|
68
|
+
# Notify other agents
|
69
|
+
broadcast_to_agents(:lieutenant, {
|
70
|
+
type: 'deployment',
|
71
|
+
target: target,
|
72
|
+
initiated_by: agent_name
|
73
|
+
})
|
74
|
+
|
75
|
+
room.send_notice("Deploying to #{target}...")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Handle inter-agent messages
|
79
|
+
def receive_message(data, from:)
|
80
|
+
case data[:type]
|
81
|
+
when 'status_report'
|
82
|
+
memory.set("status_#{data[:component]}", data[:status])
|
83
|
+
logger.info "Received status update from #{from.agent_name}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
26
87
|
```
|
27
88
|
|
89
|
+
### Setting Up Agents
|
90
|
+
|
28
91
|
```ruby
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
92
|
+
# Create agent records in Rails console or seeds
|
93
|
+
captain = MatrixAgent.create!(
|
94
|
+
name: 'captain',
|
95
|
+
homeserver: 'https://matrix.org',
|
96
|
+
username: 'captain_bot',
|
97
|
+
password: 'secure_password',
|
98
|
+
bot_class: 'CaptainBot',
|
99
|
+
settings: {
|
100
|
+
rooms_to_join: ['!warroom:matrix.org'],
|
101
|
+
command_prefix: '!'
|
102
|
+
}
|
103
|
+
)
|
104
|
+
|
105
|
+
lieutenant = MatrixAgent.create!(
|
106
|
+
name: 'lieutenant',
|
107
|
+
homeserver: 'https://matrix.org',
|
108
|
+
username: 'lieutenant_bot',
|
109
|
+
password: 'secure_password',
|
110
|
+
bot_class: 'LieutenantBot'
|
111
|
+
)
|
43
112
|
```
|
44
113
|
|
114
|
+
### Managing Agents
|
115
|
+
|
45
116
|
```ruby
|
46
|
-
#
|
47
|
-
|
117
|
+
# Start all agents
|
118
|
+
ActiveMatrix::AgentManager.instance.start_all
|
119
|
+
|
120
|
+
# Start specific agent
|
121
|
+
ActiveMatrix::AgentManager.instance.start_agent(captain)
|
48
122
|
|
49
|
-
|
50
|
-
|
123
|
+
# Check status
|
124
|
+
ActiveMatrix::AgentManager.instance.status
|
125
|
+
# => { running: 2, agents: [...], monitor_active: true }
|
51
126
|
|
52
|
-
#
|
53
|
-
|
54
|
-
# => 0
|
127
|
+
# Stop agent
|
128
|
+
ActiveMatrix::AgentManager.instance.stop_agent(captain)
|
55
129
|
|
56
|
-
|
57
|
-
|
58
|
-
# => 5
|
130
|
+
# Restart agent
|
131
|
+
ActiveMatrix::AgentManager.instance.restart_agent(captain)
|
59
132
|
```
|
60
133
|
|
134
|
+
## Memory System
|
135
|
+
|
136
|
+
### Agent Memory (Private)
|
137
|
+
```ruby
|
138
|
+
# In your bot
|
139
|
+
memory.set('last_deployment', Time.current)
|
140
|
+
memory.get('last_deployment')
|
141
|
+
memory.increment('deployment_count')
|
142
|
+
memory.remember('config') { load_config_from_api }
|
143
|
+
```
|
144
|
+
|
145
|
+
### Conversation Memory (Per User/Room)
|
61
146
|
```ruby
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
147
|
+
# Automatically available in commands
|
148
|
+
conversation_memory[:last_command] = 'deploy'
|
149
|
+
context = conversation_context # Hash of conversation data
|
150
|
+
|
151
|
+
# Track message history
|
152
|
+
conversation_memory.add_message(event)
|
153
|
+
recent = conversation_memory.recent_messages(5)
|
154
|
+
```
|
155
|
+
|
156
|
+
### Global Memory (Shared)
|
157
|
+
```ruby
|
158
|
+
# Set global data
|
159
|
+
global_memory.set('system_status', 'operational',
|
160
|
+
category: 'monitoring',
|
161
|
+
expires_in: 5.minutes,
|
162
|
+
public_read: true
|
163
|
+
)
|
164
|
+
|
165
|
+
# Broadcast to all agents
|
166
|
+
global_memory.broadcast('alert', {
|
167
|
+
level: 'warning',
|
168
|
+
message: 'High CPU usage detected'
|
169
|
+
})
|
170
|
+
|
171
|
+
# Share between specific agents
|
172
|
+
global_memory.share('secret_key', 'value', ['captain', 'lieutenant'])
|
173
|
+
```
|
174
|
+
|
175
|
+
## Event Routing
|
176
|
+
|
177
|
+
```ruby
|
178
|
+
class MonitorBot < ActiveMatrix::Bot::MultiInstanceBase
|
179
|
+
# Route specific events to this bot
|
180
|
+
route event_type: 'm.room.message', priority: 100 do |bot, event|
|
181
|
+
# Custom processing
|
182
|
+
end
|
183
|
+
|
184
|
+
route room_id: '!monitoring:matrix.org' do |bot, event|
|
185
|
+
# Handle all events from monitoring room
|
186
|
+
end
|
71
187
|
end
|
72
188
|
```
|
73
189
|
|
74
|
-
##
|
190
|
+
## Basic Client Usage
|
191
|
+
|
192
|
+
For simple, single-bot applications:
|
75
193
|
|
76
|
-
|
194
|
+
```ruby
|
195
|
+
# Traditional client usage still works
|
196
|
+
client = ActiveMatrix::Client.new 'https://matrix.org'
|
197
|
+
client.login 'username', 'password'
|
77
198
|
|
199
|
+
room = client.find_room '#matrix:matrix.org'
|
200
|
+
room.send_text "Hello from ActiveMatrix!"
|
201
|
+
```
|
78
202
|
|
79
|
-
##
|
203
|
+
## Configuration
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
# config/initializers/active_matrix.rb
|
207
|
+
ActiveMatrix.configure do |config|
|
208
|
+
config.agent_startup_delay = 2.seconds
|
209
|
+
config.max_agents_per_process = 10
|
210
|
+
config.agent_health_check_interval = 30.seconds
|
211
|
+
config.conversation_history_limit = 20
|
212
|
+
config.conversation_stale_after = 1.day
|
213
|
+
config.memory_cleanup_interval = 1.hour
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
## Testing
|
80
218
|
|
81
|
-
|
219
|
+
```ruby
|
220
|
+
# spec/bots/captain_bot_spec.rb
|
221
|
+
RSpec.describe CaptainBot do
|
222
|
+
let(:agent) { create(:matrix_agent, bot_class: 'CaptainBot') }
|
223
|
+
let(:bot) { described_class.new(agent) }
|
224
|
+
|
225
|
+
it 'responds to status command' do
|
226
|
+
expect(room).to receive(:send_notice).with(/operational/)
|
227
|
+
bot.status
|
228
|
+
end
|
229
|
+
end
|
230
|
+
```
|
231
|
+
|
232
|
+
## Architecture
|
233
|
+
|
234
|
+
ActiveMatrix implements a sophisticated multi-agent architecture:
|
235
|
+
|
236
|
+
- **AgentManager**: Manages lifecycle of all bots (start/stop/restart)
|
237
|
+
- **AgentRegistry**: Thread-safe registry of running bot instances
|
238
|
+
- **EventRouter**: Routes Matrix events to appropriate bots
|
239
|
+
- **ClientPool**: Manages shared client connections efficiently
|
240
|
+
- **Memory System**: Hierarchical storage with caching
|
241
|
+
- **State Machines**: Track agent states (offline/connecting/online/busy/error)
|
242
|
+
|
243
|
+
## Contributing
|
244
|
+
|
245
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/seuros/agent_smith
|
246
|
+
|
247
|
+
## License
|
82
248
|
|
249
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -0,0 +1,275 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module ActiveMatrix
|
6
|
+
# Manages the lifecycle of Matrix bot agents
|
7
|
+
class AgentManager
|
8
|
+
include Singleton
|
9
|
+
include ActiveMatrix::Logging
|
10
|
+
|
11
|
+
attr_reader :registry, :config
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@registry = AgentRegistry.instance
|
15
|
+
@config = ActiveMatrix.config
|
16
|
+
@shutdown = false
|
17
|
+
@monitor_thread = nil
|
18
|
+
|
19
|
+
setup_signal_handlers
|
20
|
+
end
|
21
|
+
|
22
|
+
# Start all agents marked as active in the database
|
23
|
+
def start_all
|
24
|
+
return if @shutdown
|
25
|
+
|
26
|
+
logger.info 'Starting all active agents...'
|
27
|
+
|
28
|
+
agents = defined?(MatrixAgent) ? MatrixAgent.where.not(state: :offline) : []
|
29
|
+
agents.each_with_index do |agent, index|
|
30
|
+
sleep(config.agent_startup_delay || 2) if index.positive?
|
31
|
+
start_agent(agent)
|
32
|
+
end
|
33
|
+
|
34
|
+
start_monitor_thread
|
35
|
+
logger.info "Started #{@registry.count} agents"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Start a specific agent
|
39
|
+
def start_agent(agent)
|
40
|
+
return if @shutdown
|
41
|
+
|
42
|
+
if @registry.running?(agent)
|
43
|
+
logger.warn "Agent #{agent.name} is already running"
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
|
47
|
+
logger.info "Starting agent: #{agent.name}"
|
48
|
+
|
49
|
+
begin
|
50
|
+
# Update state
|
51
|
+
agent.connect!
|
52
|
+
|
53
|
+
# Create bot instance in a new thread
|
54
|
+
thread = Thread.new do
|
55
|
+
Thread.current.name = "agent-#{agent.name}"
|
56
|
+
|
57
|
+
begin
|
58
|
+
# Create client and bot instance
|
59
|
+
client = create_client_for_agent(agent)
|
60
|
+
bot_class = agent.bot_class.constantize
|
61
|
+
bot_instance = bot_class.new(client)
|
62
|
+
|
63
|
+
# Register the agent
|
64
|
+
@registry.register(agent, bot_instance)
|
65
|
+
|
66
|
+
# Authenticate if needed
|
67
|
+
if agent.access_token.present?
|
68
|
+
client.access_token = agent.access_token
|
69
|
+
else
|
70
|
+
client.login(agent.username, agent.password)
|
71
|
+
agent.update(access_token: client.access_token)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Restore sync token if available
|
75
|
+
client.sync_token = agent.last_sync_token if agent.last_sync_token.present?
|
76
|
+
|
77
|
+
# Mark as online
|
78
|
+
agent.connection_established!
|
79
|
+
|
80
|
+
# Start the sync loop
|
81
|
+
client.start_listener_thread
|
82
|
+
client.instance_variable_get(:@sync_thread).join
|
83
|
+
rescue StandardError => e
|
84
|
+
logger.error "Error in agent #{agent.name}: #{e.message}"
|
85
|
+
logger.error e.backtrace.join("\n")
|
86
|
+
agent.encounter_error!
|
87
|
+
raise
|
88
|
+
ensure
|
89
|
+
@registry.unregister(agent)
|
90
|
+
agent.disconnect! if agent.may_disconnect?
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
thread.abort_on_exception = true
|
95
|
+
true
|
96
|
+
rescue StandardError => e
|
97
|
+
logger.error "Failed to start agent #{agent.name}: #{e.message}"
|
98
|
+
agent.encounter_error!
|
99
|
+
false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Stop a specific agent
|
104
|
+
def stop_agent(agent)
|
105
|
+
entry = @registry.get(agent.id)
|
106
|
+
return false unless entry
|
107
|
+
|
108
|
+
logger.info "Stopping agent: #{agent.name}"
|
109
|
+
|
110
|
+
begin
|
111
|
+
# Stop the client sync
|
112
|
+
client = entry[:instance].client
|
113
|
+
client.stop_listener_thread if client.listening?
|
114
|
+
|
115
|
+
# Save sync token
|
116
|
+
agent.update(last_sync_token: client.sync_token) if client.sync_token.present?
|
117
|
+
|
118
|
+
# Kill the thread if still alive
|
119
|
+
thread = entry[:thread]
|
120
|
+
if thread&.alive?
|
121
|
+
thread.kill
|
122
|
+
thread.join(5) # Wait up to 5 seconds
|
123
|
+
end
|
124
|
+
|
125
|
+
# Update state
|
126
|
+
agent.disconnect! if agent.may_disconnect?
|
127
|
+
|
128
|
+
true
|
129
|
+
rescue StandardError => e
|
130
|
+
logger.error "Error stopping agent #{agent.name}: #{e.message}"
|
131
|
+
false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Stop all running agents
|
136
|
+
def stop_all
|
137
|
+
logger.info 'Stopping all agents...'
|
138
|
+
@shutdown = true
|
139
|
+
|
140
|
+
# Stop monitor thread
|
141
|
+
@monitor_thread&.kill
|
142
|
+
|
143
|
+
# Stop all agents
|
144
|
+
@registry.all_records.each do |agent|
|
145
|
+
stop_agent(agent)
|
146
|
+
end
|
147
|
+
|
148
|
+
logger.info 'All agents stopped'
|
149
|
+
end
|
150
|
+
|
151
|
+
# Restart an agent
|
152
|
+
def restart_agent(agent)
|
153
|
+
stop_agent(agent)
|
154
|
+
sleep(1) # Brief pause
|
155
|
+
start_agent(agent)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Pause an agent (keep it registered but stop processing)
|
159
|
+
def pause_agent(agent)
|
160
|
+
return false unless agent.may_pause?
|
161
|
+
|
162
|
+
entry = @registry.get(agent.id)
|
163
|
+
return false unless entry
|
164
|
+
|
165
|
+
logger.info "Pausing agent: #{agent.name}"
|
166
|
+
|
167
|
+
client = entry[:instance].client
|
168
|
+
client.stop_listener_thread if client.listening?
|
169
|
+
agent.pause!
|
170
|
+
|
171
|
+
true
|
172
|
+
end
|
173
|
+
|
174
|
+
# Resume a paused agent
|
175
|
+
def resume_agent(agent)
|
176
|
+
return false unless agent.paused?
|
177
|
+
|
178
|
+
entry = @registry.get(agent.id)
|
179
|
+
return false unless entry
|
180
|
+
|
181
|
+
logger.info "Resuming agent: #{agent.name}"
|
182
|
+
|
183
|
+
agent.resume!
|
184
|
+
client = entry[:instance].client
|
185
|
+
client.start_listener_thread
|
186
|
+
agent.connection_established!
|
187
|
+
|
188
|
+
true
|
189
|
+
end
|
190
|
+
|
191
|
+
# Get status of all agents
|
192
|
+
def status
|
193
|
+
{
|
194
|
+
running: @registry.count,
|
195
|
+
agents: @registry.health_status,
|
196
|
+
monitor_active: @monitor_thread&.alive? || false,
|
197
|
+
shutdown: @shutdown
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def create_client_for_agent(agent)
|
204
|
+
# Use shared client pool if available
|
205
|
+
if defined?(ClientPool)
|
206
|
+
ClientPool.instance.get_client(agent.homeserver)
|
207
|
+
else
|
208
|
+
ActiveMatrix::Client.new(agent.homeserver,
|
209
|
+
client_cache: :some,
|
210
|
+
sync_filter_limit: 20)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def start_monitor_thread
|
215
|
+
return if @monitor_thread&.alive?
|
216
|
+
|
217
|
+
@monitor_thread = Thread.new do
|
218
|
+
Thread.current.name = 'agent-monitor'
|
219
|
+
|
220
|
+
loop do
|
221
|
+
break if @shutdown
|
222
|
+
|
223
|
+
begin
|
224
|
+
check_agent_health
|
225
|
+
cleanup_stale_data
|
226
|
+
rescue StandardError => e
|
227
|
+
logger.error "Monitor thread error: #{e.message}"
|
228
|
+
end
|
229
|
+
|
230
|
+
sleep(config.agent_health_check_interval || 30)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def check_agent_health
|
236
|
+
@registry.all.each do |entry|
|
237
|
+
agent = entry[:record]
|
238
|
+
thread = entry[:thread]
|
239
|
+
|
240
|
+
# Check if thread is alive
|
241
|
+
unless thread&.alive?
|
242
|
+
logger.warn "Agent #{agent.name} thread died, restarting..."
|
243
|
+
@registry.unregister(agent)
|
244
|
+
agent.encounter_error!
|
245
|
+
start_agent(agent) unless @shutdown
|
246
|
+
next
|
247
|
+
end
|
248
|
+
|
249
|
+
# Check last activity
|
250
|
+
if agent.last_active_at && agent.last_active_at < 5.minutes.ago
|
251
|
+
logger.warn "Agent #{agent.name} seems inactive"
|
252
|
+
# Could implement additional health checks here
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def cleanup_stale_data
|
258
|
+
# Clean up old conversation contexts
|
259
|
+
ConversationContext.cleanup_stale! if defined?(ConversationContext)
|
260
|
+
|
261
|
+
# Clean up expired memories
|
262
|
+
AgentMemory.cleanup_expired! if defined?(AgentMemory)
|
263
|
+
GlobalMemory.cleanup_expired! if defined?(GlobalMemory)
|
264
|
+
end
|
265
|
+
|
266
|
+
def setup_signal_handlers
|
267
|
+
%w[INT TERM].each do |signal|
|
268
|
+
Signal.trap(signal) do
|
269
|
+
Thread.new { stop_all }.join
|
270
|
+
exit
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|