language-operator 0.1.63 → 0.1.66

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.plan.md +127 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +4 -1
  6. data/Makefile +34 -80
  7. data/README.md +20 -1
  8. data/components/agent/Gemfile +1 -1
  9. data/docs/cheat-sheet.md +173 -0
  10. data/docs/observability.md +208 -0
  11. data/lib/language_operator/agent/base.rb +10 -1
  12. data/lib/language_operator/agent/event_config.rb +172 -0
  13. data/lib/language_operator/agent/safety/ast_validator.rb +1 -1
  14. data/lib/language_operator/agent/safety/safe_executor.rb +5 -1
  15. data/lib/language_operator/agent/task_executor.rb +97 -7
  16. data/lib/language_operator/agent/telemetry.rb +25 -3
  17. data/lib/language_operator/agent/web_server.rb +6 -9
  18. data/lib/language_operator/agent.rb +24 -14
  19. data/lib/language_operator/cli/commands/agent/base.rb +155 -64
  20. data/lib/language_operator/cli/commands/agent/code_operations.rb +157 -16
  21. data/lib/language_operator/cli/commands/cluster.rb +2 -2
  22. data/lib/language_operator/cli/commands/status.rb +2 -2
  23. data/lib/language_operator/cli/commands/system/synthesize.rb +1 -1
  24. data/lib/language_operator/cli/errors/suggestions.rb +1 -1
  25. data/lib/language_operator/cli/formatters/value_formatter.rb +1 -1
  26. data/lib/language_operator/cli/helpers/ux_helper.rb +3 -4
  27. data/lib/language_operator/config.rb +3 -3
  28. data/lib/language_operator/constants/kubernetes_labels.rb +2 -2
  29. data/lib/language_operator/constants.rb +1 -0
  30. data/lib/language_operator/dsl/task_definition.rb +18 -7
  31. data/lib/language_operator/instrumentation/task_tracer.rb +44 -3
  32. data/lib/language_operator/kubernetes/client.rb +112 -1
  33. data/lib/language_operator/templates/schema/CHANGELOG.md +28 -0
  34. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  35. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  36. data/lib/language_operator/type_coercion.rb +22 -8
  37. data/lib/language_operator/version.rb +1 -1
  38. data/synth/002/agent.rb +23 -12
  39. data/synth/002/output.log +88 -15
  40. data/synth/003/Makefile +17 -4
  41. data/synth/003/agent.txt +1 -1
  42. data/synth/004/Makefile +54 -0
  43. data/synth/004/README.md +281 -0
  44. data/synth/004/instructions.txt +1 -0
  45. metadata +11 -6
  46. data/lib/language_operator/cli/commands/agent/learning.rb +0 -289
  47. data/synth/003/agent.optimized.rb +0 -66
  48. data/synth/003/agent.synthesized.rb +0 -41
