language-operator 0.1.37 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b57f326a81a7683900ef0700d54b97c296019f74e044dc886cc8aa02703e5a3
4
- data.tar.gz: 7ba35359cc513dd5d53df60269f7052449453032c6ce749631e5dd6d80983a66
3
+ metadata.gz: 4476732244850657fb5161189ad68010e2ffdef4f4949361786893d119f0de89
4
+ data.tar.gz: 5755720ffc0df24c88c5ec5ea223a4f8899c604f938422e420e878833d172de5
5
5
  SHA512:
6
- metadata.gz: 62327e75b4357c55c1de51fbb56eebe4a16faede2b3ead5591a0dd2b8dc8760116130f9e9820ca47499aaa5ad3e285048862db7fef788d96416c4cc9fe123560
7
- data.tar.gz: 0f63c9ca8e9523f6825a053cbd02a21ccd4103067b36ad190fb78316df051d0b6972a36917fd2a4ef9f26e449deaaaac487a83ae63e9f562fd9c317a8df2f490
6
+ metadata.gz: a4408235ba4cc775fc175f7760e0632e78457630b263690efeb3ddc772c58facfcd7b7e0780a38d33bda5c4f9d3d12b46eed0f83d43631c94f1ddfe7180be61d
7
+ data.tar.gz: 3ccbd9c8c91ef1d19ae467cafe3740cc84586853692cf46569d77b692b1cddf682fb21300bbb75244e71335b84a10fde768f6baff1016d18acf8b4c31a869862
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.38)
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
@@ -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
@@ -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.38
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.38",
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.38'
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.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Ryan