language-operator 0.1.30 → 0.1.35

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -8
  3. data/CHANGELOG.md +49 -0
  4. data/CI_STATUS.md +56 -0
  5. data/Gemfile.lock +2 -2
  6. data/Makefile +28 -7
  7. data/Rakefile +29 -0
  8. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  9. data/docs/dsl/agent-reference.md +13 -0
  10. data/lib/language_operator/agent/base.rb +10 -6
  11. data/lib/language_operator/agent/executor.rb +19 -97
  12. data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
  13. data/lib/language_operator/agent/safety/safe_executor.rb +39 -2
  14. data/lib/language_operator/agent/scheduler.rb +60 -0
  15. data/lib/language_operator/agent/task_executor.rb +548 -0
  16. data/lib/language_operator/agent.rb +90 -27
  17. data/lib/language_operator/cli/base_command.rb +117 -0
  18. data/lib/language_operator/cli/commands/agent.rb +351 -466
  19. data/lib/language_operator/cli/commands/cluster.rb +276 -256
  20. data/lib/language_operator/cli/commands/install.rb +110 -119
  21. data/lib/language_operator/cli/commands/model.rb +284 -184
  22. data/lib/language_operator/cli/commands/persona.rb +220 -289
  23. data/lib/language_operator/cli/commands/quickstart.rb +4 -5
  24. data/lib/language_operator/cli/commands/status.rb +36 -53
  25. data/lib/language_operator/cli/commands/system.rb +760 -0
  26. data/lib/language_operator/cli/commands/tool.rb +356 -422
  27. data/lib/language_operator/cli/commands/use.rb +19 -22
  28. data/lib/language_operator/cli/formatters/code_formatter.rb +3 -7
  29. data/lib/language_operator/cli/formatters/log_formatter.rb +3 -5
  30. data/lib/language_operator/cli/formatters/progress_formatter.rb +3 -7
  31. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  32. data/lib/language_operator/cli/formatters/table_formatter.rb +10 -26
  33. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  34. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
  35. data/lib/language_operator/cli/main.rb +4 -0
  36. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
  37. data/lib/language_operator/client/config.rb +20 -21
  38. data/lib/language_operator/config.rb +115 -3
  39. data/lib/language_operator/constants.rb +54 -0
  40. data/lib/language_operator/dsl/agent_context.rb +7 -7
  41. data/lib/language_operator/dsl/agent_definition.rb +111 -26
  42. data/lib/language_operator/dsl/config.rb +30 -66
  43. data/lib/language_operator/dsl/main_definition.rb +114 -0
  44. data/lib/language_operator/dsl/schema.rb +1143 -0
  45. data/lib/language_operator/dsl/task_definition.rb +315 -0
  46. data/lib/language_operator/dsl.rb +1 -1
  47. data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
  48. data/lib/language_operator/logger.rb +4 -4
  49. data/lib/language_operator/synthesis_test_harness.rb +324 -0
  50. data/lib/language_operator/templates/README.md +23 -0
  51. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +133 -0
  52. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  53. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  54. data/lib/language_operator/templates/schema/CHANGELOG.md +119 -0
  55. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  56. data/lib/language_operator/templates/schema/agent_dsl_schema.json +494 -0
  57. data/lib/language_operator/type_coercion.rb +250 -0
  58. data/lib/language_operator/ux/base.rb +81 -0
  59. data/lib/language_operator/ux/concerns/README.md +155 -0
  60. data/lib/language_operator/ux/concerns/headings.rb +90 -0
  61. data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
  62. data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
  63. data/lib/language_operator/ux/create_agent.rb +252 -0
  64. data/lib/language_operator/ux/create_model.rb +267 -0
  65. data/lib/language_operator/ux/quickstart.rb +594 -0
  66. data/lib/language_operator/version.rb +1 -1
  67. data/lib/language_operator.rb +2 -0
  68. data/requirements/ARCHITECTURE.md +1 -0
  69. data/requirements/SCRATCH.md +153 -0
  70. data/requirements/dsl.md +0 -0
  71. data/requirements/features +1 -0
  72. data/requirements/personas +1 -0
  73. data/requirements/proposals +1 -0
  74. data/requirements/tasks/iterate.md +14 -15
  75. data/requirements/tasks/optimize.md +13 -4
  76. data/synth/001/Makefile +90 -0
  77. data/synth/001/agent.rb +26 -0
  78. data/synth/001/agent.yaml +7 -0
  79. data/synth/001/output.log +44 -0
  80. data/synth/Makefile +39 -0
  81. data/synth/README.md +342 -0
  82. metadata +49 -18
  83. data/examples/README.md +0 -569
  84. data/examples/agent_example.rb +0 -86
  85. data/examples/chat_endpoint_agent.rb +0 -118
  86. data/examples/github_webhook_agent.rb +0 -171
  87. data/examples/mcp_agent.rb +0 -158
  88. data/examples/oauth_callback_agent.rb +0 -296
  89. data/examples/stripe_webhook_agent.rb +0 -219
  90. data/examples/webhook_agent.rb +0 -80
  91. data/lib/language_operator/dsl/workflow_definition.rb +0 -259
  92. data/test_agent_dsl.rb +0 -108
