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,45 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
1. Fork the repository
|
|
6
|
+
2. Clone your fork
|
|
7
|
+
3. Install dependencies: `bin/setup`
|
|
8
|
+
4. Create a feature branch: `git checkout -b my-feature`
|
|
9
|
+
5. Make your changes
|
|
10
|
+
6. Run the tests: `rake test`
|
|
11
|
+
7. Commit and push
|
|
12
|
+
8. Open a pull request
|
|
13
|
+
|
|
14
|
+
## Guidelines
|
|
15
|
+
|
|
16
|
+
### Code Style
|
|
17
|
+
|
|
18
|
+
- Follow existing patterns in the codebase
|
|
19
|
+
- All private helpers are prefixed `self_agency_` to avoid name collisions with host classes
|
|
20
|
+
- Keep the gem's runtime dependency count minimal
|
|
21
|
+
|
|
22
|
+
### Testing
|
|
23
|
+
|
|
24
|
+
- All tests must pass offline (no LLM connection required)
|
|
25
|
+
- Add tests for new features or bug fixes
|
|
26
|
+
- Test validation, sanitization, and security patterns directly
|
|
27
|
+
|
|
28
|
+
### Security
|
|
29
|
+
|
|
30
|
+
- Any changes to `DANGEROUS_PATTERNS` or `Sandbox` must be carefully reviewed
|
|
31
|
+
- New patterns should have corresponding test cases
|
|
32
|
+
- The two-layer security model (static + runtime) should be maintained
|
|
33
|
+
|
|
34
|
+
### Documentation
|
|
35
|
+
|
|
36
|
+
- Update relevant docs pages for user-facing changes
|
|
37
|
+
- Run `mkdocs serve` to preview documentation changes locally
|
|
38
|
+
|
|
39
|
+
## Reporting Issues
|
|
40
|
+
|
|
41
|
+
Report bugs and feature requests on [GitHub Issues](https://github.com/madbomber/self_agency/issues).
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
By contributing, you agree that your contributions will be licensed under the MIT License.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Development Setup
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Ruby >= 3.2.0
|
|
6
|
+
- Bundler
|
|
7
|
+
|
|
8
|
+
## Getting Started
|
|
9
|
+
|
|
10
|
+
Clone the repository and install dependencies:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
git clone https://github.com/madbomber/self_agency.git
|
|
14
|
+
cd self_agency
|
|
15
|
+
bin/setup
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`bin/setup` installs gem dependencies via Bundler.
|
|
19
|
+
|
|
20
|
+
## Dependencies
|
|
21
|
+
|
|
22
|
+
### Runtime
|
|
23
|
+
|
|
24
|
+
| Gem | Purpose |
|
|
25
|
+
|-----|---------|
|
|
26
|
+
| `ruby_llm` | LLM provider communication |
|
|
27
|
+
| `ruby_llm-template` | ERB prompt template management |
|
|
28
|
+
| `method_source` | Source code retrieval for file-defined methods |
|
|
29
|
+
|
|
30
|
+
### Development
|
|
31
|
+
|
|
32
|
+
| Gem | Purpose |
|
|
33
|
+
|-----|---------|
|
|
34
|
+
| `minitest` | Test framework |
|
|
35
|
+
| `rake` | Task runner |
|
|
36
|
+
| `simplecov` | Code coverage |
|
|
37
|
+
| `debug_me` | Debugging output |
|
|
38
|
+
| `irb` | Interactive console |
|
|
39
|
+
|
|
40
|
+
## Console
|
|
41
|
+
|
|
42
|
+
Start an interactive console with the gem preloaded:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
bin/console
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Project Structure
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
self_agency/
|
|
52
|
+
lib/
|
|
53
|
+
self_agency.rb # Main module
|
|
54
|
+
self_agency/
|
|
55
|
+
version.rb # VERSION constant
|
|
56
|
+
errors.rb # Error hierarchy
|
|
57
|
+
configuration.rb # Configuration + singleton methods
|
|
58
|
+
sandbox.rb # Runtime sandbox
|
|
59
|
+
validator.rb # Static analysis + validation
|
|
60
|
+
generator.rb # LLM communication
|
|
61
|
+
saver.rb # _save! helpers
|
|
62
|
+
prompts/ # ERB templates
|
|
63
|
+
shape/
|
|
64
|
+
system.txt.erb
|
|
65
|
+
user.txt.erb
|
|
66
|
+
generate/
|
|
67
|
+
system.txt.erb
|
|
68
|
+
user.txt.erb
|
|
69
|
+
test/
|
|
70
|
+
test_helper.rb # Test setup
|
|
71
|
+
test_configuration.rb # Configuration tests
|
|
72
|
+
test_generator.rb # Generator tests
|
|
73
|
+
test_pipeline.rb # Pipeline tests
|
|
74
|
+
test_sandbox.rb # Sandbox tests
|
|
75
|
+
test_save.rb # _save! tests
|
|
76
|
+
test_source_for.rb # Source inspection tests
|
|
77
|
+
test_templates.rb # Template tests
|
|
78
|
+
test_validator.rb # Validator tests
|
|
79
|
+
examples/ # 12 example scripts
|
|
80
|
+
docs/ # This documentation
|
|
81
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
SelfAgency uses Minitest. All tests run offline -- they exercise configuration, validation, sanitization, and sandboxing without calling an LLM.
|
|
4
|
+
|
|
5
|
+
## Running Tests
|
|
6
|
+
|
|
7
|
+
Run the full test suite:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
rake test
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This is the default Rake task, so `rake` alone works too:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
rake
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Running Individual Tests
|
|
20
|
+
|
|
21
|
+
Run a specific test file:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bundle exec ruby -Ilib -Itest test/test_validator.rb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run a single test method:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bundle exec ruby -Ilib -Itest test/test_validator.rb -n test_method_name
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Test Structure
|
|
34
|
+
|
|
35
|
+
| File | Tests |
|
|
36
|
+
|------|-------|
|
|
37
|
+
| `test_configuration.rb` | Configuration options, `reset!`, `ensure_configured!` |
|
|
38
|
+
| `test_generator.rb` | Generator helper methods |
|
|
39
|
+
| `test_pipeline.rb` | End-to-end pipeline logic |
|
|
40
|
+
| `test_sandbox.rb` | Runtime sandbox blocks dangerous methods |
|
|
41
|
+
| `test_save.rb` | `_save!` file generation |
|
|
42
|
+
| `test_source_for.rb` | `_source_for` at instance and class level |
|
|
43
|
+
| `test_templates.rb` | Prompt template loading |
|
|
44
|
+
| `test_validator.rb` | Sanitization, validation, security patterns |
|
|
45
|
+
|
|
46
|
+
## Test Design
|
|
47
|
+
|
|
48
|
+
Tests define a `SampleClass` that includes `SelfAgency` for testing private helpers via `send`. This avoids needing a live LLM connection -- the tests exercise the internal machinery directly:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
class SampleClass
|
|
52
|
+
include SelfAgency
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Test private helpers
|
|
56
|
+
obj = SampleClass.new
|
|
57
|
+
obj.send(:self_agency_validate!, code)
|
|
58
|
+
obj.send(:self_agency_sanitize, raw)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Code Coverage
|
|
62
|
+
|
|
63
|
+
SimpleCov runs automatically with every test execution. Coverage reports are generated in the `coverage/` directory after each `rake test` run:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
rake test
|
|
67
|
+
# Coverage report written to coverage/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Open `coverage/index.html` in a browser to view the detailed report.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Example 12: Autonomous Robots
|
|
2
|
+
|
|
3
|
+
Three robots autonomously build a city landmarks tour pipeline. Unlike Example 11, each robot receives only a high-level **goal** -- the LLM decides method names, algorithms, and data structures.
|
|
4
|
+
|
|
5
|
+
**Source:** `examples/12_autonomous_robots/`
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
A[Collector] -->|landmark data| B[Analyst]
|
|
12
|
+
B -->|analyzed data| C[Planner]
|
|
13
|
+
C -->|itinerary| D[Output]
|
|
14
|
+
|
|
15
|
+
style A fill:#6366f1,color:#fff
|
|
16
|
+
style B fill:#8b5cf6,color:#fff
|
|
17
|
+
style C fill:#a855f7,color:#fff
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Robot | Goal | Methods |
|
|
21
|
+
|-------|------|---------|
|
|
22
|
+
| **Collector** | Return an Array of 8 fictional city landmarks | LLM decides |
|
|
23
|
+
| **Analyst** | Analyze landmark data (statistics, ranking, grouping) | LLM decides |
|
|
24
|
+
| **Planner** | Create a formatted one-day tour itinerary | LLM decides |
|
|
25
|
+
|
|
26
|
+
## Three-Layer LLM Approach
|
|
27
|
+
|
|
28
|
+
This example adds a third layer compared to Example 11:
|
|
29
|
+
|
|
30
|
+
1. **Layer 1 (Decompose):** Ask the LLM to break a high-level goal into helper method specs. The LLM chooses method names, parameters, and algorithms
|
|
31
|
+
2. **Layer 2 (Generate Helpers):** Loop through specs, call `_()` for each. SelfAgency handles shape, generate, validate, sandbox eval
|
|
32
|
+
3. **Layer 3 (Generate Orchestrator):** A final `_()` call generates an `execute_task` method that wires the helpers together
|
|
33
|
+
|
|
34
|
+
```mermaid
|
|
35
|
+
flowchart TD
|
|
36
|
+
A[High-Level Goal] --> B["Layer 1: Decompose"]
|
|
37
|
+
B --> C["JSON specs<br/>(LLM-chosen names)"]
|
|
38
|
+
C --> D["Layer 2: Generate Helpers"]
|
|
39
|
+
D --> E["Layer 3: Generate Orchestrator"]
|
|
40
|
+
E --> F[Robot Ready]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Self-Repair
|
|
44
|
+
|
|
45
|
+
If `execute_task` raises at runtime, the robot attempts self-repair:
|
|
46
|
+
|
|
47
|
+
1. **Identify the failing method** from the backtrace
|
|
48
|
+
2. **Build a repair prompt** with the error, current source, goal context, and input data shape
|
|
49
|
+
3. **Regenerate** the failing method via `_()` with `scope: :singleton`
|
|
50
|
+
4. **Retry** (up to `MAX_REPAIR_ATTEMPTS = 3`)
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
def perform_task(input = nil)
|
|
54
|
+
attempts = 0
|
|
55
|
+
|
|
56
|
+
begin
|
|
57
|
+
execute_task(input)
|
|
58
|
+
rescue => error
|
|
59
|
+
attempts += 1
|
|
60
|
+
if attempts < MAX_REPAIR_ATTEMPTS
|
|
61
|
+
repair_method(error, input)
|
|
62
|
+
retry
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The repair prompt includes:
|
|
69
|
+
|
|
70
|
+
- The robot's overall goal
|
|
71
|
+
- All generated capabilities
|
|
72
|
+
- The current source code of the failing method
|
|
73
|
+
- The runtime error (class, message, backtrace)
|
|
74
|
+
- A description of the input data shape (class, keys, sample elements)
|
|
75
|
+
|
|
76
|
+
## Key Differences from Example 11
|
|
77
|
+
|
|
78
|
+
| Aspect | Example 11 (Collaborative) | Example 12 (Autonomous) |
|
|
79
|
+
|--------|---------------------------|------------------------|
|
|
80
|
+
| Task description | Prescriptive (exact method names, algorithms) | Goal-oriented (what, not how) |
|
|
81
|
+
| LLM layers | 2 (analyze + generate) | 3 (decompose + generate + orchestrate) |
|
|
82
|
+
| Method naming | Dictated by user | Chosen by LLM |
|
|
83
|
+
| Error recovery | None | Self-repair with retry |
|
|
84
|
+
| Scope | Instance methods | Singleton methods |
|
|
85
|
+
|
|
86
|
+
## Pipeline Execution
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# Step 1: Collector builds a landmark catalog
|
|
90
|
+
collector_result = collector.perform_task
|
|
91
|
+
|
|
92
|
+
# Step 2: Analyst processes the catalog
|
|
93
|
+
analyst_result = analyst.perform_task(analyst_input)
|
|
94
|
+
|
|
95
|
+
# Step 3: Planner creates the itinerary
|
|
96
|
+
final_itinerary = planner.perform_task(planner_input)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Saving Robots
|
|
100
|
+
|
|
101
|
+
Each robot's generated methods are saved as a subclass:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
[collector, analyst, planner].each do |robot|
|
|
105
|
+
path = robot._save!(as: robot.name)
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This captures the LLM's autonomous design decisions as permanent Ruby source files.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Basic Examples (01--09)
|
|
2
|
+
|
|
3
|
+
These examples cover SelfAgency's core features one at a time.
|
|
4
|
+
|
|
5
|
+
## 01: Basic Usage
|
|
6
|
+
|
|
7
|
+
The simplest example: include `SelfAgency`, generate a single method, call it.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
class Calculator
|
|
11
|
+
include SelfAgency
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
calc = Calculator.new
|
|
15
|
+
method_names = calc._("an instance method named 'add' that accepts two integer parameters a and b, and returns their sum")
|
|
16
|
+
|
|
17
|
+
puts method_names.inspect #=> [:add]
|
|
18
|
+
puts calc.add(3, 7) #=> 10
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Source:** `examples/01_basic_usage.rb`
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 02: Multiple Methods
|
|
26
|
+
|
|
27
|
+
Generate several related methods in a single `_()` call.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
class Arithmetic
|
|
31
|
+
include SelfAgency
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
arith = Arithmetic.new
|
|
35
|
+
method_names = arith._(
|
|
36
|
+
"create four instance methods: " \
|
|
37
|
+
"'add(a, b)' returns a + b, " \
|
|
38
|
+
"'subtract(a, b)' returns a - b, " \
|
|
39
|
+
"'multiply(a, b)' returns a * b, " \
|
|
40
|
+
"'divide(a, b)' returns a.to_f / b (raises ZeroDivisionError if b is zero)"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
puts method_names.inspect #=> [:add, :subtract, :multiply, :divide]
|
|
44
|
+
puts arith.multiply(10, 3) #=> 30
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Source:** `examples/02_multiple_methods.rb`
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 03: Scopes
|
|
52
|
+
|
|
53
|
+
Demonstrates all three scopes: instance, singleton, and class.
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class Greeter
|
|
57
|
+
include SelfAgency
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
alice = Greeter.new
|
|
61
|
+
bob = Greeter.new
|
|
62
|
+
|
|
63
|
+
# Instance -- available to all instances
|
|
64
|
+
alice._("an instance method named 'hello' that returns 'Hello, world!'")
|
|
65
|
+
bob.hello #=> "Hello, world!"
|
|
66
|
+
|
|
67
|
+
# Singleton -- available to one instance only
|
|
68
|
+
alice._("a method named 'secret' that returns 'Alice only'", scope: :singleton)
|
|
69
|
+
alice.secret #=> "Alice only"
|
|
70
|
+
bob.respond_to?(:secret) #=> false
|
|
71
|
+
|
|
72
|
+
# Class -- available on the class itself
|
|
73
|
+
alice._("a class method named 'self.class_greeting' that returns 'Greetings from Greeter'", scope: :class)
|
|
74
|
+
Greeter.class_greeting #=> "Greetings from Greeter"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Source:** `examples/03_scopes.rb`
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 04: Source Inspection
|
|
82
|
+
|
|
83
|
+
View generated source code and the fallback to `method_source` for file-defined methods.
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
class MathHelper
|
|
87
|
+
include SelfAgency
|
|
88
|
+
|
|
89
|
+
# Multiplies a number by two.
|
|
90
|
+
def double(n)
|
|
91
|
+
n * 2
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
helper = MathHelper.new
|
|
96
|
+
helper._("an instance method named 'square' that accepts an integer n and returns n * n")
|
|
97
|
+
|
|
98
|
+
# LLM-generated method (includes description as comment header)
|
|
99
|
+
puts helper._source_for(:square)
|
|
100
|
+
|
|
101
|
+
# File-defined method (falls back to method_source gem)
|
|
102
|
+
puts helper._source_for(:double)
|
|
103
|
+
|
|
104
|
+
# Unknown method
|
|
105
|
+
helper._source_for(:nonexistent) #=> nil
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Source:** `examples/04_source_inspection.rb`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 05: Lifecycle Hook
|
|
113
|
+
|
|
114
|
+
Override `on_method_generated` to log or persist each generated method.
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
class PersistentCalculator
|
|
118
|
+
include SelfAgency
|
|
119
|
+
|
|
120
|
+
attr_reader :generation_log
|
|
121
|
+
|
|
122
|
+
def initialize
|
|
123
|
+
@generation_log = []
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def on_method_generated(method_name, scope, code)
|
|
127
|
+
@generation_log << { method_name: method_name, scope: scope, code: code }
|
|
128
|
+
puts "[hook] Generated :#{method_name} (scope: #{scope})"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
calc = PersistentCalculator.new
|
|
133
|
+
calc._("an instance method named 'increment' that returns n + 1")
|
|
134
|
+
# [hook] Generated :increment (scope: instance)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Source:** `examples/05_lifecycle_hook.rb`
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 06: Configuration
|
|
142
|
+
|
|
143
|
+
Explores all configuration options. Runs offline (no LLM required).
|
|
144
|
+
|
|
145
|
+
Demonstrates:
|
|
146
|
+
|
|
147
|
+
- Default values before `configure`
|
|
148
|
+
- `ensure_configured!` raises before `configure`
|
|
149
|
+
- Setting custom values
|
|
150
|
+
- `reset!` restores defaults
|
|
151
|
+
- Custom `template_directory`
|
|
152
|
+
|
|
153
|
+
**Source:** `examples/06_configuration.rb`
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 07: Error Handling
|
|
158
|
+
|
|
159
|
+
Exercises the error hierarchy. Runs offline (no LLM required).
|
|
160
|
+
|
|
161
|
+
Demonstrates:
|
|
162
|
+
|
|
163
|
+
- `SelfAgency::Error` hierarchy verification
|
|
164
|
+
- Calling `_()` before `configure` raises `Error`
|
|
165
|
+
- `ValidationError` for empty code, missing `def...end`, syntax errors
|
|
166
|
+
- `SecurityError` for `system`, `File`, `eval`, `require` patterns
|
|
167
|
+
- Catching all errors with `rescue SelfAgency::Error`
|
|
168
|
+
|
|
169
|
+
**Source:** `examples/07_error_handling.rb`
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 08: Class Context
|
|
174
|
+
|
|
175
|
+
Shows how the LLM receives class introspection (class name, instance variables, public methods) to generate context-aware code.
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
class BankAccount
|
|
179
|
+
include SelfAgency
|
|
180
|
+
|
|
181
|
+
attr_reader :owner, :balance
|
|
182
|
+
|
|
183
|
+
def initialize(owner, balance)
|
|
184
|
+
@owner = owner
|
|
185
|
+
@balance = balance
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def deposit(amount)
|
|
189
|
+
@balance += amount
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
account = BankAccount.new("Alice", 1000)
|
|
194
|
+
account._(
|
|
195
|
+
"an instance method named 'summary' that returns 'Account for <owner>: $<balance>'"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
puts account.summary #=> "Account for Alice: $1000"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Source:** `examples/08_class_context.rb`
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 09: Method Override
|
|
206
|
+
|
|
207
|
+
Demonstrates that generating a method with the same name as an existing method overrides it via `Module#prepend`.
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
class Formatter
|
|
211
|
+
include SelfAgency
|
|
212
|
+
|
|
213
|
+
def greet(name)
|
|
214
|
+
"Hello, #{name}"
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
fmt = Formatter.new
|
|
219
|
+
puts fmt.greet("World") #=> "Hello, World"
|
|
220
|
+
|
|
221
|
+
fmt._(
|
|
222
|
+
"an instance method named 'greet' that returns 'Greetings, <name>! Welcome aboard.'"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
puts fmt.greet("World") #=> "Greetings, World! Welcome aboard."
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The MRO shows the prepended module:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
0. #<Module:0x...> ← anonymous module with generated method
|
|
232
|
+
1. Formatter
|
|
233
|
+
2. SelfAgency
|
|
234
|
+
3. ...
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
**Source:** `examples/09_method_override.rb`
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Example 11: Collaborative Robots
|
|
2
|
+
|
|
3
|
+
Three robots collaborate through a shared message bus to build a weather data pipeline. Each robot's methods are generated by the LLM based on detailed task descriptions.
|
|
4
|
+
|
|
5
|
+
**Source:** `examples/11_collaborative_robots/`
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
A[Atlas] -->|weather data| B[Nova]
|
|
12
|
+
B -->|analyzed data| C[Echo]
|
|
13
|
+
C -->|formatted report| D[Output]
|
|
14
|
+
|
|
15
|
+
style A fill:#6366f1,color:#fff
|
|
16
|
+
style B fill:#8b5cf6,color:#fff
|
|
17
|
+
style C fill:#a855f7,color:#fff
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Three robots connected by a `MessageBus`:
|
|
21
|
+
|
|
22
|
+
| Robot | Role | Methods Generated |
|
|
23
|
+
|-------|------|-------------------|
|
|
24
|
+
| **Atlas** | Data generator | `generate_weather_data`, `summarize_raw_data` |
|
|
25
|
+
| **Nova** | Analyzer | `compute_basic_statistics`, `classify_conditions` |
|
|
26
|
+
| **Echo** | Reporter | `format_weather_report` |
|
|
27
|
+
|
|
28
|
+
## Two-Layer LLM Approach
|
|
29
|
+
|
|
30
|
+
This example uses a **two-layer** approach:
|
|
31
|
+
|
|
32
|
+
1. **Layer 1 (Analyze):** A direct `RubyLLM.chat().ask()` call decomposes the task description into a JSON array of method specifications
|
|
33
|
+
2. **Layer 2 (Generate):** Loops through the specs and calls `_()` for each one. SelfAgency handles shape, generate, validate, and sandbox eval
|
|
34
|
+
|
|
35
|
+
The task descriptions are **prescriptive** -- they dictate exact method names, parameters, algorithms, and data structures.
|
|
36
|
+
|
|
37
|
+
## Robot Class
|
|
38
|
+
|
|
39
|
+
The `Robot` class includes `SelfAgency` and adds:
|
|
40
|
+
|
|
41
|
+
- **Task decomposition** via `analyze_task` -- asks the LLM to parse a task into method specs
|
|
42
|
+
- **Pipeline execution** via `execute` -- chains generated methods by arity
|
|
43
|
+
- **Message bus** -- `send_message`, `receive_message`, `broadcast`
|
|
44
|
+
- **Generation logging** via `on_method_generated`
|
|
45
|
+
- **Persistence** via `_save!` -- saves each robot as a subclass
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
class Robot
|
|
49
|
+
include SelfAgency
|
|
50
|
+
|
|
51
|
+
attr_reader :name, :task, :bus, :inbox, :capabilities, :generation_log
|
|
52
|
+
|
|
53
|
+
def initialize(name:, task:, bus:)
|
|
54
|
+
@name = name
|
|
55
|
+
@task = task
|
|
56
|
+
@bus = bus
|
|
57
|
+
# ... decompose task, generate methods
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def execute(input = nil)
|
|
61
|
+
# Chain capabilities by arity
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Pipeline Execution
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
# Step 1: Atlas generates weather data
|
|
70
|
+
atlas_result = atlas.execute
|
|
71
|
+
atlas.send_message(to: "Nova", content: atlas_result)
|
|
72
|
+
|
|
73
|
+
# Step 2: Nova analyzes the data
|
|
74
|
+
nova_input = nova.inbox.last&.dig(:content)
|
|
75
|
+
nova_result = nova.execute(nova_input)
|
|
76
|
+
nova.send_message(to: "Echo", content: nova_result)
|
|
77
|
+
|
|
78
|
+
# Step 3: Echo formats the report
|
|
79
|
+
echo_input = echo.inbox.last&.dig(:content)
|
|
80
|
+
final_report = echo.execute(echo_input)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Saving Robots
|
|
84
|
+
|
|
85
|
+
After the pipeline runs, each robot's generated methods are saved as a subclass file:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
[atlas, nova, echo].each do |robot|
|
|
89
|
+
path = robot._save!(as: robot.name)
|
|
90
|
+
puts "#{robot.name}: saved to #{path}"
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This produces files like `atlas.rb`, `nova.rb`, `echo.rb`, each containing a `Robot` subclass with the generated methods baked in.
|
|
95
|
+
|
|
96
|
+
## Key Difference from Example 12
|
|
97
|
+
|
|
98
|
+
In this example, the task descriptions **dictate** method names and algorithms. The LLM is told *exactly* what to implement. Compare with [Example 12: Autonomous Robots](autonomous-robots.md), where robots receive only a high-level goal and the LLM decides everything.
|