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.
- checksums.yaml +4 -4
- data/.rubocop.yml +7 -8
- data/CHANGELOG.md +49 -0
- data/CI_STATUS.md +56 -0
- data/Gemfile.lock +2 -2
- data/Makefile +28 -7
- data/Rakefile +29 -0
- data/docs/dsl/SCHEMA_VERSION.md +250 -0
- data/docs/dsl/agent-reference.md +13 -0
- 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 +39 -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 +351 -466
- data/lib/language_operator/cli/commands/cluster.rb +276 -256
- 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 +220 -289
- data/lib/language_operator/cli/commands/quickstart.rb +4 -5
- data/lib/language_operator/cli/commands/status.rb +36 -53
- data/lib/language_operator/cli/commands/system.rb +760 -0
- 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/formatters/code_formatter.rb +3 -7
- data/lib/language_operator/cli/formatters/log_formatter.rb +3 -5
- data/lib/language_operator/cli/formatters/progress_formatter.rb +3 -7
- data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +10 -26
- data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
- data/lib/language_operator/cli/main.rb +4 -0
- 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 +1143 -0
- data/lib/language_operator/dsl/task_definition.rb +315 -0
- data/lib/language_operator/dsl.rb +1 -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/README.md +23 -0
- data/lib/language_operator/templates/examples/agent_synthesis.tmpl +133 -0
- data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
- data/lib/language_operator/templates/schema/.gitkeep +0 -0
- data/lib/language_operator/templates/schema/CHANGELOG.md +119 -0
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +494 -0
- 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 +49 -18
- data/examples/README.md +0 -569
- data/examples/agent_example.rb +0 -86
- data/examples/chat_endpoint_agent.rb +0 -118
- data/examples/github_webhook_agent.rb +0 -171
- data/examples/mcp_agent.rb +0 -158
- data/examples/oauth_callback_agent.rb +0 -296
- data/examples/stripe_webhook_agent.rb +0 -219
- data/examples/webhook_agent.rb +0 -80
- data/lib/language_operator/dsl/workflow_definition.rb +0 -259
- data/test_agent_dsl.rb +0 -108
|
@@ -13,14 +13,14 @@ module LanguageOperator
|
|
|
13
13
|
#
|
|
14
14
|
# schedule "0 12 * * *"
|
|
15
15
|
#
|
|
16
|
-
#
|
|
17
|
-
# "
|
|
18
|
-
#
|
|
19
|
-
#
|
|
16
|
+
# task :search,
|
|
17
|
+
# instructions: "search for recent news",
|
|
18
|
+
# inputs: {},
|
|
19
|
+
# outputs: { results: 'array' }
|
|
20
20
|
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
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 '
|
|
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,
|
|
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
|
-
#
|
|
25
|
-
# "
|
|
26
|
-
#
|
|
27
|
-
#
|
|
25
|
+
# task :search,
|
|
26
|
+
# instructions: "search for latest news",
|
|
27
|
+
# inputs: {},
|
|
28
|
+
# outputs: { results: 'array' }
|
|
28
29
|
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
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, :
|
|
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
|
-
@
|
|
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
|
|
123
|
+
# Define main execution block (DSL v1)
|
|
122
124
|
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
329
|
-
if @
|
|
330
|
-
logger.timed('Objective
|
|
331
|
-
@
|
|
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
|
|
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
|
-
#
|
|
8
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|