language-operator 0.0.1 → 0.1.31

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +125 -0
  3. data/CHANGELOG.md +88 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +284 -0
  6. data/LICENSE +229 -21
  7. data/Makefile +82 -0
  8. data/README.md +3 -11
  9. data/Rakefile +63 -0
  10. data/bin/aictl +7 -0
  11. data/completions/_aictl +232 -0
  12. data/completions/aictl.bash +121 -0
  13. data/completions/aictl.fish +114 -0
  14. data/docs/architecture/agent-runtime.md +585 -0
  15. data/docs/dsl/SCHEMA_VERSION.md +250 -0
  16. data/docs/dsl/agent-reference.md +604 -0
  17. data/docs/dsl/best-practices.md +1078 -0
  18. data/docs/dsl/chat-endpoints.md +895 -0
  19. data/docs/dsl/constraints.md +671 -0
  20. data/docs/dsl/mcp-integration.md +1177 -0
  21. data/docs/dsl/webhooks.md +932 -0
  22. data/docs/dsl/workflows.md +744 -0
  23. data/lib/language_operator/agent/base.rb +110 -0
  24. data/lib/language_operator/agent/executor.rb +440 -0
  25. data/lib/language_operator/agent/instrumentation.rb +54 -0
  26. data/lib/language_operator/agent/metrics_tracker.rb +183 -0
  27. data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
  28. data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
  29. data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
  30. data/lib/language_operator/agent/safety/content_filter.rb +93 -0
  31. data/lib/language_operator/agent/safety/manager.rb +207 -0
  32. data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
  33. data/lib/language_operator/agent/safety/safe_executor.rb +127 -0
  34. data/lib/language_operator/agent/scheduler.rb +183 -0
  35. data/lib/language_operator/agent/telemetry.rb +116 -0
  36. data/lib/language_operator/agent/web_server.rb +610 -0
  37. data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
  38. data/lib/language_operator/agent.rb +149 -0
  39. data/lib/language_operator/cli/commands/agent.rb +1205 -0
  40. data/lib/language_operator/cli/commands/cluster.rb +371 -0
  41. data/lib/language_operator/cli/commands/install.rb +404 -0
  42. data/lib/language_operator/cli/commands/model.rb +266 -0
  43. data/lib/language_operator/cli/commands/persona.rb +393 -0
  44. data/lib/language_operator/cli/commands/quickstart.rb +22 -0
  45. data/lib/language_operator/cli/commands/status.rb +143 -0
  46. data/lib/language_operator/cli/commands/system.rb +772 -0
  47. data/lib/language_operator/cli/commands/tool.rb +537 -0
  48. data/lib/language_operator/cli/commands/use.rb +47 -0
  49. data/lib/language_operator/cli/errors/handler.rb +180 -0
  50. data/lib/language_operator/cli/errors/suggestions.rb +176 -0
  51. data/lib/language_operator/cli/formatters/code_formatter.rb +77 -0
  52. data/lib/language_operator/cli/formatters/log_formatter.rb +288 -0
  53. data/lib/language_operator/cli/formatters/progress_formatter.rb +49 -0
  54. data/lib/language_operator/cli/formatters/status_formatter.rb +37 -0
  55. data/lib/language_operator/cli/formatters/table_formatter.rb +163 -0
  56. data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
  57. data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
  58. data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
  59. data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
  60. data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
  61. data/lib/language_operator/cli/helpers/pastel_helper.rb +24 -0
  62. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
  63. data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
  64. data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
  65. data/lib/language_operator/cli/main.rb +236 -0
  66. data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
  67. data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
  68. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
  69. data/lib/language_operator/client/base.rb +214 -0
  70. data/lib/language_operator/client/config.rb +136 -0
  71. data/lib/language_operator/client/cost_calculator.rb +37 -0
  72. data/lib/language_operator/client/mcp_connector.rb +123 -0
  73. data/lib/language_operator/client.rb +19 -0
  74. data/lib/language_operator/config/cluster_config.rb +101 -0
  75. data/lib/language_operator/config/tool_patterns.yaml +57 -0
  76. data/lib/language_operator/config/tool_registry.rb +96 -0
  77. data/lib/language_operator/config.rb +138 -0
  78. data/lib/language_operator/dsl/adapter.rb +124 -0
  79. data/lib/language_operator/dsl/agent_context.rb +90 -0
  80. data/lib/language_operator/dsl/agent_definition.rb +427 -0
  81. data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
  82. data/lib/language_operator/dsl/config.rb +119 -0
  83. data/lib/language_operator/dsl/context.rb +50 -0
  84. data/lib/language_operator/dsl/execution_context.rb +47 -0
  85. data/lib/language_operator/dsl/helpers.rb +109 -0
  86. data/lib/language_operator/dsl/http.rb +184 -0
  87. data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
  88. data/lib/language_operator/dsl/parameter_definition.rb +124 -0
  89. data/lib/language_operator/dsl/registry.rb +36 -0
  90. data/lib/language_operator/dsl/schema.rb +1102 -0
  91. data/lib/language_operator/dsl/shell.rb +125 -0
  92. data/lib/language_operator/dsl/tool_definition.rb +112 -0
  93. data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
  94. data/lib/language_operator/dsl/webhook_definition.rb +106 -0
  95. data/lib/language_operator/dsl/workflow_definition.rb +259 -0
  96. data/lib/language_operator/dsl.rb +161 -0
  97. data/lib/language_operator/errors.rb +60 -0
  98. data/lib/language_operator/kubernetes/client.rb +279 -0
  99. data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
  100. data/lib/language_operator/loggable.rb +47 -0
  101. data/lib/language_operator/logger.rb +141 -0
  102. data/lib/language_operator/retry.rb +123 -0
  103. data/lib/language_operator/retryable.rb +132 -0
  104. data/lib/language_operator/templates/README.md +23 -0
  105. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +115 -0
  106. data/lib/language_operator/templates/examples/persona_distillation.tmpl +19 -0
  107. data/lib/language_operator/templates/schema/.gitkeep +0 -0
  108. data/lib/language_operator/templates/schema/CHANGELOG.md +93 -0
  109. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +306 -0
  110. data/lib/language_operator/templates/schema/agent_dsl_schema.json +452 -0
  111. data/lib/language_operator/tool_loader.rb +242 -0
  112. data/lib/language_operator/validators.rb +170 -0
  113. data/lib/language_operator/version.rb +1 -1
  114. data/lib/language_operator.rb +65 -3
  115. data/requirements/tasks/challenge.md +9 -0
  116. data/requirements/tasks/iterate.md +36 -0
  117. data/requirements/tasks/optimize.md +21 -0
  118. data/requirements/tasks/tag.md +5 -0
  119. data/test_agent_dsl.rb +108 -0
  120. metadata +507 -20
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LanguageOperator
4
+ module Agent
5
+ module Safety
6
+ # Executes Ruby code in a sandboxed context with method whitelisting
7
+ # Wraps the execution context to prevent dangerous method calls at runtime
8
+ class SafeExecutor
9
+ class SecurityError < StandardError; end
10
+
11
+ # Methods that are always safe to call
12
+ ALWAYS_SAFE_METHODS = %i[
13
+ nil? == != eql? equal? hash object_id class is_a? kind_of? instance_of?
14
+ respond_to? send_to? methods public_methods private_methods
15
+ instance_variables instance_variable_get instance_variable_set
16
+ to_s to_str inspect to_a to_h to_i to_f to_sym
17
+ freeze frozen? dup clone
18
+ ].freeze
19
+
20
+ def initialize(context, validator: nil)
21
+ @context = context
22
+ @validator = validator || ASTValidator.new
23
+ @audit_log = []
24
+ end
25
+
26
+ # Execute code in the sandboxed context
27
+ # @param code [String] Ruby code to execute
28
+ # @param file_path [String] Path to file (for error reporting)
29
+ # @return [Object] Result of code execution
30
+ def eval(code, file_path = '(eval)')
31
+ # Step 1: Validate code with AST analysis
32
+ @validator.validate!(code, file_path)
33
+
34
+ # Step 2: Execute in sandboxed context
35
+ sandbox = SandboxProxy.new(@context, self)
36
+
37
+ # Step 3: Execute using instance_eval
38
+ # Note: We still use instance_eval but with validated code
39
+ # and wrapped context
40
+ sandbox.instance_eval(code, file_path)
41
+ rescue ASTValidator::SecurityError => e
42
+ # Re-raise validation errors as executor errors for clarity
43
+ raise SecurityError, "Code validation failed: #{e.message}"
44
+ end
45
+
46
+ # Log method calls for auditing
47
+ def log_call(receiver, method_name, args)
48
+ @audit_log << {
49
+ timestamp: Time.now,
50
+ receiver: receiver.class.name,
51
+ method: method_name,
52
+ args: args.map(&:class).map(&:name)
53
+ }
54
+ end
55
+
56
+ # Get audit log
57
+ attr_reader :audit_log
58
+
59
+ # Proxy class that wraps the context and intercepts method calls
60
+ class SandboxProxy < BasicObject
61
+ def initialize(context, executor)
62
+ @__context__ = context
63
+ @__executor__ = executor
64
+ end
65
+
66
+ # Delegate method calls to the underlying context
67
+ # but check for dangerous methods first
68
+ def method_missing(method_name, *args, &)
69
+ # Log the call
70
+ @__executor__.log_call(@__context__, method_name, args)
71
+
72
+ # Special handling for require - allow only 'language_operator'
73
+ if %i[require require_relative].include?(method_name)
74
+ required_gem = args.first.to_s
75
+ return ::Kernel.require(required_gem) if required_gem == 'language_operator'
76
+
77
+ # Allow require 'language_operator'
78
+
79
+ ::Kernel.raise ::LanguageOperator::Agent::Safety::SafeExecutor::SecurityError,
80
+ "Require '#{required_gem}' is not allowed. Only 'require \"language_operator\"' is permitted."
81
+
82
+ end
83
+
84
+ # Check if method is safe
85
+ unless safe_method?(method_name)
86
+ ::Kernel.raise ::LanguageOperator::Agent::Safety::SafeExecutor::SecurityError,
87
+ "Method '#{method_name}' is not allowed in sandboxed code"
88
+ end
89
+
90
+ # Delegate to underlying context
91
+ @__context__.send(method_name, *args, &)
92
+ end
93
+
94
+ def respond_to_missing?(method_name, include_private = false)
95
+ @__context__.respond_to?(method_name, include_private)
96
+ end
97
+
98
+ # Provide access to safe constants from the context
99
+ def const_missing(name)
100
+ # Allow access to HTTP and Shell helper classes
101
+ if name == :HTTP
102
+ return ::LanguageOperator::Dsl::HTTP
103
+ elsif name == :Shell
104
+ return ::LanguageOperator::Dsl::Shell
105
+ end
106
+
107
+ # Otherwise delegate to the context's module
108
+ @__context__.class.const_get(name)
109
+ rescue ::NameError
110
+ ::Kernel.raise ::NameError, "uninitialized constant #{name}"
111
+ end
112
+
113
+ private
114
+
115
+ def safe_method?(method_name)
116
+ # Always allow safe basic methods
117
+ return true if ::LanguageOperator::Agent::Safety::SafeExecutor::ALWAYS_SAFE_METHODS.include?(method_name)
118
+
119
+ # Check if the underlying context responds to the method
120
+ # (This allows DSL methods defined on the context)
121
+ @__context__.respond_to?(method_name)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rufus-scheduler'
4
+ require_relative 'executor'
5
+ require_relative 'instrumentation'
6
+ require_relative '../logger'
7
+ require_relative '../loggable'
8
+
9
+ module LanguageOperator
10
+ module Agent
11
+ # Task Scheduler
12
+ #
13
+ # Handles scheduled and event-driven task execution using rufus-scheduler.
14
+ #
15
+ # @example
16
+ # scheduler = Scheduler.new(agent)
17
+ # scheduler.start
18
+ class Scheduler
19
+ include LanguageOperator::Loggable
20
+ include LanguageOperator::Agent::Instrumentation
21
+
22
+ attr_reader :agent, :rufus_scheduler
23
+
24
+ # Initialize the scheduler
25
+ #
26
+ # @param agent [LanguageOperator::Agent::Base] The agent instance
27
+ def initialize(agent)
28
+ @agent = agent
29
+ @rufus_scheduler = Rufus::Scheduler.new
30
+ @executor = Executor.new(agent)
31
+
32
+ logger.debug('Scheduler initialized',
33
+ workspace: @agent.workspace_path,
34
+ servers: @agent.servers_info.length)
35
+ end
36
+
37
+ # Start the scheduler
38
+ #
39
+ # @return [void]
40
+ def start
41
+ logger.info('Agent starting in scheduled mode')
42
+ logger.info("Workspace: #{@agent.workspace_path}")
43
+ logger.info("Connected to #{@agent.servers_info.length} MCP server(s)")
44
+
45
+ setup_schedules
46
+ logger.info('Scheduler started, waiting for scheduled tasks')
47
+ @rufus_scheduler.join
48
+ end
49
+
50
+ # Start the scheduler with a workflow definition
51
+ #
52
+ # @param agent_def [LanguageOperator::Dsl::AgentDefinition] The agent definition with workflow
53
+ # @return [void]
54
+ def start_with_workflow(agent_def)
55
+ logger.info('Agent starting in scheduled mode with workflow',
56
+ agent_name: agent_def.name,
57
+ has_workflow: !agent_def.workflow.nil?)
58
+ logger.info("Workspace: #{@agent.workspace_path}")
59
+ logger.info("Connected to #{@agent.servers_info.length} MCP server(s)")
60
+
61
+ # Extract schedule from agent definition or use default
62
+ cron_schedule = agent_def.schedule&.cron || '0 6 * * *'
63
+
64
+ logger.info('Scheduling workflow', cron: cron_schedule, agent: agent_def.name)
65
+
66
+ @rufus_scheduler.cron(cron_schedule) do
67
+ with_span('agent.scheduler.execute', attributes: {
68
+ 'scheduler.cron_expression' => cron_schedule,
69
+ 'agent.name' => agent_def.name,
70
+ 'scheduler.task_type' => 'workflow'
71
+ }) do
72
+ logger.timed('Scheduled workflow execution') do
73
+ logger.info('Executing scheduled workflow', agent: agent_def.name)
74
+ result = @executor.execute_workflow(agent_def)
75
+ result_text = result.is_a?(String) ? result : result.content
76
+ preview = result_text[0..200]
77
+ preview += '...' if result_text.length > 200
78
+ logger.info('Workflow completed', result_preview: preview)
79
+ end
80
+ end
81
+ end
82
+
83
+ logger.info('Scheduler started, waiting for scheduled tasks')
84
+ @rufus_scheduler.join
85
+ end
86
+
87
+ # Stop the scheduler
88
+ #
89
+ # @return [void]
90
+ def stop
91
+ logger.info('Shutting down scheduler')
92
+ @rufus_scheduler.shutdown
93
+ logger.info('Scheduler stopped')
94
+ end
95
+
96
+ private
97
+
98
+ def logger_component
99
+ 'Agent::Scheduler'
100
+ end
101
+
102
+ # Setup schedules from config
103
+ #
104
+ # @return [void]
105
+ def setup_schedules
106
+ schedules = @agent.config.dig('agent', 'schedules') || []
107
+
108
+ logger.debug('Loading schedules from config', count: schedules.length)
109
+
110
+ if schedules.empty?
111
+ logger.warn('No schedules configured, using default daily schedule')
112
+ setup_default_schedule
113
+ return
114
+ end
115
+
116
+ schedules.each do |schedule|
117
+ add_schedule(schedule)
118
+ end
119
+
120
+ logger.info("#{schedules.length} schedule(s) configured")
121
+ end
122
+
123
+ # Add a single schedule
124
+ #
125
+ # @param schedule [Hash] Schedule configuration
126
+ # @return [void]
127
+ def add_schedule(schedule)
128
+ cron = schedule['cron']
129
+ task = schedule['task']
130
+ agent_name = @agent.config.dig('agent', 'name')
131
+
132
+ logger.info('Scheduling task', cron: cron, task: task[0..100])
133
+
134
+ @rufus_scheduler.cron(cron) do
135
+ with_span('agent.scheduler.execute', attributes: {
136
+ 'scheduler.cron_expression' => cron,
137
+ 'agent.name' => agent_name,
138
+ 'scheduler.task_type' => 'scheduled'
139
+ }) do
140
+ logger.timed('Scheduled task execution') do
141
+ logger.info('Executing scheduled task', task: task[0..100])
142
+ result = @executor.execute(task)
143
+ preview = result[0..200]
144
+ preview += '...' if result.length > 200
145
+ logger.info('Task completed', result_preview: preview)
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # Setup default daily schedule
152
+ #
153
+ # @return [void]
154
+ def setup_default_schedule
155
+ instructions = @agent.config.dig('agent', 'instructions') ||
156
+ 'Check for updates and report status'
157
+ agent_name = @agent.config.dig('agent', 'name')
158
+ cron = '0 6 * * *'
159
+
160
+ logger.info('Setting up default schedule', cron: cron,
161
+ instructions: instructions[0..100])
162
+
163
+ @rufus_scheduler.cron(cron) do
164
+ with_span('agent.scheduler.execute', attributes: {
165
+ 'scheduler.cron_expression' => cron,
166
+ 'agent.name' => agent_name,
167
+ 'scheduler.task_type' => 'default'
168
+ }) do
169
+ logger.timed('Daily task execution') do
170
+ logger.info('Executing daily task')
171
+ result = @executor.execute(instructions)
172
+ preview = result[0..200]
173
+ preview += '...' if result.length > 200
174
+ logger.info('Daily task completed', result_preview: preview)
175
+ end
176
+ end
177
+ end
178
+
179
+ logger.info('Scheduled: Daily at 6:00 AM')
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentelemetry/sdk'
4
+ require 'opentelemetry/exporter/otlp'
5
+
6
+ module LanguageOperator
7
+ module Agent
8
+ # OpenTelemetry configuration for agent runtime
9
+ #
10
+ # Initializes distributed tracing and telemetry for language operator agents.
11
+ # Reads configuration from environment variables and gracefully handles errors.
12
+ #
13
+ # @example Configure telemetry
14
+ # LanguageOperator::Agent::Telemetry.configure
15
+ module Telemetry
16
+ class << self
17
+ # Configure OpenTelemetry for the agent
18
+ #
19
+ # Reads configuration from environment variables:
20
+ # - OTEL_EXPORTER_OTLP_ENDPOINT: OTLP endpoint URL (required)
21
+ # - TRACEPARENT: W3C trace context for distributed tracing
22
+ # - AGENT_NAMESPACE: Kubernetes namespace
23
+ # - AGENT_NAME: Agent name
24
+ # - AGENT_MODE: Agent operating mode
25
+ # - HOSTNAME: Pod hostname
26
+ #
27
+ # @return [void]
28
+ def configure
29
+ endpoint = ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil)
30
+ return unless endpoint
31
+
32
+ OpenTelemetry::SDK.configure do |c|
33
+ c.service_name = 'language-operator-agent'
34
+ c.service_version = LanguageOperator::VERSION
35
+
36
+ # Configure resource attributes
37
+ c.resource = OpenTelemetry::SDK::Resources::Resource.create(
38
+ build_resource_attributes
39
+ )
40
+
41
+ # Configure OTLP exporter
42
+ c.add_span_processor(
43
+ OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
44
+ OpenTelemetry::Exporter::OTLP::Exporter.new(
45
+ endpoint: endpoint
46
+ )
47
+ )
48
+ )
49
+ end
50
+
51
+ # Restore trace context from TRACEPARENT if present
52
+ restore_trace_context if ENV['TRACEPARENT']
53
+ rescue StandardError => e
54
+ warn "Failed to configure OpenTelemetry: #{e.message}"
55
+ warn e.backtrace.join("\n")
56
+ end
57
+
58
+ private
59
+
60
+ # Build resource attributes from environment variables
61
+ #
62
+ # @return [Hash] Resource attributes
63
+ def build_resource_attributes
64
+ attributes = {}
65
+
66
+ # Service namespace
67
+ if (namespace = ENV.fetch('AGENT_NAMESPACE', nil))
68
+ attributes['service.namespace'] = namespace
69
+ attributes['k8s.namespace.name'] = namespace
70
+ end
71
+
72
+ # Kubernetes pod name
73
+ attributes['k8s.pod.name'] = ENV['HOSTNAME'] if ENV['HOSTNAME']
74
+
75
+ # Agent-specific attributes
76
+ attributes['agent.name'] = ENV['AGENT_NAME'] if ENV['AGENT_NAME']
77
+ attributes['agent.mode'] = ENV['AGENT_MODE'] if ENV['AGENT_MODE']
78
+
79
+ attributes
80
+ end
81
+
82
+ # Restore trace context from TRACEPARENT environment variable
83
+ #
84
+ # Extracts W3C trace context and sets it as the current context,
85
+ # enabling distributed tracing across service boundaries.
86
+ #
87
+ # @return [void]
88
+ def restore_trace_context
89
+ traceparent = ENV.fetch('TRACEPARENT', nil)
90
+ return unless traceparent
91
+
92
+ # Parse TRACEPARENT (format: version-trace_id-parent_id-flags)
93
+ parts = traceparent.split('-')
94
+ return unless parts.length == 4
95
+
96
+ _version, trace_id, parent_id, _flags = parts
97
+
98
+ # Create span context from extracted values
99
+ span_context = OpenTelemetry::Trace::SpanContext.new(
100
+ trace_id: [trace_id].pack('H*'),
101
+ span_id: [parent_id].pack('H*'),
102
+ trace_flags: OpenTelemetry::Trace::TraceFlags::SAMPLED,
103
+ remote: true
104
+ )
105
+
106
+ # Set as current context using proper OpenTelemetry API
107
+ span = OpenTelemetry::Trace.non_recording_span(span_context)
108
+ context = OpenTelemetry::Trace.context_with_span(span)
109
+ OpenTelemetry::Context.attach(context)
110
+ rescue StandardError => e
111
+ warn "Failed to restore trace context: #{e.message}"
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end