activematrix 0.0.7 → 0.0.9

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -28
  3. data/app/models/active_matrix/agent.rb +36 -1
  4. data/app/models/active_matrix/agent_store.rb +29 -0
  5. data/app/models/active_matrix/application_record.rb +8 -0
  6. data/app/models/active_matrix/chat_session.rb +29 -0
  7. data/app/models/active_matrix/knowledge_base.rb +26 -0
  8. data/exe/activematrix +7 -0
  9. data/lib/active_matrix/agent_manager.rb +160 -121
  10. data/lib/active_matrix/agent_registry.rb +25 -21
  11. data/lib/active_matrix/api.rb +8 -2
  12. data/lib/active_matrix/async_query.rb +58 -0
  13. data/lib/active_matrix/bot/base.rb +3 -3
  14. data/lib/active_matrix/bot/builtin_commands.rb +188 -0
  15. data/lib/active_matrix/bot/command_parser.rb +175 -0
  16. data/lib/active_matrix/cli.rb +273 -0
  17. data/lib/active_matrix/client.rb +21 -6
  18. data/lib/active_matrix/client_pool.rb +38 -27
  19. data/lib/active_matrix/daemon/probe_server.rb +118 -0
  20. data/lib/active_matrix/daemon/signal_handler.rb +156 -0
  21. data/lib/active_matrix/daemon/worker.rb +109 -0
  22. data/lib/active_matrix/daemon.rb +236 -0
  23. data/lib/active_matrix/engine.rb +7 -3
  24. data/lib/active_matrix/errors.rb +1 -1
  25. data/lib/active_matrix/event_router.rb +61 -49
  26. data/lib/active_matrix/events.rb +1 -0
  27. data/lib/active_matrix/instrumentation.rb +148 -0
  28. data/lib/active_matrix/memory/agent_memory.rb +7 -21
  29. data/lib/active_matrix/memory/conversation_memory.rb +4 -20
  30. data/lib/active_matrix/memory/global_memory.rb +15 -30
  31. data/lib/active_matrix/message_dispatcher.rb +197 -0
  32. data/lib/active_matrix/metrics.rb +424 -0
  33. data/lib/active_matrix/presence_manager.rb +181 -0
  34. data/lib/active_matrix/telemetry.rb +134 -0
  35. data/lib/active_matrix/version.rb +1 -1
  36. data/lib/active_matrix.rb +12 -2
  37. data/lib/generators/active_matrix/install/install_generator.rb +3 -15
  38. data/lib/generators/active_matrix/install/templates/README +5 -2
  39. data/lib/generators/active_matrix/install/templates/active_matrix.yml +32 -0
  40. metadata +142 -45
  41. data/lib/active_matrix/protocols/cs/message_relationships.rb +0 -318
  42. data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +0 -17
  43. data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +0 -21
  44. data/lib/generators/active_matrix/install/templates/create_global_memories.rb +0 -20
  45. data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bf1ad81648f055b2fdb10da688c66ac7267456cea155b0d76bb41864ae6bab5
4
- data.tar.gz: 282f29e6c278cc4ee5b721039e1ec36c2a48b03ba115e02e9beb017b45211706
3
+ metadata.gz: 763fcbfe5b98eb8dcddb9e512f782ce3c8ff994f2581e0b5a18767a52e109515
4
+ data.tar.gz: cf71a8ec3f33ea722e6c3b0217e05585d2c70b07816875f602ad1bf74f054cc8
5
5
  SHA512:
6
- metadata.gz: '08aedb897355151e41f5db2b169d58d0b2842b0c5b7c51a596708ab8ce60be886cb99550f7bb7dfdbf1e71fae918014489d4c48d4271077b930765f6513e28b6'
7
- data.tar.gz: b6a3e216fecc58cf8416bc84980c7e5c17edff2b971a2707ee9f82740c69518b60a77880440522765f4befc8a1fc2fc7bb81df39e8fc120c7ddc4451c4368908
6
+ metadata.gz: e6e3925d13d2708cd4b6e7e1280a3c1bfc275ad8accb353f37b7f9ea73e4886e7d69622c52bd984d307f592cdbd93482707892f5a7e64deed369296ef3b91be2
7
+ data.tar.gz: c61243b17caafb3a3752b1381526ebba8995b0b6033bbc1e84584b4785cc4dcc5acb59bc5a5352ee725380721fc50fbe15ab4b1bfd69f2f2e764a61c627d9d89
data/README.md CHANGED
@@ -2,23 +2,31 @@
2
2
 
3
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
+ ## Requirements
6
+
7
+ - **Ruby 3.4+**
8
+ - **Rails 8.0+**
9
+ - **PostgreSQL 18+** (required for UUIDv7 primary keys)
10
+
5
11
  ## Features
6
12
 
7
- - **Multi-Agent Architecture**: Run multiple bots concurrently with lifecycle management
13
+ - **Multi-Agent Architecture**: Run multiple bots concurrently with async fiber-based lifecycle management
14
+ - **Daemon Binary**: Production-ready `activematrix` daemon with multi-process workers and health probes
8
15
  - **Rails Integration**: Deep integration with ActiveRecord, Rails.cache, and Rails.logger
9
16
  - **State Machines**: state_machines-powered state management for bot lifecycle
10
17
  - **Memory System**: Three-tier memory architecture (agent, conversation, global)
11
18
  - **Event Routing**: Intelligent event distribution to appropriate agents
12
- - **Client Pooling**: Efficient connection management for multiple bots
19
+ - **Client Pooling**: Efficient connection management with async semaphores
13
20
  - **Generators**: Rails generators for quick bot creation
14
21
  - **Inter-Agent Communication**: Built-in messaging between bots
22
+ - **PostgreSQL 18 Features**: UUIDv7 primary keys, JSONB with GIN indexes
15
23
 
16
24
  ## Installation
17
25
 
18
26
  Add this line to your application's Gemfile:
19
27
 
20
28
  ```ruby
21
- gem 'activematrix', '~> 0.0.3'
29
+ gem 'activematrix'
22
30
  ```
23
31
 
24
32
  And then execute:
@@ -43,7 +51,7 @@ This creates a bot class in `app/bots/captain_bot.rb`:
43
51
  class CaptainBot < ActiveMatrix::Bot::MultiInstanceBase
44
52
  set :accept_invites, true
45
53
  set :command_prefix, '!'
46
-
54
+
47
55
  command :status,
48
56
  desc: 'Get system status',
49
57
  args: '[component]' do |component = nil|
@@ -56,7 +64,7 @@ class CaptainBot < ActiveMatrix::Bot::MultiInstanceBase
56
64
  room.send_notice("All systems operational!")
57
65
  end
58
66
  end
59
-
67
+
60
68
  command :deploy,
61
69
  desc: 'Deploy to production',
62
70
  args: 'target' do |target|
@@ -64,17 +72,17 @@ class CaptainBot < ActiveMatrix::Bot::MultiInstanceBase
64
72
  deployments = conversation_memory.remember(:deployments) { [] }
65
73
  deployments << { target: target, time: Time.current }
66
74
  conversation_memory[:deployments] = deployments
67
-
75
+
68
76
  # Notify other agents
69
77
  broadcast_to_agents(:lieutenant, {
70
78
  type: 'deployment',
71
79
  target: target,
72
80
  initiated_by: agent_name
73
81
  })
74
-
82
+
75
83
  room.send_notice("Deploying to #{target}...")
76
84
  end
77
-
85
+
78
86
  # Handle inter-agent messages
79
87
  def receive_message(data, from:)
80
88
  case data[:type]
@@ -90,7 +98,7 @@ end
90
98
 
91
99
  ```ruby
92
100
  # Create agent records in Rails console or seeds
93
- captain = MatrixAgent.create!(
101
+ captain = ActiveMatrix::Agent.create!(
94
102
  name: 'captain',
95
103
  homeserver: 'https://matrix.org',
96
104
  username: 'captain_bot',
@@ -102,8 +110,8 @@ captain = MatrixAgent.create!(
102
110
  }
103
111
  )
104
112
 
105
- lieutenant = MatrixAgent.create!(
106
- name: 'lieutenant',
113
+ lieutenant = ActiveMatrix::Agent.create!(
114
+ name: 'lieutenant',
107
115
  homeserver: 'https://matrix.org',
108
116
  username: 'lieutenant_bot',
109
117
  password: 'secure_password',
@@ -111,10 +119,47 @@ lieutenant = MatrixAgent.create!(
111
119
  )
112
120
  ```
113
121
 
