rails_ai 0.1.0

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 (82) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec_status +96 -0
  3. data/AGENT_GUIDE.md +513 -0
  4. data/Appraisals +49 -0
  5. data/COMMERCIAL_LICENSE_TEMPLATE.md +92 -0
  6. data/FEATURES.md +204 -0
  7. data/LEGAL_PROTECTION_GUIDE.md +222 -0
  8. data/LICENSE +62 -0
  9. data/LICENSE_SUMMARY.md +74 -0
  10. data/MIT-LICENSE +62 -0
  11. data/PERFORMANCE.md +300 -0
  12. data/PROVIDERS.md +495 -0
  13. data/README.md +454 -0
  14. data/Rakefile +11 -0
  15. data/SPEED_OPTIMIZATIONS.md +217 -0
  16. data/STRUCTURE.md +139 -0
  17. data/USAGE_GUIDE.md +288 -0
  18. data/app/channels/ai_stream_channel.rb +33 -0
  19. data/app/components/ai/prompt_component.rb +25 -0
  20. data/app/controllers/concerns/ai/context_aware.rb +77 -0
  21. data/app/controllers/concerns/ai/streaming.rb +41 -0
  22. data/app/helpers/ai_helper.rb +164 -0
  23. data/app/jobs/ai/generate_embedding_job.rb +25 -0
  24. data/app/jobs/ai/generate_summary_job.rb +25 -0
  25. data/app/models/concerns/ai/embeddable.rb +38 -0
  26. data/app/views/rails_ai/dashboard/index.html.erb +51 -0
  27. data/config/routes.rb +19 -0
  28. data/lib/generators/rails_ai/install/install_generator.rb +38 -0
  29. data/lib/rails_ai/agents/agent_manager.rb +258 -0
  30. data/lib/rails_ai/agents/agent_team.rb +243 -0
  31. data/lib/rails_ai/agents/base_agent.rb +331 -0
  32. data/lib/rails_ai/agents/collaboration.rb +238 -0
  33. data/lib/rails_ai/agents/memory.rb +116 -0
  34. data/lib/rails_ai/agents/message_bus.rb +95 -0
  35. data/lib/rails_ai/agents/specialized_agents.rb +391 -0
  36. data/lib/rails_ai/agents/task_queue.rb +111 -0
  37. data/lib/rails_ai/cache.rb +14 -0
  38. data/lib/rails_ai/config.rb +40 -0
  39. data/lib/rails_ai/context.rb +7 -0
  40. data/lib/rails_ai/context_analyzer.rb +86 -0
  41. data/lib/rails_ai/engine.rb +48 -0
  42. data/lib/rails_ai/events.rb +9 -0
  43. data/lib/rails_ai/image_context.rb +110 -0
  44. data/lib/rails_ai/performance.rb +231 -0
  45. data/lib/rails_ai/provider.rb +8 -0
  46. data/lib/rails_ai/providers/anthropic_adapter.rb +256 -0
  47. data/lib/rails_ai/providers/base.rb +60 -0
  48. data/lib/rails_ai/providers/dummy_adapter.rb +29 -0
  49. data/lib/rails_ai/providers/gemini_adapter.rb +509 -0
  50. data/lib/rails_ai/providers/openai_adapter.rb +535 -0
  51. data/lib/rails_ai/providers/secure_anthropic_adapter.rb +206 -0
  52. data/lib/rails_ai/providers/secure_openai_adapter.rb +284 -0
  53. data/lib/rails_ai/railtie.rb +48 -0
  54. data/lib/rails_ai/redactor.rb +12 -0
  55. data/lib/rails_ai/security/api_key_manager.rb +82 -0
  56. data/lib/rails_ai/security/audit_logger.rb +46 -0
  57. data/lib/rails_ai/security/error_handler.rb +62 -0
  58. data/lib/rails_ai/security/input_validator.rb +176 -0
  59. data/lib/rails_ai/security/secure_file_handler.rb +45 -0
  60. data/lib/rails_ai/security/secure_http_client.rb +177 -0
  61. data/lib/rails_ai/security.rb +0 -0
  62. data/lib/rails_ai/version.rb +5 -0
  63. data/lib/rails_ai/window_context.rb +103 -0
  64. data/lib/rails_ai.rb +502 -0
  65. data/monitoring/ci_setup_guide.md +214 -0
  66. data/monitoring/enhanced_monitoring_script.rb +237 -0
  67. data/monitoring/google_alerts_setup.md +42 -0
  68. data/monitoring_log_20250921.txt +0 -0
  69. data/monitoring_script.rb +161 -0
  70. data/rails_ai.gemspec +54 -0
  71. data/scripts/security_scanner.rb +353 -0
  72. data/setup_monitoring.sh +163 -0
  73. data/wiki/API-Documentation.md +734 -0
  74. data/wiki/Architecture-Overview.md +672 -0
  75. data/wiki/Contributing-Guide.md +407 -0
  76. data/wiki/Development-Setup.md +532 -0
  77. data/wiki/Home.md +278 -0
  78. data/wiki/Installation-Guide.md +527 -0
  79. data/wiki/Quick-Start.md +186 -0
  80. data/wiki/README.md +135 -0
  81. data/wiki/Release-Process.md +467 -0
  82. metadata +385 -0
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AiHelper
4
+ # Text-based AI operations
5
+ def ai_chat(prompt, model: nil, **options)
6
+ RailsAi.chat(prompt, model: model, **options)
7
+ end
8
+
9
+ def ai_stream(prompt, model: nil, **options, &block)
10
+ RailsAi.stream(prompt, model: model, **options, &block)
11
+ end
12
+
13
+ def ai_embed(texts, model: nil, **options)
14
+ RailsAi.embed(texts, model: model, **options)
15
+ end
16
+
17
+ # Image generation
18
+ def ai_generate_image(prompt, model: "dall-e-3", size: "1024x1024", **options)
19
+ RailsAi.generate_image(prompt, model: model, size: size, **options)
20
+ end
21
+
22
+ def ai_edit_image(image, prompt, **options)
23
+ RailsAi.edit_image(image, prompt, **options)
24
+ end
25
+
26
+ def ai_create_variation(image, **options)
27
+ RailsAi.create_variation(image, **options)
28
+ end
29
+
30
+ # Video generation
31
+ def ai_generate_video(prompt, model: "sora", duration: 5, **options)
32
+ RailsAi.generate_video(prompt, model: model, duration: duration, **options)
33
+ end
34
+
35
+ def ai_edit_video(video, prompt, **options)
36
+ RailsAi.edit_video(video, prompt, **options)
37
+ end
38
+
39
+ # Audio generation
40
+ def ai_generate_speech(text, voice: "alloy", **options)
41
+ RailsAi.generate_speech(text, voice: voice, **options)
42
+ end
43
+
44
+ def ai_transcribe_audio(audio, **options)
45
+ RailsAi.transcribe_audio(audio, **options)
46
+ end
47
+
48
+ # Multimodal analysis
49
+ def ai_analyze_image(image, prompt, **options)
50
+ RailsAi.analyze_image(image, prompt, **options)
51
+ end
52
+
53
+ def ai_analyze_video(video, prompt, **options)
54
+ RailsAi.analyze_video(video, prompt, **options)
55
+ end
56
+
57
+ # Context-aware AI operations
58
+ def ai_analyze_image_with_context(image, prompt, user_context: {}, window_context: {}, image_context: {}, **options)
59
+ RailsAi.analyze_image_with_context(image, prompt,
60
+ user_context: user_context,
61
+ window_context: window_context,
62
+ image_context: image_context,
63
+ **options
64
+ )
65
+ end
66
+
67
+ def ai_generate_with_context(prompt, user_context: {}, window_context: {}, **options)
68
+ RailsAi.generate_with_context(prompt,
69
+ user_context: user_context,
70
+ window_context: window_context,
71
+ **options
72
+ )
73
+ end
74
+
75
+ def ai_generate_image_with_context(prompt, user_context: {}, window_context: {}, **options)
76
+ RailsAi.generate_image_with_context(prompt,
77
+ user_context: user_context,
78
+ window_context: window_context,
79
+ **options
80
+ )
81
+ end
82
+
83
+ # Convenience methods for common AI tasks
84
+ def ai_summarize(content, model: nil, **options)
85
+ RailsAi.summarize(content, model: model, **options)
86
+ end
87
+
88
+ def ai_translate(content, target_language, **options)
89
+ RailsAi.translate(content, target_language, **options)
90
+ end
91
+
92
+ def ai_classify(content, categories, **options)
93
+ RailsAi.classify(content, categories, **options)
94
+ end
95
+
96
+ def ai_extract_entities(content, **options)
97
+ RailsAi.extract_entities(content, **options)
98
+ end
99
+
100
+ def ai_generate_code(prompt, language: "ruby", **options)
101
+ RailsAi.generate_code(prompt, language: language, **options)
102
+ end
103
+
104
+ def ai_explain_code(code, language: "ruby", **options)
105
+ RailsAi.explain_code(code, language: language, **options)
106
+ end
107
+
108
+ # Context extraction helpers
109
+ def ai_user_context
110
+ return {} unless respond_to?(:current_user) && current_user
111
+
112
+ {
113
+ id: current_user.id,
114
+ email: current_user.respond_to?(:email) ? current_user.email : nil,
115
+ role: current_user.respond_to?(:role) ? current_user.role : 'user',
116
+ created_at: current_user.created_at,
117
+ preferences: extract_user_preferences
118
+ }.compact
119
+ end
120
+
121
+ def ai_window_context
122
+ {
123
+ controller: controller_name,
124
+ action: action_name,
125
+ params: params.except('password', 'password_confirmation', 'token', 'secret', 'key'),
126
+ user_agent: request.user_agent,
127
+ referer: request.referer,
128
+ ip_address: request.remote_ip,
129
+ timestamp: Time.current.iso8601
130
+ }
131
+ end
132
+
133
+ def ai_image_context(image_data)
134
+ RailsAi::ImageContext.new(image_data).to_h
135
+ end
136
+
137
+ # Utility methods
138
+ def ai_stream_id
139
+ @ai_stream_id ||= SecureRandom.uuid
140
+ end
141
+
142
+ def ai_redact(text)
143
+ RailsAi::Redactor.call(text)
144
+ end
145
+
146
+ def ai_safe_content(text)
147
+ RailsAi::Redactor.call(text)
148
+ end
149
+
150
+ private
151
+
152
+ def extract_user_preferences
153
+ return {} unless current_user.respond_to?(:preferences)
154
+
155
+ case current_user.preferences
156
+ when Hash
157
+ current_user.preferences
158
+ when String
159
+ JSON.parse(current_user.preferences) rescue {}
160
+ else
161
+ {}
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ class GenerateEmbeddingJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(text, model: nil, **options)
8
+ model ||= RailsAi.config.default_model
9
+
10
+ embedding = RailsAi.provider.embed!(
11
+ texts: [text],
12
+ model: model,
13
+ **options
14
+ ).first
15
+
16
+ RailsAi::Events.log!(
17
+ kind: :embedding,
18
+ name: "generated",
19
+ payload: {text_length: text.length, model: model}
20
+ )
21
+
22
+ embedding
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ class GenerateSummaryJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(content, model: nil, **options)
8
+ model ||= RailsAi.config.default_model
9
+
10
+ summary = RailsAi.chat(
11
+ "Please provide a concise summary of the following content: #{content}",
12
+ model: model,
13
+ **options
14
+ )
15
+
16
+ RailsAi::Events.log!(
17
+ kind: :summary,
18
+ name: "generated",
19
+ payload: {content_length: content.length, model: model}
20
+ )
21
+
22
+ summary
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ module Embeddable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :ai_embeddings, as: :embeddable, dependent: :destroy
9
+ end
10
+
11
+ def generate_embedding!(field: :content, model: nil)
12
+ text = send(field)
13
+ return if text.blank?
14
+
15
+ model ||= RailsAi.config.default_model
16
+
17
+ embedding = RailsAi.provider.embed!(
18
+ texts: [text],
19
+ model: model
20
+ ).first
21
+
22
+ ai_embeddings.create!(
23
+ content: text,
24
+ embedding: embedding,
25
+ model: model,
26
+ field: field.to_s
27
+ )
28
+ end
29
+
30
+ def similar_records(limit: 5, threshold: 0.8)
31
+ return [] unless ai_embeddings.any?
32
+
33
+ # This would need to be implemented based on your vector database
34
+ # For now, return empty array
35
+ []
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ <%# frozen_string_literal: true %>
2
+
3
+ <div class="rails-ai-dashboard">
4
+ <h1>Rails AI Dashboard</h1>
5
+
6
+ <div class="ai-chat-interface">
7
+ <%= render RailsAi::PromptComponent.new(
8
+ prompt: "Hello, how can I help you today?",
9
+ stream_id: ai_stream_id
10
+ ) %>
11
+ </div>
12
+
13
+ <div class="ai-features">
14
+ <h2>Available Features</h2>
15
+ <ul>
16
+ <li>AI Chat Interface</li>
17
+ <li>Content Summarization</li>
18
+ <li>Embedding Generation</li>
19
+ <li>Streaming Responses</li>
20
+ </ul>
21
+ </div>
22
+
23
+ <div class="ai-config">
24
+ <h2>Configuration</h2>
25
+ <p>Provider: <%= RailsAi.config.provider %></p>
26
+ <p>Model: <%= RailsAi.config.default_model %></p>
27
+ <p>Cache TTL: <%= RailsAi.config.cache_ttl %></p>
28
+ </div>
29
+ </div>
30
+
31
+ <style>
32
+ .rails-ai-dashboard {
33
+ max-width: 800px;
34
+ margin: 0 auto;
35
+ padding: 20px;
36
+ }
37
+
38
+ .ai-chat-interface {
39
+ margin: 20px 0;
40
+ padding: 20px;
41
+ border: 1px solid #ddd;
42
+ border-radius: 8px;
43
+ }
44
+
45
+ .ai-features, .ai-config {
46
+ margin: 20px 0;
47
+ padding: 15px;
48
+ background-color: #f9f9f9;
49
+ border-radius: 8px;
50
+ }
51
+ </style>
data/config/routes.rb ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ RailsAi::Engine.routes.draw do
4
+ root "dashboard#index"
5
+
6
+ resources :streams, only: [:create] do
7
+ member do
8
+ post :stream
9
+ end
10
+ end
11
+
12
+ namespace :api do
13
+ namespace :v1 do
14
+ resources :chat, only: [:create]
15
+ resources :embeddings, only: [:create]
16
+ resources :summarize, only: [:create]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ def create_initializer
9
+ copy_file "initializer.rb", "config/initializers/rails_ai.rb"
10
+ end
11
+
12
+ def create_migrations
13
+ copy_file "ai_migrations.rb", "db/migrate/#{Time.current.strftime("%Y%m%d%H%M%S")}_create_ai_tables.rb"
14
+ end
15
+
16
+ def create_jobs
17
+ copy_file "summarizer_job.rb", "app/jobs/ai/generate_summary_job.rb"
18
+ copy_file "summarizer_service.rb", "app/services/ai/summarizer_service.rb"
19
+ end
20
+
21
+ def create_helpers
22
+ copy_file "ai_helper.rb", "app/helpers/ai_helper.rb"
23
+ end
24
+
25
+ def create_components
26
+ copy_file "ai_widget_component.rb", "app/components/ai/prompt_component.rb"
27
+ end
28
+
29
+ def add_routes
30
+ route 'mount RailsAi::Engine, at: "/rails_ai"'
31
+ end
32
+
33
+ def show_readme
34
+ readme "README" if behavior == :invoke
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsAi
4
+ module Agents
5
+ class AgentManager
6
+ attr_reader :agents, :message_bus, :task_queue
7
+
8
+ def initialize
9
+ @agents = {}
10
+ @message_bus = MessageBus.new
11
+ @task_queue = TaskQueue.new
12
+ @running = false
13
+ @thread_pool = Concurrent::ThreadPoolExecutor.new(
14
+ min_threads: 2,
15
+ max_threads: 10,
16
+ max_queue: 100
17
+ )
18
+ end
19
+
20
+ # Agent lifecycle management
21
+ def register_agent(agent)
22
+ @agents[agent.name] = agent
23
+ @message_bus.subscribe(agent.name, agent)
24
+ defined?(Rails) && Rails.logger && Rails.logger.info("Registered agent: #{agent.name}")
25
+ agent
26
+ end
27
+
28
+ def unregister_agent(agent_name)
29
+ agent = @agents.delete(agent_name)
30
+ return false unless agent
31
+
32
+ @message_bus.unsubscribe(agent_name)
33
+ agent.stop!
34
+ defined?(Rails) && Rails.logger && Rails.logger.info("Unregistered agent: #{agent_name}")
35
+ true
36
+ end
37
+
38
+ def get_agent(agent_name)
39
+ @agents[agent_name]
40
+ end
41
+
42
+ def list_agents
43
+ @agents.values.map(&:status)
44
+ end
45
+
46
+ # Task management
47
+ def submit_task(task)
48
+ @task_queue.enqueue(task)
49
+ defined?(Rails) && Rails.logger && Rails.logger.info("Submitted task: #{task[:description]}")
50
+ task
51
+ end
52
+
53
+ def assign_task_to_agent(task, agent_name)
54
+ agent = get_agent(agent_name)
55
+ return false unless agent
56
+
57
+ agent.assign_task(task)
58
+ end
59
+
60
+ def find_best_agent_for_task(task)
61
+ available_agents = @agents.values.select { |agent| agent.state == :active }
62
+
63
+ return nil if available_agents.empty?
64
+
65
+ # Score agents based on capabilities and current workload
66
+ scored_agents = available_agents.map do |agent|
67
+ score = calculate_agent_score(agent, task)
68
+ { agent: agent, score: score }
69
+ end
70
+
71
+ scored_agents.max_by { |item| item[:score] }&.dig(:agent)
72
+ end
73
+
74
+ def auto_assign_task(task)
75
+ best_agent = find_best_agent_for_task(task)
76
+ return false unless best_agent
77
+
78
+ assign_task_to_agent(task, best_agent.name)
79
+ end
80
+
81
+ # Communication
82
+ def send_message(from_agent, to_agent, message)
83
+ @message_bus.send_message(from_agent, to_agent, message)
84
+ end
85
+
86
+ def broadcast_message(from_agent, message, exclude: [])
87
+ @message_bus.broadcast(from_agent, message, exclude: exclude)
88
+ end
89
+
90
+ # System control
91
+ def start!
92
+ return false if @running
93
+
94
+ @running = true
95
+ start_task_processor
96
+ start_agent_monitor
97
+ defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager started")
98
+ true
99
+ end
100
+
101
+ def stop!
102
+ return false unless @running
103
+
104
+ @running = false
105
+ @thread_pool.shutdown
106
+ @thread_pool.wait_for_termination(30)
107
+
108
+ @agents.each_value(&:stop!)
109
+ defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager stopped")
110
+ true
111
+ end
112
+
113
+ def pause!
114
+ @running = false
115
+ @agents.each_value(&:pause!)
116
+ defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager paused")
117
+ end
118
+
119
+ def resume!
120
+ @running = true
121
+ @agents.each_value(&:resume!)
122
+ start_task_processor
123
+ start_agent_monitor
124
+ defined?(Rails) && Rails.logger && Rails.logger.info("Agent Manager resumed")
125
+ end
126
+
127
+ # Monitoring and health
128
+ def system_status
129
+ {
130
+ running: @running,
131
+ total_agents: @agents.length,
132
+ active_agents: @agents.values.count { |a| a.state == :active },
133
+ paused_agents: @agents.values.count { |a| a.state == :paused },
134
+ stopped_agents: @agents.values.count { |a| a.state == :stopped },
135
+ pending_tasks: @task_queue.size,
136
+ total_tasks_processed: @task_queue.total_processed,
137
+ thread_pool_status: @thread_pool.running?
138
+ }
139
+ end
140
+
141
+ def health_check
142
+ agent_health = @agents.values.map do |agent|
143
+ { name: agent.name, health: agent.health_check }
144
+ end
145
+
146
+ {
147
+ system_healthy: @running && @thread_pool.running?,
148
+ agent_health: agent_health,
149
+ memory_usage: calculate_memory_usage,
150
+ task_queue_healthy: @task_queue.size < 1000
151
+ }
152
+ end
153
+
154
+ # Agent collaboration
155
+ def create_agent_team(team_name, agents, collaboration_strategy: :round_robin)
156
+ team = AgentTeam.new(
157
+ name: team_name,
158
+ agents: agents,
159
+ strategy: collaboration_strategy,
160
+ manager: self
161
+ )
162
+
163
+ team.agents.each { |agent| register_agent(agent) }
164
+ team
165
+ end
166
+
167
+ def orchestrate_collaboration(task, agent_names)
168
+ agents = agent_names.map { |name| get_agent(name) }.compact
169
+ return false if agents.empty?
170
+
171
+ collaboration = Collaboration.new(
172
+ task: task,
173
+ agents: agents,
174
+ manager: self
175
+ )
176
+
177
+ collaboration.start!
178
+ collaboration
179
+ end
180
+
181
+ private
182
+
183
+ def calculate_agent_score(agent, task)
184
+ score = 0
185
+
186
+ # Capability match (40% of score)
187
+ required_capabilities = task[:required_capabilities] || []
188
+ capability_match = required_capabilities.count { |cap| agent.has_capability?(cap) }
189
+ score += (capability_match.to_f / required_capabilities.length * 40) if required_capabilities.any?
190
+
191
+ # Current workload (30% of score)
192
+ workload_score = [0, 30 - (agent.active_tasks.length * 10)].max
193
+ score += workload_score
194
+
195
+ # Memory health (20% of score)
196
+ memory_score = agent.memory.usage_percentage < 80 ? 20 : 10
197
+ score += memory_score
198
+
199
+ # Recent activity (10% of score)
200
+ activity_score = (Time.now - agent.last_activity) < 5 * 60 ? 10 : 5
201
+ score += activity_score
202
+
203
+ score
204
+ end
205
+
206
+ def calculate_memory_usage
207
+ total_memory = @agents.values.sum { |agent| agent.memory.size }
208
+ total_capacity = @agents.values.sum { |agent| agent.memory.instance_variable_get(:@max_size) }
209
+
210
+ return 0 if total_capacity.zero?
211
+
212
+ (total_memory.to_f / total_capacity * 100).round(2)
213
+ end
214
+
215
+ def start_task_processor
216
+ @thread_pool.post do
217
+ while @running
218
+ begin
219
+ task = @task_queue.dequeue(timeout: 1)
220
+ next unless task
221
+
222
+ best_agent = find_best_agent_for_task(task)
223
+ if best_agent
224
+ assign_task_to_agent(task, best_agent.name)
225
+ else
226
+ # No available agent, re-queue task
227
+ @task_queue.enqueue(task, priority: :high)
228
+ sleep(5) # Wait before retrying
229
+ end
230
+ rescue => e
231
+ defined?(Rails) && Rails.logger && Rails.logger.error("Task processor error: #{e.message}")
232
+ sleep(1)
233
+ end
234
+ end
235
+ end
236
+ end
237
+
238
+ def start_agent_monitor
239
+ @thread_pool.post do
240
+ while @running
241
+ begin
242
+ @agents.each_value do |agent|
243
+ health = agent.health_check
244
+ unless health.values.all?
245
+ defined?(Rails) && Rails.logger && Rails.logger.warn("Agent #{agent.name} health issues: #{health}")
246
+ end
247
+ end
248
+ sleep(30) # Check every 30 seconds
249
+ rescue => e
250
+ defined?(Rails) && Rails.logger && Rails.logger.error("Agent monitor error: #{e.message}")
251
+ sleep(5)
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end