language-operator 0.1.59 → 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 (144) 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 +14 -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 +369 -68
  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 +31 -1
  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/task_definition.rb +7 -6
  95. data/lib/language_operator/dsl.rb +153 -6
  96. data/lib/language_operator/errors.rb +50 -0
  97. data/lib/language_operator/kubernetes/client.rb +11 -6
  98. data/lib/language_operator/kubernetes/resource_builder.rb +58 -84
  99. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  100. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  101. data/lib/language_operator/type_coercion.rb +118 -34
  102. data/lib/language_operator/utils/secure_path.rb +74 -0
  103. data/lib/language_operator/utils.rb +7 -0
  104. data/lib/language_operator/validators.rb +54 -2
  105. data/lib/language_operator/version.rb +1 -1
  106. data/synth/001/Makefile +10 -2
  107. data/synth/001/agent.rb +16 -15
  108. data/synth/001/output.log +27 -10
  109. data/synth/002/Makefile +10 -2
  110. data/synth/003/Makefile +3 -3
  111. data/synth/003/README.md +205 -133
  112. data/synth/003/agent.optimized.rb +66 -0
  113. data/synth/003/agent.synthesized.rb +41 -0
  114. metadata +111 -35
  115. data/docs/dsl/agent-reference.md +0 -604
  116. data/docs/dsl/mcp-integration.md +0 -1177
  117. data/docs/dsl/webhooks.md +0 -932
  118. data/docs/dsl/workflows.md +0 -744
  119. data/lib/language_operator/cli/commands/agent.rb +0 -1712
  120. data/lib/language_operator/cli/commands/model.rb +0 -366
  121. data/lib/language_operator/cli/commands/system.rb +0 -1259
  122. data/lib/language_operator/cli/commands/tool.rb +0 -654
  123. data/lib/language_operator/cli/formatters/optimization_formatter.rb +0 -226
  124. data/lib/language_operator/cli/helpers/pastel_helper.rb +0 -24
  125. data/lib/language_operator/learning/adapters/base_adapter.rb +0 -147
  126. data/lib/language_operator/learning/adapters/jaeger_adapter.rb +0 -218
  127. data/lib/language_operator/learning/adapters/signoz_adapter.rb +0 -432
  128. data/lib/language_operator/learning/adapters/tempo_adapter.rb +0 -236
  129. data/lib/language_operator/learning/optimizer.rb +0 -318
  130. data/lib/language_operator/learning/pattern_detector.rb +0 -260
  131. data/lib/language_operator/learning/task_synthesizer.rb +0 -261
  132. data/lib/language_operator/learning/trace_analyzer.rb +0 -280
  133. data/lib/language_operator/templates/task_synthesis.tmpl +0 -97
  134. data/lib/language_operator/ux/base.rb +0 -81
  135. data/lib/language_operator/ux/concerns/README.md +0 -155
  136. data/lib/language_operator/ux/concerns/headings.rb +0 -90
  137. data/lib/language_operator/ux/create_agent.rb +0 -255
  138. data/lib/language_operator/ux/create_model.rb +0 -267
  139. data/lib/language_operator/ux/quickstart.rb +0 -594
  140. data/synth/003/agent.rb +0 -41
  141. data/synth/003/output.log +0 -68
  142. /data/docs/{architecture/agent-runtime.md → agent-internals.md} +0 -0
  143. /data/docs/{dsl/chat-endpoints.md → chat-endpoints.md} +0 -0
  144. /data/docs/{dsl/SCHEMA_VERSION.md → schema-versioning.md} +0 -0
