language-operator 0.1.65 → 0.1.67
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/Gemfile.lock +1 -1
- data/README.md +20 -1
- data/components/agent/Gemfile +1 -1
- data/docs/observability.md +208 -0
- data/lib/language_operator/agent/task_executor.rb +11 -1
- data/lib/language_operator/agent.rb +24 -14
- data/lib/language_operator/cli/commands/agent/base.rb +140 -47
- data/lib/language_operator/cli/commands/agent/code_operations.rb +157 -16
- data/lib/language_operator/cli/errors/suggestions.rb +1 -1
- data/lib/language_operator/constants.rb +1 -0
- data/lib/language_operator/kubernetes/client.rb +1 -1
- 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/version.rb +1 -1
- data/synth/003/Makefile +6 -2
- data/synth/003/agent.txt +1 -1
- metadata +2 -4
- data/lib/language_operator/cli/commands/agent/learning.rb +0 -408
- data/synth/003/agent.optimized.rb +0 -66
- data/synth/003/agent.synthesized.rb +0 -41
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'json'
|
|
4
|
-
require_relative '../../command_loader'
|
|
5
|
-
require_relative '../../../constants/kubernetes_labels'
|
|
6
|
-
|
|
7
|
-
module LanguageOperator
|
|
8
|
-
module CLI
|
|
9
|
-
module Commands
|
|
10
|
-
module Agent
|
|
11
|
-
# Learning monitoring and control for agents
|
|
12
|
-
module Learning
|
|
13
|
-
def self.included(base)
|
|
14
|
-
base.class_eval do
|
|
15
|
-
desc 'learning SUBCOMMAND ...ARGS', 'Monitor and control agent learning'
|
|
16
|
-
subcommand 'learning', LearningCommands
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Learning subcommand class
|
|
21
|
-
class LearningCommands < BaseCommand
|
|
22
|
-
include Constants
|
|
23
|
-
include CLI::Helpers::ClusterValidator
|
|
24
|
-
include CLI::Helpers::UxHelper
|
|
25
|
-
|
|
26
|
-
desc 'status NAME', 'Show current learning status and optimization history'
|
|
27
|
-
long_desc <<-DESC
|
|
28
|
-
Display the current learning status and optimization history for an agent.
|
|
29
|
-
|
|
30
|
-
Shows learned tasks, confidence scores, and automatic optimization progress
|
|
31
|
-
managed by the operator.
|
|
32
|
-
|
|
33
|
-
Examples:
|
|
34
|
-
aictl agent learning status my-agent
|
|
35
|
-
aictl agent learning status my-agent --cluster production
|
|
36
|
-
DESC
|
|
37
|
-
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
38
|
-
def status(name)
|
|
39
|
-
handle_command_error('get learning status') do
|
|
40
|
-
ctx = CLI::Helpers::ClusterContext.from_options(options)
|
|
41
|
-
|
|
42
|
-
# Get agent to verify it exists
|
|
43
|
-
agent = ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
|
|
44
|
-
|
|
45
|
-
# Query learning status ConfigMap
|
|
46
|
-
learning_status = get_learning_status(ctx.client, name, ctx.namespace)
|
|
47
|
-
|
|
48
|
-
# Display learning information
|
|
49
|
-
display_learning_status(agent, learning_status, ctx.name)
|
|
50
|
-
end
|
|
51
|
-
rescue K8s::Error::NotFound
|
|
52
|
-
# Handle agent not found
|
|
53
|
-
ctx = CLI::Helpers::ClusterContext.from_options(options)
|
|
54
|
-
available_agents = ctx.client.list_resources(RESOURCE_AGENT, namespace: ctx.namespace)
|
|
55
|
-
available_names = available_agents.map { |a| a.dig('metadata', 'name') }
|
|
56
|
-
|
|
57
|
-
error = K8s::Error::NotFound.new(404, 'Not Found', RESOURCE_AGENT)
|
|
58
|
-
CLI::Errors::Handler.handle_not_found(error,
|
|
59
|
-
resource_type: RESOURCE_AGENT,
|
|
60
|
-
resource_name: name,
|
|
61
|
-
cluster: ctx.name,
|
|
62
|
-
available_resources: available_names)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
desc 'enable NAME', 'Enable automatic learning for an agent'
|
|
66
|
-
long_desc <<-DESC
|
|
67
|
-
Enable automatic learning for an agent by removing the learning-disabled annotation.
|
|
68
|
-
|
|
69
|
-
Learning is enabled by default, so this command only needs to be used if learning
|
|
70
|
-
was previously disabled.
|
|
71
|
-
|
|
72
|
-
Examples:
|
|
73
|
-
aictl agent learning enable my-agent
|
|
74
|
-
DESC
|
|
75
|
-
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
76
|
-
def enable(name)
|
|
77
|
-
handle_command_error('enable learning') do
|
|
78
|
-
ctx = CLI::Helpers::ClusterContext.from_options(options)
|
|
79
|
-
|
|
80
|
-
# Get agent to verify it exists
|
|
81
|
-
agent = ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
|
|
82
|
-
|
|
83
|
-
# Check current status
|
|
84
|
-
annotations = agent.dig('metadata', 'annotations')
|
|
85
|
-
annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
|
|
86
|
-
disabled_annotation = Constants::KubernetesLabels::LEARNING_DISABLED_LABEL
|
|
87
|
-
|
|
88
|
-
unless annotations.key?(disabled_annotation)
|
|
89
|
-
Formatters::ProgressFormatter.info("Learning is already enabled for agent '#{name}'")
|
|
90
|
-
return
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Remove the learning-disabled annotation
|
|
94
|
-
Formatters::ProgressFormatter.with_spinner("Enabling learning for agent '#{name}'") do
|
|
95
|
-
remove_annotation(ctx.client, name, ctx.namespace, disabled_annotation)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
Formatters::ProgressFormatter.success("Learning enabled for agent '#{name}'")
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
desc 'disable NAME', 'Disable automatic learning for an agent'
|
|
103
|
-
long_desc <<-DESC
|
|
104
|
-
Disable automatic learning for an agent by adding the learning-disabled annotation.
|
|
105
|
-
|
|
106
|
-
This prevents the operator from automatically optimizing the agent's tasks but
|
|
107
|
-
does not affect existing learned optimizations.
|
|
108
|
-
|
|
109
|
-
Examples:
|
|
110
|
-
aictl agent learning disable my-agent
|
|
111
|
-
DESC
|
|
112
|
-
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
113
|
-
def disable(name)
|
|
114
|
-
handle_command_error('disable learning') do
|
|
115
|
-
ctx = CLI::Helpers::ClusterContext.from_options(options)
|
|
116
|
-
|
|
117
|
-
# Get agent to verify it exists
|
|
118
|
-
agent = ctx.client.get_resource(RESOURCE_AGENT, name, ctx.namespace)
|
|
119
|
-
|
|
120
|
-
# Check current status
|
|
121
|
-
annotations = agent.dig('metadata', 'annotations')
|
|
122
|
-
annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
|
|
123
|
-
disabled_annotation = Constants::KubernetesLabels::LEARNING_DISABLED_LABEL
|
|
124
|
-
|
|
125
|
-
if annotations.key?(disabled_annotation)
|
|
126
|
-
Formatters::ProgressFormatter.info("Learning is already disabled for agent '#{name}'")
|
|
127
|
-
return
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Add the learning-disabled annotation
|
|
131
|
-
Formatters::ProgressFormatter.with_spinner("Disabling learning for agent '#{name}'") do
|
|
132
|
-
add_annotation(ctx.client, name, ctx.namespace, disabled_annotation, 'true')
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
Formatters::ProgressFormatter.success("Learning disabled for agent '#{name}'")
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
private
|
|
140
|
-
|
|
141
|
-
def get_learning_status(client, name, namespace)
|
|
142
|
-
config_map_name = "#{name}-learning-status"
|
|
143
|
-
begin
|
|
144
|
-
client.get_resource('ConfigMap', config_map_name, namespace)
|
|
145
|
-
rescue K8s::Error::NotFound
|
|
146
|
-
# Learning status ConfigMap doesn't exist yet - return nil
|
|
147
|
-
nil
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def display_learning_status(agent, learning_status, cluster_name)
|
|
152
|
-
agent.dig('metadata', 'name')
|
|
153
|
-
annotations = agent.dig('metadata', 'annotations')
|
|
154
|
-
annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
|
|
155
|
-
|
|
156
|
-
puts
|
|
157
|
-
|
|
158
|
-
# Display Agent Status box
|
|
159
|
-
display_agent_status_box(agent, cluster_name)
|
|
160
|
-
puts
|
|
161
|
-
|
|
162
|
-
# Display Learning Status box
|
|
163
|
-
display_learning_status_box(agent, learning_status, annotations)
|
|
164
|
-
puts
|
|
165
|
-
|
|
166
|
-
# If learning status ConfigMap exists, show detailed information
|
|
167
|
-
if learning_status
|
|
168
|
-
display_detailed_learning_status(learning_status)
|
|
169
|
-
else
|
|
170
|
-
learning_enabled = !annotations.key?(Constants::KubernetesLabels::LEARNING_DISABLED_LABEL)
|
|
171
|
-
display_learning_explanation(learning_enabled)
|
|
172
|
-
end
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
def display_detailed_learning_status(learning_status)
|
|
176
|
-
data = learning_status['data'] || {}
|
|
177
|
-
|
|
178
|
-
# Parse learning data if available
|
|
179
|
-
if data['tasks']
|
|
180
|
-
tasks_data = begin
|
|
181
|
-
JSON.parse(data['tasks'])
|
|
182
|
-
rescue StandardError
|
|
183
|
-
{}
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
if tasks_data.any?
|
|
187
|
-
puts pastel.white.bold('Learned Tasks:')
|
|
188
|
-
tasks_data.each do |task_name, task_info|
|
|
189
|
-
confidence = task_info['confidence'] || 0
|
|
190
|
-
executions = task_info['executions'] || 0
|
|
191
|
-
status = task_info['status'] || 'neural'
|
|
192
|
-
|
|
193
|
-
confidence_color = determine_confidence_color(confidence)
|
|
194
|
-
|
|
195
|
-
puts " #{pastel.cyan(task_name)}"
|
|
196
|
-
puts " Status: #{format_task_status(status)}"
|
|
197
|
-
confidence_text = pastel.send(confidence_color, "#{confidence}%")
|
|
198
|
-
puts " Confidence: #{confidence_text} (#{executions} executions)"
|
|
199
|
-
end
|
|
200
|
-
puts
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
# Show optimization history if available
|
|
205
|
-
return unless data['history']
|
|
206
|
-
|
|
207
|
-
history_data = begin
|
|
208
|
-
JSON.parse(data['history'])
|
|
209
|
-
rescue StandardError
|
|
210
|
-
[]
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
return unless history_data.any?
|
|
214
|
-
|
|
215
|
-
puts pastel.white.bold('Optimization History:')
|
|
216
|
-
history_data.last(5).each do |event|
|
|
217
|
-
timestamp = event['timestamp'] || 'Unknown'
|
|
218
|
-
action = event['action'] || 'Unknown'
|
|
219
|
-
task = event['task'] || 'Unknown'
|
|
220
|
-
|
|
221
|
-
puts " #{pastel.dim(timestamp)} - #{action} #{pastel.cyan(task)}"
|
|
222
|
-
end
|
|
223
|
-
puts
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def format_task_status(status)
|
|
227
|
-
case status
|
|
228
|
-
when 'symbolic'
|
|
229
|
-
pastel.green('Learned (Symbolic)')
|
|
230
|
-
when 'neural'
|
|
231
|
-
pastel.yellow('Learning (Neural)')
|
|
232
|
-
when 'hybrid'
|
|
233
|
-
pastel.blue('Hybrid')
|
|
234
|
-
else
|
|
235
|
-
pastel.dim(status.capitalize)
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def add_annotation(client, name, namespace, annotation_key, annotation_value)
|
|
240
|
-
# Get current agent
|
|
241
|
-
agent = client.get_resource(RESOURCE_AGENT, name, namespace)
|
|
242
|
-
|
|
243
|
-
# Add annotation
|
|
244
|
-
annotations = agent.dig('metadata', 'annotations')
|
|
245
|
-
annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
|
|
246
|
-
annotations[annotation_key] = annotation_value
|
|
247
|
-
agent['metadata']['annotations'] = annotations
|
|
248
|
-
|
|
249
|
-
# Update the agent
|
|
250
|
-
client.update_resource(agent)
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def remove_annotation(client, name, namespace, annotation_key)
|
|
254
|
-
# Get current agent
|
|
255
|
-
agent = client.get_resource(RESOURCE_AGENT, name, namespace)
|
|
256
|
-
|
|
257
|
-
# Remove annotation
|
|
258
|
-
annotations = agent.dig('metadata', 'annotations')
|
|
259
|
-
annotations = annotations.respond_to?(:to_h) ? annotations.to_h : (annotations || {})
|
|
260
|
-
annotations.delete(annotation_key)
|
|
261
|
-
agent['metadata']['annotations'] = annotations
|
|
262
|
-
|
|
263
|
-
# Update the agent
|
|
264
|
-
client.update_resource(agent)
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
def format_agent_timestamp(agent)
|
|
268
|
-
created_time = agent.dig('metadata', 'creationTimestamp')
|
|
269
|
-
return 'Unknown' unless created_time
|
|
270
|
-
|
|
271
|
-
begin
|
|
272
|
-
Time.parse(created_time).strftime('%Y-%m-%d %H:%M:%S UTC')
|
|
273
|
-
rescue StandardError
|
|
274
|
-
'Unknown'
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
def display_agent_status_box(agent, cluster_name)
|
|
279
|
-
agent_name = agent.dig('metadata', 'name')
|
|
280
|
-
timestamp = format_agent_timestamp(agent)
|
|
281
|
-
|
|
282
|
-
# Get agent operational status
|
|
283
|
-
status = agent['status']
|
|
284
|
-
if status && status['conditions']
|
|
285
|
-
ready_condition = status['conditions'].find { |c| c['type'] == 'Ready' }
|
|
286
|
-
if ready_condition
|
|
287
|
-
begin
|
|
288
|
-
last_activity = Time.parse(ready_condition['lastTransitionTime']).strftime('%Y-%m-%d %H:%M:%S UTC')
|
|
289
|
-
status_text = ready_condition['status'] == 'True' ? 'Ready' : 'Not Ready'
|
|
290
|
-
status_colored = ready_condition['status'] == 'True' ? pastel.green(status_text) : pastel.yellow(status_text)
|
|
291
|
-
rescue StandardError
|
|
292
|
-
last_activity = 'Unknown'
|
|
293
|
-
status_colored = pastel.dim('Unknown')
|
|
294
|
-
end
|
|
295
|
-
else
|
|
296
|
-
last_activity = 'Unknown'
|
|
297
|
-
status_colored = pastel.dim('Unknown')
|
|
298
|
-
end
|
|
299
|
-
else
|
|
300
|
-
last_activity = 'Unknown'
|
|
301
|
-
status_colored = pastel.dim('Unknown')
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
highlighted_box(
|
|
305
|
-
title: 'Agent Status',
|
|
306
|
-
color: :yellow,
|
|
307
|
-
rows: {
|
|
308
|
-
'Name' => pastel.white.bold(agent_name),
|
|
309
|
-
'Cluster' => cluster_name,
|
|
310
|
-
'Created' => timestamp,
|
|
311
|
-
'Last Activity' => last_activity,
|
|
312
|
-
'Status' => status_colored
|
|
313
|
-
}
|
|
314
|
-
)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
def display_learning_status_box(_agent, learning_status, annotations)
|
|
318
|
-
learning_enabled = !annotations.key?(Constants::KubernetesLabels::LEARNING_DISABLED_LABEL)
|
|
319
|
-
status_color = learning_enabled ? :green : :yellow
|
|
320
|
-
status_text = learning_enabled ? 'Enabled' : 'Disabled'
|
|
321
|
-
|
|
322
|
-
# Parse execution summary from ConfigMap if available
|
|
323
|
-
execution_summary = parse_execution_summary(learning_status)
|
|
324
|
-
|
|
325
|
-
if execution_summary
|
|
326
|
-
total_executions = execution_summary['totalExecutions'] || 0
|
|
327
|
-
learning_threshold = execution_summary['learningThreshold'] || 10
|
|
328
|
-
execution_summary['successRate'] || 0.0
|
|
329
|
-
last_execution = execution_summary['lastExecution']
|
|
330
|
-
|
|
331
|
-
runs_processed = "#{total_executions}/#{learning_threshold}"
|
|
332
|
-
progress_percent = [(total_executions.to_f / learning_threshold * 100).round, 100].min
|
|
333
|
-
progress = "#{progress_percent}% toward learning threshold"
|
|
334
|
-
|
|
335
|
-
last_run = if last_execution && !last_execution.empty?
|
|
336
|
-
begin
|
|
337
|
-
Time.parse(last_execution).strftime('%Y-%m-%d %H:%M:%S UTC')
|
|
338
|
-
rescue StandardError
|
|
339
|
-
'Unknown'
|
|
340
|
-
end
|
|
341
|
-
else
|
|
342
|
-
'No executions yet'
|
|
343
|
-
end
|
|
344
|
-
else
|
|
345
|
-
runs_processed = 'No data'
|
|
346
|
-
progress = 'Waiting for agent executions'
|
|
347
|
-
last_run = 'No executions yet'
|
|
348
|
-
end
|
|
349
|
-
|
|
350
|
-
highlighted_box(
|
|
351
|
-
title: 'Learning Status',
|
|
352
|
-
color: :cyan,
|
|
353
|
-
rows: {
|
|
354
|
-
'Learning' => pastel.send(status_color).bold(status_text),
|
|
355
|
-
'Threshold' => "#{pastel.cyan('10 successful runs')} (auto-learning trigger)",
|
|
356
|
-
'Confidence Target' => "#{pastel.cyan('85%')} (pattern detection)",
|
|
357
|
-
'Runs Processed' => runs_processed,
|
|
358
|
-
'Progress' => progress,
|
|
359
|
-
'Last Execution' => last_run
|
|
360
|
-
}
|
|
361
|
-
)
|
|
362
|
-
end
|
|
363
|
-
|
|
364
|
-
def display_learning_explanation(learning_enabled)
|
|
365
|
-
if learning_enabled
|
|
366
|
-
puts pastel.dim('Learning is enabled and will begin automatically after the agent completes 10 successful runs.')
|
|
367
|
-
puts pastel.dim('Neural tasks will be analyzed for patterns and converted to symbolic implementations.')
|
|
368
|
-
else
|
|
369
|
-
puts pastel.yellow('Learning is disabled for this agent.')
|
|
370
|
-
puts pastel.dim('Enable learning to allow automatic task optimization after sufficient executions.')
|
|
371
|
-
end
|
|
372
|
-
puts
|
|
373
|
-
puts pastel.dim('Note: Execution metrics will be available once the agent starts running and')
|
|
374
|
-
puts pastel.dim('the operator begins collecting telemetry data.')
|
|
375
|
-
puts
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
def determine_confidence_color(confidence)
|
|
379
|
-
if confidence >= 85
|
|
380
|
-
:green
|
|
381
|
-
elsif confidence >= 70
|
|
382
|
-
:yellow
|
|
383
|
-
else
|
|
384
|
-
:red
|
|
385
|
-
end
|
|
386
|
-
end
|
|
387
|
-
|
|
388
|
-
def parse_execution_summary(learning_status)
|
|
389
|
-
return nil unless learning_status
|
|
390
|
-
|
|
391
|
-
data = learning_status['data']
|
|
392
|
-
return nil unless data
|
|
393
|
-
|
|
394
|
-
execution_summary_json = data['execution-summary']
|
|
395
|
-
return nil unless execution_summary_json
|
|
396
|
-
|
|
397
|
-
begin
|
|
398
|
-
JSON.parse(execution_summary_json)
|
|
399
|
-
rescue StandardError
|
|
400
|
-
nil
|
|
401
|
-
end
|
|
402
|
-
end
|
|
403
|
-
end
|
|
404
|
-
end
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
end
|
|
408
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
require 'language_operator'
|
|
2
|
-
|
|
3
|
-
agent "s003" do
|
|
4
|
-
description "Build a story one sentence at a time, with one new sentence every hour"
|
|
5
|
-
mode :scheduled
|
|
6
|
-
schedule "0 * * * *"
|
|
7
|
-
|
|
8
|
-
task :read_existing_story,
|
|
9
|
-
inputs: { },
|
|
10
|
-
outputs: { content: 'string', sentence_count: 'integer' } do |inputs|
|
|
11
|
-
file_info = execute_tool('get_file_info', { path: 'story.txt' })
|
|
12
|
-
if file_info.is_a?(Hash) && file_info[:error]
|
|
13
|
-
{ content: '', sentence_count: 0 }
|
|
14
|
-
else
|
|
15
|
-
content = execute_tool('read_file', { path: 'story.txt' })
|
|
16
|
-
sentence_count = content.split(/[.!?]+\s*/).length
|
|
17
|
-
{ content: content, sentence_count: sentence_count }
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
task :generate_next_sentence,
|
|
22
|
-
instructions: "Generate exactly one new sentence to continue this story. Maintain consistent tone and style. Only output the new sentence.",
|
|
23
|
-
inputs: { existing_content: 'string' },
|
|
24
|
-
outputs: { sentence: 'string' }
|
|
25
|
-
|
|
26
|
-
task :append_to_story,
|
|
27
|
-
inputs: { sentence: 'string' },
|
|
28
|
-
outputs: { success: 'boolean', total_sentences: 'integer' } do |inputs|
|
|
29
|
-
existing_content = execute_tool('read_file', { path: 'story.txt' })
|
|
30
|
-
|
|
31
|
-
# Determine if we need to add a newline before appending
|
|
32
|
-
content_to_write = if existing_content.empty?
|
|
33
|
-
inputs[:sentence]
|
|
34
|
-
else
|
|
35
|
-
"\n#{inputs[:sentence]}"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Append the new sentence to the file
|
|
39
|
-
execute_tool('write_file', { path: 'story.txt', content: existing_content + content_to_write })
|
|
40
|
-
|
|
41
|
-
# Count total sentences by splitting on newlines and filtering empty lines
|
|
42
|
-
sentences = existing_content.split("\n").reject(&:empty?)
|
|
43
|
-
new_sentence_count = sentences.length + 1
|
|
44
|
-
|
|
45
|
-
{ success: true, total_sentences: new_sentence_count }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
main do |inputs|
|
|
49
|
-
story_data = execute_task(:read_existing_story)
|
|
50
|
-
new_sentence = execute_task(:generate_next_sentence,
|
|
51
|
-
inputs: { existing_content: story_data[:content] })
|
|
52
|
-
result = execute_task(:append_to_story,
|
|
53
|
-
inputs: { sentence: new_sentence[:sentence] })
|
|
54
|
-
{ sentence: new_sentence[:sentence], total: result[:total_sentences] }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
constraints do
|
|
58
|
-
max_iterations 999999
|
|
59
|
-
timeout "10m"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
output do |outputs|
|
|
63
|
-
puts "Added sentence: #{outputs[:sentence]}"
|
|
64
|
-
puts "Story now has #{outputs[:total]} sentences"
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
require 'language_operator'
|
|
2
|
-
|
|
3
|
-
agent "s003" do
|
|
4
|
-
description "Build a story one sentence at a time, with one new sentence every hour"
|
|
5
|
-
mode :scheduled
|
|
6
|
-
schedule "0 * * * *"
|
|
7
|
-
|
|
8
|
-
task :read_existing_story,
|
|
9
|
-
instructions: "Read the story.txt file from workspace. If it doesn't exist, return empty string. Return the content and count of sentences.",
|
|
10
|
-
inputs: {},
|
|
11
|
-
outputs: { content: 'string', sentence_count: 'integer' }
|
|
12
|
-
|
|
13
|
-
task :generate_next_sentence,
|
|
14
|
-
instructions: "Generate exactly one new sentence to continue this story. Maintain consistent tone and style. Only output the new sentence.",
|
|
15
|
-
inputs: { existing_content: 'string' },
|
|
16
|
-
outputs: { sentence: 'string' }
|
|
17
|
-
|
|
18
|
-
task :append_to_story,
|
|
19
|
-
instructions: "Append the new sentence to story.txt in workspace. If the file has existing content, add a newline first.",
|
|
20
|
-
inputs: { sentence: 'string' },
|
|
21
|
-
outputs: { success: 'boolean', total_sentences: 'integer' }
|
|
22
|
-
|
|
23
|
-
main do |inputs|
|
|
24
|
-
story_data = execute_task(:read_existing_story)
|
|
25
|
-
new_sentence = execute_task(:generate_next_sentence,
|
|
26
|
-
inputs: { existing_content: story_data[:content] })
|
|
27
|
-
result = execute_task(:append_to_story,
|
|
28
|
-
inputs: { sentence: new_sentence[:sentence] })
|
|
29
|
-
{ sentence: new_sentence[:sentence], total: result[:total_sentences] }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
constraints do
|
|
33
|
-
max_iterations 999999
|
|
34
|
-
timeout "10m"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
output do |outputs|
|
|
38
|
-
puts "Added sentence: #{outputs[:sentence]}"
|
|
39
|
-
puts "Story now has #{outputs[:total]} sentences"
|
|
40
|
-
end
|
|
41
|
-
end
|