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.
- checksums.yaml +4 -4
- data/.plan.md +127 -0
- data/.rspec +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +4 -1
- data/Makefile +34 -80
- data/README.md +20 -1
- data/components/agent/Gemfile +1 -1
- data/docs/cheat-sheet.md +173 -0
- data/docs/observability.md +208 -0
- data/lib/language_operator/agent/base.rb +10 -1
- data/lib/language_operator/agent/event_config.rb +172 -0
- data/lib/language_operator/agent/safety/ast_validator.rb +1 -1
- data/lib/language_operator/agent/safety/safe_executor.rb +5 -1
- data/lib/language_operator/agent/task_executor.rb +97 -7
- data/lib/language_operator/agent/telemetry.rb +25 -3
- data/lib/language_operator/agent/web_server.rb +6 -9
- data/lib/language_operator/agent.rb +24 -14
- data/lib/language_operator/cli/commands/agent/base.rb +155 -64
- data/lib/language_operator/cli/commands/agent/code_operations.rb +157 -16
- data/lib/language_operator/cli/commands/cluster.rb +2 -2
- data/lib/language_operator/cli/commands/status.rb +2 -2
- data/lib/language_operator/cli/commands/system/synthesize.rb +1 -1
- data/lib/language_operator/cli/errors/suggestions.rb +1 -1
- data/lib/language_operator/cli/formatters/value_formatter.rb +1 -1
- data/lib/language_operator/cli/helpers/ux_helper.rb +3 -4
- data/lib/language_operator/config.rb +3 -3
- data/lib/language_operator/constants/kubernetes_labels.rb +2 -2
- data/lib/language_operator/constants.rb +1 -0
- data/lib/language_operator/dsl/task_definition.rb +18 -7
- data/lib/language_operator/instrumentation/task_tracer.rb +44 -3
- data/lib/language_operator/kubernetes/client.rb +112 -1
- data/lib/language_operator/templates/schema/CHANGELOG.md +28 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
- data/lib/language_operator/type_coercion.rb +22 -8
- data/lib/language_operator/version.rb +1 -1
- data/synth/002/agent.rb +23 -12
- data/synth/002/output.log +88 -15
- data/synth/003/Makefile +17 -4
- data/synth/003/agent.txt +1 -1
- data/synth/004/Makefile +54 -0
- data/synth/004/README.md +281 -0
- data/synth/004/instructions.txt +1 -0
- metadata +11 -6
- data/lib/language_operator/cli/commands/agent/learning.rb +0 -289
- data/synth/003/agent.optimized.rb +0 -66
- 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
|