114
- ### Managing Agents
122
+ ### Running the Daemon
123
+
124
+ The `activematrix` binary manages your bots in production, similar to Sidekiq or GoodJob:
125
+
126
+ ```bash
127
+ # Start in foreground
128
+ bundle exec activematrix start
129
+
130
+ # Start with multiple worker processes
131
+ bundle exec activematrix start --workers 3
132
+
133
+ # Start specific agents only
134
+ bundle exec activematrix start --agents captain,lieutenant
135
+
136
+ # Daemonize with PID file
137
+ bundle exec activematrix start --daemon --pidfile tmp/pids/activematrix.pid
138
+
139
+ # Check status (queries health probe)
140
+ bundle exec activematrix status
141
+
142
+ # Graceful shutdown
143
+ bundle exec activematrix stop
144
+
145
+ # Reload agent configuration
146
+ bundle exec activematrix reload
147
+ ```
148
+
149
+ **Health Probes** (for Kubernetes/Docker):
150
+ - `GET /health` - Returns 200 if healthy
151
+ - `GET /status` - JSON with detailed agent status
152
+ - `GET /metrics` - Prometheus-compatible metrics
153
+
154
+ ```bash
155
+ curl http://localhost:3042/health
156
+ curl http://localhost:3042/status
157
+ ```
158
+
159
+ ### Programmatic Agent Management
115
160
 
116
161
  ```ruby
117
- # Start all agents
162
+ # Start all agents (blocks until shutdown)
118
163
  ActiveMatrix::AgentManager.instance.start_all
119
164
 
120
165
  # Start specific agent
@@ -156,7 +201,7 @@ recent = conversation_memory.recent_messages(5)
156
201
  ### Global Memory (Shared)
157
202
  ```ruby
158
203
  # Set global data
