activematrix 0.0.5 → 0.0.8

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +96 -28
  3. data/app/jobs/active_matrix/application_job.rb +11 -0
  4. data/app/models/active_matrix/agent/jobs/memory_reaper.rb +87 -0
  5. data/app/models/active_matrix/agent.rb +166 -0
  6. data/app/models/active_matrix/agent_store.rb +80 -0
  7. data/app/models/active_matrix/application_record.rb +15 -0
  8. data/app/models/active_matrix/chat_session.rb +105 -0
  9. data/app/models/active_matrix/knowledge_base.rb +100 -0
  10. data/exe/activematrix +7 -0
  11. data/lib/active_matrix/agent_manager.rb +160 -121
  12. data/lib/active_matrix/agent_registry.rb +25 -21
  13. data/lib/active_matrix/api.rb +8 -2
  14. data/lib/active_matrix/async_query.rb +58 -0
  15. data/lib/active_matrix/bot/base.rb +3 -3
  16. data/lib/active_matrix/bot/builtin_commands.rb +188 -0
  17. data/lib/active_matrix/bot/command_parser.rb +175 -0
  18. data/lib/active_matrix/cli.rb +273 -0
  19. data/lib/active_matrix/client.rb +21 -6
  20. data/lib/active_matrix/client_pool.rb +38 -27
  21. data/lib/active_matrix/daemon/probe_server.rb +118 -0
  22. data/lib/active_matrix/daemon/signal_handler.rb +156 -0
  23. data/lib/active_matrix/daemon/worker.rb +109 -0
  24. data/lib/active_matrix/daemon.rb +236 -0
  25. data/lib/active_matrix/engine.rb +18 -0
  26. data/lib/active_matrix/errors.rb +1 -1
  27. data/lib/active_matrix/event_router.rb +61 -49
  28. data/lib/active_matrix/events.rb +1 -0
  29. data/lib/active_matrix/instrumentation.rb +148 -0
  30. data/lib/active_matrix/memory/agent_memory.rb +7 -21
  31. data/lib/active_matrix/memory/conversation_memory.rb +4 -20
  32. data/lib/active_matrix/memory/global_memory.rb +15 -30
  33. data/lib/active_matrix/message_dispatcher.rb +197 -0
  34. data/lib/active_matrix/metrics.rb +424 -0
  35. data/lib/active_matrix/presence_manager.rb +181 -0
  36. data/lib/active_matrix/railtie.rb +8 -0
  37. data/lib/active_matrix/telemetry.rb +134 -0
  38. data/lib/active_matrix/version.rb +1 -1
  39. data/lib/active_matrix.rb +18 -11
  40. data/lib/generators/active_matrix/install/install_generator.rb +3 -22
  41. data/lib/generators/active_matrix/install/templates/README +5 -2
  42. metadata +191 -31
  43. data/lib/generators/active_matrix/install/templates/agent_memory.rb +0 -47
  44. data/lib/generators/active_matrix/install/templates/conversation_context.rb +0 -72
  45. data/lib/generators/active_matrix/install/templates/create_agent_memories.rb +0 -17
  46. data/lib/generators/active_matrix/install/templates/create_conversation_contexts.rb +0 -21
  47. data/lib/generators/active_matrix/install/templates/create_global_memories.rb +0 -20
  48. data/lib/generators/active_matrix/install/templates/create_matrix_agents.rb +0 -26
  49. data/lib/generators/active_matrix/install/templates/global_memory.rb +0 -70
  50. data/lib/generators/active_matrix/install/templates/matrix_agent.rb +0 -127
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module ActiveMatrix
6
+ # Manages Matrix presence for agents
7
+ # Provides automatic presence updates, wake hour awareness, and graceful shutdown
8
+ #
9
+ # @example Basic usage
10
+ # presence = ActiveMatrix::PresenceManager.new(api: api, user_id: '@bot:example.com')
11
+ # presence.start
12
+ # presence.set_online(status_msg: 'Ready to help')
13
+ # # ... later
14
+ # presence.stop
15
+ #
16
+ # @example With wake hours
17
+ # presence = ActiveMatrix::PresenceManager.new(
18
+ # api: api,
19
+ # user_id: '@bot:example.com',
20
+ # wake_hour: 6,
21
+ # sleep_hour: 22
22
+ # )
23
+ # presence.start # Will auto-set unavailable outside 6:00-22:00
24
+ #
25
+ class PresenceManager
26
+ include Instrumentation
27
+
28
+ attr_reader :user_id, :current_status, :current_message
29
+
30
+ # @param api [ActiveMatrix::Api] Matrix API instance
31
+ # @param user_id [String] The user ID to manage presence for
32
+ # @param refresh_interval [Integer] Seconds between presence refreshes (default: 300)
33
+ # @param wake_hour [Integer, nil] Hour (0-23) when bot becomes available (optional)
34
+ # @param sleep_hour [Integer, nil] Hour (0-23) when bot becomes unavailable (optional)
35
+ # @param timezone [String] Timezone for wake/sleep hours (default: system timezone)
36
+ def initialize(api:, user_id:, refresh_interval: 300, wake_hour: nil, sleep_hour: nil, timezone: nil)
37
+ @api = api
38
+ @user_id = user_id
39
+ @refresh_interval = refresh_interval
40
+ @wake_hour = wake_hour
41
+ @sleep_hour = sleep_hour
42
+ @timezone = timezone
43
+
44
+ @current_status = 'offline'
45
+ @current_message = nil
46
+ @running = Concurrent::AtomicBoolean.new(false)
47
+ @task = nil
48
+ @mutex = Mutex.new
49
+ end
50
+
51
+ # Start the presence manager
52
+ # Begins periodic presence updates
53
+ def start
54
+ return if @running.true?
55
+
56
+ @running.make_true
57
+ schedule_refresh
58
+ ActiveMatrix.logger.info("PresenceManager started for #{@user_id}")
59
+ end
60
+
61
+ # Stop the presence manager
62
+ # Sets presence to offline and stops refresh loop
63
+ def stop
64
+ return unless @running.true?
65
+
66
+ @running.make_false
67
+ @task&.cancel
68
+ @task = nil
69
+
70
+ # Set offline on shutdown
71
+ set_offline
72
+ ActiveMatrix.logger.info("PresenceManager stopped for #{@user_id}")
73
+ end
74
+
75
+ # Set presence to online
76
+ #
77
+ # @param status_msg [String, nil] Optional status message
78
+ def set_online(status_msg: nil)
79
+ set_presence('online', status_msg)
80
+ end
81
+
82
+ # Set presence to unavailable
83
+ #
84
+ # @param status_msg [String, nil] Optional status message
85
+ def set_unavailable(status_msg: nil)
86
+ set_presence('unavailable', status_msg)
87
+ end
88
+
89
+ # Set presence to offline
90
+ def set_offline
91
+ set_presence('offline', nil)
92
+ end
93
+
94
+ # Check if currently within wake hours
95
+ #
96
+ # @return [Boolean] true if within wake hours or no wake hours configured
97
+ def within_wake_hours?
98
+ return true if @wake_hour.nil? || @sleep_hour.nil?
99
+
100
+ current_hour = current_time.hour
101
+
102
+ if @wake_hour < @sleep_hour
103
+ # Normal case: wake 6, sleep 22 -> active from 6:00 to 21:59
104
+ current_hour >= @wake_hour && current_hour < @sleep_hour
105
+ else
106
+ # Overnight case: wake 22, sleep 6 -> active from 22:00 to 5:59
107
+ current_hour >= @wake_hour || current_hour < @sleep_hour
108
+ end
109
+ end
110
+
111
+ # Get current presence status from server
112
+ #
113
+ # @return [Hash] Presence status including :presence and :status_msg
114
+ def get_status
115
+ instrument_operation(:get_presence, user_id: @user_id) do
116
+ @api.get_presence_status(@user_id)
117
+ end
118
+ rescue StandardError => e
119
+ ActiveMatrix.logger.warn("Failed to get presence for #{@user_id}: #{e.message}")
120
+ { presence: @current_status, status_msg: @current_message }
121
+ end
122
+
123
+ private
124
+
125
+ def agent_id
126
+ @user_id
127
+ end
128
+
129
+ def set_presence(status, message)
130
+ @mutex.synchronize do
131
+ # Check wake hours before setting online
132
+ actual_status = if status == 'online' && !within_wake_hours?
133
+ 'unavailable'
134
+ else
135
+ status
136
+ end
137
+
138
+ instrument_operation(:set_presence, user_id: @user_id, status: actual_status) do
139
+ @api.set_presence_status(@user_id, actual_status, message: message)
140
+ end
141
+
142
+ @current_status = actual_status
143
+ @current_message = message
144
+
145
+ ActiveMatrix.logger.debug("Presence set to #{actual_status} for #{@user_id}")
146
+ end
147
+ rescue StandardError => e
148
+ ActiveMatrix.logger.error("Failed to set presence for #{@user_id}: #{e.message}")
149
+ end
150
+
151
+ def schedule_refresh
152
+ return unless @running.true?
153
+
154
+ @task = Concurrent::ScheduledTask.execute(@refresh_interval) do
155
+ refresh_presence
156
+ schedule_refresh
157
+ end
158
+ end
159
+
160
+ def refresh_presence
161
+ return unless @running.true?
162
+
163
+ # Re-check wake hours and update if needed
164
+ if within_wake_hours?
165
+ set_presence(@current_status == 'offline' ? 'online' : @current_status, @current_message)
166
+ else
167
+ set_presence('unavailable', @current_message)
168
+ end
169
+ rescue StandardError => e
170
+ ActiveMatrix.logger.error("Error refreshing presence: #{e.message}")
171
+ end
172
+
173
+ def current_time
174
+ if @timezone && defined?(ActiveSupport::TimeZone)
175
+ ActiveSupport::TimeZone[@timezone]&.now || Time.current
176
+ else
177
+ Time.current
178
+ end
179
+ end
180
+ end
181
+ end
@@ -4,9 +4,17 @@ require 'rails/railtie'
4
4
 
5
5
  module ActiveMatrix
6
6
  class Railtie < Rails::Railtie
7
+ Rails.logger.debug 'ActiveMatrix::Railtie: Loading...'
8
+
7
9
  initializer 'activematrix.configure_rails_initialization' do
10
+ Rails.logger.debug 'ActiveMatrix::Railtie: Initializer running'
8
11
  # Configure Rails.logger as the default logger
9
12
  ActiveMatrix.logger = Rails.logger
13
+ Rails.logger.debug 'ActiveMatrix::Railtie: Logger configured'
14
+
15
+ # Debug autoload paths
16
+ Rails.logger.debug { "ActiveMatrix::Railtie: Autoload paths = #{Rails.application.config.autoload_paths}" }
17
+ Rails.logger.debug { "ActiveMatrix::Railtie: Eager load paths = #{Rails.application.config.eager_load_paths}" }
10
18
  end
11
19
  end
12
20
  end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveMatrix
4
+ # OpenTelemetry integration for ActiveMatrix
5
+ #
6
+ # Provides distributed tracing for:
7
+ # - Agent lifecycle (connect, disconnect, error)
8
+ # - Message handling
9
+ # - Room operations
10
+ # - Matrix API calls
11
+ #
12
+ # @example Enable with OTLP exporter
13
+ # ENV['OTEL_TRACES_EXPORTER'] = 'otlp'
14
+ # ENV['OTEL_SERVICE_NAME'] = 'activematrix'
15
+ # ActiveMatrix::Telemetry.configure!
16
+ #
17
+ # @example Enable with console exporter for debugging
18
+ # ActiveMatrix::Telemetry.configure!(exporter: :console)
19
+ #
20
+ module Telemetry
21
+ TRACER_NAME = 'activematrix'
22
+ TRACER_VERSION = ActiveMatrix::VERSION
23
+
24
+ class << self
25
+ # @return [Boolean] whether OpenTelemetry is available
26
+ def available?
27
+ @available ||= begin
28
+ require 'opentelemetry/sdk'
29
+ true
30
+ rescue LoadError
31
+ false
32
+ end
33
+ end
34
+
35
+ # @return [Boolean] whether telemetry has been configured
36
+ def configured?
37
+ @configured ||= false
38
+ end
39
+
40
+ # Configure OpenTelemetry SDK for ActiveMatrix
41
+ #
42
+ # @param exporter [Symbol, nil] :console, :otlp, or nil for env-based config
43
+ # @param service_name [String] service name for traces
44
+ def configure!(exporter: nil, service_name: 'activematrix')
45
+ return false unless available?
46
+ return true if configured?
47
+
48
+ require 'opentelemetry/sdk'
49
+
50
+ case exporter
51
+ when :console
52
+ require 'opentelemetry/sdk'
53
+ OpenTelemetry::SDK.configure do |c|
54
+ c.service_name = service_name
55
+ c.add_span_processor(
56
+ OpenTelemetry::SDK::Trace::Export::SimpleSpanProcessor.new(
57
+ OpenTelemetry::SDK::Trace::Export::ConsoleSpanExporter.new
58
+ )
59
+ )
60
+ end
61
+ when :otlp
62
+ require 'opentelemetry/exporter/otlp'
63
+ ENV['OTEL_SERVICE_NAME'] ||= service_name
64
+ OpenTelemetry::SDK.configure
65
+ else
66
+ # Use environment variables
67
+ ENV['OTEL_SERVICE_NAME'] ||= service_name
68
+ OpenTelemetry::SDK.configure
69
+ end
70
+
71
+ @configured = true
72
+ end
73
+
74
+ # Get the ActiveMatrix tracer
75
+ #
76
+ # @return [OpenTelemetry::Trace::Tracer, NullTracer]
77
+ def tracer
78
+ return NullTracer.instance unless configured?
79
+
80
+ OpenTelemetry.tracer_provider.tracer(TRACER_NAME, TRACER_VERSION)
81
+ end
82
+
83
+ # Trace a block of code
84
+ #
85
+ # @param name [String] span name
86
+ # @param attributes [Hash] span attributes
87
+ # @yield [span] the current span
88
+ def trace(name, attributes: {}, kind: :internal, &)
89
+ return yield(NullSpan.instance) unless configured?
90
+
91
+ tracer.in_span(name, attributes: attributes, kind: kind, &)
92
+ end
93
+
94
+ # Record an exception on the current span
95
+ #
96
+ # @param exception [Exception]
97
+ # @param attributes [Hash] additional attributes
98
+ def record_exception(exception, attributes: {})
99
+ return unless configured?
100
+
101
+ span = OpenTelemetry::Trace.current_span
102
+ span.record_exception(exception, attributes: attributes)
103
+ span.status = OpenTelemetry::Trace::Status.error(exception.message)
104
+ end
105
+
106
+ # Shutdown the tracer provider
107
+ def shutdown
108
+ return unless configured?
109
+
110
+ OpenTelemetry.tracer_provider.shutdown
111
+ @configured = false
112
+ end
113
+ end
114
+
115
+ # Null tracer for when OTel is not available
116
+ class NullTracer
117
+ include Singleton
118
+
119
+ def in_span(_name, **_opts)
120
+ yield NullSpan.instance
121
+ end
122
+ end
123
+
124
+ # Null span for when OTel is not available
125
+ class NullSpan
126
+ include Singleton
127
+
128
+ def set_attribute(_key, _value); end
129
+ def add_event(_name, **_opts); end
130
+ def record_exception(_exception, **_opts); end
131
+ def status=(_status); end
132
+ end
133
+ end
134
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveMatrix
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.8'
5
5
  end
