language-operator 0.1.31 → 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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -8
  3. data/CHANGELOG.md +14 -0
  4. data/CI_STATUS.md +56 -0
  5. data/Gemfile.lock +2 -2
  6. data/Makefile +22 -6
  7. data/lib/language_operator/agent/base.rb +10 -6
  8. data/lib/language_operator/agent/executor.rb +19 -97
  9. data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
  10. data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
  11. data/lib/language_operator/agent/scheduler.rb +60 -0
  12. data/lib/language_operator/agent/task_executor.rb +548 -0
  13. data/lib/language_operator/agent.rb +90 -27
  14. data/lib/language_operator/cli/base_command.rb +117 -0
  15. data/lib/language_operator/cli/commands/agent.rb +339 -407
  16. data/lib/language_operator/cli/commands/cluster.rb +274 -290
  17. data/lib/language_operator/cli/commands/install.rb +110 -119
  18. data/lib/language_operator/cli/commands/model.rb +284 -184
  19. data/lib/language_operator/cli/commands/persona.rb +218 -284
  20. data/lib/language_operator/cli/commands/quickstart.rb +4 -5
  21. data/lib/language_operator/cli/commands/status.rb +31 -35
  22. data/lib/language_operator/cli/commands/system.rb +221 -233
  23. data/lib/language_operator/cli/commands/tool.rb +356 -422
  24. data/lib/language_operator/cli/commands/use.rb +19 -22
  25. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
  26. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
  27. data/lib/language_operator/client/config.rb +20 -21
  28. data/lib/language_operator/config.rb +115 -3
  29. data/lib/language_operator/constants.rb +54 -0
  30. data/lib/language_operator/dsl/agent_context.rb +7 -7
  31. data/lib/language_operator/dsl/agent_definition.rb +111 -26
  32. data/lib/language_operator/dsl/config.rb +30 -66
  33. data/lib/language_operator/dsl/main_definition.rb +114 -0
  34. data/lib/language_operator/dsl/schema.rb +84 -43
  35. data/lib/language_operator/dsl/task_definition.rb +315 -0
  36. data/lib/language_operator/dsl.rb +0 -1
  37. data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
  38. data/lib/language_operator/logger.rb +4 -4
  39. data/lib/language_operator/synthesis_test_harness.rb +324 -0
  40. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
  41. data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
  42. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  43. data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
  44. data/lib/language_operator/type_coercion.rb +250 -0
  45. data/lib/language_operator/ux/base.rb +81 -0
  46. data/lib/language_operator/ux/concerns/README.md +155 -0
  47. data/lib/language_operator/ux/concerns/headings.rb +90 -0
  48. data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
  49. data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
  50. data/lib/language_operator/ux/create_agent.rb +252 -0
  51. data/lib/language_operator/ux/create_model.rb +267 -0
  52. data/lib/language_operator/ux/quickstart.rb +594 -0
  53. data/lib/language_operator/version.rb +1 -1
  54. data/lib/language_operator.rb +2 -0
  55. data/requirements/ARCHITECTURE.md +1 -0
  56. data/requirements/SCRATCH.md +153 -0
  57. data/requirements/dsl.md +0 -0
  58. data/requirements/features +1 -0
  59. data/requirements/personas +1 -0
  60. data/requirements/proposals +1 -0
  61. data/requirements/tasks/iterate.md +14 -15
  62. data/requirements/tasks/optimize.md +13 -4
  63. data/synth/001/Makefile +90 -0
  64. data/synth/001/agent.rb +26 -0
  65. data/synth/001/agent.yaml +7 -0
  66. data/synth/001/output.log +44 -0
  67. data/synth/Makefile +39 -0
  68. data/synth/README.md +342 -0
  69. metadata +37 -10
  70. data/lib/language_operator/dsl/workflow_definition.rb +0 -259
  71. data/test_agent_dsl.rb +0 -108
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'agent/base'
4
4
  require_relative 'agent/executor'
5
+ require_relative 'agent/task_executor'
5
6
  require_relative 'agent/scheduler'
6
7
  require_relative 'agent/web_server'
7
8
  require_relative 'dsl'
@@ -23,6 +24,13 @@ module LanguageOperator
23
24
  # agent = LanguageOperator::Agent::Base.new(config)
