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,86 @@
1
+ # Lifecycle Hooks
2
+
3
+ SelfAgency provides a lifecycle hook that fires each time a method is generated. Override it in your class to persist, log, or react to generated methods.
4
+
5
+ ## `on_method_generated`
6
+
7
+ ```ruby
8
+ def on_method_generated(method_name, scope, code)
9
+ ```
10
+
11
+ | Parameter | Type | Description |
12
+ |-----------|------|-------------|
13
+ | `method_name` | `Symbol` | Name of the generated method |
14
+ | `scope` | `Symbol` | `:instance`, `:singleton`, or `:class` |
15
+ | `code` | `String` | The generated source code |
16
+
17
+ By default, `on_method_generated` is a no-op. Override it in your class:
18
+
19
+ ```ruby
20
+ class PersistentCalculator
21
+ include SelfAgency
22
+
23
+ attr_reader :generation_log
24
+
25
+ def initialize
26
+ @generation_log = []
27
+ end
28
+
29
+ def on_method_generated(method_name, scope, code)
30
+ @generation_log << { method_name: method_name, scope: scope, code: code }
31
+ puts "[hook] Generated :#{method_name} (scope: #{scope})"
32
+ end
33
+ end
34
+ ```
35
+
36
+ ## When It Fires
37
+
38
+ The hook fires **once per method** after successful validation and installation. If `_()` generates multiple methods in a single call, the hook fires for each one:
39
+
40
+ ```ruby
41
+ calc = PersistentCalculator.new
42
+
43
+ calc._("an instance method named 'increment' that returns n + 1")
44
+ # [hook] Generated :increment (scope: instance)
45
+
46
+ calc._(
47
+ "two methods: 'min_of(a, b)' returns the smaller, " \
48
+ "'max_of(a, b)' returns the larger"
49
+ )
50
+ # [hook] Generated :min_of (scope: instance)
51
+ # [hook] Generated :max_of (scope: instance)
52
+ ```
53
+
54
+ ## Common Patterns
55
+
56
+ ### Save to Files
57
+
58
+ ```ruby
59
+ def on_method_generated(method_name, scope, code)
60
+ filepath = "generated/#{method_name}_#{scope}.rb"
61
+ File.write(filepath, code)
62
+ end
63
+ ```
64
+
65
+ ### Log to a Database
66
+
67
+ ```ruby
68
+ def on_method_generated(method_name, scope, code)
69
+ GeneratedMethod.create!(
70
+ name: method_name.to_s,
71
+ scope: scope.to_s,
72
+ source: code,
73
+ class_name: self.class.name
74
+ )
75
+ end
76
+ ```
77
+
78
+ ### Collect for Later Inspection
79
+
80
+ ```ruby
81
+ def on_method_generated(method_name, scope, code)
82
+ @generation_log << { method_name: method_name, scope: scope, code: code }
83
+ end
84
+ ```
85
+
86
+ This pattern is used in the [collaborative robots](../examples/collaborative-robots.md) and [autonomous robots](../examples/autonomous-robots.md) examples.
@@ -0,0 +1,189 @@
1
+ # Prompt Templates
2
+
3
+ SelfAgency uses [ruby_llm-template](https://github.com/danielfriis/ruby_llm-template) for prompt management. The two-stage pipeline (shape and generate) each use a pair of ERB templates.
4
+
5
+ ## Default Template Layout
6
+
7
+ ```
8
+ lib/self_agency/prompts/
9
+ shape/
10
+ system.txt.erb # System prompt for the shape stage
11
+ user.txt.erb # User prompt for the shape stage
12
+ generate/
13
+ system.txt.erb # System prompt for the generate stage
14
+ user.txt.erb # User prompt for the generate stage
15
+ ```
16
+
17
+ ## Shape Stage Templates
18
+
19
+ The **shape** stage rewrites a casual language description into a precise Ruby method specification.
20
+
21
+ **`shape/system.txt.erb`** -- Instructs the LLM to act as a prompt engineer, with rules for rewriting descriptions:
22
+
23
+ - State the exact method name (snake_case)
24
+ - State the method signature with parameter names, types, defaults
25
+ - State the return type and value
26
+ - Describe the algorithm step by step
27
+ - Translate vague terms into concrete Ruby operations
28
+ - Output only plain language, no code
29
+
30
+ **`shape/user.txt.erb`** -- Provides class context and the user's request:
31
+
32
+ ```erb
33
+ Rewrite the following casual request into a precise Ruby method specification.
34
+
35
+ Class context:
36
+ - Class name: <%= class_name %>
37
+ - Instance variables: <%= ivars %>
38
+ - Public methods: <%= methods %>
39
+ - Scope: <%= scope_instruction %>
40
+
41
+ User request:
42
+ <%= raw_prompt %>
43
+ ```
44
+
45
+ ### Available Variables (Shape)
46
+
47
+ | Variable | Description |
48
+ |----------|-------------|
49
+ | `class_name` | Name of the including class |
50
+ | `ivars` | Comma-separated list of instance variables |
51
+ | `methods` | Comma-separated list of public instance methods |
52
+ | `scope_instruction` | Human-readable scope description |
53
+ | `raw_prompt` | The user's original description |
54
+
55
+ ## Generate Stage Templates
56
+
57
+ The **generate** stage produces Ruby code from the shaped specification.
58
+
59
+ **`generate/system.txt.erb`** -- Instructs the LLM to act as a Ruby code generator:
60
+
61
+ - Return exactly one `def method_name ... end` block
62
+ - Do not use dangerous methods (`system`, `exec`, `File`, `IO`, `eval`, etc.)
63
+ - Do not wrap code in markdown fences
64
+ - The method must be self-contained
65
+
66
+ !!! note
67
+ The template instructs the LLM to return "exactly one" method definition. However, when describing multiple methods in a single `_()` call, some LLMs return multiple `def...end` blocks despite this instruction. SelfAgency handles this gracefully -- it splits the output into individual method blocks and installs each one separately.
68
+
69
+ **`generate/user.txt.erb`** -- Passes the shaped specification. On retries, includes the previous error and code so the LLM can self-correct:
70
+
71
+ ```erb
72
+ <%= shaped_spec %>
73
+ <%% if previous_error %>
74
+
75
+ Your previous attempt produced this code:
76
+
77
+ <%= previous_code %>
78
+
79
+ It failed with the following error:
80
+
81
+ <%= previous_error %>
82
+
83
+ Please fix the issue and generate corrected code.
84
+ <%% end %>
85
+ ```
86
+
87
+ ### Available Variables (Generate)
88
+
89
+ | Variable | Description |
90
+ |----------|-------------|
91
+ | `class_name` | Name of the including class |
92
+ | `ivars` | Comma-separated list of instance variables |
93
+ | `methods` | Comma-separated list of public instance methods |
94
+ | `shaped_spec` | Output from the shape stage |
95
+ | `previous_error` | Error message from the previous attempt (`nil` on first attempt) |
96
+ | `previous_code` | Generated code from the previous attempt (`nil` on first attempt) |
97
+
98
+ !!! warning
99
+ If you customize the generate templates, your `user.txt.erb` must handle the `previous_error` and `previous_code` variables (they are passed on every call). Use a conditional `<%% if previous_error %>` block to include them only during retries.
100
+
101
+ ## Custom Templates
102
+
103
+ ### Why Customize?
104
+
105
+ The default prompts are tuned for general-purpose code generation, but different providers and models respond best to different prompt styles. You may want to customize templates when:
106
+
107
+ - **Switching providers or models** -- A prompt that works well with Ollama's Qwen may produce poor results with OpenAI's GPT-4o or Anthropic's Claude. Some models need more explicit instructions; others perform better with fewer constraints. Smaller models may need step-by-step algorithmic guidance that a larger model would find redundant.
108
+ - **Domain-specific generation** -- If your class operates in a specific domain (financial calculations, data science, text processing), you can add domain rules and conventions directly into the system prompt so every generated method follows them.
109
+ - **Code style enforcement** -- You may want generated methods to follow your project's conventions: frozen string literals, specific naming patterns, guard clauses, or particular error handling styles.
110
+ - **Controlling output format** -- Some models wrap output in markdown fences or include chain-of-thought reasoning despite instructions not to. Tailoring the prompt to your model's quirks reduces the sanitization needed.
111
+
112
+ ### Setup
113
+
114
+ Start by copying the default prompts into your project:
115
+
116
+ ```bash
117
+ cp -r $(bundle show self_agency)/lib/self_agency/prompts my_prompts
118
+ ```
119
+
120
+ Then point SelfAgency at your copy:
121
+
122
+ ```ruby
123
+ SelfAgency.configure do |config|
124
+ config.template_directory = File.expand_path("my_prompts", __dir__)
125
+ # ...
126
+ end
127
+ ```
128
+
129
+ Your custom directory must follow the same layout:
130
+
131
+ ```
132
+ my_prompts/
133
+ shape/
134
+ system.txt.erb
135
+ user.txt.erb
136
+ generate/
137
+ system.txt.erb
138
+ user.txt.erb
139
+ ```
140
+
141
+ All ERB variables listed above are available in your custom templates.
142
+
143
+ ### Example: Adapting the Generate Prompt for a Different Model
144
+
145
+ The default `generate/system.txt.erb` is concise:
146
+
147
+ ```erb
148
+ You are a Ruby code generator. You MUST respond with ONLY a Ruby method
149
+ definition — nothing else. No explanation, no markdown fences, no comments
150
+ outside the method, no extra text.
151
+
152
+ Context for the class you are writing a method for:
153
+ - Class name: <%= class_name %>
154
+ - Instance variables: <%= ivars %>
155
+ - Public methods: <%= methods %>
156
+
157
+ Rules:
158
+ - Return exactly one `def method_name ... end` block.
159
+ - Use the EXACT method name, parameter names, and Hash key names from the specification. Do NOT rename or abbreviate any identifier.
160
+ - Do NOT use any of these forbidden patterns: system, exec, spawn, fork, abort, exit, backticks, %x, File., IO., Kernel., Open3., Process., require, load, __send__, eval, send, public_send, method(), const_get, class_eval, module_eval, instance_eval, instance_variable_set, instance_variable_get, define_method, Binding, BasicObject, remove_method, undef_method.
161
+ - Do NOT wrap the code in markdown fences.
162
+ - The method must be self-contained.
163
+ ```
164
+
165
+ A smaller or less instruction-following model might ignore the "no markdown fences" rule, or include conversational preamble before the code. You could adapt it with stronger guardrails:
166
+
167
+ ```erb
168
+ TASK: Generate a Ruby method definition.
169
+
170
+ CRITICAL FORMAT RULES:
171
+ 1. Your ENTIRE response must be a single `def ... end` block.
172
+ 2. Do NOT output any text before `def` or after `end`.
173
+ 3. Do NOT use ``` fences. Do NOT use <think> tags.
174
+ 4. Do NOT include comments, explanations, or notes.
175
+
176
+ If you output anything other than a method definition, the parse will fail.
177
+
178
+ Class: <%= class_name %>
179
+ Instance variables: <%= ivars %>
180
+ Existing methods: <%= methods %>
181
+
182
+ FORBIDDEN constructs (will be rejected):
183
+ system, exec, spawn, fork, backticks, File, IO, Kernel, Open3,
184
+ Process, require, load, eval, send, __send__, remove_method, undef_method
185
+
186
+ Write ONLY the def ... end block now.
187
+ ```
188
+
189
+ This version uses imperative language, repeats the format constraint, and explicitly warns about parse failure -- techniques that help smaller models stay on track.
@@ -0,0 +1,84 @@
1
+ # Saving Methods
2
+
3
+ `_save!` writes an object's generated methods as a subclass in a Ruby source file. This lets you capture LLM-generated methods as permanent, version-controlled code.
4
+
5
+ ## Basic Usage
6
+
7
+ ```ruby
8
+ foo = Foo.new
9
+ foo._("an instance method to add two integers")
10
+ foo._("an instance method to subtract two integers")
11
+
12
+ foo._save!(as: :calculator)
13
+ ```
14
+
15
+ This writes `calculator.rb`:
16
+
17
+ ```ruby
18
+ # frozen_string_literal: true
19
+
20
+ require_relative "foo"
21
+
22
+ class Calculator < Foo
23
+ # an instance method to add two integers
24
+ def add(a, b)
25
+ a + b
26
+ end
27
+
28
+ # an instance method to subtract two integers
29
+ def subtract(a, b)
30
+ a - b
31
+ end
32
+ end
33
+ ```
34
+
35
+ ## The `as:` Parameter
36
+
37
+ `as:` is required. It sets the subclass name and (by default) the output filename. It accepts a String or Symbol.
38
+
39
+ Snake case is automatically converted to CamelCase:
40
+
41
+ ```ruby
42
+ foo._save!(as: :weather_analyst)
43
+ # Writes weather_analyst.rb with class WeatherAnalyst < Foo
44
+
45
+ foo._save!(as: "WeatherAnalyst")
46
+ # Same result
47
+ ```
48
+
49
+ ## Custom File Path
50
+
51
+ Override the default file path with `path:`:
52
+
53
+ ```ruby
54
+ foo._save!(as: :calculator, path: "lib/calculator.rb")
55
+ ```
56
+
57
+ ## Require Path
58
+
59
+ The generated file includes a `require_relative` pointing back to the parent class source file. The path is computed relative to the output file location.
60
+
61
+ ## Multiple Instances, Different Methods
62
+
63
+ Each instance of a class can have different generated methods. `_save!` captures only the methods generated on that specific instance:
64
+
65
+ ```ruby
66
+ collector = Robot.new(name: "Collector")
67
+ analyst = Robot.new(name: "Analyst")
68
+
69
+ # Each robot generates different methods via _()
70
+ collector._save!(as: collector.name) # collector.rb with class Collector < Robot
71
+ analyst._save!(as: analyst.name) # analyst.rb with class Analyst < Robot
72
+ ```
73
+
74
+ ## Description Comments
75
+
76
+ Each method in the saved file includes the original natural language description as a comment header, so the intent behind the generated code is preserved.
77
+
78
+ ## Errors
79
+
80
+ | Error | Condition |
81
+ |-------|-----------|
82
+ | `ArgumentError` | `as:` is not a String or Symbol |
83
+ | `SelfAgency::Error` | No generated methods to save |
84
+ | `SelfAgency::Error` | Parent class is anonymous (no name) |
@@ -0,0 +1,74 @@
1
+ # Scopes
2
+
3
+ SelfAgency supports three scopes for generated methods: `:instance`, `:singleton`, and `:class`. The scope is passed as the `scope:` keyword argument to `_()`.
4
+
5
+ ## Instance Scope (Default)
6
+
7
+ Instance methods are available on **all instances** of the class. This is the default scope.
8
+
9
+ ```ruby
10
+ class Greeter
11
+ include SelfAgency
12
+ end
13
+
14
+ alice = Greeter.new
15
+ bob = Greeter.new
16
+
17
+ alice._("an instance method named 'hello' that returns the string 'Hello, world!'")
18
+
19
+ alice.hello #=> "Hello, world!"
20
+ bob.hello #=> "Hello, world!" -- available to all instances
21
+ ```
22
+
23
+ Internally, SelfAgency creates an anonymous module containing the generated method and uses `prepend` to add it to the class. This means the method appears on all current and future instances.
24
+
25
+ ## Singleton Scope
26
+
27
+ Singleton methods are available on **only one specific instance**. Other instances of the same class do not have the method.
28
+
29
+ ```ruby
30
+ alice._("a method named 'secret' that returns 'Alice only'", scope: :singleton)
31
+
32
+ alice.secret #=> "Alice only"
33
+ bob.respond_to?(:secret) #=> false
34
+ ```
35
+
36
+ Internally, singleton-scoped methods are prepended to the instance's singleton class.
37
+
38
+ ## Class Scope
39
+
40
+ Class methods are available on the **class itself**, not on instances.
41
+
42
+ ```ruby
43
+ alice._("a class method named 'self.class_greeting' that returns 'Greetings from Greeter'", scope: :class)
44
+
45
+ Greeter.class_greeting #=> "Greetings from Greeter"
46
+ ```
47
+
48
+ !!! note
49
+ For class methods, the LLM generates `def self.method_name`. SelfAgency strips the `self.` prefix before evaluating, since it prepends the method to the class's singleton class.
50
+
51
+ ## Scope Comparison
52
+
53
+ | Scope | Keyword | Available On | Mechanism |
54
+ |-------|---------|-------------|-----------|
55
+ | Instance | `scope: :instance` | All instances of the class | `self.class.prepend(module)` |
56
+ | Singleton | `scope: :singleton` | One specific instance | `singleton_class.prepend(module)` |
57
+ | Class | `scope: :class` | The class itself | `self.class.singleton_class.prepend(module)` |
58
+
59
+ ## Combining Scopes
60
+
61
+ You can mix scopes freely on the same class:
62
+
63
+ ```ruby
64
+ calc = StatisticsCalculator.new([10, 20, 30])
65
+
66
+ # Instance methods -- available to all instances
67
+ calc._("an instance method named 'mean' ...")
68
+
69
+ # Class method -- available on StatisticsCalculator itself
70
+ calc._("a class method named 'self.from_range' ...", scope: :class)
71
+
72
+ # Singleton method -- available only on this calc instance
73
+ calc._("a method named 'report' ...", scope: :singleton)
74
+ ```
@@ -0,0 +1,96 @@
1
+ # Source Inspection
2
+
3
+ `_source_for` returns the source code for any method -- both LLM-generated and file-defined methods.
4
+
5
+ ## Instance-Level Inspection
6
+
7
+ Call `_source_for` on an instance to retrieve source code:
8
+
9
+ ```ruby
10
+ class MathHelper
11
+ include SelfAgency
12
+
13
+ # Multiplies a number by two.
14
+ def double(n)
15
+ n * 2
16
+ end
17
+ end
18
+
19
+ helper = MathHelper.new
20
+ helper._("an instance method named 'square' that accepts an integer n and returns n * n")
21
+ ```
22
+
23
+ For LLM-generated methods, `_source_for` returns the code with the original description as a comment header:
24
+
25
+ ```ruby
26
+ puts helper._source_for(:square)
27
+ # >> # an instance method named 'square' that accepts an integer n and returns n * n
28
+ # >> def square(n)
29
+ # >> n * n
30
+ # >> end
31
+ ```
32
+
33
+ ## Class-Level Inspection
34
+
35
+ `_source_for` is also available as a class method:
36
+
37
+ ```ruby
38
+ puts MathHelper._source_for(:square)
39
+ # >> # an instance method named 'square' that accepts an integer n and returns n * n
40
+ # >> def square(n)
41
+ # >> n * n
42
+ # >> end
43
+ ```
44
+
45
+ Both instance and class level lookups return the same source for LLM-generated methods.
46
+
47
+ ## File-Defined Methods
48
+
49
+ For methods defined in source files (not generated by the LLM), `_source_for` falls back to the `method_source` gem. It includes any comments directly above the method definition:
50
+
51
+ ```ruby
52
+ puts helper._source_for(:double)
53
+ # >> # Multiplies a number by two.
54
+ # >> def double(n)
55
+ # >> n * 2
56
+ # >> end
57
+ ```
58
+
59
+ ## Unknown Methods
60
+
61
+ If a method doesn't exist or its source is unavailable, `_source_for` returns `nil`:
62
+
63
+ ```ruby
64
+ helper._source_for(:nonexistent) #=> nil
65
+ ```
66
+
67
+ ## Version History
68
+
69
+ When a method is regenerated (e.g., with a refined description), SelfAgency tracks each version. Use `_source_versions_for` at the class level to retrieve the history:
70
+
71
+ ```ruby
72
+ helper._("an instance method named 'square' that accepts n and returns n * n")
73
+ helper._("an instance method named 'square' that accepts n, raises ArgumentError if n < 0, and returns n * n")
74
+
75
+ versions = MathHelper._source_versions_for(:square)
76
+ versions.size #=> 2
77
+
78
+ versions.each do |v|
79
+ puts "#{v[:at]} — #{v[:description]}"
80
+ puts v[:code]
81
+ puts
82
+ end
83
+ ```
84
+
85
+ Each version entry is a Hash with keys `:code`, `:description`, `:instance_id`, and `:at`.
86
+
87
+ Returns an empty array if no versions exist for the method.
88
+
89
+ ## How It Works
90
+
91
+ `_source_for` checks two sources in order:
92
+
93
+ 1. **LLM-generated source** -- Stored in an internal hash when `_()` creates the method
94
+ 2. **File source** -- Falls back to `method_source` gem for methods defined in `.rb` files
95
+
96
+ If both lookups fail (method doesn't exist or source file can't be located), `nil` is returned.
data/docs/index.md ADDED
@@ -0,0 +1,77 @@
1
+ <div align="center">
2
+ <h1>SelfAgency</h1>
3
+ Describe what you want in plain language, get working methods back.<br/>
4
+ SelfAgency is a mixin module that gives any Ruby class the ability to<br/>
5
+ generate and install methods at runtime via an LLM.<br/>
6
+ <br/>
7
+ <img src="assets/images/self_agency.gif" alt="SelfAgency Demo" width="100%">
8
+ <h2>Key Features</h2>
9
+ </div>
10
+
11
+ <table>
12
+ <tr>
13
+ <td width="50%" valign="top">
14
+ <ul>
15
+ <li><strong>Natural language to Ruby methods</strong> — describe what you want, get working code</li>
16
+ <li><strong>Multiple methods at once</strong> — generate related methods in a single call</li>
17
+ <li><strong>Three scopes</strong> — instance, singleton, and class methods</li>
18
+ <li><strong>Two-stage LLM pipeline</strong> — shape the prompt, then generate code</li>
19
+ </ul>
20
+ </td>
21
+ <td width="50%" valign="top">
22
+ <ul>
23
+ <li><strong>Security by default</strong> — 26 static patterns + runtime sandbox</li>
24
+ <li><strong>Automatic retries</strong> — self-corrects on validation failure</li>
25
+ <li><strong>Source inspection &amp; versioning</strong> — view code and track history</li>
26
+ <li><strong>Provider agnostic</strong> — any LLM via <a href="https://github.com/crmne/ruby_llm">ruby_llm</a></li>
27
+ </ul>
28
+ </td>
29
+ </tr>
30
+ </table>
31
+
32
+ > [!CAUTION]
33
+ > This is an experiment. It may not be fit for any specific purpose. Its micro-prompting. Instead of asking Claude Code, CodeX or Gemini to create an entire application, you can use SelfAgency to generate individual methods. So far the experiments are showing good success with methods that perform math stuff on its input.
34
+
35
+
36
+ ## Quick Example
37
+
38
+ ```ruby
39
+ require "self_agency"
40
+
41
+ SelfAgency.configure do |config|
42
+ config.provider = :ollama
43
+ config.model = "qwen3-coder:30b"
44
+ config.api_base = "http://localhost:11434/v1"
45
+ end
46
+
47
+ class Calculator
48
+ include SelfAgency
49
+ end
50
+
51
+ calc = Calculator.new
52
+ calc._("an instance method to add two integers, return the result")
53
+ #=> [:add]
54
+
55
+ calc.add(3, 7)
56
+ #=> 10
57
+ ```
58
+
59
+ ## How It Works
60
+
61
+ Your casual description is first "shaped" into a precise Ruby method specification, then passed through a multi-stage pipeline:
62
+
63
+ 1. **Shape** -- Rewrites your casual description into a precise Ruby method specification
64
+ 2. **Generate** -- Produces `def...end` block(s) from the shaped spec
65
+ 3. **Sanitize** -- Strips markdown fences and `<think>` blocks
66
+ 4. **Validate** -- Checks for empty code, missing `def...end`, syntax errors, and dangerous patterns
67
+ 5. **Retry** -- On validation/security failure, feeds the error back to the LLM for self-correction (up to `generation_retries` attempts)
68
+ 6. **Sandbox Eval** -- Evaluates code inside a cached sandbox module that shadows dangerous Kernel methods
69
+
70
+ ## Requirements
71
+
72
+ - Ruby >= 3.2.0
73
+ - An LLM provider (Ollama by default, or any provider supported by ruby_llm)
74
+
75
+ ## Getting Started
76
+
77
+ Head to the [Installation](getting-started/installation.md) guide to add SelfAgency to your project, then follow the [Quick Start](getting-started/quick-start.md) for a complete walkthrough. For a broader perspective on where SelfAgency fits in your development process, see [How to Use SelfAgency](guide/how-to-use.md).
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # 01_basic_usage.rb — Single method generation
5
+ #
6
+ # Demonstrates:
7
+ # - include SelfAgency
8
+ # - _() to generate a method from a description
9
+ # - Calling the generated method
10
+ #
11
+ # Requires a running Ollama instance with the configured model.
12
+
13
+ require_relative "lib/setup"
14
+
15
+ class Calculator
16
+ include SelfAgency
17
+ end
18
+
19
+ calc = Calculator.new
20
+
21
+ puts "Generating a method to add two integers..."
22
+ method_names = calc._("an instance method named 'add' that accepts two integer parameters a and b, and returns their sum")
23
+
24
+ puts "Generated methods: #{method_names.inspect}"
25
+ puts "calc.add(3, 7) = #{calc.add(3, 7)}"
26
+ puts "calc.add(-1, 1) = #{calc.add(-1, 1)}"
27
+ puts "calc.add(100, 200) = #{calc.add(100, 200)}"
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # 02_multiple_methods.rb — Multiple methods from one call
5
+ #
6
+ # Demonstrates:
7
+ # - Generating several related methods in a single _() call
8
+ # - _() returns an Array of Symbols
9
+ # - Calling each generated method
10
+ #
11
+ # Requires a running Ollama instance with the configured model.
12
+
13
+ require_relative "lib/setup"
14
+
15
+ class Arithmetic
16
+ include SelfAgency
17
+ end
18
+
19
+ arith = Arithmetic.new
20
+
21
+ puts "Generating four arithmetic methods in a single call..."
22
+ method_names = arith._(
23
+ "create four instance methods: " \
24
+ "'add(a, b)' returns a + b, " \
25
+ "'subtract(a, b)' returns a - b, " \
26
+ "'multiply(a, b)' returns a * b, " \
27
+ "'divide(a, b)' returns a.to_f / b (raises ZeroDivisionError if b is zero)"
28
+ )
29
+
30
+ puts "_() returned: #{method_names.inspect}"
31
+ puts "Type: #{method_names.class}"
32
+ puts "Count: #{method_names.length} methods generated"
33
+ puts
34
+
35
+ method_names.each do |name|
36
+ puts " #{name} is now defined"
37
+ end
38
+
39
+ puts
40
+ puts "arith.add(10, 3) = #{arith.add(10, 3)}"
41
+ puts "arith.subtract(10, 3) = #{arith.subtract(10, 3)}"
42
+ puts "arith.multiply(10, 3) = #{arith.multiply(10, 3)}"
43
+ puts "arith.divide(10, 3) = #{arith.divide(10, 3)}"