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.
- checksums.yaml +7 -0
- data/.rspec_status +96 -0
- data/AGENT_GUIDE.md +513 -0
- data/Appraisals +49 -0
- data/COMMERCIAL_LICENSE_TEMPLATE.md +92 -0
- data/FEATURES.md +204 -0
- data/LEGAL_PROTECTION_GUIDE.md +222 -0
- data/LICENSE +62 -0
- data/LICENSE_SUMMARY.md +74 -0
- data/MIT-LICENSE +62 -0
- data/PERFORMANCE.md +300 -0
- data/PROVIDERS.md +495 -0
- data/README.md +454 -0
- data/Rakefile +11 -0
- data/SPEED_OPTIMIZATIONS.md +217 -0
- data/STRUCTURE.md +139 -0
- data/USAGE_GUIDE.md +288 -0
- data/app/channels/ai_stream_channel.rb +33 -0
- data/app/components/ai/prompt_component.rb +25 -0
- data/app/controllers/concerns/ai/context_aware.rb +77 -0
- data/app/controllers/concerns/ai/streaming.rb +41 -0
- data/app/helpers/ai_helper.rb +164 -0
- data/app/jobs/ai/generate_embedding_job.rb +25 -0
- data/app/jobs/ai/generate_summary_job.rb +25 -0
- data/app/models/concerns/ai/embeddable.rb +38 -0
- data/app/views/rails_ai/dashboard/index.html.erb +51 -0
- data/config/routes.rb +19 -0
- data/lib/generators/rails_ai/install/install_generator.rb +38 -0
- data/lib/rails_ai/agents/agent_manager.rb +258 -0
- data/lib/rails_ai/agents/agent_team.rb +243 -0
- data/lib/rails_ai/agents/base_agent.rb +331 -0
- data/lib/rails_ai/agents/collaboration.rb +238 -0
- data/lib/rails_ai/agents/memory.rb +116 -0
- data/lib/rails_ai/agents/message_bus.rb +95 -0
- data/lib/rails_ai/agents/specialized_agents.rb +391 -0
- data/lib/rails_ai/agents/task_queue.rb +111 -0
- data/lib/rails_ai/cache.rb +14 -0
- data/lib/rails_ai/config.rb +40 -0
- data/lib/rails_ai/context.rb +7 -0
- data/lib/rails_ai/context_analyzer.rb +86 -0
- data/lib/rails_ai/engine.rb +48 -0
- data/lib/rails_ai/events.rb +9 -0
- data/lib/rails_ai/image_context.rb +110 -0
- data/lib/rails_ai/performance.rb +231 -0
- data/lib/rails_ai/provider.rb +8 -0
- data/lib/rails_ai/providers/anthropic_adapter.rb +256 -0
- data/lib/rails_ai/providers/base.rb +60 -0
- data/lib/rails_ai/providers/dummy_adapter.rb +29 -0
- data/lib/rails_ai/providers/gemini_adapter.rb +509 -0
- data/lib/rails_ai/providers/openai_adapter.rb +535 -0
- data/lib/rails_ai/providers/secure_anthropic_adapter.rb +206 -0
- data/lib/rails_ai/providers/secure_openai_adapter.rb +284 -0
- data/lib/rails_ai/railtie.rb +48 -0
- data/lib/rails_ai/redactor.rb +12 -0
- data/lib/rails_ai/security/api_key_manager.rb +82 -0
- data/lib/rails_ai/security/audit_logger.rb +46 -0
- data/lib/rails_ai/security/error_handler.rb +62 -0
- data/lib/rails_ai/security/input_validator.rb +176 -0
- data/lib/rails_ai/security/secure_file_handler.rb +45 -0
- data/lib/rails_ai/security/secure_http_client.rb +177 -0
- data/lib/rails_ai/security.rb +0 -0
- data/lib/rails_ai/version.rb +5 -0
- data/lib/rails_ai/window_context.rb +103 -0
- data/lib/rails_ai.rb +502 -0
- data/monitoring/ci_setup_guide.md +214 -0
- data/monitoring/enhanced_monitoring_script.rb +237 -0
- data/monitoring/google_alerts_setup.md +42 -0
- data/monitoring_log_20250921.txt +0 -0
- data/monitoring_script.rb +161 -0
- data/rails_ai.gemspec +54 -0
- data/scripts/security_scanner.rb +353 -0
- data/setup_monitoring.sh +163 -0
- data/wiki/API-Documentation.md +734 -0
- data/wiki/Architecture-Overview.md +672 -0
- data/wiki/Contributing-Guide.md +407 -0
- data/wiki/Development-Setup.md +532 -0
- data/wiki/Home.md +278 -0
- data/wiki/Installation-Guide.md +527 -0
- data/wiki/Quick-Start.md +186 -0
- data/wiki/README.md +135 -0
- data/wiki/Release-Process.md +467 -0
- metadata +385 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Agents
|
5
|
+
class AgentTeam
|
6
|
+
attr_reader :name, :agents, :strategy, :manager, :created_at
|
7
|
+
|
8
|
+
def initialize(name:, agents:, strategy: :round_robin, manager:)
|
9
|
+
@name = name
|
10
|
+
@agents = Array(agents)
|
11
|
+
@strategy = strategy
|
12
|
+
@manager = manager
|
13
|
+
@created_at = Time.now
|
14
|
+
@current_agent_index = 0
|
15
|
+
@team_memory = {}
|
16
|
+
@collaboration_history = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Team task assignment
|
20
|
+
def assign_task(task)
|
21
|
+
case @strategy
|
22
|
+
when :round_robin
|
23
|
+
assign_round_robin(task)
|
24
|
+
when :capability_based
|
25
|
+
assign_capability_based(task)
|
26
|
+
when :load_balanced
|
27
|
+
assign_load_balanced(task)
|
28
|
+
when :collaborative
|
29
|
+
assign_collaborative(task)
|
30
|
+
else
|
31
|
+
assign_round_robin(task)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def collaborate_on_task(task)
|
36
|
+
return false unless @strategy == :collaborative
|
37
|
+
|
38
|
+
collaboration = {
|
39
|
+
id: SecureRandom.uuid,
|
40
|
+
task: task,
|
41
|
+
agents: @agents.map(&:name),
|
42
|
+
started_at: Time.now,
|
43
|
+
status: :in_progress,
|
44
|
+
contributions: {}
|
45
|
+
}
|
46
|
+
|
47
|
+
# Each agent contributes to the task
|
48
|
+
@agents.each do |agent|
|
49
|
+
begin
|
50
|
+
contribution = agent.collaborate_with(agent, task, context: build_team_context)
|
51
|
+
collaboration[:contributions][agent.name] = contribution
|
52
|
+
rescue => e
|
53
|
+
defined?(Rails) && Rails.logger && Rails.logger.error("Agent #{agent.name} collaboration failed: #{e.message}")
|
54
|
+
collaboration[:contributions][agent.name] = { error: e.message }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
collaboration[:completed_at] = Time.now
|
59
|
+
collaboration[:status] = :completed
|
60
|
+
@collaboration_history << collaboration
|
61
|
+
|
62
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Team #{@name} completed collaborative task: #{task[:description]}")
|
63
|
+
collaboration
|
64
|
+
end
|
65
|
+
|
66
|
+
# Team communication
|
67
|
+
def team_meeting(agenda)
|
68
|
+
meeting = {
|
69
|
+
id: SecureRandom.uuid,
|
70
|
+
agenda: agenda,
|
71
|
+
participants: @agents.map(&:name),
|
72
|
+
started_at: Time.now,
|
73
|
+
discussions: {}
|
74
|
+
}
|
75
|
+
|
76
|
+
# Each agent shares their perspective
|
77
|
+
@agents.each do |agent|
|
78
|
+
perspective = agent.think("Team meeting agenda: #{agenda}. Share your perspective and insights.",
|
79
|
+
context: build_team_context)
|
80
|
+
meeting[:discussions][agent.name] = perspective
|
81
|
+
end
|
82
|
+
|
83
|
+
meeting[:ended_at] = Time.now
|
84
|
+
meeting[:duration] = meeting[:ended_at] - meeting[:started_at]
|
85
|
+
|
86
|
+
# Store meeting results in team memory
|
87
|
+
@team_memory["meeting_#{meeting[:id]}"] = meeting
|
88
|
+
|
89
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Team #{@name} meeting completed: #{agenda}")
|
90
|
+
meeting
|
91
|
+
end
|
92
|
+
|
93
|
+
def share_knowledge(agent_name, knowledge)
|
94
|
+
@team_memory["knowledge_#{Time.now.to_i}"] = {
|
95
|
+
shared_by: agent_name,
|
96
|
+
knowledge: knowledge,
|
97
|
+
shared_at: Time.now
|
98
|
+
}
|
99
|
+
|
100
|
+
# Broadcast knowledge to other agents
|
101
|
+
@manager.broadcast_message(agent_name, {
|
102
|
+
type: :knowledge_share,
|
103
|
+
knowledge: knowledge,
|
104
|
+
shared_by: agent_name
|
105
|
+
}, exclude: [agent_name])
|
106
|
+
|
107
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{agent_name} shared knowledge with team #{@name}")
|
108
|
+
end
|
109
|
+
|
110
|
+
# Team monitoring
|
111
|
+
def team_status
|
112
|
+
{
|
113
|
+
name: @name,
|
114
|
+
strategy: @strategy,
|
115
|
+
agent_count: @agents.length,
|
116
|
+
active_agents: @agents.count { |a| a.state == :active },
|
117
|
+
total_tasks: @agents.sum { |a| a.active_tasks.length },
|
118
|
+
team_memory_size: @team_memory.length,
|
119
|
+
collaboration_count: @collaboration_history.length,
|
120
|
+
created_at: @created_at
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def team_health
|
125
|
+
agent_health = @agents.map do |agent|
|
126
|
+
{ name: agent.name, health: agent.health_check }
|
127
|
+
end
|
128
|
+
|
129
|
+
{
|
130
|
+
overall_health: agent_health.all? { |ah| ah[:health].values.all? },
|
131
|
+
agent_health: agent_health,
|
132
|
+
memory_usage: calculate_team_memory_usage,
|
133
|
+
collaboration_success_rate: calculate_collaboration_success_rate
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
# Team learning
|
138
|
+
def learn_from_experience
|
139
|
+
recent_collaborations = @collaboration_history.last(10)
|
140
|
+
return if recent_collaborations.empty?
|
141
|
+
|
142
|
+
insights = analyze_collaboration_patterns(recent_collaborations)
|
143
|
+
|
144
|
+
# Share insights with all agents
|
145
|
+
@agents.each do |agent|
|
146
|
+
agent.remember("team_insights_#{Time.now.to_i}", insights, importance: :high)
|
147
|
+
end
|
148
|
+
|
149
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Team #{@name} learned from recent collaborations")
|
150
|
+
insights
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def assign_round_robin(task)
|
156
|
+
agent = @agents[@current_agent_index]
|
157
|
+
@current_agent_index = (@current_agent_index + 1) % @agents.length
|
158
|
+
|
159
|
+
agent.assign_task(task)
|
160
|
+
end
|
161
|
+
|
162
|
+
def assign_capability_based(task)
|
163
|
+
required_capabilities = task[:required_capabilities] || []
|
164
|
+
return false if required_capabilities.empty?
|
165
|
+
|
166
|
+
best_agent = @agents
|
167
|
+
.select { |agent| agent.state == :active }
|
168
|
+
.max_by do |agent|
|
169
|
+
required_capabilities.count { |cap| agent.has_capability?(cap) }
|
170
|
+
end
|
171
|
+
|
172
|
+
best_agent&.assign_task(task)
|
173
|
+
end
|
174
|
+
|
175
|
+
def assign_load_balanced(task)
|
176
|
+
best_agent = @agents
|
177
|
+
.select { |agent| agent.state == :active }
|
178
|
+
.min_by { |agent| agent.active_tasks.length }
|
179
|
+
|
180
|
+
best_agent&.assign_task(task)
|
181
|
+
end
|
182
|
+
|
183
|
+
def assign_collaborative(task)
|
184
|
+
# For collaborative tasks, all agents work together
|
185
|
+
collaborate_on_task(task)
|
186
|
+
end
|
187
|
+
|
188
|
+
def build_team_context
|
189
|
+
{
|
190
|
+
team_name: @name,
|
191
|
+
team_strategy: @strategy,
|
192
|
+
team_members: @agents.map { |a| { name: a.name, role: a.role, capabilities: a.capabilities } },
|
193
|
+
team_memory: @team_memory,
|
194
|
+
recent_collaborations: @collaboration_history.last(5)
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
def calculate_team_memory_usage
|
199
|
+
total_memory = @agents.sum { |agent| agent.memory.size }
|
200
|
+
total_capacity = @agents.sum { |agent| agent.memory.instance_variable_get(:@max_size) }
|
201
|
+
|
202
|
+
return 0 if total_capacity.zero?
|
203
|
+
|
204
|
+
(total_memory.to_f / total_capacity * 100).round(2)
|
205
|
+
end
|
206
|
+
|
207
|
+
def calculate_collaboration_success_rate
|
208
|
+
return 0 if @collaboration_history.empty?
|
209
|
+
|
210
|
+
successful = @collaboration_history.count { |c| c[:status] == :completed }
|
211
|
+
(successful.to_f / @collaboration_history.length * 100).round(2)
|
212
|
+
end
|
213
|
+
|
214
|
+
def analyze_collaboration_patterns(collaborations)
|
215
|
+
{
|
216
|
+
total_collaborations: collaborations.length,
|
217
|
+
success_rate: calculate_collaboration_success_rate,
|
218
|
+
average_contributors: collaborations.map { |c| c[:contributions].length }.sum.to_f / collaborations.length,
|
219
|
+
common_issues: extract_common_issues(collaborations),
|
220
|
+
best_practices: extract_best_practices(collaborations)
|
221
|
+
}
|
222
|
+
end
|
223
|
+
|
224
|
+
def extract_common_issues(collaborations)
|
225
|
+
issues = collaborations.flat_map do |collab|
|
226
|
+
collab[:contributions].values.select { |c| c[:error] }.map { |c| c[:error] }
|
227
|
+
end
|
228
|
+
issues.tally.sort_by { |_, count| -count }.first(3)
|
229
|
+
end
|
230
|
+
|
231
|
+
def extract_best_practices(collaborations)
|
232
|
+
successful_collaborations = collaborations.select { |c| c[:status] == :completed }
|
233
|
+
return [] if successful_collaborations.empty?
|
234
|
+
|
235
|
+
# Analyze what made successful collaborations work
|
236
|
+
{
|
237
|
+
average_duration: successful_collaborations.map { |c| c[:completed_at] - c[:started_at] }.sum / successful_collaborations.length,
|
238
|
+
most_active_contributors: successful_collaborations.flat_map { |c| c[:contributions].keys }.tally.sort_by { |_, count| -count }.first(3)
|
239
|
+
}
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -0,0 +1,331 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsAi
|
4
|
+
module Agents
|
5
|
+
class BaseAgent
|
6
|
+
attr_reader :name, :role, :capabilities, :memory, :state, :created_at, :last_activity, :active_tasks, :completed_tasks, :failed_tasks
|
7
|
+
|
8
|
+
def initialize(name:, role:, capabilities: [], memory_size: 1000, **opts)
|
9
|
+
@name = name
|
10
|
+
@role = role
|
11
|
+
@capabilities = Array(capabilities)
|
12
|
+
@memory = Memory.new(max_size: memory_size)
|
13
|
+
@state = :idle
|
14
|
+
@created_at = Time.now
|
15
|
+
@last_activity = Time.now
|
16
|
+
@config = opts
|
17
|
+
@message_queue = []
|
18
|
+
@active_tasks = []
|
19
|
+
@completed_tasks = []
|
20
|
+
@failed_tasks = []
|
21
|
+
end
|
22
|
+
|
23
|
+
# Core agent lifecycle methods
|
24
|
+
def start!
|
25
|
+
@state = :active
|
26
|
+
@last_activity = Time.now
|
27
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} started")
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop!
|
32
|
+
@state = :stopped
|
33
|
+
@last_activity = Time.now
|
34
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} stopped")
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def pause!
|
39
|
+
@state = :paused
|
40
|
+
@last_activity = Time.now
|
41
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} paused")
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def resume!
|
46
|
+
@state = :active
|
47
|
+
@last_activity = Time.now
|
48
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} resumed")
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Task management
|
53
|
+
def assign_task(task)
|
54
|
+
return false unless can_handle_task?(task)
|
55
|
+
|
56
|
+
@active_tasks << task.merge(
|
57
|
+
assigned_at: Time.now,
|
58
|
+
status: :in_progress
|
59
|
+
)
|
60
|
+
@last_activity = Time.now
|
61
|
+
|
62
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} assigned task: #{task[:description]}")
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def complete_task(task_id, result)
|
67
|
+
task = @active_tasks.find { |t| t[:id] == task_id }
|
68
|
+
return false unless task
|
69
|
+
|
70
|
+
@active_tasks.delete(task)
|
71
|
+
@completed_tasks << task.merge(
|
72
|
+
completed_at: Time.now,
|
73
|
+
status: :completed,
|
74
|
+
result: result
|
75
|
+
)
|
76
|
+
@last_activity = Time.now
|
77
|
+
|
78
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} completed task: #{task[:description]}")
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
def fail_task(task_id, error)
|
83
|
+
task = @active_tasks.find { |t| t[:id] == task_id }
|
84
|
+
return false unless task
|
85
|
+
|
86
|
+
@active_tasks.delete(task)
|
87
|
+
@failed_tasks << task.merge(
|
88
|
+
failed_at: Time.now,
|
89
|
+
status: :failed,
|
90
|
+
error: error
|
91
|
+
)
|
92
|
+
@last_activity = Time.now
|
93
|
+
|
94
|
+
defined?(Rails) && Rails.logger && Rails.logger.error("Agent #{@name} failed task: #{task[:description]} - #{error}")
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
# Communication methods
|
99
|
+
def send_message(to_agent, message)
|
100
|
+
message_obj = {
|
101
|
+
from: @name,
|
102
|
+
to: to_agent,
|
103
|
+
content: message,
|
104
|
+
timestamp: Time.now,
|
105
|
+
id: SecureRandom.uuid
|
106
|
+
}
|
107
|
+
|
108
|
+
@message_queue << message_obj
|
109
|
+
@last_activity = Time.now
|
110
|
+
|
111
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} sent message to #{to_agent}")
|
112
|
+
message_obj
|
113
|
+
end
|
114
|
+
|
115
|
+
def receive_message(message)
|
116
|
+
@memory.add(:message, message)
|
117
|
+
@last_activity = Time.now
|
118
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} received message from #{message[:from]}")
|
119
|
+
end
|
120
|
+
|
121
|
+
def get_messages(from_agent = nil)
|
122
|
+
messages = @message_queue
|
123
|
+
messages = messages.select { |m| m[:from] == from_agent } if from_agent
|
124
|
+
messages
|
125
|
+
end
|
126
|
+
|
127
|
+
# AI-powered decision making
|
128
|
+
def think(prompt, context: {})
|
129
|
+
return "[stubbed] Agent #{@name} thinking: #{prompt}" if RailsAi.config.stub_responses
|
130
|
+
|
131
|
+
full_context = build_context(context)
|
132
|
+
enhanced_prompt = build_agent_prompt(prompt, full_context)
|
133
|
+
|
134
|
+
RailsAi.chat(enhanced_prompt, model: RailsAi.config.default_model)
|
135
|
+
end
|
136
|
+
|
137
|
+
def decide_next_action(context: {})
|
138
|
+
return { action: :wait, reason: "Stubbed response" } if RailsAi.config.stub_responses
|
139
|
+
|
140
|
+
decision_prompt = build_decision_prompt(context)
|
141
|
+
response = think(decision_prompt, context: context)
|
142
|
+
|
143
|
+
parse_decision(response)
|
144
|
+
end
|
145
|
+
|
146
|
+
def collaborate_with(other_agent, task, context: {})
|
147
|
+
return "[stubbed] Collaboration between #{@name} and #{other_agent.name}" if RailsAi.config.stub_responses
|
148
|
+
|
149
|
+
collaboration_prompt = build_collaboration_prompt(other_agent, task, context)
|
150
|
+
response = think(collaboration_prompt, context: context)
|
151
|
+
|
152
|
+
# Send collaboration result to other agent
|
153
|
+
send_message(other_agent.name, {
|
154
|
+
type: :collaboration_result,
|
155
|
+
task: task,
|
156
|
+
result: response,
|
157
|
+
from_agent: @name
|
158
|
+
})
|
159
|
+
|
160
|
+
response
|
161
|
+
end
|
162
|
+
|
163
|
+
# Capability checks
|
164
|
+
def can_handle_task?(task)
|
165
|
+
return false unless @state == :active
|
166
|
+
return false if @active_tasks.length >= max_concurrent_tasks
|
167
|
+
|
168
|
+
required_capabilities = task[:required_capabilities] || []
|
169
|
+
required_capabilities.all? { |cap| @capabilities.include?(cap) }
|
170
|
+
end
|
171
|
+
|
172
|
+
def has_capability?(capability)
|
173
|
+
@capabilities.include?(capability.to_sym)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Status and monitoring
|
177
|
+
def status
|
178
|
+
{
|
179
|
+
name: @name,
|
180
|
+
role: @role,
|
181
|
+
state: @state,
|
182
|
+
capabilities: @capabilities,
|
183
|
+
active_tasks: @active_tasks.length,
|
184
|
+
completed_tasks: @completed_tasks.length,
|
185
|
+
failed_tasks: @failed_tasks.length,
|
186
|
+
memory_usage: @memory.usage_percentage,
|
187
|
+
last_activity: @last_activity,
|
188
|
+
uptime: Time.now - @created_at
|
189
|
+
}
|
190
|
+
end
|
191
|
+
|
192
|
+
def health_check
|
193
|
+
{
|
194
|
+
state: @state,
|
195
|
+
memory_healthy: @memory.usage_percentage < 90,
|
196
|
+
no_stuck_tasks: @active_tasks.none? { |t| Time.now - t[:assigned_at] > max_task_duration },
|
197
|
+
last_activity_recent: Time.now - @last_activity < 5 * 60
|
198
|
+
}
|
199
|
+
end
|
200
|
+
|
201
|
+
# Memory management
|
202
|
+
def remember(key, value, importance: :normal)
|
203
|
+
@memory.add(key, value, importance: importance)
|
204
|
+
@last_activity = Time.now
|
205
|
+
end
|
206
|
+
|
207
|
+
def recall(key)
|
208
|
+
@memory.get(key)
|
209
|
+
end
|
210
|
+
|
211
|
+
def forget(key)
|
212
|
+
@memory.remove(key)
|
213
|
+
@last_activity = Time.now
|
214
|
+
end
|
215
|
+
|
216
|
+
# Task delegation
|
217
|
+
def delegate_task(task, target_agent, reason: nil)
|
218
|
+
return false unless can_delegate_task?(task, target_agent)
|
219
|
+
|
220
|
+
delegation = {
|
221
|
+
task: task,
|
222
|
+
from_agent: @name,
|
223
|
+
to_agent: target_agent.name,
|
224
|
+
reason: reason,
|
225
|
+
delegated_at: Time.now,
|
226
|
+
status: :pending
|
227
|
+
}
|
228
|
+
|
229
|
+
send_message(target_agent.name, {
|
230
|
+
type: :task_delegation,
|
231
|
+
delegation: delegation
|
232
|
+
})
|
233
|
+
|
234
|
+
@last_activity = Time.now
|
235
|
+
defined?(Rails) && Rails.logger && Rails.logger.info("Agent #{@name} delegated task to #{target_agent.name}")
|
236
|
+
delegation
|
237
|
+
end
|
238
|
+
|
239
|
+
def accept_delegated_task(delegation)
|
240
|
+
task = delegation[:delegation][:task]
|
241
|
+
return false unless can_handle_task?(task)
|
242
|
+
|
243
|
+
assign_task(task.merge(
|
244
|
+
delegated_from: delegation[:from],
|
245
|
+
delegation_id: delegation[:delegation][:id]
|
246
|
+
))
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def max_concurrent_tasks
|
252
|
+
@config[:max_concurrent_tasks] || 3
|
253
|
+
end
|
254
|
+
|
255
|
+
def max_task_duration
|
256
|
+
@config[:max_task_duration] || 30.minutes
|
257
|
+
end
|
258
|
+
|
259
|
+
def can_delegate_task?(task, target_agent)
|
260
|
+
target_agent.can_handle_task?(task) &&
|
261
|
+
target_agent.state == :active &&
|
262
|
+
@state == :active
|
263
|
+
end
|
264
|
+
|
265
|
+
def build_context(context)
|
266
|
+
{
|
267
|
+
agent_name: @name,
|
268
|
+
agent_role: @role,
|
269
|
+
agent_capabilities: @capabilities,
|
270
|
+
current_tasks: @active_tasks,
|
271
|
+
recent_memory: @memory.recent(10),
|
272
|
+
current_time: Time.now,
|
273
|
+
**context
|
274
|
+
}
|
275
|
+
end
|
276
|
+
|
277
|
+
def build_agent_prompt(prompt, context)
|
278
|
+
<<~PROMPT
|
279
|
+
You are #{@name}, a #{@role} AI agent with the following capabilities: #{@capabilities.join(', ')}.
|
280
|
+
|
281
|
+
Current context:
|
282
|
+
#{context.map { |k, v| "#{k}: #{v}" }.join("\n")}
|
283
|
+
|
284
|
+
Your task: #{prompt}
|
285
|
+
|
286
|
+
Respond as this agent would, considering your role and capabilities.
|
287
|
+
PROMPT
|
288
|
+
end
|
289
|
+
|
290
|
+
def build_decision_prompt(context)
|
291
|
+
<<~PROMPT
|
292
|
+
As #{@name}, analyze the current situation and decide what to do next.
|
293
|
+
|
294
|
+
Current context:
|
295
|
+
#{context.map { |k, v| "#{k}: #{v}" }.join("\n")}
|
296
|
+
|
297
|
+
Available actions:
|
298
|
+
- :wait (if no immediate action needed)
|
299
|
+
- :think (if need more information)
|
300
|
+
- :act (if ready to take action)
|
301
|
+
- :collaborate (if need help from other agents)
|
302
|
+
- :delegate (if task should be handled by another agent)
|
303
|
+
|
304
|
+
Respond with a JSON object: {"action": "action_name", "reason": "explanation", "details": {...}}
|
305
|
+
PROMPT
|
306
|
+
end
|
307
|
+
|
308
|
+
def build_collaboration_prompt(other_agent, task, context)
|
309
|
+
<<~PROMPT
|
310
|
+
You are #{@name} collaborating with #{other_agent.name} (#{other_agent.role}).
|
311
|
+
|
312
|
+
Task: #{task[:description]}
|
313
|
+
Other agent's capabilities: #{other_agent.capabilities.join(', ')}
|
314
|
+
|
315
|
+
Context:
|
316
|
+
#{context.map { |k, v| "#{k}: #{v}" }.join("\n")}
|
317
|
+
|
318
|
+
Provide your contribution to this collaboration.
|
319
|
+
PROMPT
|
320
|
+
end
|
321
|
+
|
322
|
+
def parse_decision(response)
|
323
|
+
begin
|
324
|
+
JSON.parse(response)
|
325
|
+
rescue JSON::ParserError
|
326
|
+
{ action: :wait, reason: "Failed to parse decision", error: response }
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|