language-operator 0.1.65 → 0.1.67

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.
@@ -1,408 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
- require_relative '../../command_loader'
5
- require_relative '../../../constants/kubernetes_labels'
6
-
7
- module LanguageOperator
8
- module CLI
9
- module Commands
10
- module Agent
11
- # Learning monitoring and control for agents
12
- module Learning
13
- def self.included(base)
14
- base.class_eval do
15
- desc 'learning SUBCOMMAND ...ARGS', 'Monitor and control agent learning'
16
- subcommand 'learning', LearningCommands
17
- end
18
- end
19
-
20
- # Learning subcommand class
21
- class LearningCommands < BaseCommand
22
- include Constants
23
- include CLI::Helpers::ClusterValidator
24
- include CLI::Helpers::UxHelper
25
-
26
- desc 'status NAME', 'Show current learning status and optimization history'
27
- long_desc <<-DESC
28
- Display the current learning status and optimization history for an agent.
29
-
30
- Shows learned tasks, confidence scores, and automatic optimization progress
31
- managed by the operator.
32
-
33
- Examples:
34
- aictl agent learning status my-agent
35
- aictl agent learning status my-agent --cluster production
36
- DESC
37
- option :cluster, type: :string, desc: 'Override current cluster context'
38
- def status(name)
39
- handle_command_error('get learning status') do
40
- ctx = CLI::Helpers::ClusterContext.from_options(options)
41
-
42
- # Get agent to verify it exists
43
- agent = ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
44
-
45
- # Query learning status ConfigMap
46
- learning_status = get_learning_status(ctx.client, name, ctx.namespace)
47
-
48
- # Display learning information
49
- display_learning_status(agent, learning_status, ctx.name)
50
- end
51
- rescue K8s::Error::NotFound
52
- # Handle agent not found
53
- ctx = CLI::Helpers::ClusterContext.from_options(options)
54
- available_agents = ctx.client.list_resources(RESOURCE_AGENT, namespace: ctx.namespace)
55
- available_names = available_agents.map { |a| a.dig('metadata', 'name') }
56
-
57
- error = K8s::Error::NotFound.new(404, 'Not Found', RESOURCE_AGENT)
58
- CLI::Errors::Handler.handle_not_found(error,
59
- resource_type: RESOURCE_AGENT,
60
- resource_name: name,
61
- cluster: ctx.name,
62
- available_resources: available_names)
63
- end
64
-
65
- desc 'enable NAME', 'Enable automatic learning for an agent'
66
- long_desc <<-DESC
67
- Enable automatic learning for an agent by removing the learning-disabled annotation.
68
-
69
- Learning is enabled by default, so this command only needs to be used if learning
70
- was previously disabled.
71
-
72
- Examples:
73
- aictl agent learning enable my-agent
74
- DESC
75
- option :cluster, type: :string, desc: 'Override current cluster context'
76
- def enable(name)
77
- handle_command_error('enable learning') do
78
- ctx = CLI::Helpers::ClusterContext.from_options(options)
79
-
80
- # Get agent to verify it exists
81
- agent = ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
82
-
83
- # Check current status
84
- annotations = agent.dig('metadata', 'annotations')
85
- annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
86
- disabled_annotation = Constants::KubernetesLabels::LEARNING_DISABLED_LABEL
87
-
88
- unless annotations.key?(disabled_annotation)
89
- Formatters::ProgressFormatter.info("Learning is already enabled for agent '#{name}'")
90
- return
91
- end
92
-
93
- # Remove the learning-disabled annotation
94
- Formatters::ProgressFormatter.with_spinner("Enabling learning for agent '#{name}'") do
95
- remove_annotation(ctx.client, name, ctx.namespace, disabled_annotation)
96
- end
97
-
98
- Formatters::ProgressFormatter.success("Learning enabled for agent '#{name}'")
99
- end
100
- end
101
-
102
- desc 'disable NAME', 'Disable automatic learning for an agent'
103
- long_desc <<-DESC
104
- Disable automatic learning for an agent by adding the learning-disabled annotation.
105
-
106
- This prevents the operator from automatically optimizing the agent's tasks but
107
- does not affect existing learned optimizations.
108
-
109
- Examples:
110
- aictl agent learning disable my-agent
111
- DESC
112
- option :cluster, type: :string, desc: 'Override current cluster context'
113
- def disable(name)
114
- handle_command_error('disable learning') do
115
- ctx = CLI::Helpers::ClusterContext.from_options(options)
116
-
117
- # Get agent to verify it exists
118
- agent = ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
119
-
120
- # Check current status
121
- annotations = agent.dig('metadata', 'annotations')
122
- annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
123
- disabled_annotation = Constants::KubernetesLabels::LEARNING_DISABLED_LABEL
124
-
125
- if annotations.key?(disabled_annotation)
126
- Formatters::ProgressFormatter.info("Learning is already disabled for agent '#{name}'")
127
- return
128
- end
129
-
130
- # Add the learning-disabled annotation
131
- Formatters::ProgressFormatter.with_spinner("Disabling learning for agent '#{name}'") do
132
- add_annotation(ctx.client, name, ctx.namespace, disabled_annotation, 'true')
133
- end
134
-
135
- Formatters::ProgressFormatter.success("Learning disabled for agent '#{name}'")
136
- end
137
- end
138
-
139
- private
140
-
141
- def get_learning_status(client, name, namespace)
142
- config_map_name = "#{name}-learning-status"
143
- begin
144
- client.get_resource('ConfigMap', config_map_name, namespace)
145
- rescue K8s::Error::NotFound
146
- # Learning status ConfigMap doesn't exist yet - return nil
147
- nil
148
- end
149
- end
150
-
151
- def display_learning_status(agent, learning_status, cluster_name)
152
- agent.dig('metadata', 'name')
153
- annotations = agent.dig('metadata', 'annotations')
154
- annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
155
-
156
- puts
157
-
158
- # Display Agent Status box
159
- display_agent_status_box(agent, cluster_name)
160
- puts
161
-
162
- # Display Learning Status box
163
- display_learning_status_box(agent, learning_status, annotations)
164
- puts
165
-
166
- # If learning status ConfigMap exists, show detailed information
167
- if learning_status
168
- display_detailed_learning_status(learning_status)
169
- else
170
- learning_enabled = !annotations.key?(Constants::KubernetesLabels::LEARNING_DISABLED_LABEL)
171
- display_learning_explanation(learning_enabled)
172
- end
173
- end
174
-
175
- def display_detailed_learning_status(learning_status)
176
- data = learning_status['data'] || {}
177
-
178
- # Parse learning data if available
179
- if data['tasks']
180
- tasks_data = begin
181
- JSON.parse(data['tasks'])
182
- rescue StandardError
183
- {}
184
- end
185
-
186
- if tasks_data.any?
187
- puts pastel.white.bold('Learned Tasks:')
188
- tasks_data.each do |task_name, task_info|
189
- confidence = task_info['confidence'] || 0
190
- executions = task_info['executions'] || 0
191
- status = task_info['status'] || 'neural'
192
-
193
- confidence_color = determine_confidence_color(confidence)
194
-
195
- puts " #{pastel.cyan(task_name)}"
196
- puts " Status: #{format_task_status(status)}"
197
- confidence_text = pastel.send(confidence_color, "#{confidence}%")
198
- puts " Confidence: #{confidence_text} (#{executions} executions)"
199
- end
200
- puts
201
- end
202
- end
203
-
204
- # Show optimization history if available
205
- return unless data['history']
206
-
207
- history_data = begin
208
- JSON.parse(data['history'])
209
- rescue StandardError
210
- []
211
- end
212
-
213
- return unless history_data.any?
214
-
215
- puts pastel.white.bold('Optimization History:')
216
- history_data.last(5).each do |event|
217
- timestamp = event['timestamp'] || 'Unknown'
218
- action = event['action'] || 'Unknown'
219
- task = event['task'] || 'Unknown'
220
-
221
- puts " #{pastel.dim(timestamp)} - #{action} #{pastel.cyan(task)}"
222
- end
223
- puts
224
- end
225
-
226
- def format_task_status(status)
227
- case status
228
- when 'symbolic'
229
- pastel.green('Learned (Symbolic)')
230
- when 'neural'
231
- pastel.yellow('Learning (Neural)')
232
- when 'hybrid'
233
- pastel.blue('Hybrid')
234
- else
235
- pastel.dim(status.capitalize)
236
- end
237
- end
238
-
239
- def add_annotation(client, name, namespace, annotation_key, annotation_value)
240
- # Get current agent
241
- agent = client.get_resource(RESOURCE_AGENT, name, namespace)
242
-
243
- # Add annotation
244
- annotations = agent.dig('metadata', 'annotations')
245
- annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
246
- annotations[annotation_key] = annotation_value
247
- agent['metadata']['annotations'] = annotations
248
-
249
- # Update the agent
250
- client.update_resource(agent)
251
- end
252
-
253
- def remove_annotation(client, name, namespace, annotation_key)
254
- # Get current agent
255
- agent = client.get_resource(RESOURCE_AGENT, name, namespace)
256
-
257
- # Remove annotation
258
- annotations = agent.dig('metadata', 'annotations')
259
- annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
260
- annotations.delete(annotation_key)
261
- agent['metadata']['annotations'] = annotations
262
-
263
- # Update the agent
264
- client.update_resource(agent)
265
- end
266
-
267
- def format_agent_timestamp(agent)
268
- created_time = agent.dig('metadata', 'creationTimestamp')
269
- return 'Unknown' unless created_time
270
-
271
- begin
272
- Time.parse(created_time).strftime('%Y-%m-%d %H:%M:%S UTC')
273
- rescue StandardError
274
- 'Unknown'
275
- end
276
- end
277
-
278
- def display_agent_status_box(agent, cluster_name)
279
- agent_name = agent.dig('metadata', 'name')
280
- timestamp = format_agent_timestamp(agent)
281
-
282
- # Get agent operational status
283
- status = agent['status']
284
- if status && status['conditions']
285
- ready_condition = status['conditions'].find { |c| c['type'] == 'Ready' }
286
- if ready_condition
287
- begin
288
- last_activity = Time.parse(ready_condition['lastTransitionTime']).strftime('%Y-%m-%d %H:%M:%S UTC')
289
- status_text = ready_condition['status'] == 'True' ? 'Ready' : 'Not Ready'
290
- status_colored = ready_condition['status'] == 'True' ? pastel.green(status_text) : pastel.yellow(status_text)
291
- rescue StandardError
292
- last_activity = 'Unknown'
293
- status_colored = pastel.dim('Unknown')
294
- end
295
- else
296
- last_activity = 'Unknown'
297
- status_colored = pastel.dim('Unknown')
298
- end
299
- else
300
- last_activity = 'Unknown'
301
- status_colored = pastel.dim('Unknown')
302
- end
303
-
304
- highlighted_box(
305
- title: 'Agent Status',
306
- color: :yellow,
307
- rows: {
308
- 'Name' => pastel.white.bold(agent_name),
309
- 'Cluster' => cluster_name,
310
- 'Created' => timestamp,
311
- 'Last Activity' => last_activity,
312
- 'Status' => status_colored
313
- }
314
- )
315
- end
316
-
317
- def display_learning_status_box(_agent, learning_status, annotations)
318
- learning_enabled = !annotations.key?(Constants::KubernetesLabels::LEARNING_DISABLED_LABEL)
319
- status_color = learning_enabled ? :green : :yellow
320
- status_text = learning_enabled ? 'Enabled' : 'Disabled'
321
-
322
- # Parse execution summary from ConfigMap if available
323
- execution_summary = parse_execution_summary(learning_status)
324
-
325
- if execution_summary
326
- total_executions = execution_summary['totalExecutions'] || 0
327
- learning_threshold = execution_summary['learningThreshold'] || 10
328
- execution_summary['successRate'] || 0.0
329
- last_execution = execution_summary['lastExecution']
330
-
331
- runs_processed = "#{total_executions}/#{learning_threshold}"
332
- progress_percent = [(total_executions.to_f / learning_threshold * 100).round, 100].min
333
- progress = "#{progress_percent}% toward learning threshold"
334
-
335
- last_run = if last_execution && !last_execution.empty?
336
- begin
337
- Time.parse(last_execution).strftime('%Y-%m-%d %H:%M:%S UTC')
338
- rescue StandardError
339
- 'Unknown'
340
- end
341
- else
342
- 'No executions yet'
343
- end
344
- else
345
- runs_processed = 'No data'
346
- progress = 'Waiting for agent executions'
347
- last_run = 'No executions yet'
348
- end
349
-
350
- highlighted_box(
351
- title: 'Learning Status',
352
- color: :cyan,
353
- rows: {
354
- 'Learning' => pastel.send(status_color).bold(status_text),
355
- 'Threshold' => "#{pastel.cyan('10 successful runs')} (auto-learning trigger)",
356
- 'Confidence Target' => "#{pastel.cyan('85%')} (pattern detection)",
357
- 'Runs Processed' => runs_processed,
358
- 'Progress' => progress,
359
- 'Last Execution' => last_run
360
- }
361
- )
362
- end
363
-
364
- def display_learning_explanation(learning_enabled)
365
- if learning_enabled
366
- puts pastel.dim('Learning is enabled and will begin automatically after the agent completes 10 successful runs.')
367
- puts pastel.dim('Neural tasks will be analyzed for patterns and converted to symbolic implementations.')
368
- else
369
- puts pastel.yellow('Learning is disabled for this agent.')
370
- puts pastel.dim('Enable learning to allow automatic task optimization after sufficient executions.')
371
- end
372
- puts
373
- puts pastel.dim('Note: Execution metrics will be available once the agent starts running and')
374
- puts pastel.dim('the operator begins collecting telemetry data.')
375
- puts
376
- end
377
-
378
- def determine_confidence_color(confidence)
379
- if confidence >= 85
380
- :green
381
- elsif confidence >= 70
382
- :yellow
383
- else
384
- :red
385
- end
386
- end
387
-
388
- def parse_execution_summary(learning_status)
389
- return nil unless learning_status
390
-
391
- data = learning_status['data']
392
- return nil unless data
393
-
394
- execution_summary_json = data['execution-summary']
395
- return nil unless execution_summary_json
396
-
397
- begin
398
- JSON.parse(execution_summary_json)
399
- rescue StandardError
400
- nil
401
- end
402
- end
403
- end
404
- end
405
- end
406
- end
407
- end
408
- end
@@ -1,66 +0,0 @@
1
- require 'language_operator'
2
-
3
- agent "s003" do
4
- description "Build a story one sentence at a time, with one new sentence every hour"
5
- mode :scheduled
6
- schedule "0 * * * *"
7
-
8
- task :read_existing_story,
9
- inputs: { },
10
- outputs: { content: 'string', sentence_count: 'integer' } do |inputs|
11
- file_info = execute_tool('get_file_info', { path: 'story.txt' })
12
- if file_info.is_a?(Hash) && file_info[:error]
13
- { content: '', sentence_count: 0 }
14
- else
15
- content = execute_tool('read_file', { path: 'story.txt' })
16
- sentence_count = content.split(/[.!?]+\s*/).length
17
- { content: content, sentence_count: sentence_count }
18
- end
19
- end
20
-
21
- task :generate_next_sentence,
22
- instructions: "Generate exactly one new sentence to continue this story. Maintain consistent tone and style. Only output the new sentence.",
23
- inputs: { existing_content: 'string' },
24
- outputs: { sentence: 'string' }
25
-
26
- task :append_to_story,
27
- inputs: { sentence: 'string' },
28
- outputs: { success: 'boolean', total_sentences: 'integer' } do |inputs|
29
- existing_content = execute_tool('read_file', { path: 'story.txt' })
30
-
31
- # Determine if we need to add a newline before appending
32
- content_to_write = if existing_content.empty?
33
- inputs[:sentence]
34
- else
35
- "\n#{inputs[:sentence]}"
36
- end
37
-
38
- # Append the new sentence to the file
39
- execute_tool('write_file', { path: 'story.txt', content: existing_content + content_to_write })
40
-
41
- # Count total sentences by splitting on newlines and filtering empty lines
42
- sentences = existing_content.split("\n").reject(&:empty?)
43
- new_sentence_count = sentences.length + 1
44
-
45
- { success: true, total_sentences: new_sentence_count }
46
- end
47
-
48
- main do |inputs|
49
- story_data = execute_task(:read_existing_story)
50
- new_sentence = execute_task(:generate_next_sentence,
51
- inputs: { existing_content: story_data[:content] })
52
- result = execute_task(:append_to_story,
53
- inputs: { sentence: new_sentence[:sentence] })
54
- { sentence: new_sentence[:sentence], total: result[:total_sentences] }
55
- end
56
-
57
- constraints do
58
- max_iterations 999999
59
- timeout "10m"
60
- end
61
-
62
- output do |outputs|
63
- puts "Added sentence: #{outputs[:sentence]}"
64
- puts "Story now has #{outputs[:total]} sentences"
65
- end
66
- end
@@ -1,41 +0,0 @@
1
- require 'language_operator'
2
-
3
- agent "s003" do
4
- description "Build a story one sentence at a time, with one new sentence every hour"
5
- mode :scheduled
6
- schedule "0 * * * *"
7
-
8
- task :read_existing_story,
9
- instructions: "Read the story.txt file from workspace. If it doesn't exist, return empty string. Return the content and count of sentences.",
10
- inputs: {},
11
- outputs: { content: 'string', sentence_count: 'integer' }
12
-
13
- task :generate_next_sentence,
14
- instructions: "Generate exactly one new sentence to continue this story. Maintain consistent tone and style. Only output the new sentence.",
15
- inputs: { existing_content: 'string' },
16
- outputs: { sentence: 'string' }
17
-
18
- task :append_to_story,
19
- instructions: "Append the new sentence to story.txt in workspace. If the file has existing content, add a newline first.",
20
- inputs: { sentence: 'string' },
21
- outputs: { success: 'boolean', total_sentences: 'integer' }
22
-
23
- main do |inputs|
24
- story_data = execute_task(:read_existing_story)
25
- new_sentence = execute_task(:generate_next_sentence,
26
- inputs: { existing_content: story_data[:content] })
27
- result = execute_task(:append_to_story,
28
- inputs: { sentence: new_sentence[:sentence] })
29
- { sentence: new_sentence[:sentence], total: result[:total_sentences] }
30
- end
31
-
32
- constraints do
33
- max_iterations 999999
34
- timeout "10m"
35
- end
36
-
37
- output do |outputs|
38
- puts "Added sentence: #{outputs[:sentence]}"
39
- puts "Story now has #{outputs[:total]} sentences"
40
- end
41
- end