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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b57f326a81a7683900ef0700d54b97c296019f74e044dc886cc8aa02703e5a3
4
- data.tar.gz: 7ba35359cc513dd5d53df60269f7052449453032c6ce749631e5dd6d80983a66
3
+ metadata.gz: a3674b7904fe01e280096b7e7c37ce8be922379d75a194e31ffd5239b082e7b8
4
+ data.tar.gz: 61cd4ab21b5495b537cb4babbe5e0a14c4890a93b9b1f2a1d4d4069c53c3ddc1
5
5
  SHA512:
6
- metadata.gz: 62327e75b4357c55c1de51fbb56eebe4a16faede2b3ead5591a0dd2b8dc8760116130f9e9820ca47499aaa5ad3e285048862db7fef788d96416c4cc9fe123560
7
- data.tar.gz: 0f63c9ca8e9523f6825a053cbd02a21ccd4103067b36ad190fb78316df051d0b6972a36917fd2a4ef9f26e449deaaaac487a83ae63e9f562fd9c317a8df2f490
6
+ metadata.gz: 33714e1bd58eb58d54101a038954a1fbd425ffa34904fdfc8cb3dcb9ba83b0090d445343071190b7bb48e599d8691d99b4918f8a9eab2ecf135634f92c7825ac
7
+ data.tar.gz: 235149dfa2f126e31b8450426d70ebb25f39badb30b343e274c7b50ab0d7b3674f224ffae251589967c4ea47022b171af3f0212cd466bc6c994171534e0b48ef
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- language-operator (0.1.37)
4
+ language-operator (0.1.39)
5
5
  faraday (~> 2.0)
6
6
  k8s-ruby (~> 0.17)
7
7
  mcp (~> 0.4)
@@ -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.neural?
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 configuration
226
+ # Define output handler (organic function) - DSL v1
227
227
  #
228
- # @yield Output configuration block
229
- # @return [Hash] Current output config
230
- def output(&block)
231
- return @output_config if block.nil?
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
- output_builder = OutputBuilder.new
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) ? resource : K8s::Resource.new(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
 
@@ -2,7 +2,7 @@
2
2
  :openapi: 3.0.3
3
3
  :info:
4
4
  :title: Language Operator Agent API
5
- :version: 0.1.37
5
+ :version: 0.1.39
6
6
  :description: HTTP API endpoints exposed by Language Operator reactive agents
7
7
  :contact:
8
8
  :name: Language Operator
@@ -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.37",
6
+ "version": "0.1.39",
7
7
  "type": "object",
8
8
  "properties": {
9
9
  "name": {
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LanguageOperator
4
- VERSION = '0.1.37'
4
+ VERSION = '0.1.39'
5
5
  end
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.37
4
+ version: 0.1.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Ryan