@@ -1,289 +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
- disabled_annotation = Constants::KubernetesLabels::LEARNING_DISABLED_LABEL
86
-
87
- unless annotations.key?(disabled_annotation)
88
- Formatters::ProgressFormatter.info("Learning is already enabled for agent '#{name}'")
89
- return
90
- end
91
-
92
- # Remove the learning-disabled annotation
93
- Formatters::ProgressFormatter.with_spinner("Enabling learning for agent '#{name}'") do
94
- remove_annotation(ctx.client, name, ctx.namespace, disabled_annotation)
95
- end
96
-
97
- Formatters::ProgressFormatter.success("Learning enabled for agent '#{name}'")
98
- end
99
- end
100
-
101
- desc 'disable NAME', 'Disable automatic learning for an agent'
102
- long_desc <<-DESC
103
- Disable automatic learning for an agent by adding the learning-disabled annotation.
104
-
105
- This prevents the operator from automatically optimizing the agent's tasks but
106
- does not affect existing learned optimizations.
107
-
108
- Examples:
109
- aictl agent learning disable my-agent
110
- DESC
111
- option :cluster, type: :string, desc: 'Override current cluster context'
112
- def disable(name)
113
- handle_command_error('disable learning') do
114
- ctx = CLI::Helpers::ClusterContext.from_options(options)
115
-
116
- # Get agent to verify it exists
117
- agent = ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
118
-
119
- # Check current status
120
- annotations = agent.dig('metadata', 'annotations') || {}
121
- disabled_annotation = Constants::KubernetesLabels::LEARNING_DISABLED_LABEL
122
-
123
- if annotations.key?(disabled_annotation)
124
- Formatters::ProgressFormatter.info("Learning is already disabled for agent '#{name}'")
125
- return
126
- end
127
-
128
- # Add the learning-disabled annotation
129
- Formatters::ProgressFormatter.with_spinner("Disabling learning for agent '#{name}'") do
130
- add_annotation(ctx.client, name, ctx.namespace, disabled_annotation, 'true')
131
- end
132
-
133
- Formatters::ProgressFormatter.success("Learning disabled for agent '#{name}'")
134
- end
135
- end
136
-
137
- private
138
-
139
- def get_learning_status(client, name, namespace)
140
- config_map_name = "#{name}-learning-status"
141
- begin
142
- client.get_resource('ConfigMap', config_map_name, namespace)
143
- rescue K8s::Error::NotFound
144
- # Learning status ConfigMap doesn't exist yet - return nil
145
- nil
146
- end
147
- end
148
-
149
- def display_learning_status(agent, learning_status, cluster_name)
150
- agent_name = agent.dig('metadata', 'name')
151
- annotations = agent.dig('metadata', 'annotations') || {}
152
-
153
- puts
154
-
155
- # Learning enablement status
156
- learning_enabled = !annotations.key?(Constants::KubernetesLabels::LEARNING_DISABLED_LABEL)
157
- status_color = learning_enabled ? :green : :yellow
158
- status_text = learning_enabled ? 'Enabled' : 'Disabled'
159
-
160
- highlighted_box(
161
- title: 'Learning Status',
162
- rows: {
163
- 'Agent' => pastel.white.bold(agent_name),
164
- 'Cluster' => cluster_name,
165
- 'Learning' => pastel.send(status_color).bold(status_text),
166
- 'Last Updated' => agent.dig('metadata', 'resourceVersion') || 'Unknown'
167
- }
168
- )
169
- puts
170
-
171
- # If learning status ConfigMap exists, show detailed information
172
- if learning_status
173
- display_detailed_learning_status(learning_status)
174
- else
175
- puts pastel.dim('No learning status data available yet.')
176
- puts pastel.dim('Learning data will appear after the agent has run and the operator has analyzed its behavior.')
177
- puts
178
- end
179
-
180
- # Show next steps
181
- puts pastel.white.bold('Available Commands:')
182
- if learning_enabled
183
- puts pastel.dim(" aictl agent learning disable #{agent_name}")
184
- else
185
- puts pastel.dim(" aictl agent learning enable #{agent_name}")
186
- end
187
- puts pastel.dim(" aictl agent versions #{agent_name}")
188
- puts pastel.dim(" aictl agent inspect #{agent_name}")
189
- end
190
-
191
- def display_detailed_learning_status(learning_status)
192
- data = learning_status['data'] || {}
193
-
194
- # Parse learning data if available
195
- if data['tasks']
196
- tasks_data = begin
197
- JSON.parse(data['tasks'])
198
- rescue StandardError
199
- {}
200
- end
201
-
202
- if tasks_data.any?
203
- puts pastel.white.bold('Learned Tasks:')
204
- tasks_data.each do |task_name, task_info|
205
- confidence = task_info['confidence'] || 0
206
- executions = task_info['executions'] || 0
207
- status = task_info['status'] || 'neural'
208
-
209
- confidence_color = if confidence >= 85
210
- :green
211
- else
212
- confidence >= 70 ? :yellow : :red
213
- end
214
-
215
- puts " #{pastel.cyan(task_name)}"
216
- puts " Status: #{format_task_status(status)}"
217
- confidence_text = pastel.send(confidence_color, "#{confidence}%")
218
- puts " Confidence: #{confidence_text} (#{executions} executions)"
219
- end
220
- puts
221
- end
222
- end
223
-
224
- # Show optimization history if available
225
- return unless data['history']
226
-
227
- history_data = begin
228
- JSON.parse(data['history'])
229
- rescue StandardError
230
- []
231
- end
232
-
233
- return unless history_data.any?
234
-
235
- puts pastel.white.bold('Optimization History:')
236
- history_data.last(5).each do |event|
237
- timestamp = event['timestamp'] || 'Unknown'
238
- action = event['action'] || 'Unknown'
239
- task = event['task'] || 'Unknown'
240
-
241
- puts " #{pastel.dim(timestamp)} - #{action} #{pastel.cyan(task)}"
242
- end
243
- puts
244
- end
245
-
246
- def format_task_status(status)
247
- case status
248
- when 'symbolic'
249
- pastel.green('Learned (Symbolic)')
250
- when 'neural'
251
- pastel.yellow('Learning (Neural)')
252
- when 'hybrid'
253
- pastel.blue('Hybrid')
254
- else
255
- pastel.dim(status.capitalize)
256
- end
257
- end
258
-
259
- def add_annotation(client, name, namespace, annotation_key, annotation_value)
260
- # Get current agent
261
- agent = client.get_resource(RESOURCE_AGENT, name, namespace)
262
-
263
- # Add annotation
264
- annotations = agent.dig('metadata', 'annotations') || {}
265
- annotations[annotation_key] = annotation_value
266
- agent['metadata']['annotations'] = annotations
267
-
268
- # Update the agent
269
- client.update_resource(agent)
270
- end
271
-
272
- def remove_annotation(client, name, namespace, annotation_key)
273
- # Get current agent
274
- agent = client.get_resource(RESOURCE_AGENT, name, namespace)
275
-
276
- # Remove annotation
277
- annotations = agent.dig('metadata', 'annotations') || {}
278
- annotations.delete(annotation_key)
279
- agent['metadata']['annotations'] = annotations
280
-
281
- # Update the agent
282
- client.update_resource(agent)
283
- end
284
- end
285
- end
286
- end
287
- end
288
- end
289
- 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