language-operator 0.1.36 → 0.1.38
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/Gemfile.lock +2 -1
- data/lib/language_operator/agent/executor.rb +0 -34
- data/lib/language_operator/cli/commands/system.rb +230 -90
- data/lib/language_operator/dsl/agent_definition.rb +81 -33
- data/lib/language_operator/templates/agent_synthesis.tmpl +243 -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/version.rb +1 -1
- metadata +17 -4
- data/CI_STATUS.md +0 -56
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +0 -133
- /data/lib/language_operator/templates/{examples/persona_distillation.tmpl → persona_distillation.tmpl} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4476732244850657fb5161189ad68010e2ffdef4f4949361786893d119f0de89
|
|
4
|
+
data.tar.gz: 5755720ffc0df24c88c5ec5ea223a4f8899c604f938422e420e878833d172de5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4408235ba4cc775fc175f7760e0632e78457630b263690efeb3ddc772c58facfcd7b7e0780a38d33bda5c4f9d3d12b46eed0f83d43631c94f1ddfe7180be61d
|
|
7
|
+
data.tar.gz: 3ccbd9c8c91ef1d19ae467cafe3740cc84586853692cf46569d77b692b1cddf682fb21300bbb75244e71335b84a10fde768f6baff1016d18acf8b4c31a869862
|
data/Gemfile.lock
CHANGED
|
@@ -203,40 +203,6 @@ module LanguageOperator
|
|
|
203
203
|
reason: 'Hit max_iterations limit')
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
-
# Write output to configured destinations
|
|
207
|
-
#
|
|
208
|
-
# @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
|
|
209
|
-
# @param result [RubyLLM::Message] The result to write
|
|
210
|
-
def write_output(agent_def, result)
|
|
211
|
-
return unless agent_def.output_config
|
|
212
|
-
|
|
213
|
-
content = result.is_a?(String) ? result : result.content
|
|
214
|
-
|
|
215
|
-
if (workspace_path = agent_def.output_config[:workspace])
|
|
216
|
-
full_path = File.join(@agent.workspace_path, workspace_path)
|
|
217
|
-
|
|
218
|
-
begin
|
|
219
|
-
FileUtils.mkdir_p(File.dirname(full_path))
|
|
220
|
-
File.write(full_path, content)
|
|
221
|
-
logger.info("📝 Wrote output to #{workspace_path}")
|
|
222
|
-
rescue Errno::EACCES, Errno::EPERM
|
|
223
|
-
# Permission denied - try writing to workspace root
|
|
224
|
-
fallback_path = File.join(@agent.workspace_path, 'output.txt')
|
|
225
|
-
begin
|
|
226
|
-
File.write(fallback_path, content)
|
|
227
|
-
logger.warn("Could not write to #{workspace_path}, wrote to output.txt instead")
|
|
228
|
-
rescue StandardError => e2
|
|
229
|
-
logger.warn("Could not write output to workspace: #{e2.message}")
|
|
230
|
-
logger.info("Output (first 500 chars): #{content[0..500]}")
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Future: Handle Slack, email outputs
|
|
236
|
-
rescue StandardError => e
|
|
237
|
-
logger.warn('Output writing failed', error: e.message)
|
|
238
|
-
end
|
|
239
|
-
|
|
240
206
|
private
|
|
241
207
|
|
|
242
208
|
def logger_component
|
|
@@ -198,49 +198,58 @@ module LanguageOperator
|
|
|
198
198
|
end
|
|
199
199
|
end
|
|
200
200
|
|
|
201
|
-
desc '
|
|
201
|
+
desc 'synthesize INSTRUCTIONS', 'Synthesize agent code from natural language instructions'
|
|
202
202
|
long_desc <<-DESC
|
|
203
|
-
|
|
203
|
+
Synthesize agent code by converting natural language instructions
|
|
204
204
|
into Ruby DSL code without creating an actual agent.
|
|
205
205
|
|
|
206
|
+
This command uses a LanguageModel resource from your cluster to generate
|
|
207
|
+
agent code. If --model is not specified, the first available model will
|
|
208
|
+
be auto-selected.
|
|
209
|
+
|
|
206
210
|
This command helps you validate your instructions and understand how the
|
|
207
211
|
synthesis engine interprets them. Use --dry-run to see the prompt that
|
|
208
212
|
would be sent to the LLM, or run without it to generate actual code.
|
|
209
213
|
|
|
210
214
|
Examples:
|
|
211
215
|
# Test with dry-run (show prompt only)
|
|
212
|
-
aictl system
|
|
216
|
+
aictl system synthesize "Monitor GitHub issues daily" --dry-run
|
|
217
|
+
|
|
218
|
+
# Generate code from instructions (auto-selects first available model)
|
|
219
|
+
aictl system synthesize "Send daily reports to Slack"
|
|
220
|
+
|
|
221
|
+
# Use a specific cluster model
|
|
222
|
+
aictl system synthesize "Process webhooks from GitHub" --model my-claude
|
|
213
223
|
|
|
214
|
-
#
|
|
215
|
-
aictl system
|
|
224
|
+
# Output raw code without formatting (useful for piping to files)
|
|
225
|
+
aictl system synthesize "Monitor logs" --raw > agent.rb
|
|
216
226
|
|
|
217
227
|
# Specify custom agent name and tools
|
|
218
|
-
aictl system
|
|
219
|
-
--instructions "Process webhooks from GitHub" \\
|
|
228
|
+
aictl system synthesize "Process webhooks from GitHub" \\
|
|
220
229
|
--agent-name github-processor \\
|
|
221
|
-
--tools github,slack
|
|
222
|
-
|
|
223
|
-
# Specify available models
|
|
224
|
-
aictl system test-synthesis \\
|
|
225
|
-
--instructions "Analyze logs every hour" \\
|
|
226
|
-
--models gpt-4,claude-3-5-sonnet
|
|
230
|
+
--tools github,slack \\
|
|
231
|
+
--model my-gpt4
|
|
227
232
|
DESC
|
|
228
|
-
option :instructions, type: :string, required: true, desc: 'Natural language instructions for the agent'
|
|
229
233
|
option :agent_name, type: :string, default: 'test-agent', desc: 'Name for the test agent'
|
|
230
234
|
option :tools, type: :string, desc: 'Comma-separated list of available tools'
|
|
231
|
-
option :models, type: :string, desc: 'Comma-separated list of available models'
|
|
235
|
+
option :models, type: :string, desc: 'Comma-separated list of available models (from cluster)'
|
|
236
|
+
option :model, type: :string, desc: 'Model to use for synthesis (defaults to first available in cluster)'
|
|
232
237
|
option :dry_run, type: :boolean, default: false, desc: 'Show prompt without calling LLM'
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
option :raw, type: :boolean, default: false, desc: 'Output only the raw code without formatting'
|
|
239
|
+
def synthesize(instructions)
|
|
240
|
+
handle_command_error('synthesize agent') do
|
|
241
|
+
# Select model to use for synthesis
|
|
242
|
+
selected_model = select_synthesis_model
|
|
243
|
+
|
|
235
244
|
# Load synthesis template
|
|
236
245
|
template_content = load_bundled_template('agent')
|
|
237
246
|
|
|
238
247
|
# Detect temporal intent from instructions
|
|
239
|
-
temporal_intent = detect_temporal_intent(
|
|
248
|
+
temporal_intent = detect_temporal_intent(instructions)
|
|
240
249
|
|
|
241
250
|
# Prepare template data
|
|
242
251
|
template_data = {
|
|
243
|
-
'Instructions' =>
|
|
252
|
+
'Instructions' => instructions,
|
|
244
253
|
'AgentName' => options[:agent_name],
|
|
245
254
|
'ToolsList' => format_tools_list(options[:tools]),
|
|
246
255
|
'ModelsList' => format_models_list(options[:models]),
|
|
@@ -267,11 +276,8 @@ module LanguageOperator
|
|
|
267
276
|
return
|
|
268
277
|
end
|
|
269
278
|
|
|
270
|
-
# Call LLM to generate code
|
|
271
|
-
|
|
272
|
-
puts
|
|
273
|
-
|
|
274
|
-
llm_response = call_llm_for_synthesis(rendered_prompt)
|
|
279
|
+
# Call LLM to generate code (no output - just do it)
|
|
280
|
+
llm_response = call_llm_for_synthesis(rendered_prompt, selected_model)
|
|
275
281
|
|
|
276
282
|
# Extract Ruby code from response
|
|
277
283
|
generated_code = extract_ruby_code(llm_response)
|
|
@@ -284,34 +290,19 @@ module LanguageOperator
|
|
|
284
290
|
exit 1
|
|
285
291
|
end
|
|
286
292
|
|
|
287
|
-
#
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
puts '=' * 80
|
|
292
|
-
puts
|
|
293
|
-
|
|
294
|
-
# Validate generated code
|
|
295
|
-
puts 'Validating generated code...'
|
|
296
|
-
validation_result = validate_code_against_schema(generated_code)
|
|
297
|
-
|
|
298
|
-
if validation_result[:valid] && validation_result[:warnings].empty?
|
|
299
|
-
Formatters::ProgressFormatter.success('✅ Code is valid - No issues found')
|
|
300
|
-
elsif validation_result[:valid]
|
|
301
|
-
Formatters::ProgressFormatter.success('✅ Code is valid - With warnings')
|
|
302
|
-
puts
|
|
303
|
-
validation_result[:warnings].each do |warn|
|
|
304
|
-
puts " ⚠ #{warn[:message]}"
|
|
305
|
-
end
|
|
306
|
-
else
|
|
307
|
-
Formatters::ProgressFormatter.error('❌ Code validation failed')
|
|
308
|
-
puts
|
|
309
|
-
validation_result[:errors].each do |err|
|
|
310
|
-
puts " ✗ #{err[:message]}"
|
|
311
|
-
end
|
|
293
|
+
# Handle raw output
|
|
294
|
+
if options[:raw]
|
|
295
|
+
puts generated_code
|
|
296
|
+
return
|
|
312
297
|
end
|
|
313
298
|
|
|
314
|
-
|
|
299
|
+
# Display formatted code
|
|
300
|
+
require 'rouge'
|
|
301
|
+
formatter = Rouge::Formatters::Terminal256.new
|
|
302
|
+
lexer = Rouge::Lexers::Ruby.new
|
|
303
|
+
highlighted_code = formatter.format(lexer.lex(generated_code))
|
|
304
|
+
|
|
305
|
+
puts highlighted_code
|
|
315
306
|
end
|
|
316
307
|
end
|
|
317
308
|
|
|
@@ -449,68 +440,217 @@ module LanguageOperator
|
|
|
449
440
|
|
|
450
441
|
# Format models list for template
|
|
451
442
|
def format_models_list(models_str)
|
|
452
|
-
# If not specified, try to detect from
|
|
443
|
+
# If not specified, try to detect from cluster
|
|
453
444
|
if models_str.nil? || models_str.strip.empty?
|
|
454
445
|
models = detect_available_models
|
|
455
446
|
return models.map { |model| "- #{model}" }.join("\n") unless models.empty?
|
|
456
447
|
|
|
457
|
-
return 'No models
|
|
448
|
+
return 'No models available (run: aictl model list)'
|
|
458
449
|
end
|
|
459
450
|
|
|
460
451
|
models = models_str.split(',').map(&:strip)
|
|
461
452
|
models.map { |model| "- #{model}" }.join("\n")
|
|
462
453
|
end
|
|
463
454
|
|
|
464
|
-
# Detect available models from
|
|
455
|
+
# Detect available models from cluster
|
|
465
456
|
def detect_available_models
|
|
466
|
-
models =
|
|
467
|
-
models
|
|
468
|
-
|
|
469
|
-
models
|
|
457
|
+
models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
|
|
458
|
+
models.map { |m| m.dig('metadata', 'name') }
|
|
459
|
+
rescue StandardError => e
|
|
460
|
+
Formatters::ProgressFormatter.error("Failed to list models from cluster: #{e.message}")
|
|
461
|
+
[]
|
|
470
462
|
end
|
|
471
463
|
|
|
472
|
-
#
|
|
473
|
-
def
|
|
474
|
-
|
|
464
|
+
# Select model to use for synthesis
|
|
465
|
+
def select_synthesis_model
|
|
466
|
+
# If --model option specified, use it
|
|
467
|
+
return options[:model] if options[:model]
|
|
468
|
+
|
|
469
|
+
# Otherwise, auto-select from available cluster models
|
|
470
|
+
available_models = detect_available_models
|
|
475
471
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
Formatters::ProgressFormatter.error('No LLM credentials found')
|
|
472
|
+
if available_models.empty?
|
|
473
|
+
Formatters::ProgressFormatter.error('No models available in cluster')
|
|
479
474
|
puts
|
|
480
|
-
puts 'Please
|
|
481
|
-
puts '
|
|
482
|
-
puts
|
|
475
|
+
puts 'Please create a model first:'
|
|
476
|
+
puts ' aictl model create'
|
|
477
|
+
puts
|
|
478
|
+
puts 'Or list existing models:'
|
|
479
|
+
puts ' aictl model list'
|
|
483
480
|
exit 1
|
|
484
481
|
end
|
|
485
482
|
|
|
486
|
-
#
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
483
|
+
# Auto-select first available model (silently)
|
|
484
|
+
available_models.first
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Get endpoint for a cluster model
|
|
488
|
+
def get_model_endpoint(model_name)
|
|
489
|
+
# For cluster models, we use the service endpoint
|
|
490
|
+
# The service is typically named the same as the model and listens on port 4000
|
|
491
|
+
"http://#{model_name}.#{ctx.namespace}.svc.cluster.local:4000/v1"
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Call LLM to generate code from synthesis prompt using cluster model
|
|
495
|
+
def call_llm_for_synthesis(prompt, model_name)
|
|
496
|
+
require 'json'
|
|
497
|
+
require 'faraday'
|
|
498
|
+
|
|
499
|
+
# Get model resource
|
|
500
|
+
model = get_resource_or_exit('LanguageModel', model_name)
|
|
501
|
+
model_id = model.dig('spec', 'modelName')
|
|
502
|
+
|
|
503
|
+
# Get the model's pod
|
|
504
|
+
pod = get_model_pod(model_name)
|
|
505
|
+
pod_name = pod.dig('metadata', 'name')
|
|
506
|
+
|
|
507
|
+
# Set up port-forward to access the model pod
|
|
508
|
+
port_forward_pid = nil
|
|
509
|
+
local_port = find_available_port
|
|
510
|
+
|
|
511
|
+
begin
|
|
512
|
+
# Start kubectl port-forward in background
|
|
513
|
+
port_forward_pid = start_port_forward(pod_name, local_port, 4000)
|
|
514
|
+
|
|
515
|
+
# Wait for port-forward to be ready
|
|
516
|
+
wait_for_port(local_port)
|
|
517
|
+
|
|
518
|
+
# Build the JSON payload for the chat completion request
|
|
519
|
+
payload = {
|
|
520
|
+
model: model_id,
|
|
521
|
+
messages: [{ role: 'user', content: prompt }],
|
|
522
|
+
max_tokens: 4000,
|
|
523
|
+
temperature: 0.3
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
# Make HTTP request using Faraday
|
|
527
|
+
conn = Faraday.new(url: "http://localhost:#{local_port}") do |f|
|
|
528
|
+
f.request :json
|
|
529
|
+
f.response :json
|
|
530
|
+
f.adapter Faraday.default_adapter
|
|
531
|
+
f.options.timeout = 120
|
|
532
|
+
f.options.open_timeout = 10
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
response = conn.post('/v1/chat/completions', payload)
|
|
536
|
+
|
|
537
|
+
# Parse response
|
|
538
|
+
result = response.body
|
|
539
|
+
|
|
540
|
+
if result['error']
|
|
541
|
+
error_msg = result['error']['message'] || result['error']
|
|
542
|
+
raise "Model error: #{error_msg}"
|
|
543
|
+
elsif !result['choices'] || result['choices'].empty?
|
|
544
|
+
raise "Unexpected response format: #{result.inspect}"
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Extract the content from the first choice
|
|
548
|
+
result.dig('choices', 0, 'message', 'content')
|
|
549
|
+
rescue Faraday::TimeoutError
|
|
550
|
+
raise 'LLM request timed out after 120 seconds'
|
|
551
|
+
rescue Faraday::ConnectionFailed => e
|
|
552
|
+
raise "Failed to connect to model: #{e.message}"
|
|
553
|
+
rescue StandardError => e
|
|
554
|
+
Formatters::ProgressFormatter.error("LLM call failed: #{e.message}")
|
|
555
|
+
puts
|
|
556
|
+
puts "Make sure the model '#{model_name}' is running: kubectl get pods -n #{ctx.namespace}"
|
|
557
|
+
exit 1
|
|
558
|
+
ensure
|
|
559
|
+
# Clean up port-forward process
|
|
560
|
+
cleanup_port_forward(port_forward_pid) if port_forward_pid
|
|
495
561
|
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Get the pod for a model
|
|
565
|
+
def get_model_pod(model_name)
|
|
566
|
+
# Get the deployment for the model
|
|
567
|
+
deployment = ctx.client.get_resource('Deployment', model_name, ctx.namespace)
|
|
568
|
+
labels = deployment.dig('spec', 'selector', 'matchLabels')
|
|
496
569
|
|
|
497
|
-
#
|
|
498
|
-
client = RubyLLM.new(provider: provider, api_key: api_key)
|
|
499
|
-
messages = [{ role: 'user', content: prompt }]
|
|
570
|
+
raise "Deployment '#{model_name}' has no selector labels" if labels.nil?
|
|
500
571
|
|
|
501
|
-
|
|
572
|
+
# Convert to hash if needed
|
|
573
|
+
labels_hash = labels.respond_to?(:to_h) ? labels.to_h : labels
|
|
574
|
+
raise "Deployment '#{model_name}' has empty selector labels" if labels_hash.empty?
|
|
502
575
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
576
|
+
label_selector = labels_hash.map { |k, v| "#{k}=#{v}" }.join(',')
|
|
577
|
+
|
|
578
|
+
# Find a running pod
|
|
579
|
+
pods = ctx.client.list_resources('Pod', namespace: ctx.namespace, label_selector: label_selector)
|
|
580
|
+
raise "No pods found for model '#{model_name}'" if pods.empty?
|
|
581
|
+
|
|
582
|
+
running_pod = pods.find do |pod|
|
|
583
|
+
pod.dig('status', 'phase') == 'Running' &&
|
|
584
|
+
pod.dig('status', 'conditions')&.any? { |c| c['type'] == 'Ready' && c['status'] == 'True' }
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
if running_pod.nil?
|
|
588
|
+
pod_phases = pods.map { |p| p.dig('status', 'phase') }.join(', ')
|
|
589
|
+
raise "No running pods found. Pod phases: #{pod_phases}"
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
running_pod
|
|
593
|
+
rescue K8s::Error::NotFound
|
|
594
|
+
raise "Model deployment '#{model_name}' not found"
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Find an available local port for port-forwarding
|
|
598
|
+
def find_available_port
|
|
599
|
+
require 'socket'
|
|
600
|
+
|
|
601
|
+
# Try ports in the range 14000-14999
|
|
602
|
+
(14_000..14_999).each do |port|
|
|
603
|
+
server = TCPServer.new('127.0.0.1', port)
|
|
604
|
+
server.close
|
|
605
|
+
return port
|
|
606
|
+
rescue Errno::EADDRINUSE
|
|
607
|
+
# Port in use, try next
|
|
608
|
+
next
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
raise 'No available ports found in range 14000-14999'
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
# Start kubectl port-forward in background
|
|
615
|
+
def start_port_forward(pod_name, local_port, remote_port)
|
|
616
|
+
require 'English'
|
|
617
|
+
|
|
618
|
+
cmd = "kubectl port-forward -n #{ctx.namespace} #{pod_name} #{local_port}:#{remote_port}"
|
|
619
|
+
pid = spawn(cmd, out: '/dev/null', err: '/dev/null')
|
|
620
|
+
|
|
621
|
+
# Detach so it runs in background
|
|
622
|
+
Process.detach(pid)
|
|
623
|
+
|
|
624
|
+
pid
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Wait for port-forward to be ready
|
|
628
|
+
def wait_for_port(port, max_attempts: 30)
|
|
629
|
+
require 'socket'
|
|
630
|
+
|
|
631
|
+
max_attempts.times do
|
|
632
|
+
socket = TCPSocket.new('127.0.0.1', port)
|
|
633
|
+
socket.close
|
|
634
|
+
return true
|
|
635
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
|
|
636
|
+
sleep 0.1
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
raise "Port-forward to localhost:#{port} failed to become ready after #{max_attempts} attempts"
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
# Clean up port-forward process
|
|
643
|
+
def cleanup_port_forward(pid)
|
|
644
|
+
return unless pid
|
|
645
|
+
|
|
646
|
+
begin
|
|
647
|
+
Process.kill('TERM', pid)
|
|
648
|
+
Process.wait(pid, Process::WNOHANG)
|
|
649
|
+
rescue Errno::ESRCH
|
|
650
|
+
# Process already gone
|
|
651
|
+
rescue Errno::ECHILD
|
|
652
|
+
# Process already reaped
|
|
510
653
|
end
|
|
511
|
-
rescue StandardError => e
|
|
512
|
-
Formatters::ProgressFormatter.error("LLM call failed: #{e.message}")
|
|
513
|
-
exit 1
|
|
514
654
|
end
|
|
515
655
|
|
|
516
656
|
# Extract Ruby code from LLM response
|
|
@@ -550,7 +690,7 @@ module LanguageOperator
|
|
|
550
690
|
# Load bundled template from gem
|
|
551
691
|
def load_bundled_template(type)
|
|
552
692
|
filename = type == 'agent' ? 'agent_synthesis.tmpl' : 'persona_distillation.tmpl'
|
|
553
|
-
template_path = File.join(__dir__, '..', '..', 'templates',
|
|
693
|
+
template_path = File.join(__dir__, '..', '..', 'templates', filename)
|
|
554
694
|
File.read(template_path)
|
|
555
695
|
end
|
|
556
696
|
|
|
@@ -60,7 +60,7 @@ module LanguageOperator
|
|
|
60
60
|
@main = nil
|
|
61
61
|
@tasks = {}
|
|
62
62
|
@constraints = {}
|
|
63
|
-
@output_config =
|
|
63
|
+
@output_config = nil
|
|
64
64
|
@execution_mode = :autonomous
|
|
65
65
|
@webhooks = []
|
|
66
66
|
@mcp_server = nil
|
|
@@ -223,16 +223,58 @@ module LanguageOperator
|
|
|
223
223
|
@constraints = constraint_builder.to_h
|
|
224
224
|
end
|
|
225
225
|
|
|
226
|
-
# Define output
|
|
226
|
+
# Define output handler (organic function) - DSL v1
|
|
227
227
|
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
|
|
231
|
-
|
|
228
|
+
# The output is an organic function that receives the final outputs from main execution
|
|
229
|
+
# and handles them (logging, saving to workspace, notifications, etc.). Like tasks,
|
|
230
|
+
# it can be neural (instructions-based), symbolic (code block), or hybrid (both).
|
|
231
|
+
#
|
|
232
|
+
# @param options [Hash] Output configuration
|
|
233
|
+
# @option options [String] :instructions Natural language instructions (neural)
|
|
234
|
+
# @yield [outputs] Symbolic implementation block (optional)
|
|
235
|
+
# @yieldparam outputs [Hash] The outputs returned from main execution
|
|
236
|
+
# @return [TaskDefinition] The output task definition
|
|
237
|
+
#
|
|
238
|
+
# @example Neural output
|
|
239
|
+
# output instructions: "save results to workspace as JSON"
|
|
240
|
+
#
|
|
241
|
+
# @example Symbolic output
|
|
242
|
+
# output do |outputs|
|
|
243
|
+
# File.write("/workspace/result.json", JSON.pretty_generate(outputs))
|
|
244
|
+
# end
|
|
245
|
+
#
|
|
246
|
+
# @example Hybrid output
|
|
247
|
+
# output instructions: "save results to workspace" do |outputs|
|
|
248
|
+
# File.write("/workspace/result.json", outputs.to_json)
|
|
249
|
+
# end
|
|
250
|
+
def output(**options, &block)
|
|
251
|
+
return @output_config if options.empty? && block.nil?
|
|
252
|
+
|
|
253
|
+
# Create a TaskDefinition for output (it's an organic function)
|
|
254
|
+
output_task = TaskDefinition.new(:output)
|
|
255
|
+
|
|
256
|
+
# Output task always receives main's outputs as inputs (type: any)
|
|
257
|
+
# No need to specify inputs - they come from main
|
|
258
|
+
|
|
259
|
+
# Configure instructions if provided (neural)
|
|
260
|
+
output_task.instructions(options[:instructions]) if options[:instructions]
|
|
261
|
+
|
|
262
|
+
# Symbolic implementation (if block provided)
|
|
263
|
+
output_task.execute(&block) if block
|
|
264
|
+
|
|
265
|
+
@output_config = output_task
|
|
266
|
+
|
|
267
|
+
task_type = if output_task.neural? && output_task.symbolic?
|
|
268
|
+
'hybrid'
|
|
269
|
+
elsif output_task.neural?
|
|
270
|
+
'neural'
|
|
271
|
+
else
|
|
272
|
+
'symbolic'
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
logger.debug('Output defined', type: task_type)
|
|
232
276
|
|
|
233
|
-
|
|
234
|
-
output_builder.instance_eval(&block) if block
|
|
235
|
-
@output_config = output_builder.to_h
|
|
277
|
+
output_task
|
|
236
278
|
end
|
|
237
279
|
|
|
238
280
|
# Set execution mode
|
|
@@ -408,9 +450,15 @@ module LanguageOperator
|
|
|
408
450
|
|
|
409
451
|
# If main defined, execute it; otherwise just log
|
|
410
452
|
if @main
|
|
411
|
-
logger.timed('Objective main execution') do
|
|
453
|
+
outputs = logger.timed('Objective main execution') do
|
|
412
454
|
@main.call({ objective: objective })
|
|
413
455
|
end
|
|
456
|
+
|
|
457
|
+
# Call output handler if defined (it's an organic function)
|
|
458
|
+
if @output_config.is_a?(TaskDefinition)
|
|
459
|
+
logger.debug('Executing output handler', outputs: outputs)
|
|
460
|
+
execute_output_handler(outputs)
|
|
461
|
+
end
|
|
414
462
|
else
|
|
415
463
|
logger.warn('No main block defined, skipping execution')
|
|
416
464
|
end
|
|
@@ -418,6 +466,29 @@ module LanguageOperator
|
|
|
418
466
|
|
|
419
467
|
logger.info('All objectives completed', total: @objectives.size)
|
|
420
468
|
end
|
|
469
|
+
|
|
470
|
+
# Execute the output handler (neural or symbolic)
|
|
471
|
+
#
|
|
472
|
+
# @param outputs [Hash] The outputs from main execution
|
|
473
|
+
def execute_output_handler(outputs)
|
|
474
|
+
# If symbolic implementation exists, use it
|
|
475
|
+
if @output_config.symbolic?
|
|
476
|
+
logger.debug('Executing symbolic output handler')
|
|
477
|
+
# execute_symbolic takes (inputs, context) - outputs are the inputs, context is nil
|
|
478
|
+
@output_config.execute_symbolic(outputs, nil)
|
|
479
|
+
elsif @output_config.neural?
|
|
480
|
+
# Neural output - would need LLM access to execute
|
|
481
|
+
# For now, just log the instruction
|
|
482
|
+
logger.info('Neural output handler',
|
|
483
|
+
instruction: @output_config.instructions_text,
|
|
484
|
+
outputs: outputs)
|
|
485
|
+
logger.warn('Neural output execution not yet implemented - instruction logged only')
|
|
486
|
+
end
|
|
487
|
+
rescue StandardError => e
|
|
488
|
+
logger.error('Output handler failed',
|
|
489
|
+
error: e.message,
|
|
490
|
+
backtrace: e.backtrace[0..5])
|
|
491
|
+
end
|
|
421
492
|
end
|
|
422
493
|
|
|
423
494
|
# Helper class for building constraints
|
|
@@ -485,28 +556,5 @@ module LanguageOperator
|
|
|
485
556
|
@constraints
|
|
486
557
|
end
|
|
487
558
|
end
|
|
488
|
-
|
|
489
|
-
# Helper class for building output configuration
|
|
490
|
-
class OutputBuilder
|
|
491
|
-
def initialize
|
|
492
|
-
@config = {}
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
def workspace(path)
|
|
496
|
-
@config[:workspace] = path
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
def slack(channel:)
|
|
500
|
-
@config[:slack] = { channel: channel }
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
def email(to:)
|
|
504
|
-
@config[:email] = { to: to }
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
def to_h
|
|
508
|
-
@config
|
|
509
|
-
end
|
|
510
|
-
end
|
|
511
559
|
end
|
|
512
560
|
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
You are generating Ruby DSL code for an autonomous agent in a Kubernetes operator.
|
|
2
|
+
|
|
3
|
+
{{if .ErrorContext}}
|
|
4
|
+
## IMPORTANT: Self-Healing Synthesis - Attempt {{.AttemptNumber}}
|
|
5
|
+
|
|
6
|
+
The previous code synthesis encountered errors. Please analyze the errors below and generate CORRECTED code.
|
|
7
|
+
|
|
8
|
+
### Previous Synthesis Failures
|
|
9
|
+
|
|
10
|
+
{{if .ErrorContext.ValidationErrors}}
|
|
11
|
+
**Validation Errors** (detected during code generation):
|
|
12
|
+
{{range .ErrorContext.ValidationErrors}}
|
|
13
|
+
- {{.}}
|
|
14
|
+
{{end}}
|
|
15
|
+
{{end}}
|
|
16
|
+
|
|
17
|
+
{{if .ErrorContext.RuntimeErrors}}
|
|
18
|
+
**Runtime Errors** (detected during execution):
|
|
19
|
+
{{range .ErrorContext.RuntimeErrors}}
|
|
20
|
+
- Time: {{.Timestamp}}
|
|
21
|
+
- Type: {{.ErrorType}}
|
|
22
|
+
- Message: {{.ErrorMessage}}
|
|
23
|
+
{{if .StackTrace}}
|
|
24
|
+
- Stack Trace:
|
|
25
|
+
{{range .StackTrace}}
|
|
26
|
+
{{.}}
|
|
27
|
+
{{end}}
|
|
28
|
+
{{end}}
|
|
29
|
+
- Exit Code: {{.ContainerExitCode}}
|
|
30
|
+
{{end}}
|
|
31
|
+
{{end}}
|
|
32
|
+
|
|
33
|
+
{{if .ErrorContext.LastCrashLog}}
|
|
34
|
+
**Last Container Logs** (before crash):
|
|
35
|
+
```
|
|
36
|
+
{{.ErrorContext.LastCrashLog}}
|
|
37
|
+
```
|
|
38
|
+
{{end}}
|
|
39
|
+
|
|
40
|
+
### Your Task
|
|
41
|
+
|
|
42
|
+
1. Carefully analyze each error above
|
|
43
|
+
2. Identify the root cause of the failure
|
|
44
|
+
3. Generate CORRECTED Ruby DSL code that addresses ALL errors
|
|
45
|
+
4. Ensure the code:
|
|
46
|
+
- Fixes the specific errors mentioned
|
|
47
|
+
- Uses only available tools: {{.ToolsList}}
|
|
48
|
+
- Uses only available models: {{.ModelsList}}
|
|
49
|
+
- Follows the Language Operator DSL syntax exactly
|
|
50
|
+
- Does NOT use any dangerous Ruby methods (system, eval, etc.)
|
|
51
|
+
|
|
52
|
+
This is attempt {{.AttemptNumber}} of {{.MaxAttempts}}. The user is counting on you to get it right!
|
|
53
|
+
|
|
54
|
+
{{if .LastKnownGoodCode}}
|
|
55
|
+
### Last Known Working Code (for reference)
|
|
56
|
+
```ruby
|
|
57
|
+
{{.LastKnownGoodCode}}
|
|
58
|
+
```
|
|
59
|
+
{{end}}
|
|
60
|
+
|
|
61
|
+
{{else}}
|
|
62
|
+
## Agent Synthesis Request
|
|
63
|
+
{{end}}
|
|
64
|
+
|
|
65
|
+
**User Instructions:**
|
|
66
|
+
{{.Instructions}}
|
|
67
|
+
|
|
68
|
+
**Available Tools:**
|
|
69
|
+
{{.ToolsList}}
|
|
70
|
+
|
|
71
|
+
**Available Models:**
|
|
72
|
+
{{.ModelsList}}
|
|
73
|
+
|
|
74
|
+
**Agent Name:** {{.AgentName}}
|
|
75
|
+
|
|
76
|
+
**Detected Temporal Intent:** {{.TemporalIntent}}
|
|
77
|
+
|
|
78
|
+
**Runtime Context:**
|
|
79
|
+
- All agent messages and output are automatically logged to stdout
|
|
80
|
+
- Agents have access to a workspace directory for file operations
|
|
81
|
+
- LLM responses are captured and available in agent execution context
|
|
82
|
+
|
|
83
|
+
## DSL v1 Reference Examples
|
|
84
|
+
|
|
85
|
+
Study these examples to understand the task/main model with organic functions:
|
|
86
|
+
|
|
87
|
+
### Example 1: Simple Scheduled Agent (Fully Neural)
|
|
88
|
+
```ruby
|
|
89
|
+
agent "daily-report" do
|
|
90
|
+
description "Generate daily sales report"
|
|
91
|
+
mode :scheduled
|
|
92
|
+
schedule "0 9 * * *"
|
|
93
|
+
|
|
94
|
+
task :fetch_sales,
|
|
95
|
+
instructions: "fetch yesterday's sales data from database",
|
|
96
|
+
inputs: {},
|
|
97
|
+
outputs: { sales: 'array', total: 'number' }
|
|
98
|
+
|
|
99
|
+
task :generate_report,
|
|
100
|
+
instructions: "create a markdown report summarizing the sales",
|
|
101
|
+
inputs: { sales: 'array', total: 'number' },
|
|
102
|
+
outputs: { report: 'string' }
|
|
103
|
+
|
|
104
|
+
main do |inputs|
|
|
105
|
+
sales_data = execute_task(:fetch_sales)
|
|
106
|
+
report = execute_task(:generate_report, inputs: sales_data)
|
|
107
|
+
report
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
output do |outputs|
|
|
111
|
+
puts outputs[:report]
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Example 2: Hybrid Neural-Symbolic Agent
|
|
117
|
+
```ruby
|
|
118
|
+
agent "code-reviewer" do
|
|
119
|
+
description "Review pull requests"
|
|
120
|
+
|
|
121
|
+
# Symbolic task - deterministic tool call
|
|
122
|
+
task :fetch_pr_diff do |inputs|
|
|
123
|
+
{
|
|
124
|
+
diff: execute_tool('github', 'get_pr_diff', pr_number: inputs[:pr_number])
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Neural task - creative analysis
|
|
129
|
+
task :analyze_code,
|
|
130
|
+
instructions: "review code changes for bugs and improvements",
|
|
131
|
+
inputs: { diff: 'string' },
|
|
132
|
+
outputs: { issues: 'array', severity: 'string' }
|
|
133
|
+
|
|
134
|
+
# Symbolic task - deterministic logic
|
|
135
|
+
task :should_approve do |inputs|
|
|
136
|
+
critical = inputs[:issues].select { |i| i['severity'] == 'critical' }
|
|
137
|
+
{ approve: critical.empty? }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
main do |inputs|
|
|
141
|
+
pr_data = execute_task(:fetch_pr_diff, inputs: inputs)
|
|
142
|
+
analysis = execute_task(:analyze_code, inputs: pr_data)
|
|
143
|
+
decision = execute_task(:should_approve, inputs: analysis)
|
|
144
|
+
analysis.merge(decision)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Example 3: Multi-Step Agent with Tools
|
|
150
|
+
```ruby
|
|
151
|
+
agent "data-pipeline" do
|
|
152
|
+
description "ETL pipeline"
|
|
153
|
+
|
|
154
|
+
task :extract_data,
|
|
155
|
+
instructions: "extract data from the source database",
|
|
156
|
+
inputs: { source: 'string' },
|
|
157
|
+
outputs: { records: 'array', count: 'integer' }
|
|
158
|
+
|
|
159
|
+
task :transform_data,
|
|
160
|
+
instructions: "clean and normalize the records",
|
|
161
|
+
inputs: { records: 'array' },
|
|
162
|
+
outputs: { cleaned_records: 'array' }
|
|
163
|
+
|
|
164
|
+
task :load_data,
|
|
165
|
+
instructions: "load cleaned records into warehouse",
|
|
166
|
+
inputs: { cleaned_records: 'array' },
|
|
167
|
+
outputs: { success: 'boolean', loaded_count: 'integer' }
|
|
168
|
+
|
|
169
|
+
main do |inputs|
|
|
170
|
+
extracted = execute_task(:extract_data, inputs: inputs)
|
|
171
|
+
transformed = execute_task(:transform_data, inputs: extracted)
|
|
172
|
+
result = execute_task(:load_data, inputs: transformed)
|
|
173
|
+
result
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Your Task: Generate DSL v1 Agent
|
|
179
|
+
|
|
180
|
+
Using the examples above as reference, generate Ruby DSL code in this format:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
require 'language_operator'
|
|
184
|
+
|
|
185
|
+
agent "{{.AgentName}}" do
|
|
186
|
+
description "Brief description extracted from instructions"
|
|
187
|
+
{{.PersonaSection}}{{.ScheduleSection}}
|
|
188
|
+
# Break down instructions into tasks
|
|
189
|
+
# Each task needs:
|
|
190
|
+
# - instructions: what to do (for neural tasks)
|
|
191
|
+
# - inputs: hash with parameter types
|
|
192
|
+
# - outputs: hash with result types
|
|
193
|
+
|
|
194
|
+
task :task_name,
|
|
195
|
+
instructions: "clear description of what this task does",
|
|
196
|
+
inputs: { param_name: 'type' },
|
|
197
|
+
outputs: { result_name: 'type' }
|
|
198
|
+
|
|
199
|
+
# For symbolic tasks (when logic is simple/deterministic), use code blocks:
|
|
200
|
+
# task :task_name do |inputs|
|
|
201
|
+
# { result: inputs[:param] * 2 }
|
|
202
|
+
# end
|
|
203
|
+
|
|
204
|
+
# REQUIRED: main block defines execution flow
|
|
205
|
+
main do |inputs|
|
|
206
|
+
result1 = execute_task(:task_name, inputs: { param_name: value })
|
|
207
|
+
# Chain tasks by passing outputs as inputs
|
|
208
|
+
result2 = execute_task(:another_task, inputs: result1)
|
|
209
|
+
result2 # Return final result
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
{{.ConstraintsSection}}
|
|
213
|
+
|
|
214
|
+
# Output handling
|
|
215
|
+
output do |outputs|
|
|
216
|
+
# Save results, send notifications, etc.
|
|
217
|
+
puts "Agent completed: #{outputs.inspect}"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Type System:**
|
|
223
|
+
- `'string'` - text values
|
|
224
|
+
- `'integer'` - whole numbers
|
|
225
|
+
- `'number'` - decimal numbers
|
|
226
|
+
- `'boolean'` - true/false
|
|
227
|
+
- `'array'` - lists
|
|
228
|
+
- `'hash'` - key-value objects
|
|
229
|
+
- `'any'` - any type
|
|
230
|
+
|
|
231
|
+
**Rules:**
|
|
232
|
+
1. Generate ONLY the Ruby code within triple-backticks, no explanations before or after
|
|
233
|
+
{{.ScheduleRules}}
|
|
234
|
+
3. Break down instructions into clear tasks with type-safe contracts
|
|
235
|
+
4. REQUIRED: Always include a `main` block that calls tasks via `execute_task()`
|
|
236
|
+
5. For simple deterministic tasks, use symbolic code blocks (do |inputs| ... end)
|
|
237
|
+
6. For complex/creative tasks, use neural tasks with instructions
|
|
238
|
+
7. Chain tasks by passing outputs as inputs
|
|
239
|
+
8. Use available tools: {{.ToolsList}}
|
|
240
|
+
9. Type schemas are REQUIRED for all tasks (inputs/outputs hashes)
|
|
241
|
+
10. Use the agent name: "{{.AgentName}}"
|
|
242
|
+
|
|
243
|
+
Generate the code now:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://github.com/language-operator/language-operator-gem/schema/agent-dsl.json",
|
|
4
4
|
"title": "Language Operator Agent DSL",
|
|
5
5
|
"description": "Schema for defining autonomous AI agents using the Language Operator DSL",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.38",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"name": {
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: language-operator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.38
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Ryan
|
|
@@ -135,6 +135,20 @@ dependencies:
|
|
|
135
135
|
- - "~>"
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '3.9'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: faraday
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '2.0'
|
|
145
|
+
type: :runtime
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '2.0'
|
|
138
152
|
- !ruby/object:Gem::Dependency
|
|
139
153
|
name: k8s-ruby
|
|
140
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -397,7 +411,6 @@ extra_rdoc_files: []
|
|
|
397
411
|
files:
|
|
398
412
|
- ".rubocop.yml"
|
|
399
413
|
- CHANGELOG.md
|
|
400
|
-
- CI_STATUS.md
|
|
401
414
|
- Gemfile
|
|
402
415
|
- Gemfile.lock
|
|
403
416
|
- LICENSE
|
|
@@ -506,8 +519,8 @@ files:
|
|
|
506
519
|
- lib/language_operator/retryable.rb
|
|
507
520
|
- lib/language_operator/synthesis_test_harness.rb
|
|
508
521
|
- lib/language_operator/templates/README.md
|
|
509
|
-
- lib/language_operator/templates/
|
|
510
|
-
- lib/language_operator/templates/
|
|
522
|
+
- lib/language_operator/templates/agent_synthesis.tmpl
|
|
523
|
+
- lib/language_operator/templates/persona_distillation.tmpl
|
|
511
524
|
- lib/language_operator/templates/schema/.gitkeep
|
|
512
525
|
- lib/language_operator/templates/schema/CHANGELOG.md
|
|
513
526
|
- lib/language_operator/templates/schema/agent_dsl_openapi.yaml
|
data/CI_STATUS.md
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# CI Integration Test Status
|
|
2
|
-
|
|
3
|
-
## Summary
|
|
4
|
-
|
|
5
|
-
The CI integration tests are significantly improved from their previous completely broken state.
|
|
6
|
-
|
|
7
|
-
### Fixed Issues
|
|
8
|
-
|
|
9
|
-
1. **Numeric Constant Error** ✅
|
|
10
|
-
- **Problem**: SafeExecutor sandbox was blocking access to Ruby type constants (Numeric, Integer, Float, etc.)
|
|
11
|
-
- **Solution**: Inject type constants into the evaluated code scope in SafeExecutor#eval
|
|
12
|
-
- **Impact**: All symbolic tasks using type checking now work correctly
|
|
13
|
-
|
|
14
|
-
2. **Neural Task Connection Errors** ✅
|
|
15
|
-
- **Problem**: Agent tried to connect to real LLM when INTEGRATION_MOCK_LLM=true, failing with "Not connected"
|
|
16
|
-
- **Solution**: Create mock chat object in create_test_agent when mocking is enabled
|
|
17
|
-
- **Impact**: Neural tasks can now execute without real LLM connection
|
|
18
|
-
|
|
19
|
-
3. **Deep Symbol Keys** ✅
|
|
20
|
-
- **Problem**: Nested hashes in neural task outputs had string keys, tests expected symbol keys
|
|
21
|
-
- **Solution**: Implement deep_symbolize_keys in TaskExecutor#parse_neural_response
|
|
22
|
-
- **Impact**: Nested hash structures now match test expectations
|
|
23
|
-
|
|
24
|
-
4. **Multi-Provider LLM Support** ✅
|
|
25
|
-
- **Problem**: Tests only supported OpenAI
|
|
26
|
-
- **Solution**: Added support for SYNTHESIS_*, ANTHROPIC_*, and OPENAI_API_KEY env vars
|
|
27
|
-
- **Impact**: Tests can use local models, Claude, or OpenAI
|
|
28
|
-
|
|
29
|
-
### Current Test Status
|
|
30
|
-
|
|
31
|
-
**Passing Tests** (28/72, 39%):
|
|
32
|
-
- ✅ Comprehensive DSL v1 Integration (all 4 scenarios)
|
|
33
|
-
- ✅ Symbolic Task Execution (complete)
|
|
34
|
-
- ✅ Error Handling (skipped DSL syntax issues)
|
|
35
|
-
- ✅ Type Coercion (partial)
|
|
36
|
-
|
|
37
|
-
**Failing Tests** (44/72, 61%):
|
|
38
|
-
- ❌ Neural Task Execution - individual mocks don't match all output schemas
|
|
39
|
-
- ❌ Hybrid Agent Execution - some neural tasks failing
|
|
40
|
-
- ❌ Parallel Execution - some neural tasks failing
|
|
41
|
-
|
|
42
|
-
**Pending Tests**: 20 (performance benchmarks disabled)
|
|
43
|
-
|
|
44
|
-
### Recommendations
|
|
45
|
-
|
|
46
|
-
For full CI coverage with mocked LLMs, consider:
|
|
47
|
-
1. Use real LLM in CI (with API key secrets) instead of mocking
|
|
48
|
-
2. Add schema-aware mock generation based on task output definitions
|
|
49
|
-
3. Add individual mocks for each failing neural task (tedious but thorough)
|
|
50
|
-
|
|
51
|
-
### Bottom Line
|
|
52
|
-
|
|
53
|
-
**Before**: 100% failure rate - all tests broken
|
|
54
|
-
**After**: 39% pass rate with core functionality working
|
|
55
|
-
|
|
56
|
-
The most critical tests (comprehensive integration) now pass. The CI is in a MUCH better state than before.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
You are generating Ruby DSL code for an autonomous agent in a Kubernetes operator.
|
|
2
|
-
|
|
3
|
-
{{if .ErrorContext}}
|
|
4
|
-
## IMPORTANT: Self-Healing Synthesis - Attempt {{.AttemptNumber}}
|
|
5
|
-
|
|
6
|
-
The previous code synthesis encountered errors. Please analyze the errors below and generate CORRECTED code.
|
|
7
|
-
|
|
8
|
-
### Previous Synthesis Failures
|
|
9
|
-
|
|
10
|
-
{{if .ErrorContext.ValidationErrors}}
|
|
11
|
-
**Validation Errors** (detected during code generation):
|
|
12
|
-
{{range .ErrorContext.ValidationErrors}}
|
|
13
|
-
- {{.}}
|
|
14
|
-
{{end}}
|
|
15
|
-
{{end}}
|
|
16
|
-
|
|
17
|
-
{{if .ErrorContext.RuntimeErrors}}
|
|
18
|
-
**Runtime Errors** (detected during execution):
|
|
19
|
-
{{range .ErrorContext.RuntimeErrors}}
|
|
20
|
-
- Time: {{.Timestamp}}
|
|
21
|
-
- Type: {{.ErrorType}}
|
|
22
|
-
- Message: {{.ErrorMessage}}
|
|
23
|
-
{{if .StackTrace}}
|
|
24
|
-
- Stack Trace:
|
|
25
|
-
{{range .StackTrace}}
|
|
26
|
-
{{.}}
|
|
27
|
-
{{end}}
|
|
28
|
-
{{end}}
|
|
29
|
-
- Exit Code: {{.ContainerExitCode}}
|
|
30
|
-
{{end}}
|
|
31
|
-
{{end}}
|
|
32
|
-
|
|
33
|
-
{{if .ErrorContext.LastCrashLog}}
|
|
34
|
-
**Last Container Logs** (before crash):
|
|
35
|
-
```
|
|
36
|
-
{{.ErrorContext.LastCrashLog}}
|
|
37
|
-
```
|
|
38
|
-
{{end}}
|
|
39
|
-
|
|
40
|
-
### Your Task
|
|
41
|
-
|
|
42
|
-
1. Carefully analyze each error above
|
|
43
|
-
2. Identify the root cause of the failure
|
|
44
|
-
3. Generate CORRECTED Ruby DSL code that addresses ALL errors
|
|
45
|
-
4. Ensure the code:
|
|
46
|
-
- Fixes the specific errors mentioned
|
|
47
|
-
- Uses only available tools: {{.ToolsList}}
|
|
48
|
-
- Uses only available models: {{.ModelsList}}
|
|
49
|
-
- Follows the Language Operator DSL syntax exactly
|
|
50
|
-
- Does NOT use any dangerous Ruby methods (system, eval, etc.)
|
|
51
|
-
|
|
52
|
-
This is attempt {{.AttemptNumber}} of {{.MaxAttempts}}. The user is counting on you to get it right!
|
|
53
|
-
|
|
54
|
-
{{if .LastKnownGoodCode}}
|
|
55
|
-
### Last Known Working Code (for reference)
|
|
56
|
-
```ruby
|
|
57
|
-
{{.LastKnownGoodCode}}
|
|
58
|
-
```
|
|
59
|
-
{{end}}
|
|
60
|
-
|
|
61
|
-
{{else}}
|
|
62
|
-
## Agent Synthesis Request
|
|
63
|
-
{{end}}
|
|
64
|
-
|
|
65
|
-
**User Instructions:**
|
|
66
|
-
{{.Instructions}}
|
|
67
|
-
|
|
68
|
-
**Available Tools:**
|
|
69
|
-
{{.ToolsList}}
|
|
70
|
-
|
|
71
|
-
**Available Models:**
|
|
72
|
-
{{.ModelsList}}
|
|
73
|
-
|
|
74
|
-
**Agent Name:** {{.AgentName}}
|
|
75
|
-
|
|
76
|
-
**Detected Temporal Intent:** {{.TemporalIntent}}
|
|
77
|
-
|
|
78
|
-
**Runtime Context:**
|
|
79
|
-
- All agent messages and output are automatically logged to stdout
|
|
80
|
-
- Agents have access to a workspace directory for file operations
|
|
81
|
-
- LLM responses are captured and available in agent execution context
|
|
82
|
-
|
|
83
|
-
Generate Ruby DSL code using this exact format (wrapped in triple-backticks with ruby):
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
require 'language_operator'
|
|
87
|
-
|
|
88
|
-
agent "{{.AgentName}}" do
|
|
89
|
-
description "Brief description extracted from instructions"
|
|
90
|
-
{{.PersonaSection}}{{.ScheduleSection}}
|
|
91
|
-
# Extract objectives from instructions
|
|
92
|
-
objectives [
|
|
93
|
-
"First objective",
|
|
94
|
-
"Second objective"
|
|
95
|
-
]
|
|
96
|
-
|
|
97
|
-
# REQUIRED: Define workflow with at least one step
|
|
98
|
-
workflow do
|
|
99
|
-
# Use tools when available
|
|
100
|
-
step :step_name, tool: "tool_name", params: {key: "value"}
|
|
101
|
-
|
|
102
|
-
# Or use execute blocks for custom Ruby code (simple logging, calculations, etc.)
|
|
103
|
-
step :custom_step do
|
|
104
|
-
execute do
|
|
105
|
-
puts "Custom output from Ruby code"
|
|
106
|
-
{ result: "done" }
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Chain steps with dependencies if needed
|
|
111
|
-
step :another_step, depends_on: :step_name
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
{{.ConstraintsSection}}
|
|
115
|
-
|
|
116
|
-
# Output configuration (if workspace enabled)
|
|
117
|
-
output do
|
|
118
|
-
workspace "results/output.txt"
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Rules:**
|
|
124
|
-
1. Generate ONLY the Ruby code within triple-backticks, no explanations before or after
|
|
125
|
-
{{.ScheduleRules}}
|
|
126
|
-
5. Break down instructions into clear, actionable objectives
|
|
127
|
-
6. REQUIRED: Always include a workflow block with at least one step (even for simple single-action agents)
|
|
128
|
-
7. For simple tasks (logging, calculations), use a single step with an execute block containing Ruby code
|
|
129
|
-
8. For complex tasks, use multiple steps with tools or execute blocks
|
|
130
|
-
9. Use available tools in workflow steps when tools are provided
|
|
131
|
-
10. Use the agent name: "{{.AgentName}}"
|
|
132
|
-
|
|
133
|
-
Generate the code now:
|
|
File without changes
|