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
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'singleton'
4
- require 'concurrent'
4
+ require 'async'
5
+ require 'async/queue'
5
6
 
6
7
  module ActiveMatrix
7
- # Routes Matrix events to appropriate agents
8
+ # Routes Matrix events to appropriate agents using async fibers
8
9
  class EventRouter
9
10
  include Singleton
10
11
  include ActiveMatrix::Logging
11
12
 
12
13
  def initialize
13
- @routes = Concurrent::Array.new
14
- @event_queue = Queue.new
14
+ @routes = []
15
+ @mutex = Mutex.new
16
+ @event_queue = nil
15
17
  @processing = false
16
- @worker_thread = nil
18
+ @worker_task = nil
17
19
  end
18
20
 
19
21
  # Register an event route
@@ -28,8 +30,10 @@ module ActiveMatrix
28
30
  handler: block
29
31
  }
30
32
 
31
- @routes << route
32
- @routes.sort_by! { |r| -r[:priority] } # Higher priority first
33
+ @mutex.synchronize do
34
+ @routes << route
35
+ @routes.sort_by! { |r| -r[:priority] } # Higher priority first
36
+ end
33
37
 
34
38
  logger.debug "Registered route: #{route.except(:handler).inspect}"
35
39
  route[:id]
@@ -37,29 +41,35 @@ module ActiveMatrix
37
41
 
38
42
  # Unregister a route
39
43
  def unregister_route(route_id)
40
- @routes.delete_if { |route| route[:id] == route_id }
44
+ @mutex.synchronize do
45
+ @routes.delete_if { |route| route[:id] == route_id }
46
+ end
41
47
  end
42
48
 
43
49
  # Clear all routes for an agent
44
50
  def clear_agent_routes(agent_id)
45
- @routes.delete_if { |route| route[:agent_id] == agent_id }
51
+ @mutex.synchronize do
52
+ @routes.delete_if { |route| route[:agent_id] == agent_id }
53
+ end
46
54
  end
47
55
 
48
56
  # Route an event to appropriate agents
49
57
  def route_event(event)
50
- return unless @processing
58
+ return unless @processing && @event_queue
51
59
 
52
60
  # Queue the event for processing
53
- @event_queue << event
61
+ @event_queue.enqueue(event)
54
62
  end
55
63
 
56
- # Start the event router
64
+ # Start the event router (call from within async context)
57
65
  def start
58
66
  return if @processing
59
67
 
60
68
  @processing = true
61
- @worker_thread = Thread.new do
62
- Thread.current.name = 'event-router'
69
+ @event_queue = Async::Queue.new
70
+
71
+ @worker_task = Async(transient: true) do |task|
72
+ task.annotate 'event-router'
63
73
  process_events
64
74
  end
65
75
 
@@ -69,20 +79,22 @@ module ActiveMatrix
69
79
  # Stop the event router
70
80
  def stop
71
81
  @processing = false
72
- @worker_thread&.kill
73
- @event_queue.clear
82
+ @worker_task&.stop
83
+ @event_queue = nil
74
84
 
75
85
  logger.info 'Event router stopped'
76
86
  end
77
87
 
78
88
  # Check if router is running
79
89
  def running?
80
- @processing && @worker_thread&.alive?
90
+ @processing && @worker_task&.alive?
81
91
  end
82
92
 
83
93
  # Get routes for debugging
84
94
  def routes_summary
85
- @routes.map { |r| r.except(:handler) }
95
+ @mutex.synchronize do
96
+ @routes.map { |r| r.except(:handler) }
97
+ end
86
98
  end
87
99
 
88
100
  # Broadcast an event to all agents
@@ -99,9 +111,7 @@ module ActiveMatrix
99
111
  def process_events
100
112
  while @processing
101
113
  begin
102
- # Wait for event with timeout
103
- event = nil
104
- Timeout.timeout(1) { event = @event_queue.pop }
114
+ event = @event_queue.dequeue
105
115
 
106
116
  next unless event
107
117
 
@@ -113,36 +123,40 @@ module ActiveMatrix
113
123
  next
114
124
  end
115
125
 
116
- # Process routes in priority order
126
+ # Process routes in priority order (each in its own fiber)
117
127
  matching_routes.each do |route|
118
- process_route(route, event)
128
+ Async do
129
+ process_route(route, event)
130
+ end
119
131
  end
