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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8f659d81bffc80250db041ffe9aad7b22618c0ed7cfe93836adc248010295ad
4
- data.tar.gz: d4364c0cd5cd8c7f3d8f009d5ddf9665a328f9a82c1ddbf9fbdb5c3c95ff791b
3
+ metadata.gz: ac246ba1f01262701f8e193f697a2df7753f23f376c71a8cbfc24ab87a59e088
4
+ data.tar.gz: b75afe1475935a9b9ccbe3cd717d46e5b180ba123818fc53ff954f228f5663c8
5
5
  SHA512:
6
- metadata.gz: 9131e2a4882cf617ef92724dd2c90a1133203c25e1aac1c89b7f7b28baf77d05c4347f98cf901f4c39c2423713140142f01887f5b3698285d19c8694564bdd14
7
- data.tar.gz: 3f11916df339b3a605287db9ad38794642d6e843c42710354b5c09e34b10fa12ce94012a793127a1ef7ad4ffdb400656b10f33afb764e05683b341a6dca2109e
6
+ metadata.gz: d66f44b6d599faaf6c0d93368ff03a50f50372c0924ad8b85f387c842c0302fa5e31c37472dbe484236fc917957ed33a2fa8b80502df0605a1d91909a5613b80
7
+ data.tar.gz: 727ac1463af4516edcc552582909f77351f913fbdad2ae260bcc62f1f0f058006cf56dbb71ccd4910ec0f63aeb41ec7c812ea2ab3ee8f413dab5e620443cd5a1
data/.rubocop.yml CHANGED
@@ -9,6 +9,7 @@ AllCops:
9
9
  - 'tmp/**/*'
10
10
  - 'bin/bump-version'
11
11
  - 'lib/language_operator/cli/templates/**/*'
12
+ - 'synth/**/*'
12
13
 
13
14
  # Metrics
14
15
  Metrics/BlockLength:
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- language-operator (0.1.55)
4
+ language-operator (0.1.56)
5
5
  faraday (~> 2.0)
6
6
  k8s-ruby (~> 0.17)
7
7
  mcp (~> 0.4)
@@ -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('Analyzing description and generating code') do
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
@@ -158,7 +158,7 @@ module LanguageOperator
158
158
  end
159
159
 
160
160
  # Render template
161
- template = ERB.new(template_content)
161
+ template = ERB.new(template_content, trim_mode: '-')
162
162
  yaml_content = template.result_with_hash(vars)
163
163
 
164
164
  # Dry run mode
@@ -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
- if truncated
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('─' * 80)
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
- begin
30
- tools = fetch_remote
31
- @cache = tools
32
- @cache_time = Time.now
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
@@ -2,7 +2,7 @@
2
2
  :openapi: 3.0.3
3
3
  :info:
4
4
  :title: Language Operator Agent API
5
- :version: 0.1.55
5
+ :version: 0.1.56
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.55",
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
- # Execute in sandbox with validation
73
- executor = LanguageOperator::Agent::Safety::SafeExecutor.new(context)
74
- executor.eval(code, file)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LanguageOperator
4
- VERSION = '0.1.55'
4
+ VERSION = '0.1.56'
5
5
  end
data/synth/001/Makefile CHANGED
@@ -1,14 +1,16 @@
1
- .PHONY: synthesize exec
1
+ .PHONY: create code logs clean
2
2
 
3
- help:
4
- @echo "Synthesis Test Targets:"
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
- synthesize:
11
- cat agent.txt | bundle exec ../../bin/aictl system synthesize --raw | tee agent.rb
6
+ create:
7
+ cat agent.txt | $(AICTL) create --name $(AGENT)
12
8
 
13
- exec:
14
- cat agent.rb | bundle exec ../../bin/aictl system exec | tee output.log
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 the **foundational validation** of the DSL v1 architecture. It proves that the entire synthesis pipeline works end-to-end with the simplest possible agent.
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
- Think of this as the "Hello World" of organic function synthesis - before we can validate learning, progressive synthesis, or neural task execution, we must first prove we can synthesize and execute *anything at all*.
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 can produce valid Ruby DSL
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 correctly
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 works
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 can load and parse synthesized agents
28
- - **Task execution** - `TaskExecutor` can execute symbolic tasks
29
- - **Agent lifecycle** - Agent runs to completion successfully
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 **symbolic organic function** - explicit code implementation with a stable contract:
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 must prove the basic pipeline works:
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 **new DSL v1 model** (task/main) rather than the old workflow/step model. It proves:
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 is viable
65
+ - ✅ Organic function abstraction works
66
66
 
67
67
  ### No Neural Complexity Yet
68
- By using a **purely symbolic task**, this test isolates synthesis validation from LLM execution complexity:
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 pure: synthesize → execute → output
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 'test-agent' do
6
- description 'Log a message to stdout as per instructions'
5
+ agent '001' do
6
+ description 'Continuously logs a message to stdout'
7
7
 
8
- task :generate_log_message do |_inputs|
9
- { message: 'Test agent is saying hello!' }
8
+ task :generate_message do |inputs|
9
+ { message: "Agent 001 active at iteration #{inputs[:iteration]}" }
10
10
  end
11
11
 
12
- main do |_inputs|
13
- result = execute_task(:generate_log_message)
14
- result
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: synthesize exec help
1
+ .PHONY: create code logs clean
2
2
 
3
- help:
4
- @echo "Synthesis Test Targets:"
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
- synthesize:
9
- cat agent.txt | bundle exec ../../bin/aictl system synthesize --raw | tee agent.rb
6
+ create:
7
+ cat agent.txt | $(AICTL) create --name $(AGENT)
10
8
 
11
- exec:
12
- cat agent.rb | bundle exec ../../bin/aictl system exec | tee output.log
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 is the **core validation of the Organic Function concept** - the secret sauce that makes DSL v1 revolutionary.
9
+ This validates the Organic Function concept in DSL v1.
10
10
 
11
- While test 001 proved we can synthesize and execute code at all, this test proves we can execute **neural organic functions** - tasks defined purely by instructions where the LLM decides implementation at runtime.
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 is what differentiates Language Operator from every other agent framework. No other system can do 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 **neural organic function** - the implementation exists only as natural language instructions:
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
- **This is the organic function abstraction in action**: The caller doesn't know (and doesn't care) whether the task is neural or symbolic.
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 Secret Sauce
103
+ ### The Organic Function Approach
104
104
 
105
- This test validates the **fundamental innovation** of DSL v1:
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 Magic**: The contract (`outputs: { fortune: 'string' }`) is stable. The implementation (neural vs symbolic) can change without breaking callers.
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 proves the foundation for learning:
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
- **The critical insight**: Because `execute_task(:generate_fortune)` works the same whether the task is neural or symbolic, we can replace implementations without breaking the `main` block.
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 makes this revolutionary:**
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
- **This is what "living code" means**: Code that starts neural (flexible, works immediately) and becomes symbolic (fast, cheap) through observation, all while maintaining a stable contract.
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.
@@ -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
@@ -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
@@ -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
@@ -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.55
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: