language-operator 0.1.61 → 0.1.62

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/persona.md +9 -0
  3. data/.claude/commands/task.md +46 -1
  4. data/.rubocop.yml +13 -0
  5. data/.rubocop_custom/use_ux_helper.rb +44 -0
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile.lock +12 -1
  8. data/Makefile +26 -7
  9. data/Makefile.common +50 -0
  10. data/bin/aictl +8 -1
  11. data/components/agent/Gemfile +1 -1
  12. data/components/agent/bin/langop-agent +7 -0
  13. data/docs/README.md +58 -0
  14. data/docs/{dsl/best-practices.md → best-practices.md} +4 -4
  15. data/docs/cli-reference.md +274 -0
  16. data/docs/{dsl/constraints.md → constraints.md} +5 -5
  17. data/docs/how-agents-work.md +156 -0
  18. data/docs/installation.md +218 -0
  19. data/docs/quickstart.md +299 -0
  20. data/docs/understanding-generated-code.md +265 -0
  21. data/docs/using-tools.md +457 -0
  22. data/docs/webhooks.md +509 -0
  23. data/examples/ux_helpers_demo.rb +296 -0
  24. data/lib/language_operator/agent/base.rb +11 -1
  25. data/lib/language_operator/agent/executor.rb +23 -6
  26. data/lib/language_operator/agent/safety/safe_executor.rb +41 -39
  27. data/lib/language_operator/agent/task_executor.rb +346 -63
  28. data/lib/language_operator/agent/web_server.rb +110 -14
  29. data/lib/language_operator/agent/webhook_authenticator.rb +39 -5
  30. data/lib/language_operator/agent.rb +88 -2
  31. data/lib/language_operator/cli/base_command.rb +17 -11
  32. data/lib/language_operator/cli/command_loader.rb +72 -0
  33. data/lib/language_operator/cli/commands/agent/base.rb +837 -0
  34. data/lib/language_operator/cli/commands/agent/code_operations.rb +102 -0
  35. data/lib/language_operator/cli/commands/agent/helpers/cluster_llm_client.rb +116 -0
  36. data/lib/language_operator/cli/commands/agent/helpers/code_parser.rb +115 -0
  37. data/lib/language_operator/cli/commands/agent/helpers/synthesis_watcher.rb +96 -0
  38. data/lib/language_operator/cli/commands/agent/learning.rb +289 -0
  39. data/lib/language_operator/cli/commands/agent/lifecycle.rb +102 -0
  40. data/lib/language_operator/cli/commands/agent/logs.rb +125 -0
  41. data/lib/language_operator/cli/commands/agent/workspace.rb +327 -0
  42. data/lib/language_operator/cli/commands/cluster.rb +129 -84
  43. data/lib/language_operator/cli/commands/install.rb +1 -1
  44. data/lib/language_operator/cli/commands/model/base.rb +215 -0
  45. data/lib/language_operator/cli/commands/model/test.rb +165 -0
  46. data/lib/language_operator/cli/commands/persona.rb +16 -34
  47. data/lib/language_operator/cli/commands/quickstart.rb +3 -2
  48. data/lib/language_operator/cli/commands/status.rb +40 -67
  49. data/lib/language_operator/cli/commands/system/base.rb +44 -0
  50. data/lib/language_operator/cli/commands/system/exec.rb +147 -0
  51. data/lib/language_operator/cli/commands/system/helpers/llm_synthesis.rb +183 -0
  52. data/lib/language_operator/cli/commands/system/helpers/pod_manager.rb +212 -0
  53. data/lib/language_operator/cli/commands/system/helpers/template_loader.rb +57 -0
  54. data/lib/language_operator/cli/commands/system/helpers/template_validator.rb +174 -0
  55. data/lib/language_operator/cli/commands/system/schema.rb +92 -0
  56. data/lib/language_operator/cli/commands/system/synthesis_template.rb +151 -0
  57. data/lib/language_operator/cli/commands/system/synthesize.rb +224 -0
  58. data/lib/language_operator/cli/commands/system/validate_template.rb +130 -0
  59. data/lib/language_operator/cli/commands/tool/base.rb +271 -0
  60. data/lib/language_operator/cli/commands/tool/install.rb +255 -0
  61. data/lib/language_operator/cli/commands/tool/search.rb +69 -0
  62. data/lib/language_operator/cli/commands/tool/test.rb +115 -0
  63. data/lib/language_operator/cli/commands/use.rb +29 -6
  64. data/lib/language_operator/cli/errors/handler.rb +20 -17
  65. data/lib/language_operator/cli/errors/suggestions.rb +3 -5
  66. data/lib/language_operator/cli/errors/thor_errors.rb +55 -0
  67. data/lib/language_operator/cli/formatters/code_formatter.rb +4 -11
  68. data/lib/language_operator/cli/formatters/log_formatter.rb +8 -15
  69. data/lib/language_operator/cli/formatters/progress_formatter.rb +6 -8
  70. data/lib/language_operator/cli/formatters/status_formatter.rb +26 -7
  71. data/lib/language_operator/cli/formatters/table_formatter.rb +47 -36
  72. data/lib/language_operator/cli/formatters/value_formatter.rb +75 -0
  73. data/lib/language_operator/cli/helpers/cluster_context.rb +5 -3
  74. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +2 -1
  75. data/lib/language_operator/cli/helpers/label_utils.rb +97 -0
  76. data/lib/language_operator/{ux/concerns/provider_helpers.rb → cli/helpers/provider_helper.rb} +10 -29
  77. data/lib/language_operator/cli/helpers/schedule_builder.rb +21 -1
  78. data/lib/language_operator/cli/helpers/user_prompts.rb +19 -11
  79. data/lib/language_operator/cli/helpers/ux_helper.rb +538 -0
  80. data/lib/language_operator/{ux/concerns/input_validation.rb → cli/helpers/validation_helper.rb} +13 -66
  81. data/lib/language_operator/cli/main.rb +50 -40
  82. data/lib/language_operator/cli/templates/tools/generic.yaml +3 -0
  83. data/lib/language_operator/cli/wizards/agent_wizard.rb +12 -20
  84. data/lib/language_operator/cli/wizards/model_wizard.rb +271 -0
  85. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +8 -34
  86. data/lib/language_operator/client/base.rb +28 -0
  87. data/lib/language_operator/client/config.rb +4 -1
  88. data/lib/language_operator/client/mcp_connector.rb +1 -1
  89. data/lib/language_operator/config/cluster_config.rb +3 -2
  90. data/lib/language_operator/config.rb +38 -11
  91. data/lib/language_operator/constants/kubernetes_labels.rb +80 -0
  92. data/lib/language_operator/constants.rb +13 -0
  93. data/lib/language_operator/dsl/http.rb +127 -10
  94. data/lib/language_operator/dsl.rb +153 -6
  95. data/lib/language_operator/errors.rb +50 -0
  96. data/lib/language_operator/kubernetes/client.rb +11 -6
  97. data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
  98. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  99. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  100. data/lib/language_operator/type_coercion.rb +118 -34
  101. data/lib/language_operator/utils/secure_path.rb +74 -0
  102. data/lib/language_operator/utils.rb +7 -0
  103. data/lib/language_operator/validators.rb +54 -2
  104. data/lib/language_operator/version.rb +1 -1
  105. data/synth/001/Makefile +10 -2
  106. data/synth/001/agent.rb +16 -15
  107. data/synth/001/output.log +27 -10
  108. data/synth/002/Makefile +10 -2
  109. data/synth/003/Makefile +1 -1
  110. data/synth/003/README.md +205 -133
  111. data/synth/003/agent.optimized.rb +66 -0
  112. data/synth/003/agent.synthesized.rb +41 -0
  113. metadata +111 -35
  114. data/docs/dsl/agent-reference.md +0 -604
  115. data/docs/dsl/mcp-integration.md +0 -1177
  116. data/docs/dsl/webhooks.md +0 -932
  117. data/docs/dsl/workflows.md +0 -744
  118. data/lib/language_operator/cli/commands/agent.rb +0 -1712
  119. data/lib/language_operator/cli/commands/model.rb +0 -366
  120. data/lib/language_operator/cli/commands/system.rb +0 -1259
  121. data/lib/language_operator/cli/commands/tool.rb +0 -654
  122. data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
  123. data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
  124. data/lib/language_operator/learning/adapters/base_adapter.rb +0 -149
  125. data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -221
  126. data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -435
  127. data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -239
  128. data/lib/language_operator/learning/optimizer.rb +0 -319
  129. data/lib/language_operator/learning/pattern_detector.rb +0 -260
  130. data/lib/language_operator/learning/task_synthesizer.rb +0 -288
  131. data/lib/language_operator/learning/trace_analyzer.rb +0 -285
  132. data/lib/language_operator/templates/task_synthesis.tmpl +0 -98
  133. data/lib/language_operator/ux/base.rb +0 -81
  134. data/lib/language_operator/ux/concerns/README.md +0 -155
  135. data/lib/language_operator/ux/concerns/headings.rb +0 -90
  136. data/lib/language_operator/ux/create_agent.rb +0 -255
  137. data/lib/language_operator/ux/create_model.rb +0 -267
  138. data/lib/language_operator/ux/quickstart.rb +0 -594
  139. data/synth/003/agent.rb +0 -41
  140. data/synth/003/output.log +0 -68
  141. /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
  142. /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
  143. /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