120
- rescue Timeout::Error
121
- # Normal timeout, continue loop
132
+ rescue Async::Stop
133
+ break
122
134
  rescue StandardError => e
123
135
  logger.error "Event router error: #{e.message}"
124
- logger.error e.backtrace.join("\n")
136
+ logger.error e.backtrace.first(10).join("\n")
125
137
  end
126
138
  end
127
139
  end
128
140
 
129
141
  def find_matching_routes(event)
130
- @routes.select do |route|
131
- # Check room match
132
- next false if route[:room_id] && route[:room_id] != event[:room_id]
142
+ @mutex.synchronize do
143
+ @routes.select do |route|
144
+ # Check room match
145
+ next false if route[:room_id] && route[:room_id] != event[:room_id]
133
146
 
134
- # Check event type match
135
- next false if route[:event_type] && route[:event_type] != event[:type]
147
+ # Check event type match
148
+ next false if route[:event_type] && route[:event_type] != event[:type]
136
149
 
137
- # Check user match
138
- next false if route[:user_id] && route[:user_id] != event[:sender]
150
+ # Check user match
151
+ next false if route[:user_id] && route[:user_id] != event[:sender]
139
152
 
140
- # Check if agent is running
141
- registry = AgentRegistry.instance
142
- agent_entry = registry.get(route[:agent_id])
143
- next false unless agent_entry
153
+ # Check if agent is running
154
+ registry = AgentRegistry.instance
155
+ agent_entry = registry.get(route[:agent_id])
156
+ next false unless agent_entry
144
157
 
145
- true
158
+ true
159
+ end
146
160
  end
147
161
  end
148
162
 
@@ -154,18 +168,16 @@ module ActiveMatrix
154
168
 
155
169
  bot = agent_entry[:instance]
156
170
 
157
- begin
158
- if route[:handler]
159
- # Custom handler
160
- route[:handler].call(bot, event)
161
- elsif bot.respond_to?(:_handle_event)
162
- # Default handling
163
- bot._handle_event(event)
164
- end
165
- rescue StandardError => e
166
- logger.error "Error processing route for agent #{agent_entry[:record].name}: #{e.message}"
167
- logger.error e.backtrace.first(5).join("\n")
171
+ if route[:handler]
172
+ # Custom handler
173
+ route[:handler].call(bot, event)
174
+ elsif bot.respond_to?(:_handle_event)
175
+ # Default handling
176
+ bot._handle_event(event)
168
177
  end
178
+ rescue StandardError => e
179
+ logger.error "Error processing route for agent #{agent_entry[:record].name}: #{e.message}"
180
+ logger.error e.backtrace.first(5).join("\n")
169
181
  end
170
182
  end
171
183
 
@@ -3,6 +3,7 @@
3
3
  module ActiveMatrix
4
4
  class EventHandlerArray < Hash
5
5
  include ActiveMatrix::Logging
6
+
6
7
  attr_accessor :reraise_exceptions
7
8
 
