Module: LanguageOperator::Agent
- Extended by:
- Instrumentation
- Defined in:
- lib/language_operator/agent.rb,
lib/language_operator/agent/base.rb,
lib/language_operator/agent/executor.rb,
lib/language_operator/agent/telemetry.rb,
lib/language_operator/agent/web_server.rb,
lib/language_operator/agent/task_executor.rb,
lib/language_operator/agent/prompt_builder.rb,
lib/language_operator/agent/safety/manager.rb,
lib/language_operator/agent/execution_state.rb,
lib/language_operator/agent/instrumentation.rb,
lib/language_operator/agent/metrics_tracker.rb,
lib/language_operator/agent/metadata_collector.rb,
lib/language_operator/agent/safety/audit_logger.rb,
lib/language_operator/agent/safety/rate_limiter.rb,
lib/language_operator/agent/safety/ast_validator.rb,
lib/language_operator/agent/safety/safe_executor.rb,
lib/language_operator/agent/safety/budget_tracker.rb,
lib/language_operator/agent/safety/content_filter.rb,
lib/language_operator/agent/webhook_authenticator.rb
Overview
Agent Framework
Provides autonomous execution capabilities for language agents. Extends LanguageOperator::Client with agent-specific features like scheduling, goal evaluation, and workspace integration.
rubocop:disable Metrics/ModuleLength
Defined Under Namespace
Modules: Instrumentation, Safety, Telemetry Classes: Base, ExecutionInProgressError, ExecutionState, Executor, MetadataCollector, MetricsTracker, PromptBuilder, StreamingBody, TaskExecutionError, TaskExecutor, TaskNetworkError, TaskTimeoutError, TaskValidationError, WebServer, WebhookAuthenticator
Class Method Summary collapse
-
.build_executor_config(agent_def) ⇒ Hash
Build executor configuration from agent definition constraints.
-
.execute_main_block(agent, agent_def) ⇒ void
Execute main block (DSL v1) in autonomous mode.
-
.execute_main_block_persistent(agent, agent_def) ⇒ void
Execute main block (DSL v1) in persistent mode for autonomous agents.
-
.execute_output_handler(agent_def, outputs, task_executor) ⇒ void
Execute the output handler (neural or symbolic).
-
.handle_new_instruction ⇒ Boolean
Handle new instruction from environment variable.
-
.load_and_run(agent) ⇒ void
Load synthesized agent code and run with definition if available.
-
.load_and_run_from_file(code_path, agent_name = nil, config_path: nil) ⇒ void
Load and run agent from a specific file path.
-
.load_synthesized_agent(agent, code_path, agent_name) ⇒ void
Load synthesized agent code and execute.
-
.log_definition_not_found(agent_name) ⇒ void
Log when agent definition is not found.
-
.log_load_error(error) ⇒ void
Log agent code loading error.
- .logger ⇒ Object
-
.parse_duration(duration) ⇒ Numeric
Parse duration string to seconds.
-
.register_standard_endpoints(web_server, agent, agent_def) ⇒ void
Register standard endpoints for web server.
-
.run(config_path: nil) ⇒ void
Run the default agent based on environment configuration.
-
.run_idle_loop(shutdown_requested) ⇒ void
Run the idle loop waiting for new instructions.
-
.run_with_definition(agent, agent_def) ⇒ void
Run agent with a loaded definition.
-
.setup_signal_handlers ⇒ Proc
Setup signal handlers for graceful shutdown.
Class Method Details
.build_executor_config(agent_def) ⇒ Hash
Build executor configuration from agent definition constraints
410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/language_operator/agent.rb', line 410 def self.build_executor_config(agent_def) config = {} if agent_def.constraints if agent_def.constraints[:timeout] timeout = agent_def.constraints[:timeout] config[:timeout] = timeout.is_a?(String) ? parse_duration(timeout) : timeout end config[:max_retries] = agent_def.constraints[:max_retries] if agent_def.constraints[:max_retries] end config end |
.execute_main_block(agent, agent_def) ⇒ void
This method returns an undefined value.
Execute main block (DSL v1) in autonomous mode
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/language_operator/agent.rb', line 250 def self.execute_main_block(agent, agent_def) # Build executor config from agent constraints config = build_executor_config(agent_def) task_executor = LanguageOperator::Agent::TaskExecutor.new(agent, agent_def.tasks, config) logger.info('Executing main block', agent: agent_def.name, task_count: agent_def.tasks.size) # Execute main block within agent_executor span for learning system integration with_span('agent_executor', attributes: { 'agent.name' => agent_def.name, 'agent.task_count' => agent_def.tasks.size, 'agent.mode' => ENV.fetch('AGENT_MODE', 'unknown') }) do # Get inputs from environment or default to empty hash inputs = {} # Execute main block with task executor as context result = agent_def.main.call(inputs, task_executor) logger.info('Main block execution completed', result: result) # Call output handler if defined if agent_def.output logger.debug('Executing output handler', outputs: result) execute_output_handler(agent_def, result, task_executor) end result end end |
.execute_main_block_persistent(agent, agent_def) ⇒ void
This method returns an undefined value.
Execute main block (DSL v1) in persistent mode for autonomous agents
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/language_operator/agent.rb', line 289 def self.execute_main_block_persistent(agent, agent_def) shutdown_requested = setup_signal_handlers logger.info('Starting agent in persistent autonomous mode', agent_name: agent_def.name, task_count: agent_def.tasks.size) # Execute initial main block execute_main_block(agent, agent_def) logger.info('Initial task completed - entering idle state') # Enter waiting loop for additional instructions run_idle_loop(shutdown_requested) logger.info('Graceful shutdown requested - agent exiting') end |
.execute_output_handler(agent_def, outputs, task_executor) ⇒ void
This method returns an undefined value.
Execute the output handler (neural or symbolic)
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/language_operator/agent.rb', line 376 def self.execute_output_handler(agent_def, outputs, task_executor) output_config = agent_def.output # If symbolic implementation exists, use it if output_config.symbolic? logger.debug('Executing symbolic output handler') # Call the execute block directly without validation # Output blocks don't need input validation since they receive whatever main returns block = output_config.execute_block if block.arity == 1 block.call(outputs) elsif block.arity == 2 block.call(outputs, task_executor) else block.call end elsif output_config.neural? # Neural output - would need LLM access to execute # For now, just log the instruction logger.info('Neural output handler', instruction: output_config.instructions_text, outputs: outputs) logger.warn('Neural output execution not yet implemented - instruction logged only') end rescue StandardError => e logger.error('Output handler failed', error: e., backtrace: e.backtrace[0..5]) end |
.handle_new_instruction ⇒ Boolean
Handle new instruction from environment variable
355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/language_operator/agent.rb', line 355 def self.handle_new_instruction new_instruction = ENV.fetch('AGENT_NEW_INSTRUCTION', nil) return false unless new_instruction && !new_instruction.strip.empty? logger.info('New instruction received', instruction: new_instruction[0..100]) # Clear the environment variable to prevent re-execution ENV['AGENT_NEW_INSTRUCTION'] = '' # Execute the new instruction (implementation would depend on requirements) logger.info('Instruction acknowledged - agent remains in autonomous mode') true end |
.load_and_run(agent) ⇒ void
This method returns an undefined value.
Load synthesized agent code and run with definition if available
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/language_operator/agent.rb', line 82 def self.load_and_run(agent) agent_code_path = ENV.fetch('AGENT_CODE_PATH', nil) agent_name = ENV.fetch('AGENT_NAME', nil) if agent_code_path && File.exist?(agent_code_path) load_synthesized_agent(agent, agent_code_path, agent_name) else logger.info('No synthesized code found, running in standard mode', agent_code_path: agent_code_path) agent.run end end |
.load_and_run_from_file(code_path, agent_name = nil, config_path: nil) ⇒ void
This method returns an undefined value.
Load and run agent from a specific file path
63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/language_operator/agent.rb', line 63 def self.load_and_run_from_file(code_path, agent_name = nil, config_path: nil) # Disable stdout buffering for real-time logging in containers $stdout.sync = true $stderr.sync = true config_path ||= ENV.fetch('CONFIG_PATH', 'config.yaml') config = LanguageOperator::Client::Config.load_with_fallback(config_path) # Create agent instance agent = LanguageOperator::Agent::Base.new(config) # Load and run the specified agent code load_synthesized_agent(agent, code_path, agent_name) end |
.load_synthesized_agent(agent, code_path, agent_name) ⇒ void
This method returns an undefined value.
Load synthesized agent code and execute
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/language_operator/agent.rb', line 101 def self.load_synthesized_agent(agent, code_path, agent_name) logger.info('DSL code loading', path: code_path, agent_name: agent_name) # Load synthesized DSL code LanguageOperator::Dsl.load_agent_file(code_path) # Get agent definition from registry agent_def = LanguageOperator::Dsl.agent_registry.get(agent_name) if agent_name if agent_def logger.info('Agent definition loaded', agent_name: agent_name, has_workflow: !agent_def.workflow.nil?) run_with_definition(agent, agent_def) else log_definition_not_found(agent_name) agent.run end rescue StandardError => e log_load_error(e) agent.run end |
.log_definition_not_found(agent_name) ⇒ void
This method returns an undefined value.
Log when agent definition is not found
130 131 132 133 134 135 |
# File 'lib/language_operator/agent.rb', line 130 def self.log_definition_not_found(agent_name) logger.warn('Agent definition not found in registry', agent_name: agent_name, available: LanguageOperator::Dsl.agent_registry.all.map(&:name)) logger.info('Falling back to autonomous mode') end |
.log_load_error(error) ⇒ void
This method returns an undefined value.
Log agent code loading error
141 142 143 144 145 146 |
# File 'lib/language_operator/agent.rb', line 141 def self.log_load_error(error) logger.error('Failed to load agent code', error: error., backtrace: error.backtrace[0..3]) logger.info('Falling back to autonomous mode') end |
.logger ⇒ Object
34 35 36 |
# File 'lib/language_operator/agent.rb', line 34 def self.logger @logger end |
.parse_duration(duration) ⇒ Numeric
Parse duration string to seconds
428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/language_operator/agent.rb', line 428 def self.parse_duration(duration) case duration when /^(\d+)s$/ ::Regexp.last_match(1).to_i when /^(\d+)m$/ ::Regexp.last_match(1).to_i * 60 when /^(\d+)h$/ ::Regexp.last_match(1).to_i * 3600 when Numeric duration else raise ArgumentError, "Invalid duration format: #{duration}. Use format like '10m', '2h', '30s'" end end |
.register_standard_endpoints(web_server, agent, agent_def) ⇒ void
This method returns an undefined value.
Register standard endpoints for web server
239 240 241 242 243 |
# File 'lib/language_operator/agent.rb', line 239 def self.register_standard_endpoints(web_server, agent, agent_def) web_server.register_mcp_tools(agent_def.mcp_server) if agent_def.mcp_server&.tools? web_server.register_chat_endpoint(agent) web_server.register_workspace_endpoints(agent) end |
.run(config_path: nil) ⇒ void
This method returns an undefined value.
Run the default agent based on environment configuration
42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/language_operator/agent.rb', line 42 def self.run(config_path: nil) # Disable stdout buffering for real-time logging in containers $stdout.sync = true $stderr.sync = true config_path ||= ENV.fetch('CONFIG_PATH', 'config.yaml') config = LanguageOperator::Client::Config.load_with_fallback(config_path) # Create agent instance agent = LanguageOperator::Agent::Base.new(config) # Load and run with synthesized code if available load_and_run(agent) end |
.run_idle_loop(shutdown_requested) ⇒ void
This method returns an undefined value.
Run the idle loop waiting for new instructions
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/language_operator/agent.rb', line 327 def self.run_idle_loop(shutdown_requested) idle_timeout = ENV.fetch('AGENT_IDLE_TIMEOUT', '300').to_i # 5 minutes default last_activity = Time.now until shutdown_requested.call begin last_activity = Time.now if handle_new_instruction sleep 5 # Avoid busy waiting # Reset idle timer when timeout reached (stay persistent) if idle_timeout.positive? && Time.now - last_activity > idle_timeout logger.info('Idle timeout reached - agent remaining available', timeout_seconds: idle_timeout) last_activity = Time.now end rescue StandardError => e logger.error('Error in idle loop', error: e., backtrace: e.backtrace[0..3]) sleep 10 # Back off on error end end end |
.run_with_definition(agent, agent_def) ⇒ void
This method returns an undefined value.
Run agent with a loaded definition
rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/language_operator/agent.rb', line 154 def self.run_with_definition(agent, agent_def) agent.connect! # Check if agent uses DSL v1 (task/main) or v0 (workflow/step) uses_dsl_v1 = agent_def.main&.defined? uses_dsl_v0 = agent_def.respond_to?(:workflow) && agent_def.workflow case agent.mode when 'autonomous', 'interactive' # Hybrid mode: All agents now run main work AND web server (chat endpoints always enabled) logger.info('Starting hybrid agent (autonomous + web server)', agent_name: agent_def.name, chat_endpoint_enabled: true, # Always true now has_webhooks: agent_def.webhooks.any?, has_mcp_tools: !!agent_def.mcp_server&.tools?) # Start web server in background thread web_server = LanguageOperator::Agent::WebServer.new(agent) agent_def.webhooks.each { |webhook_def| webhook_def.register(web_server) } register_standard_endpoints(web_server, agent, agent_def) web_thread = Thread.new do web_server.start rescue StandardError => e logger.error('Web server error', error: e., backtrace: e.backtrace[0..5]) raise end # Set up signal handlers for graceful shutdown %w[INT TERM].each do |signal| Signal.trap(signal) do logger.info('Received shutdown signal, stopping hybrid agent') web_server.cleanup if web_server.respond_to?(:cleanup) web_thread.kill if web_thread&.alive? exit 0 end end # Run main work in foreground if uses_dsl_v1 execute_main_block_persistent(agent, agent_def) elsif uses_dsl_v0 executor = LanguageOperator::Agent::Executor.new(agent) executor.execute_workflow(agent_def) else raise 'Agent definition must have either main block (DSL v1) or workflow (DSL v0)' end when 'scheduled', 'event-driven' # Standby mode - web server only, wait for /api/v1/execute triggers logger.info('Starting agent in standby mode (scheduled) - waiting for HTTP triggers', agent_name: agent_def.name, chat_endpoint_enabled: true, has_webhooks: agent_def.webhooks.any?, has_mcp_tools: !!agent_def.mcp_server&.tools?) web_server = LanguageOperator::Agent::WebServer.new(agent) agent_def.webhooks.each { |webhook_def| webhook_def.register(web_server) } register_standard_endpoints(web_server, agent, agent_def) web_server.register_execute_endpoint(agent, agent_def) web_server.start # Blocks here, waiting for requests when 'reactive', 'http', 'webhook' # Standby mode - web server with webhooks, MCP tools, chat, and execute endpoint logger.info('Starting agent in standby mode (reactive)', agent_name: agent_def.name, has_webhooks: agent_def.webhooks.any?) web_server = LanguageOperator::Agent::WebServer.new(agent) agent_def.webhooks.each { |webhook_def| webhook_def.register(web_server) } register_standard_endpoints(web_server, agent, agent_def) web_server.register_execute_endpoint(agent, agent_def) web_server.start else raise "Unknown agent mode: #{agent.mode}" end end |
.setup_signal_handlers ⇒ Proc
Setup signal handlers for graceful shutdown
309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/language_operator/agent.rb', line 309 def self.setup_signal_handlers shutdown_requested = false trap('TERM') do logger.info('SIGTERM received - requesting graceful shutdown') shutdown_requested = true end trap('INT') do logger.info('SIGINT received - requesting graceful shutdown') shutdown_requested = true end -> { shutdown_requested } end |