data/lib/active_matrix.rb CHANGED
@@ -5,12 +5,9 @@ require_relative 'active_matrix/version'
5
5
 
6
6
  require 'json'
7
7
  require 'zeitwerk'
8
- require 'active_support'
9
- require 'active_support/core_ext/integer/time'
10
- require 'active_support/core_ext/time/calculations'
11
- require 'active_support/core_ext/time/zones'
12
- require 'active_support/core_ext/hash/keys'
13
- require 'active_support/core_ext/object/blank'
8
+ require 'active_record'
9
+ require 'active_job'
10
+ require 'state_machines-activerecord'
14
11
 
15
12
  module ActiveMatrix
16
13
  # Configuration
@@ -31,7 +28,9 @@ module ActiveMatrix
31
28
  :conversation_stale_after, :memory_cleanup_interval,
32
29
  :event_queue_size, :event_processing_timeout,
33
30
  :max_clients_per_homeserver, :client_idle_timeout,
34
- :agent_log_level, :log_agent_events
31
+ :agent_log_level, :log_agent_events,
32
+ # Daemon settings
33
+ :daemon_workers, :probe_port, :probe_host, :shutdown_timeout
35
34
 
36
35
  def initialize
37
36
  # Set defaults
@@ -47,6 +46,11 @@ module ActiveMatrix
47
46
  @client_idle_timeout = 300 # 5 minutes
