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
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'ruby_llm'
|
|
6
|
+
require_relative 'agent/safety/ast_validator'
|
|
7
|
+
|
|
8
|
+
module LanguageOperator
|
|
9
|
+
# SynthesisTestHarness replicates the Go operator's synthesis logic for local testing.
|
|
10
|
+
# This allows testing agent code generation without requiring a Kubernetes cluster.
|
|
11
|
+
#
|
|
12
|
+
# Usage:
|
|
13
|
+
# harness = LanguageOperator::SynthesisTestHarness.new
|
|
14
|
+
# code = harness.synthesize('synth/001/agent.yaml', model: 'claude-3-5-sonnet-20241022')
|
|
15
|
+
# File.write('agent.rb', code)
|
|
16
|
+
#
|
|
17
|
+
class SynthesisTestHarness
|
|
18
|
+
attr_reader :template_content, :model
|
|
19
|
+
|
|
20
|
+
def initialize(model: nil)
|
|
21
|
+
@model = model || detect_default_model
|
|
22
|
+
@synthesis_endpoint = ENV.fetch('SYNTHESIS_ENDPOINT', nil)
|
|
23
|
+
@synthesis_api_key = ENV['SYNTHESIS_API_KEY'] || 'dummy'
|
|
24
|
+
@template_path = File.join(__dir__, 'templates', 'examples', 'agent_synthesis.tmpl')
|
|
25
|
+
load_template
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Synthesize agent code from a LanguageAgent YAML file
|
|
29
|
+
#
|
|
30
|
+
# @param yaml_path [String] Path to LanguageAgent YAML file
|
|
31
|
+
# @param model [String, nil] LLM model to use (overrides default)
|
|
32
|
+
# @return [String] Generated Ruby DSL code
|
|
33
|
+
def synthesize(yaml_path, model: nil)
|
|
34
|
+
agent_spec = load_agent_spec(yaml_path)
|
|
35
|
+
|
|
36
|
+
# Build synthesis request
|
|
37
|
+
request = build_synthesis_request(agent_spec)
|
|
38
|
+
|
|
39
|
+
# Build prompt from template
|
|
40
|
+
prompt = build_prompt(request)
|
|
41
|
+
|
|
42
|
+
# Call LLM
|
|
43
|
+
response = call_llm(prompt, model: model || @model)
|
|
44
|
+
|
|
45
|
+
# Extract code from markdown
|
|
46
|
+
code = extract_code_from_markdown(response)
|
|
47
|
+
|
|
48
|
+
# Validate code
|
|
49
|
+
validate_code(code)
|
|
50
|
+
|
|
51
|
+
code
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def load_template
|
|
57
|
+
raise "Synthesis template not found at: #{@template_path}" unless File.exist?(@template_path)
|
|
58
|
+
|
|
59
|
+
@template_content = File.read(@template_path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def load_agent_spec(yaml_path)
|
|
63
|
+
raise "Agent YAML file not found: #{yaml_path}" unless File.exist?(yaml_path)
|
|
64
|
+
|
|
65
|
+
yaml_content = File.read(yaml_path)
|
|
66
|
+
full_spec = YAML.safe_load(yaml_content, permitted_classes: [Symbol])
|
|
67
|
+
|
|
68
|
+
raise "Invalid kind: expected LanguageAgent, got #{full_spec['kind']}" unless full_spec['kind'] == 'LanguageAgent'
|
|
69
|
+
|
|
70
|
+
# Extract agent name from metadata and merge into spec
|
|
71
|
+
agent_spec = full_spec['spec'].dup
|
|
72
|
+
agent_spec['agentName'] = full_spec.dig('metadata', 'name') if full_spec.dig('metadata', 'name')
|
|
73
|
+
|
|
74
|
+
agent_spec
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_synthesis_request(agent_spec)
|
|
78
|
+
# NOTE: agent_spec is the 'spec' section from the YAML
|
|
79
|
+
# The agent name comes from metadata.name, which we need to extract from the full YAML
|
|
80
|
+
{
|
|
81
|
+
instructions: agent_spec['instructions'],
|
|
82
|
+
agent_name: agent_spec['agentName'] || 'test-agent', # Will be overridden by metadata.name
|
|
83
|
+
tools: agent_spec['toolRefs'] || [],
|
|
84
|
+
models: agent_spec['modelRefs'] || [],
|
|
85
|
+
persona: agent_spec['personaRefs']&.first || nil
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def build_prompt(request)
|
|
90
|
+
# Detect temporal intent from instructions
|
|
91
|
+
temporal_intent = detect_temporal_intent(request[:instructions])
|
|
92
|
+
|
|
93
|
+
# Format tools list
|
|
94
|
+
tools_list = format_list(request[:tools], 'No tools specified')
|
|
95
|
+
|
|
96
|
+
# Format models list
|
|
97
|
+
models_list = format_list(request[:models], 'No models specified')
|
|
98
|
+
|
|
99
|
+
# Build persona section
|
|
100
|
+
persona_section = ''
|
|
101
|
+
persona_section = " persona <<~PERSONA\n #{request[:persona]}\n PERSONA\n" if request[:persona]
|
|
102
|
+
|
|
103
|
+
# Build schedule section
|
|
104
|
+
schedule_section = ''
|
|
105
|
+
schedule_rules = ''
|
|
106
|
+
|
|
107
|
+
# Build constraints section
|
|
108
|
+
constraints_section = build_constraints_section(temporal_intent)
|
|
109
|
+
|
|
110
|
+
case temporal_intent
|
|
111
|
+
when :scheduled
|
|
112
|
+
schedule_section = "\n # Extract schedule from instructions (e.g., \"daily at noon\" -> \"0 12 * * *\")\n schedule \"CRON_EXPRESSION\""
|
|
113
|
+
schedule_rules = "2. Schedule detected - extract cron expression from instructions\n3. Set schedule block with appropriate cron expression\n4. Use high max_iterations for continuous scheduled operation"
|
|
114
|
+
when :oneshot
|
|
115
|
+
schedule_rules = "2. One-shot execution detected - agent will run a limited number of times\n3. Do NOT include a schedule block for one-shot agents"
|
|
116
|
+
when :continuous
|
|
117
|
+
schedule_rules = "2. No temporal intent detected - defaulting to continuous execution\n3. Do NOT include a schedule block unless explicitly mentioned\n4. Use high max_iterations for continuous operation"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Render template with variable substitution
|
|
121
|
+
rendered = @template_content.dup
|
|
122
|
+
|
|
123
|
+
# Handle conditional sections (simple implementation for {{if .ErrorContext}})
|
|
124
|
+
rendered.gsub!(/\{\{if \.ErrorContext\}\}.*?\{\{else\}\}/m, '')
|
|
125
|
+
rendered.gsub!('{{end}}', '')
|
|
126
|
+
|
|
127
|
+
# Replace variables
|
|
128
|
+
rendered.gsub!('{{.Instructions}}', request[:instructions])
|
|
129
|
+
rendered.gsub!('{{.ToolsList}}', tools_list)
|
|
130
|
+
rendered.gsub!('{{.ModelsList}}', models_list)
|
|
131
|
+
rendered.gsub!('{{.AgentName}}', request[:agent_name])
|
|
132
|
+
rendered.gsub!('{{.TemporalIntent}}', temporal_intent.to_s.capitalize)
|
|
133
|
+
rendered.gsub!('{{.PersonaSection}}', persona_section)
|
|
134
|
+
rendered.gsub!('{{.ScheduleSection}}', schedule_section)
|
|
135
|
+
rendered.gsub!('{{.ConstraintsSection}}', constraints_section)
|
|
136
|
+
rendered.gsub!('{{.ScheduleRules}}', schedule_rules)
|
|
137
|
+
|
|
138
|
+
rendered
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def detect_temporal_intent(instructions)
|
|
142
|
+
return :continuous if instructions.nil? || instructions.strip.empty?
|
|
143
|
+
|
|
144
|
+
lower = instructions.downcase
|
|
145
|
+
|
|
146
|
+
# One-shot indicators
|
|
147
|
+
oneshot_keywords = ['run once', 'one time', 'single time', 'execute once', 'just once']
|
|
148
|
+
return :oneshot if oneshot_keywords.any? { |keyword| lower.include?(keyword) }
|
|
149
|
+
|
|
150
|
+
# Schedule indicators
|
|
151
|
+
schedule_keywords = %w[every daily hourly weekly monthly cron schedule periodically]
|
|
152
|
+
return :scheduled if schedule_keywords.any? { |keyword| lower.include?(keyword) }
|
|
153
|
+
|
|
154
|
+
# Default to continuous
|
|
155
|
+
:continuous
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def build_constraints_section(temporal_intent)
|
|
159
|
+
case temporal_intent
|
|
160
|
+
when :oneshot
|
|
161
|
+
<<~CONSTRAINTS.chomp
|
|
162
|
+
# One-shot execution detected from instructions
|
|
163
|
+
constraints do
|
|
164
|
+
max_iterations 10
|
|
165
|
+
timeout "10m"
|
|
166
|
+
end
|
|
167
|
+
CONSTRAINTS
|
|
168
|
+
when :scheduled
|
|
169
|
+
<<~CONSTRAINTS.chomp
|
|
170
|
+
# Scheduled execution - high iteration limit for continuous operation
|
|
171
|
+
constraints do
|
|
172
|
+
max_iterations 999999
|
|
173
|
+
timeout "10m"
|
|
174
|
+
end
|
|
175
|
+
CONSTRAINTS
|
|
176
|
+
when :continuous
|
|
177
|
+
<<~CONSTRAINTS.chomp
|
|
178
|
+
# Continuous execution - no specific schedule or one-shot indicator found
|
|
179
|
+
constraints do
|
|
180
|
+
max_iterations 999999
|
|
181
|
+
timeout "10m"
|
|
182
|
+
end
|
|
183
|
+
CONSTRAINTS
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def format_list(items, default_text)
|
|
188
|
+
return default_text if items.nil? || items.empty?
|
|
189
|
+
|
|
190
|
+
items.map { |item| " - #{item}" }.join("\n")
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def call_llm(prompt, model:)
|
|
194
|
+
# Priority 1: Use SYNTHESIS_ENDPOINT if configured (OpenAI-compatible)
|
|
195
|
+
return call_openai_compatible(prompt, model) if @synthesis_endpoint
|
|
196
|
+
|
|
197
|
+
# Priority 2: Detect provider from model name
|
|
198
|
+
provider, api_key = detect_provider(model)
|
|
199
|
+
|
|
200
|
+
unless api_key
|
|
201
|
+
raise "No API key found. Set either:\n " \
|
|
202
|
+
"SYNTHESIS_ENDPOINT (for local/OpenAI-compatible)\n " \
|
|
203
|
+
"ANTHROPIC_API_KEY (for Claude)\n " \
|
|
204
|
+
'OPENAI_API_KEY (for GPT)'
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Configure RubyLLM for the provider
|
|
208
|
+
RubyLLM.configure do |config|
|
|
209
|
+
case provider
|
|
210
|
+
when :anthropic
|
|
211
|
+
config.anthropic_api_key = api_key
|
|
212
|
+
when :openai
|
|
213
|
+
config.openai_api_key = api_key
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Create chat and send message
|
|
218
|
+
chat = RubyLLM.chat(model: model, provider: provider)
|
|
219
|
+
response = chat.ask(prompt)
|
|
220
|
+
|
|
221
|
+
# Extract content
|
|
222
|
+
if response.respond_to?(:content)
|
|
223
|
+
response.content
|
|
224
|
+
elsif response.is_a?(Hash) && response.key?('content')
|
|
225
|
+
response['content']
|
|
226
|
+
elsif response.is_a?(String)
|
|
227
|
+
response
|
|
228
|
+
else
|
|
229
|
+
response.to_s
|
|
230
|
+
end
|
|
231
|
+
rescue StandardError => e
|
|
232
|
+
raise "LLM call failed: #{e.message}"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def call_openai_compatible(prompt, model)
|
|
236
|
+
# Configure RubyLLM for OpenAI-compatible endpoint
|
|
237
|
+
RubyLLM.configure do |config|
|
|
238
|
+
config.openai_api_key = @synthesis_api_key
|
|
239
|
+
config.openai_api_base = @synthesis_endpoint
|
|
240
|
+
config.openai_use_system_role = true # Better compatibility with local models
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Create chat with OpenAI provider (will use configured endpoint)
|
|
244
|
+
chat = RubyLLM.chat(model: model, provider: :openai, assume_model_exists: true)
|
|
245
|
+
|
|
246
|
+
# Send message
|
|
247
|
+
response = chat.ask(prompt)
|
|
248
|
+
|
|
249
|
+
# Extract content
|
|
250
|
+
if response.respond_to?(:content)
|
|
251
|
+
response.content
|
|
252
|
+
elsif response.is_a?(Hash) && response.key?('content')
|
|
253
|
+
response['content']
|
|
254
|
+
elsif response.is_a?(String)
|
|
255
|
+
response
|
|
256
|
+
else
|
|
257
|
+
response.to_s
|
|
258
|
+
end
|
|
259
|
+
rescue StandardError => e
|
|
260
|
+
raise "OpenAI-compatible endpoint call failed: #{e.message}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def detect_provider(model)
|
|
264
|
+
if model.start_with?('claude')
|
|
265
|
+
[:anthropic, ENV.fetch('ANTHROPIC_API_KEY', nil)]
|
|
266
|
+
elsif model.start_with?('gpt')
|
|
267
|
+
[:openai, ENV.fetch('OPENAI_API_KEY', nil)]
|
|
268
|
+
else
|
|
269
|
+
# Default to Anthropic
|
|
270
|
+
[:anthropic, ENV.fetch('ANTHROPIC_API_KEY', nil)]
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def detect_default_model
|
|
275
|
+
# Priority 1: Use SYNTHESIS_MODEL if configured
|
|
276
|
+
return ENV['SYNTHESIS_MODEL'] if ENV['SYNTHESIS_MODEL']
|
|
277
|
+
|
|
278
|
+
# Priority 2: Use cloud providers
|
|
279
|
+
if ENV['ANTHROPIC_API_KEY']
|
|
280
|
+
'claude-3-5-sonnet-20241022'
|
|
281
|
+
elsif ENV['OPENAI_API_KEY']
|
|
282
|
+
'gpt-4-turbo'
|
|
283
|
+
else
|
|
284
|
+
# Default to a reasonable model name for local endpoints
|
|
285
|
+
'mistralai/magistral-small-2509'
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def extract_code_from_markdown(content)
|
|
290
|
+
content = content.strip
|
|
291
|
+
|
|
292
|
+
# Try ```ruby first
|
|
293
|
+
if (match = content.match(/```ruby\n(.*?)```/m))
|
|
294
|
+
return match[1].strip
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Try generic ``` blocks
|
|
298
|
+
if (match = content.match(/```\n(.*?)```/m))
|
|
299
|
+
return match[1].strip
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# If no code blocks, return as-is and let validation catch it
|
|
303
|
+
content
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def validate_code(code)
|
|
307
|
+
# Basic checks
|
|
308
|
+
raise 'Empty code generated' if code.strip.empty?
|
|
309
|
+
raise "Code does not contain 'agent' definition" unless code.include?('agent ')
|
|
310
|
+
raise "Code does not require 'language_operator'" unless code.match?(/require ['"]language_operator['"]/)
|
|
311
|
+
|
|
312
|
+
# AST validation for security
|
|
313
|
+
validator = LanguageOperator::Agent::Safety::ASTValidator.new
|
|
314
|
+
violations = validator.validate(code, '(generated)')
|
|
315
|
+
|
|
316
|
+
unless violations.empty?
|
|
317
|
+
error_msgs = violations.map { |v| v[:message] }.join("\n")
|
|
318
|
+
raise "Security validation failed:\n#{error_msgs}"
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
true
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
@@ -75,6 +75,11 @@ This is attempt {{.AttemptNumber}} of {{.MaxAttempts}}. The user is counting on
|
|
|
75
75
|
|
|
76
76
|
**Detected Temporal Intent:** {{.TemporalIntent}}
|
|
77
77
|
|
|
78
|
+
**Runtime Context:**
|
|
79
|
+
- All agent messages and output are automatically logged to stdout
|
|
80
|
+
- Agents have access to a workspace directory for file operations
|
|
81
|
+
- LLM responses are captured and available in agent execution context
|
|
82
|
+
|
|
78
83
|
Generate Ruby DSL code using this exact format (wrapped in triple-backticks with ruby):
|
|
79
84
|
|
|
80
85
|
```ruby
|
|
@@ -89,11 +94,22 @@ agent "{{.AgentName}}" do
|
|
|
89
94
|
"Second objective"
|
|
90
95
|
]
|
|
91
96
|
|
|
92
|
-
# Define workflow
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
# REQUIRED: Define workflow with at least one step
|
|
98
|
+
workflow do
|
|
99
|
+
# Use tools when available
|
|
100
|
+
step :step_name, tool: "tool_name", params: {key: "value"}
|
|
101
|
+
|
|
102
|
+
# Or use execute blocks for custom Ruby code (simple logging, calculations, etc.)
|
|
103
|
+
step :custom_step do
|
|
104
|
+
execute do
|
|
105
|
+
puts "Custom output from Ruby code"
|
|
106
|
+
{ result: "done" }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Chain steps with dependencies if needed
|
|
111
|
+
step :another_step, depends_on: :step_name
|
|
112
|
+
end
|
|
97
113
|
|
|
98
114
|
{{.ConstraintsSection}}
|
|
99
115
|
|
|
@@ -108,8 +124,10 @@ end
|
|
|
108
124
|
1. Generate ONLY the Ruby code within triple-backticks, no explanations before or after
|
|
109
125
|
{{.ScheduleRules}}
|
|
110
126
|
5. Break down instructions into clear, actionable objectives
|
|
111
|
-
6.
|
|
112
|
-
7.
|
|
113
|
-
8.
|
|
127
|
+
6. REQUIRED: Always include a workflow block with at least one step (even for simple single-action agents)
|
|
128
|
+
7. For simple tasks (logging, calculations), use a single step with an execute block containing Ruby code
|
|
129
|
+
8. For complex tasks, use multiple steps with tools or execute blocks
|
|
130
|
+
9. Use available tools in workflow steps when tools are provided
|
|
131
|
+
10. Use the agent name: "{{.AgentName}}"
|
|
114
132
|
|
|
115
133
|
Generate the code now:
|
|
@@ -12,6 +12,32 @@ The schema version is tied directly to the gem version and follows [Semantic Ver
|
|
|
12
12
|
|
|
13
13
|
## Version History
|
|
14
14
|
|
|
15
|
+
### 0.1.34 (2025-11-14)
|
|
16
|
+
|
|
17
|
+
**DSL v1: Task/Main Primitives Added**
|
|
18
|
+
|
|
19
|
+
This release adds support for the new DSL v1 pattern with task/main primitives while maintaining backward compatibility with the workflow/step pattern.
|
|
20
|
+
|
|
21
|
+
**New Features:**
|
|
22
|
+
- Added `task()` DSL method to AgentDefinition for defining organic functions
|
|
23
|
+
- Task definitions support neural (instructions), symbolic (code block), and hybrid implementations
|
|
24
|
+
- Tasks stored in `@tasks` hash on AgentDefinition
|
|
25
|
+
- Full input/output schema validation via TaskDefinition
|
|
26
|
+
|
|
27
|
+
**Improvements:**
|
|
28
|
+
- Added deprecation warning to `workflow()` method
|
|
29
|
+
- Updated schema to include task definitions
|
|
30
|
+
- Added comprehensive test coverage for task registration
|
|
31
|
+
|
|
32
|
+
**Deprecated:**
|
|
33
|
+
- `workflow` and `step` pattern (use `task` and `main` instead)
|
|
34
|
+
- Migration guide available in requirements/proposals/dsl-v1.md
|
|
35
|
+
|
|
36
|
+
**Backward Compatibility:**
|
|
37
|
+
- Existing workflow-based agents continue to work
|
|
38
|
+
- Both task and workflow can coexist in same agent during migration
|
|
39
|
+
- No breaking changes to existing code
|
|
40
|
+
|
|
15
41
|
### 0.1.30 (2025-11-12)
|
|
16
42
|
|
|
17
43
|
**Initial schema artifact generation**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"$id": "https://github.com/language-operator/language-operator-gem/schema/agent-dsl.json",
|
|
4
4
|
"title": "Language Operator Agent DSL",
|
|
5
5
|
"description": "Schema for defining autonomous AI agents using the Language Operator DSL",
|
|
6
|
-
"version": "0.1.
|
|
6
|
+
"version": "0.1.35",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"name": {
|
|
@@ -43,8 +43,16 @@
|
|
|
43
43
|
},
|
|
44
44
|
"minItems": 0
|
|
45
45
|
},
|
|
46
|
-
"
|
|
47
|
-
"
|
|
46
|
+
"tasks": {
|
|
47
|
+
"type": "array",
|
|
48
|
+
"description": "Task definitions (organic functions with stable contracts)",
|
|
49
|
+
"items": {
|
|
50
|
+
"$ref": "#/definitions/TaskDefinition"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"main": {
|
|
54
|
+
"$ref": "#/definitions/MainDefinition",
|
|
55
|
+
"description": "Main execution block (imperative entry point)"
|
|
48
56
|
},
|
|
49
57
|
"constraints": {
|
|
50
58
|
"$ref": "#/definitions/ConstraintsDefinition"
|
|
@@ -70,57 +78,91 @@
|
|
|
70
78
|
"name"
|
|
71
79
|
],
|
|
72
80
|
"definitions": {
|
|
73
|
-
"
|
|
81
|
+
"TaskDefinition": {
|
|
74
82
|
"type": "object",
|
|
75
|
-
"description": "
|
|
83
|
+
"description": "Organic function with stable contract (inputs/outputs) and evolving implementation",
|
|
76
84
|
"properties": {
|
|
77
|
-
"
|
|
78
|
-
"type": "
|
|
79
|
-
"description": "
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
"name": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "Task identifier (symbol)",
|
|
88
|
+
"pattern": "^[a-z_][a-z0-9_]*$"
|
|
89
|
+
},
|
|
90
|
+
"inputs": {
|
|
91
|
+
"$ref": "#/definitions/TypeSchema",
|
|
92
|
+
"description": "Input contract (parameter types)"
|
|
93
|
+
},
|
|
94
|
+
"outputs": {
|
|
95
|
+
"$ref": "#/definitions/TypeSchema",
|
|
96
|
+
"description": "Output contract (return value types)"
|
|
97
|
+
},
|
|
98
|
+
"instructions": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "Natural language instructions for neural implementation (optional)"
|
|
101
|
+
},
|
|
102
|
+
"implementation_type": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Implementation approach",
|
|
105
|
+
"enum": [
|
|
106
|
+
"neural",
|
|
107
|
+
"symbolic",
|
|
108
|
+
"hybrid",
|
|
109
|
+
"undefined"
|
|
110
|
+
]
|
|
83
111
|
}
|
|
84
|
-
}
|
|
112
|
+
},
|
|
113
|
+
"required": [
|
|
114
|
+
"name",
|
|
115
|
+
"inputs",
|
|
116
|
+
"outputs"
|
|
117
|
+
]
|
|
85
118
|
},
|
|
86
|
-
"
|
|
119
|
+
"MainDefinition": {
|
|
87
120
|
"type": "object",
|
|
88
|
-
"description": "
|
|
121
|
+
"description": "Imperative entry point for agent execution",
|
|
89
122
|
"properties": {
|
|
90
|
-
"
|
|
123
|
+
"type": {
|
|
91
124
|
"type": "string",
|
|
92
|
-
"description": "
|
|
125
|
+
"description": "Block type",
|
|
126
|
+
"enum": [
|
|
127
|
+
"main"
|
|
128
|
+
]
|
|
93
129
|
},
|
|
94
|
-
"
|
|
130
|
+
"description": {
|
|
95
131
|
"type": "string",
|
|
96
|
-
"description": "
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
"type": "string"
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
"type": "array",
|
|
110
|
-
"items": {
|
|
111
|
-
"type": "string"
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
],
|
|
115
|
-
"description": "Step dependencies (must complete before this step)"
|
|
116
|
-
},
|
|
117
|
-
"prompt": {
|
|
132
|
+
"description": "Main block executes tasks using execute_task() with Ruby control flow"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"additionalProperties": false
|
|
136
|
+
},
|
|
137
|
+
"TypeSchema": {
|
|
138
|
+
"type": "object",
|
|
139
|
+
"description": "Type schema for task contract validation",
|
|
140
|
+
"patternProperties": {
|
|
141
|
+
"^[a-z_][a-z0-9_]*$": {
|
|
118
142
|
"type": "string",
|
|
119
|
-
"description": "
|
|
143
|
+
"description": "Parameter type",
|
|
144
|
+
"enum": [
|
|
145
|
+
"string",
|
|
146
|
+
"integer",
|
|
147
|
+
"number",
|
|
148
|
+
"boolean",
|
|
149
|
+
"array",
|
|
150
|
+
"hash",
|
|
151
|
+
"any"
|
|
152
|
+
]
|
|
120
153
|
}
|
|
121
154
|
},
|
|
122
|
-
"
|
|
123
|
-
|
|
155
|
+
"additionalProperties": false,
|
|
156
|
+
"examples": [
|
|
157
|
+
{
|
|
158
|
+
"user_id": "integer",
|
|
159
|
+
"name": "string",
|
|
160
|
+
"active": "boolean"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"data": "array",
|
|
164
|
+
"metadata": "hash"
|
|
165
|
+
}
|
|
124
166
|
]
|
|
125
167
|
},
|
|
126
168
|
"ConstraintsDefinition": {
|