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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -2
  3. data/CHANGELOG.md +53 -0
  4. data/CLAUDE.md +4 -2
  5. data/README.md +15 -17
  6. data/docs/architecture/core-concepts.md +1 -1
  7. data/docs/architecture/state-management.md +4 -4
  8. data/docs/concepts.md +1 -1
  9. data/docs/getting-started/installation.md +1 -4
  10. data/docs/guides/memory.md +2 -2
  11. data/examples/.envrc +2 -0
  12. data/examples/02_tools.rb +8 -8
  13. data/examples/03_network.rb +1 -1
  14. data/examples/04_mcp.rb +7 -5
  15. data/examples/08_llm_config.rb +5 -5
  16. data/examples/09_chaining.rb +3 -3
  17. data/examples/14_rusty_circuit/comic.rb +8 -8
  18. data/examples/14_rusty_circuit/scout.rb +4 -4
  19. data/examples/15_memory_network_and_bus/README.md +66 -0
  20. data/examples/15_memory_network_and_bus/output/combined_article.md +5 -7
  21. data/examples/15_memory_network_and_bus/output/final_article.md +5 -10
  22. data/examples/15_memory_network_and_bus/output/linux_draft.md +3 -3
  23. data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
  24. data/examples/15_memory_network_and_bus/output/memory.json +6 -6
  25. data/examples/15_memory_network_and_bus/output/revision_1.md +21 -10
  26. data/examples/15_memory_network_and_bus/output/revision_2.md +43 -6
  27. data/examples/15_memory_network_and_bus/output/revision_3.md +8 -0
  28. data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
  29. data/examples/16_writers_room/tools.rb +14 -14
  30. data/examples/19_token_tracking.rb +2 -2
  31. data/examples/20_circuit_breaker.rb +3 -3
  32. data/examples/22_context_compression.rb +1 -1
  33. data/examples/27_incident_response/README.md +65 -0
  34. data/examples/28_mcp_discovery.rb +2 -2
  35. data/examples/29_ractor_tools.rb +4 -4
  36. data/examples/30_ractor_network.rb +2 -2
  37. data/examples/33_stock_predictor.rb +8 -8
  38. data/examples/35_hooks.rb +3 -3
  39. data/examples/README.md +17 -0
  40. data/examples/common.rb +55 -23
  41. data/examples/run_all.rb +60 -0
  42. data/lib/robot_lab/ask_user.rb +3 -3
  43. data/lib/robot_lab/config/defaults.yml +5 -5
  44. data/lib/robot_lab/config.rb +2 -2
  45. data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -3
  46. data/lib/robot_lab/memory.rb +16 -7
  47. data/lib/robot_lab/robot/hooking.rb +26 -0
  48. data/lib/robot_lab/robot/result_building.rb +119 -0
  49. data/lib/robot_lab/robot.rb +37 -120
  50. data/lib/robot_lab/run_config.rb +52 -20
  51. data/lib/robot_lab/tool.rb +7 -12
  52. data/lib/robot_lab/version.rb +1 -1
  53. data/lib/robot_lab.rb +3 -3
  54. metadata +18 -27
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de87b78206bc424703b465d3a1f236db723ea38fe4235c07413466ab5b469ead
4
- data.tar.gz: 6a7db14e4530e615e258415491422f297567a2f640983035f1542d88e7d200e5
3
+ metadata.gz: 411c496dec788609f2ac0d2e1eb02ce7fd0523f40424b9437de0ffb24c00c644
4
+ data.tar.gz: 9fb9bb55b865734dc7c9ae1ac8d1994685575c1ee206d6cf9865c0af3d3a522c
5
5
  SHA512:
