agentic 0.1.0 → 0.2.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 (130) hide show
  1. checksums.yaml +4 -4
  2. data/.agentic.yml +2 -0
  3. data/.architecture/decisions/ArchitecturalFeatureBuilder.md +136 -0
  4. data/.architecture/decisions/ArchitectureConsiderations.md +200 -0
  5. data/.architecture/decisions/adr_001_observer_pattern_implementation.md +196 -0
  6. data/.architecture/decisions/adr_002_plan_orchestrator.md +320 -0
  7. data/.architecture/decisions/adr_003_plan_orchestrator_interface.md +179 -0
  8. data/.architecture/decisions/adrs/ADR-001-dependency-management.md +147 -0
  9. data/.architecture/decisions/adrs/ADR-002-system-boundaries.md +162 -0
  10. data/.architecture/decisions/adrs/ADR-003-content-safety.md +158 -0
  11. data/.architecture/decisions/adrs/ADR-004-agent-permissions.md +161 -0
  12. data/.architecture/decisions/adrs/ADR-005-adaptation-engine.md +127 -0
  13. data/.architecture/decisions/adrs/ADR-006-extension-system.md +273 -0
  14. data/.architecture/decisions/adrs/ADR-007-learning-system.md +156 -0
  15. data/.architecture/decisions/adrs/ADR-008-prompt-generation.md +325 -0
  16. data/.architecture/decisions/adrs/ADR-009-task-failure-handling.md +353 -0
  17. data/.architecture/decisions/adrs/ADR-010-task-input-handling.md +251 -0
  18. data/.architecture/decisions/adrs/ADR-011-task-observable-pattern.md +391 -0
  19. data/.architecture/decisions/adrs/ADR-012-task-output-handling.md +205 -0
  20. data/.architecture/decisions/adrs/ADR-013-architecture-alignment.md +211 -0
  21. data/.architecture/decisions/adrs/ADR-014-agent-capability-registry.md +80 -0
  22. data/.architecture/decisions/adrs/ADR-015-persistent-agent-store.md +100 -0
  23. data/.architecture/decisions/adrs/ADR-016-agent-assembly-engine.md +117 -0
  24. data/.architecture/decisions/adrs/ADR-017-streaming-observability.md +171 -0
  25. data/.architecture/decisions/capability_tools_distinction.md +150 -0
  26. data/.architecture/decisions/cli_command_structure.md +61 -0
  27. data/.architecture/implementation/agent_self_assembly_implementation.md +267 -0
  28. data/.architecture/implementation/agent_self_assembly_summary.md +138 -0
  29. data/.architecture/members.yml +187 -0
  30. data/.architecture/planning/self_implementation_exercise.md +295 -0
  31. data/.architecture/planning/session_compaction_rule.md +43 -0
  32. data/.architecture/planning/streaming_observability_feature.md +223 -0
  33. data/.architecture/principles.md +151 -0
  34. data/.architecture/recalibration/0-2-0.md +92 -0
  35. data/.architecture/recalibration/agent_self_assembly.md +238 -0
  36. data/.architecture/recalibration/cli_command_structure.md +91 -0
  37. data/.architecture/recalibration/implementation_roadmap_0-2-0.md +301 -0
  38. data/.architecture/recalibration/progress_tracking_0-2-0.md +114 -0
  39. data/.architecture/recalibration_process.md +127 -0
  40. data/.architecture/reviews/0-2-0.md +181 -0
  41. data/.architecture/reviews/cli_command_duplication.md +98 -0
  42. data/.architecture/templates/adr.md +105 -0
  43. data/.architecture/templates/implementation_roadmap.md +125 -0
  44. data/.architecture/templates/progress_tracking.md +89 -0
  45. data/.architecture/templates/recalibration_plan.md +70 -0
  46. data/.architecture/templates/version_comparison.md +124 -0
  47. data/.claude/settings.local.json +13 -0
  48. data/.claude-sessions/001-task-class-architecture-implementation.md +129 -0
  49. data/.claude-sessions/002-plan-orchestrator-interface-review.md +105 -0
  50. data/.claude-sessions/architecture-governance-implementation.md +37 -0
  51. data/.claude-sessions/architecture-review-session.md +27 -0
  52. data/ArchitecturalFeatureBuilder.md +136 -0
  53. data/ArchitectureConsiderations.md +229 -0
  54. data/CHANGELOG.md +57 -2
  55. data/CLAUDE.md +111 -0
  56. data/CONTRIBUTING.md +286 -0
  57. data/MAINTAINING.md +301 -0
  58. data/README.md +582 -28
  59. data/docs/agent_capabilities_api.md +259 -0
  60. data/docs/artifact_extension_points.md +757 -0
  61. data/docs/artifact_generation_architecture.md +323 -0
  62. data/docs/artifact_implementation_plan.md +596 -0
  63. data/docs/artifact_integration_points.md +345 -0
  64. data/docs/artifact_verification_strategies.md +581 -0
  65. data/docs/streaming_observability_architecture.md +510 -0
  66. data/exe/agentic +6 -1
  67. data/lefthook.yml +5 -0
  68. data/lib/agentic/adaptation_engine.rb +124 -0
  69. data/lib/agentic/agent.rb +181 -4
  70. data/lib/agentic/agent_assembly_engine.rb +442 -0
  71. data/lib/agentic/agent_capability_registry.rb +260 -0
  72. data/lib/agentic/agent_config.rb +63 -0
  73. data/lib/agentic/agent_specification.rb +46 -0
  74. data/lib/agentic/capabilities/examples.rb +530 -0
  75. data/lib/agentic/capabilities.rb +14 -0
  76. data/lib/agentic/capability_provider.rb +146 -0
  77. data/lib/agentic/capability_specification.rb +118 -0
  78. data/lib/agentic/cli/agent.rb +31 -0
  79. data/lib/agentic/cli/capabilities.rb +191 -0
  80. data/lib/agentic/cli/config.rb +134 -0
  81. data/lib/agentic/cli/execution_observer.rb +796 -0
  82. data/lib/agentic/cli.rb +1068 -0
  83. data/lib/agentic/default_agent_provider.rb +35 -0
  84. data/lib/agentic/errors/llm_error.rb +184 -0
  85. data/lib/agentic/execution_plan.rb +53 -0
  86. data/lib/agentic/execution_result.rb +91 -0
  87. data/lib/agentic/expected_answer_format.rb +46 -0
  88. data/lib/agentic/extension/domain_adapter.rb +109 -0
  89. data/lib/agentic/extension/plugin_manager.rb +163 -0
  90. data/lib/agentic/extension/protocol_handler.rb +116 -0
  91. data/lib/agentic/extension.rb +45 -0
  92. data/lib/agentic/factory_methods.rb +9 -1
  93. data/lib/agentic/generation_stats.rb +61 -0
  94. data/lib/agentic/learning/README.md +84 -0
  95. data/lib/agentic/learning/capability_optimizer.rb +613 -0
  96. data/lib/agentic/learning/execution_history_store.rb +251 -0
  97. data/lib/agentic/learning/pattern_recognizer.rb +500 -0
  98. data/lib/agentic/learning/strategy_optimizer.rb +706 -0
  99. data/lib/agentic/learning.rb +131 -0
  100. data/lib/agentic/llm_assisted_composition_strategy.rb +188 -0
  101. data/lib/agentic/llm_client.rb +215 -15
  102. data/lib/agentic/llm_config.rb +65 -1
  103. data/lib/agentic/llm_response.rb +163 -0
  104. data/lib/agentic/logger.rb +1 -1
  105. data/lib/agentic/observable.rb +51 -0
  106. data/lib/agentic/persistent_agent_store.rb +385 -0
  107. data/lib/agentic/plan_execution_result.rb +129 -0
  108. data/lib/agentic/plan_orchestrator.rb +464 -0
  109. data/lib/agentic/plan_orchestrator_config.rb +57 -0
  110. data/lib/agentic/retry_config.rb +63 -0
  111. data/lib/agentic/retry_handler.rb +125 -0
  112. data/lib/agentic/structured_outputs.rb +1 -1
  113. data/lib/agentic/task.rb +193 -0
  114. data/lib/agentic/task_definition.rb +39 -0
  115. data/lib/agentic/task_execution_result.rb +92 -0
  116. data/lib/agentic/task_failure.rb +66 -0
  117. data/lib/agentic/task_output_schemas.rb +112 -0
  118. data/lib/agentic/task_planner.rb +54 -19
  119. data/lib/agentic/task_result.rb +48 -0
  120. data/lib/agentic/ui.rb +244 -0
  121. data/lib/agentic/verification/critic_framework.rb +116 -0
  122. data/lib/agentic/verification/llm_verification_strategy.rb +60 -0
  123. data/lib/agentic/verification/schema_verification_strategy.rb +47 -0
  124. data/lib/agentic/verification/verification_hub.rb +62 -0
  125. data/lib/agentic/verification/verification_result.rb +50 -0
  126. data/lib/agentic/verification/verification_strategy.rb +26 -0
  127. data/lib/agentic/version.rb +1 -1
  128. data/lib/agentic.rb +74 -2
  129. data/plugins/README.md +41 -0
  130. metadata +245 -6