@@ -13,14 +13,14 @@ module LanguageOperator
13
13
  #
14
14
  # schedule "0 12 * * *"
15
15
  #
16
- # objectives [
17
- # "Search for recent news",
18
- # "Summarize findings"
19
- # ]
16
+ # task :search,
17
+ # instructions: "search for recent news",
18
+ # inputs: {},
19
+ # outputs: { results: 'array' }
20
20
  #
21
- # workflow do
22
- # step :search, tool: "web_search"
23
- # step :summarize, depends_on: :search
21
+ # main do |inputs|
22
+ # results = execute_task(:search)
23
+ # results
24
24
  # end
25
25
  # end
26
26
  class AgentContext
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'workflow_definition'
3
+ require_relative 'main_definition'
4
+ require_relative 'task_definition'
4
5
  require_relative 'webhook_definition'
5
6
  require_relative 'mcp_server_definition'
6
7
  require_relative 'chat_endpoint_definition'
@@ -11,7 +12,7 @@ module LanguageOperator
11
12
  module Dsl
12
13
  # Agent definition for autonomous agents
13
14
  #
14
- # Defines an agent with objectives, workflow, schedule, and constraints.
15
+ # Defines an agent with objectives, tasks, main execution block, schedule, and constraints.
15
16
  # Used within the DSL to create agents that can be executed standalone
16
17
  # or deployed to Kubernetes.
17
18
  #
@@ -21,14 +22,14 @@ module LanguageOperator
21
22
  #
22
23
  # schedule "0 12 * * *"
23
24
  #
24
- # objectives [
25
- # "Search for recent news",
26
- # "Summarize findings"
27
- # ]
25
+ # task :search,
26
+ # instructions: "search for latest news",
27
+ # inputs: {},
28
+ # outputs: { results: 'array' }
28
29
  #
29
- # workflow do
30
- # step :search, tool: "web_search", params: {query: "latest news"}
31
- # step :summarize, depends_on: :search
30
+ # main do |inputs|
31
+ # results = execute_task(:search)
32
+ # results
32
33
  # end
33
34
  # end
34
35
  #
@@ -47,7 +48,7 @@ module LanguageOperator
47
48
  class AgentDefinition
48
49
  include LanguageOperator::Loggable
49
50
 
50
- attr_reader :name, :description, :persona, :schedule, :objectives, :workflow,
51
+ attr_reader :name, :description, :persona, :schedule, :objectives, :main, :tasks,
51
52
  :constraints, :output_config, :execution_mode, :webhooks, :mcp_server, :chat_endpoint
52
53
 
53
54
  def initialize(name)
@@ -56,7 +57,8 @@ module LanguageOperator
56
57
  @persona = nil
57
58
  @schedule = nil
58
59
  @objectives = []
59
- @workflow = nil
60
+ @main = nil
61
+ @tasks = {}
60
62
  @constraints = {}
