self_agency 0.0.1
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 +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +40 -0
- data/.irbrc +22 -0
- data/CHANGELOG.md +5 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +177 -0
- data/Rakefile +8 -0
- data/docs/api/configuration.md +85 -0
- data/docs/api/errors.md +166 -0
- data/docs/api/index.md +37 -0
- data/docs/api/self-agency-module.md +198 -0
- data/docs/architecture/overview.md +181 -0
- data/docs/architecture/security.md +101 -0
- data/docs/assets/images/self_agency.gif +0 -0
- data/docs/assets/images/self_agency.mp4 +0 -0
- data/docs/development/contributing.md +45 -0
- data/docs/development/setup.md +81 -0
- data/docs/development/testing.md +70 -0
- data/docs/examples/autonomous-robots.md +109 -0
- data/docs/examples/basic-examples.md +237 -0
- data/docs/examples/collaborative-robots.md +98 -0
- data/docs/examples/full-workflow.md +100 -0
- data/docs/examples/index.md +36 -0
- data/docs/getting-started/installation.md +71 -0
- data/docs/getting-started/quick-start.md +94 -0
- data/docs/guide/configuration.md +113 -0
- data/docs/guide/generating-methods.md +146 -0
- data/docs/guide/how-to-use.md +144 -0
- data/docs/guide/lifecycle-hooks.md +86 -0
- data/docs/guide/prompt-templates.md +189 -0
- data/docs/guide/saving-methods.md +84 -0
- data/docs/guide/scopes.md +74 -0
- data/docs/guide/source-inspection.md +96 -0
- data/docs/index.md +77 -0
- data/examples/01_basic_usage.rb +27 -0
- data/examples/02_multiple_methods.rb +43 -0
- data/examples/03_scopes.rb +40 -0
- data/examples/04_source_inspection.rb +46 -0
- data/examples/05_lifecycle_hook.rb +55 -0
- data/examples/06_configuration.rb +97 -0
- data/examples/07_error_handling.rb +103 -0
- data/examples/08_class_context.rb +64 -0
- data/examples/09_method_override.rb +52 -0
- data/examples/10_full_workflow.rb +118 -0
- data/examples/11_collaborative_robots/atlas.rb +31 -0
- data/examples/11_collaborative_robots/echo.rb +30 -0
- data/examples/11_collaborative_robots/main.rb +190 -0
- data/examples/11_collaborative_robots/nova.rb +71 -0
- data/examples/11_collaborative_robots/robot.rb +119 -0
- data/examples/12_autonomous_robots/analyst.rb +193 -0
- data/examples/12_autonomous_robots/collector.rb +78 -0
- data/examples/12_autonomous_robots/main.rb +166 -0
- data/examples/12_autonomous_robots/planner.rb +125 -0
- data/examples/12_autonomous_robots/robot.rb +284 -0
- data/examples/generated/from_range_class.rb +3 -0
- data/examples/generated/mean_instance.rb +4 -0
- data/examples/generated/median_instance.rb +15 -0
- data/examples/generated/report_singleton.rb +3 -0
- data/examples/generated/standard_deviation_instance.rb +8 -0
- data/examples/lib/message_bus.rb +57 -0
- data/examples/lib/setup.rb +8 -0
- data/lib/self_agency/configuration.rb +76 -0
- data/lib/self_agency/errors.rb +35 -0
- data/lib/self_agency/generator.rb +47 -0
- data/lib/self_agency/prompts/generate/system.txt.erb +15 -0
- data/lib/self_agency/prompts/generate/user.txt.erb +13 -0
- data/lib/self_agency/prompts/shape/system.txt.erb +26 -0
- data/lib/self_agency/prompts/shape/user.txt.erb +10 -0
- data/lib/self_agency/sandbox.rb +17 -0
- data/lib/self_agency/saver.rb +62 -0
- data/lib/self_agency/validator.rb +64 -0
- data/lib/self_agency/version.rb +5 -0
- data/lib/self_agency.rb +315 -0
- data/mkdocs.yml +156 -0
- data/sig/self_agency.rbs +4 -0
- metadata +163 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 03_scopes.rb — Instance, singleton, and class scopes
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - scope: :instance — available to all instances
|
|
8
|
+
# - scope: :singleton — available to only one instance
|
|
9
|
+
# - scope: :class — available on the class itself
|
|
10
|
+
# - Singleton methods are NOT available on other instances
|
|
11
|
+
#
|
|
12
|
+
# Requires a running Ollama instance with the configured model.
|
|
13
|
+
|
|
14
|
+
require_relative "lib/setup"
|
|
15
|
+
|
|
16
|
+
class Greeter
|
|
17
|
+
include SelfAgency
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
alice = Greeter.new
|
|
21
|
+
bob = Greeter.new
|
|
22
|
+
|
|
23
|
+
# --- Instance scope (default) ---
|
|
24
|
+
puts "=== Instance scope ==="
|
|
25
|
+
alice._("an instance method named 'hello' that returns the string 'Hello, world!'")
|
|
26
|
+
puts "alice.hello = #{alice.hello}"
|
|
27
|
+
puts "bob.hello = #{bob.hello} (available to all instances)"
|
|
28
|
+
puts
|
|
29
|
+
|
|
30
|
+
# --- Singleton scope ---
|
|
31
|
+
puts "=== Singleton scope ==="
|
|
32
|
+
alice._("an instance method named 'secret' that returns the string 'Alice only'", scope: :singleton)
|
|
33
|
+
puts "alice.secret = #{alice.secret}"
|
|
34
|
+
puts "bob.respond_to?(:secret) = #{bob.respond_to?(:secret)} (NOT available on other instances)"
|
|
35
|
+
puts
|
|
36
|
+
|
|
37
|
+
# --- Class scope ---
|
|
38
|
+
puts "=== Class scope ==="
|
|
39
|
+
alice._("a class method named 'self.class_greeting' that returns 'Greetings from Greeter'", scope: :class)
|
|
40
|
+
puts "Greeter.class_greeting = #{Greeter.class_greeting}"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 04_source_inspection.rb — Viewing generated source code
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - _source_for on an instance (instance-level lookup)
|
|
8
|
+
# - _source_for on the class (class-level lookup via ClassMethods)
|
|
9
|
+
# - Original description appears as a comment header
|
|
10
|
+
# - Fallback to method_source for file-defined methods
|
|
11
|
+
#
|
|
12
|
+
# Requires a running Ollama instance with the configured model.
|
|
13
|
+
|
|
14
|
+
require_relative "lib/setup"
|
|
15
|
+
|
|
16
|
+
class MathHelper
|
|
17
|
+
include SelfAgency
|
|
18
|
+
|
|
19
|
+
# Multiplies a number by two.
|
|
20
|
+
def double(n)
|
|
21
|
+
n * 2
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
helper = MathHelper.new
|
|
26
|
+
|
|
27
|
+
puts "=== Generate a method ==="
|
|
28
|
+
description = "an instance method named 'square' that accepts an integer n and returns n * n"
|
|
29
|
+
helper._(description)
|
|
30
|
+
puts "helper.square(5) = #{helper.square(5)}"
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
puts "=== Instance-level _source_for ==="
|
|
34
|
+
puts helper._source_for(:square)
|
|
35
|
+
puts
|
|
36
|
+
|
|
37
|
+
puts "=== Class-level _source_for ==="
|
|
38
|
+
puts MathHelper._source_for(:square)
|
|
39
|
+
puts
|
|
40
|
+
|
|
41
|
+
puts "=== Fallback to method_source for file-defined methods ==="
|
|
42
|
+
puts helper._source_for(:double)
|
|
43
|
+
puts
|
|
44
|
+
|
|
45
|
+
puts "=== _source_for returns nil for unknown methods ==="
|
|
46
|
+
puts helper._source_for(:nonexistent).inspect
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 05_lifecycle_hook.rb — Persisting generated methods
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Overriding on_method_generated(method_name, scope, code)
|
|
8
|
+
# - Hook fires for each method generated
|
|
9
|
+
# - Persistence pattern: saving generated code to files
|
|
10
|
+
#
|
|
11
|
+
# Requires a running Ollama instance with the configured model.
|
|
12
|
+
|
|
13
|
+
require_relative "lib/setup"
|
|
14
|
+
|
|
15
|
+
class PersistentCalculator
|
|
16
|
+
include SelfAgency
|
|
17
|
+
|
|
18
|
+
attr_reader :generation_log
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
@generation_log = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def on_method_generated(method_name, scope, code)
|
|
25
|
+
@generation_log << { method_name: method_name, scope: scope, code: code }
|
|
26
|
+
puts " [hook] Generated :#{method_name} (scope: #{scope})"
|
|
27
|
+
puts " [hook] Code preview: #{code.lines.first.chomp}..."
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
calc = PersistentCalculator.new
|
|
32
|
+
|
|
33
|
+
puts "=== Generating methods (hook will fire for each) ==="
|
|
34
|
+
puts
|
|
35
|
+
|
|
36
|
+
calc._("an instance method named 'increment' that accepts an integer n and returns n + 1")
|
|
37
|
+
puts
|
|
38
|
+
|
|
39
|
+
calc._(
|
|
40
|
+
"two instance methods: " \
|
|
41
|
+
"'min_of(a, b)' returns the smaller of a and b, " \
|
|
42
|
+
"'max_of(a, b)' returns the larger of a and b"
|
|
43
|
+
)
|
|
44
|
+
puts
|
|
45
|
+
|
|
46
|
+
puts "=== Generation log ==="
|
|
47
|
+
calc.generation_log.each_with_index do |entry, i|
|
|
48
|
+
puts "#{i + 1}. :#{entry[:method_name]} (#{entry[:scope]})"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
puts
|
|
52
|
+
puts "=== Using the generated methods ==="
|
|
53
|
+
puts "calc.increment(41) = #{calc.increment(41)}"
|
|
54
|
+
puts "calc.min_of(3, 7) = #{calc.min_of(3, 7)}"
|
|
55
|
+
puts "calc.max_of(3, 7) = #{calc.max_of(3, 7)}"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 06_configuration.rb — Configuration options
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - All configurable options with their defaults
|
|
8
|
+
# - Changing provider, model, timeouts, retries
|
|
9
|
+
# - SelfAgency.reset! and SelfAgency.ensure_configured!
|
|
10
|
+
# - Custom template_directory configuration
|
|
11
|
+
#
|
|
12
|
+
# This example does NOT require a running Ollama instance for
|
|
13
|
+
# the configuration inspection parts. Only the final generation
|
|
14
|
+
# call requires a live LLM.
|
|
15
|
+
|
|
16
|
+
require_relative "../lib/self_agency"
|
|
17
|
+
|
|
18
|
+
puts "=== Default configuration (before configure) ==="
|
|
19
|
+
SelfAgency.reset!
|
|
20
|
+
cfg = SelfAgency.configuration
|
|
21
|
+
puts <<~DEFAULTS
|
|
22
|
+
provider: #{cfg.provider.inspect}
|
|
23
|
+
model: #{cfg.model.inspect}
|
|
24
|
+
api_base: #{cfg.api_base.inspect}
|
|
25
|
+
request_timeout: #{cfg.request_timeout}
|
|
26
|
+
max_retries: #{cfg.max_retries}
|
|
27
|
+
retry_interval: #{cfg.retry_interval}
|
|
28
|
+
template_directory: #{cfg.template_directory}
|
|
29
|
+
DEFAULTS
|
|
30
|
+
|
|
31
|
+
puts "=== ensure_configured! before configure ==="
|
|
32
|
+
begin
|
|
33
|
+
SelfAgency.ensure_configured!
|
|
34
|
+
rescue SelfAgency::Error => e
|
|
35
|
+
puts "Caught expected error: #{e.message}"
|
|
36
|
+
end
|
|
37
|
+
puts
|
|
38
|
+
|
|
39
|
+
puts "=== Configuring with custom values ==="
|
|
40
|
+
SelfAgency.configure do |config|
|
|
41
|
+
config.provider = :ollama
|
|
42
|
+
config.model = "qwen3-coder:30b"
|
|
43
|
+
config.api_base = "http://localhost:11434/v1"
|
|
44
|
+
config.request_timeout = 60
|
|
45
|
+
config.max_retries = 3
|
|
46
|
+
config.retry_interval = 1.0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
cfg = SelfAgency.configuration
|
|
50
|
+
puts <<~CUSTOM
|
|
51
|
+
provider: #{cfg.provider.inspect}
|
|
52
|
+
model: #{cfg.model.inspect}
|
|
53
|
+
api_base: #{cfg.api_base.inspect}
|
|
54
|
+
request_timeout: #{cfg.request_timeout}
|
|
55
|
+
max_retries: #{cfg.max_retries}
|
|
56
|
+
retry_interval: #{cfg.retry_interval}
|
|
57
|
+
template_directory: #{cfg.template_directory}
|
|
58
|
+
CUSTOM
|
|
59
|
+
|
|
60
|
+
puts "=== ensure_configured! after configure ==="
|
|
61
|
+
SelfAgency.ensure_configured!
|
|
62
|
+
puts "No error — configuration is active."
|
|
63
|
+
puts
|
|
64
|
+
|
|
65
|
+
puts "=== reset! restores defaults ==="
|
|
66
|
+
SelfAgency.reset!
|
|
67
|
+
cfg = SelfAgency.configuration
|
|
68
|
+
puts "provider after reset: #{cfg.provider.inspect}"
|
|
69
|
+
puts "model after reset: #{cfg.model.inspect}"
|
|
70
|
+
puts
|
|
71
|
+
|
|
72
|
+
begin
|
|
73
|
+
SelfAgency.ensure_configured!
|
|
74
|
+
rescue SelfAgency::Error => e
|
|
75
|
+
puts "ensure_configured! after reset: #{e.message}"
|
|
76
|
+
end
|
|
77
|
+
puts
|
|
78
|
+
|
|
79
|
+
puts "=== Custom template directory ==="
|
|
80
|
+
SelfAgency.configure do |config|
|
|
81
|
+
config.provider = :ollama
|
|
82
|
+
config.model = "qwen3-coder:30b"
|
|
83
|
+
config.api_base = "http://localhost:11434/v1"
|
|
84
|
+
config.template_directory = "/tmp/my_custom_prompts"
|
|
85
|
+
end
|
|
86
|
+
puts "template_directory: #{SelfAgency.configuration.template_directory}"
|
|
87
|
+
puts
|
|
88
|
+
|
|
89
|
+
# Restore standard config for any follow-up usage
|
|
90
|
+
SelfAgency.reset!
|
|
91
|
+
SelfAgency.configure do |config|
|
|
92
|
+
config.provider = :ollama
|
|
93
|
+
config.model = "qwen3-coder:30b"
|
|
94
|
+
config.api_base = "http://localhost:11434/v1"
|
|
95
|
+
end
|
|
96
|
+
puts "=== Configuration restored for live usage ==="
|
|
97
|
+
puts "Ready to generate methods (requires running Ollama)."
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 07_error_handling.rb — Error classes and rescue patterns
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - The SelfAgency error hierarchy
|
|
8
|
+
# - Rescuing GenerationError, ValidationError, SecurityError
|
|
9
|
+
# - Calling _() before configure raises SelfAgency::Error
|
|
10
|
+
# - Defensive usage patterns
|
|
11
|
+
#
|
|
12
|
+
# This example does NOT require a running Ollama instance.
|
|
13
|
+
|
|
14
|
+
require_relative "../lib/self_agency"
|
|
15
|
+
|
|
16
|
+
puts "=== Error hierarchy ==="
|
|
17
|
+
puts <<~HIERARCHY
|
|
18
|
+
SelfAgency::Error < StandardError
|
|
19
|
+
SelfAgency::GenerationError < SelfAgency::Error
|
|
20
|
+
SelfAgency::ValidationError < SelfAgency::Error
|
|
21
|
+
SelfAgency::SecurityError < SelfAgency::Error
|
|
22
|
+
HIERARCHY
|
|
23
|
+
|
|
24
|
+
puts "Verification:"
|
|
25
|
+
puts " GenerationError < Error: #{SelfAgency::GenerationError < SelfAgency::Error}"
|
|
26
|
+
puts " ValidationError < Error: #{SelfAgency::ValidationError < SelfAgency::Error}"
|
|
27
|
+
puts " SecurityError < Error: #{SelfAgency::SecurityError < SelfAgency::Error}"
|
|
28
|
+
puts " Error < StandardError: #{SelfAgency::Error < StandardError}"
|
|
29
|
+
puts
|
|
30
|
+
|
|
31
|
+
# --- Calling _() before configure ---
|
|
32
|
+
puts "=== Calling _() before configure ==="
|
|
33
|
+
SelfAgency.reset!
|
|
34
|
+
|
|
35
|
+
class Widget
|
|
36
|
+
include SelfAgency
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
Widget.new._("a method")
|
|
41
|
+
rescue SelfAgency::Error => e
|
|
42
|
+
puts "Caught SelfAgency::Error: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
puts
|
|
45
|
+
|
|
46
|
+
# --- Validation errors via private helpers ---
|
|
47
|
+
puts "=== ValidationError examples ==="
|
|
48
|
+
SelfAgency.configure do |config|
|
|
49
|
+
config.provider = :ollama
|
|
50
|
+
config.model = "qwen3-coder:30b"
|
|
51
|
+
config.api_base = "http://localhost:11434/v1"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
widget = Widget.new
|
|
55
|
+
|
|
56
|
+
# Empty code
|
|
57
|
+
begin
|
|
58
|
+
widget.send(:self_agency_validate!, "")
|
|
59
|
+
rescue SelfAgency::ValidationError => e
|
|
60
|
+
puts "Empty code: #{e.class} — #{e.message}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Missing def...end
|
|
64
|
+
begin
|
|
65
|
+
widget.send(:self_agency_validate!, "puts 'hello'")
|
|
66
|
+
rescue SelfAgency::ValidationError => e
|
|
67
|
+
puts "No def...end: #{e.class} — #{e.message}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Syntax error
|
|
71
|
+
begin
|
|
72
|
+
widget.send(:self_agency_validate!, "def broken\n if true\nend")
|
|
73
|
+
rescue SelfAgency::ValidationError => e
|
|
74
|
+
puts "Syntax error: #{e.class} — #{e.message}"
|
|
75
|
+
end
|
|
76
|
+
puts
|
|
77
|
+
|
|
78
|
+
# --- Security errors ---
|
|
79
|
+
puts "=== SecurityError examples ==="
|
|
80
|
+
|
|
81
|
+
dangerous_samples = {
|
|
82
|
+
"system call" => "def hack\n system('ls')\nend",
|
|
83
|
+
"File access" => "def hack\n File.read('/etc/passwd')\nend",
|
|
84
|
+
"eval usage" => "def hack\n eval('1+1')\nend",
|
|
85
|
+
"require" => "def hack\n require 'socket'\nend",
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
dangerous_samples.each do |label, code|
|
|
89
|
+
begin
|
|
90
|
+
widget.send(:self_agency_validate!, code)
|
|
91
|
+
rescue SelfAgency::SecurityError => e
|
|
92
|
+
puts "#{label.ljust(14)}: #{e.class} — #{e.message}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
puts
|
|
96
|
+
|
|
97
|
+
# --- Catching all SelfAgency errors with a single rescue ---
|
|
98
|
+
puts "=== Catching all errors with rescue SelfAgency::Error ==="
|
|
99
|
+
begin
|
|
100
|
+
raise SelfAgency::SecurityError, "example security violation"
|
|
101
|
+
rescue SelfAgency::Error => e
|
|
102
|
+
puts "Caught via base class: #{e.class} — #{e.message}"
|
|
103
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 08_class_context.rb — Instance variable and method awareness
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - The LLM receives class context (class name, ivars, public methods)
|
|
8
|
+
# - Generated methods can reference existing instance variables
|
|
9
|
+
# - Class introspection drives smarter code generation
|
|
10
|
+
#
|
|
11
|
+
# Requires a running Ollama instance with the configured model.
|
|
12
|
+
|
|
13
|
+
require_relative "lib/setup"
|
|
14
|
+
|
|
15
|
+
class BankAccount
|
|
16
|
+
include SelfAgency
|
|
17
|
+
|
|
18
|
+
attr_reader :owner, :balance
|
|
19
|
+
|
|
20
|
+
def initialize(owner, balance)
|
|
21
|
+
@owner = owner
|
|
22
|
+
@balance = balance
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def deposit(amount)
|
|
26
|
+
@balance += amount
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
account = BankAccount.new("Alice", 1000)
|
|
31
|
+
|
|
32
|
+
# Show what the LLM will see as context
|
|
33
|
+
vars = account.send(:self_agency_generation_vars)
|
|
34
|
+
puts "=== Class context sent to LLM ==="
|
|
35
|
+
puts " class_name: #{vars[:class_name]}"
|
|
36
|
+
puts " ivars: #{vars[:ivars]}"
|
|
37
|
+
puts " methods: #{vars[:methods]}"
|
|
38
|
+
puts
|
|
39
|
+
|
|
40
|
+
puts "=== Generating a method that uses existing instance variables ==="
|
|
41
|
+
account._(
|
|
42
|
+
"an instance method named 'summary' that returns a string like " \
|
|
43
|
+
"'Account for <owner>: $<balance>' using the @owner and @balance instance variables"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
puts account.summary
|
|
47
|
+
puts
|
|
48
|
+
|
|
49
|
+
puts "=== Generating a method that complements existing methods ==="
|
|
50
|
+
account._(
|
|
51
|
+
"an instance method named 'withdraw' that accepts an amount, " \
|
|
52
|
+
"raises a RuntimeError with 'Insufficient funds' if amount > @balance, " \
|
|
53
|
+
"otherwise decreases @balance by amount and returns the new balance"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
puts "Balance before withdraw: $#{account.balance}"
|
|
57
|
+
puts "Withdrawing $200..."
|
|
58
|
+
puts "Balance after withdraw: $#{account.withdraw(200)}"
|
|
59
|
+
|
|
60
|
+
begin
|
|
61
|
+
account.withdraw(999_999)
|
|
62
|
+
rescue RuntimeError => e
|
|
63
|
+
puts "Caught expected error: #{e.message}"
|
|
64
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 09_method_override.rb — Overriding existing methods
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Defining a class with an existing method
|
|
8
|
+
# - Generating a method with the same name overrides the original
|
|
9
|
+
# - Prepend-based MRO means the generated method takes priority
|
|
10
|
+
# - _source_for returns the new (generated) source
|
|
11
|
+
#
|
|
12
|
+
# Requires a running Ollama instance with the configured model.
|
|
13
|
+
|
|
14
|
+
require_relative "lib/setup"
|
|
15
|
+
|
|
16
|
+
class Formatter
|
|
17
|
+
include SelfAgency
|
|
18
|
+
|
|
19
|
+
def greet(name)
|
|
20
|
+
"Hello, #{name}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
fmt = Formatter.new
|
|
25
|
+
|
|
26
|
+
puts "=== Before override ==="
|
|
27
|
+
puts "fmt.greet('World') = #{fmt.greet('World')}"
|
|
28
|
+
puts
|
|
29
|
+
puts "Source (from file):"
|
|
30
|
+
puts fmt._source_for(:greet)
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
puts "=== Generating a replacement method with the same name ==="
|
|
34
|
+
fmt._(
|
|
35
|
+
"an instance method named 'greet' that accepts a name parameter " \
|
|
36
|
+
"and returns the string 'Greetings, <name>! Welcome aboard.' " \
|
|
37
|
+
"(this should override the existing greet method)"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
puts
|
|
41
|
+
puts "=== After override ==="
|
|
42
|
+
puts "fmt.greet('World') = #{fmt.greet('World')}"
|
|
43
|
+
puts
|
|
44
|
+
puts "Source (now LLM-generated):"
|
|
45
|
+
puts fmt._source_for(:greet)
|
|
46
|
+
puts
|
|
47
|
+
|
|
48
|
+
puts "=== MRO shows prepended module ==="
|
|
49
|
+
puts "Formatter ancestors:"
|
|
50
|
+
Formatter.ancestors.first(5).each_with_index do |ancestor, i|
|
|
51
|
+
puts " #{i}. #{ancestor}"
|
|
52
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# 10_full_workflow.rb — Complete real-world workflow
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Building a domain class with multiple generated methods
|
|
8
|
+
# - Using different scopes (instance, singleton, class)
|
|
9
|
+
# - Lifecycle hook to save generated code to files
|
|
10
|
+
# - Source inspection for all generated methods
|
|
11
|
+
# - End-to-end usage combining all features
|
|
12
|
+
#
|
|
13
|
+
# Requires a running Ollama instance with the configured model.
|
|
14
|
+
|
|
15
|
+
require "fileutils"
|
|
16
|
+
require_relative "lib/setup"
|
|
17
|
+
|
|
18
|
+
# Output directory for persisted generated code
|
|
19
|
+
GENERATED_DIR = File.join(__dir__, "generated")
|
|
20
|
+
FileUtils.mkdir_p(GENERATED_DIR)
|
|
21
|
+
|
|
22
|
+
class StatisticsCalculator
|
|
23
|
+
include SelfAgency
|
|
24
|
+
|
|
25
|
+
attr_reader :data
|
|
26
|
+
|
|
27
|
+
def initialize(data = [])
|
|
28
|
+
@data = data.dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def on_method_generated(method_name, scope, code)
|
|
32
|
+
filename = "#{method_name}_#{scope}.rb"
|
|
33
|
+
filepath = File.join(GENERATED_DIR, filename)
|
|
34
|
+
File.write(filepath, code)
|
|
35
|
+
puts " [saved] #{filepath}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
puts "=== Building a StatisticsCalculator ==="
|
|
40
|
+
calc = StatisticsCalculator.new([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
|
41
|
+
puts "Data: #{calc.data.inspect}"
|
|
42
|
+
puts
|
|
43
|
+
|
|
44
|
+
# --- Instance methods ---
|
|
45
|
+
puts "--- Generating instance methods ---"
|
|
46
|
+
|
|
47
|
+
calc._(
|
|
48
|
+
"an instance method named 'mean' that calculates and returns the " \
|
|
49
|
+
"arithmetic mean of the @data array as a Float"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
calc._(
|
|
53
|
+
"an instance method named 'median' that returns the median value " \
|
|
54
|
+
"of the @data array (sort the array, return the middle element for " \
|
|
55
|
+
"odd length, or average of two middle elements for even length) as a Float"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
calc._(
|
|
59
|
+
"an instance method named 'standard_deviation' that calculates and " \
|
|
60
|
+
"returns the population standard deviation of @data as a Float " \
|
|
61
|
+
"(square root of the mean of squared differences from the mean)"
|
|
62
|
+
)
|
|
63
|
+
puts
|
|
64
|
+
|
|
65
|
+
# --- Class method ---
|
|
66
|
+
puts "--- Generating a class method ---"
|
|
67
|
+
calc._(
|
|
68
|
+
"a class method named 'self.from_range' that accepts two integers " \
|
|
69
|
+
"(low, high) and returns a new StatisticsCalculator instance " \
|
|
70
|
+
"initialized with (low..high).to_a",
|
|
71
|
+
scope: :class
|
|
72
|
+
)
|
|
73
|
+
puts
|
|
74
|
+
|
|
75
|
+
# --- Singleton method ---
|
|
76
|
+
puts "--- Generating a singleton method ---"
|
|
77
|
+
calc._(
|
|
78
|
+
"an instance method named 'report' that returns a multi-line string " \
|
|
79
|
+
"with the data count (data.length), mean (call self.mean), " \
|
|
80
|
+
"median (call self.median), and standard_deviation (call self.standard_deviation)",
|
|
81
|
+
scope: :singleton
|
|
82
|
+
)
|
|
83
|
+
puts
|
|
84
|
+
|
|
85
|
+
# --- Use the generated methods ---
|
|
86
|
+
puts "=== Using generated methods ==="
|
|
87
|
+
puts "Mean: #{calc.mean}"
|
|
88
|
+
puts "Median: #{calc.median}"
|
|
89
|
+
puts "Standard deviation: #{calc.standard_deviation}"
|
|
90
|
+
puts
|
|
91
|
+
|
|
92
|
+
puts "=== Class method ==="
|
|
93
|
+
range_calc = StatisticsCalculator.from_range(1, 5)
|
|
94
|
+
puts "StatisticsCalculator.from_range(1, 5).data = #{range_calc.data.inspect}"
|
|
95
|
+
puts "Mean of 1..5: #{range_calc.mean}"
|
|
96
|
+
puts
|
|
97
|
+
|
|
98
|
+
puts "=== Singleton report (only on this instance) ==="
|
|
99
|
+
puts calc.report
|
|
100
|
+
puts
|
|
101
|
+
|
|
102
|
+
other = StatisticsCalculator.new([1, 2, 3])
|
|
103
|
+
puts "other.respond_to?(:report) = #{other.respond_to?(:report)} (singleton — not available)"
|
|
104
|
+
puts
|
|
105
|
+
|
|
106
|
+
# --- Inspect source ---
|
|
107
|
+
puts "=== Source inspection ==="
|
|
108
|
+
[:mean, :median, :standard_deviation].each do |name|
|
|
109
|
+
puts "--- #{name} ---"
|
|
110
|
+
puts calc._source_for(name)
|
|
111
|
+
puts
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# --- List saved files ---
|
|
115
|
+
puts "=== Saved generated code ==="
|
|
116
|
+
Dir.glob(File.join(GENERATED_DIR, "*.rb")).sort.each do |path|
|
|
117
|
+
puts " #{path}"
|
|
118
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "robot"
|
|
4
|
+
|
|
5
|
+
class Atlas < Robot
|
|
6
|
+
# Method named 'generate_weather_data' that takes no parameters. It must return an Array of 24 Hashes (one per hour, index 0..23). Each Hash has these keys (all Symbols): :hour => the integer hour (0..23), :temperature => a Float computed as 20.0 + 8.0 * Math.sin((hour - 6) * Math::PI / 12.0), :humidity => a Float computed as 60.0 + 20.0 * Math.cos((hour - 14) * Math::PI / 12.0), :wind_speed => a Float computed as 10.0 + 5.0 * Math.sin((hour * 7) * Math::PI / 24.0). Do NOT use random numbers. Use only the deterministic formulas above.
|
|
7
|
+
def generate_weather_data
|
|
8
|
+
weather_data = []
|
|
9
|
+
(0..23).each do |hour|
|
|
10
|
+
temperature = 20.0 + 8.0 * Math.sin(((hour - 6) * Math::PI) / 12.0)
|
|
11
|
+
humidity = 60.0 + 20.0 * Math.cos(((hour - 14) * Math::PI) / 12.0)
|
|
12
|
+
wind_speed = 10.0 + 5.0 * Math.sin(((hour * 7) * Math::PI) / 24.0)
|
|
13
|
+
weather_data << {
|
|
14
|
+
hour: hour,
|
|
15
|
+
temperature: temperature,
|
|
16
|
+
humidity: humidity,
|
|
17
|
+
wind_speed: wind_speed
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
weather_data
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Method named 'summarize_raw_data' that takes one parameter (data), an Array of Hashes as described above. It returns a Hash with: :readings_count => data.size, :raw_data => data, :source => "Atlas"
|
|
24
|
+
def summarize_raw_data(data)
|
|
25
|
+
{
|
|
26
|
+
readings_count: data.size,
|
|
27
|
+
raw_data: data,
|
|
28
|
+
source: "Atlas"
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "robot"
|
|
4
|
+
|
|
5
|
+
class Echo < Robot
|
|
6
|
+
# Return a formatted multi-line String report like: "=== Weather Report ===\n" + "Source: #{data[:source]} | Readings: #{data[:readings]}\n" + "Temperature: #{data[:avg_temp]}° (min: #{data[:min_temp]}°, max: #{data[:max_temp]}°) [#{data[:temperature_class]}]\n" + "Humidity: #{data[:avg_humidity]}% [#{data[:humidity_class]}]\n" + "Wind: #{data[:avg_wind]} km/h [#{data[:wind_class]}]\n" + "======================". Use string interpolation. Return the String, do not print it.
|
|
7
|
+
def weather_report
|
|
8
|
+
data = {
|
|
9
|
+
source: "Weather Station 42",
|
|
10
|
+
readings: "24 hr",
|
|
11
|
+
avg_temp: 22.5,
|
|
12
|
+
min_temp: 18.2,
|
|
13
|
+
max_temp: 28.7,
|
|
14
|
+
temperature_class: "Mild",
|
|
15
|
+
avg_humidity: 65,
|
|
16
|
+
humidity_class: "Moderate",
|
|
17
|
+
avg_wind: 12.3,
|
|
18
|
+
wind_class: "Light Breeze"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
report = "=== Weather Report ===\n"
|
|
22
|
+
report += "Source: #{data[:source]} | Readings: #{data[:readings]}\n"
|
|
23
|
+
report += "Temperature: #{data[:avg_temp]}° (min: #{data[:min_temp]}°, max: #{data[:max_temp]}°) [#{data[:temperature_class]}]\n"
|
|
24
|
+
report += "Humidity: #{data[:avg_humidity]}% [#{data[:humidity_class]}]\n"
|
|
25
|
+
report += "Wind: #{data[:avg_wind]} km/h [#{data[:wind_class]}]\n"
|
|
26
|
+
report += "======================"
|
|
27
|
+
|
|
28
|
+
report
|
|
29
|
+
end
|
|
30
|
+
end
|