159
- global_memory.set('system_status', 'operational',
204
+ global_memory.set('system_status', 'operational',
160
205
  category: 'monitoring',
161
206
  expires_in: 5.minutes,
162
207
  public_read: true
@@ -180,7 +225,7 @@ class MonitorBot < ActiveMatrix::Bot::MultiInstanceBase
180
225
  route event_type: 'm.room.message', priority: 100 do |bot, event|
181
226
  # Custom processing
182
227
  end
183
-
228
+
184
229
  route room_id: '!monitoring:matrix.org' do |bot, event|
185
230
  # Handle all events from monitoring room
186
231
  end
@@ -205,26 +250,40 @@ room.send_text "Hello from ActiveMatrix!"
205
250
  ```ruby
206
251
  # config/initializers/active_matrix.rb
207
252
  ActiveMatrix.configure do |config|
253
+ # Agent settings
208
254
  config.agent_startup_delay = 2.seconds
209
255
  config.max_agents_per_process = 10
210
256
  config.agent_health_check_interval = 30.seconds
257
+
258
+ # Memory settings
211
259
  config.conversation_history_limit = 20
212
260
  config.conversation_stale_after = 1.day
213
261
  config.memory_cleanup_interval = 1.hour
262
+
263
+ # Daemon settings
264
+ config.daemon_workers = 2
265
+ config.probe_port = 3042
266
+ config.probe_host = '0.0.0.0'
267
+ config.shutdown_timeout = 30
214
268
  end
215
269
  ```
216
270
 
217
271
  ## Testing
218
272
 
219
273
  ```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
274
+ # test/bots/captain_bot_test.rb
275
+ class CaptainBotTest < ActiveSupport::TestCase
276
+ def setup
277
+ @agent = ActiveMatrix::Agent.create!(
278
+ name: 'test_captain',
279
+ homeserver: 'https://matrix.org',
280
+ username: 'test_bot',
281
+ bot_class: 'CaptainBot'
282
+ )
283
+ end
284
+
285
+ test 'responds to status command' do
286
+ # Your test logic here
228
287
  end
229
288
  end
230
289
  ```
@@ -233,16 +292,25 @@ end
233
292
 
234
293
  ActiveMatrix implements a sophisticated multi-agent architecture:
235
294
 
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
295
+ - **AgentManager**: Manages lifecycle of all bots using async fibers (start/stop/restart)
296
+ - **AgentRegistry**: Fiber-safe registry of running bot instances
297
+ - **EventRouter**: Routes Matrix events to appropriate bots via async queues
298
+ - **ClientPool**: Manages shared client connections with async semaphores
240
299
  - **Memory System**: Hierarchical storage with caching
241
300
  - **State Machines**: Track agent states (offline/connecting/online/busy/error)
242
301
 
302
+ ### Models
303
+
304
+ All models are namespaced under `ActiveMatrix::`:
305
+
306
+ - `ActiveMatrix::Agent` - Bot agent records with state machine
307
+ - `ActiveMatrix::AgentStore` - Per-agent key-value storage
308
+ - `ActiveMatrix::ChatSession` - Conversation context per user/room
309
+ - `ActiveMatrix::KnowledgeBase` - Global shared storage
310
+
243
311
  ## Contributing
244
312
 
245
- Bug reports and pull requests are welcome on GitHub at https://github.com/seuros/agent_smith
313
+ Bug reports and pull requests are welcome on GitHub at https://github.com/seuros/activematrix
246
314
 
247
315
  ## License
248
316
 
@@ -1,5 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bcrypt'
4
+
5
+ # <rails-lens:schema:begin>
6
+ # table = "active_matrix_agents"
7
+ # database_dialect = "PostgreSQL"
8
+ #
9
+ # columns = [
10
+ # { name = "id", type = "integer", pk = true, null = false },
11
+ # { name = "name", type = "string", null = false },
12
+ # { name = "homeserver", type = "string", null = false },
13
+ # { name = "username", type = "string", null = false },
14
+ # { name = "bot_class", type = "string", null = false },
15
+ # { name = "state", type = "string", null = false, default = "offline" },
16
+ # { name = "access_token", type = "string" },
17
+ # { name = "encrypted_password", type = "string" },
18
+ # { name = "settings", type = "json" },
19
+ # { name = "last_sync_token", type = "string" },
20
+ # { name = "last_active_at", type = "datetime" },
21
+ # { name = "messages_handled", type = "integer", null = false, default = "0" },
22
+ # { name = "created_at", type = "datetime", null = false },
23
+ # { name = "updated_at", type = "datetime", null = false }
24
+ # ]
25
+ #
26
+ # indexes = [
27
+ # { name = "index_active_matrix_agents_on_homeserver", columns = ["homeserver"] },
28
+ # { name = "index_active_matrix_agents_on_name", columns = ["name"], unique = true },
29
+ # { name = "index_active_matrix_agents_on_state", columns = ["state"] }
30
+ # ]
31
+ #
32
+ # [callbacks]
33
+ # before_save = [{ method = "encrypt_password", if = ["password_changed?"] }]
34
+ # around_validation = [{ method = "machine" }]
35
+ #
36
+ # notes = ["agent_stores:INVERSE_OF", "chat_sessions:INVERSE_OF", "agent_stores:N_PLUS_ONE", "chat_sessions:N_PLUS_ONE", "access_token:NOT_NULL", "encrypted_password:NOT_NULL", "settings:NOT_NULL", "name:LIMIT", "homeserver:LIMIT", "username:LIMIT", "bot_class:LIMIT", "state:LIMIT", "access_token:LIMIT", "encrypted_password:LIMIT", "last_sync_token:LIMIT", "username:INDEX", "access_token:INDEX", "last_sync_token:INDEX"]
37
+ # <rails-lens:schema:end>
3
38
  module ActiveMatrix
4
39
  class Agent < ApplicationRecord
5
40
  self.table_name = 'active_matrix_agents'
@@ -41,7 +76,7 @@ module ActiveMatrix
41
76
  end
42
77
 
43
78
  after_transition to: :online_idle do |agent|
44
- agent.update(last_active_at: Time.current)
79
+ agent.update_column(:last_active_at, Time.current)
45
80
  end
46
81
 
47
82
  event :start_processing do
@@ -1,5 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # <rails-lens:schema:begin>
4
+ # table = "active_matrix_agent_stores"
5
+ # database_dialect = "PostgreSQL"
6
+ #
7
+ # columns = [
8
+ # { name = "id", type = "integer", pk = true, null = false },
9
+ # { name = "agent_id", type = "integer", null = false },
10
+ # { name = "key", type = "string", null = false },
11
+ # { name = "value", type = "json" },
12
+ # { name = "expires_at", type = "datetime" },
13
+ # { name = "created_at", type = "datetime", null = false },
14
+ # { name = "updated_at", type = "datetime", null = false }
15
+ # ]
16
+ #
17
+ # indexes = [
18
+ # { name = "index_active_matrix_agent_stores_on_agent_id", columns = ["agent_id"] },
19
+ # { name = "index_active_matrix_agent_stores_on_agent_id_and_key", columns = ["agent_id", "key"], unique = true },
20
+ # { name = "index_active_matrix_agent_stores_on_expires_at", columns = ["expires_at"] }
21
+ # ]
22
+ #
23
+ # foreign_keys = [
24
+ # { column = "agent_id", references_table = "active_matrix_agents", references_column = "id", name = "fk_rails_59b3dc556f" }
25
+ # ]
26
+ #
27
+ # [callbacks]
28
+ # after_commit = [{ method = "schedule_cleanup", if = ["expires_at?"] }]
29
+ #
30
+ # notes = ["agent:INVERSE_OF", "value:NOT_NULL", "key:LIMIT"]
31
+ # <rails-lens:schema:end>
3
32
  module ActiveMatrix
4
33
  class AgentStore < ApplicationRecord
5
34
  self.table_name = 'active_matrix_agent_stores'
@@ -1,5 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # <rails-lens:schema:begin>
4
+ # connection = "primary"
5
+ # database_dialect = "SQLite"
6
+ # database_version = "3.50.4"
7
+ #
8
+ # # This is an abstract class that establishes a database connection
9
+ # # but does not have an associated table.
10
+ # <rails-lens:schema:end>
3
11
  module ActiveMatrix
4
12
  class ApplicationRecord < ActiveRecord::Base
5
13
  self.abstract_class = true
@@ -1,5 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # <rails-lens:schema:begin>
4
+ # table = "active_matrix_chat_sessions"
5
+ # database_dialect = "PostgreSQL"
6
+ #
7
+ # columns = [
8
+ # { name = "id", type = "integer", pk = true, null = false },
9
+ # { name = "agent_id", type = "integer", null = false },
10
+ # { name = "user_id", type = "string", null = false },
11
+ # { name = "room_id", type = "string", null = false },
12
+ # { name = "context", type = "json" },
13
+ # { name = "message_history", type = "json" },
14
+ # { name = "last_message_at", type = "datetime" },
15
+ # { name = "message_count", type = "integer", null = false, default = "0" },
16
+ # { name = "created_at", type = "datetime", null = false },
17
+ # { name = "updated_at", type = "datetime", null = false }
18
+ # ]
19
+ #
20
+ # indexes = [
21
+ # { name = "index_active_matrix_chat_sessions_on_agent_id", columns = ["agent_id"] },
22
+ # { name = "index_active_matrix_chat_sessions_on_last_message_at", columns = ["last_message_at"] },
23
+ # { name = "index_chat_sessions_on_agent_user_room", columns = ["agent_id", "user_id", "room_id"], unique = true }
24
+ # ]
25
+ #
26
+ # foreign_keys = [
27
+ # { column = "agent_id", references_table = "active_matrix_agents", references_column = "id", name = "fk_rails_53457da357" }
28
+ # ]
29
+ #
30
+ # notes = ["agent:INVERSE_OF", "context:NOT_NULL", "message_history:NOT_NULL", "user_id:LIMIT", "room_id:LIMIT"]
31
+ # <rails-lens:schema:end>
3
32
  module ActiveMatrix
4
33
  class ChatSession < ApplicationRecord
5
34
  self.table_name = 'active_matrix_chat_sessions'
@@ -1,5 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # <rails-lens:schema:begin>
4
+ # table = "active_matrix_knowledge_bases"
5
+ # database_dialect = "PostgreSQL"
6
+ #
7
+ # columns = [
8
+ # { name = "id", type = "integer", pk = true, null = false },
9
+ # { name = "key", type = "string", null = false },
10
+ # { name = "value", type = "json" },
11
+ # { name = "category", type = "string" },
12
+ # { name = "expires_at", type = "datetime" },
13
+ # { name = "public_read", type = "boolean", null = false, default = "true" },
14
+ # { name = "public_write", type = "boolean", null = false, default = "false" },
15
+ # { name = "created_at", type = "datetime", null = false },
16
+ # { name = "updated_at", type = "datetime", null = false }
17
+ # ]
18
+ #
19
+ # indexes = [
20
+ # { name = "index_active_matrix_knowledge_bases_on_category", columns = ["category"] },
21
+ # { name = "index_active_matrix_knowledge_bases_on_expires_at", columns = ["expires_at"] },
22
+ # { name = "index_active_matrix_knowledge_bases_on_key", columns = ["key"], unique = true },
23
+ # { name = "index_active_matrix_knowledge_bases_on_public_read", columns = ["public_read"] },
24
+ # { name = "index_active_matrix_knowledge_bases_on_public_write", columns = ["public_write"] }
25
+ # ]
26
+ #
27
+ # notes = ["value:NOT_NULL", "category:NOT_NULL", "key:LIMIT", "category:LIMIT"]
28
+ # <rails-lens:schema:end>
3
29
  module ActiveMatrix
4
30
  class KnowledgeBase < ApplicationRecord
5
31
  self.table_name = 'active_matrix_knowledge_bases'
data/exe/activematrix ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'active_matrix/cli'
6
+
7
+ ActiveMatrix::CLI.start(ARGV)