robot_lab 0.2.8 → 0.3.0
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 +4 -4
- data/.envrc +2 -2
- data/CHANGELOG.md +53 -0
- data/CLAUDE.md +4 -2
- data/README.md +15 -17
- data/docs/architecture/core-concepts.md +1 -1
- data/docs/architecture/state-management.md +4 -4
- data/docs/concepts.md +1 -1
- data/docs/getting-started/installation.md +1 -4
- data/docs/guides/memory.md +2 -2
- data/examples/.envrc +2 -0
- data/examples/02_tools.rb +8 -8
- data/examples/03_network.rb +1 -1
- data/examples/04_mcp.rb +7 -5
- data/examples/08_llm_config.rb +5 -5
- data/examples/09_chaining.rb +3 -3
- data/examples/14_rusty_circuit/comic.rb +8 -8
- data/examples/14_rusty_circuit/scout.rb +4 -4
- data/examples/15_memory_network_and_bus/README.md +66 -0
- data/examples/15_memory_network_and_bus/output/combined_article.md +5 -7
- data/examples/15_memory_network_and_bus/output/final_article.md +5 -10
- data/examples/15_memory_network_and_bus/output/linux_draft.md +3 -3
- data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
- data/examples/15_memory_network_and_bus/output/memory.json +6 -6
- data/examples/15_memory_network_and_bus/output/revision_1.md +21 -10
- data/examples/15_memory_network_and_bus/output/revision_2.md +43 -6
- data/examples/15_memory_network_and_bus/output/revision_3.md +8 -0
- data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
- data/examples/16_writers_room/tools.rb +14 -14
- data/examples/19_token_tracking.rb +2 -2
- data/examples/20_circuit_breaker.rb +3 -3
- data/examples/22_context_compression.rb +1 -1
- data/examples/27_incident_response/README.md +65 -0
- data/examples/28_mcp_discovery.rb +2 -2
- data/examples/29_ractor_tools.rb +4 -4
- data/examples/30_ractor_network.rb +2 -2
- data/examples/33_stock_predictor.rb +8 -8
- data/examples/35_hooks.rb +3 -3
- data/examples/README.md +17 -0
- data/examples/common.rb +55 -23
- data/examples/run_all.rb +60 -0
- data/lib/robot_lab/ask_user.rb +3 -3
- data/lib/robot_lab/config/defaults.yml +5 -5
- data/lib/robot_lab/config.rb +2 -2
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -3
- data/lib/robot_lab/memory.rb +16 -7
- data/lib/robot_lab/robot/hooking.rb +26 -0
- data/lib/robot_lab/robot/result_building.rb +119 -0
- data/lib/robot_lab/robot.rb +37 -120
- data/lib/robot_lab/run_config.rb +52 -20
- data/lib/robot_lab/tool.rb +7 -12
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +3 -3
- metadata +18 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 411c496dec788609f2ac0d2e1eb02ce7fd0523f40424b9437de0ffb24c00c644
|
|
4
|
+
data.tar.gz: 9fb9bb55b865734dc7c9ae1ac8d1994685575c1ee206d6cf9865c0af3d3a522c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b34ce72ebe6443c5f25423a595c0ffd468416ea1be02531b125efed33289ae8ea898c5413697fe373cebd658bddb510f322ef09623bb7b74f7587cf58b9ad064
|
|
7
|
+
data.tar.gz: e9884f0c8b501bfc57eafdc6c5492fea7455eb807267c370fc68b59e7322f97c0360e3af6f7e61b7c2493df3e2026f72095b9c287e65e6bf5f1ec5274948ef7d
|
data/.envrc
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,59 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [0.3.0] - 2026-09-18
|
|
10
|
+
|
|
11
|
+
The ruby_llm 2.0 release. Core moves from ruby_llm 1.x to the official 2.0.0
|
|
12
|
+
release; every `robot_lab-*` extension gem releases in lockstep at 0.3.0 and
|
|
13
|
+
now requires `robot_lab ~> 0.3.0`.
|
|
14
|
+
|
|
15
|
+
### Changed (BREAKING)
|
|
16
|
+
- **ruby_llm 1.x → 2.0.** The gemspec now requires `ruby_llm "~> 2.0"`
|
|
17
|
+
(official release). Robot tools written with the 1.x DSL must migrate:
|
|
18
|
+
`param` → `parameter`, `desc:` → `description:`; `halt` is gone (the caller
|
|
19
|
+
controls the loop). Token counts moved to `response.tokens.input/.output`,
|
|
20
|
+
and `finish_reason` (Symbol) replaces `stop_reason`. The `claude-sonnet-4`
|
|
21
|
+
registry alias no longer resolves; the default models are now
|
|
22
|
+
`claude-sonnet-4-6` (production) and `claude-haiku-4-5` (test).
|
|
23
|
+
- **Dropped hard dependencies** on `ruby_llm-mcp`, `ruby_llm-schema`, and
|
|
24
|
+
`ruby_llm-semantic_cache`. Semantic caching is now optional: `Memory#cache`
|
|
25
|
+
returns `nil` when `ruby_llm-semantic_cache` is not installed and Memory
|
|
26
|
+
runs with caching disabled. (Its released versions still pin ruby_llm 1.x,
|
|
27
|
+
so it cannot co-resolve with 2.0 yet.)
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- `ruby_llm-providers-apfel` and `ruby_llm-providers-lms` (`~> 0.2.1`) as
|
|
31
|
+
runtime dependencies, consumed as released gems — the local `path:`
|
|
32
|
+
overrides for sibling checkouts are gone from the Gemfiles.
|
|
33
|
+
- `Robot::ResultBuilding` — extracted module that adapts a ruby_llm response
|
|
34
|
+
into a `RobotResult` (token accounting, stop-reason normalization, message
|
|
35
|
+
coercion).
|
|
36
|
+
- `examples/run_all.rb` — runs every executable `NN_*.rb` demo serially with
|
|
37
|
+
a banner between each; works from any cwd, and Ctrl-C terminates only the
|
|
38
|
+
demo currently running (the runner notes it and moves on).
|
|
39
|
+
- READMEs for the subdirectory demos: `15_memory_network_and_bus`,
|
|
40
|
+
`26_document_store` (corpus), and `27_incident_response`.
|
|
41
|
+
- Fasterer quality gate now actually runs: `fasterer` added to the
|
|
42
|
+
development group, with a `.fasterer.yml` documenting the disabled
|
|
43
|
+
speed-over-readability checks.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- Robot, Tool, RunConfig, and the hook system adapted to the ruby_llm 2.0
|
|
47
|
+
API; docs and examples synced to match.
|
|
48
|
+
- Examples run against a local LM Studio server through the `:lms` provider;
|
|
49
|
+
`examples/common.rb` anchors `BUNDLE_GEMFILE` to the gem root so demos run
|
|
50
|
+
from any directory, and `examples/README.md` notes that demo speed and
|
|
51
|
+
answer quality depend on the provider/model in use.
|
|
52
|
+
- `.envrc` no longer exports `BUNDLE_GEMFILE`; it is inherited from the
|
|
53
|
+
project root (`asgard dev` / `asgard prod`).
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
- amazing_print 3.0 crash at demo startup (`undefined method 'try' for
|
|
57
|
+
module ActiveSupport`): robot_lab's dependencies load parts of
|
|
58
|
+
ActiveSupport, which tricked amazing_print into loading its Rails
|
|
59
|
+
extension; `examples/common.rb` now preloads
|
|
60
|
+
`active_support/core_ext/object/try` and `active_support/log_subscriber`.
|
|
61
|
+
|
|
9
62
|
## [0.2.8] - 2026-09-09
|
|
10
63
|
|
|
11
64
|
### Added
|
data/CLAUDE.md
CHANGED
|
@@ -55,7 +55,7 @@ bundle exec rake examples:run[1]
|
|
|
55
55
|
|
|
56
56
|
### Memory System
|
|
57
57
|
|
|
58
|
-
- **`Memory`** (`lib/robot_lab/memory.rb`): Key-value store with reserved keys (`:data`, `:results`, `:messages`, `:session_id`, `:cache`). Supports Redis backend.
|
|
58
|
+
- **`Memory`** (`lib/robot_lab/memory.rb`): Key-value store with reserved keys (`:data`, `:results`, `:messages`, `:session_id`, `:cache`). Supports Redis backend. Semantic caching via RubyLLM::SemanticCache when the optional ruby_llm-semantic_cache gem is installed (`memory.cache` is nil otherwise)
|
|
59
59
|
|
|
60
60
|
### MCP (Model Context Protocol)
|
|
61
61
|
|
|
@@ -119,7 +119,9 @@ Router receives `Router::Args` with: `context`, `network`, `stack`, `call_count`
|
|
|
119
119
|
|
|
120
120
|
## Dependencies
|
|
121
121
|
|
|
122
|
-
Core: zeitwerk, ruby_llm (~>
|
|
122
|
+
Core: zeitwerk, ruby_llm (~> 2.0.0.rc3), prompt_manager, async, simple_flow, state_machines
|
|
123
|
+
|
|
124
|
+
Optional: ruby_llm-semantic_cache (Memory#cache; no ruby_llm 2.0-compatible release yet — Memory runs with caching disabled when absent), classifier (compress_history), ruby_llm-providers-lms (local LM Studio provider used by the examples: qwen/qwen3.8-27b for complex work, openai/gpt-oss-20b for simple demos)
|
|
123
125
|
|
|
124
126
|
### Templates
|
|
125
127
|
|
data/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- <strong>Human-in-the-Loop</strong> - AskUser tool for interactive prompting<br>
|
|
22
22
|
- <strong>Content Streaming</strong> - Stored callbacks, per-call blocks, or both<br>
|
|
23
23
|
- <strong>MCP Integration</strong> - Connect to external tool servers with timeouts and retry<br>
|
|
24
|
-
- <strong>Local LLM Providers</strong> - Ollama and GPUStack via provider passthrough<br>
|
|
24
|
+
- <strong>Local LLM Providers</strong> - LM Studio (via ruby_llm-providers-lms), Ollama, and GPUStack via provider passthrough<br>
|
|
25
25
|
- <strong>Shared Memory</strong> - Reactive key-value store with subscriptions<br>
|
|
26
26
|
- <strong>Message Bus</strong> - Bidirectional robot communication via TypedBus<br>
|
|
27
27
|
- <strong>Dynamic Spawning</strong> - Robots create new robots at runtime<br>
|
|
@@ -82,17 +82,15 @@ puts result.last_text_content
|
|
|
82
82
|
|
|
83
83
|
### Local LLM Providers
|
|
84
84
|
|
|
85
|
-
For local LLM providers, use the `provider:` parameter. RubyLLM's provider list is the authority on valid values — `:ollama` and `:gpustack` are
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
export ROBOT_LAB_RUBY_LLM__OLLAMA_API_BASE=http://localhost:11434/v1
|
|
89
|
-
```
|
|
85
|
+
For local LLM providers, use the `provider:` parameter. RubyLLM's provider list is the authority on valid values — `:ollama` and `:gpustack` are built in, and provider gems add more (alongside `:anthropic`, `:azure`, `:bedrock`, `:deepseek`, `:gemini`, `:mistral`, `:openai`, `:openrouter`, `:perplexity`, `:vertexai`, and `:xai`). The examples in this repo use LM Studio through the [ruby_llm-providers-lms](https://github.com/madbomber/ruby_llm-providers-lms) gem, which registers the `:lms` provider (defaults to `http://localhost:1234/v1`, no API key needed):
|
|
90
86
|
|
|
91
87
|
```ruby
|
|
88
|
+
require "ruby_llm/providers/lms"
|
|
89
|
+
|
|
92
90
|
robot = RobotLab.build(
|
|
93
91
|
name: "local_bot",
|
|
94
|
-
model: "
|
|
95
|
-
provider: :
|
|
92
|
+
model: "qwen/qwen3.8-27b", # complex work; use "openai/gpt-oss-20b" for simple chat
|
|
93
|
+
provider: :lms,
|
|
96
94
|
system_prompt: "You are a helpful assistant."
|
|
97
95
|
)
|
|
98
96
|
```
|
|
@@ -113,12 +111,12 @@ RobotLab uses [MywayConfig](https://github.com/MadBomber/myway_config) for layer
|
|
|
113
111
|
# Set API keys via environment variables (double underscore for nesting)
|
|
114
112
|
export ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
115
113
|
export ROBOT_LAB_RUBY_LLM__OPENAI_API_KEY=sk-...
|
|
116
|
-
export ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
|
|
114
|
+
export ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4-6
|
|
117
115
|
```
|
|
118
116
|
|
|
119
117
|
```ruby
|
|
120
118
|
# Access configuration values
|
|
121
|
-
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
119
|
+
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4-6"
|
|
122
120
|
RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
123
121
|
```
|
|
124
122
|
|
|
@@ -126,7 +124,7 @@ Or create a project config file at `./config/robot_lab.yml`:
|
|
|
126
124
|
|
|
127
125
|
```yaml
|
|
128
126
|
ruby_llm:
|
|
129
|
-
model: claude-sonnet-4
|
|
127
|
+
model: claude-sonnet-4-6
|
|
130
128
|
anthropic_api_key: sk-ant-...
|
|
131
129
|
request_timeout: 180
|
|
132
130
|
```
|
|
@@ -202,7 +200,7 @@ mcp:
|
|
|
202
200
|
type: stdio
|
|
203
201
|
command: npx
|
|
204
202
|
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
205
|
-
model: claude-sonnet-4
|
|
203
|
+
model: claude-sonnet-4-6
|
|
206
204
|
---
|
|
207
205
|
You are a GitHub assistant. Use available tools to help with repository tasks.
|
|
208
206
|
```
|
|
@@ -265,7 +263,7 @@ robot = RobotLab.build(
|
|
|
265
263
|
```ruby
|
|
266
264
|
# Create a shared config
|
|
267
265
|
shared = RobotLab::RunConfig.new(
|
|
268
|
-
model: "claude-sonnet-4",
|
|
266
|
+
model: "claude-sonnet-4-6",
|
|
269
267
|
temperature: 0.7,
|
|
270
268
|
max_tokens: 2000
|
|
271
269
|
)
|
|
@@ -302,16 +300,16 @@ RunConfig supports keyword construction, block DSL, and merge semantics:
|
|
|
302
300
|
```ruby
|
|
303
301
|
# Block DSL
|
|
304
302
|
config = RobotLab::RunConfig.new do |c|
|
|
305
|
-
c.model "claude-sonnet-4"
|
|
303
|
+
c.model "claude-sonnet-4-6"
|
|
306
304
|
c.temperature 0.7
|
|
307
305
|
end
|
|
308
306
|
|
|
309
307
|
# Merge (more-specific wins)
|
|
310
|
-
network_config = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
|
|
308
|
+
network_config = RobotLab::RunConfig.new(model: "claude-sonnet-4-6", temperature: 0.5)
|
|
311
309
|
robot_config = RobotLab::RunConfig.new(temperature: 0.9)
|
|
312
310
|
effective = network_config.merge(robot_config)
|
|
313
311
|
effective.temperature #=> 0.9
|
|
314
|
-
effective.model #=> "claude-sonnet-4"
|
|
312
|
+
effective.model #=> "claude-sonnet-4-6"
|
|
315
313
|
```
|
|
316
314
|
|
|
317
315
|
### Chaining Configuration
|
|
@@ -323,7 +321,7 @@ robot = RobotLab.build(name: "writer", system_prompt: "You are a creative writer
|
|
|
323
321
|
|
|
324
322
|
result = robot
|
|
325
323
|
.with_temperature(0.9)
|
|
326
|
-
.with_model("claude-sonnet-4")
|
|
324
|
+
.with_model("claude-sonnet-4-6")
|
|
327
325
|
.run("Write a haiku about Ruby programming")
|
|
328
326
|
```
|
|
329
327
|
|
|
@@ -226,7 +226,7 @@ end
|
|
|
226
226
|
| `:results` | `Array` | Accumulated robot results |
|
|
227
227
|
| `:messages` | `Array` | Conversation history |
|
|
228
228
|
| `:session_id` | `String` | Session identifier |
|
|
229
|
-
| `:cache` | `Module` | Semantic cache (RubyLLM::SemanticCache) |
|
|
229
|
+
| `:cache` | `Module`, `nil` | Semantic cache (RubyLLM::SemanticCache when the optional gem is installed) |
|
|
230
230
|
|
|
231
231
|
### Reactive Features
|
|
232
232
|
|
|
@@ -13,7 +13,7 @@ memory.data # StateProxy - custom key-value data with method-style access
|
|
|
13
13
|
memory.results # Array<RobotResult> - execution history
|
|
14
14
|
memory.messages # Array<Message> - conversation history
|
|
15
15
|
memory.session_id # String - optional persistence identifier
|
|
16
|
-
memory.cache # RubyLLM::SemanticCache
|
|
16
|
+
memory.cache # RubyLLM::SemanticCache when the optional gem is installed, else nil
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Standalone Robot Memory
|
|
@@ -108,7 +108,7 @@ Memory has five reserved keys with special behavior and dedicated accessors:
|
|
|
108
108
|
| `:results` | `Array<RobotResult>` | Accumulated robot execution results |
|
|
109
109
|
| `:messages` | `Array<Message>` | Conversation history |
|
|
110
110
|
| `:session_id` | `String` | Conversation session identifier |
|
|
111
|
-
| `:cache` | `RubyLLM::SemanticCache` | Semantic cache module (read-only after init) |
|
|
111
|
+
| `:cache` | `RubyLLM::SemanticCache` or `nil` | Semantic cache module when the optional ruby_llm-semantic_cache gem is installed (read-only after init) |
|
|
112
112
|
|
|
113
113
|
Reserved keys are accessed through dedicated methods and are excluded from `memory.keys`:
|
|
114
114
|
|
|
@@ -118,7 +118,7 @@ memory.data.category #=> "billing" (method-style via StateProxy)
|
|
|
118
118
|
|
|
119
119
|
memory.results #=> []
|
|
120
120
|
memory.session_id #=> nil
|
|
121
|
-
memory.cache #=> RubyLLM::SemanticCache (the module itself), or nil when enable_cache: false
|
|
121
|
+
memory.cache #=> RubyLLM::SemanticCache (the module itself), or nil when enable_cache: false or the optional gem is absent
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
## StateProxy
|
|
@@ -319,7 +319,7 @@ memory = Memory.from_hash(hash)
|
|
|
319
319
|
|
|
320
320
|
## Semantic Cache
|
|
321
321
|
|
|
322
|
-
Memory includes a semantic cache via `RubyLLM::SemanticCache` that reduces costs and latency by returning cached responses for semantically equivalent queries:
|
|
322
|
+
Memory includes a semantic cache via `RubyLLM::SemanticCache` (optional ruby_llm-semantic_cache gem; not yet ruby_llm 2.0-compatible, so `memory.cache` is `nil` without it) that reduces costs and latency by returning cached responses for semantically equivalent queries:
|
|
323
323
|
|
|
324
324
|
```ruby
|
|
325
325
|
# Using the cache with fetch
|
data/docs/concepts.md
CHANGED
|
@@ -338,7 +338,7 @@ robot.clear_messages(keep_system: true)
|
|
|
338
338
|
| `:results` | Accumulated robot results |
|
|
339
339
|
| `:messages` | Conversation history |
|
|
340
340
|
| `:session_id` | Session identifier for history persistence |
|
|
341
|
-
| `:cache` | Semantic cache
|
|
341
|
+
| `:cache` | Semantic cache (RubyLLM::SemanticCache when the optional gem is installed, else nil) |
|
|
342
342
|
|
|
343
343
|
### Reactive Memory in Networks
|
|
344
344
|
|
|
@@ -35,13 +35,10 @@ RobotLab automatically installs these core dependencies:
|
|
|
35
35
|
|
|
36
36
|
| Gem | Purpose |
|
|
37
37
|
|-----|---------|
|
|
38
|
-
| `ruby_llm` (~>
|
|
38
|
+
| `ruby_llm` (~> 2.0.0.rc3) | LLM provider integrations (Anthropic, OpenAI, Gemini, etc.) |
|
|
39
39
|
| `prompt_manager` (~> 1.0) | Template-based prompt management with YAML front matter |
|
|
40
40
|
| `simple_flow` (~> 0.4) | Pipeline workflow execution for networks |
|
|
41
41
|
| `myway_config` (~> 0.1) | Layered configuration (defaults, env vars, config files) |
|
|
42
|
-
| `ruby_llm-mcp` (~> 1.0) | Model Context Protocol client for external tool servers |
|
|
43
|
-
| `ruby_llm-schema` (~> 0.3) | Schema validation for structured outputs |
|
|
44
|
-
| `ruby_llm-semantic_cache` (~> 0.1) | Semantic caching for LLM responses |
|
|
45
42
|
| `zeitwerk` (~> 2.6) | Autoloading and eager loading |
|
|
46
43
|
| `async` (~> 2.0) | Fiber-based concurrency |
|
|
47
44
|
| `async-http` (~> 0.60) | MCP SSE and streamable-HTTP transports |
|
data/docs/guides/memory.md
CHANGED
|
@@ -10,7 +10,7 @@ Memory is a reactive key-value store that provides:
|
|
|
10
10
|
- Reserved keys for structured data (`:data`, `:results`, `:messages`, `:session_id`, `:cache`)
|
|
11
11
|
- Reactive subscriptions and blocking reads for inter-robot communication
|
|
12
12
|
- Optional Redis backend for persistence
|
|
13
|
-
- Semantic caching via `RubyLLM::SemanticCache`
|
|
13
|
+
- Semantic caching via `RubyLLM::SemanticCache` (optional ruby_llm-semantic_cache gem)
|
|
14
14
|
|
|
15
15
|
## Standalone Robot Memory
|
|
16
16
|
|
|
@@ -86,7 +86,7 @@ Memory has reserved keys with special behavior:
|
|
|
86
86
|
| `:results` | Array | Accumulated robot results |
|
|
87
87
|
| `:messages` | Array | Conversation history |
|
|
88
88
|
| `:session_id` | String | Session identifier for history persistence |
|
|
89
|
-
| `:cache` | `RubyLLM::SemanticCache` module, or `nil` | Semantic cache (read-only after init). Set at construction time; `nil` when built with `enable_cache: false` |
|
|
89
|
+
| `:cache` | `RubyLLM::SemanticCache` module, or `nil` | Semantic cache (read-only after init). Set at construction time; `nil` when built with `enable_cache: false` or when the optional gem is absent |
|
|
90
90
|
|
|
91
91
|
### The Data Hash
|
|
92
92
|
|
data/examples/.envrc
CHANGED
data/examples/02_tools.rb
CHANGED
|
@@ -14,17 +14,17 @@ require_relative "common"
|
|
|
14
14
|
class Calculator < RubyLLM::Tool
|
|
15
15
|
description "Performs basic arithmetic operations"
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
parameter :operation,
|
|
18
18
|
type: "string",
|
|
19
|
-
|
|
19
|
+
description: "The operation to perform (add, subtract, multiply, divide)"
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
parameter :a,
|
|
22
22
|
type: "number",
|
|
23
|
-
|
|
23
|
+
description: "First operand"
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
parameter :b,
|
|
26
26
|
type: "number",
|
|
27
|
-
|
|
27
|
+
description: "Second operand"
|
|
28
28
|
|
|
29
29
|
def execute(operation:, a:, b:)
|
|
30
30
|
case operation
|
|
@@ -40,9 +40,9 @@ end
|
|
|
40
40
|
class FortuneCookie < RubyLLM::Tool
|
|
41
41
|
description "Get a fortune cookie message with wisdom and lucky numbers"
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
parameter :category,
|
|
44
44
|
type: "string",
|
|
45
|
-
|
|
45
|
+
description: "The category of fortune (wisdom, love, career, adventure)"
|
|
46
46
|
|
|
47
47
|
FORTUNES = {
|
|
48
48
|
"wisdom" => [
|
data/examples/03_network.rb
CHANGED
|
@@ -40,7 +40,7 @@ end
|
|
|
40
40
|
|
|
41
41
|
# Shared RunConfig — operational defaults every robot in this network inherits.
|
|
42
42
|
#
|
|
43
|
-
# RunConfig has no `provider` field (see RunConfig::FIELDS), and an
|
|
43
|
+
# RunConfig has no `provider` field (see RunConfig::FIELDS), and an LM Studio
|
|
44
44
|
# model is absent from RubyLLM's registry, so provider and model still travel
|
|
45
45
|
# together on each robot via **llm_opts. The RunConfig carries the settings
|
|
46
46
|
# that genuinely are shared.
|
data/examples/04_mcp.rb
CHANGED
|
@@ -163,14 +163,16 @@ begin
|
|
|
163
163
|
puts "Query: 'What are the top 3 most starred Ruby web frameworks on GitHub?'"
|
|
164
164
|
hr
|
|
165
165
|
|
|
166
|
-
# mcp: :inherit
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
166
|
+
# mcp: :inherit is required — run() defaults it to :none, which resolves to
|
|
167
|
+
# "no MCP servers this turn" and the discovered tools would never reach the
|
|
168
|
+
# provider. tools: takes an allowlist of tool names; :inherit would send all
|
|
169
|
+
# ~90 GitHub tools, whose JSON schemas alone exceed 13k tokens and overflow
|
|
170
|
+
# a local model's context window (LM Studio's default is 8192) before the
|
|
171
|
+
# query even arrives. Send only the one tool this query needs.
|
|
170
172
|
result = robot.run(
|
|
171
173
|
"What are the top 3 most starred Ruby web frameworks on GitHub? Just list their names and star counts.",
|
|
172
174
|
mcp: :inherit,
|
|
173
|
-
tools:
|
|
175
|
+
tools: %w[search_repositories]
|
|
174
176
|
)
|
|
175
177
|
|
|
176
178
|
puts
|
data/examples/08_llm_config.rb
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
# Environment is determined by ROBOT_LAB_ENV, RAILS_ENV, or RACK_ENV.
|
|
21
21
|
#
|
|
22
22
|
# Environment variable examples:
|
|
23
|
-
# ROBOT_LAB_RUBY_LLM__MODEL=qwen3.
|
|
23
|
+
# ROBOT_LAB_RUBY_LLM__MODEL=qwen/qwen3.8-27b
|
|
24
24
|
# ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
|
|
25
|
-
#
|
|
25
|
+
# LMS_API_BASE=http://localhost:1234/v1
|
|
26
26
|
#
|
|
27
27
|
# Usage:
|
|
28
28
|
# ruby examples/08_llm_config.rb
|
|
@@ -63,7 +63,7 @@ puts " max_retries: #{config.ruby_llm.max_retries}"
|
|
|
63
63
|
puts " log_level: #{config.ruby_llm.log_level}"
|
|
64
64
|
|
|
65
65
|
# Show where the LLM traffic actually goes
|
|
66
|
-
puts "
|
|
66
|
+
puts " lms_api_base: #{LMS_API_BASE}"
|
|
67
67
|
puts " (local inference — no API key in play)"
|
|
68
68
|
puts
|
|
69
69
|
|
|
@@ -92,7 +92,7 @@ shared = RobotLab::RunConfig.new(model: LLM[:default].model, temperature: 0.5)
|
|
|
92
92
|
puts " shared = RunConfig.new(model: #{LLM[:default].model.inspect}, temperature: 0.5)"
|
|
93
93
|
puts " shared.to_h => #{shared.to_h.inspect}"
|
|
94
94
|
puts
|
|
95
|
-
puts " Note: RunConfig carries no `provider` field, so an
|
|
95
|
+
puts " Note: RunConfig carries no `provider` field, so an LM Studio model still"
|
|
96
96
|
puts " needs provider: passed to each robot alongside the shared config."
|
|
97
97
|
puts
|
|
98
98
|
|
|
@@ -153,7 +153,7 @@ puts <<~FOOTER
|
|
|
153
153
|
Example environment variable overrides:
|
|
154
154
|
ROBOT_LAB_RUBY_LLM__MODEL=#{LLM[:default].model}
|
|
155
155
|
ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
|
|
156
|
-
|
|
156
|
+
LMS_API_BASE=http://localhost:1234/v1
|
|
157
157
|
|
|
158
158
|
Try running with different environments:
|
|
159
159
|
ROBOT_LAB_ENV=test ruby examples/08_llm_config.rb
|
data/examples/09_chaining.rb
CHANGED
|
@@ -233,9 +233,9 @@ interactive.output = output
|
|
|
233
233
|
interactive.input = StringIO.new("2\n")
|
|
234
234
|
|
|
235
235
|
result = demo_tool.call(
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
236
|
+
question: "What type of task should I optimize for?",
|
|
237
|
+
choices: %w[general analysis creative coding research],
|
|
238
|
+
default: "general"
|
|
239
239
|
)
|
|
240
240
|
|
|
241
241
|
puts output.string
|
|
@@ -11,8 +11,8 @@ class ReinventStyle < RobotLab::Tool
|
|
|
11
11
|
"Be bold — try something totally different. " \
|
|
12
12
|
"The new style takes effect on your next bit."
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
parameter :new_persona, type: "string",
|
|
15
|
+
description: "Your new comedy persona, style, and approach. " \
|
|
16
16
|
"Be specific: what kind of humor, what voice, what attitude."
|
|
17
17
|
|
|
18
18
|
def execute(new_persona:)
|
|
@@ -29,10 +29,10 @@ class AdjustEnergy < RobotLab::Tool
|
|
|
29
29
|
"Higher (0.8-1.0) = wilder, riskier, more unpredictable. " \
|
|
30
30
|
"Lower (0.2-0.4) = tighter, more controlled, precise."
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
parameter :level, type: "number",
|
|
33
|
+
description: "Energy level from 0.1 (very controlled) to 1.0 (unhinged)"
|
|
34
|
+
parameter :reason, type: "string",
|
|
35
|
+
description: "Why you're adjusting", required: false
|
|
36
36
|
|
|
37
37
|
def execute(level:, reason: "tactical adjustment")
|
|
38
38
|
clamped = [[level.to_f, 0.1].max, 1.0].min
|
|
@@ -47,8 +47,8 @@ class GetCoaching < RobotLab::Tool
|
|
|
47
47
|
"Use when you're struggling with the crowd and need " \
|
|
48
48
|
"an outside perspective on what to try next."
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
parameter :situation, type: "string",
|
|
51
|
+
description: "Describe what's happening and what you need help with"
|
|
52
52
|
|
|
53
53
|
def execute(situation:)
|
|
54
54
|
@coaches ||= {}
|
|
@@ -10,8 +10,8 @@ class RecruitAnalyst < RobotLab::Tool
|
|
|
10
10
|
"of the comedian's performance. The analyst will " \
|
|
11
11
|
"review your accumulated notes and provide insight."
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
parameter :specialty, type: "string",
|
|
14
|
+
description: "What to analyze: timing, crowd_work, " \
|
|
15
15
|
"originality, adaptability, stage_presence, " \
|
|
16
16
|
"material_evolution"
|
|
17
17
|
|
|
@@ -46,8 +46,8 @@ class RefineCriteria < RobotLab::Tool
|
|
|
46
46
|
"important qualities aren't what you initially expected. " \
|
|
47
47
|
"The update takes effect on your next evaluation."
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
parameter :updated_criteria, type: "string",
|
|
50
|
+
description: "Your refined evaluation criteria and focus areas"
|
|
51
51
|
|
|
52
52
|
def execute(updated_criteria:)
|
|
53
53
|
robot.pending_criteria = updated_criteria
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Example 15: OS Research Editorial Pipeline
|
|
2
|
+
|
|
3
|
+
A multi-robot editorial pipeline that showcases RobotLab's three coordination
|
|
4
|
+
mechanisms — **Network**, **Memory**, and **Bus** — plus dynamic robot
|
|
5
|
+
creation with **spawn()**, all in one demo.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
Three writer robots each advocate for a different operating system (macOS,
|
|
10
|
+
Windows, Linux/BSD) as the base for a home AI research lab:
|
|
11
|
+
|
|
12
|
+
1. **Phase 1 — Writing pipeline (Network + Memory + Spawn).** The network runs
|
|
13
|
+
the three writers in parallel; each stores its draft in shared memory
|
|
14
|
+
(`:mac_draft`, `:windows_draft`, `:linux_draft`). The Linux writer first
|
|
15
|
+
`spawn()`s distro specialists and folds their analyses into its draft. An
|
|
16
|
+
editor robot then synthesizes the drafts into one combined article.
|
|
17
|
+
2. **Phase 2 — Editorial review (Bus).** An editor-in-chief robot lives
|
|
18
|
+
*outside* the network and talks to the editor only over the message bus.
|
|
19
|
+
It reviews the article and replies `APPROVED` or `REVISE: <feedback>`,
|
|
20
|
+
looping until approval or `MAX_REVISIONS` (3) is reached.
|
|
21
|
+
|
|
22
|
+
The demo is lightly interactive: at startup an `AskUser` prompt asks for the
|
|
23
|
+
article's research focus. Press Enter to accept the default ("LLM
|
|
24
|
+
fine-tuning, image generation, and local inference").
|
|
25
|
+
|
|
26
|
+
## How to run
|
|
27
|
+
|
|
28
|
+
From the gem root (an LM Studio server must be running — `lms server start`;
|
|
29
|
+
see `examples/common.rb` for the provider/model configuration):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle exec ruby examples/15_memory_network_and_bus/editorial_pipeline.rb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Note that `examples/run_all.rb` does **not** include this demo — it only runs
|
|
36
|
+
the top-level `NN_*.rb` files, so run it explicitly as above.
|
|
37
|
+
|
|
38
|
+
This is one of the heavier examples (roughly 20-30 LLM calls). On the default
|
|
39
|
+
model expect a long run; `LLM_PROFILE=small` trades answer quality for a much
|
|
40
|
+
shorter one:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
LLM_PROFILE=small bundle exec ruby examples/15_memory_network_and_bus/editorial_pipeline.rb
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Output
|
|
47
|
+
|
|
48
|
+
Everything lands in `output/`:
|
|
49
|
+
|
|
50
|
+
| File | Content |
|
|
51
|
+
|------|---------|
|
|
52
|
+
| `mac_draft.md`, `windows_draft.md`, `linux_draft.md` | each writer's advocacy draft |
|
|
53
|
+
| `combined_article.md` | the editor's synthesis of the three drafts |
|
|
54
|
+
| `revision_N.md` | one file per bus-driven revision round |
|
|
55
|
+
| `final_article.md` | the approved article (header notes APPROVED or NOT APPROVED) |
|
|
56
|
+
| `memory.json` | dump of the shared memory at the end of the run |
|
|
57
|
+
|
|
58
|
+
## Files
|
|
59
|
+
|
|
60
|
+
- `editorial_pipeline.rb` — entry point; builds the robots, network, and bus, then runs both phases
|
|
61
|
+
- `os_writer.rb` — base writer; runs the LLM and stores its draft in shared memory
|
|
62
|
+
- `linux_writer.rb` — extends `OsWriter`; spawns distro specialists before drafting
|
|
63
|
+
- `os_editor.rb` — synthesizes drafts; handles revision requests from the bus
|
|
64
|
+
- `editor_in_chief.rb` — bus-only reviewer; approves or requests revisions
|
|
65
|
+
- `prompts/` — templates for the writers (`os_advocate.md`), editor, and chief
|
|
66
|
+
- `.envrc` — sets `ROBOT_LAB_TEMPLATE_PATH` to `prompts/` for direnv users (the script sets the same fallback itself, so direnv is optional)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# Combined Article (Editor Draft)
|
|
2
2
|
|
|
3
|
-
For a home AI research lab, the operating-system
|
|
3
|
+
For a home AI research lab, the operating-system question is less about which platform is “best” in the abstract and more about which one best fits the workloads, hardware, and maintenance appetite you actually have. macOS, Windows, and Linux/BSD all offer credible paths into local LLM inference, fine-tuning, and image generation, but they optimize for different priorities. Apple Silicon Macs emphasize integrated memory, efficiency, and ease of use. Windows emphasizes broad hardware support and NVIDIA compatibility with a familiar desktop environment. Linux emphasizes direct control, reproducibility, and server-grade workflows. A strong home lab should therefore be built around the OS that matches the intended balance of local experimentation, daily usability, and long-term maintainability.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
macOS is especially compelling when the goal is a compact, low-noise, local-first lab rather than a maximum-throughput training server. High-memory Apple Silicon machines can run quantized open-weight models, embedding pipelines, RAG systems, and image-generation workflows with a level of convenience that is hard to match on a separate GPU workstation. Tools such as MLX, MLX-LM, llama.cpp, Ollama, LM Studio, PyTorch MPS, and Hugging Face’s local inference stack have made Apple Silicon far more capable for practical AI experimentation than earlier generations of Macs. The platform’s strengths are its unified memory architecture, Unix-based developer environment, strong power efficiency, and the ability to keep a powerful local server in a small space without the noise and heat of a traditional GPU rig. Its limitations are equally clear: it does not run NVIDIA CUDA, it is less flexible for large-scale training workloads, and the cost of high-memory configurations can be substantial. macOS is therefore best suited to solo researchers and small teams whose primary needs are local inference, prototyping, evaluation, embeddings, and efficient fine-tuning rather than large multi-GPU training.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Windows offers the broadest practical hardware foundation for many home labs, particularly when NVIDIA GPUs are involved. Its advantage is not merely that it supports CUDA natively, but that it pairs that support with a mature consumer ecosystem and WSL2, which gives access to a near-native Linux environment for tools, containers, and workflows that expect a Linux userspace. That combination allows a home researcher to use Visual Studio Code, Docker Desktop, PyTorch, Hugging Face, TensorBoard, Ollama, llama.cpp, and ComfyUI-style image-generation pipelines while retaining a familiar desktop operating system. Windows also supports a wider range of consumer and workstation GPUs than macOS, and with DirectML, ONNX Runtime, and OpenVINO it can accommodate AMD and Intel acceleration when needed. Its trade-offs are that driver and hardware fragmentation can create more debugging complexity, and WSL2, while excellent, is still an abstraction layer that occasionally complicates containerized or GPU-dependent workflows. Windows is therefore a strong choice for users who want the greatest hardware flexibility, especially when building a high-VRAM local lab around NVIDIA GPUs while still having access to Linux-based tooling.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Linux and BSD are the most natural foundations for a serious, reproducible home research environment. They offer direct control over drivers, kernel settings, container runtimes, and ML frameworks, which is valuable when running multiple workloads such as LLM inference, fine-tuning, image generation, and long-running services on the same machine. Ubuntu, Debian, and Fedora provide mature paths for CUDA, PyTorch, Docker, Podman, vLLM, llama.cpp, Stable Diffusion, and ComfyUI, while FreeBSD can add meaningful value for data integrity, snapshots, and isolated workloads. The main drawback is that Linux generally demands more setup, maintenance, and troubleshooting than macOS or Windows. Driver issues, kernel updates, and container configuration are more visible, and the experience is less turnkey for users who prefer a consumer desktop. For that reason, Linux is best suited to researchers who value control, reproducibility, and a server-like environment over maximum convenience.
|
|
10
10
|
|
|
11
|
-
The
|
|
12
|
-
|
|
13
|
-
My recommendation is nuanced but clear: for most serious home AI labs, Linux is the best long-term platform, with Ubuntu, Debian, or Fedora the safest bets. Windows is the best choice for users who want top consumer GPU performance with less lifestyle disruption, especially on a single versatile workstation. macOS is the best choice for researchers who prioritize stability, efficiency, and a tightly integrated system over absolute training performance. In other words, choose Linux for infrastructure fidelity, Windows for GPU-driven versatility, and macOS for elegant, low-maintenance local experimentation.
|
|
11
|
+
The clearest recommendation is to choose the operating system that matches the primary use case of the lab. If the priority is a quiet, efficient, low-maintenance environment for local inference, embeddings, RAG, and modest fine-tuning, macOS on Apple Silicon is an excellent choice. If the priority is maximum GPU flexibility, especially with NVIDIA hardware, and the user wants a familiar desktop with access to both native Windows tools and Linux-based workflows, Windows with WSL2 is likely the most practical all-round option. If the lab will run multiple long-lived services, containerized experiments, and demanding local workloads, Linux is the strongest foundation, with FreeBSD a valuable companion for storage isolation and data protection. In practice, the best home AI lab is not the one built on the most ideological platform, but the one whose operating system best aligns with the hardware, workflow, and maintenance model the researcher can sustain over time.
|