61
63
  @output_config = {}
62
64
  @execution_mode = :autonomous
@@ -118,16 +120,95 @@ module LanguageOperator
118
120
  @objectives << text
119
121
  end
120
122
 
121
- # Define workflow with steps
123
+ # Define main execution block (DSL v1)
122
124
  #
123
- # @yield Workflow definition block
124
- # @return [WorkflowDefinition] Current workflow
125
- def workflow(&block)
126
- return @workflow if block.nil?
125
+ # The main block is the imperative entry point for agent execution.
126
+ # It receives agent inputs and returns agent outputs. Use execute_task()
127
+ # to call organic functions (tasks) defined with the task directive.
128
+ #
129
+ # @yield Main execution block
130
+ # @return [MainDefinition] Current main definition
131
+ # @example
132
+ # main do |inputs|
133
+ # result = execute_task(:fetch_data, inputs: inputs)
134
+ # execute_task(:process_data, inputs: result)
135
+ # end
136
+ def main(&block)
137
+ return @main if block.nil?
138
+
139
+ @main = MainDefinition.new
140
+ @main.execute(&block) if block
141
+ @main
142
+ end
143
+
144
+ # Define a task (organic function) - DSL v1
145
+ #
146
+ # Tasks are the core primitive of DSL v1, representing organic functions with
147
+ # stable input/output contracts. Tasks can be neural (instructions-based),
148
+ # symbolic (code-based), or hybrid (both).
149
+ #
150
+ # @param name [Symbol] Task name
151
+ # @param options [Hash] Task configuration
152
+ # @option options [Hash] :inputs Input schema (param => type)
153
+ # @option options [Hash] :outputs Output schema (field => type)
154
+ # @option options [String] :instructions Natural language instructions (neural)
155
+ # @yield [inputs] Symbolic implementation block (optional)
156
+ # @yieldparam inputs [Hash] Validated input parameters
157
+ # @yieldreturn [Hash] Output matching outputs schema
158
+ # @return [TaskDefinition] The task definition
159
+ #
160
+ # @example Neural task
161
+ # task :analyze_data,
162
+ # instructions: "Analyze the data for anomalies",
163
+ # inputs: { data: 'array' },
164
+ # outputs: { issues: 'array', summary: 'string' }
165
+ #
166
+ # @example Symbolic task
167
+ # task :calculate_total,
168
+ # inputs: { items: 'array' },
169
+ # outputs: { total: 'number' }
170
+ # do |inputs|
171
+ # { total: inputs[:items].sum { |i| i['amount'] } }
172
+ # end
173
+ #
174
+ # @example Hybrid task
175
+ # task :fetch_user,
176
+ # instructions: "Fetch user from database",
177
+ # inputs: { user_id: 'integer' },
178
+ # outputs: { user: 'hash' }
179
+ # do |inputs|
180
+ # execute_tool('database', 'get_user', id: inputs[:user_id])
181
+ # end
182
+ def task(name, **options, &block)
183
+ # Create task definition
184
+ task_def = TaskDefinition.new(name)
185
+
186
+ # Configure from options (keyword arguments)
187
+ task_def.inputs(options[:inputs]) if options[:inputs]
188
+ task_def.outputs(options[:outputs]) if options[:outputs]
189
+ task_def.instructions(options[:instructions]) if options[:instructions]
190
+
191
+ # Symbolic implementation (if block provided)
192
+ task_def.execute(&block) if block
193
+
194
+ # Store in tasks collection
195
+ @tasks[name] = task_def
196
+
197
+ task_type = if task_def.neural? && task_def.symbolic?
198
+ 'hybrid'
199
+ elsif task_def.neural?
200
+ 'neural'
201
+ else
202
+ 'symbolic'
203
+ end
204
+
205
+ logger.debug('Task defined',
206
+ name: name,
207
+ type: task_type,
208
+ inputs: options[:inputs]&.keys || [],
209
+ outputs: options[:outputs]&.keys || [])
127
210
 
