language-operator 0.1.46 → 0.1.48

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/task.md +10 -0
  3. data/Gemfile.lock +1 -1
  4. data/components/agent/.rubocop.yml +1 -0
  5. data/components/agent/Dockerfile +43 -0
  6. data/components/agent/Dockerfile.dev +38 -0
  7. data/components/agent/Gemfile +15 -0
  8. data/components/agent/Makefile +67 -0
  9. data/components/agent/bin/langop-agent +140 -0
  10. data/components/agent/config/config.yaml +47 -0
  11. data/components/base/Dockerfile +34 -0
  12. data/components/base/Makefile +42 -0
  13. data/components/base/entrypoint.sh +12 -0
  14. data/components/base/gem-credentials +2 -0
  15. data/components/tool/.gitignore +10 -0
  16. data/components/tool/.rubocop.yml +19 -0
  17. data/components/tool/.yardopts +7 -0
  18. data/components/tool/Dockerfile +44 -0
  19. data/components/tool/Dockerfile.dev +39 -0
  20. data/components/tool/Gemfile +18 -0
  21. data/components/tool/Makefile +77 -0
  22. data/components/tool/README.md +145 -0
  23. data/components/tool/config.ru +4 -0
  24. data/components/tool/examples/calculator.rb +63 -0
  25. data/components/tool/examples/example_tool.rb +190 -0
  26. data/components/tool/lib/langop/dsl.rb +20 -0
  27. data/components/tool/server.rb +7 -0
  28. data/lib/language_operator/agent/task_executor.rb +49 -11
  29. data/lib/language_operator/cli/commands/agent.rb +3 -6
  30. data/lib/language_operator/cli/commands/system.rb +1 -0
  31. data/lib/language_operator/cli/formatters/log_formatter.rb +19 -67
  32. data/lib/language_operator/cli/formatters/log_style.rb +151 -0
  33. data/lib/language_operator/cli/formatters/progress_formatter.rb +10 -6
  34. data/lib/language_operator/logger.rb +3 -8
  35. data/lib/language_operator/templates/agent_synthesis.tmpl +35 -28
  36. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  37. data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
  38. data/lib/language_operator/version.rb +1 -1
  39. data/synth/001/README.md +72 -0
  40. data/synth/001/output.log +13 -13
  41. data/synth/002/Makefile +12 -0
  42. data/synth/002/README.md +287 -0
  43. data/synth/002/agent.rb +23 -0
  44. data/synth/002/agent.txt +1 -0
  45. data/synth/002/output.log +22 -0
  46. metadata +33 -3
  47. data/synth/Makefile +0 -39
  48. data/synth/README.md +0 -342