8
9
  def initialize(*)
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/notifications'
5
+ require 'timeout'
6
+ require 'socket'
7
+ require 'json'
8
+
9
+ module ActiveMatrix
10
+ # Instrumentation module for Matrix bot operations
11
+ # Provides ActiveSupport::Notifications events and structured logging
12
+ #
13
+ # @example Include in a class
14
+ # class MyService
15
+ # include ActiveMatrix::Instrumentation
16
+ #
17
+ # def perform
18
+ # instrument_operation(:my_operation, room_id: '!abc:matrix.org') do
19
+ # # ... operation code
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ module Instrumentation
25
+ extend ActiveSupport::Concern
26
+
27
+ private
28
+
29
+ # Instrument a Matrix bot operation with timing and result tracking
30
+ #
31
+ # @param operation [Symbol, String] Operation name (e.g., :send_message, :sync)
32
+ # @param metadata [Hash] Additional context to include in the event
33
+ # @yield Block to execute and instrument
34
+ # @return [Object] Result of the block
35
+ # @raise [StandardError] Re-raises any exception after logging
36
+ def instrument_operation(operation, **metadata)
37
+ event_data = metadata.merge(
38
+ operation: operation,
39
+ agent_id: respond_to?(:agent_id) ? agent_id : nil,
40
+ component: self.class.name&.demodulize || 'Unknown'
41
+ )
42
+
43
+ ActiveSupport::Notifications.instrument(
44
+ "activematrix.#{operation}",
45
+ event_data
46
+ ) do |payload|
47
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
48
+
49
+ begin
50
+ result = yield
51
+
52
+ payload[:status] = 'success'
53
+ payload[:duration_ms] = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
54
+ payload[:result] = summarize_result(result)
55
+
56
+ log_operation_result(operation, 'SUCCESS', payload)
57
+
58
+ result
59
+ rescue StandardError => e
60
+ payload[:status] = 'error'
61
+ payload[:duration_ms] = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) * 1000).round(2)
62
+ payload[:error_class] = e.class.name
63
+ payload[:error_message] = e.message
64
+ payload[:error_category] = classify_error(e)
65
+
66
+ log_operation_result(operation, 'ERROR', payload)
67
+
68
+ raise
69
+ end
70
+ end
71
+ end
72
+
73
+ # Classify errors for better monitoring and alerting
74
+ #
75
+ # @param error [StandardError] The error to classify
76
+ # @return [String] Error category
77
+ def classify_error(error)
78
+ case error
79
+ when Timeout::Error
80
+ 'timeout'
81
+ when SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH
82
+ 'network'
83
+ when JSON::ParserError
84
+ 'parse'
85
+ when OpenSSL::SSL::SSLError
86
+ 'ssl'
87
+ else
88
+ case error.class.name
89
+ when /ActiveMatrix::Errors::MatrixConnectionError/
90
+ 'matrix_connection'
91
+ when /ActiveMatrix::Errors::MatrixRequestError/
92
+ 'matrix_request'
93
+ when /ActiveMatrix::Errors::MatrixNotAuthorizedError/
94
+ 'matrix_auth'
95
+ when /ActiveMatrix::Errors::MatrixForbiddenError/
96
+ 'matrix_forbidden'
97
+ when /ActiveMatrix::Errors::MatrixNotFoundError/
98
+ 'matrix_not_found'
99
+ when /PG::/
100
+ 'database'
101
+ else
102
+ 'application'
103
+ end
104
+ end
105
+ end
106
+
107
+ # Log operation result with structured data
108
+ #
109
+ # @param operation [Symbol, String] Operation name
110
+ # @param status [String] 'SUCCESS' or 'ERROR'
111
+ # @param data [Hash] Event payload data
112
+ def log_operation_result(operation, status, data)
113
+ component = data[:component] || 'Unknown'
114
+ agent_id = data[:agent_id] || 'unknown'
115
+
116
+ message = "#{operation} - #{status}"
117
+ message += " (#{data[:duration_ms]}ms)" if data[:duration_ms]
118
+ message = "[#{component}][agent:#{agent_id}] #{message}"
119
+
120
+ if status == 'ERROR'
121
+ ActiveMatrix.logger.error("#{message}: #{data[:error_class]} - #{data[:error_message]}")
122
+ else
123
+ ActiveMatrix.logger.debug(message)
124
+ end
125
+ end
126
+
127
+ # Summarize result for logging without exposing sensitive data
128
+ #
129
+ # @param result [Object] The result to summarize
130
+ # @return [String] Human-readable summary
131
+ def summarize_result(result)
132
+ case result
133
+ when String
134
+ result.length > 100 ? "#{result[0...97]}..." : result
135
+ when Numeric, true, false
136
+ result.to_s
137
+ when nil
138
+ 'nil'
139
+ when Hash
140
+ "Hash(#{result.keys.size} keys)"
141
+ when Array
142
+ "Array(#{result.size} items)"
143
+ else
144
+ result.class.name
145
+ end
146
+ end
147
+ end
148
+ end
@@ -14,19 +14,15 @@ module ActiveMatrix
14
14
  # Get a value from agent memory
15
15
  def get(key)
16
16
  fetch_with_cache(key) do
17
- return nil unless defined?(::AgentMemory)
18
-
19
- memory = @agent.agent_memories.active.find_by(key: key)
17
+ memory = @agent.agent_stores.active.find_by(key: key)
20
18
  memory&.value
21
19
  end
22
20
  end
23
21
 
24
22
  # Set a value in agent memory
25
23
  def set(key, value, expires_in: nil)
26
- return false unless defined?(::AgentMemory)
27
-
28
24
  write_through(key, value, expires_in: expires_in) do
29
- memory = @agent.agent_memories.find_or_initialize_by(key: key)
25
+ memory = @agent.agent_stores.find_or_initialize_by(key: key)
30
26
  memory.value = value