128
- @workflow = WorkflowDefinition.new
129
- @workflow.instance_eval(&block) if block
130
- @workflow
211
+ task_def
131
212
  end
132
213
 
133
214
  # Define constraints (max_iterations, timeout, etc.)
@@ -213,7 +294,7 @@ module LanguageOperator
213
294
  name: @name,
214
295
  mode: @execution_mode,
215
296
  objectives_count: @objectives.size,
216
- has_workflow: !@workflow.nil?)
297
+ has_main: !@main.nil?)
217
298
 
218
299
  case @execution_mode
219
300
  when :scheduled
@@ -317,7 +398,7 @@ module LanguageOperator
317
398
  def execute_objectives
318
399
  logger.info('Executing objectives',
319
400
  total: @objectives.size,
320
- has_workflow: !@workflow.nil?)
401
+ has_main: !@main.nil?)
321
402
 
322
403
  @objectives.each_with_index do |objective, index|
323
404
  logger.info('Executing objective',
@@ -325,13 +406,13 @@ module LanguageOperator
325
406
  total: @objectives.size,
326
407
  objective: objective[0..100])
327
408
 
328
- # If workflow defined, execute it; otherwise just log
329
- if @workflow
330
- logger.timed('Objective workflow execution') do
331
- @workflow.execute(objective)
409
+ # If main defined, execute it; otherwise just log
410
+ if @main
411
+ logger.timed('Objective main execution') do
412
+ @main.call({ objective: objective })
332
413
  end
333
414
  else
334
- logger.warn('No workflow defined, skipping execution')
415
+ logger.warn('No main block defined, skipping execution')
335
416
  end
336
417
  end
337
418
 
@@ -353,6 +434,10 @@ module LanguageOperator
353
434
  @constraints[:timeout] = value
354
435
  end
355
436
 
437
+ def max_retries(value)
438
+ @constraints[:max_retries] = value
439
+ end
440
+
356
441
  def memory(value)
357
442
  @constraints[:memory] = value
358
443
  end
@@ -4,8 +4,11 @@ module LanguageOperator
4
4
  module Dsl
5
5
  # Configuration helper for managing environment variables
6
6
  #
7
- # Provides utilities for reading and managing environment variables with fallback support,
8
- # type conversion, and validation. All methods are class methods.
7
+ # This class delegates to LanguageOperator::Config for all functionality.
8
+ # It exists for backwards compatibility with existing code that uses
9
+ # Dsl::Config.
10
+ #
11
+ # @deprecated Use LanguageOperator::Config directly instead
9
12
  #
10
13
  # @example Basic usage
11
14
  # Config.get('SMTP_HOST', 'MAIL_HOST', default: 'localhost')
@@ -13,92 +16,45 @@ module LanguageOperator
13
16
  # Config.get_bool('USE_TLS', default: true)
14
17
  class Config
15
18
  # Get environment variable with multiple fallback keys
16
- #
17
- # @param keys [Array<String>] Environment variable names to try
18
- # @param default [Object, nil] Default value if none found
19
- # @return [String, nil] The first non-nil value or default
19
+ # Delegates to LanguageOperator::Config.get
20
20
  def self.get(*keys, default: nil)
21
- keys.each do |key|
22
- value = ENV.fetch(key.to_s, nil)
23
- return value if value
24
- end
25
- default
21
+ LanguageOperator::Config.get(*keys, default: default)
26
22
  end
27
23
 
28
24
  # Get required environment variable with fallback keys
29
- #
30
- # @param keys [Array<String>] Environment variable names to try
31
- # @return [String] The first non-nil value
32
- # @raise [ArgumentError] If none of the keys are set
25
+ # Delegates to LanguageOperator::Config.require
33
26
  def self.require(*keys)
34
- value = get(*keys)
35
- raise ArgumentError, "Missing required configuration: #{keys.join(' or ')}" unless value
36
-
37
- value
27
+ LanguageOperator::Config.require(*keys)
38
28
  end
39
29
 
40
30
  # Get environment variable as integer
