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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -8
- data/CHANGELOG.md +14 -0
- data/CI_STATUS.md +56 -0
- data/Gemfile.lock +2 -2
- data/Makefile +22 -6
- data/lib/language_operator/agent/base.rb +10 -6
- data/lib/language_operator/agent/executor.rb +19 -97
- data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
- data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
- data/lib/language_operator/agent/scheduler.rb +60 -0
- data/lib/language_operator/agent/task_executor.rb +548 -0
- data/lib/language_operator/agent.rb +90 -27
- data/lib/language_operator/cli/base_command.rb +117 -0
- data/lib/language_operator/cli/commands/agent.rb +339 -407
- data/lib/language_operator/cli/commands/cluster.rb +274 -290
- data/lib/language_operator/cli/commands/install.rb +110 -119
- data/lib/language_operator/cli/commands/model.rb +284 -184
- data/lib/language_operator/cli/commands/persona.rb +218 -284
- data/lib/language_operator/cli/commands/quickstart.rb +4 -5
- data/lib/language_operator/cli/commands/status.rb +31 -35
- data/lib/language_operator/cli/commands/system.rb +221 -233
- data/lib/language_operator/cli/commands/tool.rb +356 -422
- data/lib/language_operator/cli/commands/use.rb +19 -22
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
- data/lib/language_operator/client/config.rb +20 -21
- data/lib/language_operator/config.rb +115 -3
- data/lib/language_operator/constants.rb +54 -0
- data/lib/language_operator/dsl/agent_context.rb +7 -7
- data/lib/language_operator/dsl/agent_definition.rb +111 -26
- data/lib/language_operator/dsl/config.rb +30 -66
- data/lib/language_operator/dsl/main_definition.rb +114 -0
- data/lib/language_operator/dsl/schema.rb +84 -43
- data/lib/language_operator/dsl/task_definition.rb +315 -0
- data/lib/language_operator/dsl.rb +0 -1
- data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
- data/lib/language_operator/logger.rb +4 -4
- data/lib/language_operator/synthesis_test_harness.rb +324 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
- data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
- data/lib/language_operator/type_coercion.rb +250 -0
- data/lib/language_operator/ux/base.rb +81 -0
- data/lib/language_operator/ux/concerns/README.md +155 -0
- data/lib/language_operator/ux/concerns/headings.rb +90 -0
- data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
- data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
- data/lib/language_operator/ux/create_agent.rb +252 -0
- data/lib/language_operator/ux/create_model.rb +267 -0
- data/lib/language_operator/ux/quickstart.rb +594 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +2 -0
- data/requirements/ARCHITECTURE.md +1 -0
- data/requirements/SCRATCH.md +153 -0
- data/requirements/dsl.md +0 -0
- data/requirements/features +1 -0
- data/requirements/personas +1 -0
- data/requirements/proposals +1 -0
- data/requirements/tasks/iterate.md +14 -15
- data/requirements/tasks/optimize.md +13 -4
- data/synth/001/Makefile +90 -0
- data/synth/001/agent.rb +26 -0
- data/synth/001/agent.yaml +7 -0
- data/synth/001/output.log +44 -0
- data/synth/Makefile +39 -0
- data/synth/README.md +342 -0
- metadata +37 -10
- data/lib/language_operator/dsl/workflow_definition.rb +0 -259
- 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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|