48
47
  @agent_log_level = :info
49
48
  @log_agent_events = false
49
+ # Daemon defaults
50
+ @daemon_workers = 1
51
+ @probe_port = 3042
52
+ @probe_host = '127.0.0.1'
53
+ @shutdown_timeout = 30
50
54
  end
51
55
  end
52
56
 
@@ -82,11 +86,14 @@ module ActiveMatrix
82
86
  # Ignore directories and files that shouldn't be autoloaded
83
87
  Loader.ignore("#{__dir__}/generators")
84
88
  Loader.ignore("#{__dir__}/activematrix.rb")
85
-
86
- # Ignore files that don't follow Zeitwerk naming conventions
89
+
90
+ # Ignore files that don't follow Zeitwerk naming conventions or are standalone
87
91
  Loader.ignore("#{__dir__}/active_matrix/errors.rb")
88
92
  Loader.ignore("#{__dir__}/active_matrix/events.rb")
89
93
  Loader.ignore("#{__dir__}/active_matrix/uri_module.rb")
94
+ Loader.ignore("#{__dir__}/active_matrix/cli.rb")
95
+ Loader.ignore("#{__dir__}/active_matrix/daemon.rb")
96
+ Loader.ignore("#{__dir__}/active_matrix/daemon")
90
97
 
