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,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.