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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +40 -0
  4. data/.irbrc +22 -0
  5. data/CHANGELOG.md +5 -0
  6. data/COMMITS.md +196 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +177 -0
  9. data/Rakefile +8 -0
  10. data/docs/api/configuration.md +85 -0
  11. data/docs/api/errors.md +166 -0
  12. data/docs/api/index.md +37 -0
  13. data/docs/api/self-agency-module.md +198 -0
  14. data/docs/architecture/overview.md +181 -0
  15. data/docs/architecture/security.md +101 -0
  16. data/docs/assets/images/self_agency.gif +0 -0
  17. data/docs/assets/images/self_agency.mp4 +0 -0
  18. data/docs/development/contributing.md +45 -0
  19. data/docs/development/setup.md +81 -0
  20. data/docs/development/testing.md +70 -0
  21. data/docs/examples/autonomous-robots.md +109 -0
  22. data/docs/examples/basic-examples.md +237 -0
  23. data/docs/examples/collaborative-robots.md +98 -0
  24. data/docs/examples/full-workflow.md +100 -0
  25. data/docs/examples/index.md +36 -0
  26. data/docs/getting-started/installation.md +71 -0
  27. data/docs/getting-started/quick-start.md +94 -0
  28. data/docs/guide/configuration.md +113 -0
  29. data/docs/guide/generating-methods.md +146 -0
  30. data/docs/guide/how-to-use.md +144 -0
  31. data/docs/guide/lifecycle-hooks.md +86 -0
  32. data/docs/guide/prompt-templates.md +189 -0
  33. data/docs/guide/saving-methods.md +84 -0
  34. data/docs/guide/scopes.md +74 -0
  35. data/docs/guide/source-inspection.md +96 -0
  36. data/docs/index.md +77 -0
  37. data/examples/01_basic_usage.rb +27 -0
  38. data/examples/02_multiple_methods.rb +43 -0
  39. data/examples/03_scopes.rb +40 -0
  40. data/examples/04_source_inspection.rb +46 -0
  41. data/examples/05_lifecycle_hook.rb +55 -0
  42. data/examples/06_configuration.rb +97 -0
  43. data/examples/07_error_handling.rb +103 -0
  44. data/examples/08_class_context.rb +64 -0
  45. data/examples/09_method_override.rb +52 -0
  46. data/examples/10_full_workflow.rb +118 -0
  47. data/examples/11_collaborative_robots/atlas.rb +31 -0
  48. data/examples/11_collaborative_robots/echo.rb +30 -0
  49. data/examples/11_collaborative_robots/main.rb +190 -0
  50. data/examples/11_collaborative_robots/nova.rb +71 -0
  51. data/examples/11_collaborative_robots/robot.rb +119 -0
  52. data/examples/12_autonomous_robots/analyst.rb +193 -0
  53. data/examples/12_autonomous_robots/collector.rb +78 -0
  54. data/examples/12_autonomous_robots/main.rb +166 -0
  55. data/examples/12_autonomous_robots/planner.rb +125 -0
  56. data/examples/12_autonomous_robots/robot.rb +284 -0
  57. data/examples/generated/from_range_class.rb +3 -0
  58. data/examples/generated/mean_instance.rb +4 -0
  59. data/examples/generated/median_instance.rb +15 -0
  60. data/examples/generated/report_singleton.rb +3 -0
  61. data/examples/generated/standard_deviation_instance.rb +8 -0
  62. data/examples/lib/message_bus.rb +57 -0
  63. data/examples/lib/setup.rb +8 -0
  64. data/lib/self_agency/configuration.rb +76 -0
  65. data/lib/self_agency/errors.rb +35 -0
  66. data/lib/self_agency/generator.rb +47 -0
  67. data/lib/self_agency/prompts/generate/system.txt.erb +15 -0
  68. data/lib/self_agency/prompts/generate/user.txt.erb +13 -0
  69. data/lib/self_agency/prompts/shape/system.txt.erb +26 -0
  70. data/lib/self_agency/prompts/shape/user.txt.erb +10 -0
  71. data/lib/self_agency/sandbox.rb +17 -0
  72. data/lib/self_agency/saver.rb +62 -0
  73. data/lib/self_agency/validator.rb +64 -0
  74. data/lib/self_agency/version.rb +5 -0
  75. data/lib/self_agency.rb +315 -0
  76. data/mkdocs.yml +156 -0
  77. data/sig/self_agency.rbs +4 -0
  78. 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.