@@ -1,319 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'logger'
4
-
5
- module LanguageOperator
6
- module Learning
7
- # Orchestrates the optimization of neural tasks to symbolic implementations
8
- #
9
- # The Optimizer analyzes running agents, identifies optimization opportunities,
10
- # proposes code changes, and applies them with user approval. It integrates
11
- # TraceAnalyzer (pattern detection) and PatternDetector (code generation) to
12
- # provide a complete optimization workflow.
13
- #
14
- # @example Basic usage
15
- # optimizer = Optimizer.new(
16
- # agent_name: 'github-monitor',
17
- # agent_definition: agent_def,
18
- # trace_analyzer: TraceAnalyzer.new(endpoint: ENV['OTEL_QUERY_ENDPOINT']),
19
- # pattern_detector: PatternDetector.new(...)
20
- # )
21
- #
22
- # opportunities = optimizer.analyze
23
- # opportunities.each do |opp|
24
- # proposal = optimizer.propose(task_name: opp[:task_name])
25
- # # Show to user, get approval
26
- # optimizer.apply(proposal) if approved
27
- # end
28
- class Optimizer
29
- # Minimum consistency score required for optimization
30
- DEFAULT_MIN_CONSISTENCY = 0.85
31
-
32
- # Minimum executions required for optimization
33
- DEFAULT_MIN_EXECUTIONS = 10
34
-
35
- # Initialize optimizer
36
- #
37
- # @param agent_name [String] Name of the agent to optimize
38
- # @param agent_definition [Dsl::AgentDefinition] Agent definition object
39
- # @param trace_analyzer [TraceAnalyzer] Analyzer for querying execution traces
40
- # @param pattern_detector [PatternDetector] Detector for generating symbolic code
41
- # @param task_synthesizer [TaskSynthesizer, nil] Optional LLM-based synthesizer
42
- # @param logger [Logger, nil] Logger instance (creates default if nil)
43
- def initialize(agent_name:, agent_definition:, trace_analyzer:, pattern_detector:, task_synthesizer: nil,
44
- logger: nil)
45
- @agent_name = agent_name
46
- @agent_definition = agent_definition
47
- @trace_analyzer = trace_analyzer
48
- @pattern_detector = pattern_detector
49
- @task_synthesizer = task_synthesizer
50
- @logger = logger || ::Logger.new($stdout, level: ::Logger::WARN)
51
- end
52
-
53
- # Analyze agent for optimization opportunities
54
- #
55
- # Queries execution traces for each neural task and determines which tasks
56
- # are eligible for optimization based on consistency and execution count.
57
- #
58
- # @param min_consistency [Float] Minimum consistency threshold (0.0-1.0)
59
- # @param min_executions [Integer] Minimum execution count required
60
- # @param time_range [Integer, Range<Time>, nil] Time range for trace queries
61
- # @return [Array<Hash>] Array of optimization opportunities
62
- def analyze(min_consistency: DEFAULT_MIN_CONSISTENCY, min_executions: DEFAULT_MIN_EXECUTIONS, time_range: nil)
63
- opportunities = []
64
-
65
- # Find all neural tasks in the agent
66
- neural_tasks = find_neural_tasks
67
-
68
- if neural_tasks.empty?
69
- @logger.info("No neural tasks found in agent '#{@agent_name}'")
70
- return opportunities
71
- end
72
-
73
- # Analyze each neural task
74
- neural_tasks.each do |task|
75
- analysis = @trace_analyzer.analyze_patterns(
76
- task_name: task[:name],
77
- agent_name: @agent_name,
78
- min_executions: min_executions,
79
- consistency_threshold: min_consistency,
80
- time_range: time_range
81
- )
82
-
83
- next unless analysis
84
-
85
- opportunities << {
86
- task_name: task[:name],
87
- task_definition: task[:definition],
88
- execution_count: analysis[:execution_count],
89
- consistency_score: analysis[:consistency_score],
90
- ready_for_learning: analysis[:ready_for_learning],
91
- common_pattern: analysis[:common_pattern],
92
- reason: analysis[:reason]
93
- }
94
- end
95
-
96
- opportunities
97
- end
98
-
99
- # Generate optimization proposal for a specific task
100
- #
101
- # Uses PatternDetector to generate symbolic code and calculates
102
- # the performance impact of the optimization. Falls back to TaskSynthesizer
103
- # (LLM-based) if pattern detection fails and synthesizer is available.
104
- #
105
- # @param task_name [String] Name of task to optimize
106
- # @param use_synthesis [Boolean] Force use of LLM synthesis instead of pattern detection
107
- # @return [Hash] Optimization proposal with code, metrics, and metadata
108
- def propose(task_name:, use_synthesis: false)
109
- task_def = find_task_definition(task_name)
110
- raise ArgumentError, "Task '#{task_name}' not found" unless task_def
111
-
112
- analysis = @trace_analyzer.analyze_patterns(task_name: task_name, agent_name: @agent_name)
113
- raise ArgumentError, "No execution data found for task '#{task_name}'" unless analysis
114
-
115
- traces = @trace_analyzer.query_task_traces(task_name: task_name, agent_name: @agent_name, limit: 20)
116
- detection_result = @pattern_detector.detect_pattern(analysis_result: analysis) unless use_synthesis
117
-
118
- return propose_via_synthesis(task_name, task_def, analysis, traces) if should_use_synthesis?(use_synthesis, detection_result)
119
-
120
- unless detection_result&.dig(:success)
121
- raise ArgumentError, "Cannot optimize task '#{task_name}': #{detection_result&.dig(:reason) || 'No common pattern found'}"
122
- end
123
-
124
- build_pattern_proposal(task_name, task_def, analysis, detection_result)
125
- end
126
-
127
- # Apply optimization proposal
128
- #
129
- # This method would update the agent definition in Kubernetes.
130
- # For now, it returns the updated agent code that would be applied.
131
- #
132
- # @param proposal [Hash] Proposal from #propose
133
- # @return [Hash] Result with updated agent definition
134
- def apply(proposal:)
135
- # In a real implementation, this would:
136
- # 1. Update the agent CRD with new task definition
137
- # 2. Create new ConfigMap version
138
- # 3. Trigger pod restart
139
- # For now, we return what would be applied
140
-
141
- {
142
- success: true,
143
- task_name: proposal[:task_name],
144
- updated_code: proposal[:proposed_code],
145
- action: 'would_update_agent_definition',
146
- message: "Optimization for '#{proposal[:task_name]}' ready to apply"
147
- }
148
- end
149
-
150
- private
151
-
152
- def should_use_synthesis?(use_synthesis, detection_result)
153
- (use_synthesis || !detection_result&.dig(:success)) && @task_synthesizer
154
- end
155
-
156
- def propose_via_synthesis(task_name, task_def, analysis, traces)
157
- @logger.info("Using LLM synthesis for task '#{task_name}'")
158
- synthesis_result = @task_synthesizer.synthesize(
159
- task_definition: task_def,
160
- traces: traces,
161
- available_tools: detect_available_tools,
162
- consistency_score: analysis[:consistency_score],
163
- common_pattern: analysis[:common_pattern]
164
- )
165
-
166
- raise ArgumentError, "Cannot optimize task '#{task_name}': #{synthesis_result[:explanation]}" unless synthesis_result[:is_deterministic]
167
-
168
- build_synthesis_proposal(task_name: task_name, task_def: task_def, analysis: analysis,
169
- synthesis_result: synthesis_result)
170
- end
171
-
172
- def build_pattern_proposal(task_name, task_def, analysis, detection_result)
173
- impact = calculate_impact(execution_count: analysis[:execution_count],
174
- consistency_score: analysis[:consistency_score])
175
- {
176
- task_name: task_name, current_code: format_current_code(task_def),
177
- proposed_code: extract_task_code(detection_result[:generated_code]),
178
- full_generated_code: detection_result[:generated_code],
179
- consistency_score: analysis[:consistency_score], execution_count: analysis[:execution_count],
180
- pattern: analysis[:common_pattern], performance_impact: impact,
181
- validation_violations: detection_result[:validation_violations],
182
- ready_to_deploy: detection_result[:ready_to_deploy], synthesis_method: :pattern_detection
183
- }
184
- end
185
-
186
- # Find all neural tasks in the agent definition
187
- #
188
- # @return [Array<Hash>] Array of neural task info
189
- def find_neural_tasks
190
- return [] unless @agent_definition.respond_to?(:tasks)
191
-
192
- neural_tasks = @agent_definition.tasks.select do |_name, task_def|
193
- # Neural tasks have instructions but no code block
194
- task_def.neural?
195
- end
196
-
197
- neural_tasks.map do |name, task_def|
198
- {
199
- name: name.to_s,
200
- definition: task_def
201
- }
202
- end
203
- end
204
-
205
- # Find a specific task definition by name
206
- #
207
- # @param task_name [String] Task name
208
- # @return [Dsl::TaskDefinition, nil] Task definition or nil
209
- def find_task_definition(task_name)
210
- return nil unless @agent_definition.respond_to?(:tasks)
211
-
212
- @agent_definition.tasks[task_name.to_sym]
213
- end
214
-
215
- # Format current task code for display
216
- #
217
- # @param task_def [Dsl::TaskDefinition] Task definition
218
- # @return [String] Formatted code
219
- def format_current_code(task_def)
220
- inputs_str = (task_def.inputs || {}).map { |k, v| "#{k}: '#{v}'" }.join(', ')
221
- outputs_str = (task_def.outputs || {}).map { |k, v| "#{k}: '#{v}'" }.join(', ')
222
-
223
- <<~RUBY
224
- task :#{task_def.name},
225
- instructions: "#{task_def.instructions}",
226
- inputs: { #{inputs_str} },
227
- outputs: { #{outputs_str} }
228
- RUBY
229
- end
230
-
231
- # Extract task code from full agent definition
232
- #
233
- # @param full_code [String] Complete agent definition
234
- # @return [String] Just the task definition portion
235
- def extract_task_code(full_code)
236
- # Extract just the task definition from the full agent code
237
- lines = full_code.lines
238
- task_start = lines.index { |l| l.strip.start_with?('task :') }
239
- task_end = lines.index { |l| l.strip == 'end' && l.start_with?(' end') }
240
-
241
- return full_code unless task_start && task_end
242
-
243
- lines[task_start..task_end].join
244
- end
245
-
246
- # Calculate performance impact of optimization
247
- #
248
- # @param execution_count [Integer] Number of executions observed
249
- # @param consistency_score [Float] Pattern consistency
250
- # @return [Hash] Impact metrics
251
- def calculate_impact(execution_count:, consistency_score:)
252
- # Estimates based on typical LLM vs symbolic execution
253
- avg_neural_time = 2.5 # seconds
254
- avg_neural_cost = 0.003 # dollars
255
- avg_symbolic_time = 0.1 # seconds
256
- avg_symbolic_cost = 0.0 # dollars
257
-
258
- time_saved = avg_neural_time - avg_symbolic_time
259
- cost_saved = avg_neural_cost - avg_symbolic_cost
260
-
261
- {
262
- current_avg_time: avg_neural_time,
263
- optimized_avg_time: avg_symbolic_time,
264
- time_reduction_pct: ((time_saved / avg_neural_time) * 100).round(1),
265
- current_avg_cost: avg_neural_cost,
266
- optimized_avg_cost: avg_symbolic_cost,
267
- cost_reduction_pct: ((cost_saved / avg_neural_cost) * 100).round(1),
268
- projected_monthly_savings: (cost_saved * execution_count * 30).round(2)
269
- }
270
- end
271
-
272
- # Build proposal from synthesis result
273
- #
274
- # @param task_name [String] Task name
275
- # @param task_def [Dsl::TaskDefinition] Task definition
276
- # @param analysis [Hash] Pattern analysis result
277
- # @param synthesis_result [Hash] LLM synthesis result
278
- # @return [Hash] Optimization proposal
279
- def build_synthesis_proposal(task_name:, task_def:, analysis:, synthesis_result:)
280
- impact = calculate_impact(
281
- execution_count: analysis[:execution_count],
282
- consistency_score: synthesis_result[:confidence]
283
- )
284
-
285
- {
286
- task_name: task_name,
287
- current_code: format_current_code(task_def),
288
- proposed_code: synthesis_result[:code],
289
- full_generated_code: synthesis_result[:code],
290
- consistency_score: analysis[:consistency_score],
291
- execution_count: analysis[:execution_count],
292
- pattern: analysis[:common_pattern],
293
- performance_impact: impact,
294
- validation_violations: synthesis_result[:validation_errors] || [],
295
- ready_to_deploy: synthesis_result[:validation_errors].nil?,
296
- synthesis_method: :llm_synthesis,
297
- synthesis_confidence: synthesis_result[:confidence],
298
- synthesis_explanation: synthesis_result[:explanation]
299
- }
300
- end
301
-
302
- # Detect available tools from agent definition
303
- #
304
- # @return [Array<String>] Tool names
305
- def detect_available_tools
306
- return [] unless @agent_definition.respond_to?(:mcp_servers)
307
-
308
- # Extract tool names from MCP server configurations
309
- tools = []
310
- @agent_definition.mcp_servers.each_value do |server|
311
- tools.concat(server[:tools] || []) if server.is_a?(Hash)
312
- end
313
- tools.uniq
314
- rescue StandardError
315
- []
316
- end
317
- end
318
- end
319
- end
@@ -1,260 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'logger'
4
-
5
- module LanguageOperator
6
- module Learning
7
- # Detects patterns in task execution traces and generates symbolic code
8
- #
9
- # The PatternDetector analyzes execution patterns from TraceAnalyzer and
10
- # converts deterministic neural task behavior into symbolic Ruby DSL v1 code.
11
- # This enables the learning system to automatically optimize neural tasks
12
- # into faster, cheaper symbolic implementations.
13
- #
14
- # @example Basic usage
15
- # analyzer = TraceAnalyzer.new(endpoint: ENV['OTEL_QUERY_ENDPOINT'])
16
- # validator = Agent::Safety::ASTValidator.new
17
- # detector = PatternDetector.new(trace_analyzer: analyzer, validator: validator)
18
- #
19
- # analysis = analyzer.analyze_patterns(task_name: 'fetch_user_data')
20
- # result = detector.detect_pattern(analysis_result: analysis)
21
- #
22
- # if result[:success]
23
- # puts "Generated code:"
24
- # puts result[:generated_code]
25
- # end
26
- class PatternDetector
27
- # Default minimum consistency threshold for learning
28
- DEFAULT_CONSISTENCY_THRESHOLD = 0.85
29
-
30
- # Default minimum executions required before learning
31
- DEFAULT_MIN_EXECUTIONS = 10
32
-
33
- # Minimum pattern confidence for code generation
34
- MIN_PATTERN_CONFIDENCE = 0.75
35
-
36
- # Initialize pattern detector
37
- #
38
- # @param trace_analyzer [TraceAnalyzer] Analyzer for querying execution traces
39
- # @param validator [Agent::Safety::ASTValidator] Validator for generated code
40
- # @param logger [Logger, nil] Logger instance (creates default if nil)
41
- def initialize(trace_analyzer:, validator:, logger: nil)
42
- @trace_analyzer = trace_analyzer
43
- @validator = validator
44
- @logger = logger || ::Logger.new($stdout, level: ::Logger::WARN)
45
- end
46
-
47
- # Detect patterns and generate symbolic code
48
- #
49
- # Main entry point for pattern detection. Takes analysis results from
50
- # TraceAnalyzer and generates validated symbolic code if the pattern
51
- # meets consistency and execution count thresholds.
52
- #
53
- # @param analysis_result [Hash] Result from TraceAnalyzer#analyze_patterns
54
- # @return [Hash] Detection result with generated code and metadata
55
- def detect_pattern(analysis_result:)
56
- # Validate that we can generate code from this analysis
57
- return early_rejection_result(analysis_result) unless can_generate_code?(analysis_result)
58
-
59
- # Generate symbolic code from the pattern
60
- code = generate_symbolic_code(
61
- pattern: analysis_result[:common_pattern],
62
- task_name: analysis_result[:task_name]
63
- )
64
-
65
- # Validate the generated code
66
- validation_result = validate_generated_code(code: code)
67
-
68
- # Build result
69
- {
70
- success: validation_result[:valid],
71
- task_name: analysis_result[:task_name],
72
- generated_code: code,
73
- validation_violations: validation_result[:violations],
74
- consistency_score: analysis_result[:consistency_score],
75
- execution_count: analysis_result[:execution_count],
76
- pattern: analysis_result[:common_pattern],
77
- ready_to_deploy: validation_result[:valid] && analysis_result[:consistency_score] >= 0.90,
78
- generated_at: Time.now.iso8601
79
- }
80
- end
81
-
82
- # Generate symbolic Ruby code from tool call pattern
83
- #
84
- # Converts a deterministic tool call sequence into a valid Ruby DSL v1
85
- # task definition with chained execute_task calls.
86
- #
87
- # @param pattern [String] Tool sequence like "db_fetch → cache_get → api"
88
- # @param task_name [String] Name of the task being learned
89
- # @return [String] Complete Ruby DSL v1 agent definition
90
- def generate_symbolic_code(pattern:, task_name:)
91
- sequence = extract_tool_sequence(pattern)
92
-
93
- # Generate the task code body with chained execute_task calls
94
- task_body = generate_task_code(sequence: sequence)
95
-
96
- # Wrap in complete agent definition
97
- generate_agent_wrapper(
98
- task_name: task_name,
99
- task_body: task_body
100
- )
101
- end
102
-
103
- # Validate generated code with ASTValidator
104
- #
105
- # @param code [String] Ruby code to validate
106
- # @return [Hash] Validation result with violations
107
- def validate_generated_code(code:)
108
- violations = @validator.validate(code)
109
-
110
- {
111
- valid: violations.empty?,
112
- violations: violations,
113
- safe_methods_used: true
114
- }
115
- rescue StandardError => e
116
- @logger.error("Failed to validate generated code: #{e.message}")
117
- {
118
- valid: false,
119
- violations: [{ type: :validation_error, message: e.message }],
120
- safe_methods_used: false
121
- }
122
- end
123
-
124
- private
125
-
126
- # Check if pattern analysis meets criteria for code generation
127
- #
128
- # @param analysis_result [Hash] Analysis result from TraceAnalyzer
129
- # @return [Boolean] True if code can be generated
130
- def can_generate_code?(analysis_result)
131
- return false unless analysis_result.is_a?(Hash)
132
-
133
- # Check if ready for learning flag is set
134
- return false unless analysis_result[:ready_for_learning]
135
-
136
- # Check execution count
137
- return false if analysis_result[:execution_count].to_i < DEFAULT_MIN_EXECUTIONS
138
-
139
- # Check consistency score
140
- return false if analysis_result[:consistency_score].to_f < DEFAULT_CONSISTENCY_THRESHOLD
141
-
142
- # Check pattern exists
143
- return false if analysis_result[:common_pattern].nil? || analysis_result[:common_pattern].empty?
144
-
145
- true
146
- end
147
-
148
- # Build early rejection result when criteria not met
149
- #
150
- # @param analysis_result [Hash, nil] Analysis result if available
151
- # @return [Hash] Rejection result with reason
152
- def early_rejection_result(analysis_result)
153
- if analysis_result.nil? || !analysis_result.is_a?(Hash)
154
- return {
155
- success: false,
156
- reason: 'Invalid analysis result'
157
- }
158
- end
159
-
160
- reasons = []
161
- if analysis_result[:execution_count].to_i < DEFAULT_MIN_EXECUTIONS
162
- reasons << "Insufficient executions (#{analysis_result[:execution_count]}/#{DEFAULT_MIN_EXECUTIONS})"
163
- end
164
- if analysis_result[:consistency_score].to_f < DEFAULT_CONSISTENCY_THRESHOLD
165
- reasons << "Low consistency (#{analysis_result[:consistency_score]}/#{DEFAULT_CONSISTENCY_THRESHOLD})"
166
- end
167
- reasons << 'No common pattern found' if analysis_result[:common_pattern].nil? || analysis_result[:common_pattern].empty?
168
-
169
- {
170
- success: false,
171
- task_name: analysis_result[:task_name],
172
- execution_count: analysis_result[:execution_count],
173
- consistency_score: analysis_result[:consistency_score],
174
- ready_for_learning: false,
175
- reason: reasons.join('; ')
176
- }
177
- end
178
-
179
- # Extract tool sequence from pattern string
180
- #
181
- # @param pattern [String] Pattern like "db_fetch → cache_get → api"
182
- # @return [Array<Symbol>] Sequence like [:db_fetch, :cache_get, :api]
183
- def extract_tool_sequence(pattern)
184
- pattern.split('→').map(&:strip).map(&:to_sym)
185
- end
186
-
187
- # Generate task code body with chained execute_task calls
188
- #
189
- # Creates Ruby code that executes tools in sequence, passing outputs
190
- # from each tool to the next one as inputs.
191
- #
192
- # @param sequence [Array<Symbol>] Tool sequence
193
- # @return [String] Ruby code for task body
194
- def generate_task_code(sequence:)
195
- return ' { result: {} }' if sequence.empty?
196
-
197
- lines = []
198
-
199
- # First call: use original inputs
200
- first_tool = sequence[0]
201
- lines << "step1_result = execute_task(:#{first_tool}, inputs: inputs)"
202
-
203
- # Middle calls: chain outputs from previous step
204
- if sequence.size > 1
205
- sequence[1..-2].each_with_index do |tool, index|
206
- step_num = index + 2
207
- prev_step = "step#{step_num - 1}_result"
208
- lines << "step#{step_num}_result = execute_task(:#{tool}, inputs: #{prev_step})"
209
- end
210
-
211
- # Final call
212
- final_tool = sequence[-1]
213
- last_step = "step#{sequence.size - 1}_result"
214
- lines << "final_result = execute_task(:#{final_tool}, inputs: #{last_step})"
215
- else
216
- lines << 'final_result = step1_result'
217
- end
218
-
219
- # Return statement
220
- lines << '{ result: final_result }'
221
-
222
- # Indent and join
223
- lines.map { |line| " #{line}" }.join("\n")
224
- end
225
-
226
- # Generate complete agent wrapper with task definition
227
- #
228
- # @param task_name [String] Name of the task
229
- # @param task_body [String] Generated task code body
230
- # @return [String] Complete Ruby DSL v1 agent definition
231
- def generate_agent_wrapper(task_name:, task_body:)
232
- # Convert task name to kebab-case for agent name
233
- agent_name = task_name.to_s.gsub('_', '-')
234
-
235
- <<~RUBY
236
- # frozen_string_literal: true
237
-
238
- require 'language_operator'
239
-
240
- LanguageOperator::Dsl.define_agents do
241
- agent "#{agent_name}-symbolic" do
242
- description "Symbolic implementation of #{task_name} (learned from execution patterns)"
243
-
244
- task :core_pattern,
245
- inputs: { data: 'hash' },
246
- outputs: { result: 'hash' }
247
- do |inputs|
248
- #{task_body}
249
- end
250
-
251
- main do |inputs|
252
- execute_task(:core_pattern, inputs: inputs)
253
- end
254
- end
255
- end
256
- RUBY
257
- end
258
- end
259
- end
260
- end