activematrix 0.0.0 → 0.0.2

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +218 -51
  3. data/lib/active_matrix/agent_manager.rb +275 -0
  4. data/lib/active_matrix/agent_registry.rb +154 -0
  5. data/lib/{matrix_sdk → active_matrix}/api.rb +18 -22
  6. data/lib/{matrix_sdk → active_matrix}/bot/base.rb +42 -39
  7. data/lib/{matrix_sdk → active_matrix}/bot/main.rb +4 -5
  8. data/lib/active_matrix/bot/multi_instance_base.rb +189 -0
  9. data/lib/active_matrix/bot.rb +7 -0
  10. data/lib/{matrix_sdk → active_matrix}/client.rb +21 -34
  11. data/lib/active_matrix/client_pool.rb +194 -0
  12. data/lib/{matrix_sdk → active_matrix}/errors.rb +4 -4
  13. data/lib/active_matrix/event_router.rb +215 -0
  14. data/lib/active_matrix/logging.rb +56 -0
  15. data/lib/active_matrix/memory/agent_memory.rb +128 -0
  16. data/lib/active_matrix/memory/base.rb +101 -0
  17. data/lib/active_matrix/memory/conversation_memory.rb +161 -0
  18. data/lib/active_matrix/memory/global_memory.rb +153 -0
  19. data/lib/active_matrix/memory.rb +28 -0
  20. data/lib/{matrix_sdk → active_matrix}/mxid.rb +2 -2
  21. data/lib/{matrix_sdk → active_matrix}/protocols/as.rb +1 -1
  22. data/lib/{matrix_sdk → active_matrix}/protocols/cs.rb +6 -8
  23. data/lib/{matrix_sdk → active_matrix}/protocols/is.rb +1 -1
  24. data/lib/{matrix_sdk → active_matrix}/protocols/msc.rb +6 -8
  25. data/lib/{matrix_sdk → active_matrix}/protocols/ss.rb +2 -2
  26. data/lib/active_matrix/railtie.rb +18 -0
  27. data/lib/{matrix_sdk → active_matrix}/response.rb +2 -2
  28. data/lib/{matrix_sdk → active_matrix}/room.rb +148 -72
  29. data/lib/{matrix_sdk → active_matrix}/rooms/space.rb +3 -7
  30. data/lib/{matrix_sdk → active_matrix}/user.rb +23 -15
  31. data/lib/active_matrix/util/account_data_cache.rb +129 -0
  32. data/lib/active_matrix/util/cacheable.rb +73 -0
  33. data/lib/{matrix_sdk → active_matrix}/util/events.rb +8 -8
  34. data/lib/{matrix_sdk → active_matrix}/util/extensions.rb +6 -15
  35. data/lib/active_matrix/util/state_event_cache.rb +167 -0
  36. data/lib/{matrix_sdk → active_matrix}/util/uri.rb +4 -4
  37. data/lib/active_matrix/version.rb +5 -0
  38. data/lib/active_matrix.rb +81 -0
  39. data/lib/generators/active_matrix/bot/bot_generator.rb +38 -0
  40. data/lib/generators/active_matrix/bot/templates/bot.rb.erb +111 -0
  41. data/lib/generators/active_matrix/bot/templates/bot_spec.rb.erb +68 -0
  42. data/lib/generators/active_matrix/install/install_generator.rb +44 -0
  43. data/lib/generators/active_matrix/install/templates/README +30 -0
  44. data/lib/generators/active_matrix/install/templates/active_matrix.rb +33 -0
  45. data/lib/generators/active_matrix/install/templates/agent_memory.rb +47 -0
  46. data/lib/generators/active_matrix/install/templates/conversation_context.rb +72 -0
  47. data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +17 -0
  48. data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +21 -0
  49. data/lib/generators/active_matrix/install/templates/create_global_memories.rb +20 -0
  50. data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +26 -0
  51. data/lib/generators/active_matrix/install/templates/global_memory.rb +70 -0
  52. data/lib/generators/active_matrix/install/templates/matrix_agent.rb +127 -0
  53. metadata +168 -30
  54. data/lib/matrix_sdk/bot.rb +0 -4
  55. data/lib/matrix_sdk/util/account_data_cache.rb +0 -91
  56. data/lib/matrix_sdk/util/state_event_cache.rb +0 -92
  57. data/lib/matrix_sdk/util/tinycache.rb +0 -140
  58. data/lib/matrix_sdk/util/tinycache_adapter.rb +0 -87
  59. data/lib/matrix_sdk/version.rb +0 -5
  60. data/lib/matrix_sdk.rb +0 -75
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d14da1f32ce2269f47a0e1124b264a0e2e61c3f19f94ad224b60951200ba55b
4
- data.tar.gz: b12caea772bf4d1df1cbad903caaa888eff93c40dbcda081c53c89b598f69475
3
+ metadata.gz: fa2944820a83b530d113e5b8f0ee837adc88827c49d40e0752a4dbd0102fc7e7
4
+ data.tar.gz: 47aeb5d3c5e4d6979701a3a6eeb0f50f46d678de198453282951a8c53100d8b1
5
5
  SHA512:
6
- metadata.gz: 9f62a5e1eb8bd28688e2b9b029c156271f827ace85d6898ba75fb1b2d47c3185a7aaaadf8ed2a18ef1829ab4a1563a498715f1cbc188b2106f590eb8474b0fe3
7
- data.tar.gz: fc32226c83adadca7cc70b4be9ca50e302e518185e26bd0dade5e91013ec582aaee9f8f40c601bd299384898686373f54da80868ab0a3ee6b805ee9210262947
6
+ metadata.gz: 2fe49c7a1b0544e5188d49b2be2575c1c7d035b8163ed9c0e0d59f75c21eb5ef3d6ae7097a353b7016bd6b36edb4f98491e9fd29a2691906d656c6a32eabd06a
7
+ data.tar.gz: d8e8e6859e01c760cb70585cfa14a7a113ed2c215572dbf92994f451d9c9d02bdded3048ea720362debabbe7e4cd393b88cbe892c040b2c004b1929d3b5ad557
data/README.md CHANGED
@@ -1,82 +1,249 @@
1
1
  # ActiveMatrix
2
2
 
3
- A Rails gem for integrating Matrix protocol communication into Ruby on Rails applications. This gem is a fork of the [matrix-sdk](https://github.com/ananace/ruby-matrix-sdk) gem, enhanced with Rails-specific features and tight integration with ActiveRecord and the Rails framework.
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
- ## About
5
+ ## Features
6
6
 
7
- ActiveMatrix provides a seamless way to add Matrix protocol support to your Rails applications, allowing you to build chat features, real-time messaging, and collaborative tools using the decentralized Matrix network.
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
- ## Example usage
16
+ ## Installation
10
17
 
11
- For more fully-featured examples, check the [examples](examples/) folder.
18
+ Add this line to your application's Gemfile:
12
19
 
13
20
  ```ruby
14
- # Raw API usage
15
- require 'matrix_sdk'
21
+ gem 'activematrix', '~> 0.0.2'
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
- api = MatrixSdk::Api.new 'https://matrix.org'
36
+ ```bash
37
+ $ rails generate active_matrix:bot captain
38
+ ```
18
39
 
19
- api.login user: 'example', password: 'notarealpass'
20
- api.whoami?
21
- # => {:user_id=>"@example:matrix.org"}
40
+ This creates a bot class in `app/bots/captain_bot.rb`:
22
41
 
23
- # It's possible to call arbitrary APIs as well
24
- api.request :get, :federation_v1, '/version'
25
- # => {:server=>{:version=>"0.28.1", :name=>"Synapse"}}
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
- # Client wrapper with login
30
- require 'matrix_sdk'
31
-
32
- client = MatrixSdk::Client.new 'https://example.com'
33
- client.login 'username', 'notarealpass' #, no_sync: true
34
-
35
- client.rooms.count
36
- # => 5
37
- hq = client.find_room '#matrix:matrix.org'
38
- # => #<MatrixSdk::Room:00005592a1161528 @id="!cURbafjkfsMDVwdRDQ:matrix.org" @name="Matrix HQ" @topic="The Official Matrix HQ - please come chat here! | To support Matrix.org development: https://patreon.com/matrixdotorg | Try http://riot.im/app for a glossy web client | Looking for homeserver hosting? Check out https://upcloud.com/matrix!" @canonical_alias="#matrix:matrix.org" @aliases=["#matrix:jda.mn"] @join_rule=:public @guest_access=:can_join @event_history_limit=10>
39
- hq.guest_access?
40
- # => true
41
- hq.send_text "This is an example message - don't actually do this ;)"
42
- # => {:event_id=>"$123457890abcdef:matrix.org"}
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
- # Client wrapper with token
47
- require 'matrix_sdk'
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
- client = MatrixSdk::Client.new 'https://example.com'
50
- client.api.access_token = 'thisisnotarealtoken'
123
+ # Check status
124
+ ActiveMatrix::AgentManager.instance.status
125
+ # => { running: 2, agents: [...], monitor_active: true }
51
126
 
52
- # Doesn't automatically trigger a sync when setting the token directly
53
- client.rooms.count
54
- # => 0
127
+ # Stop agent
128
+ ActiveMatrix::AgentManager.instance.stop_agent(captain)
55
129
 
56
- client.sync
57
- client.rooms.count
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
- #!/bin/env ruby
63
- # Bot DSL
64
- require 'matrix_sdk/bot'
65
-
66
- command :plug do
67
- room.send_text <<~PLUG
68
- The Ruby SDK is a fine method for writing applications communicating over the Matrix protocol.
69
- It can easily be integrated with Rails, and it supports most client/bot use-cases.
70
- PLUG
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
- ## Contributing
190
+ ## Basic Client Usage
191
+
192
+ For simple, single-bot applications:
75
193
 
76
- Bug reports and pull requests are welcome on GitHub at https://github.com/ananace/ruby-matrix-sdk
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
- ## License
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
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
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