@@ -1,318 +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
- min_executions: min_executions,
78
- consistency_threshold: min_consistency,
79
- time_range: time_range
80
- )
81
-
82
- next unless analysis
83
-
84
- opportunities << {
85
- task_name: task[:name],
86
- task_definition: task[:definition],
87
- execution_count: analysis[:execution_count],
88
- consistency_score: analysis[:consistency_score],
89
- ready_for_learning: analysis[:ready_for_learning],
90
- common_pattern: analysis[:common_pattern],
91
- reason: analysis[:reason]
92
- }
93
- end
94
-
95
- opportunities
96
- end
97
-
98
- # Generate optimization proposal for a specific task
99
- #
100
- # Uses PatternDetector to generate symbolic code and calculates
101
- # the performance impact of the optimization. Falls back to TaskSynthesizer
102
- # (LLM-based) if pattern detection fails and synthesizer is available.
103
- #
104
- # @param task_name [String] Name of task to optimize
105
- # @param use_synthesis [Boolean] Force use of LLM synthesis instead of pattern detection
106
- # @return [Hash] Optimization proposal with code, metrics, and metadata
107
- def propose(task_name:, use_synthesis: false)
108
- task_def = find_task_definition(task_name)
109
- raise ArgumentError, "Task '#{task_name}' not found" unless task_def
110
-
111
- analysis = @trace_analyzer.analyze_patterns(task_name: task_name)
112
- raise ArgumentError, "No execution data found for task '#{task_name}'" unless analysis
113
-
114
- traces = @trace_analyzer.query_task_traces(task_name: task_name, limit: 20)
115
- detection_result = @pattern_detector.detect_pattern(analysis_result: analysis) unless use_synthesis
116
-
117
- return propose_via_synthesis(task_name, task_def, analysis, traces) if should_use_synthesis?(use_synthesis, detection_result)
118
-
119
- unless detection_result&.dig(:success)
120
- raise ArgumentError, "Cannot optimize task '#{task_name}': #{detection_result&.dig(:reason) || 'No common pattern found'}"
121
- end
122
-
123
- build_pattern_proposal(task_name, task_def, analysis, detection_result)
124
- end
125
-
126
- # Apply optimization proposal
127
- #
128
- # This method would update the agent definition in Kubernetes.
129
- # For now, it returns the updated agent code that would be applied.
130
- #
131
- # @param proposal [Hash] Proposal from #propose
132
- # @return [Hash] Result with updated agent definition
133
- def apply(proposal:)
134
- # In a real implementation, this would:
135
- # 1. Update the agent CRD with new task definition
136
- # 2. Create new ConfigMap version
137
- # 3. Trigger pod restart
138
- # For now, we return what would be applied
139
-
140
- {
141
- success: true,
142
- task_name: proposal[:task_name],
143
- updated_code: proposal[:proposed_code],
144
- action: 'would_update_agent_definition',
145
- message: "Optimization for '#{proposal[:task_name]}' ready to apply"
146
- }
147
- end
148
-
149
- private
150
-
151
- def should_use_synthesis?(use_synthesis, detection_result)
152
- (use_synthesis || !detection_result&.dig(:success)) && @task_synthesizer
153
- end
154
-
155
- def propose_via_synthesis(task_name, task_def, analysis, traces)
156
- @logger.info("Using LLM synthesis for task '#{task_name}'")
157
- synthesis_result = @task_synthesizer.synthesize(
158
- task_definition: task_def,
159
- traces: traces,
160
- available_tools: detect_available_tools,
161
- consistency_score: analysis[:consistency_score],
162
- common_pattern: analysis[:common_pattern]
163
- )
164
-
165
- raise ArgumentError, "Cannot optimize task '#{task_name}': #{synthesis_result[:explanation]}" unless synthesis_result[:is_deterministic]
166
-
167
- build_synthesis_proposal(task_name: task_name, task_def: task_def, analysis: analysis,
168
- synthesis_result: synthesis_result)
169
- end
170
-
171
- def build_pattern_proposal(task_name, task_def, analysis, detection_result)
172
- impact = calculate_impact(execution_count: analysis[:execution_count],
173
- consistency_score: analysis[:consistency_score])
174
- {
175
- task_name: task_name, current_code: format_current_code(task_def),
176
- proposed_code: extract_task_code(detection_result[:generated_code]),
177
- full_generated_code: detection_result[:generated_code],
178
- consistency_score: analysis[:consistency_score], execution_count: analysis[:execution_count],
179
- pattern: analysis[:common_pattern], performance_impact: impact,
180
- validation_violations: detection_result[:validation_violations],
181
- ready_to_deploy: detection_result[:ready_to_deploy], synthesis_method: :pattern_detection
182
- }
183
- end
184
-
185
- # Find all neural tasks in the agent definition
186
- #
187
- # @return [Array<Hash>] Array of neural task info
188
- def find_neural_tasks
189
- return [] unless @agent_definition.respond_to?(:tasks)
190
-
191
- neural_tasks = @agent_definition.tasks.select do |_name, task_def|
192
- # Neural tasks have instructions but no code block
193
- task_def.neural?
194
- end
195
-
196
- neural_tasks.map do |name, task_def|
197
- {
198
- name: name.to_s,
199
- definition: task_def
200
- }
201
- end
202
- end
203
-
204
- # Find a specific task definition by name
205
- #
206
- # @param task_name [String] Task name
207
- # @return [Dsl::TaskDefinition, nil] Task definition or nil
208
- def find_task_definition(task_name)
209
- return nil unless @agent_definition.respond_to?(:tasks)
210
-
211
- @agent_definition.tasks[task_name.to_sym]
212
- end
213
-
214
- # Format current task code for display
215
- #
216
- # @param task_def [Dsl::TaskDefinition] Task definition
217
- # @return [String] Formatted code
218
- def format_current_code(task_def)
219
- inputs_str = (task_def.inputs || {}).map { |k, v| "#{k}: '#{v}'" }.join(', ')
220
- outputs_str = (task_def.outputs || {}).map { |k, v| "#{k}: '#{v}'" }.join(', ')
221
-
222
- <<~RUBY
223
- task :#{task_def.name},
224
- instructions: "#{task_def.instructions}",
225
- inputs: { #{inputs_str} },
226
- outputs: { #{outputs_str} }
227
- RUBY
228
- end
229
-
230
- # Extract task code from full agent definition
231
- #
232
- # @param full_code [String] Complete agent definition
233
- # @return [String] Just the task definition portion
234
- def extract_task_code(full_code)
235
- # Extract just the task definition from the full agent code
236
- lines = full_code.lines
237
- task_start = lines.index { |l| l.strip.start_with?('task :') }
238
- task_end = lines.index { |l| l.strip == 'end' && l.start_with?(' end') }
239
-
240
- return full_code unless task_start && task_end
241
-
242
- lines[task_start..task_end].join
243
- end
244
-
245
- # Calculate performance impact of optimization
246
- #
247
- # @param execution_count [Integer] Number of executions observed
248
- # @param consistency_score [Float] Pattern consistency
249
- # @return [Hash] Impact metrics
250
- def calculate_impact(execution_count:, consistency_score:)
251
- # Estimates based on typical LLM vs symbolic execution
252
- avg_neural_time = 2.5 # seconds
253
- avg_neural_cost = 0.003 # dollars
254
- avg_symbolic_time = 0.1 # seconds
255
- avg_symbolic_cost = 0.0 # dollars
256
-
257
- time_saved = avg_neural_time - avg_symbolic_time
258
- cost_saved = avg_neural_cost - avg_symbolic_cost
259
-
260
- {
261
- current_avg_time: avg_neural_time,
262
- optimized_avg_time: avg_symbolic_time,
263
- time_reduction_pct: ((time_saved / avg_neural_time) * 100).round(1),
264
- current_avg_cost: avg_neural_cost,
265
- optimized_avg_cost: avg_symbolic_cost,
266
- cost_reduction_pct: ((cost_saved / avg_neural_cost) * 100).round(1),
267
- projected_monthly_savings: (cost_saved * execution_count * 30).round(2)
268
- }
269
- end
270
-
271
- # Build proposal from synthesis result
272
- #
273
- # @param task_name [String] Task name
274
- # @param task_def [Dsl::TaskDefinition] Task definition
275
- # @param analysis [Hash] Pattern analysis result
276
- # @param synthesis_result [Hash] LLM synthesis result
277
- # @return [Hash] Optimization proposal
278
- def build_synthesis_proposal(task_name:, task_def:, analysis:, synthesis_result:)
279
- impact = calculate_impact(
280
- execution_count: analysis[:execution_count],
281
- consistency_score: synthesis_result[:confidence]
282
- )
283
-
284
- {
285
- task_name: task_name,
286
- current_code: format_current_code(task_def),
287
- proposed_code: synthesis_result[:code],
288
- full_generated_code: synthesis_result[:code],
289
- consistency_score: analysis[:consistency_score],
290
- execution_count: analysis[:execution_count],
291
- pattern: analysis[:common_pattern],
292
- performance_impact: impact,
293
- validation_violations: synthesis_result[:validation_errors] || [],
294
- ready_to_deploy: synthesis_result[:validation_errors].nil?,
295
- synthesis_method: :llm_synthesis,
296
- synthesis_confidence: synthesis_result[:confidence],
297
- synthesis_explanation: synthesis_result[:explanation]
298
- }
299
- end
300
-
301
- # Detect available tools from agent definition
302
- #
303
- # @return [Array<String>] Tool names
304
- def detect_available_tools
305
- return [] unless @agent_definition.respond_to?(:mcp_servers)
306
-
307
- # Extract tool names from MCP server configurations
308
- tools = []
309
- @agent_definition.mcp_servers.each_value do |server|
310
- tools.concat(server[:tools] || []) if server.is_a?(Hash)
311
- end
312
- tools.uniq
313
- rescue StandardError
314
- []
315
- end
316
- end
317
- end
318
- 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