91
98
  # Configure inflections for special cases
92
99
  Loader.inflector.inflect(
@@ -107,6 +114,6 @@ module ActiveMatrix
107
114
  require_relative 'active_matrix/events'
108
115
  require_relative 'active_matrix/uri_module'
109
116
 
110
- # Load Railtie for Rails integration
111
- require 'active_matrix/railtie' if defined?(Rails::Railtie)
117
+ # Load Engine for Rails integration
118
+ require 'active_matrix/engine'
112
119
  end
@@ -1,44 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails/generators'
4
- require 'rails/generators/active_record'
5
4
 
6
5
  module ActiveMatrix
7
6
  module Generators
8
7
  class InstallGenerator < Rails::Generators::Base
9
- include ActiveRecord::Generators::Migration
10
-
11
8
  source_root File.expand_path('templates', __dir__)
12
9
 
13
- desc 'Creates ActiveMatrix migrations and initializers'
10
+ desc 'Installs ActiveMatrix initializer and copies migrations'
14
11
 
15
- def create_migrations
16
- migration_template 'create_matrix_agents.rb', 'db/migrate/create_matrix_agents.rb'
17
- migration_template 'create_agent_memories.rb', 'db/migrate/create_agent_memories.rb'
18
- migration_template 'create_conversation_contexts.rb', 'db/migrate/create_conversation_contexts.rb'
19
- migration_template 'create_global_memories.rb', 'db/migrate/create_global_memories.rb'
12
+ def copy_migrations
13
+ rails_command 'active_matrix:install:migrations', inline: true
20
14
  end
21
15
 
22
16
  def create_initializer
23
17
  template 'active_matrix.rb', 'config/initializers/active_matrix.rb'
24
18
  end
25
19
 
26
- def create_models
27
- template 'matrix_agent.rb', 'app/models/matrix_agent.rb'
28
- template 'agent_memory.rb', 'app/models/agent_memory.rb'
29
- template 'conversation_context.rb', 'app/models/conversation_context.rb'
30
- template 'global_memory.rb', 'app/models/global_memory.rb'
31
- end
32
-
33
20
  def display_post_install
34
21
  readme 'README' if behavior == :invoke
35
22
  end
36
-
37
- private
38
-
39
- def migration_version
40
- "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
41
- end
42
23
  end
43
24
  end
44
25
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  ActiveMatrix Multi-Agent System has been installed!
4
4
 
5
+ Requirements:
6
+ - PostgreSQL 18+ (for UUIDv7 support)
7
+
5
8
  Next steps:
6
9
 
7
10
  1. Run migrations:
@@ -11,7 +14,7 @@ Next steps:
11
14
  rails generate active_matrix:bot MyFirstBot
12
15
 
13
16
  3. Configure your agents in the Rails console:
14
- agent = MatrixAgent.create!(
17
+ agent = ActiveMatrix::Agent.create!(
15
18
  name: 'captain',
16
19
  homeserver: 'https://matrix.org',
17
20
  username: 'captain_bot',
@@ -25,6 +28,6 @@ Next steps:
25
28
  Or start individual agents:
26
29
  ActiveMatrix::AgentManager.instance.start_agent(agent)
27
30
 
28
- For more information, visit: https://github.com/seuros/agent_smith
31
+ For more information, visit: https://github.com/seuros/activematrix
29
32
 
30
33
  ===============================================================================