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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -8
  3. data/CHANGELOG.md +14 -0
  4. data/CI_STATUS.md +56 -0
  5. data/Gemfile.lock +2 -2
  6. data/Makefile +22 -6
  7. data/lib/language_operator/agent/base.rb +10 -6
  8. data/lib/language_operator/agent/executor.rb +19 -97
  9. data/lib/language_operator/agent/safety/ast_validator.rb +62 -43
  10. data/lib/language_operator/agent/safety/safe_executor.rb +27 -2
  11. data/lib/language_operator/agent/scheduler.rb +60 -0
  12. data/lib/language_operator/agent/task_executor.rb +548 -0
  13. data/lib/language_operator/agent.rb +90 -27
  14. data/lib/language_operator/cli/base_command.rb +117 -0
  15. data/lib/language_operator/cli/commands/agent.rb +339 -407
  16. data/lib/language_operator/cli/commands/cluster.rb +274 -290
  17. data/lib/language_operator/cli/commands/install.rb +110 -119
  18. data/lib/language_operator/cli/commands/model.rb +284 -184
  19. data/lib/language_operator/cli/commands/persona.rb +218 -284
  20. data/lib/language_operator/cli/commands/quickstart.rb +4 -5
  21. data/lib/language_operator/cli/commands/status.rb +31 -35
  22. data/lib/language_operator/cli/commands/system.rb +221 -233
  23. data/lib/language_operator/cli/commands/tool.rb +356 -422
  24. data/lib/language_operator/cli/commands/use.rb +19 -22
  25. data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +0 -18
  26. data/lib/language_operator/cli/wizards/quickstart_wizard.rb +0 -1
  27. data/lib/language_operator/client/config.rb +20 -21
  28. data/lib/language_operator/config.rb +115 -3
  29. data/lib/language_operator/constants.rb +54 -0
  30. data/lib/language_operator/dsl/agent_context.rb +7 -7
  31. data/lib/language_operator/dsl/agent_definition.rb +111 -26
  32. data/lib/language_operator/dsl/config.rb +30 -66
  33. data/lib/language_operator/dsl/main_definition.rb +114 -0
  34. data/lib/language_operator/dsl/schema.rb +84 -43
  35. data/lib/language_operator/dsl/task_definition.rb +315 -0
  36. data/lib/language_operator/dsl.rb +0 -1
  37. data/lib/language_operator/instrumentation/task_tracer.rb +285 -0
  38. data/lib/language_operator/logger.rb +4 -4
  39. data/lib/language_operator/synthesis_test_harness.rb +324 -0
  40. data/lib/language_operator/templates/examples/agent_synthesis.tmpl +26 -8
  41. data/lib/language_operator/templates/schema/CHANGELOG.md +26 -0
  42. data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
  43. data/lib/language_operator/templates/schema/agent_dsl_schema.json +84 -42
  44. data/lib/language_operator/type_coercion.rb +250 -0
  45. data/lib/language_operator/ux/base.rb +81 -0
  46. data/lib/language_operator/ux/concerns/README.md +155 -0
  47. data/lib/language_operator/ux/concerns/headings.rb +90 -0
  48. data/lib/language_operator/ux/concerns/input_validation.rb +146 -0
  49. data/lib/language_operator/ux/concerns/provider_helpers.rb +167 -0
  50. data/lib/language_operator/ux/create_agent.rb +252 -0
  51. data/lib/language_operator/ux/create_model.rb +267 -0
  52. data/lib/language_operator/ux/quickstart.rb +594 -0
  53. data/lib/language_operator/version.rb +1 -1
  54. data/lib/language_operator.rb +2 -0
  55. data/requirements/ARCHITECTURE.md +1 -0
  56. data/requirements/SCRATCH.md +153 -0
  57. data/requirements/dsl.md +0 -0
  58. data/requirements/features +1 -0
  59. data/requirements/personas +1 -0
  60. data/requirements/proposals +1 -0
  61. data/requirements/tasks/iterate.md +14 -15
  62. data/requirements/tasks/optimize.md +13 -4
  63. data/synth/001/Makefile +90 -0
  64. data/synth/001/agent.rb +26 -0
  65. data/synth/001/agent.yaml +7 -0
  66. data/synth/001/output.log +44 -0
  67. data/synth/Makefile +39 -0
  68. data/synth/README.md +342 -0
  69. metadata +37 -10
  70. data/lib/language_operator/dsl/workflow_definition.rb +0 -259
  71. 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 if instructions mention specific steps
93
- # workflow do
94
- # step :step_name, tool: "tool_name", params: {key: "value"}
95
- # step :another_step, depends_on: :step_name
96
- # end
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. Create workflow steps if instructions describe a process
112
- 7. Use available tools in workflow steps
113
- 8. Use the agent name: "{{.AgentName}}"
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**
@@ -2,7 +2,7 @@
2
2
  :openapi: 3.0.3
3
3
  :info:
4
4
  :title: Language Operator Agent API
5
- :version: 0.1.31
5
+ :version: 0.1.35
6
6
  :description: HTTP API endpoints exposed by Language Operator reactive agents
7
7
  :contact:
8
8
  :name: Language Operator
@@ -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.31",
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
- "workflow": {
47
- "$ref": "#/definitions/WorkflowDefinition"
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
- "WorkflowDefinition": {
81
+ "TaskDefinition": {
74
82
  "type": "object",
75
- "description": "Multi-step workflow with dependencies",
83
+ "description": "Organic function with stable contract (inputs/outputs) and evolving implementation",
76
84
  "properties": {
77
- "steps": {
78
- "type": "array",
79
- "description": "Ordered list of workflow steps",
80
- "items": {
81
- "$ref": "#/definitions/StepDefinition"
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
- "StepDefinition": {
119
+ "MainDefinition": {
87
120
  "type": "object",
88
- "description": "Individual workflow step",
121
+ "description": "Imperative entry point for agent execution",
89
122
  "properties": {
90
- "name": {
123
+ "type": {
91
124
  "type": "string",
92
- "description": "Step identifier (symbol or string)"
125
+ "description": "Block type",
126
+ "enum": [
127
+ "main"
128
+ ]
93
129
  },
94
- "tool": {
130
+ "description": {
95
131
  "type": "string",
96
- "description": "Tool name to execute in this step"
97
- },
98
- "params": {
99
- "type": "object",
100
- "description": "Parameters to pass to the tool",
101
- "additionalProperties": true
102
- },
103
- "depends_on": {
104
- "oneOf": [
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": "LLM prompt template for this step"
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
- "required": [
123
- "name"
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": {