31
27
  memory.expires_at = expires_in.present? ? Time.current + expires_in : nil
32
28
  memory.save!
@@ -35,43 +31,33 @@ module ActiveMatrix
35
31
 
36
32
  # Check if a key exists
37
33
  def exists?(key)
38
- return false unless defined?(::AgentMemory)
39
-
40
34
  if @cache_enabled && Rails.cache.exist?(cache_key(key))
41
35
  true
42
36
  else
43
- @agent.agent_memories.active.exists?(key: key)
37
+ @agent.agent_stores.active.exists?(key: key)
44
38
  end
45
39
  end
46
40
 
47
41
  # Delete a key
48
42
  def delete(key)
49
- return false unless defined?(::AgentMemory)
50
-
51
43
  delete_through(key) do
52
- @agent.agent_memories.where(key: key).destroy_all.any?
44
+ @agent.agent_stores.where(key: key).destroy_all.any?
53
45
  end
54
46
  end
55
47
 
56
48
  # Get all keys
57
49
  def keys
58
- return [] unless defined?(::AgentMemory)
59
-
60
- @agent.agent_memories.active.pluck(:key)
50
+ AsyncQuery.async_pluck(@agent.agent_stores.active, :key)
61
51
  end
62
52
 
63
53
  # Get all memory as hash
64
54
  def all
65
- return {} unless defined?(::AgentMemory)
66
-
67
- @agent.agent_memories.active.pluck(:key, :value).to_h
55
+ AsyncQuery.async_pluck(@agent.agent_stores.active, :key, :value).to_h
68
56
  end
69
57
 
70
58
  # Clear all agent memory
71
59
  def clear!
72
- return false unless defined?(::AgentMemory)
73
-
74
- @agent.agent_memories.destroy_all
60
+ @agent.agent_stores.destroy_all
75
61
 
76
62
  # Clear cache entries
77
63
  keys.each { |key| Rails.cache.delete(cache_key(key)) } if @cache_enabled
@@ -16,8 +16,6 @@ module ActiveMatrix
16
16
  # Get conversation context
17
17
  def context
18
18
  fetch_with_cache('context', expires_in: 1.hour) do
19
- return {} unless defined?(::ConversationContext)
20
-
21
19
  record = find_or_create_record
22
20
  record.context
23
21
  end
@@ -25,8 +23,6 @@ module ActiveMatrix
25
23
 
26
24
  # Update conversation context
27
25
  def update_context(data)
28
- return false unless defined?(::ConversationContext)
29
-
30
26
  record = find_or_create_record
31
27
  record.context = record.context.merge(data)
32
28
  record.save!
@@ -38,8 +34,6 @@ module ActiveMatrix
38
34
 
39
35
  # Add a message to history
40
36
  def add_message(event)
41
- return false unless defined?(::ConversationContext)
42
-
43
37
  record = find_or_create_record
