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,100 @@
|
|
|
1
|
+
# Example 10: Full Workflow
|
|
2
|
+
|
|
3
|
+
A complete real-world workflow that combines all SelfAgency features: multiple scopes, lifecycle hooks, source inspection, and file persistence.
|
|
4
|
+
|
|
5
|
+
**Source:** `examples/10_full_workflow.rb`
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Builds a `StatisticsCalculator` class that:
|
|
10
|
+
|
|
11
|
+
1. Generates instance methods (`mean`, `median`, `standard_deviation`)
|
|
12
|
+
2. Generates a class method (`self.from_range`)
|
|
13
|
+
3. Generates a singleton method (`report`)
|
|
14
|
+
4. Persists all generated code to files via `on_method_generated`
|
|
15
|
+
5. Inspects source for all generated methods
|
|
16
|
+
|
|
17
|
+
## The Class
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
class StatisticsCalculator
|
|
21
|
+
include SelfAgency
|
|
22
|
+
|
|
23
|
+
attr_reader :data
|
|
24
|
+
|
|
25
|
+
def initialize(data = [])
|
|
26
|
+
@data = data.dup
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def on_method_generated(method_name, scope, code)
|
|
30
|
+
filename = "#{method_name}_#{scope}.rb"
|
|
31
|
+
filepath = File.join(GENERATED_DIR, filename)
|
|
32
|
+
File.write(filepath, code)
|
|
33
|
+
puts " [saved] #{filepath}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## What It Generates
|
|
39
|
+
|
|
40
|
+
### Instance Methods
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
calc = StatisticsCalculator.new([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
|
|
44
|
+
|
|
45
|
+
calc._("an instance method named 'mean' that calculates the arithmetic mean of @data as a Float")
|
|
46
|
+
calc._("an instance method named 'median' that returns the median value of @data as a Float")
|
|
47
|
+
calc._("an instance method named 'standard_deviation' that returns the population standard deviation of @data as a Float")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Class Method
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
calc._(
|
|
54
|
+
"a class method named 'self.from_range' that accepts (low, high) " \
|
|
55
|
+
"and returns a new StatisticsCalculator initialized with (low..high).to_a",
|
|
56
|
+
scope: :class
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
range_calc = StatisticsCalculator.from_range(1, 5)
|
|
60
|
+
range_calc.data #=> [1, 2, 3, 4, 5]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Singleton Method
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
calc._(
|
|
67
|
+
"a method named 'report' that returns a multi-line summary " \
|
|
68
|
+
"of count, mean, median, and standard_deviation",
|
|
69
|
+
scope: :singleton
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
puts calc.report
|
|
73
|
+
# Only available on this specific calc instance
|
|
74
|
+
other = StatisticsCalculator.new([1, 2, 3])
|
|
75
|
+
other.respond_to?(:report) #=> false
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Generated Files
|
|
79
|
+
|
|
80
|
+
The lifecycle hook saves each method to the `examples/generated/` directory:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
examples/generated/
|
|
84
|
+
mean_instance.rb
|
|
85
|
+
median_instance.rb
|
|
86
|
+
standard_deviation_instance.rb
|
|
87
|
+
from_range_class.rb
|
|
88
|
+
report_singleton.rb
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Source Inspection
|
|
92
|
+
|
|
93
|
+
After generation, the source for each method can be retrieved:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
[:mean, :median, :standard_deviation].each do |name|
|
|
97
|
+
puts "--- #{name} ---"
|
|
98
|
+
puts calc._source_for(name)
|
|
99
|
+
end
|
|
100
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
SelfAgency ships with 12 examples that progressively demonstrate its features. All examples live in the `examples/` directory.
|
|
4
|
+
|
|
5
|
+
## Running Examples
|
|
6
|
+
|
|
7
|
+
Most examples require a running LLM. The default configuration targets Ollama:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Start Ollama
|
|
11
|
+
ollama serve
|
|
12
|
+
|
|
13
|
+
# Run an example
|
|
14
|
+
bundle exec ruby examples/01_basic_usage.rb
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Examples 06 and 07 run offline (no LLM required) as they only exercise configuration and error handling.
|
|
18
|
+
|
|
19
|
+
## Example Index
|
|
20
|
+
|
|
21
|
+
| # | Name | Features | LLM Required |
|
|
22
|
+
|---|------|----------|:---:|
|
|
23
|
+
| 01 | [Basic Usage](basic-examples.md#01-basic-usage) | `_()`, single method generation | Yes |
|
|
24
|
+
| 02 | [Multiple Methods](basic-examples.md#02-multiple-methods) | Multiple methods from one call | Yes |
|
|
25
|
+
| 03 | [Scopes](basic-examples.md#03-scopes) | Instance, singleton, class scopes | Yes |
|
|
26
|
+
| 04 | [Source Inspection](basic-examples.md#04-source-inspection) | `_source_for`, file fallback | Yes |
|
|
27
|
+
| 05 | [Lifecycle Hook](basic-examples.md#05-lifecycle-hook) | `on_method_generated` | Yes |
|
|
28
|
+
| 06 | [Configuration](basic-examples.md#06-configuration) | All config options, `reset!`, `ensure_configured!` | No |
|
|
29
|
+
| 07 | [Error Handling](basic-examples.md#07-error-handling) | Error hierarchy, rescue patterns | No |
|
|
30
|
+
| 08 | [Class Context](basic-examples.md#08-class-context) | Instance variables, method awareness | Yes |
|
|
31
|
+
| 09 | [Method Override](basic-examples.md#09-method-override) | Replacing existing methods | Yes |
|
|
32
|
+
| 10 | [Full Workflow](full-workflow.md) | Complete real-world workflow | Yes |
|
|
33
|
+
| 11 | [Collaborative Robots](collaborative-robots.md) | Multi-robot pipeline, message bus | Yes |
|
|
34
|
+
| 12 | [Autonomous Robots](autonomous-robots.md) | Three-layer LLM, self-repair | Yes |
|
|
35
|
+
|
|
36
|
+
Examples 01--09 are covered in [Basic Examples](basic-examples.md). Examples 10--12 each have their own deep-dive page.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- Ruby >= 3.2.0
|
|
6
|
+
- An LLM provider (Ollama recommended for local development)
|
|
7
|
+
|
|
8
|
+
## Add to Your Project
|
|
9
|
+
|
|
10
|
+
Add SelfAgency to your Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem "self_agency"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then install:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install directly:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gem install self_agency
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Dependencies
|
|
29
|
+
|
|
30
|
+
SelfAgency depends on:
|
|
31
|
+
|
|
32
|
+
| Gem | Purpose |
|
|
33
|
+
|-----|---------|
|
|
34
|
+
| `ruby_llm` | LLM provider communication |
|
|
35
|
+
| `ruby_llm-template` | ERB prompt template management |
|
|
36
|
+
| `method_source` | Source code retrieval for file-defined methods |
|
|
37
|
+
|
|
38
|
+
These are installed automatically when you install the gem.
|
|
39
|
+
|
|
40
|
+
## LLM Provider Setup
|
|
41
|
+
|
|
42
|
+
SelfAgency uses [ruby_llm](https://github.com/crmne/ruby_llm) under the hood, which supports multiple providers. The default configuration targets a local Ollama instance.
|
|
43
|
+
|
|
44
|
+
### Ollama (Default)
|
|
45
|
+
|
|
46
|
+
Install Ollama and pull a model:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Install Ollama (macOS)
|
|
50
|
+
brew install ollama
|
|
51
|
+
|
|
52
|
+
# Start the server
|
|
53
|
+
ollama serve
|
|
54
|
+
|
|
55
|
+
# Pull a model
|
|
56
|
+
ollama pull qwen3-coder:30b
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Other Providers
|
|
60
|
+
|
|
61
|
+
Any provider supported by ruby_llm works. Set the `provider`, `model`, and `api_base` in your configuration:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
SelfAgency.configure do |config|
|
|
65
|
+
config.provider = :openai
|
|
66
|
+
config.model = "gpt-4o"
|
|
67
|
+
config.api_base = "https://api.openai.com/v1"
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
See [Configuration](../guide/configuration.md) for all options.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Quick Start
|
|
2
|
+
|
|
3
|
+
This guide walks through a complete example from configuration to using generated methods.
|
|
4
|
+
|
|
5
|
+
## 1. Configure SelfAgency
|
|
6
|
+
|
|
7
|
+
Before generating any methods, call `SelfAgency.configure`:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require "self_agency"
|
|
11
|
+
|
|
12
|
+
SelfAgency.configure do |config|
|
|
13
|
+
config.provider = :ollama
|
|
14
|
+
config.model = "qwen3-coder:30b"
|
|
15
|
+
config.api_base = "http://localhost:11434/v1"
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Configuration is mandatory. Calling `_()` before `configure` raises `SelfAgency::Error`.
|
|
20
|
+
|
|
21
|
+
## 2. Include the Module
|
|
22
|
+
|
|
23
|
+
Add `include SelfAgency` to any class:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
class Calculator
|
|
27
|
+
include SelfAgency
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
calc = Calculator.new
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 3. Generate a Method
|
|
34
|
+
|
|
35
|
+
Call `_()` with a natural language description:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
names = calc._("an instance method named 'add' that accepts two integer parameters a and b, and returns their sum")
|
|
39
|
+
#=> [:add]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`_()` always returns an Array of Symbol method names.
|
|
43
|
+
|
|
44
|
+
## 4. Use the Method
|
|
45
|
+
|
|
46
|
+
The generated method is available immediately:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
calc.add(3, 7) #=> 10
|
|
50
|
+
calc.add(-1, 1) #=> 0
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Instance methods (the default scope) are available on all instances of the class:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
other = Calculator.new
|
|
57
|
+
other.add(100, 200) #=> 300
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 5. View the Source
|
|
61
|
+
|
|
62
|
+
Inspect what the LLM generated:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
puts calc._source_for(:add)
|
|
66
|
+
# >> # an instance method named 'add' that accepts two integer
|
|
67
|
+
# >> # parameters a and b, and returns their sum
|
|
68
|
+
# >> def add(a, b)
|
|
69
|
+
# >> a + b
|
|
70
|
+
# >> end
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 6. Generate Multiple Methods
|
|
74
|
+
|
|
75
|
+
A single `_()` call can produce several methods:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
names = calc._(
|
|
79
|
+
"create add, subtract, multiply, and divide methods for two integers"
|
|
80
|
+
)
|
|
81
|
+
#=> [:add, :subtract, :multiply, :divide]
|
|
82
|
+
|
|
83
|
+
calc.subtract(10, 3) #=> 7
|
|
84
|
+
calc.multiply(4, 5) #=> 20
|
|
85
|
+
calc.divide(10, 3) #=> 3.333...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Next Steps
|
|
89
|
+
|
|
90
|
+
- [How to Use SelfAgency](../guide/how-to-use.md) -- Where SelfAgency fits in your design workflow
|
|
91
|
+
- [Scopes](../guide/scopes.md) -- Generate singleton and class methods
|
|
92
|
+
- [Source Inspection](../guide/source-inspection.md) -- View and retrieve generated source
|
|
93
|
+
- [Saving Methods](../guide/saving-methods.md) -- Persist generated methods to files
|
|
94
|
+
- [Configuration](../guide/configuration.md) -- All configuration options
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
SelfAgency must be configured before generating any methods. Call `SelfAgency.configure` with a block:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
SelfAgency.configure do |config|
|
|
7
|
+
config.provider = :ollama
|
|
8
|
+
config.model = "qwen3-coder:30b"
|
|
9
|
+
config.api_base = "http://localhost:11434/v1"
|
|
10
|
+
end
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Options
|
|
14
|
+
|
|
15
|
+
| Option | Type | Default | Description |
|
|
16
|
+
|--------|------|---------|-------------|
|
|
17
|
+
| `provider` | `Symbol` | `:ollama` | RubyLLM provider name |
|
|
18
|
+
| `model` | `String` | `"qwen3-coder:30b"` | LLM model identifier |
|
|
19
|
+
| `api_base` | `String` | `"http://localhost:11434/v1"` | Provider API endpoint |
|
|
20
|
+
| `request_timeout` | `Integer` | `30` | Request timeout in seconds |
|
|
21
|
+
| `max_retries` | `Integer` | `1` | Number of retries on failure |
|
|
22
|
+
| `retry_interval` | `Float` | `0.5` | Seconds between retries |
|
|
23
|
+
| `template_directory` | `String` | `lib/self_agency/prompts` | Path to ERB prompt templates |
|
|
24
|
+
| `generation_retries` | `Integer` | `3` | Max retry attempts when validation or security checks fail |
|
|
25
|
+
| `logger` | `Proc`, `Logger`, or `nil` | `nil` | Logger for pipeline events (see [Logging](#logging) below) |
|
|
26
|
+
|
|
27
|
+
## Configuration Is Mandatory
|
|
28
|
+
|
|
29
|
+
Calling `_()` before `configure` raises `SelfAgency::Error`:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
SelfAgency.reset!
|
|
33
|
+
|
|
34
|
+
class Widget
|
|
35
|
+
include SelfAgency
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Widget.new._("a method")
|
|
39
|
+
#=> SelfAgency::Error: SelfAgency.configure has not been called
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Applying Configuration
|
|
43
|
+
|
|
44
|
+
`SelfAgency.configure` delegates to `RubyLLM.configure` and `RubyLLM::Template.configure` under the hood:
|
|
45
|
+
|
|
46
|
+
- `provider` + `api_base` set the provider-specific API base on RubyLLM. The `api_base` value is assigned to the RubyLLM config key `<provider>_api_base`. For example, `:openai` maps to `openai_api_base`, `:ollama` maps to `ollama_api_base`.
|
|
47
|
+
- `request_timeout`, `max_retries`, and `retry_interval` map directly to RubyLLM settings
|
|
48
|
+
- `template_directory` is passed to `RubyLLM::Template`
|
|
49
|
+
|
|
50
|
+
## Resetting Configuration
|
|
51
|
+
|
|
52
|
+
`SelfAgency.reset!` restores all defaults and marks the gem as unconfigured:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
SelfAgency.reset!
|
|
56
|
+
|
|
57
|
+
SelfAgency.ensure_configured!
|
|
58
|
+
#=> SelfAgency::Error: SelfAgency.configure has not been called
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Checking Configuration
|
|
62
|
+
|
|
63
|
+
`SelfAgency.ensure_configured!` raises if `configure` has not been called:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
SelfAgency.ensure_configured! # raises or succeeds silently
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Logging
|
|
70
|
+
|
|
71
|
+
Set `logger` to observe each stage of the pipeline. It accepts either a callable (receives `stage` and `message`) or a `Logger`-compatible object (uses `.debug`):
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# Callable logger
|
|
75
|
+
SelfAgency.configure do |config|
|
|
76
|
+
config.logger = ->(stage, message) { puts "[#{stage}] #{message}" }
|
|
77
|
+
# ...
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Standard library Logger
|
|
81
|
+
require "logger"
|
|
82
|
+
SelfAgency.configure do |config|
|
|
83
|
+
config.logger = Logger.new($stdout)
|
|
84
|
+
# ...
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Logged stages: `:shape`, `:generate`, `:validate`, `:retry`, `:complete`.
|
|
89
|
+
|
|
90
|
+
## Example: Custom Timeouts
|
|
91
|
+
|
|
92
|
+
For complex method generation that takes longer:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
SelfAgency.configure do |config|
|
|
96
|
+
config.provider = :ollama
|
|
97
|
+
config.model = "qwen3-coder:30b"
|
|
98
|
+
config.api_base = "http://localhost:11434/v1"
|
|
99
|
+
config.request_timeout = 120
|
|
100
|
+
config.max_retries = 3
|
|
101
|
+
config.retry_interval = 1.0
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Example: OpenAI
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
SelfAgency.configure do |config|
|
|
109
|
+
config.provider = :openai
|
|
110
|
+
config.model = "gpt-4o"
|
|
111
|
+
config.api_base = "https://api.openai.com/v1"
|
|
112
|
+
end
|
|
113
|
+
```
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Generating Methods
|
|
2
|
+
|
|
3
|
+
The `_()` method is the core API of SelfAgency. It takes a natural language description and generates one or more Ruby methods at runtime.
|
|
4
|
+
|
|
5
|
+
## Single Method
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
names = calc._("an instance method named 'add' that accepts two integer parameters a and b, and returns their sum")
|
|
9
|
+
#=> [:add]
|
|
10
|
+
|
|
11
|
+
calc.add(3, 7) #=> 10
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Multiple Methods
|
|
15
|
+
|
|
16
|
+
Describe several methods in one call:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
names = calc._(
|
|
20
|
+
"create four instance methods: " \
|
|
21
|
+
"'add(a, b)' returns a + b, " \
|
|
22
|
+
"'subtract(a, b)' returns a - b, " \
|
|
23
|
+
"'multiply(a, b)' returns a * b, " \
|
|
24
|
+
"'divide(a, b)' returns a.to_f / b (raises ZeroDivisionError if b is zero)"
|
|
25
|
+
)
|
|
26
|
+
#=> [:add, :subtract, :multiply, :divide]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`_()` always returns an Array of Symbol method names, even for a single method.
|
|
30
|
+
|
|
31
|
+
## Return Value
|
|
32
|
+
|
|
33
|
+
The return value is always an `Array<Symbol>` containing the names of all methods defined by that call:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
method_names = calc._("a method to double a number")
|
|
37
|
+
method_names #=> [:double]
|
|
38
|
+
method_names.class #=> Array
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Class Context
|
|
42
|
+
|
|
43
|
+
The LLM receives introspection context about your class when generating methods:
|
|
44
|
+
|
|
45
|
+
- **Class name** -- So it can reference the correct class
|
|
46
|
+
- **Instance variables** -- Current instance variables on the calling object
|
|
47
|
+
- **Public methods** -- Methods defined directly on your class (excludes inherited `Object` methods)
|
|
48
|
+
|
|
49
|
+
This means generated methods can work with your class's existing state:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
class BankAccount
|
|
53
|
+
include SelfAgency
|
|
54
|
+
|
|
55
|
+
attr_reader :owner, :balance
|
|
56
|
+
|
|
57
|
+
def initialize(owner, balance)
|
|
58
|
+
@owner = owner
|
|
59
|
+
@balance = balance
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
account = BankAccount.new("Alice", 1000)
|
|
64
|
+
account._(
|
|
65
|
+
"an instance method named 'summary' that returns a string like " \
|
|
66
|
+
"'Account for <owner>: $<balance>' using the @owner and @balance instance variables"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
account.summary #=> "Account for Alice: $1000"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Method Overrides
|
|
73
|
+
|
|
74
|
+
Generating a method with the same name as an existing method overrides it. SelfAgency uses `Module#prepend`, so the generated method takes priority in Ruby's method resolution order (MRO):
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
class Formatter
|
|
78
|
+
include SelfAgency
|
|
79
|
+
|
|
80
|
+
def greet(name)
|
|
81
|
+
"Hello, #{name}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
fmt = Formatter.new
|
|
86
|
+
fmt.greet("World") #=> "Hello, World"
|
|
87
|
+
|
|
88
|
+
fmt._(
|
|
89
|
+
"an instance method named 'greet' that accepts a name parameter " \
|
|
90
|
+
"and returns 'Greetings, <name>! Welcome aboard.'"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
fmt.greet("World") #=> "Greetings, World! Welcome aboard."
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Automatic Retries
|
|
97
|
+
|
|
98
|
+
When validation or security checks fail, SelfAgency automatically retries code generation. On each retry, the previous error message and failed code are fed back to the LLM so it can self-correct.
|
|
99
|
+
|
|
100
|
+
The number of attempts is controlled by `generation_retries` (default: `3`):
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
SelfAgency.configure do |config|
|
|
104
|
+
config.generation_retries = 5 # try up to 5 times
|
|
105
|
+
# ...
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
If all attempts fail, the last `ValidationError` or `SecurityError` is raised. The error includes an `attempt` attribute indicating which attempt produced it.
|
|
110
|
+
|
|
111
|
+
!!! note
|
|
112
|
+
Only validation and security failures trigger retries. If the LLM returns `nil` (a `GenerationError`), the error is raised immediately with no retry.
|
|
113
|
+
|
|
114
|
+
## Error Handling
|
|
115
|
+
|
|
116
|
+
`_()` can raise three types of errors. Each error class carries additional attributes for programmatic inspection:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
begin
|
|
120
|
+
calc._("a method to do something")
|
|
121
|
+
rescue SelfAgency::GenerationError => e
|
|
122
|
+
e.stage #=> :shape or :generate
|
|
123
|
+
e.attempt #=> Integer (attempt number, if during retry)
|
|
124
|
+
rescue SelfAgency::ValidationError => e
|
|
125
|
+
e.generated_code #=> String (the code that failed)
|
|
126
|
+
e.attempt #=> Integer (attempt number, if during retry)
|
|
127
|
+
rescue SelfAgency::SecurityError => e
|
|
128
|
+
e.matched_pattern #=> String (the pattern that triggered the error)
|
|
129
|
+
e.generated_code #=> String (the code that failed)
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Or catch all SelfAgency errors at once:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
begin
|
|
137
|
+
calc._("a method to do something")
|
|
138
|
+
rescue SelfAgency::Error => e
|
|
139
|
+
puts "Generation failed: #{e.message}"
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
!!! note
|
|
144
|
+
LLM communication failures (network errors, timeouts, API errors) are wrapped and re-raised as `GenerationError` with a message like `"LLM request failed (Faraday::TimeoutError: timeout)"`. The original exception class and message are preserved in the error message. If generation consistently fails, verify your LLM provider is running and reachable.
|
|
145
|
+
|
|
146
|
+
See [Errors](../api/errors.md) for the full error hierarchy.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# How to Use SelfAgency
|
|
2
|
+
|
|
3
|
+
## Two Schools of Software Design
|
|
4
|
+
|
|
5
|
+
Software architects have long worked with two complementary approaches to building systems. The names change over the decades — structured vs. exploratory, waterfall vs. iterative, specification-first vs. prototype-first — but the underlying concepts remain the same: **Top-Down** and **Bottom-Up**.
|
|
6
|
+
|
|
7
|
+
### Top-Down Design
|
|
8
|
+
|
|
9
|
+
Top-Down design begins at the highest level of abstraction. You define the overall system architecture, decompose it into subsystems, decompose those into modules, and continue refining until you arrive at the concrete methods and functions that do the actual work.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
System
|
|
13
|
+
└── Subsystem A
|
|
14
|
+
│ └── Module A1
|
|
15
|
+
│ │ └── method_x
|
|
16
|
+
│ │ └── method_y
|
|
17
|
+
│ └── Module A2
|
|
18
|
+
│ └── method_z
|
|
19
|
+
└── Subsystem B
|
|
20
|
+
└── ...
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This approach excels at maintaining coherence across large systems. You know the shape of the whole before you write a line of implementation code.
|
|
24
|
+
|
|
25
|
+
### Bottom-Up Design
|
|
26
|
+
|
|
27
|
+
Bottom-Up design starts at the opposite end. You write the lowest-level functions first — the critical algorithms, the core transformations, the essential business logic — and then compose those pieces into higher-level abstractions.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
method_x + method_y → Module A1
|
|
31
|
+
Module A1 + Module A2 → Subsystem A
|
|
32
|
+
Subsystem A + Subsystem B → System
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This approach excels at producing battle-tested components. Each building block is proven through direct experimentation before it is wired into a larger structure.
|
|
36
|
+
|
|
37
|
+
### The Pragmatic Architect
|
|
38
|
+
|
|
39
|
+
A skilled architect does not choose one approach exclusively. The most effective strategy combines both:
|
|
40
|
+
|
|
41
|
+
- **Top-Down** to establish the overall architecture, define interfaces, and ensure the system hangs together coherently.
|
|
42
|
+
- **Bottom-Up** to experiment with the critical pieces, validate assumptions, and build confidence that the low-level logic actually works.
|
|
43
|
+
|
|
44
|
+
The two approaches meet in the middle. Top-Down gives you the map. Bottom-Up gives you proven ground truth.
|
|
45
|
+
|
|
46
|
+
## Where SelfAgency Fits
|
|
47
|
+
|
|
48
|
+
SelfAgency is a Bottom-Up experimentation tool. It lives in the space where you are exploring an idea, testing a hypothesis, or prototyping a piece of business logic — before you commit to a full architectural design.
|
|
49
|
+
|
|
50
|
+
The typical workflow looks like this:
|
|
51
|
+
|
|
52
|
+
### 1. Start an IRB Session
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bin/console
|
|
56
|
+
# or
|
|
57
|
+
bundle exec irb -r self_agency
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Configure Your LLM
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
SelfAgency.configure do |config|
|
|
64
|
+
config.provider = :ollama
|
|
65
|
+
config.model = "qwen3-coder:30b"
|
|
66
|
+
config.api_base = "http://localhost:11434/v1"
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. Sketch a Class Around Your Domain
|
|
71
|
+
|
|
72
|
+
You do not need a complete design. Start with the concept:
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
class PricingEngine
|
|
76
|
+
include SelfAgency
|
|
77
|
+
|
|
78
|
+
def initialize(base_rate:, discount_tiers:)
|
|
79
|
+
@base_rate = base_rate
|
|
80
|
+
@discount_tiers = discount_tiers
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
engine = PricingEngine.new(
|
|
85
|
+
base_rate: 100.0,
|
|
86
|
+
discount_tiers: { silver: 0.05, gold: 0.10, platinum: 0.15 }
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 4. Generate and Experiment
|
|
91
|
+
|
|
92
|
+
Describe the behavior you need in plain language. Try it. Refine the description. Try again.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
engine._("calculate the discounted price for a given tier and quantity")
|
|
96
|
+
engine.calculate_discounted_price(:gold, 5)
|
|
97
|
+
#=> 450.0
|
|
98
|
+
|
|
99
|
+
# Not quite what you wanted? Inspect the source:
|
|
100
|
+
puts engine._source_for(:calculate_discounted_price)
|
|
101
|
+
|
|
102
|
+
# Refine and regenerate:
|
|
103
|
+
engine._(
|
|
104
|
+
"calculate the discounted price: multiply base_rate by quantity, " \
|
|
105
|
+
"then subtract the tier's discount percentage from discount_tiers. " \
|
|
106
|
+
"Raise ArgumentError if the tier is not recognized."
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Each iteration gives you a working method you can call immediately. You see the generated source, test it with real inputs, and adjust your description until the logic is right.
|
|
111
|
+
|
|
112
|
+
### 5. Save What Works
|
|
113
|
+
|
|
114
|
+
Once a method behaves correctly, persist it:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
engine._save!(:calculate_discounted_price)
|
|
118
|
+
# Writes the method source to a file you can incorporate into your codebase
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 6. Build Up
|
|
122
|
+
|
|
123
|
+
With proven components in hand, you can now make informed architectural decisions. You know what the critical methods look like, what their interfaces are, and how they behave. Wire them into your Top-Down design with confidence.
|
|
124
|
+
|
|
125
|
+
## The Experimentation Loop
|
|
126
|
+
|
|
127
|
+
The core value of SelfAgency is shortening the feedback loop during Bottom-Up exploration:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
Describe → Generate → Test → Inspect → Refine
|
|
131
|
+
↑ │
|
|
132
|
+
└──────────────────────────────────────────────┘
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
You stay in IRB the entire time. No file switching, no boilerplate, no deploy cycle. Just describe what you need, see if it works, and iterate until it does.
|
|
136
|
+
|
|
137
|
+
This makes SelfAgency particularly useful for:
|
|
138
|
+
|
|
139
|
+
- **Prototyping business rules** — "calculate the late fee given these conditions"
|
|
140
|
+
- **Exploring algorithms** — "sort these records by weighted score using these criteria"
|
|
141
|
+
- **Validating data transformations** — "parse this CSV row into a normalized hash"
|
|
142
|
+
- **Building up a utility class** — generate methods one at a time, test each, save the keepers
|
|
143
|
+
|
|
144
|
+
When you are done experimenting, you have real, tested Ruby methods — not pseudocode or diagrams. Those methods become the foundation your architecture is built on.
|