24
25
  # agent.execute_goal("Summarize daily news")
25
26
  module Agent
27
+ # Module-level logger for Agent framework
28
+ @logger = LanguageOperator::Logger.new(component: 'Agent')
29
+
30
+ def self.logger
31
+ @logger
32
+ end
33
+
26
34
  # Run the default agent based on environment configuration
27
35
  #
28
36
  # @param config_path [String] Path to configuration file
@@ -53,9 +61,8 @@ module LanguageOperator
53
61
  if agent_code_path && File.exist?(agent_code_path)
54
62
  load_synthesized_agent(agent, agent_code_path, agent_name)
55
63
  else
56
- LanguageOperator::Logger.info('No synthesized code found, running in standard mode',
57
- component: 'Agent',
58
- agent_code_path: agent_code_path)
64
+ logger.info('No synthesized code found, running in standard mode',
65
+ agent_code_path: agent_code_path)
59
66
  agent.run
60
67
  end
61
68
  end
@@ -67,10 +74,9 @@ module LanguageOperator
67
74
  # @param agent_name [String] Name of agent definition
68
75
  # @return [void]
69
76
  def self.load_synthesized_agent(agent, code_path, agent_name)
70
- LanguageOperator::Logger.info('DSL code loading',
71
- component: 'Agent',
72
- path: code_path,
73
- agent_name: agent_name)
77
+ logger.info('DSL code loading',
78
+ path: code_path,
79
+ agent_name: agent_name)
74
80
 
75
81
  # Load synthesized DSL code
76
82
  LanguageOperator::Dsl.load_agent_file(code_path)
@@ -79,10 +85,9 @@ module LanguageOperator
79
85
  agent_def = LanguageOperator::Dsl.agent_registry.get(agent_name) if agent_name
80
86
 
81
87
  if agent_def
82
- LanguageOperator::Logger.info('Agent definition loaded',
83
- component: 'Agent',
84
- agent_name: agent_name,
85
- has_workflow: !agent_def.workflow.nil?)
88
+ logger.info('Agent definition loaded',
89
+ agent_name: agent_name,
90
+ has_workflow: !agent_def.workflow.nil?)
86
91
  run_with_definition(agent, agent_def)
87
92
  else
88
93
  log_definition_not_found(agent_name)
@@ -98,11 +103,10 @@ module LanguageOperator
98
103
  # @param agent_name [String] Name of agent
99
104
  # @return [void]
100
105
  def self.log_definition_not_found(agent_name)
101
- LanguageOperator::Logger.warn('Agent definition not found in registry',
102
- component: 'Agent',
103
- agent_name: agent_name,
104
- available: LanguageOperator::Dsl.agent_registry.all.map(&:name))
105
- LanguageOperator::Logger.info('Falling back to autonomous mode', component: 'Agent')
106
+ logger.warn('Agent definition not found in registry',
107
+ agent_name: agent_name,
108
+ available: LanguageOperator::Dsl.agent_registry.all.map(&:name))
109
+ logger.info('Falling back to autonomous mode')
106
110
  end
107
111
 
108
112
  # Log agent code loading error
@@ -110,11 +114,10 @@ module LanguageOperator
110
114
  # @param error [StandardError] The error
111
115
  # @return [void]
112
116
  def self.log_load_error(error)
113
- LanguageOperator::Logger.error('Failed to load agent code',
114
- component: 'Agent',
115
- error: error.message,
116
- backtrace: error.backtrace[0..3])
117
- LanguageOperator::Logger.info('Falling back to autonomous mode', component: 'Agent')
117
+ logger.error('Failed to load agent code',
118
+ error: error.message,
119
+ backtrace: error.backtrace[0..3])
120
+ logger.info('Falling back to autonomous mode')
118
121
  end
119
122
 
120
123
  # Run agent with a loaded definition
@@ -125,15 +128,34 @@ module LanguageOperator
125
128
  def self.run_with_definition(agent, agent_def)
126
129
  agent.connect!
127
130
 
131
+ # Check if agent uses DSL v1 (task/main) or v0 (workflow/step)
132
+ uses_dsl_v1 = agent_def.main&.defined?
133
+ uses_dsl_v0 = agent_def.workflow
134
+
128
135
  case agent.mode
