robot_lab 0.2.6 → 0.2.7
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/README.md +124 -64
- data/docs/api/core/index.md +41 -15
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +255 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +551 -61
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +110 -17
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +80 -7
- data/docs/api/mcp/client.md +129 -35
- data/docs/api/mcp/index.md +164 -23
- data/docs/api/mcp/server.md +27 -3
- data/docs/api/mcp/transports.md +94 -22
- data/docs/api/messages/index.md +26 -3
- data/docs/api/messages/text-message.md +33 -11
- data/docs/api/messages/tool-call-message.md +27 -4
- data/docs/api/messages/tool-result-message.md +23 -4
- data/docs/api/messages/user-message.md +45 -8
- data/docs/api/skills.md +519 -0
- data/docs/api/streaming/context.md +28 -5
- data/docs/api/streaming/index.md +57 -11
- data/docs/api/support.md +846 -0
- data/docs/architecture/core-concepts.md +79 -31
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +145 -38
- data/docs/architecture/robot-execution.md +172 -90
- data/docs/architecture/state-management.md +31 -12
- data/docs/concepts.md +176 -21
- data/docs/examples/basic-chat.md +72 -19
- data/docs/examples/index.md +117 -31
- data/docs/examples/mcp-server.md +154 -45
- data/docs/examples/multi-robot-network.md +91 -21
- data/docs/examples/tool-usage.md +104 -37
- data/docs/getting-started/configuration.md +284 -97
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +258 -50
- data/docs/guides/creating-networks.md +214 -30
- data/docs/guides/hooks.md +141 -54
- data/docs/guides/knowledge.md +35 -4
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +95 -47
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +237 -17
- data/docs/index.md +36 -4
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +12 -7
- data/examples/04_mcp.rb +11 -4
- data/examples/05_streaming.rb +8 -5
- data/examples/06_prompt_templates.rb +13 -9
- data/examples/07_network_memory.rb +5 -5
- data/examples/08_llm_config.rb +20 -15
- data/examples/09_chaining.rb +4 -4
- data/examples/11_network_introspection.rb +4 -4
- data/examples/12_message_bus.rb +2 -2
- data/examples/13_spawn.rb +2 -2
- data/examples/14_rusty_circuit/README.md +1 -0
- data/examples/14_rusty_circuit/comic.rb +7 -3
- data/examples/14_rusty_circuit/display.rb +14 -0
- data/examples/14_rusty_circuit/heckler.rb +8 -6
- data/examples/14_rusty_circuit/open_mic.rb +17 -6
- data/examples/14_rusty_circuit/scout.rb +17 -10
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
- data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
- data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
- data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
- data/examples/16_writers_room/writer.rb +22 -22
- data/examples/16_writers_room/writers_room.rb +2 -0
- data/examples/17_skills.rb +14 -13
- data/examples/18_rails/README.md +20 -1
- data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
- data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
- data/examples/18_rails/app/robots/chat_robot.rb +11 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
- data/examples/19_token_tracking.rb +25 -9
- data/examples/20_circuit_breaker.rb +10 -7
- data/examples/21_learning_loop.rb +42 -16
- data/examples/22_context_compression.rb +23 -23
- data/examples/23_convergence.rb +24 -17
- data/examples/24_structured_delegation.rb +13 -8
- data/examples/25_history_search.rb +12 -8
- data/examples/27_incident_response/incident_response.rb +31 -13
- data/examples/28_mcp_discovery.rb +17 -13
- data/examples/29_ractor_tools.rb +4 -2
- data/examples/30_ractor_network.rb +22 -17
- data/examples/31_launch_assessment.rb +20 -9
- data/examples/32_newsletter_reader.rb +7 -2
- data/examples/33_stock_predictor.rb +34 -13
- data/examples/34_agentskills.rb +7 -3
- data/examples/35_hooks.rb +18 -8
- data/examples/README.md +199 -45
- data/examples/common.rb +79 -11
- data/examples/xyzzy.rb +8 -1
- data/lib/robot_lab/config.rb +10 -5
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/bus_messaging.rb +16 -8
- data/lib/robot_lab/robot/template_rendering.rb +16 -3
- data/lib/robot_lab/robot.rb +23 -2
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +21 -15
- data/mkdocs.yml +6 -1
- metadata +7 -2
data/examples/README.md
CHANGED
|
@@ -6,7 +6,71 @@ Working demonstrations of RobotLab features, from single-robot basics to multi-r
|
|
|
6
6
|
|
|
7
7
|
- Ruby >= 3.2
|
|
8
8
|
- `bundle install` (from the project root)
|
|
9
|
-
-
|
|
9
|
+
- [Ollama](https://ollama.com) running locally with the demo model pulled:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
ollama serve
|
|
13
|
+
ollama pull qwen3.6
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Every example runs against a **local** model — no API keys, no egress, no
|
|
17
|
+
per-token cost. The model, provider and endpoint are set in one place,
|
|
18
|
+
`examples/common.rb`:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
LLM = {
|
|
22
|
+
default: LlmConfig.new(provider: "ollama", model: "qwen3.6:latest"),
|
|
23
|
+
small: LlmConfig.new(provider: "ollama", model: "qwen2.5:7b"),
|
|
24
|
+
large: LlmConfig.new(provider: "ollama", model: "llama3.3:latest")
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Robots pick it up with the `llm_opts` helper:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
robot = RobotLab.build(name: "helper", **llm_opts) # default model
|
|
32
|
+
cheap = RobotLab.build(name: "helper", **llm_opts(:small)) # smaller model
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`provider:` is not optional here. Ollama models are absent from RubyLLM's
|
|
36
|
+
model registry, so passing `model:` alone raises `RubyLLM::ModelNotFoundError`
|
|
37
|
+
— supplying `provider:` is what makes RubyLLM skip the registry lookup. Note
|
|
38
|
+
also that `RunConfig` has no `provider` field, so provider and model travel
|
|
39
|
+
together on the robot even when other settings come from a shared `RunConfig`.
|
|
40
|
+
|
|
41
|
+
`common.rb` also raises `request_timeout` to 900s. A 20B+ model on consumer
|
|
42
|
+
hardware routinely takes longer than robot_lab's 120s default on a long
|
|
43
|
+
answer, which otherwise surfaces mid-example as `Net::ReadTimeout`. Override
|
|
44
|
+
with `LLM_REQUEST_TIMEOUT`.
|
|
45
|
+
|
|
46
|
+
## Runtime: pick your model with `LLM_PROFILE`
|
|
47
|
+
|
|
48
|
+
Every example is a *sequence* of LLM calls, so wall-clock is dominated by
|
|
49
|
+
`calls × seconds-per-call`. On `qwen3.6:latest` expect roughly **60-70s per
|
|
50
|
+
call**. That is fine for the single-robot examples and painful for the
|
|
51
|
+
multi-robot ones:
|
|
52
|
+
|
|
53
|
+
| Example | LLM calls | `qwen3.6:latest` | `LLM_PROFILE=small` |
|
|
54
|
+
|---------|-----------|------------------|---------------------|
|
|
55
|
+
| 01, 02, 19, 20 | 1-6 | seconds to ~5 min | fast |
|
|
56
|
+
| 03, 06, 07, 24, 27, 31 | 4-8 | ~5-10 min | ~2 min |
|
|
57
|
+
| 13, 14, 15 | ~20-30 | **30-40 min** | ~5-10 min |
|
|
58
|
+
| 16 | unbounded (600s cap) | usually hits the cap | often completes |
|
|
59
|
+
|
|
60
|
+
`LLM_PROFILE` switches the model for any example without editing it:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
LLM_PROFILE=small bundle exec ruby examples/14_rusty_circuit/open_mic.rb
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Valid values are the keys of `LLM` in `common.rb`: `default`, `small`, `large`.
|
|
67
|
+
|
|
68
|
+
**Long silences are not hangs.** In example 14 the scout writes its notes to
|
|
69
|
+
`output/scout_notes.md` rather than STDOUT, and a scout turn is 1-4 sequential
|
|
70
|
+
calls — minutes of blank terminal. The `· Scout …` lines exist so you can tell
|
|
71
|
+
progress from a stall. Example 16 is the same idea at larger scale: writers
|
|
72
|
+
coordinate through shared memory, and much of the work shows up in
|
|
73
|
+
`output/room.log` rather than on screen.
|
|
10
74
|
|
|
11
75
|
## Running Examples
|
|
12
76
|
|
|
@@ -21,17 +85,36 @@ bundle exec rake examples:all
|
|
|
21
85
|
bundle exec ruby examples/01_simple_robot.rb
|
|
22
86
|
```
|
|
23
87
|
|
|
88
|
+
## Tools require `tools: :inherit` at run time
|
|
89
|
+
|
|
90
|
+
This trips up everyone once. `Robot#run` defaults to `tools: :none`, which
|
|
91
|
+
means *"send zero tools this turn"* — the robot still holds them in
|
|
92
|
+
`local_tools`, but the provider never sees them and the model answers from
|
|
93
|
+
memory instead of calling anything:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
robot = RobotLab.build(name: "bot", **llm_opts, local_tools: [Calculator])
|
|
97
|
+
|
|
98
|
+
robot.run("What is 15 * 7?") # Calculator is NEVER offered
|
|
99
|
+
robot.run("What is 15 * 7?", tools: :inherit) # Calculator is offered
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The same applies to MCP (`mcp: :inherit`), to network tasks
|
|
103
|
+
(`task :name, robot, tools: :inherit`), and to anything that forwards keywords
|
|
104
|
+
into `run` — including `RobotLab::RailsIntegration::Job`. Examples 02, 04, 06,
|
|
105
|
+
14, 15, 16, 20, 33 and the Rails app all pass it explicitly.
|
|
106
|
+
|
|
24
107
|
## Directory Structure
|
|
25
108
|
|
|
26
109
|
```
|
|
27
110
|
examples/
|
|
28
|
-
|
|
29
|
-
|
|
111
|
+
common.rb # Shared LLM config (Ollama), llm_opts, output helpers
|
|
112
|
+
xyzzy.rb # Single-file hook extension used by 35
|
|
30
113
|
01_simple_robot.rb # Basic robot with template
|
|
31
114
|
02_tools.rb # Robot with custom tools
|
|
32
115
|
03_network.rb # Multi-robot network with routing
|
|
33
116
|
04_mcp.rb # MCP server integration (GitHub)
|
|
34
|
-
05_streaming.rb # Real-time streaming
|
|
117
|
+
05_streaming.rb # Real-time streaming callbacks
|
|
35
118
|
06_prompt_templates.rb # Template-based e-commerce support
|
|
36
119
|
07_network_memory.rb # Shared memory with concurrent robots
|
|
37
120
|
08_llm_config.rb # Configuration hierarchy demo
|
|
@@ -47,6 +130,20 @@ examples/
|
|
|
47
130
|
scout.rb # Talent scout with analyst spawning
|
|
48
131
|
display.rb # Terminal formatting (color, wrapping, file output)
|
|
49
132
|
prompts/ # Templates for comic, heckler, and scout
|
|
133
|
+
15_memory_network_and_bus/ # Network + memory + bus + spawn in one pipeline
|
|
134
|
+
editorial_pipeline.rb # Main entrypoint — three writers, editor, chief
|
|
135
|
+
16_writers_room/ # Self-organizing group: no orchestration at all
|
|
136
|
+
writers_room.rb # Main entrypoint — book and screenplay modes
|
|
137
|
+
17_skills.rb # Composable skills, flat and recursive
|
|
138
|
+
18_rails/ # Minimal Rails 8 demo app (full integration)
|
|
139
|
+
app/robots/chat_robot.rb # Robot factory with system prompt + TimeTool
|
|
140
|
+
app/tools/time_tool.rb # Custom RobotLab::Tool subclass
|
|
141
|
+
app/jobs/robot_run_job.rb # Background job with Turbo Stream callbacks
|
|
142
|
+
app/controllers/ # Chat controller (index + create)
|
|
143
|
+
app/views/ # Layout with CDN importmap, chat view with streaming
|
|
144
|
+
app/models/ # RobotLabThread, RobotLabResult
|
|
145
|
+
config/ # Minimal Rails 8 config (async adapters, no asset pipeline)
|
|
146
|
+
db/migrate/ # Migration from generator template
|
|
50
147
|
19_token_tracking.rb # Per-robot token & cost tracking
|
|
51
148
|
20_circuit_breaker.rb # Tool loop circuit breaker with max_tool_rounds
|
|
52
149
|
21_learning_loop.rb # Learning accumulation across runs with robot.learn
|
|
@@ -55,19 +152,17 @@ examples/
|
|
|
55
152
|
24_structured_delegation.rb # Structured delegation with duration and token tracking
|
|
56
153
|
25_history_search.rb # Semantic search over a robot's conversation history
|
|
57
154
|
26_document_store.rb # Embedding-based document store (RAG) via fastembed
|
|
155
|
+
27_incident_response/ # BusPoller, reactive memory, poller groups
|
|
156
|
+
incident_response.rb # Main entrypoint — wires up the war room
|
|
157
|
+
28_mcp_discovery.rb # Semantic MCP server selection before connecting
|
|
58
158
|
29_ractor_tools.rb # Ractor-safe tools: worker pool, freeze_deep, parallel batch
|
|
59
159
|
30_ractor_network.rb # Ractor network scheduler: dependency waves, parallel_mode
|
|
60
160
|
31_launch_assessment.rb # 6 parallel analysts, max_concurrent_robots: 4 semaphore cap
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
app/controllers/ # Chat controller (index + create)
|
|
67
|
-
app/views/ # Layout with CDN importmap, chat view with streaming
|
|
68
|
-
app/models/ # RobotLabThread, RobotLabResult
|
|
69
|
-
config/ # Minimal Rails 8 config (async adapters, no asset pipeline)
|
|
70
|
-
db/migrate/ # Migration from generator template
|
|
161
|
+
32_newsletter_reader.rb # Utility script (no RobotLab) — RSS to Markdown
|
|
162
|
+
33_stock_generator.rb # Companion publisher for 33_stock_predictor (Redis)
|
|
163
|
+
33_stock_predictor.rb # Durable cross-session learning via robot_lab-durable
|
|
164
|
+
34_agentskills.rb # AgentSkills.io folder-format skills, matched per run
|
|
165
|
+
35_hooks.rb # Hook architecture demo using xyzzy.rb
|
|
71
166
|
prompts/ # Prompt templates (.md with YAML front matter)
|
|
72
167
|
```
|
|
73
168
|
|
|
@@ -77,43 +172,43 @@ examples/
|
|
|
77
172
|
|
|
78
173
|
Create and run a basic robot using a prompt template. Sends a single message and displays the response.
|
|
79
174
|
|
|
80
|
-
**Requires:**
|
|
175
|
+
**Requires:** Ollama
|
|
81
176
|
|
|
82
177
|
### 02 — Tools
|
|
83
178
|
|
|
84
179
|
Give a robot custom tools (`Calculator`, `FortuneCookie`) defined as `RubyLLM::Tool` subclasses. The LLM decides when to call each tool based on the user's request.
|
|
85
180
|
|
|
86
|
-
**Requires:**
|
|
181
|
+
**Requires:** Ollama
|
|
87
182
|
|
|
88
183
|
### 03 — Multi-Robot Network
|
|
89
184
|
|
|
90
185
|
Build a customer support network with a classifier robot that routes requests to billing, technical, or general specialists. Uses SimpleFlow's optional task activation for conditional routing.
|
|
91
186
|
|
|
92
|
-
**Requires:**
|
|
187
|
+
**Requires:** Ollama
|
|
93
188
|
|
|
94
189
|
### 04 — MCP Integration
|
|
95
190
|
|
|
96
191
|
Connect to the GitHub MCP server via stdio transport. Part 1 demonstrates direct `MCP::Client` usage (listing tools, calling `search_repositories`). Part 2 wraps the MCP server inside a robot for natural-language queries.
|
|
97
192
|
|
|
98
|
-
**Requires:**
|
|
193
|
+
**Requires:** Ollama, `GITHUB_PERSONAL_ACCESS_TOKEN`, `github-mcp-server` installed
|
|
99
194
|
|
|
100
195
|
### 05 — Streaming
|
|
101
196
|
|
|
102
|
-
Real-time streaming of robot responses
|
|
197
|
+
Real-time streaming of robot responses through four routes: the stored `on_content:` callback wired at build time, a per-call block passed to `run()`, both together (stored fires first, then the block), and `on_content` arriving through a `RunConfig`.
|
|
103
198
|
|
|
104
|
-
**Requires:**
|
|
199
|
+
**Requires:** Ollama (makes four live calls)
|
|
105
200
|
|
|
106
201
|
### 06 — Prompt Templates
|
|
107
202
|
|
|
108
203
|
Full e-commerce support system using prompt_manager templates with YAML front matter. A triage robot classifies customer requests and routes to order, product, or escalation specialists. Demonstrates build-time context (company info, policies) and run-time context (customer data, order history).
|
|
109
204
|
|
|
110
|
-
**Requires:**
|
|
205
|
+
**Requires:** Ollama
|
|
111
206
|
|
|
112
207
|
### 07 — Network Memory
|
|
113
208
|
|
|
114
209
|
Reactive shared memory with concurrent robots. Multiple analysis robots (sentiment, entity extraction, keywords) run in parallel and write to shared memory. A synthesizer robot waits for all results using blocking reads, then produces a combined analysis. Demonstrates subscriptions, notifications, and network broadcast.
|
|
115
210
|
|
|
116
|
-
**Requires:**
|
|
211
|
+
**Requires:** Ollama
|
|
117
212
|
|
|
118
213
|
### 08 — LLM Configuration
|
|
119
214
|
|
|
@@ -155,7 +250,7 @@ Bidirectional robot communication via TypedBus. A comedy critic (Alice) tasks a
|
|
|
155
250
|
|
|
156
251
|
Demonstrates: Robot subclasses, prompt templates, auto-ack `on_message`, `reply()` convenience, temperature ramping, convergence patterns.
|
|
157
252
|
|
|
158
|
-
**Requires:**
|
|
253
|
+
**Requires:** Ollama
|
|
159
254
|
|
|
160
255
|
### 13 — Spawning Robots
|
|
161
256
|
|
|
@@ -163,7 +258,7 @@ Dynamic specialist creation at runtime. A dispatcher robot receives questions, a
|
|
|
163
258
|
|
|
164
259
|
Demonstrates: `spawn` for dynamic robot creation, lazy bus creation, `on_message` for reply handling, LLM-driven delegation.
|
|
165
260
|
|
|
166
|
-
**Requires:**
|
|
261
|
+
**Requires:** Ollama
|
|
167
262
|
|
|
168
263
|
### 14 — The Rusty Circuit (Open Mic Night)
|
|
169
264
|
|
|
@@ -171,27 +266,51 @@ A comedy club where three robots interact through a shared message bus. A comedi
|
|
|
171
266
|
|
|
172
267
|
Terminal output is color-formatted: comic bits in cyan (left-aligned), heckler reactions in yellow (right-indented), tool annotations dimmed. Scout notes go to `scout_notes.md` instead of STDOUT. The final verdict appears in green on both STDOUT and the scout file.
|
|
173
268
|
|
|
174
|
-
Demonstrates: Robot subclasses, self-modification via tool side effects, dynamic spawning (`spawn`), shared `:room` channel + personal channels,
|
|
269
|
+
Demonstrates: Robot subclasses, self-modification via tool side effects, dynamic spawning (`spawn`), shared `:room` channel + personal channels, `enqueue_delivery` for serializing async deliveries, `[SILENCE]` opt-out pattern, style reinvention via user-prompt injection.
|
|
270
|
+
|
|
271
|
+
**Requires:** Ollama. The most expensive example in the suite — ~30 LLM calls, 30+ minutes on `qwen3.6`. Use `LLM_PROFILE=small` to watch it in under ten.
|
|
272
|
+
|
|
273
|
+
### 15 — Memory, Network, Bus & Spawn Together
|
|
274
|
+
|
|
275
|
+
An editorial pipeline where three writers advocate for macOS, Windows, and Linux/BSD as a home AI research lab platform. The network runs the writers in parallel and hands their drafts to an editor through shared memory; the Linux writer spawns three distro specialists mid-pipeline; an editor-in-chief outside the pipeline reviews the combined article over the bus and can demand revisions.
|
|
276
|
+
|
|
277
|
+
Demonstrates: all four coordination mechanisms in one program, plus the direct `shared_memory` reference pattern that parallel pipeline steps need (`extract_run_context` deletes from a shared hash, so only the first parallel step would otherwise see `network_memory`).
|
|
175
278
|
|
|
176
|
-
**Requires:**
|
|
279
|
+
**Requires:** Ollama
|
|
280
|
+
|
|
281
|
+
### 16 — The Writers' Room (Self-Organizing Group)
|
|
282
|
+
|
|
283
|
+
A team of identical writer robots produces a 10-chapter novella with no orchestration, no pipeline, and no assigned roles. Each writer subscribes to a `:room` broadcast channel and a personal channel, and has tools to read/write shared memory, broadcast, DM, spawn more writers, and mark the work complete. The script seeds the room with an assignment and waits.
|
|
284
|
+
|
|
285
|
+
`--screenplay-from output/memory.json` re-runs the room in screenplay mode, adapting a finished book into a 4-act TV movie pilot at scene granularity.
|
|
286
|
+
|
|
287
|
+
Demonstrates: emergent coordination, `clear_messages(keep_system: true)` as a per-message conversation reset, dynamic team growth, memory as the single source of truth.
|
|
288
|
+
|
|
289
|
+
**Requires:** Ollama (long-running — `--timeout` defaults to 600s)
|
|
290
|
+
|
|
291
|
+
### 17 — Composable Skills
|
|
292
|
+
|
|
293
|
+
Skills are ordinary templates whose bodies are prepended to the main template. An SRE incident responder is composed from `runbook_protocol` and `structured_output` (flat skills) plus `sre_compliance`, which recursively expands to `pii_redactor` and `audit_trail`. The assembled system prompt is printed line-by-line so the depth-first expansion order is visible, and a skill-less robot is built alongside it for size comparison.
|
|
294
|
+
|
|
295
|
+
**Requires:** Ollama
|
|
177
296
|
|
|
178
297
|
### 19 — Token & Cost Tracking
|
|
179
298
|
|
|
180
|
-
Track token usage across runs using `result.input_tokens` / `result.output_tokens` for per-run counts and `robot.total_input_tokens` / `robot.total_output_tokens` for running totals. Demonstrates `reset_token_totals` to start a fresh batch
|
|
299
|
+
Track token usage across runs using `result.input_tokens` / `result.output_tokens` for per-run counts and `robot.total_input_tokens` / `robot.total_output_tokens` for running totals. Demonstrates `reset_token_totals` to start a fresh batch. Local inference is free, so cost is reported as `$0.00000 (local)`; set `RATE_INPUT_CPM` / `RATE_OUTPUT_CPM` to price the same traffic against a hosted provider.
|
|
181
300
|
|
|
182
|
-
**Requires:**
|
|
301
|
+
**Requires:** Ollama
|
|
183
302
|
|
|
184
303
|
### 20 — Tool Loop Circuit Breaker
|
|
185
304
|
|
|
186
305
|
Guards against runaway tool call loops using `max_tool_rounds:`. A step processor tool is designed to always return "more steps remain", which would loop indefinitely without a guard. The circuit breaker fires after the configured limit and raises `RobotLab::ToolLoopError`. Shows how to rescue the error gracefully and confirms the robot is fully reusable after a breaker trip.
|
|
187
306
|
|
|
188
|
-
**Requires:**
|
|
307
|
+
**Requires:** Ollama
|
|
189
308
|
|
|
190
309
|
### 21 — Learning Accumulation Loop
|
|
191
310
|
|
|
192
311
|
Builds up cross-run observations with `robot.learn(text)`. A code reviewer accumulates one key insight after each review. On subsequent runs, learnings are automatically prepended to the user message as a "LEARNINGS FROM PREVIOUS RUNS:" block. Demonstrates bidirectional substring deduplication (broader learnings replace narrower ones), the `robot.learnings` accessor, and how learnings survive a robot rebuild via the shared `Memory` object.
|
|
193
312
|
|
|
194
|
-
**Requires:**
|
|
313
|
+
**Requires:** Ollama
|
|
195
314
|
|
|
196
315
|
### 22 — Context Window Compression
|
|
197
316
|
|
|
@@ -199,23 +318,17 @@ Demonstrates `robot.compress_history()` for reducing token usage in long convers
|
|
|
199
318
|
|
|
200
319
|
**Requires:** `gem 'classifier', '~> 2.3'` in your Gemfile (no LLM calls in the demo itself)
|
|
201
320
|
|
|
202
|
-
### 24 — Structured Delegation
|
|
203
|
-
|
|
204
|
-
A manager robot delegates sub-tasks to a summarizer and an analyst. Each `delegate()` call returns a `RobotResult` annotated with `delegated_by`, `duration`, and token counts. Includes a comparison table of when to use delegation vs. bus messaging vs. pipelines.
|
|
205
|
-
|
|
206
|
-
**Requires:** LLM API key
|
|
207
|
-
|
|
208
321
|
### 23 — Debate Convergence Detection
|
|
209
322
|
|
|
210
|
-
Demonstrates `RobotLab::Convergence` for detecting when two independent agents have reached the same conclusion. Scores pairs of texts from identical → semantically similar → partially related → unrelated, showing how the similarity metric varies. Includes the
|
|
323
|
+
Demonstrates `RobotLab::Convergence` for detecting when two independent agents have reached the same conclusion. Scores pairs of texts from identical → semantically similar → partially related → unrelated, showing how the similarity metric varies. Includes the fast-path pattern: a gate robot compares two verifiers' replies and only activates the expensive reconciler — via SimpleFlow optional-task activation — when they disagree.
|
|
211
324
|
|
|
212
325
|
**Requires:** `gem 'classifier', '~> 2.3'` in your Gemfile (no LLM calls in the demo itself)
|
|
213
326
|
|
|
214
327
|
### 24 — Structured Delegation
|
|
215
328
|
|
|
216
|
-
Demonstrates `robot.delegate(to:, task:)` for synchronous and asynchronous inter-robot delegation. The manager robot delegates document analysis to a summarizer and an analyst. Shows synchronous (sequential, blocking) and asynchronous (parallel fan-out, `DelegationFuture`) modes with wall-time comparison.
|
|
329
|
+
Demonstrates `robot.delegate(to:, task:)` for synchronous and asynchronous inter-robot delegation. The manager robot delegates document analysis to a summarizer and an analyst. Shows synchronous (sequential, blocking) and asynchronous (parallel fan-out, `DelegationFuture`) modes with wall-time comparison. Each result carries `delegated_by`, `duration`, and token counts.
|
|
217
330
|
|
|
218
|
-
**Requires:**
|
|
331
|
+
**Requires:** Ollama
|
|
219
332
|
|
|
220
333
|
### 25 — Chat History Search
|
|
221
334
|
|
|
@@ -249,7 +362,7 @@ bundle exec ruby examples/27_incident_response/incident_response.rb
|
|
|
249
362
|
bundle exec rake examples:run[27]
|
|
250
363
|
```
|
|
251
364
|
|
|
252
|
-
**Requires:**
|
|
365
|
+
**Requires:** Ollama
|
|
253
366
|
|
|
254
367
|
### 28 — MCP Server Discovery
|
|
255
368
|
|
|
@@ -297,7 +410,7 @@ and the `pipeline.step_dependencies` dependency graph inspection.
|
|
|
297
410
|
|
|
298
411
|
**Part 3** — Live LLM run (enabled automatically when `ANTHROPIC_API_KEY` is set).
|
|
299
412
|
|
|
300
|
-
**Requires:** None for Parts 1 & 2.
|
|
413
|
+
**Requires:** None for Parts 1 & 2. `RUN_LIVE=1` plus Ollama for Part 3 (expected to fail — ruby_llm is not Ractor-safe yet).
|
|
301
414
|
|
|
302
415
|
### 31 — Product Launch Assessment (Concurrency Cap)
|
|
303
416
|
|
|
@@ -305,13 +418,37 @@ Six specialist robots evaluate a product launch simultaneously: market, competit
|
|
|
305
418
|
|
|
306
419
|
Demonstrates: `max_concurrent_robots:` on `RunConfig`, `Async::Semaphore` back-pressure via `simple_flow`, six parallel `depends_on: :none` tasks, shared memory writes and blocking reads.
|
|
307
420
|
|
|
308
|
-
**Requires:**
|
|
421
|
+
**Requires:** Ollama
|
|
422
|
+
|
|
423
|
+
### 32 — Newsletter Reader
|
|
424
|
+
|
|
425
|
+
A plain utility script that fetches unprocessed issues from Ruby newsletter RSS feeds and saves them as Markdown. **It uses no part of RobotLab** — it lives here as a content feeder for other experiments, not as a capability demo. Set `CLIPPINGS_DIR` to choose the output folder.
|
|
426
|
+
|
|
427
|
+
**Requires:** `html2markdown` on `PATH`
|
|
428
|
+
|
|
429
|
+
### 33 — Durable Cross-Session Learning
|
|
430
|
+
|
|
431
|
+
`33_stock_generator.rb` publishes synthetic XYZZY prices to a Redis channel using geometric Brownian motion. `33_stock_predictor.rb` consumes them, predicts each window's high/low with an SMA + EMA ensemble, and after every window asks a tuner robot to adjust the predictor's parameters.
|
|
432
|
+
|
|
433
|
+
The tuner uses `robot_lab-durable`. Note the API: `learn:` / `learn_domain:` are **not** constructor parameters (`Robot#initialize` takes a fixed keyword list with no `**rest`, so passing them raises `ArgumentError`). Call `robot.setup_durable_learning(domain:)` after `build`, which seeds `robot.learnings` from `~/.robot_lab/durable/<domain>.yml` and appends the `RecallKnowledge` / `RecordKnowledge` tools. Core `Robot#run` does not invoke the reflector, so the caller drives `robot.run_reflector` to promote learnings back to the store.
|
|
434
|
+
|
|
435
|
+
Run the two scripts in separate terminals.
|
|
436
|
+
|
|
437
|
+
**Requires:** Ollama, Redis on localhost:6379, `robot_lab-durable`
|
|
438
|
+
|
|
439
|
+
### 34 — AgentSkills.io Integration
|
|
440
|
+
|
|
441
|
+
Skills declared as `skills: [:code_reviewer]` are resolved from `~/.prompts/skills/<name>/SKILL.md` and matched per-run by embedding similarity — a code-review question activates the skill, an unrelated question does not. Run the two queries and compare.
|
|
442
|
+
|
|
443
|
+
**Requires:** Ollama, `robot_lab-document_store` gem, a `SKILL.md` at `~/.prompts/skills/code_reviewer/`
|
|
309
444
|
|
|
310
445
|
### 35 — Hooks Architecture
|
|
311
446
|
|
|
312
|
-
Loads
|
|
447
|
+
Loads `xyzzy.rb`, a single-file `RobotLab::Hook` subclass that registers for every hook and logs each callback with the context it receives. Demonstrates robot run, LLM generation, tool call, network run, task, and error hooks. Stubs `robot.chat.ask` for the deterministic sections; the LLM-generation section makes four real calls to show `around_llm_generation` serving two of them from a cache.
|
|
313
448
|
|
|
314
|
-
|
|
449
|
+
`RobotLab::Hook` has no logger accessor — handlers own their output. `xyzzy.rb` freezes its log destination from `XYZZY_LOG_PATH` at load time, so the env var must be set before the `require`.
|
|
450
|
+
|
|
451
|
+
**Requires:** Ollama (four calls in the LLM-loop section)
|
|
315
452
|
|
|
316
453
|
### 18 — Rails Integration Demo
|
|
317
454
|
|
|
@@ -326,13 +463,15 @@ A minimal, hand-built Rails 8 app that exercises every piece of RobotLab's Rails
|
|
|
326
463
|
|
|
327
464
|
**No Redis, no Solid Queue, no asset pipeline.** Uses `:async` adapters for both ActiveJob and ActionCable. Turbo JS loaded via importmap from CDN (`@hotwired/turbo-rails`).
|
|
328
465
|
|
|
466
|
+
**Two API details worth copying:** the job's superclass is `RobotLab::RailsIntegration::Job` (there is no `RobotLab::Job` alias), and the controller enqueues with `tools: :inherit` so that keyword reaches `robot.run` — otherwise `TimeTool` is never offered and the model guesses the time.
|
|
467
|
+
|
|
329
468
|
```bash
|
|
330
469
|
cd examples/18_rails
|
|
331
470
|
bin/setup # bundle install + db:create + db:migrate
|
|
332
471
|
bin/dev # starts Puma on http://localhost:3000
|
|
333
472
|
```
|
|
334
473
|
|
|
335
|
-
**Requires:**
|
|
474
|
+
**Requires:** Ollama, Ruby 3.2+
|
|
336
475
|
|
|
337
476
|
## Prompt Templates
|
|
338
477
|
|
|
@@ -377,3 +516,18 @@ Front matter keys like `model`, `temperature`, `top_p`, `max_tokens` are applied
|
|
|
377
516
|
| `open_mic_comic.md` | 14 | Observational comedian with self-modification |
|
|
378
517
|
| `open_mic_heckler.md` | 14 | Tough audience heckler (can stay silent or counter-joke) |
|
|
379
518
|
| `open_mic_scout.md` | 14 | Talent scout with analyst recruitment |
|
|
519
|
+
| `os_advocate.md` | 15 | Operating-system advocacy writer |
|
|
520
|
+
| `os_editor.md` | 15 | Synthesizes the three advocacy drafts |
|
|
521
|
+
| `os_chief.md` | 15 | Editor-in-chief (APPROVED / REVISE) |
|
|
522
|
+
| `writer.md` | 16 | Self-organizing novella writer |
|
|
523
|
+
| `screenplay_writer.md` | 16 | Self-organizing screenplay writer |
|
|
524
|
+
| `incident_responder.md` | 17 | SRE on-call analyst (main template) |
|
|
525
|
+
| `runbook_protocol.md` | 17 | Flat skill: 5-step incident protocol |
|
|
526
|
+
| `structured_output.md` | 17 | Flat skill: JSON response format |
|
|
527
|
+
| `sre_compliance.md` | 17 | Recursive skill: bundles the two leaves below |
|
|
528
|
+
| `pii_redactor.md` | 17 | Leaf skill: redact PII |
|
|
529
|
+
| `audit_trail.md` | 17 | Leaf skill: audit metadata |
|
|
530
|
+
|
|
531
|
+
Templates matching `*_test.md` are fixtures for the test suite, not examples.
|
|
532
|
+
|
|
533
|
+
Templates for 14, 15 and 16 live in those examples' own `prompts/` directories; each entrypoint sets `ROBOT_LAB_TEMPLATE_PATH` before requiring `common.rb`.
|
data/examples/common.rb
CHANGED
|
@@ -7,27 +7,95 @@ ENV["ROBOT_LAB_TEMPLATE_PATH"] ||= File.join(__dir__, "prompts")
|
|
|
7
7
|
|
|
8
8
|
require_relative "../lib/robot_lab"
|
|
9
9
|
|
|
10
|
+
# ── Local LLM Configuration ───────────────────────────────────────────────────
|
|
11
|
+
#
|
|
12
|
+
# Every example runs against a LOCAL model served by Ollama. No API keys, no
|
|
13
|
+
# network egress, no per-token cost. Pull the model once before running:
|
|
14
|
+
#
|
|
15
|
+
# ollama pull qwen3.6
|
|
16
|
+
#
|
|
17
|
+
# Ollama models are not in RubyLLM's model registry, so a `provider:` must be
|
|
18
|
+
# supplied alongside `model:` — that is what makes RubyLLM skip the registry
|
|
19
|
+
# lookup (see Robot#initialize, which sets assume_model_exists when provider is
|
|
20
|
+
# given). Use the `llm_opts` helper below so every robot gets both.
|
|
21
|
+
|
|
10
22
|
LlmConfig = Data.define(:provider, :model)
|
|
11
23
|
|
|
12
24
|
LLM = {
|
|
13
|
-
default:
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
default: LlmConfig.new(provider: "ollama", model: "qwen3.6:latest"),
|
|
26
|
+
small: LlmConfig.new(provider: "ollama", model: "qwen2.5:7b"),
|
|
27
|
+
large: LlmConfig.new(provider: "ollama", model: "llama3.3:latest")
|
|
16
28
|
}.freeze
|
|
17
29
|
|
|
18
|
-
|
|
19
|
-
c.logger = Logger.new(File::NULL)
|
|
20
|
-
c.default_model = LLM[:default].model
|
|
21
|
-
c.openai_api_key = ENV['OPENAI_API_KEY']
|
|
22
|
-
c.openai_organization_id = ENV['OPENAI_ORGANIZATION_ID']
|
|
23
|
-
c.openai_project_id = ENV['OPENAI_PROJECT_ID']
|
|
24
|
-
c.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
|
|
25
|
-
end
|
|
30
|
+
OLLAMA_API_BASE = ENV.fetch("OLLAMA_API_BASE", "http://localhost:11434/v1")
|
|
26
31
|
|
|
32
|
+
# ORDER MATTERS. The first touch of RobotLab.config runs Config#after_load,
|
|
33
|
+
# which calls RubyLLM.configure itself and would clobber anything set before
|
|
34
|
+
# it. So configure RobotLab first, then RubyLLM — the later block wins.
|
|
27
35
|
RobotLab.configure do |c|
|
|
28
36
|
c.logger = Logger.new(File::NULL)
|
|
29
37
|
end
|
|
30
38
|
|
|
39
|
+
RubyLLM.configure do |c|
|
|
40
|
+
c.logger = Logger.new(File::NULL)
|
|
41
|
+
c.default_model = LLM[:default].model
|
|
42
|
+
c.ollama_api_base = OLLAMA_API_BASE
|
|
43
|
+
|
|
44
|
+
# A large local model on consumer hardware is far slower than a hosted API,
|
|
45
|
+
# and robot_lab's bundled 120s default is comfortably exceeded by a long
|
|
46
|
+
# answer from a 20B+ model — which surfaces mid-run as Net::ReadTimeout.
|
|
47
|
+
#
|
|
48
|
+
# Integer(), not the raw env string: this value reaches Net::HTTP directly,
|
|
49
|
+
# and a String raises "can't convert String into time interval". That is
|
|
50
|
+
# also why the ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT env var is the wrong
|
|
51
|
+
# lever here — env values arrive as strings.
|
|
52
|
+
c.request_timeout = Integer(ENV.fetch("LLM_REQUEST_TIMEOUT", "900"))
|
|
53
|
+
c.max_retries = 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Which LLM entry an unqualified llm_opts resolves to. Lets you run any
|
|
57
|
+
# example against a faster model without editing it — worth knowing for the
|
|
58
|
+
# multi-robot demos (14, 15, 16), which are 30+ sequential LLM calls and take
|
|
59
|
+
# over half an hour on a 20B+ model:
|
|
60
|
+
#
|
|
61
|
+
# LLM_PROFILE=small bundle exec ruby examples/14_rusty_circuit/open_mic.rb
|
|
62
|
+
LLM_PROFILE = ENV.fetch("LLM_PROFILE", "default").to_sym
|
|
63
|
+
|
|
64
|
+
unless LLM.key?(LLM_PROFILE)
|
|
65
|
+
abort "Unknown LLM_PROFILE #{LLM_PROFILE.inspect}. Choose one of: #{LLM.keys.join(', ')}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Provider + model keyword pair for RobotLab.build / Robot.new.
|
|
69
|
+
#
|
|
70
|
+
# Both are required for a local Ollama model. Splat it into any robot
|
|
71
|
+
# constructor:
|
|
72
|
+
#
|
|
73
|
+
# RobotLab.build(name: "helper", **llm_opts) # honors LLM_PROFILE
|
|
74
|
+
# RobotLab.build(name: "cheap", **llm_opts(:small)) # pinned regardless
|
|
75
|
+
#
|
|
76
|
+
# @param key [Symbol, nil] which entry of LLM to use; defaults to LLM_PROFILE
|
|
77
|
+
# @return [Hash] { provider:, model: }
|
|
78
|
+
def llm_opts(key = nil)
|
|
79
|
+
cfg = LLM.fetch(key || LLM_PROFILE)
|
|
80
|
+
{ provider: cfg.provider, model: cfg.model }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Fail fast with an actionable message when Ollama isn't reachable, instead of
|
|
84
|
+
# letting every example die inside an HTTP adapter.
|
|
85
|
+
def require_ollama!
|
|
86
|
+
require "net/http"
|
|
87
|
+
uri = URI(OLLAMA_API_BASE.sub(%r{/v1/?$}, "") + "/api/tags")
|
|
88
|
+
Net::HTTP.start(uri.host, uri.port, open_timeout: 2, read_timeout: 2) { |h| h.get(uri.request_uri) }
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
abort <<~ERROR
|
|
91
|
+
Cannot reach Ollama at #{OLLAMA_API_BASE} (#{e.class}).
|
|
92
|
+
|
|
93
|
+
Start it and pull the model used by the examples:
|
|
94
|
+
ollama serve
|
|
95
|
+
ollama pull #{LLM[:default].model.sub(/:latest\z/, "")}
|
|
96
|
+
ERROR
|
|
97
|
+
end
|
|
98
|
+
|
|
31
99
|
# ── Example Output Helpers ─────────────────────────────────────────────────────
|
|
32
100
|
|
|
33
101
|
module ExOut
|
data/examples/xyzzy.rb
CHANGED
|
@@ -13,7 +13,12 @@
|
|
|
13
13
|
# stdout : one tagline per hook call → [xyzzy] HH:MM:SS.mmm hook_name
|
|
14
14
|
# logfile: full context snapshot written to LOG_PATH via PP.pp
|
|
15
15
|
#
|
|
16
|
+
# The log destination is read from XYZZY_LOG_PATH at load time and frozen
|
|
17
|
+
# into a constant. Set it BEFORE requiring this file to redirect the log;
|
|
18
|
+
# there is deliberately no writer method, so the constant stays shareable.
|
|
19
|
+
#
|
|
16
20
|
# Usage (from any example that requires common):
|
|
21
|
+
# ENV["XYZZY_LOG_PATH"] = "/tmp/xyzzy.log" # optional, must precede require
|
|
17
22
|
# require_relative "xyzzy"
|
|
18
23
|
|
|
19
24
|
require "pp"
|
|
@@ -23,7 +28,9 @@ module RobotLab
|
|
|
23
28
|
class Xyzzy < Hook
|
|
24
29
|
self.namespace = :xyzzy
|
|
25
30
|
|
|
26
|
-
LOG_PATH = File.expand_path(
|
|
31
|
+
LOG_PATH = File.expand_path(
|
|
32
|
+
ENV.fetch("XYZZY_LOG_PATH", "~/.robot_lab/xyzzy_hooks.log")
|
|
33
|
+
).freeze
|
|
27
34
|
FileUtils.mkdir_p(File.dirname(LOG_PATH))
|
|
28
35
|
|
|
29
36
|
class << self
|
data/lib/robot_lab/config.rb
CHANGED
|
@@ -8,7 +8,8 @@ module RobotLab
|
|
|
8
8
|
# Provides:
|
|
9
9
|
# - Nested configuration with a dedicated `ruby_llm:` section
|
|
10
10
|
# - Environment-specific settings (development, test, production)
|
|
11
|
-
# - XDG config file loading (~/.config/robot_lab/
|
|
11
|
+
# - XDG config file loading (~/.config/robot_lab/robot_lab.yml — the filename
|
|
12
|
+
# repeats the `config_name`; `config.yml` is never read)
|
|
12
13
|
# - Environment variable overrides (ROBOT_LAB_*)
|
|
13
14
|
# - Automatic RubyLLM configuration application
|
|
14
15
|
#
|
|
@@ -21,10 +22,14 @@ module RobotLab
|
|
|
21
22
|
# # ROBOT_LAB_RUBY_LLM__MODEL=gpt-4
|
|
22
23
|
# # ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
23
24
|
#
|
|
24
|
-
# @example User config file (~/.config/robot_lab/
|
|
25
|
-
# defaults
|
|
26
|
-
#
|
|
27
|
-
#
|
|
25
|
+
# @example User config file (~/.config/robot_lab/robot_lab.yml)
|
|
26
|
+
# # Flat keys, or a section named for the current environment. A `defaults:`
|
|
27
|
+
# # wrapper is IGNORED here — it applies only to the gem's bundled
|
|
28
|
+
# # defaults.yml. This file is NOT run through ERB, so keep secrets in
|
|
29
|
+
# # environment variables or in ./config/robot_lab.yml (which is).
|
|
30
|
+
# ruby_llm:
|
|
31
|
+
# model: claude-sonnet-4
|
|
32
|
+
# request_timeout: 120
|
|
28
33
|
#
|
|
29
34
|
class Config < MywayConfig::Base
|
|
30
35
|
config_name :robot_lab
|