language-operator 0.1.37 → 0.1.39
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 +1 -1
- data/lib/language_operator/agent/executor.rb +0 -34
- data/lib/language_operator/agent/task_executor.rb +11 -4
- data/lib/language_operator/agent.rb +34 -0
- data/lib/language_operator/dsl/agent_definition.rb +81 -33
- data/lib/language_operator/kubernetes/client.rb +8 -1
- 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 +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a3674b7904fe01e280096b7e7c37ce8be922379d75a194e31ffd5239b082e7b8
|
|
4
|
+
data.tar.gz: 61cd4ab21b5495b537cb4babbe5e0a14c4890a93b9b1f2a1d4d4069c53c3ddc1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33714e1bd58eb58d54101a038954a1fbd425ffa34904fdfc8cb3dcb9ba83b0090d445343071190b7bb48e599d8691d99b4918f8a9eab2ecf135634f92c7825ac
|
|
7
|
+
data.tar.gz: 235149dfa2f126e31b8450426d70ebb25f39badb30b343e274c7b50ab0d7b3674f224ffae251589967c4ea47022b171af3f0212cd466bc6c994171534e0b48ef
|
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
|
|
@@ -445,15 +445,16 @@ module LanguageOperator
|
|
|
445
445
|
|
|
446
446
|
# Execute the actual task implementation (neural or symbolic)
|
|
447
447
|
#
|
|
448
|
+
# For hybrid tasks (both neural and symbolic), prefer symbolic execution
|
|
449
|
+
# as it's more efficient and deterministic.
|
|
450
|
+
#
|
|
448
451
|
# @param task [TaskDefinition] The task definition
|
|
449
452
|
# @param inputs [Hash] Input parameters
|
|
450
453
|
# @return [Hash] Task outputs
|
|
451
454
|
def execute_task_implementation(task, inputs)
|
|
452
|
-
if task.
|
|
453
|
-
# Neural execution: LLM with tool access
|
|
454
|
-
execute_neural(task, inputs)
|
|
455
|
-
else
|
|
455
|
+
if task.symbolic?
|
|
456
456
|
# Symbolic execution: Direct Ruby code within traced span
|
|
457
|
+
# This takes precedence over neural for hybrid tasks
|
|
457
458
|
tracer.in_span('task_executor.symbolic', attributes: symbolic_task_attributes(task)) do |span|
|
|
458
459
|
validated_inputs = task.validate_inputs(inputs)
|
|
459
460
|
span.set_attribute('task.input.keys', validated_inputs.keys.map(&:to_s).join(','))
|
|
@@ -465,6 +466,12 @@ module LanguageOperator
|
|
|
465
466
|
record_output_metadata(outputs, span) if outputs.is_a?(Hash)
|
|
466
467
|
outputs
|
|
467
468
|
end
|
|
469
|
+
elsif task.neural?
|
|
470
|
+
# Neural execution: LLM with tool access
|
|
471
|
+
# Only used for pure neural tasks (no symbolic implementation)
|
|
472
|
+
execute_neural(task, inputs)
|
|
473
|
+
else
|
|
474
|
+
raise ArgumentError, "Task '#{task.name}' has neither neural nor symbolic implementation"
|
|
468
475
|
end
|
|
469
476
|
end
|
|
470
477
|
|
|
@@ -191,9 +191,43 @@ module LanguageOperator
|
|
|
191
191
|
logger.info('Main block execution completed',
|
|
192
192
|
result: result)
|
|
193
193
|
|
|
194
|
+
# Call output handler if defined
|
|
195
|
+
if agent_def.output
|
|
196
|
+
logger.debug('Executing output handler', outputs: result)
|
|
197
|
+
execute_output_handler(agent_def, result, task_executor)
|
|
198
|
+
end
|
|
199
|
+
|
|
194
200
|
result
|
|
195
201
|
end
|
|
196
202
|
|
|
203
|
+
# Execute the output handler (neural or symbolic)
|
|
204
|
+
#
|
|
205
|
+
# @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
|
|
206
|
+
# @param outputs [Hash] The outputs from main execution
|
|
207
|
+
# @param task_executor [LanguageOperator::Agent::TaskExecutor] Task executor for context
|
|
208
|
+
# @return [void]
|
|
209
|
+
def self.execute_output_handler(agent_def, outputs, task_executor)
|
|
210
|
+
output_config = agent_def.output
|
|
211
|
+
|
|
212
|
+
# If symbolic implementation exists, use it
|
|
213
|
+
if output_config.symbolic?
|
|
214
|
+
logger.debug('Executing symbolic output handler')
|
|
215
|
+
# execute_symbolic takes (inputs, context) - outputs are the inputs, task_executor is context
|
|
216
|
+
output_config.execute_symbolic(outputs, task_executor)
|
|
217
|
+
elsif output_config.neural?
|
|
218
|
+
# Neural output - would need LLM access to execute
|
|
219
|
+
# For now, just log the instruction
|
|
220
|
+
logger.info('Neural output handler',
|
|
221
|
+
instruction: output_config.instructions_text,
|
|
222
|
+
outputs: outputs)
|
|
223
|
+
logger.warn('Neural output execution not yet implemented - instruction logged only')
|
|
224
|
+
end
|
|
225
|
+
rescue StandardError => e
|
|
226
|
+
logger.error('Output handler failed',
|
|
227
|
+
error: e.message,
|
|
228
|
+
backtrace: e.backtrace[0..5])
|
|
229
|
+
end
|
|
230
|
+
|
|
197
231
|
# Build executor configuration from agent definition constraints
|
|
198
232
|
#
|
|
199
233
|
# @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
|
|
@@ -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
|
|
@@ -97,7 +97,14 @@ module LanguageOperator
|
|
|
97
97
|
def create_resource(resource)
|
|
98
98
|
resource_client = resource_client_for_resource(resource)
|
|
99
99
|
# Convert hash to K8s::Resource if needed
|
|
100
|
-
k8s_resource = resource.is_a?(K8s::Resource)
|
|
100
|
+
k8s_resource = if resource.is_a?(K8s::Resource)
|
|
101
|
+
resource
|
|
102
|
+
else
|
|
103
|
+
# Remove resourceVersion if present - it should not be set on new resources
|
|
104
|
+
resource_hash = resource.dup
|
|
105
|
+
resource_hash['metadata']&.delete('resourceVersion')
|
|
106
|
+
K8s::Resource.new(resource_hash)
|
|
107
|
+
end
|
|
101
108
|
resource_client.create_resource(k8s_resource)
|
|
102
109
|
end
|
|
103
110
|
|
|
@@ -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.39",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"name": {
|