129
136
  when 'autonomous', 'interactive'
130
- # Execute workflow in autonomous mode
131
- executor = LanguageOperator::Agent::Executor.new(agent)
132
- executor.execute_workflow(agent_def)
137
+ if uses_dsl_v1
138
+ # DSL v1: Execute main block with task executor
139
+ execute_main_block(agent, agent_def)
140
+ elsif uses_dsl_v0
141
+ # DSL v0: Execute workflow in autonomous mode
142
+ executor = LanguageOperator::Agent::Executor.new(agent)
143
+ executor.execute_workflow(agent_def)
144
+ else
145
+ raise 'Agent definition must have either main block (DSL v1) or workflow (DSL v0)'
146
+ end
133
147
  when 'scheduled', 'event-driven'
134
- # Schedule workflow execution
135
- scheduler = LanguageOperator::Agent::Scheduler.new(agent)
136
- scheduler.start_with_workflow(agent_def)
148
+ if uses_dsl_v1
149
+ # DSL v1: Schedule main block execution
150
+ scheduler = LanguageOperator::Agent::Scheduler.new(agent)
151
+ scheduler.start_with_main(agent_def)
152
+ elsif uses_dsl_v0
153
+ # DSL v0: Schedule workflow execution
154
+ scheduler = LanguageOperator::Agent::Scheduler.new(agent)
155
+ scheduler.start_with_workflow(agent_def)
156
+ else
157
+ raise 'Agent definition must have either main block (DSL v1) or workflow (DSL v0)'
158
+ end
137
159
  when 'reactive', 'http', 'webhook'
138
160
  # Start web server with webhooks, MCP tools, and chat endpoint
139
161
  web_server = LanguageOperator::Agent::WebServer.new(agent)
@@ -145,5 +167,46 @@ module LanguageOperator
145
167
  raise "Unknown agent mode: #{agent.mode}"
146
168
  end
147
169
  end
170
+
171
+ # Execute main block (DSL v1) in autonomous mode
172
+ #
173
+ # @param agent [LanguageOperator::Agent::Base] The agent instance
174
+ # @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
175
+ # @return [void]
176
+ def self.execute_main_block(agent, agent_def)
177
+ # Build executor config from agent constraints
178
+ config = build_executor_config(agent_def)
179
+ task_executor = LanguageOperator::Agent::TaskExecutor.new(agent, agent_def.tasks, config)
180
+
181
+ logger.info('Executing main block',
182
+ agent: agent_def.name,
183
+ task_count: agent_def.tasks.size)
184
+
185
+ # Get inputs from environment or default to empty hash
186
+ inputs = {}
187
+
188
+ # Execute main block with task executor as context
189
+ result = agent_def.main.call(inputs, task_executor)
190
+
191
+ logger.info('Main block execution completed',
192
+ result: result)
193
+
194
+ result
195
+ end
196
+
197
+ # Build executor configuration from agent definition constraints
198
+ #
199
+ # @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition
200
+ # @return [Hash] Executor configuration
201
+ def self.build_executor_config(agent_def)
202
+ config = {}
203
+
204
+ if agent_def.constraints
205
+ config[:timeout] = agent_def.constraints[:timeout] if agent_def.constraints[:timeout]
206
+ config[:max_retries] = agent_def.constraints[:max_retries] if agent_def.constraints[:max_retries]
207
+ end
208
+
209
+ config
210
+ end
148
211
  end