41
- #
42
- # @param keys [Array<String>] Environment variable names to try
43
- # @param default [Integer, nil] Default value if none found
44
- # @return [Integer, nil] The value converted to integer, or default
31
+ # Delegates to LanguageOperator::Config.get_int
45
32
  def self.get_int(*keys, default: nil)
46
- value = get(*keys)
47
- return default if value.nil?
48
-
49
- value.to_i
33
+ LanguageOperator::Config.get_int(*keys, default: default)
50
34
  end
51
35
 
52
36
  # Get environment variable as boolean
53
- #
54
- # Treats 'true', '1', 'yes', 'on' as true (case insensitive).
55
- #
56
- # @param keys [Array<String>] Environment variable names to try
57
- # @param default [Boolean] Default value if none found
58
- # @return [Boolean] The value as boolean
37
+ # Delegates to LanguageOperator::Config.get_bool
59
38
  def self.get_bool(*keys, default: false)
60
- value = get(*keys)
61
- return default if value.nil?
62
-
63
- value.to_s.downcase.match?(/^(true|1|yes|on)$/)
39
+ LanguageOperator::Config.get_bool(*keys, default: default)
64
40
  end
65
41
 
66
- # Get environment variable as array (split by separator)
67
- #
68
- # @param keys [Array<String>] Environment variable names to try
69
- # @param default [Array] Default value if none found
70
- # @param separator [String] Character to split on (default: ',')
71
- # @return [Array<String>] The value split into array
42
+ # Get environment variable as array
43
+ # Delegates to LanguageOperator::Config.get_array
72
44
  def self.get_array(*keys, default: [], separator: ',')
73
- value = get(*keys)
74
- return default if value.nil? || value.empty?
75
-
76
- value.split(separator).map(&:strip).reject(&:empty?)
45
+ LanguageOperator::Config.get_array(*keys, default: default, separator: separator)
77
46
  end
78
47
 
79
- # Check if all required keys are present
80
- #
81
- # @param keys [Array<String>] Environment variable names to check
82
- # @return [Array<String>] Array of missing keys (empty if all present)
83
- def self.check_required(*keys)
84
- keys.reject { |key| ENV.fetch(key.to_s, nil) }
85
- end
86
-
87
- # Check if environment variable is set (even if empty string)
88
- #
89
- # @param keys [Array<String>] Environment variable names to check
90
- # @return [Boolean] True if any key is set
48
+ # Check if environment variable is set
49
+ # Delegates to LanguageOperator::Config.set?
91
50
  def self.set?(*keys)
92
- keys.any? { |key| ENV.key?(key.to_s) }
51
+ LanguageOperator::Config.set?(*keys)
93
52
  end
94
53
 
95
54
  # Get all environment variables matching a prefix
96
- #
97
- # @param prefix [String] Prefix to match
98
- # @return [Hash<String, String>] Hash with prefix removed from keys
55
+ # Delegates to LanguageOperator::Config.with_prefix
99
56
  def self.with_prefix(prefix)
100
- ENV.select { |key, _| key.start_with?(prefix) }
101
- .transform_keys { |key| key.sub(prefix, '') }
57
+ LanguageOperator::Config.with_prefix(prefix)
102
58
  end
103
59
 
104
60
  # Build a configuration hash from environment variables
@@ -109,11 +65,19 @@ module LanguageOperator
109
65
  config = {}
110
66
  mappings.each do |config_key, env_keys|
111
67
  env_keys = [env_keys] unless env_keys.is_a?(Array)
112
- value = get(*env_keys)
68
+ value = LanguageOperator::Config.get(*env_keys)
113
69
  config[config_key] = value if value
114
70
  end
115
71
  config
116
72
  end
73
+
74
+ # Check if all required keys are present
75
+ #
76
+ # @param keys [Array<String>] Environment variable names to check
77
+ # @return [Array<String>] Array of missing keys (empty if all present)
78
+ def self.check_required(*keys)
79
+ keys.reject { |key| ENV.fetch(key.to_s, nil) }
80
+ end
117
81
  end