@@ -0,0 +1,77 @@
1
+ # Image configuration
2
+ IMAGE_NAME := ghcr.io/language-operator/tool
3
+ IMAGE_TAG := latest
4
+ IMAGE_FULL := $(IMAGE_NAME):$(IMAGE_TAG)
5
+
6
+ # Include shared targets
7
+ include ../../Makefile.common
8
+
9
+ # Default target
10
+ .PHONY: help
11
+ help:
12
+ @echo "Available targets:"
13
+ @echo " build - Build the Docker image"
14
+ @echo " build-dev - Build the Docker image with local gem"
15
+ @echo " push-dev - Build and push dev image to all k8s nodes"
16
+ @echo " scan - Scan the Docker image with Trivy"
17
+ @echo " shell - Run the image and exec into it with an interactive shell"
18
+ @echo " env - Display sorted list of environment variables in the image"
19
+ @echo " run - Run the MCP server on port 8080"
20
+ @echo " run-dev - Run with custom tools directory"
21
+ @echo " test - Run the server and test the endpoints"
22
+ @echo " lint - Run RuboCop linter"
23
+ @echo " lint-fix - Run RuboCop with auto-fix"
24
+ @echo " doc - Generate API documentation with YARD"
25
+ @echo " doc-serve - Serve documentation on http://localhost:8808"
26
+ @echo " doc-clean - Remove generated documentation"
27
+
28
+ # Build development image with local gem
29
+ .PHONY: build-dev
30
+ build-dev:
31
+ @echo "Building local gem..."
32
+ cd ../../../language-operator-gem && gem build language-operator.gemspec
33
+ @echo "Copying gem to build context..."
34
+ cp ../../../language-operator-gem/*.gem .
35
+ @echo "Building Docker image..."
36
+ docker build -f Dockerfile.dev -t $(IMAGE_NAME):dev -t $(IMAGE_NAME):$(IMAGE_TAG)-dev .
37
+ @echo "Cleaning up gem file..."
38
+ rm language-operator-*.gem
39
+
40
+ # Push development image to all k8s cluster nodes (containerd/k3s)
41
+ .PHONY: push-dev
42
+ push-dev: build-dev
43
+ @echo "Pushing development image to all k8s nodes..."
44
+ @for node in $$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do \
45
+ echo "==> Pushing to node: $$node"; \
46
+ docker save $(IMAGE_NAME):dev | ssh $$node 'sudo k3s ctr images import -' || echo "Failed to push to $$node"; \
47
+ done
48
+ @echo "✓ Development image pushed to all nodes!"
49
+
50
+ # Display sorted list of environment variables in the image
51
+ .PHONY: env
52
+ env:
53
+ docker run --rm $(IMAGE_FULL) /bin/sh -c 'env | sort'
54
+
55
+ # Run the MCP server
56
+ .PHONY: run
57
+ run:
58
+ docker run --rm -p 8080:80 --name mcp-server $(IMAGE_FULL)
59
+
60
+ # Run with custom tools directory
61
+ .PHONY: run-dev
62
+ run-dev:
63
+ docker run --rm -p 8080:80 -v $(PWD)/examples:/mcp --name mcp-server $(IMAGE_FULL)
64
+
65
+ # Override test to test server endpoints
66
+ .PHONY: test
67
+ test:
68
+ @echo "Testing health endpoint..."
69
+ @curl -s http://localhost:8080/health | jq .
70
+ @echo ""
71
+ @echo "Listing tools..."
72
+ @curl -s http://localhost:8080/tools | jq .
73
+ @echo ""
74
+ @echo "Calling calculator tool..."
75
+ @curl -s -X POST http://localhost:8080/tools/call \
76
+ -H "Content-Type: application/json" \
77
+ -d '{"name":"calculator","arguments":{"operation":"add","a":5,"b":3}}' | jq .
@@ -0,0 +1,145 @@
1
+ # based/server
2
+
3
+ An extendable tool server based on [the official MCP Ruby SDK](https://github.com/modelcontextprotocol/ruby-sdk).
4
+
5
+ ## Quick Start
6
+
7
+ Run the server with example tools:
8
+
9
+ ```bash
10
+ docker run -p 8080:80 based/server:latest
11
+ ```
12
+
13
+ Test the server:
14
+
15
+ ```bash
16
+ curl http://localhost:8080/health
17
+ curl http://localhost:8080/tools
18
+ ```
19
+
20
+ ## Creating Your Own MCP Server
21
+
22
+ ### 1. Create Tool Definitions
23
+
24
+ Create Ruby files using the MCP DSL to define your tools:
25
+
26
+ ```ruby
27
+ # tools/hello.rb
28
+ tool "greet" do
29
+ description "Greets a person by name"
30
+
31
+ parameter "name" do
32
+ type :string
33
+ required true
34
+ description "The name of the person to greet"
35
+ end
36
+
37
+ parameter "greeting" do
38
+ type :string
39
+ required false
40
+ description "Custom greeting (default: Hello)"
41
+ default "Hello"
42
+ end
43
+
44
+ execute do |params|
45
+ greeting = params["greeting"] || "Hello"
46
+ "#{greeting}, #{params['name']}!"
47
+ end
48
+ end
49
+ ```
50
+
51
+ ### 2. Mount Your Tools Directory
52
+
53
+ Run the container with your tools directory mounted at `/mcp/tools`:
54
+
55
+ ```bash
56
+ docker run -p 8080:80 -v $(pwd)/tools:/mcp/tools based/server:latest
57
+ ```
58
+
59
+ ### 3. Use Your Tools
60
+
61
+ Call your tools via the MCP protocol:
62
+
63
+ ```bash
64
+ # List available tools
65
+ curl -X POST http://localhost:8080/tools/list
66
+
67
+ # Call a tool
68
+ curl -X POST http://localhost:8080/tools/call \
69
+ -H "Content-Type: application/json" \
70
+ -d '{"name":"greet","arguments":{"name":"World"}}'
71
+ ```
72
+
73
+ ## DSL Reference
74
+
75
+ ### Defining a Tool
76
+
77
+ ```ruby
78
+ tool "tool_name" do
79
+ description "What this tool does"
80
+
81
+ parameter "param_name" do
82
+ type :string # :string, :number, :boolean, :array, :object
83
+ required true # or false
84
+ description "Parameter description"
85
+ enum ["option1", "option2"] # optional: restrict to specific values
86
+ default "default_value" # optional: default value
87
+ end
88
+
89
+ execute do |params|
90
+ # Your tool logic here
91
+ # Access parameters via params["param_name"]
92
+ # Return a string result
93
+ "Result: #{params['param_name']}"
94
+ end
95
+ end
96
+ ```
97
+
98
+ ### Parameter Types
99
+
100
+ - `:string` - Text values
101
+ - `:number` - Numeric values (integers or floats)
102
+ - `:boolean` - true/false
103
+ - `:array` - Lists of values
104
+ - `:object` - Complex objects
105
+
106
+ ### Multiple Tools Per File
107
+
108
+ You can define multiple tools in a single file:
109
+
110
+ ```ruby
111
+ tool "add" do
112
+ # ... tool definition
113
+ end
114
+
115
+ tool "subtract" do
116
+ # ... tool definition
117
+ end
118
+ ```
119
+
120
+ ## Example Tools
121
+
122
+ See [examples/calculator.rb](examples/calculator.rb) for complete examples including:
123
+ - Calculator with arithmetic operations
124
+ - Echo tool for simple string operations
125
+
126
+ ## Configuration
127
+
128
+ | Environment Variable | Default | Description |
129
+ | -- | -- | -- |
130
+ | PORT | 80 | Port to run HTTP server on |
131
+ | RACK_ENV | production | Rack environment |
132
+
133
+ ## API Endpoints
134
+
135
+ ### MCP Protocol Endpoints
136
+
137
+ - `POST /initialize` - Initialize MCP session
138
+ - `POST /tools/list` - List available tools
139
+ - `POST /tools/call` - Execute a tool
140
+
141
+ ### Debug Endpoints
142
+
143
+ - `GET /health` - Health check
144
+ - `GET /tools` - List loaded tools (simple format)
145
+ - `POST /reload` - Reload tools from `/mcp` directory
@@ -0,0 +1,4 @@
1
+ require_relative 'server'
2
+
3
+ # Completely bypass Rack::Protection by not using Sinatra's run! method
4
+ run Langop::Server
@@ -0,0 +1,63 @@
1
+ # Example tool: Calculator
2
+ # This file demonstrates the MCP DSL for defining tools
3
+
4
+ tool 'calculator' do
5
+ description 'Performs basic arithmetic operations on two numbers'
6
+
7
+ parameter 'operation' do
8
+ type :string
9
+ required true
10
+ description 'The arithmetic operation to perform'
11
+ enum %w[add subtract multiply divide]
12
+ end
13
+
14
+ parameter 'a' do
15
+ type :number
16
+ required true
17
+ description 'The first number'
18
+ end
19
+
20
+ parameter 'b' do
21
+ type :number
22
+ required true
23
+ description 'The second number'
24
+ end
25
+
26
+ execute do |params|
27
+ a = params['a']
28
+ b = params['b']
29
+
30
+ result = case params['operation']
31
+ when 'add'
32
+ a + b
33
+ when 'subtract'
34
+ a - b
35
+ when 'multiply'
36
+ a * b
37
+ when 'divide'
38
+ if b.zero?
39
+ 'Error: Division by zero'
40
+ else
41
+ a / b.to_f
42
+ end
43
+ else
44
+ 'Unknown operation'
45
+ end
46
+
47
+ "Result: #{result}"
48
+ end
49
+ end
50
+
51
+ tool 'echo' do
52
+ description 'Returns the input message'
53
+
54
+ parameter 'message' do
55
+ type :string
56
+ required true
57
+ description 'The message to echo back'
58
+ end
59
+
60
+ execute do |params|
61
+ params['message']
62
+ end
63
+ end
@@ -0,0 +1,190 @@
1
+ # Example tool demonstrating all the new helper features
2
+
3
+ # Example 1: Using built-in parameter validators
4
+ tool 'send_message' do
5
+ description 'Send a message to an email or phone number'
6
+
7
+ parameter 'recipient' do
8
+ type :string
9
+ required true
10
+ description 'Email address or phone number'
11
+ # You can use built-in validators
12
+ # email_format or phone_format
13
+ end
14
+
15
+ parameter 'message' do
16
+ type :string
17
+ required true
18
+ description 'The message to send'
19
+ end
20
+
21
+ execute do |params|
22
+ recipient = params['recipient']
23
+ message = params['message']
24
+
25
+ # Use validation helpers
26
+ if recipient.include?('@')
27
+ error = validate_email(recipient)
28
+ return error if error
29
+
30
+ "Email sent to #{recipient}: #{message}"
31
+ else
32
+ error = validate_phone(recipient)
33
+ return error if error
34
+
35
+ "SMS sent to #{recipient}: #{message}"
36
+ end
37
+ end
38
+ end
39
+
40
+ # Example 2: Using Config helper for environment variables
41
+ tool 'check_smtp_config' do
42
+ description 'Check SMTP configuration with fallback keys'
43
+
44
+ execute do |_params|
45
+ # Get config with multiple fallback keys
46
+ host = Config.get('SMTP_HOST', 'MAIL_HOST', default: 'localhost')
47
+ port = Config.get_int('SMTP_PORT', 'MAIL_PORT', default: 587)
48
+ use_tls = Config.get_bool('SMTP_TLS', 'MAIL_TLS', default: true)
49
+
50
+ # Check required configs
51
+ missing = Config.check_required('SMTP_USER', 'SMTP_PASSWORD')
52
+ return error("Missing configuration: #{missing.join(', ')}") unless missing.empty?
53
+
54
+ success <<~CONFIG
55
+ SMTP Configuration:
56
+ Host: #{host}
57
+ Port: #{port}
58
+ TLS: #{use_tls}
59
+ User: #{Config.get('SMTP_USER')}
60
+ CONFIG
61
+ end
62
+ end
63
+
64
+ # Example 3: Using HTTP helper
65
+ tool 'fetch_json' do
66
+ description 'Fetch JSON data from a URL'
67
+
68
+ parameter 'url' do
69
+ type :string
70
+ required true
71
+ description 'URL to fetch'
72
+ url_format # Built-in validator
73
+ end
74
+
75
+ execute do |params|
76
+ url = params['url']
77
+
78
+ # Use HTTP helper instead of curl
79
+ response = HTTP.get(url, headers: { 'Accept' => 'application/json' })
80
+
81
+ if response[:success]
82
+ if response[:json]
83
+ "Fetched JSON with #{response[:json].keys.length} keys"
84
+ else
85
+ "Response received but not JSON: #{truncate(response[:body])}"
86
+ end
87
+ else
88
+ error("Failed to fetch URL: #{response[:error]}")
89
+ end
90
+ end
91
+ end
92
+
93
+ # Example 4: Using Shell helper for safe command execution
94
+ tool 'safe_grep' do
95
+ description 'Safely search for a pattern in a file'
96
+
97
+ parameter 'pattern' do
98
+ type :string
99
+ required true
100
+ description 'Search pattern'
101
+ end
102
+
103
+ parameter 'file' do
104
+ type :string
105
+ required true
106
+ description 'File to search in'
107
+ end
108
+
109
+ execute do |params|
110
+ # This is safe from injection attacks!
111
+ result = Shell.run('grep', params['pattern'], params['file'])
112
+
113
+ if result[:success]
114
+ "Found matches:\n#{result[:output]}"
115
+ else
116
+ 'No matches found'
117
+ end
118
+ end
119
+ end
120
+
121
+ # Example 5: Using custom validation
122
+ tool 'restricted_command' do
123
+ description 'Run a command from an allowed list'
124
+
125
+ parameter 'command' do
126
+ type :string
127
+ required true
128
+ description 'Command to run (must be in allowed list)'
129
+ validate lambda { |cmd|
130
+ allowed = %w[ls pwd whoami date]
131
+ allowed.include?(cmd) || "Command '#{cmd}' not allowed. Allowed: #{allowed.join(', ')}"
132
+ }
133
+ end
134
+
135
+ execute do |params|
136
+ result = Shell.run(params['command'])
137
+ result[:output]
138
+ end
139
+ end
140
+
141
+ # Example 6: Using multiple helpers together
142
+ tool 'web_health_check' do
143
+ description 'Check if a web service is healthy'
144
+
145
+ parameter 'url' do
146
+ type :string
147
+ required true
148
+ description 'Service URL to check'
149
+ url_format
150
+ end
151
+
152
+ parameter 'expected_status' do
153
+ type :number
154
+ required false
155
+ description 'Expected HTTP status code'
156
+ default 200
157
+ end
158
+
159
+ execute do |params|
160
+ url = params['url']
161
+ expected = params['expected_status'] || 200
162
+
163
+ # Use HTTP helper
164
+ response = HTTP.head(url)
165
+
166
+ if response[:error]
167
+ error("Health check failed: #{response[:error]}")
168
+ elsif response[:status] == expected
169
+ success("✓ Service healthy (HTTP #{response[:status]})")
170
+ else
171
+ error("Service returned HTTP #{response[:status]}, expected #{expected}")
172
+ end
173
+ end
174
+ end
175
+
176
+ # Example 7: Using env_required helper
177
+ tool 'database_info' do
178
+ description 'Show database connection info'
179
+
180
+ execute do |_params|
181
+ # Check required env vars
182
+ error = env_required('DATABASE_URL', 'DB_HOST')
183
+ return error if error
184
+
185
+ # Safe to use now
186
+ db_url = env_get('DATABASE_URL', 'DB_URL')
187
+
188
+ success("Database configured: #{db_url}")
189
+ end
190
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'language_operator'
4
+
5
+ # Convenience - export LanguageOperator classes at top level for tool definitions
6
+ #
7
+ # This allows tool files to use simplified syntax:
8
+ # tool "example" do
9
+ # ...
10
+ # end
11
+ #
12
+ # Instead of:
13
+ # LanguageOperator::Dsl.define do
14
+ # tool "example" do
15
+ # ...
16
+ # end
17
+ # end
18
+
19
+ # Alias ToolLoader at top level for convenience
20
+ ToolLoader = LanguageOperator::ToolLoader
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'language_operator/tool_loader'
4
+
5
+ # Start the MCP server using LanguageOperator SDK
6
+ # This will load all tools from /mcp and start the server on PORT (default 80)
7
+ LanguageOperator::ToolLoader.start
@@ -76,7 +76,9 @@ module LanguageOperator
76
76
  @config = default_config.merge(config)
77
77
  logger.debug('TaskExecutor initialized',
78
78
  task_count: @tasks.size,
79
- timeout: @config[:timeout],
79
+ timeout_symbolic: @config[:timeout_symbolic],
80
+ timeout_neural: @config[:timeout_neural],
81
+ timeout_hybrid: @config[:timeout_hybrid],
80
82
  max_retries: @config[:max_retries])
81
83
  end
82
84
 
@@ -95,13 +97,11 @@ module LanguageOperator
95
97
  # @raise [TaskExecutionError] If task execution fails after retries
96
98
  def execute_task(task_name, inputs: {}, timeout: nil, max_retries: nil)
97
99
  execution_start = Time.now
98
- timeout ||= @config[:timeout]
99
100
  max_retries ||= @config[:max_retries]
100
101
 
101
102
  with_span('task_executor.execute_task', attributes: {
102
103
  'task.name' => task_name.to_s,
103
104
  'task.inputs' => inputs.keys.map(&:to_s).join(','),
104
- 'task.timeout' => timeout,
105
105
  'task.max_retries' => max_retries
106
106
  }) do
107
107
  # Find task definition
@@ -109,12 +109,19 @@ module LanguageOperator
109
109
  raise ArgumentError, "Task not found: #{task_name}. Available tasks: #{@tasks.keys.join(', ')}" unless task
110
110
 
111
111
  task_type = determine_task_type(task)
112
+
113
+ # Determine timeout based on task type if not explicitly provided
114
+ timeout ||= task_timeout_for_type(task)
115
+
112
116
  logger.info('Executing task',
113
117
  task: task_name,
114
118
  type: task_type,
115
119
  timeout: timeout,
116
120
  max_retries: max_retries)
117
121
 
122
+ # Add timeout to span attributes after it's determined
123
+ OpenTelemetry::Trace.current_span&.set_attribute('task.timeout', timeout)
124
+
118
125
  # Execute with retry logic
119
126
  execute_with_retry(task, task_name, inputs, timeout, max_retries, execution_start)
120
127
  end
@@ -281,7 +288,10 @@ module LanguageOperator
281
288
  end
282
289
  prompt += "\n"
283
290
 
284
- prompt += 'Return ONLY valid JSON matching the output schema. '
291
+ prompt += "## Response Format\n"
292
+ prompt += "Return ONLY valid JSON matching the output schema above.\n"
293
+ prompt += "Do NOT include any explanations, thinking, or text before or after the JSON.\n"
294
+ prompt += "Do NOT use [THINK] tags or any other markup.\n"
285
295
  prompt += "Use available tools as needed to complete the task.\n"
286
296
 
287
297
  prompt
@@ -294,15 +304,18 @@ module LanguageOperator
294
304
  # @return [Hash] Parsed outputs
295
305
  # @raise [RuntimeError] If parsing fails
296
306
  def parse_neural_response(response_text, task)
307
+ # Strip thinking tags that some models add (e.g., [THINK]...[/THINK])
308
+ cleaned_text = response_text.gsub(%r{\[THINK\].*?\[/THINK\]}m, '').strip
309
+
297
310
  # Try to extract JSON from response
298
311
  # Look for JSON code blocks first
299
- json_match = response_text.match(/```json\s*\n(.*?)\n```/m)
312
+ json_match = cleaned_text.match(/```json\s*\n(.*?)\n```/m)
300
313
  json_text = if json_match
301
314
  json_match[1]
302
315
  else
303
316
  # Try to find raw JSON object
304
- json_object_match = response_text.match(/\{.*\}/m)
305
- json_object_match ? json_object_match[0] : response_text
317
+ json_object_match = cleaned_text.match(/\{.*\}/m)
318
+ json_object_match ? json_object_match[0] : cleaned_text
306
319
  end
307
320
 
308
321
  # Parse JSON
@@ -335,10 +348,12 @@ module LanguageOperator
335
348
  # @return [Hash] Default configuration
336
349
  def default_config
337
350
  {
338
- timeout: 30.0, # Default timeout in seconds
339
- max_retries: 3, # Default max retry attempts
340
- retry_delay_base: 1.0, # Base delay for exponential backoff
341
- retry_delay_max: 10.0 # Maximum delay between retries
351
+ timeout_symbolic: 30.0, # Default timeout for symbolic tasks (seconds)
352
+ timeout_neural: 120.0, # Default timeout for neural tasks (seconds)
353
+ timeout_hybrid: 120.0, # Default timeout for hybrid tasks (seconds)
354
+ max_retries: 3, # Default max retry attempts
355
+ retry_delay_base: 1.0, # Base delay for exponential backoff
356
+ retry_delay_max: 10.0 # Maximum delay between retries
342
357
  }
343
358
  end
344
359
 
@@ -358,6 +373,29 @@ module LanguageOperator
358
373
  end
359
374
  end
360
375
 
376
+ # Determine appropriate timeout for a task based on its type
377
+ #
378
+ # Neural tasks typically require longer timeouts due to LLM API calls,
379
+ # while symbolic tasks (pure Ruby code) can use shorter timeouts.
380
+ #
381
+ # @param task [TaskDefinition] The task definition
382
+ # @return [Float] Timeout in seconds
383
+ def task_timeout_for_type(task)
384
+ if task.neural? && task.symbolic?
385
+ # Hybrid tasks use neural timeout (they may call LLM)
386
+ @config[:timeout_hybrid]
387
+ elsif task.neural?
388
+ # Neural tasks need longer timeout for LLM calls
389
+ @config[:timeout_neural]
390
+ elsif task.symbolic?
391
+ # Symbolic tasks use shorter timeout
392
+ @config[:timeout_symbolic]
393
+ else
394
+ # Default to symbolic timeout for undefined tasks
395
+ @config[:timeout_symbolic]
396
+ end
397
+ end
398
+
361
399
  # Execute task with retry logic and timeout
362
400
  #
363
401
  # @param task [TaskDefinition] The task definition
@@ -76,9 +76,6 @@ module LanguageOperator
76
76
 
77
77
  ctx = Helpers::ClusterContext.from_options(options.merge(cluster: cluster))
78
78
 
79
- Formatters::ProgressFormatter.info("Creating agent in cluster '#{ctx.name}'")
80
- puts
81
-
82
79
  # Generate agent name from description if not provided
83
80
  agent_name = options[:name] || generate_agent_name(description)
84
81
 
@@ -295,18 +292,18 @@ module LanguageOperator
295
292
  label_selector = "app.kubernetes.io/name=#{name}"
296
293
 
297
294
  # Use kubectl logs with label selector
298
- cmd = "#{ctx.kubectl_prefix} logs -l #{label_selector} #{tail_arg} #{follow_arg} --prefix --all-containers"
295
+ cmd = "#{ctx.kubectl_prefix} logs -l #{label_selector} #{tail_arg} #{follow_arg} --all-containers"
299
296
 
300
297
  Formatters::ProgressFormatter.info("Streaming logs for agent '#{name}'...")
301
298
  puts
302
299
 
303
- # Stream and format logs in real-time
300
+ # Stream raw logs in real-time without formatting
304
301
  require 'open3'
305
302
  Open3.popen3(cmd) do |_stdin, stdout, stderr, wait_thr|
306
303
  # Handle stdout (logs)
307
304
  stdout_thread = Thread.new do
308
305
  stdout.each_line do |line|
309
- puts Formatters::LogFormatter.format_line(line.chomp)
306
+ puts line
310
307
  $stdout.flush
311
308
  end
312
309
  end
@@ -1105,6 +1105,7 @@ module LanguageOperator
1105
1105
  {
1106
1106
  'name' => 'agent',
1107
1107
  'image' => image,
1108
+ 'imagePullPolicy' => 'Always',
1108
1109
  'env' => env_vars,
1109
1110
  'volumeMounts' => [
1110
1111
  {