language-operator 0.1.55 → 0.1.56
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 +1 -0
- data/Gemfile.lock +1 -1
- data/lib/language_operator/cli/commands/agent.rb +12 -11
- data/lib/language_operator/cli/commands/tool.rb +1 -1
- data/lib/language_operator/cli/formatters/code_formatter.rb +2 -12
- data/lib/language_operator/config/tool_registry.rb +5 -20
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
- data/lib/language_operator/tool_loader.rb +3 -8
- data/lib/language_operator/ux/create_agent.rb +3 -0
- data/lib/language_operator/version.rb +1 -1
- data/synth/001/Makefile +13 -11
- data/synth/001/README.md +14 -14
- data/synth/001/agent.rb +16 -7
- data/synth/002/Makefile +13 -9
- data/synth/002/README.md +12 -12
- data/synth/003/Makefile +21 -0
- data/synth/003/README.md +188 -0
- data/synth/003/agent.rb +41 -0
- data/synth/003/agent.txt +1 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac246ba1f01262701f8e193f697a2df7753f23f376c71a8cbfc24ab87a59e088
|
|
4
|
+
data.tar.gz: b75afe1475935a9b9ccbe3cd717d46e5b180ba123818fc53ff954f228f5663c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d66f44b6d599faaf6c0d93368ff03a50f50372c0924ad8b85f387c842c0302fa5e31c37472dbe484236fc917957ed33a2fa8b80502df0605a1d91909a5613b80
|
|
7
|
+
data.tar.gz: 727ac1463af4516edcc552582909f77351f913fbdad2ae260bcc62f1f0f058006cf56dbb71ccd4910ec0f63aeb41ec7c812ea2ab3ee8f413dab5e620443cd5a1
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -48,8 +48,11 @@ module LanguageOperator
|
|
|
48
48
|
option :wizard, type: :boolean, default: false, desc: 'Use interactive wizard mode'
|
|
49
49
|
def create(description = nil)
|
|
50
50
|
handle_command_error('create agent') do
|
|
51
|
+
# Read from stdin if available and no description provided
|
|
52
|
+
description = $stdin.read.strip if description.nil? && !$stdin.tty?
|
|
53
|
+
|
|
51
54
|
# Activate wizard mode if --wizard flag or no description provided
|
|
52
|
-
if options[:wizard] || description.nil?
|
|
55
|
+
if options[:wizard] || description.nil? || description.empty?
|
|
53
56
|
description = Ux::CreateAgent.execute(ctx)
|
|
54
57
|
|
|
55
58
|
# User cancelled wizard
|
|
@@ -328,6 +331,7 @@ module LanguageOperator
|
|
|
328
331
|
|
|
329
332
|
desc 'code NAME', 'Display synthesized agent code'
|
|
330
333
|
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
334
|
+
option :raw, type: :boolean, default: false, desc: 'Output raw code without formatting'
|
|
331
335
|
def code(name)
|
|
332
336
|
handle_command_error('get code') do
|
|
333
337
|
require_relative '../formatters/code_formatter'
|
|
@@ -357,15 +361,17 @@ module LanguageOperator
|
|
|
357
361
|
exit 1
|
|
358
362
|
end
|
|
359
363
|
|
|
364
|
+
# Raw output mode - just print the code
|
|
365
|
+
if options[:raw]
|
|
366
|
+
puts code_content
|
|
367
|
+
return
|
|
368
|
+
end
|
|
369
|
+
|
|
360
370
|
# Display with syntax highlighting
|
|
361
371
|
Formatters::CodeFormatter.display_ruby_code(
|
|
362
372
|
code_content,
|
|
363
373
|
title: "Synthesized Code for Agent: #{name}"
|
|
364
374
|
)
|
|
365
|
-
|
|
366
|
-
puts
|
|
367
|
-
puts 'This code was automatically synthesized from the agent instructions.'
|
|
368
|
-
puts "View full agent details with: aictl agent inspect #{name}"
|
|
369
375
|
end
|
|
370
376
|
end
|
|
371
377
|
|
|
@@ -752,18 +758,13 @@ module LanguageOperator
|
|
|
752
758
|
end
|
|
753
759
|
|
|
754
760
|
def watch_synthesis_status(k8s, agent_name, namespace)
|
|
755
|
-
# Start with analyzing description
|
|
756
|
-
puts
|
|
757
|
-
Formatters::ProgressFormatter.info('Synthesizing agent code...')
|
|
758
|
-
puts
|
|
759
|
-
|
|
760
761
|
max_wait = 600 # Wait up to 10 minutes (local models can be slow)
|
|
761
762
|
interval = 2 # Check every 2 seconds
|
|
762
763
|
elapsed = 0
|
|
763
764
|
start_time = Time.now
|
|
764
765
|
synthesis_data = {}
|
|
765
766
|
|
|
766
|
-
result = Formatters::ProgressFormatter.with_spinner('
|
|
767
|
+
result = Formatters::ProgressFormatter.with_spinner('Synthesizing code from instructions') do
|
|
767
768
|
loop do
|
|
768
769
|
status = check_synthesis_status(k8s, agent_name, namespace, synthesis_data, start_time)
|
|
769
770
|
return status if status
|
|
@@ -32,25 +32,15 @@ module LanguageOperator
|
|
|
32
32
|
code_to_display = code_content
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
-
# Print header
|
|
36
|
-
puts
|
|
37
|
-
puts pastel.cyan(title) if title
|
|
38
|
-
puts pastel.dim('─' * 80)
|
|
39
|
-
puts
|
|
40
|
-
|
|
41
35
|
# Highlight and print the code
|
|
42
36
|
highlighted = formatter.format(lexer.lex(code_to_display))
|
|
43
37
|
puts highlighted
|
|
44
38
|
|
|
45
39
|
# Show truncation notice if applicable
|
|
46
|
-
|
|
47
|
-
puts
|
|
48
|
-
puts pastel.dim("... #{remaining_lines} more lines ...")
|
|
49
|
-
end
|
|
40
|
+
return unless truncated
|
|
50
41
|
|
|
51
|
-
# Print footer
|
|
52
42
|
puts
|
|
53
|
-
puts pastel.dim(
|
|
43
|
+
puts pastel.dim("... #{remaining_lines} more lines ...")
|
|
54
44
|
end
|
|
55
45
|
|
|
56
46
|
# Display a code snippet with context
|
|
@@ -25,18 +25,11 @@ module LanguageOperator
|
|
|
25
25
|
# Return cached data if still valid
|
|
26
26
|
return @cache if @cache && @cache_time && (Time.now - @cache_time) < CACHE_TTL
|
|
27
27
|
|
|
28
|
-
# Fetch from remote
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
tools
|
|
34
|
-
rescue StandardError => e
|
|
35
|
-
# Fall back to local file if remote fetch fails
|
|
36
|
-
warn "Failed to fetch remote registry: #{e.message}"
|
|
37
|
-
warn 'Falling back to local registry'
|
|
38
|
-
fetch_local
|
|
39
|
-
end
|
|
28
|
+
# Fetch from remote (no fallback)
|
|
29
|
+
tools = fetch_remote
|
|
30
|
+
@cache = tools
|
|
31
|
+
@cache_time = Time.now
|
|
32
|
+
tools
|
|
40
33
|
end
|
|
41
34
|
|
|
42
35
|
# Clear the cache to force a fresh fetch
|
|
@@ -83,14 +76,6 @@ module LanguageOperator
|
|
|
83
76
|
response
|
|
84
77
|
end
|
|
85
78
|
end
|
|
86
|
-
|
|
87
|
-
def fetch_local
|
|
88
|
-
# Fall back to bundled local registry
|
|
89
|
-
patterns_path = File.join(__dir__, 'tool_patterns.yaml')
|
|
90
|
-
return {} unless File.exist?(patterns_path)
|
|
91
|
-
|
|
92
|
-
YAML.load_file(patterns_path)
|
|
93
|
-
end
|
|
94
79
|
end
|
|
95
80
|
end
|
|
96
81
|
end
|
|
@@ -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.56",
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
9
|
"name": {
|
|
@@ -69,14 +69,9 @@ module LanguageOperator
|
|
|
69
69
|
context = LanguageOperator::Dsl::Context.new(@registry)
|
|
70
70
|
code = File.read(file)
|
|
71
71
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
rescue Agent::Safety::SafeExecutor::SecurityError, Agent::Safety::ASTValidator::SecurityError => e
|
|
76
|
-
# Re-raise security errors so they're not silently ignored
|
|
77
|
-
warn "Error loading tool file #{file}: #{e.message}"
|
|
78
|
-
warn e.backtrace.join("\n")
|
|
79
|
-
raise e
|
|
72
|
+
# Tools are trusted code - execute directly without sandbox validation
|
|
73
|
+
# Only synthesized agent code should be sandboxed
|
|
74
|
+
context.instance_eval(code, file)
|
|
80
75
|
rescue StandardError => e
|
|
81
76
|
warn "Error loading tool file #{file}: #{e.message}"
|
|
82
77
|
warn e.backtrace.join("\n")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
|
+
require_relative 'concerns/headings'
|
|
4
5
|
require_relative '../cli/helpers/schedule_builder'
|
|
5
6
|
|
|
6
7
|
module LanguageOperator
|
|
@@ -14,6 +15,8 @@ module LanguageOperator
|
|
|
14
15
|
#
|
|
15
16
|
# rubocop:disable Metrics/ClassLength, Metrics/AbcSize
|
|
16
17
|
class CreateAgent < Base
|
|
18
|
+
include Concerns::Headings
|
|
19
|
+
|
|
17
20
|
# Execute the agent creation flow
|
|
18
21
|
#
|
|
19
22
|
# @return [String, nil] Generated description or nil if cancelled
|
data/synth/001/Makefile
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
.PHONY:
|
|
1
|
+
.PHONY: create code logs clean
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@echo " make synthesize - Generate agent.rb using default model"
|
|
6
|
-
@echo " make run - Execute agent.rb locally with bundle exec"
|
|
7
|
-
@echo " make validate - Validate agent.rb syntax"
|
|
8
|
-
@echo " make clean - Remove all generated .rb files"
|
|
3
|
+
AGENT := synth-001
|
|
4
|
+
AICTL := bundle exec ../../bin/aictl agent
|
|
9
5
|
|
|
10
|
-
|
|
11
|
-
cat agent.txt |
|
|
6
|
+
create:
|
|
7
|
+
cat agent.txt | $(AICTL) create --name $(AGENT)
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
code:
|
|
10
|
+
$(AICTL) code $(AGENT)
|
|
11
|
+
|
|
12
|
+
logs:
|
|
13
|
+
$(AICTL) logs $(AGENT)
|
|
14
|
+
|
|
15
|
+
clean:
|
|
16
|
+
$(AICTL) delete $(AGENT) --force
|
data/synth/001/README.md
CHANGED
|
@@ -6,27 +6,27 @@
|
|
|
6
6
|
|
|
7
7
|
## Significance
|
|
8
8
|
|
|
9
|
-
This is
|
|
9
|
+
This is a basic validation of the DSL v1 architecture. It checks that the synthesis pipeline works end-to-end with a simple agent.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
This is essentially a "Hello World" test - before attempting more complex features like learning or neural task execution, we need to verify that we can synthesize and execute basic functionality.
|
|
12
12
|
|
|
13
13
|
## What This Demonstrates
|
|
14
14
|
|
|
15
15
|
### 1. Basic Synthesis Flow Works
|
|
16
|
-
- **Natural language → Agent code generation** - The synthesis template
|
|
16
|
+
- **Natural language → Agent code generation** - The synthesis template produces valid Ruby DSL
|
|
17
17
|
- **Go operator synthesis** - The operator's synthesis controller generates executable code
|
|
18
|
-
- **ConfigMap storage** - Synthesized code is stored and mounted
|
|
18
|
+
- **ConfigMap storage** - Synthesized code is stored and mounted
|
|
19
19
|
|
|
20
20
|
### 2. DSL v1 Core Primitives
|
|
21
21
|
- **`task` definition** - Symbolic task with explicit implementation block
|
|
22
22
|
- **`main` block** - Explicit entry point with imperative control flow
|
|
23
|
-
- **`execute_task`** - Task invocation mechanism
|
|
23
|
+
- **`execute_task`** - Task invocation mechanism
|
|
24
24
|
- **`output` block** - Result handling and processing
|
|
25
25
|
|
|
26
26
|
### 3. Ruby Runtime Execution
|
|
27
|
-
- **DSL parsing** - Ruby gem
|
|
28
|
-
- **Task execution** - `TaskExecutor`
|
|
29
|
-
- **Agent lifecycle** - Agent runs to completion
|
|
27
|
+
- **DSL parsing** - Ruby gem loads and parses synthesized agents
|
|
28
|
+
- **Task execution** - `TaskExecutor` executes symbolic tasks
|
|
29
|
+
- **Agent lifecycle** - Agent runs to completion
|
|
30
30
|
|
|
31
31
|
### 4. Symbolic Task Execution
|
|
32
32
|
```ruby
|
|
@@ -35,7 +35,7 @@ task :generate_log_message do |_inputs|
|
|
|
35
35
|
end
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
This is a
|
|
38
|
+
This is a symbolic organic function - explicit code implementation with a defined contract:
|
|
39
39
|
- **Contract**: `outputs: { message: 'string' }`
|
|
40
40
|
- **Implementation**: Returns hard-coded message
|
|
41
41
|
- **Caller**: `main` block calls via `execute_task(:generate_log_message)`
|
|
@@ -43,7 +43,7 @@ This is a **symbolic organic function** - explicit code implementation with a st
|
|
|
43
43
|
## Why This Matters
|
|
44
44
|
|
|
45
45
|
### Validates the Foundation
|
|
46
|
-
Before building complex features (learning, neural execution, re-synthesis), we
|
|
46
|
+
Before building more complex features (learning, neural execution, re-synthesis), we need to verify the basic pipeline works:
|
|
47
47
|
|
|
48
48
|
```
|
|
49
49
|
User Instruction
|
|
@@ -58,15 +58,15 @@ Output
|
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
### Establishes DSL v1 Baseline
|
|
61
|
-
This test uses the
|
|
61
|
+
This test uses the new DSL v1 model (task/main) rather than the old workflow/step model. It verifies:
|
|
62
62
|
- ✅ `task` replaces `step`
|
|
63
63
|
- ✅ `main` replaces implicit `workflow`
|
|
64
64
|
- ✅ Imperative control flow works
|
|
65
|
-
- ✅ Organic function abstraction
|
|
65
|
+
- ✅ Organic function abstraction works
|
|
66
66
|
|
|
67
67
|
### No Neural Complexity Yet
|
|
68
|
-
By using a
|
|
68
|
+
By using a purely symbolic task, this test isolates synthesis validation from LLM execution complexity:
|
|
69
69
|
- No LLM API calls to fail
|
|
70
70
|
- No neural task instruction parsing
|
|
71
71
|
- No MCP server connections
|
|
72
|
-
- Just
|
|
72
|
+
- Just: synthesize → execute → output
|
data/synth/001/agent.rb
CHANGED
|
@@ -2,16 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
require 'language_operator'
|
|
4
4
|
|
|
5
|
-
agent '
|
|
6
|
-
description '
|
|
5
|
+
agent '001' do
|
|
6
|
+
description 'Continuously logs a message to stdout'
|
|
7
7
|
|
|
8
|
-
task :
|
|
9
|
-
{ message:
|
|
8
|
+
task :generate_message do |inputs|
|
|
9
|
+
{ message: "Agent 001 active at iteration #{inputs[:iteration]}" }
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
main do |
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
main do |inputs|
|
|
13
|
+
current_iteration = inputs[:iteration] || 0
|
|
14
|
+
message_data = execute_task(:generate_message, inputs: { iteration: current_iteration })
|
|
15
|
+
{
|
|
16
|
+
iteration: current_iteration + 1,
|
|
17
|
+
message: message_data[:message]
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
constraints do
|
|
22
|
+
max_iterations 999_999
|
|
23
|
+
timeout '10m'
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
output do |outputs|
|
data/synth/002/Makefile
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
.PHONY:
|
|
1
|
+
.PHONY: create code logs clean
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@echo " make synthesize - Generate agent.rb using default model"
|
|
6
|
-
@echo " make exec - Execute agent.rb locally with bundle exec"
|
|
3
|
+
AGENT := synth-002
|
|
4
|
+
AICTL := bundle exec ../../bin/aictl agent
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
cat agent.txt |
|
|
6
|
+
create:
|
|
7
|
+
cat agent.txt | $(AICTL) create --name $(AGENT)
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
code:
|
|
10
|
+
$(AICTL) code $(AGENT)
|
|
11
|
+
|
|
12
|
+
logs:
|
|
13
|
+
$(AICTL) logs $(AGENT)
|
|
14
|
+
|
|
15
|
+
clean:
|
|
16
|
+
$(AICTL) delete $(AGENT) --force
|
data/synth/002/README.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
## Significance
|
|
8
8
|
|
|
9
|
-
This
|
|
9
|
+
This validates the Organic Function concept in DSL v1.
|
|
10
10
|
|
|
11
|
-
While test 001
|
|
11
|
+
While test 001 verified we can synthesize and execute code, this test verifies we can execute neural organic functions - tasks defined purely by instructions where the LLM decides implementation at runtime.
|
|
12
12
|
|
|
13
|
-
This
|
|
13
|
+
This demonstrates one of the key features that differentiates Language Operator from traditional agent frameworks.
|
|
14
14
|
|
|
15
15
|
## What This Demonstrates
|
|
16
16
|
|
|
@@ -22,13 +22,13 @@ task :generate_fortune,
|
|
|
22
22
|
outputs: { fortune: 'string' }
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
This is a
|
|
25
|
+
This is a neural organic function - the implementation exists only as natural language instructions:
|
|
26
26
|
- ✅ **No explicit code block** - Task has no `do |inputs| ... end`
|
|
27
27
|
- ✅ **LLM synthesizes behavior** - Runtime passes instructions to LLM at execution time
|
|
28
28
|
- ✅ **Contract enforcement** - Output must match `{ fortune: 'string' }` schema
|
|
29
29
|
- ✅ **Caller transparency** - `execute_task(:generate_fortune)` works identically to symbolic tasks
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
This demonstrates the organic function abstraction: The caller doesn't know (and doesn't care) whether the task is neural or symbolic.
|
|
32
32
|
|
|
33
33
|
### 2. Scheduled Execution Mode
|
|
34
34
|
```ruby
|
|
@@ -100,9 +100,9 @@ The runtime must validate that the LLM's response matches the output schema:
|
|
|
100
100
|
|
|
101
101
|
## Why This Matters
|
|
102
102
|
|
|
103
|
-
### The Organic Function
|
|
103
|
+
### The Organic Function Approach
|
|
104
104
|
|
|
105
|
-
This test validates
|
|
105
|
+
This test validates a key feature of DSL v1:
|
|
106
106
|
|
|
107
107
|
**Traditional Approach (LangChain, AutoGen, etc.):**
|
|
108
108
|
```python
|
|
@@ -135,21 +135,21 @@ end
|
|
|
135
135
|
result = execute_task(:generate_fortune)
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
**The
|
|
138
|
+
**The key insight**: The contract (`outputs: { fortune: 'string' }`) is stable. The implementation (neural vs symbolic) can change without breaking callers.
|
|
139
139
|
|
|
140
140
|
### Enables Progressive Synthesis
|
|
141
141
|
|
|
142
|
-
This test
|
|
142
|
+
This test validates the foundation for learning:
|
|
143
143
|
|
|
144
144
|
1. **Run 1-10**: Neural execution (this test validates this works)
|
|
145
145
|
2. **System observes**: LLM always calls the same tools, returns same pattern
|
|
146
146
|
3. **Run 11+**: Symbolic execution (future re-synthesis test validates this)
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
Because `execute_task(:generate_fortune)` works the same whether the task is neural or symbolic, we can replace implementations without breaking the `main` block.
|
|
149
149
|
|
|
150
150
|
## The Organic Function In Action
|
|
151
151
|
|
|
152
|
-
**What
|
|
152
|
+
**What this approach enables:**
|
|
153
153
|
|
|
154
154
|
1. **Instant Working Code**: User says "tell me a fortune" → Agent runs immediately (neural)
|
|
155
155
|
2. **No Manual Implementation**: Never wrote `execute_llm()` or fortune generation logic
|
|
@@ -157,4 +157,4 @@ This test proves the foundation for learning:
|
|
|
157
157
|
4. **Learning Ready**: After N runs, system can observe patterns and synthesize symbolic implementation
|
|
158
158
|
5. **Zero Breaking Changes**: When re-synthesized, `main` block never changes
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
This demonstrates "living code": Code that starts neural (flexible, works immediately) and becomes symbolic (fast, cheap) through observation, all while maintaining a stable contract.
|
data/synth/003/Makefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
.PHONY: create code logs clean
|
|
2
|
+
|
|
3
|
+
AGENT := synth-003
|
|
4
|
+
AICTL := bundle exec ../../bin/aictl agent
|
|
5
|
+
TOOLS := workspace
|
|
6
|
+
|
|
7
|
+
create:
|
|
8
|
+
cat agent.txt | $(AICTL) create --name $(AGENT) --tools "$(TOOLS)"
|
|
9
|
+
|
|
10
|
+
code:
|
|
11
|
+
$(AICTL) code $(AGENT)
|
|
12
|
+
|
|
13
|
+
logs:
|
|
14
|
+
$(AICTL) logs $(AGENT)
|
|
15
|
+
|
|
16
|
+
clean:
|
|
17
|
+
$(AICTL) delete $(AGENT) --force
|
|
18
|
+
|
|
19
|
+
save:
|
|
20
|
+
$(AICTL) code $(AGENT) --raw > agent.rb
|
|
21
|
+
$(AICTL) logs $(AGENT) > output.log
|
data/synth/003/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# 003 - Workspace File Operations & Scheduled Mode
|
|
2
|
+
|
|
3
|
+
## Instructions
|
|
4
|
+
|
|
5
|
+
"Write a story one sentence at a time, with one new sentence every hour."
|
|
6
|
+
|
|
7
|
+
## Significance
|
|
8
|
+
|
|
9
|
+
This test validates **stateful execution with workspace file operations** - the ability for agents to persist and accumulate data across multiple scheduled executions.
|
|
10
|
+
|
|
11
|
+
While test 002 proved we can execute neural tasks on a schedule, this test proves we can maintain **persistent state** across runs using the workspace directory.
|
|
12
|
+
|
|
13
|
+
## What This Demonstrates
|
|
14
|
+
|
|
15
|
+
### 1. Workspace File Operations
|
|
16
|
+
|
|
17
|
+
Agents have access to a persistent workspace directory for file operations:
|
|
18
|
+
- ✅ **File reading** - Check if story file exists and read current content
|
|
19
|
+
- ✅ **File writing** - Append new sentences to the story
|
|
20
|
+
- ✅ **Stateful execution** - Each run builds on previous runs
|
|
21
|
+
- ✅ **Standard Ruby File APIs** - Use `File.read`, `File.write`, etc.
|
|
22
|
+
|
|
23
|
+
### 2. Scheduled Execution with State
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
mode :scheduled
|
|
27
|
+
schedule "0 * * * *" # Every hour
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Validates that scheduled agents can maintain state across runs:
|
|
31
|
+
- ✅ **Persistent storage** - Workspace directory survives pod restarts
|
|
32
|
+
- ✅ **Cumulative behavior** - Each execution reads previous state, adds to it
|
|
33
|
+
- ✅ **Multi-run workflows** - Tasks that span multiple scheduled executions
|
|
34
|
+
|
|
35
|
+
### 3. Complete Stateful Execution Flow
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
┌─────────────────────────────────────────────────────────┐
|
|
39
|
+
│ Kubernetes CronJob Triggers (every hour) │
|
|
40
|
+
│ Pod starts with mounted workspace volume │
|
|
41
|
+
└────────────────────┬────────────────────────────────────┘
|
|
42
|
+
│
|
|
43
|
+
▼
|
|
44
|
+
┌─────────────────────────────────────────────────────────┐
|
|
45
|
+
│ Agent Runtime Loads │
|
|
46
|
+
│ Mode: scheduled → Execute once and exit │
|
|
47
|
+
└────────────────────┬────────────────────────────────────┘
|
|
48
|
+
│
|
|
49
|
+
▼
|
|
50
|
+
┌─────────────────────────────────────────────────────────┐
|
|
51
|
+
│ main Block Executes │
|
|
52
|
+
│ 1. Check if story.txt exists in workspace │
|
|
53
|
+
│ 2. Read existing content (if any) │
|
|
54
|
+
│ 3. Execute task to generate next sentence │
|
|
55
|
+
│ 4. Append sentence to story.txt │
|
|
56
|
+
└────────────────────┬────────────────────────────────────┘
|
|
57
|
+
│
|
|
58
|
+
▼
|
|
59
|
+
┌─────────────────────────────────────────────────────────┐
|
|
60
|
+
│ File persisted to workspace volume │
|
|
61
|
+
│ Pod exits → Kubernetes waits for next hour │
|
|
62
|
+
└─────────────────────────────────────────────────────────┘
|
|
63
|
+
│
|
|
64
|
+
▼
|
|
65
|
+
┌─────────────────────────────────────────────────────────┐
|
|
66
|
+
│ Next Hour: New pod starts │
|
|
67
|
+
│ Same workspace volume mounted │
|
|
68
|
+
│ Reads previous content, adds new sentence │
|
|
69
|
+
└─────────────────────────────────────────────────────────┘
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Why This Matters
|
|
73
|
+
|
|
74
|
+
### Stateful Agents Enable New Use Cases
|
|
75
|
+
|
|
76
|
+
This test unlocks a critical capability for real-world agents:
|
|
77
|
+
|
|
78
|
+
**Before (Stateless):**
|
|
79
|
+
- Each execution is isolated
|
|
80
|
+
- No memory of previous runs
|
|
81
|
+
- Limited to one-shot tasks
|
|
82
|
+
|
|
83
|
+
**After (Stateful with Workspace):**
|
|
84
|
+
- Accumulate data over time
|
|
85
|
+
- Build complex artifacts across multiple runs
|
|
86
|
+
- Enable learning and evolution
|
|
87
|
+
|
|
88
|
+
### Real-World Applications
|
|
89
|
+
|
|
90
|
+
This pattern enables:
|
|
91
|
+
|
|
92
|
+
1. **Incremental Report Building** - Add data to reports over days/weeks
|
|
93
|
+
2. **Data Collection Pipelines** - Append to datasets on each run
|
|
94
|
+
3. **Monitoring & Alerting** - Track state changes across time
|
|
95
|
+
4. **Creative Projects** - Build stories, documents, code incrementally
|
|
96
|
+
5. **Learning Systems** - Store observations and improve over time
|
|
97
|
+
|
|
98
|
+
## Expected Behavior
|
|
99
|
+
|
|
100
|
+
### Run 1 (Hour 1)
|
|
101
|
+
- Story file doesn't exist
|
|
102
|
+
- Agent generates opening sentence
|
|
103
|
+
- Writes: "Once upon a time, there was a brave knight."
|
|
104
|
+
|
|
105
|
+
### Run 2 (Hour 2)
|
|
106
|
+
- Story file exists with 1 sentence
|
|
107
|
+
- Agent reads it, generates next sentence
|
|
108
|
+
- Appends: "The knight embarked on a quest to find the lost treasure."
|
|
109
|
+
|
|
110
|
+
### Run 3 (Hour 3)
|
|
111
|
+
- Story file exists with 2 sentences
|
|
112
|
+
- Agent reads it, generates continuation
|
|
113
|
+
- Appends: "Along the way, she met a wise old wizard."
|
|
114
|
+
|
|
115
|
+
...and so on, building a complete story over time.
|
|
116
|
+
|
|
117
|
+
## Technical Implementation Notes
|
|
118
|
+
|
|
119
|
+
### Workspace Directory
|
|
120
|
+
|
|
121
|
+
- **Location**: Typically `/workspace` in the container
|
|
122
|
+
- **Persistence**: Backed by Kubernetes PersistentVolume
|
|
123
|
+
- **Access**: Standard Ruby `File` and `Dir` operations
|
|
124
|
+
- **Lifecycle**: Survives pod restarts, shared across scheduled runs
|
|
125
|
+
|
|
126
|
+
### File Operation Patterns
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
# Read existing story
|
|
130
|
+
story_path = '/workspace/story.txt'
|
|
131
|
+
existing_story = File.exist?(story_path) ? File.read(story_path) : ""
|
|
132
|
+
|
|
133
|
+
# Generate next sentence (neural task)
|
|
134
|
+
next_sentence = execute_task(:generate_next_sentence, inputs: { context: existing_story })
|
|
135
|
+
|
|
136
|
+
# Append to story
|
|
137
|
+
File.open(story_path, 'a') do |f|
|
|
138
|
+
f.puts next_sentence
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Testing Locally
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Synthesize the agent
|
|
146
|
+
make synthesize
|
|
147
|
+
|
|
148
|
+
# Execute locally (simulates one run)
|
|
149
|
+
make exec
|
|
150
|
+
|
|
151
|
+
# Run multiple times to simulate scheduled execution
|
|
152
|
+
make exec # Run 1
|
|
153
|
+
make exec # Run 2
|
|
154
|
+
make exec # Run 3
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Each local execution should append to the story, demonstrating the stateful behavior.
|
|
158
|
+
|
|
159
|
+
## What Makes This Interesting
|
|
160
|
+
|
|
161
|
+
### 1. Emergent Behavior
|
|
162
|
+
The agent doesn't know the full story in advance - it emerges sentence by sentence, with each new sentence influenced by what came before.
|
|
163
|
+
|
|
164
|
+
### 2. LLM Context Management
|
|
165
|
+
The agent must pass the growing story as context to generate coherent continuations. This tests context window management.
|
|
166
|
+
|
|
167
|
+
### 3. File I/O Integration
|
|
168
|
+
Proves that agents can use standard Ruby file operations within the safety sandbox.
|
|
169
|
+
|
|
170
|
+
### 4. Time-Based Workflows
|
|
171
|
+
Demonstrates workflows that unfold over hours/days, not just seconds.
|
|
172
|
+
|
|
173
|
+
## Success Criteria
|
|
174
|
+
|
|
175
|
+
- ✅ Agent successfully reads workspace files
|
|
176
|
+
- ✅ Agent successfully writes/appends to workspace files
|
|
177
|
+
- ✅ Story grows by one sentence per execution
|
|
178
|
+
- ✅ Each sentence is contextually coherent with previous sentences
|
|
179
|
+
- ✅ File persists across multiple runs
|
|
180
|
+
- ✅ No errors in scheduled execution
|
|
181
|
+
|
|
182
|
+
## Future Extensions
|
|
183
|
+
|
|
184
|
+
This pattern could be extended to:
|
|
185
|
+
- **Multi-file projects** - Build entire codebases incrementally
|
|
186
|
+
- **Data analysis** - Accumulate findings in structured files
|
|
187
|
+
- **Version control integration** - Track changes over time
|
|
188
|
+
- **Collaboration** - Multiple agents reading/writing shared files
|
data/synth/003/agent.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'language_operator'
|
|
4
|
+
|
|
5
|
+
agent 'synth-003' do
|
|
6
|
+
description 'Build a story one sentence at a time, adding exactly one new sentence every hour'
|
|
7
|
+
mode :scheduled
|
|
8
|
+
schedule '0 * * * *'
|
|
9
|
+
|
|
10
|
+
task :read_existing_story,
|
|
11
|
+
instructions: "Read the story.txt file from workspace. If it doesn't exist, return empty string for content and 0 for sentence count. Return the content and number of sentences (count newlines as sentence separators).",
|
|
12
|
+
inputs: {},
|
|
13
|
+
outputs: { content: 'string', sentence_count: 'integer' }
|
|
14
|
+
|
|
15
|
+
task :generate_next_sentence,
|
|
16
|
+
instructions: 'Generate exactly one new sentence to continue this story. Maintain consistent tone and style from the existing content. Only output the new sentence without any additional text or formatting.',
|
|
17
|
+
inputs: { existing_content: 'string' },
|
|
18
|
+
outputs: { sentence: 'string' }
|
|
19
|
+
|
|
20
|
+
task :append_to_story,
|
|
21
|
+
instructions: 'Append the new sentence to story.txt in workspace. If the file has existing content, add a newline before appending. Return whether the operation succeeded and the new total sentence count (counting existing sentences plus one).',
|
|
22
|
+
inputs: { sentence: 'string' },
|
|
23
|
+
outputs: { success: 'boolean', total_sentences: 'integer' }
|
|
24
|
+
|
|
25
|
+
main do |_inputs|
|
|
26
|
+
story_data = execute_task(:read_existing_story)
|
|
27
|
+
new_sentence = execute_task(:generate_next_sentence, inputs: { existing_content: story_data[:content] })
|
|
28
|
+
result = execute_task(:append_to_story, inputs: { sentence: new_sentence[:sentence] })
|
|
29
|
+
{ added_sentence: new_sentence[:sentence], total_sentences: result[:total_sentences] }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
constraints do
|
|
33
|
+
max_iterations 999_999
|
|
34
|
+
timeout '10m'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
output do |outputs|
|
|
38
|
+
puts "Added sentence: #{outputs[:added_sentence]}"
|
|
39
|
+
puts "Story now has #{outputs[:total_sentences]} sentences"
|
|
40
|
+
end
|
|
41
|
+
end
|
data/synth/003/agent.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Write a story one sentence at a time, with one new sentence every hour.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: language-operator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.56
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Ryan
|
|
@@ -557,6 +557,10 @@ files:
|
|
|
557
557
|
- synth/002/agent.rb
|
|
558
558
|
- synth/002/agent.txt
|
|
559
559
|
- synth/002/output.log
|
|
560
|
+
- synth/003/Makefile
|
|
561
|
+
- synth/003/README.md
|
|
562
|
+
- synth/003/agent.rb
|
|
563
|
+
- synth/003/agent.txt
|
|
560
564
|
- synth/README.md
|
|
561
565
|
homepage: https://github.com/language-operator/language-operator
|
|
562
566
|
licenses:
|