6
- metadata.gz: f6814c9a8b86b8e6d1df2adcc2d9b4ab84c176c67211424e8404e9d83c1e08ffd353587523e4c45efec295d0e47bbd731b0cdc6f3e62754cfd4f1ce2a33d7655
7
- data.tar.gz: 37682af098203ae333cc83fa6e66a95f1883aae0bed8411524a05d6795bb47b4a99f231353fe3546b2fdae8a5c098d0f45b863e531edaf237aab33e4e1bc3859
6
+ metadata.gz: b34ce72ebe6443c5f25423a595c0ffd468416ea1be02531b125efed33289ae8ea898c5413697fe373cebd658bddb510f322ef09623bb7b74f7587cf58b9ad064
7
+ data.tar.gz: e9884f0c8b501bfc57eafdc6c5492fea7455eb807267c370fc68b59e7322f97c0360e3af6f7e61b7c2493df3e2026f72095b9c287e65e6bf5f1ec5274948ef7d
data/.envrc CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  source_up
4
4
 
5
- export RR=`pwd`
6
- export BUNDLE_GEMFILE=Gemfile
5
+ export RR=$(pwd)
6
+ # BUNDLE_GEMFILE is inherited from the project root .envrc (asgard dev|prod)
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. Includes semantic caching via RubyLLM::SemanticCache
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 (~> 1.12), ruby_llm-mcp, prompt_manager, ruby_llm-schema, ruby_llm-semantic_cache, async, simple_flow, state_machines
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 the local ones (alongside `:anthropic`, `:azure`, `:bedrock`, `:deepseek`, `:gemini`, `:mistral`, `:openai`, `:openrouter`, `:perplexity`, `:vertexai`, and `:xai`). The provider's API base must be configured first, or building the robot raises `RubyLLM::ConfigurationError`:
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: "llama3.2",
95
- provider: :ollama,
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 - semantic caching module
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 instance (RubyLLM::SemanticCache) |
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` (~> 1.12) | LLM provider integrations (Anthropic, OpenAI, Gemini, etc.) |
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 |
@@ -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
@@ -1 +1,3 @@
1
+ source_up
2
+
1
3
  export ROBOT_LAB_TEMPLATE_PATH="${PWD}/prompts"
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
- param :operation,
17
+ parameter :operation,
18
18
  type: "string",
19
- desc: "The operation to perform (add, subtract, multiply, divide)"
19
+ description: "The operation to perform (add, subtract, multiply, divide)"
20
20
 
21
- param :a,
21
+ parameter :a,
22
22
  type: "number",
23
- desc: "First operand"
23
+ description: "First operand"
24
24
 
25
- param :b,
25
+ parameter :b,
26
26
  type: "number",
27
- desc: "Second operand"
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
- param :category,
43
+ parameter :category,
44
44
  type: "string",
45
- desc: "The category of fortune (wisdom, love, career, adventure)"
45
+ description: "The category of fortune (wisdom, love, career, adventure)"
46
46
 
47
47
  FORTUNES = {
48
48
  "wisdom" => [
@@ -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 Ollama
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 and tools: :inherit are both required. Both keywords default
167
- # to :none on run(), which resolves to "no MCP servers this turn" and "send
168
- # zero tools this turn" the discovered MCP tools would never reach the
169
- # provider and the model would answer from its training data.
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: :inherit
175
+ tools: %w[search_repositories]
174
176
  )
175
177
 
176
178
  puts
@@ -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.6:latest
23
+ # ROBOT_LAB_RUBY_LLM__MODEL=qwen/qwen3.8-27b
24
24
  # ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
25
- # OLLAMA_API_BASE=http://localhost:11434/v1
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 " ollama_api_base: #{OLLAMA_API_BASE}"
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 Ollama model still"
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
- OLLAMA_API_BASE=http://localhost:11434/v1
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
@@ -233,9 +233,9 @@ interactive.output = output
233
233
  interactive.input = StringIO.new("2\n")
234
234
 
235
235
  result = demo_tool.call(
236
- "question" => "What type of task should I optimize for?",
237
- "choices" => %w[general analysis creative coding research],
238
- "default" => "general"
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
- param :new_persona, type: "string",
15
- desc: "Your new comedy persona, style, and approach. " \
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
- param :level, type: "number",
33
- desc: "Energy level from 0.1 (very controlled) to 1.0 (unhinged)"
34
- param :reason, type: "string",
35
- desc: "Why you're adjusting", required: false
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
- param :situation, type: "string",
51
- desc: "Describe what's happening and what you need help with"
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
- param :specialty, type: "string",
14
- desc: "What to analyze: timing, crowd_work, " \
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
- param :updated_criteria, type: "string",
50
- desc: "Your refined evaluation criteria and focus areas"
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 decision is less about ideology than about trade-offs among hardware, software, and the kind of work you expect to do. All three camps have real strengths. macOS offers a polished, low-friction experience on Apple Silicon, with impressive power efficiency and strong support for local inference and lightweight fine-tuning. Windows is the most flexible consumer platform for pairing modern NVIDIA GPUs with mainstream desktop workflows, and WSL2 has narrowed the gap with Linux considerably. Linux remains the closest match to the software environment used in professional AI infrastructure, with the broadest native support for CUDA, containers, automation, and server-style workflows; BSD, while more niche, can be attractive for users who prioritize storage integrity and system control.
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
- On cost and hardware, the divide is clear. Windows and Linux generally win on price-performance if your goal is maximum GPU throughput, because both let you build or upgrade commodity x86 systems around NVIDIA RTX cards, still the center of gravity for home AI training and image-generation workloads. They also support a wider range of motherboards, storage, networking gear, and multi-GPU configurations. macOS, by contrast, is less modular and often more expensive upfront, but Apple Silicon machines deliver something unusual: quiet, compact systems with capable integrated acceleration and unified memory that can be very effective for local inference, quantized models, and practical experimentation without the heat, noise, and power draw of a discrete-GPU tower. For buyers who value efficiency and simplicity over upgradeability, that premium can be justified.
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
- Software ecosystem and GPU support are where the distinctions matter most. Linux is still the native habitat of modern ML tooling: CUDA stacks, PyTorch, JAX, DeepSpeed, vLLM, containerized workflows, and many research repos tend to work there first. It is also the best path for AMD ROCm users. Windows is close behind for NVIDIA-centric labs, with broad support for PyTorch, TensorFlow, ONNX Runtime, Stable Diffusion toolchains, and local-LLM front ends, plus the practical advantage of running desktop applications and Linux tooling side by side through WSL2. macOS has improved substantially, especially for inference and smaller-scale development via Metal, MLX, Core ML, llama.cpp, and Apple-optimized PyTorch paths, but it remains less universal for cutting-edge training stacks and does not offer the same straightforward path to top-end discrete GPU performance.
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
- Ease of use depends on what kind of friction you are trying to avoid. macOS is arguably the easiest platform to live with day to day: strong Unix underpinnings, consistent hardware support, and minimal driver drama make it appealing for researchers who want to spend more time experimenting than debugging. Windows is often the easiest place to start for users already comfortable with consumer PC hardware, especially if they want one machine for AI, productivity, and general use; its broad driver support and huge volume of community tutorials are practical advantages. Linux has the steepest learning curve for some users, but it rewards that investment with superior reproducibility, remote-management habits, and alignment with cloud and lab infrastructure. BSD is best understood as a specialist option rather than a mainstream recommendation for AI compute itself.
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 best choice, then, depends on your scenario. If your lab is centered on local inference, scripting, evaluation, and moderate fine-tuning in a quiet, power-efficient machine, macOS is an excellent fit. If you want the strongest all-around home setup for NVIDIA GPUs, image generation, and mixed desktop/Linux workflows, Windows is highly compelling. If you are building a serious research environment that mirrors production or cloud infrastructure, expect to use containers heavily, or want maximum compatibility with the latest open-source training stacks, Linux is the strongest foundation.
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.