149
212
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module LanguageOperator
6
+ module CLI
7
+ # Base class for all CLI commands providing shared functionality
8
+ class BaseCommand < Thor
9
+ no_commands do
10
+ # Lazy-initialized cluster context from options
11
+ # @return [Helpers::ClusterContext]
12
+ def ctx
13
+ @ctx ||= Helpers::ClusterContext.from_options(options)
14
+ end
15
+
16
+ # Handle command errors with consistent formatting
17
+ # @param operation [String] Description of the operation for error message
18
+ # @yield Block to execute with error handling
19
+ def handle_command_error(operation)
20
+ yield
21
+ rescue StandardError => e
22
+ Formatters::ProgressFormatter.error("Failed to #{operation}: #{e.message}")
23
+ raise if ENV['DEBUG']
24
+
25
+ exit 1
26
+ end
27
+
28
+ # Get a Kubernetes resource or exit with helpful error message
29
+ # @param type [String] Resource type (e.g., 'LanguageAgent')
30
+ # @param name [String] Resource name
31
+ # @param error_message [String, nil] Custom error message (optional)
32
+ # @return [Hash] The Kubernetes resource
33
+ def get_resource_or_exit(type, name, error_message: nil)
34
+ ctx.client.get_resource(type, name, ctx.namespace)
35
+ rescue K8s::Error::NotFound => e
36
+ # Try to provide helpful fuzzy matching suggestions
37
+ resources = ctx.client.list_resources(type, namespace: ctx.namespace)
38
+ available_names = resources.map { |r| r.dig('metadata', 'name') }
39
+
40
+ msg = error_message || "#{type} '#{name}' not found in cluster '#{ctx.name}'"
41
+ Errors::Handler.handle_not_found(
42
+ e,
43
+ resource_type: type,
44
+ resource_name: name,
45
+ available_names: available_names,
46
+ cluster_name: ctx.name,
47
+ custom_message: msg
48
+ )
49
+ exit 1
50
+ end
51
+
52
+ # Confirm resource deletion with user
53
+ # @param resource_type [String] Type of resource being deleted
54
+ # @param name [String] Resource name
55
+ # @param cluster [String] Cluster name
56
+ # @param details [Hash] Additional details to display
57
+ # @param force [Boolean] Skip confirmation if true
58
+ # @return [Boolean] True if deletion should proceed
59
+ def confirm_deletion(resource_type, name, cluster, details: {}, force: false)
60
+ return true if force
61
+
62
+ puts "This will delete #{resource_type} '#{name}' from cluster '#{cluster}':"
63
+ details.each { |key, value| puts " #{key}: #{value}" }
64
+ puts
65
+
66
+ Helpers::UserPrompts.confirm('Are you sure?')
67
+ end
68
+
69
+ # Check if resource has dependencies and confirm deletion
70
+ # @param resource_type [String] Type of resource (e.g., 'persona', 'model', 'tool')
71
+ # @param resource_name [String] Name of the resource
72
+ # @param force [Boolean] Skip dependency check if true
73
+ # @return [Boolean] True if deletion should proceed
74
+ def check_dependencies_and_confirm(resource_type, resource_name, force: false)
75
+ agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
76
+
77
+ checker_method = "agents_using_#{resource_type}"
78
+ agents_using = Helpers::ResourceDependencyChecker.send(checker_method, agents, resource_name)
79
+
80
+ if agents_using.any? && !force
81
+ Formatters::ProgressFormatter.warn(
82
+ "#{resource_type.capitalize} '#{resource_name}' is in use by #{agents_using.count} agent(s)"
83
+ )
84
+ puts
85
+ puts "Agents using this #{resource_type}:"
86
+ agents_using.each { |agent| puts " - #{agent.dig('metadata', 'name')}" }
87
+ puts
88
+ puts 'Delete these agents first, or use --force to delete anyway.'
89
+ puts
90
+
91
+ return Helpers::UserPrompts.confirm('Are you sure?')
92
+ end
93
+
94
+ true
95
+ end
96
+
97
+ # List resources or handle empty state
98
+ # @param type [String] Resource type (e.g., 'LanguageModel')
99
+ # @param empty_message [String, nil] Custom message when no resources found
100
+ # @yield Optional block to execute when list is empty (for guidance)
101
+ # @return [Array<Hash>] List of resources (empty array if none found)
102
+ def list_resources_or_empty(type, empty_message: nil)
103
+ resources = ctx.client.list_resources(type, namespace: ctx.namespace)
104
+
105
+ if resources.empty?
106
+ msg = empty_message || "No #{type} resources found in cluster '#{ctx.name}'"
107
+ Formatters::ProgressFormatter.info(msg)
108
+ yield if block_given?
109
+ end
110
+
111
+ resources
112
+ end
113
+ end
114
+ # rubocop:enable Metrics/BlockLength
115
+ end
116
+ end
117
+ end