data/lib/agentic/agent.rb CHANGED
@@ -1,17 +1,194 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "llm_client"
4
+ require_relative "llm_config"
5
+
3
6
  module Agentic
4
7
  class Agent
5
8
  include FactoryMethods
6
9
 
7
- configurable :role, :purpose, :backstory, :tools
10
+ configurable :id, :name, :role, :purpose, :backstory, :instructions, :tools, :capabilities, :llm_client
8
11
 
9
- assembly do |agent|
10
- agent.tools ||= Set.new
12
+ # Initialize with default values
13
+ def initialize
14
+ @capabilities = {}
15
+ @tools = Set.new
16
+ @llm_client = nil
11
17
  end
12
18
 
19
+ # Executes a task using this agent
20
+ # @param task [String, Task] The task to execute, either a task object or a prompt
21
+ # @return [Object] The result of the task execution
13
22
  def execute(task)
14
- task.perform(self)
23
+ if task.is_a?(String)
24
+ # Simple string prompt
25
+ execute_prompt(task)
26
+ else
27
+ # Task object
28
+ task.perform(self)
29
+ end
30
+ end
31
+
32
+ # Executes a prompt with structured output schema
33
+ # @param prompt [String] The prompt to execute
34
+ # @param schema [Agentic::StructuredOutputs::Schema] The output schema
35
+ # @return [Object] The structured response
36
+ def execute_with_schema(prompt, schema)
37
+ # If the agent has a text_generation capability, use it
38
+ if has_capability?("text_generation")
39
+ # For now, text_generation capabilities don't support schemas
40
+ # Fall back to regular execution
41
+ execute_capability("text_generation", {prompt: prompt})[:response]
42
+ elsif @llm_client
43
+ # Use the configured LLM client with structured output
44
+ response = @llm_client.complete(build_messages(prompt), output_schema: schema)
45
+ if response.successful?
46
+ response.content
47
+ else
48
+ raise "LLM execution failed: #{response.error.message}"
49
+ end
50
+ else
51
+ # Fallback error - agent not properly configured
52
+ raise "Agent not configured with LLM capabilities. Use DefaultAgentProvider or configure llm_client directly."
53
+ end
54
+ end
55
+
56
+ # Adds a capability to the agent
57
+ # @param capability_name [String] The name of the capability
58
+ # @param version [String, nil] The version of the capability, or nil for latest
59
+ # @return [Boolean] True if the capability was added successfully
60
+ def add_capability(capability_name, version = nil)
61
+ # Get the capability from the registry
62
+ registry = AgentCapabilityRegistry.instance
63
+ capability = registry.get(capability_name, version)
64
+ raise "Capability not found: #{capability_name}" unless capability
65
+
66
+ # Get the provider
67
+ provider = registry.get_provider(capability_name, version)
68
+ raise "Provider not found for capability: #{capability_name}" unless provider
69
+
70
+ # Add to the agent's capabilities
71
+ @capabilities[capability_name] = {
72
+ specification: capability,
73
+ provider: provider
74
+ }
75
+
76
+ # Also add to tools for backward compatibility
77
+ @tools.add(capability_name.to_sym)
78
+
79
+ true
80
+ end
81
+
82
+ # Checks if the agent has a capability
83
+ # @param capability_name [String] The name of the capability
84
+ # @return [Boolean] True if the agent has the capability
85
+ def has_capability?(capability_name)
86
+ @capabilities.key?(capability_name)
87
+ end
88
+
89
+ # Gets the specification for a capability
90
+ # @param capability_name [String] The name of the capability
91
+ # @return [CapabilitySpecification, nil] The capability specification or nil if not found
92
+ def capability_specification(capability_name)
93
+ return nil unless @capabilities[capability_name]
94
+ @capabilities[capability_name][:specification]
95
+ end
96
+
97
+ # Executes a capability
98
+ # @param capability_name [String] The name of the capability
99
+ # @param inputs [Hash] The inputs for the capability
100
+ # @return [Hash] The outputs from the capability
101
+ def execute_capability(capability_name, inputs = {})
102
+ raise "Capability not available: #{capability_name}" unless @capabilities.key?(capability_name)
103
+
104
+ # Get the provider
105
+ provider = @capabilities[capability_name][:provider]
106
+
107
+ # Execute the capability
108
+ provider.execute(inputs)
109
+ end
110
+
111
+ # Converts the agent to a hash representation
112
+ # @return [Hash] The hash representation
113
+ def to_h
114
+ {
115
+ role: @role,
116
+ purpose: @purpose,
117
+ backstory: @backstory,
118
+ capability_names: @capabilities.keys
119
+ }
120
+ end
121
+
122
+ # Creates an agent from a hash representation
123
+ # @param hash [Hash] The hash representation
124
+ # @return [Agent] The agent
125
+ def self.from_h(hash)
126
+ new do |a|
127
+ a.role = hash[:role] || hash["role"]
128
+ a.purpose = hash[:purpose] || hash["purpose"]
129
+ a.backstory = hash[:backstory] || hash["backstory"]
130
+ end
131
+
132
+ # Note: Capabilities need to be added separately after creation
133
+ # since they require the registry to be available
134
+ end
135
+
136
+ private
137
+
138
+ # Executes a simple string prompt
139
+ # @param prompt [String] The prompt to execute
140
+ # @return [String] The response
141
+ def execute_prompt(prompt)
142
+ # If the agent has a text_generation capability, use it
143
+ if has_capability?("text_generation")
144
+ execute_capability("text_generation", {prompt: prompt})[:response]
145
+ elsif @llm_client
146
+ # Use the configured LLM client
147
+ response = @llm_client.complete(build_messages(prompt))
148
+ if response.successful?
149
+ response.content
150
+ else
151
+ raise "LLM execution failed: #{response.error.message}"
152
+ end
153
+ else
154
+ # Fallback error - agent not properly configured
155
+ raise "Agent not configured with LLM capabilities. Use DefaultAgentProvider or configure llm_client directly."
156
+ end
157
+ end
158
+
159
+ # Builds messages array for LLM completion
160
+ # @param prompt [String] The user prompt
161
+ # @return [Array<Hash>] The messages array
162
+ def build_messages(prompt)
163
+ messages = []
164
+
165
+ # Add system message with agent context
166
+ system_content = build_system_message
167
+ messages << {role: "system", content: system_content} if system_content && !system_content.empty?
168
+
169
+ # Add user prompt
170
+ messages << {role: "user", content: prompt}
171
+
172
+ messages
173
+ end
174
+
175
+ # Builds system message with agent personality and instructions
176
+ # @return [String] The system message content
177
+ def build_system_message
178
+ parts = []
179
+
180
+ parts << "You are #{@role}" if @role && !@role.empty?
181
+ parts << "Purpose: #{@purpose}" if @purpose && !@purpose.empty?
182
+ parts << "Background: #{@backstory}" if @backstory && !@backstory.empty?
183
+ parts << "Instructions: #{@instructions}" if @instructions && !@instructions.empty?
184
+
185
+ # Add capability information
186
+ if @capabilities && !@capabilities.empty?
187
+ capabilities_list = @capabilities.keys.join(", ")
188
+ parts << "Available capabilities: #{capabilities_list}"
189
+ end
190
+
191
+ parts.join("\n\n")
15
192
  end