44
38
  record.add_message({
45
39
  event_id: event[:event_id],
@@ -60,8 +54,6 @@ module ActiveMatrix
60
54
  # Get recent messages
61
55
  def recent_messages(limit = 10)
62
56
  fetch_with_cache('recent_messages', expires_in: 5.minutes) do
63
- return [] unless defined?(::ConversationContext)
64
-
65
57
  record = find_or_create_record
66
58
  record.recent_messages(limit)
67
59
  end
@@ -69,8 +61,6 @@ module ActiveMatrix
69
61
 
70
62
  # Get last message timestamp
71
63
  def last_message_at
72
- return nil unless defined?(::ConversationContext)
73
-
74
64
  record = conversation_record
75
65
  record&.last_message_at
76
66
  end
@@ -83,8 +73,6 @@ module ActiveMatrix
83
73
 
84
74
  # Clear conversation history but keep context
85
75
  def clear_history!
86
- return false unless defined?(::ConversationContext)
87
-
88
76
  record = conversation_record
89
77
  return false unless record
90
78
 
@@ -131,20 +119,16 @@ module ActiveMatrix
131
119
  end
132
120
 
133
121
  def conversation_record
134
- return nil unless defined?(::ConversationContext)
135
-
136
- ::ConversationContext.find_by(
137
- matrix_agent: @agent,
122
+ ActiveMatrix::ChatSession.find_by(
123
+ agent: @agent,
138
124
  user_id: @user_id,
139
125
  room_id: @room_id
140
126
  )
141
127
  end
142
128
 
143
129
  def find_or_create_record
144
- return nil unless defined?(::ConversationContext)
145
-
146
- ::ConversationContext.find_or_create_by!(
147
- matrix_agent: @agent,
130
+ ActiveMatrix::ChatSession.find_or_create_by!(
131
+ agent: @agent,
148
132
  user_id: @user_id,
149
133
  room_id: @room_id
150
134
  )
@@ -11,74 +11,59 @@ module ActiveMatrix
11
11
  # Get a value from global memory
12
12
  def get(key)
13
13
  fetch_with_cache(key) do
14
- return nil unless defined?(::GlobalMemory)
15
-
16
- ::GlobalMemory.get(key)
14
+ ActiveMatrix::KnowledgeBase.get(key)
17
15
  end
18
16
  end
19
17
 
20
18
  # Set a value in global memory
21
19
  def set(key, value, category: nil, expires_in: nil, public_read: true, public_write: false)
22
- return false unless defined?(::GlobalMemory)
23
-
24
20
  write_through(key, value, expires_in: expires_in) do
25
- ::GlobalMemory.set(key, value,
26
- category: category,
27
- expires_in: expires_in,
28
- public_read: public_read,
29
- public_write: public_write)
21
+ ActiveMatrix::KnowledgeBase.set(key, value,
22
+ category: category,
23
+ expires_in: expires_in,
24
+ public_read: public_read,
25
+ public_write: public_write)
30
26
  end
31
27
  end
32
28
 
33
29
  # Check if a key exists
34
30
  def exists?(key)
35
- return false unless defined?(::GlobalMemory)
36
-
37
31
  if @cache_enabled && Rails.cache.exist?(cache_key(key))
38
32
  true
39
33
  else
40
- ::GlobalMemory.active.exists?(key: key)
34
+ ActiveMatrix::KnowledgeBase.active.exists?(key: key)
41
35
  end
42
36
  end
43
37
 
44
38
  # Delete a key
45
39
  def delete(key)
46
- return false unless defined?(::GlobalMemory)
47
-
48
40
  delete_through(key) do
49
- ::GlobalMemory.where(key: key).destroy_all.any?
41
+ ActiveMatrix::KnowledgeBase.where(key: key).destroy_all.any?
50
42
  end
51
43
  end
52
44
 
53
45
  # Get all keys in a category
54
46
  def keys(category: nil)
55
- return [] unless defined?(::GlobalMemory)
56
-
57
- scope = ::GlobalMemory.active
47
+ scope = ActiveMatrix::KnowledgeBase.active
58
48
  scope = scope.by_category(category) if category
59
- scope.pluck(:key)
49
+ AsyncQuery.async_pluck(scope, :key)
60
50
  end
61
51
 
62
52
  # Get all values in a category
63
53
  def by_category(category)
64
- return {} unless defined?(::GlobalMemory)
65
-
66
- ::GlobalMemory.active.by_category(category).pluck(:key, :value).to_h
54
+ scope = ActiveMatrix::KnowledgeBase.active.by_category(category)
55
+ AsyncQuery.async_pluck(scope, :key, :value).to_h
67
56
  end
68
57
 
69
58
  # Check if readable by agent
70
59
  def readable?(key, agent = nil)
71
- return false unless defined?(::GlobalMemory)
72
-
73
- memory = ::GlobalMemory.find_by(key: key)
60
+ memory = ActiveMatrix::KnowledgeBase.find_by(key: key)
74
61
  memory&.readable_by?(agent)
75
62
  end
76
63
 
77
64
  # Check if writable by agent
78
65
  def writable?(key, agent = nil)
79
- return false unless defined?(::GlobalMemory)
80
-
81
- memory = ::GlobalMemory.find_by(key: key)
66
+ memory = ActiveMatrix::KnowledgeBase.find_by(key: key)
82
67
  memory&.writable_by?(agent)
83
68
  end
84
69
 
@@ -92,7 +77,7 @@ module ActiveMatrix
92
77
  # Set with permission check
93
78
  def set_for_agent(key, value, agent, **)
94
79
  # Allow creating new keys or updating writable ones
95
- memory = ::GlobalMemory.find_by(key: key)
80
+ memory = ActiveMatrix::KnowledgeBase.find_by(key: key)
96
81
  return false if memory && !memory.writable_by?(agent)
97
82
 
98
83
  set(key, value, **)