118
82
  end
119
83
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../loggable'
4
+
5
+ module LanguageOperator
6
+ module Dsl
7
+ # Main execution block for agents (DSL v1)
8
+ #
9
+ # Defines the imperative entry point for agent execution. The main block receives
10
+ # agent inputs and returns agent outputs. Within the block, agents can call tasks
11
+ # using execute_task(), use standard Ruby control flow (if/else, loops), and handle
12
+ # errors with standard Ruby exceptions.
13
+ #
14
+ # This replaces the declarative workflow/step model with an imperative programming
15
+ # model centered on organic functions (tasks).
16
+ #
17
+ # @example Simple main block
18
+ # main do |inputs|
19
+ # result = execute_task(:fetch_data, inputs: inputs)
20
+ # execute_task(:process_data, inputs: result)
21
+ # end
22
+ #
23
+ # @example Main block with control flow
24
+ # main do |inputs|
25
+ # data = execute_task(:fetch_data, inputs: inputs)
26
+ #
27
+ # if data[:count] > 100
28
+ # execute_task(:send_alert, inputs: { count: data[:count] })
29
+ # end
30
+ #
31
+ # execute_task(:save_results, inputs: data)
32
+ # end
33
+ #
34
+ # @example Main block with error handling
35
+ # main do |inputs|
36
+ # begin
37
+ # result = execute_task(:risky_operation, inputs: inputs)
38
+ # { success: true, result: result }
39
+ # rescue => e
40
+ # logger.error("Operation failed: #{e.message}")
41
+ # { success: false, error: e.message }
42
+ # end
43
+ # end
44
+ class MainDefinition
45
+ include LanguageOperator::Loggable
46
+
47
+ attr_reader :execute_block
48
+
49
+ # Initialize a new main definition
50
+ def initialize
51
+ @execute_block = nil
52
+ end
53
+
54
+ # Define the main execution block
55
+ #
56
+ # @yield [inputs] Block that receives agent inputs and returns agent outputs
57
+ # @yieldparam inputs [Hash] Agent input parameters
58
+ # @yieldreturn [Object] Agent output (typically a Hash)
59
+ # @return [void]
60
+ # @example
61
+ # execute do |inputs|
62
+ # result = execute_task(:my_task, inputs: inputs)
63
+ # result
64
+ # end
65
+ def execute(&block)
66
+ raise ArgumentError, 'Main block is required' unless block
67
+
68
+ @execute_block = block
69
+ logger.debug('Main block defined', arity: block.arity)
70
+ end
71
+
72
+ # Execute the main block with given inputs
73
+ #
74
+ # @param inputs [Hash] Agent input parameters
75
+ # @param context [Object] Execution context that provides execute_task method
76
+ # @return [Object] Result from main block
77
+ # @raise [RuntimeError] If main block is not defined
78
+ # @raise [ArgumentError] If inputs is not a Hash
79
+ def call(inputs, context)
80
+ raise 'Main block not defined. Use execute { |inputs| ... } to define it.' unless @execute_block
81
+ raise ArgumentError, "inputs must be a Hash, got #{inputs.class}" unless inputs.is_a?(Hash)
82
+
83
+ logger.info('Executing main block', inputs_keys: inputs.keys)
84
+
85
+ result = logger.timed('Main execution') do
86
+ # Execute block in context to provide access to execute_task
87
+ context.instance_exec(inputs, &@execute_block)
88
+ end
89
+
90
+ logger.info('Main block completed')
91
+ result
92
+ rescue StandardError => e
93
+ logger.error('Main block execution failed',
94
+ error: e.class.name,
95
+ message: e.message,
96
+ backtrace: e.backtrace&.first(5))
97
+ raise
98
+ end
99
+
100
+ # Check if main block is defined
101
+ #
102
+ # @return [Boolean] True if execute block is set
103
+ def defined?
104
+ !@execute_block.nil?
105
+ end
106
+
107
+ private
108
+
109
+ def logger_component
110
+ 'Main'
111
+ end
112
+ end
113
+ end
114
+ end