16
193
  end
17
194
  end
@@ -0,0 +1,442 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Agentic
4
+ # Engine for assembling agents based on task requirements
5
+ # @attr_reader [AgentCapabilityRegistry] registry The capability registry
6
+ # @attr_reader [PersistentAgentStore, nil] agent_store The agent store for persistence
7
+ class AgentAssemblyEngine
8
+ attr_reader :registry, :agent_store
9
+
10
+ # Initialize a new agent assembly engine
11
+ # @param registry [AgentCapabilityRegistry] The capability registry to use
12
+ # @param agent_store [PersistentAgentStore, nil] The agent store for persistence
13
+ def initialize(registry = AgentCapabilityRegistry.instance, agent_store = nil)
14
+ @registry = registry
15
+ @agent_store = agent_store
16
+ end
17
+
18
+ # Assemble an agent for a task
19
+ # @param task [Task] The task to assemble an agent for
20
+ # @param strategy [AgentCompositionStrategy, nil] The strategy to use for assembly
21
+ # @param store [Boolean] Whether to use the agent store for finding and storing agents
22
+ # @return [Agent] The assembled agent
23
+ def assemble_agent(task, strategy: nil, store: true)
24
+ # Check if we should try to find an existing agent in the store
25
+ if store && @agent_store
26
+ existing_agent = find_suitable_agent(task)
27
+ if existing_agent
28
+ Agentic.logger.info("Using existing agent from store for task: #{task.id}")
29
+ return existing_agent
30
+ end
31
+ end
32
+
33
+ # Use the default strategy if none provided
34
+ strategy ||= DefaultCompositionStrategy.new
35
+
36
+ # Analyze task requirements
37
+ requirements = analyze_requirements(task)
38
+
39
+ # Select capabilities based on requirements
40
+ capabilities = select_capabilities(requirements, strategy)
41
+
42
+ # Create a new agent with the selected capabilities
43
+ agent = build_agent(task, capabilities)
44
+
45
+ # Store the assembled agent if requested
46
+ if store && @agent_store
47
+ store_agent(agent, task, requirements)
48
+ end
49
+
50
+ # Return the assembled agent
51
+ agent
52
+ end
53
+
54
+ # Analyze task requirements to determine needed capabilities
55
+ # @param task [Task] The task to analyze
56
+ # @return [Hash] The capability requirements
57
+ def analyze_requirements(task)
58
+ # Extract requirements from the task description and agent specification
59
+ requirements = {}
60
+
61
+ # Use the task description to infer capabilities
62
+ infer_capabilities_from_description(task.description, requirements)
63
+
64
+ # Use the agent specification to infer capabilities
65
+ if task.agent_spec
66
+ infer_capabilities_from_agent_spec(task.agent_spec, requirements)
67
+ end
68
+
69
+ # Use the task input to infer capabilities
70
+ if task.input && !task.input.empty?
71
+ infer_capabilities_from_input(task.input, requirements)
72
+ end
73
+
74
+ requirements
75
+ end
76
+
77
+ # Select capabilities based on requirements
78
+ # @param requirements [Hash] The capability requirements
79
+ # @param strategy [AgentCompositionStrategy] The strategy to use for selection
80
+ # @return [Array<Hash>] The selected capabilities
81
+ def select_capabilities(requirements, strategy)
82
+ # Use the strategy to select capabilities
83
+ strategy.select_capabilities(requirements, @registry)
84
+ end
85
+
86
+ # Build an agent with the selected capabilities
87
+ # @param task [Task] The task the agent will perform
88
+ # @param capabilities [Array<Hash>] The selected capabilities
89
+ # @return [Agent] The assembled agent
90
+ def build_agent(task, capabilities)
91
+ # Create a new agent
92
+ agent = Agent.build do |a|
93
+ # Set basic agent properties from the task agent spec
94
+ if task.agent_spec
95
+ a.role = task.agent_spec.name
96
+ a.purpose = task.agent_spec.description
97
+ a.backstory = task.agent_spec.instructions
98
+ end
99
+ end
100
+
101
+ # Add capabilities to the agent
102
+ capabilities.each do |capability_info|
103
+ agent.add_capability(capability_info[:name], capability_info[:version])
104
+ rescue => e
105
+ Agentic.logger.warn("Failed to add capability: #{capability_info[:name]} v#{capability_info[:version]} - #{e.message}")
106
+ end
107
+
108
+ agent
109
+ end
110
+
111
+ # Find a suitable agent in the store for a task
112
+ # @param task [Task] The task to find an agent for
113
+ # @return [Agent, nil] A suitable agent or nil if none found
114
+ def find_suitable_agent(task)
115
+ return nil unless @agent_store
116
+
117
+ # Analyze task requirements
118
+ requirements = analyze_requirements(task)
119
+
120
+ # Get required capabilities
121
+ required_capabilities = requirements.keys
122
+ return nil if required_capabilities.empty?
123
+
124
+ # Find agents with matching capabilities
125
+ matching_agents = []
126
+
127
+ # Start with the most important capabilities
128
+ primary_capabilities = requirements.select { |_, info| info[:importance] >= 0.8 }.keys
129
+ return nil if primary_capabilities.empty?
130
+
131
+ # Find agents with the primary capabilities
132
+ primary_capabilities.each do |capability|
133
+ # Find agents with this capability
134
+ agents = @agent_store.all(capability: capability)
135
+
136
+ # Add to matching agents
137
+ matching_agents.concat(agents)
138
+ end
139
+
140
+ # Return nil if no matching agents found
141
+ return nil if matching_agents.empty?
142
+
143
+ # Score each agent based on how well it matches the requirements
144
+ scored_agents = matching_agents.map do |agent_config|
145
+ score = calculate_agent_match_score(agent_config, requirements)
146
+ {config: agent_config, score: score}
147
+ end
148
+
149
+ # Sort by score (highest first)
150
+ scored_agents.sort_by! { |a| -a[:score] }
151
+
152
+ # Get the best match
153
+ best_match = scored_agents.first
154
+
155
+ # If the best match has a score below threshold, don't use it
156
+ return nil if best_match[:score] < 0.5
157
+
158
+ # Build the agent from the stored configuration
159
+ @agent_store.build_agent(best_match[:config][:id])
160
+ end
161
+
162
+ # Store an assembled agent in the persistent store
163
+ # @param agent [Agent] The agent to store
164
+ # @param task [Task] The task the agent was assembled for
165
+ # @param requirements [Hash] The capability requirements
166
+ # @return [String, nil] The ID of the stored agent or nil if not stored
167
+ def store_agent(agent, task, requirements)
168
+ return nil unless @agent_store
169
+
170
+ # Generate a name for the agent based on the task
171
+ name = generate_agent_name(task)
172
+
173
+ # Generate metadata for the agent
174
+ metadata = {
175
+ task_id: task.id,
176
+ task_description: task.description,
177
+ assembled_at: Time.now.iso8601,
178
+ requirements: requirements,
179
+ assembly_engine_version: "1.0.0" # Add version tracking
180
+ }
181
+
182
+ # Store the agent
183
+ @agent_store.store(agent, name: name, metadata: metadata)
184
+ end
185
+
186
+ private
187
+
188
+ # Calculate a score for how well an agent matches requirements
189
+ # @param agent_config [Hash] The agent configuration
190
+ # @param requirements [Hash] The capability requirements
191
+ # @return [Float] The match score (0.0 to 1.0)
192
+ def calculate_agent_match_score(agent_config, requirements)
193
+ # Start with a base score
194
+ score = 0.0
195
+ max_score = 0.0
196
+
197
+ # Get the agent's capabilities
198
+ agent_capabilities = agent_config[:capabilities].map { |c| c[:name] }
199
+
200
+ # Score each requirement
201
+ requirements.each do |capability_name, info|
202
+ # Add the importance to the max possible score
203
+ max_score += info[:importance]
204
+
205
+ # If the agent has the capability, add its importance to the score
206
+ if agent_capabilities.include?(capability_name)
207
+ score += info[:importance]
208
+ end
209
+ end
210
+
211
+ # Normalize the score (0.0 to 1.0)
212
+ (max_score > 0) ? score / max_score : 0.0
213
+ end
214
+
215
+ # Generate a name for an agent based on a task
216
+ # @param task [Task] The task the agent is for
217
+ # @return [String] A name for the agent
218
+ def generate_agent_name(task)
219
+ # Extract key words from the task description
220
+ words = task.description.downcase.scan(/\b[a-z]{3,}\b/).first(3)
221
+
222
+ # Get the task type from the agent spec if available
223
+ task_type = task.agent_spec ? task.agent_spec.name.downcase.gsub(/\s+/, "_") : "agent"
224
+
225
+ # Generate a name with task type and words
226
+ if words.empty?
227
+ "#{task_type}_#{SecureRandom.hex(4)}"
228
+ else
229
+ "#{task_type}_#{words.join("_")}"
230
+ end
231
+ end
232
+
233
+ # Infer capabilities from a task description
234
+ # @param description [String] The task description
235
+ # @param requirements [Hash] The requirements hash to update
236
+ # @return [void]
237
+ def infer_capabilities_from_description(description, requirements)
238
+ # Extract capability names from description using simple pattern matching
239
+ # This is a basic implementation that should be enhanced with NLP or LLM-based analysis
240
+
241
+ # Example: Look for known capability keywords
242
+ known_capabilities = [
243
+ "document_analysis", "structure_extraction", "text_generation",
244
+ "web_search", "data_analysis", "code_generation", "uml_generation",
245
+ "dependency_analysis", "testing", "performance_analysis", "code_metrics"
246
+ ]
247
+
248
+ known_capabilities.each do |capability|
249
+ if description.downcase.include?(capability.downcase)
250
+ requirements[capability] ||= {
251
+ importance: 0.5, # Default importance
252
+ version_constraint: nil # Any version
253
+ }
254
+
255
+ # Increase importance if mentioned multiple times
256
+ count = description.downcase.scan(capability.downcase).count
257
+ requirements[capability][:importance] += 0.1 * count if count > 1
258
+ end
259
+ end
260
+
261
+ # Special case for test tasks
262
+ if description.downcase.include?("code") && description.downcase.include?("generate")
263
+ requirements["code_generation"] ||= {
264
+ importance: 0.8, # High importance for code generation tasks
265
+ version_constraint: nil # Any version
266
+ }
267
+ end
268
+
269
+ # Add common capabilities for all tasks
270
+ requirements["text_generation"] ||= {
271
+ importance: 0.3, # Lower importance as it's a default capability
272
+ version_constraint: nil
273
+ }
274
+ end
275
+
276
+ # Infer capabilities from an agent specification
277
+ # @param agent_spec [AgentSpecification] The agent specification
278
+ # @param requirements [Hash] The requirements hash to update
279
+ # @return [void]
280
+ def infer_capabilities_from_agent_spec(agent_spec, requirements)
281
+ # Extract capabilities from agent name
282
+ infer_capabilities_from_description(agent_spec.name, requirements)
283
+
284
+ # Extract capabilities from agent description
285
+ if agent_spec.description
286
+ infer_capabilities_from_description(agent_spec.description, requirements)
287
+ end
288
+
289
+ # Extract capabilities from agent instructions
290
+ if agent_spec.instructions
291
+ infer_capabilities_from_description(agent_spec.instructions, requirements)
292
+ end
293
+
294
+ # If the agent spec mentions tools explicitly, prioritize those
295
+ if agent_spec.respond_to?(:tools) && agent_spec.tools
296
+ agent_spec.tools.each do |tool|
297
+ tool_name = tool.to_s
298
+ requirements[tool_name] ||= {
299
+ importance: 0.8, # High importance for explicitly mentioned tools
300
+ version_constraint: nil # Any version
301
+ }
302
+ end
303
+ end
304
+
305
+ # If the agent spec mentions capabilities explicitly, prioritize those
306
+ if agent_spec.respond_to?(:capabilities) && agent_spec.capabilities
307
+ agent_spec.capabilities.each do |capability|
308
+ capability_name = capability.to_s
309
+ requirements[capability_name] ||= {
310
+ importance: 0.9, # Very high importance for explicitly mentioned capabilities
311
+ version_constraint: nil # Any version
312
+ }
313
+ end
314
+ end
315
+ end
316
+
317
+ # Infer capabilities from task input
318
+ # @param input [Hash] The task input
319
+ # @param requirements [Hash] The requirements hash to update
320
+ # @return [void]
321
+ def infer_capabilities_from_input(input, requirements)
322
+ # Check if input contains capability requirements directly
323
+ if input[:capabilities] || input["capabilities"]
324
+ capabilities = input[:capabilities] || input["capabilities"]
325
+
326
+ if capabilities.is_a?(Array)
327
+ capabilities.each do |capability|
328
+ if capability.is_a?(String)
329
+ requirements[capability] ||= {
330
+ importance: 0.9, # Very high importance for explicitly requested capabilities
331
+ version_constraint: nil # Any version
332
+ }
333
+ elsif capability.is_a?(Hash)
334
+ name = capability[:name] || capability["name"]
335
+ version = capability[:version] || capability["version"]
336
+ importance = capability[:importance] || capability["importance"] || 0.9
337
+
338
+ if name
339
+ requirements[name] ||= {
340
+ importance: importance,
341
+ version_constraint: version
342
+ }
343
+ end
344
+ end
345
+ end
346
+ end
347
+ end
348
+
349
+ # Analyze input keys for capability hints
350
+ input.each do |key, value|
351
+ key_str = key.to_s
352
+
353
+ # Check if key suggests a capability
354
+ known_capability_indicators = [
355
+ "analyze", "generate", "search", "extract", "compute",
356
+ "test", "verify", "optimize", "format", "translate"
357
+ ]
358
+
359
+ known_capability_indicators.each do |indicator|
360
+ if key_str.include?(indicator)
361
+ capability_name = "#{indicator}_#{key_str.sub(indicator, "")}"
362
+ requirements[capability_name] ||= {
363
+ importance: 0.6, # Medium importance for inferred capabilities
364
+ version_constraint: nil # Any version
365
+ }
366
+ end
367
+ end
368
+ end
369
+ end
370
+ end
371
+
372
+ # Base class for agent composition strategies
373
+ class AgentCompositionStrategy
374
+ # Select capabilities based on requirements
375
+ # @param requirements [Hash] The capability requirements
376
+ # @param registry [AgentCapabilityRegistry] The capability registry
377
+ # @return [Array<Hash>] The selected capabilities
378
+ def select_capabilities(requirements, registry)
379
+ raise NotImplementedError, "Subclasses must implement select_capabilities"
380
+ end
381
+ end
382
+
383
+ # Default strategy for agent composition
384
+ class DefaultCompositionStrategy < AgentCompositionStrategy
385
+ # Select capabilities based on requirements
386
+ # @param requirements [Hash] The capability requirements
387
+ # @param registry [AgentCapabilityRegistry] The capability registry
388
+ # @return [Array<Hash>] The selected capabilities
389
+ def select_capabilities(requirements, registry)
390
+ selected = []
391
+
392
+ # Sort requirements by importance (highest first)
393
+ sorted_requirements = requirements.sort_by { |_, info| -info[:importance] }
394
+
395
+ # Select capabilities for each requirement
396
+ sorted_requirements.each do |name, info|
397
+ # Find the capability in the registry
398
+ capability = registry.get(name, info[:version_constraint])
399
+
400
+ # Skip if not found
401
+ next unless capability
402
+
403
+ # Add to selected capabilities
404
+ selected << {
405
+ name: capability.name,
406
+ version: capability.version
407
+ }
408
+
409
+ # Add dependencies
410
+ capability.dependencies.each do |dep|
411
+ # Check if we already selected this dependency
412
+ next if selected.any? { |sel| sel[:name] == dep[:name] }
413
+
414
+ # Find the dependency in the registry
415
+ dep_capability = registry.get(dep[:name], dep[:version])
416
+
417
+ # Skip if not found
418
+ next unless dep_capability
419
+
420
+ # Add to selected capabilities
421
+ selected << {
422
+ name: dep_capability.name,
423
+ version: dep_capability.version
424
+ }
425
+ end
426
+ end
427
+
428
+ # If no capabilities were selected, add a default text generation capability
429
+ if selected.empty?
430
+ default_capability = registry.get("text_generation")
431
+ if default_capability
432
+ selected << {
433
+ name: default_capability.name,
434
+ version: default_capability.version
435
+ }
436
+ end
437
+ end
438
+
439
+ selected
440
+ end
441
+ end
442
+ end