robot_lab 0.0.7 → 0.0.9
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/CHANGELOG.md +99 -3
- data/README.md +125 -26
- data/Rakefile +39 -1
- data/docs/api/core/robot.md +284 -8
- data/docs/api/core/tool.md +28 -2
- data/docs/api/mcp/client.md +1 -0
- data/docs/api/mcp/server.md +27 -8
- data/docs/api/mcp/transports.md +21 -6
- data/docs/architecture/core-concepts.md +1 -1
- data/docs/architecture/robot-execution.md +20 -2
- data/docs/concepts.md +11 -3
- data/docs/examples/index.md +1 -1
- data/docs/examples/rails-application.md +1 -1
- data/docs/guides/building-robots.md +245 -11
- data/docs/guides/creating-networks.md +18 -0
- data/docs/guides/mcp-integration.md +74 -2
- data/docs/guides/rails-integration.md +145 -17
- data/docs/guides/using-tools.md +45 -4
- data/docs/index.md +62 -6
- data/examples/05_streaming.rb +113 -94
- data/examples/14_rusty_circuit/.gitignore +1 -0
- data/examples/14_rusty_circuit/open_mic.rb +1 -1
- data/examples/17_skills.rb +242 -0
- data/examples/18_rails/.envrc +3 -0
- data/examples/18_rails/.gitignore +5 -0
- data/examples/18_rails/Gemfile +10 -0
- data/examples/18_rails/README.md +48 -0
- data/examples/18_rails/Rakefile +4 -0
- data/examples/18_rails/app/controllers/application_controller.rb +4 -0
- data/examples/18_rails/app/controllers/chat_controller.rb +46 -0
- data/examples/18_rails/app/jobs/application_job.rb +4 -0
- data/examples/18_rails/app/jobs/robot_run_job.rb +79 -0
- data/examples/18_rails/app/models/application_record.rb +5 -0
- data/examples/18_rails/app/models/robot_lab_result.rb +36 -0
- data/examples/18_rails/app/models/robot_lab_thread.rb +23 -0
- data/examples/18_rails/app/robots/chat_robot.rb +14 -0
- data/examples/18_rails/app/tools/time_tool.rb +9 -0
- data/examples/18_rails/app/views/chat/_user_message.html.erb +1 -0
- data/examples/18_rails/app/views/chat/index.html.erb +67 -0
- data/examples/18_rails/app/views/layouts/application.html.erb +49 -0
- data/examples/18_rails/bin/dev +7 -0
- data/examples/18_rails/bin/rails +6 -0
- data/examples/18_rails/bin/setup +15 -0
- data/examples/18_rails/config/application.rb +33 -0
- data/examples/18_rails/config/cable.yml +2 -0
- data/examples/18_rails/config/database.yml +5 -0
- data/examples/18_rails/config/environment.rb +4 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +3 -0
- data/examples/18_rails/config/routes.rb +6 -0
- data/examples/18_rails/config.ru +4 -0
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +32 -0
- data/examples/README.md +30 -0
- data/examples/prompts/audit_trail.md +8 -0
- data/examples/prompts/incident_responder.md +18 -0
- data/examples/prompts/parameterized_main_test.md +6 -0
- data/examples/prompts/pii_redactor.md +7 -0
- data/examples/prompts/runbook_protocol.md +12 -0
- data/examples/prompts/skill_a_test.md +4 -0
- data/examples/prompts/skill_b_test.md +4 -0
- data/examples/prompts/skill_config_test.md +5 -0
- data/examples/prompts/skill_cycle_a_test.md +6 -0
- data/examples/prompts/skill_cycle_b_test.md +6 -0
- data/examples/prompts/skill_description_test.md +4 -0
- data/examples/prompts/skill_leaf_test.md +4 -0
- data/examples/prompts/skill_nested_test.md +6 -0
- data/examples/prompts/skill_refs_main_test.md +6 -0
- data/examples/prompts/skill_self_ref_test.md +6 -0
- data/examples/prompts/skill_with_params_test.md +6 -0
- data/examples/prompts/sre_compliance.md +10 -0
- data/examples/prompts/structured_output.md +7 -0
- data/examples/prompts/template_with_skills_test.md +6 -0
- data/lib/generators/robot_lab/install_generator.rb +12 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +36 -22
- data/lib/generators/robot_lab/templates/job.rb.tt +92 -0
- data/lib/generators/robot_lab/templates/robot.rb.tt +6 -21
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +5 -3
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +41 -35
- data/lib/robot_lab/mcp/client.rb +6 -4
- data/lib/robot_lab/mcp/server.rb +21 -3
- data/lib/robot_lab/mcp/transports/base.rb +10 -2
- data/lib/robot_lab/mcp/transports/stdio.rb +52 -26
- data/lib/robot_lab/{rails → rails_integration}/engine.rb +1 -1
- data/lib/robot_lab/{rails → rails_integration}/railtie.rb +1 -1
- data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +72 -0
- data/lib/robot_lab/robot/mcp_management.rb +61 -4
- data/lib/robot_lab/robot/template_rendering.rb +181 -4
- data/lib/robot_lab/robot.rb +196 -33
- data/lib/robot_lab/robot_result.rb +3 -1
- data/lib/robot_lab/run_config.rb +1 -1
- data/lib/robot_lab/tool.rb +26 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +6 -4
- metadata +55 -19
- data/examples/14_rusty_circuit/scout_notes.md +0 -89
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c852fcf7f4aed4ce95fabdc5b0296723ca8aa10e780dabaa7759e618a22bc640
|
|
4
|
+
data.tar.gz: 1bcb205c958ede9967886dae78a1d1a6d47da42e4cd9bd29d7bdd3e094b0a088
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5620e7798ac04441cb23c6a7cc5f0cdad7447103825db35ef6f3a3987785b8ff5fb355ec03a309ef9c8a5ce5b0b7a29d9f5adef0e6a5d9de5cd66d3c94fb0469
|
|
7
|
+
data.tar.gz: 9300b1f5ed98e70226c7c670bcf2e3dee033310db6b2182b2705085f02474a1ea6157a011c93906da1d45ba38b4c9f8b9e62545cdb5fd304ca1550734f7dc043
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
> [!CAUTION]
|
|
4
|
-
> This gem is under active development. APIs and features may change without notice.
|
|
5
|
-
|
|
6
3
|
All notable changes to this project will be documented in this file.
|
|
7
4
|
|
|
8
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
@@ -11,6 +8,105 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
8
|
|
|
12
9
|
## [Unreleased]
|
|
13
10
|
|
|
11
|
+
## [0.0.9] - 2026-03-02
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Provider passthrough** — `provider:` parameter on Robot constructor for local LLM providers (Ollama, GPUStack, etc.)
|
|
16
|
+
- Automatically sets `assume_model_exists: true` when provider is specified
|
|
17
|
+
- Exposed via `robot.provider` accessor
|
|
18
|
+
- **MCP request timeouts** — configurable timeout for all MCP transports
|
|
19
|
+
- `MCP::Server` accepts `timeout:` parameter (default 15s); auto-converts millisecond values
|
|
20
|
+
- `MCP::Transports::Base` extracts and exposes `timeout` from config
|
|
21
|
+
- `MCP::Transports::Stdio` wraps all blocking I/O with `Timeout.timeout` — hung servers no longer block the caller forever
|
|
22
|
+
- Timeout propagated from `MCP::Server` through `MCP::Client` to transport layer
|
|
23
|
+
- **MCP connection resilience** — improved error handling and retry logic
|
|
24
|
+
- `ensure_mcp_clients` retries previously failed servers on subsequent calls
|
|
25
|
+
- `@failed_mcp_configs` tracks servers that failed to connect
|
|
26
|
+
- `robot.failed_mcp_server_names` — query which MCP servers are down
|
|
27
|
+
- `robot.connect_mcp!` — eagerly connect to MCP servers (normally lazy)
|
|
28
|
+
- `init_mcp_client` rescues `StandardError` so one bad server doesn't prevent others from connecting
|
|
29
|
+
- `cleanup_process` in Stdio transport for reliable resource cleanup
|
|
30
|
+
- Better error messages for command-not-found (`Errno::ENOENT`), broken pipe (`Errno::EPIPE`), and EOF conditions
|
|
31
|
+
- **`robot.inject_mcp!`** — inject pre-connected MCP clients and tools from an external host application
|
|
32
|
+
- **Conversation management APIs** on Robot
|
|
33
|
+
- `robot.chat` — access the underlying `RubyLLM::Chat` instance
|
|
34
|
+
- `robot.messages` — return conversation messages
|
|
35
|
+
- `robot.clear_messages(keep_system:)` — clear history, optionally preserving the system prompt
|
|
36
|
+
- `robot.replace_messages(messages)` — restore a saved conversation (checkpoint/restore)
|
|
37
|
+
- `robot.chat_provider` — query the provider name without reaching into chat internals
|
|
38
|
+
- `robot.mcp_client(server_name)` — find an MCP client by server name
|
|
39
|
+
- **`RobotResult#duration`** — elapsed seconds for a robot run, set automatically during pipeline execution
|
|
40
|
+
- **`RobotResult#raw`** — raw LLM response stored on every result (previously only settable via accessor)
|
|
41
|
+
- **Pipeline error resilience** — `Robot#call` (pipeline step) rescues all exceptions so one failing robot doesn't crash the entire network; error is captured in a `RobotResult` with the elapsed duration
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- Bumped version to 0.0.9
|
|
46
|
+
- Display `scout_path` in Rusty Circuit example updated to use `output/` subdirectory
|
|
47
|
+
- Updated `onnxruntime` dependency to 0.11.0
|
|
48
|
+
- Updated Gemfile.lock dependencies (erb, minitest, rails-html-sanitizer, json_schemer)
|
|
49
|
+
|
|
50
|
+
## [0.0.8] - 2026-02-22
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **Skills as composable templates** — prepend reusable prompt snippets at build time via `skills:` parameter
|
|
55
|
+
- Skills are regular PromptManager templates (no special subdirectory)
|
|
56
|
+
- Recursive expansion — skills can declare nested skills via front matter `skills:` key
|
|
57
|
+
- Depth-first ordering — nested skills appear before their parent
|
|
58
|
+
- Cycle detection via `Set` of visited IDs; cycles log a warning and skip
|
|
59
|
+
- Config cascade — skill₁ → skill₂ → ... → main template → constructor kwargs
|
|
60
|
+
- Shared context — all skills and the main template render with the same `context:` hash
|
|
61
|
+
- Example: `examples/17_skills.rb` — SRE incident response system with flat and recursive skills
|
|
62
|
+
- 20 new tests covering expansion, ordering, cycles, config cascade, and factory passthrough
|
|
63
|
+
- **Streaming content callback (`on_content:`)** — wire streaming at robot build time
|
|
64
|
+
- Stored callback fires on every `run()` call automatically
|
|
65
|
+
- Pass-through block on `run()` for per-call streaming
|
|
66
|
+
- When both exist, both fire (stored first, then runtime block)
|
|
67
|
+
- `effective_streaming_block` merges stored + runtime into a single Proc
|
|
68
|
+
- Added `:on_content` to `RunConfig::CALLBACK_FIELDS`
|
|
69
|
+
- 12 new tests
|
|
70
|
+
- **Graceful tool error handling** — `RobotLab::Tool#call` wraps `execute` with `rescue StandardError`
|
|
71
|
+
- Errors returned as plain string (`"Error (tool_name): message"`) so the LLM can reason about them
|
|
72
|
+
- Class-level `raise_on_error` opt-out for critical tools
|
|
73
|
+
- MCP tools inherit the same wrapper via `Tool.create` subclasses
|
|
74
|
+
- 10 new tests
|
|
75
|
+
- **`RobotRunJob` generator template** (`job.rb.tt`) — turnkey ActiveJob background job for robot runs
|
|
76
|
+
- Resolves robot class via `constantize.build`
|
|
77
|
+
- Wires `TurboStreamCallbacks` when `turbo-rails` is available (graceful no-op otherwise)
|
|
78
|
+
- Persists results via `result.export`; broadcasts completion/error via Turbo Streams
|
|
79
|
+
- `--skip-job` option on install generator
|
|
80
|
+
- **`TurboStreamCallbacks` module** (`lib/robot_lab/rails_integration/turbo_stream_callbacks.rb`)
|
|
81
|
+
- `available?` — runtime check for `Turbo::StreamsChannel`
|
|
82
|
+
- `build_content_callback(stream_name:, target:)` — broadcasts HTML-escaped content chunks
|
|
83
|
+
- `build_tool_call_callback(stream_name:, target:)` — broadcasts tool call badges
|
|
84
|
+
- 13 tests
|
|
85
|
+
- **Rails demo app** (`examples/18_rails/`) — minimal hand-built Rails 8 app exercising all Rails integration
|
|
86
|
+
- ChatRobot with TimeTool, RobotRunJob, Turbo Stream token streaming, SQLite persistence
|
|
87
|
+
- No asset pipeline — Turbo JS via importmap from CDN
|
|
88
|
+
- `:async` adapters for both ActiveJob and ActionCable (no Redis, no Solid Queue)
|
|
89
|
+
- User messages persisted in history; auto-scrolling via MutationObserver; form clears after submit
|
|
90
|
+
- Rake tasks: `examples:rails_setup`, `examples:rails`
|
|
91
|
+
- **Routing robot example** in Rails integration docs — `ClassifierRobot` subclass with `call(result)` override
|
|
92
|
+
- **Custom tool example** in Rails integration docs — `OrderLookup` tool with ActiveRecord
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
|
|
96
|
+
- Bumped version to 0.0.8
|
|
97
|
+
- **Renamed `RobotLab::Rails` → `RobotLab::RailsIntegration`** — eliminates constant shadowing where bare `Rails` inside `module RobotLab` resolved to the gem's own namespace instead of `::Rails`
|
|
98
|
+
- Moved `lib/robot_lab/rails/` → `lib/robot_lab/rails_integration/`
|
|
99
|
+
- Updated all require paths, loader.ignore, generator templates, tests, and documentation
|
|
100
|
+
- Reverted `::Rails` back to bare `Rails` in `config.rb` (shadow eliminated)
|
|
101
|
+
- Rakefile updated with `STANDALONE_APPS` map for standalone demo apps
|
|
102
|
+
- Documentation updates across README, guides, API reference, and examples for all new features
|
|
103
|
+
- Updated Gemfile.lock dependencies
|
|
104
|
+
|
|
105
|
+
### Fixed
|
|
106
|
+
|
|
107
|
+
- `RobotLab::Rails` namespace shadowing `::Rails` in `config.rb` (`NoMethodError: undefined method 'root' for module RobotLab::Rails`)
|
|
108
|
+
- MkDocs broken anchor link in `docs/examples/index.md` (`#with-conversation-history` → `#with-memory`)
|
|
109
|
+
|
|
14
110
|
## [0.0.7] - 2026-02-17 [unreleased]
|
|
15
111
|
|
|
16
112
|
### Added
|
data/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# RobotLab
|
|
2
2
|
|
|
3
|
-
> [!
|
|
4
|
-
>
|
|
3
|
+
> [!INFO]
|
|
4
|
+
> See the [CHANGELOG](CHANGELOG.md) for the latest changes. The [examples directory has a good cross section of demo apps](examples/README.md) that show-off the various capabilities of the RobotLab library.
|
|
5
|
+
|
|
5
6
|
<br>
|
|
6
7
|
<table>
|
|
7
8
|
<tr>
|
|
@@ -10,24 +11,28 @@
|
|
|
10
11
|
<em>"Build robots. Solve problems."</em>
|
|
11
12
|
</td>
|
|
12
13
|
<td width="50%" valign="top">
|
|
13
|
-
<strong>Multi-robot LLM workflow orchestration for Ruby</strong><br><br>
|
|
14
|
-
RobotLab enables you to build sophisticated AI applications using multiple specialized robots (LLM agents) that work together to accomplish complex tasks. Each robot has its own system prompt, tools, and capabilities.<br><br>
|
|
15
14
|
<strong>Key Features</strong><br>
|
|
16
15
|
|
|
17
16
|
- <strong>Multi-Robot Architecture</strong> - Build with specialized AI agents<br>
|
|
18
17
|
- <strong>Network Orchestration</strong> - Connect robots with flexible routing<br>
|
|
19
|
-
- <strong>
|
|
20
|
-
- <strong>
|
|
18
|
+
- <strong>Prompt Templates</strong> - Self-contained .md files with YAML front matter<br>
|
|
19
|
+
- <strong>Composable Skills</strong> - Mix reusable prompt behaviors into any robot<br>
|
|
20
|
+
- <strong>Extensible Tools</strong> - Custom capabilities with graceful error handling<br>
|
|
21
|
+
- <strong>Human-in-the-Loop</strong> - AskUser tool for interactive prompting<br>
|
|
22
|
+
- <strong>Content Streaming</strong> - Stored callbacks, per-call blocks, or both<br>
|
|
23
|
+
- <strong>MCP Integration</strong> - Connect to external tool servers with timeouts and retry<br>
|
|
24
|
+
- <strong>Local LLM Providers</strong> - Ollama, GPUStack, LM Studio via provider passthrough<br>
|
|
21
25
|
- <strong>Shared Memory</strong> - Reactive key-value store with subscriptions<br>
|
|
22
|
-
- <strong>Conversation History</strong> - Persist and restore threads<br>
|
|
23
26
|
- <strong>Message Bus</strong> - Bidirectional robot communication via TypedBus<br>
|
|
24
27
|
- <strong>Dynamic Spawning</strong> - Robots create new robots at runtime<br>
|
|
25
|
-
- <strong>
|
|
26
|
-
- <strong>Rails Integration</strong> - Generators
|
|
28
|
+
- <strong>Layered Configuration</strong> - Cascading YAML, env vars, and RunConfig<br>
|
|
29
|
+
- <strong>Rails Integration</strong> - Generators, background jobs, Turbo Stream broadcasting
|
|
27
30
|
</td>
|
|
28
31
|
</tr>
|
|
29
32
|
</table>
|
|
30
33
|
|
|
34
|
+
<p>RobotLab enables sophisticated AI applications using multiple specialized robots (LLM agents) that work together to accomplish complex tasks. Each robot has its own instructions, skills, tools, and capabilities. Review the [full documentation website](https://madbomber.github.io/robot_lab) snd explore the [many examples](examples/README.md) available as working demo applications.</p>
|
|
35
|
+
|
|
31
36
|
## Installation
|
|
32
37
|
|
|
33
38
|
```bash
|
|
@@ -67,6 +72,19 @@ puts result.last_text_content
|
|
|
67
72
|
# => "The capital of France is Paris."
|
|
68
73
|
```
|
|
69
74
|
|
|
75
|
+
### Local LLM Providers
|
|
76
|
+
|
|
77
|
+
For local LLM providers (Ollama, GPUStack, LM Studio, etc.), use the `provider:` parameter:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
robot = RobotLab.build(
|
|
81
|
+
name: "local_bot",
|
|
82
|
+
model: "llama3.2",
|
|
83
|
+
provider: :ollama,
|
|
84
|
+
system_prompt: "You are a helpful assistant."
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
70
88
|
### Configuration
|
|
71
89
|
|
|
72
90
|
RobotLab uses [MywayConfig](https://github.com/MadBomber/myway_config) for layered configuration. There is no `configure` block. Configuration is loaded automatically from multiple sources in priority order:
|
|
@@ -172,7 +190,35 @@ You are a GitHub assistant. Use available tools to help with repository tasks.
|
|
|
172
190
|
robot = RobotLab.build(template: :github_assistant)
|
|
173
191
|
```
|
|
174
192
|
|
|
175
|
-
Front matter supports: `description`, `robot_name`, `tools`, `mcp`, `parameters`, and LLM config keys (`model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop`). Constructor-provided values always take precedence over front matter.
|
|
193
|
+
Front matter supports: `description`, `robot_name`, `tools`, `mcp`, `skills`, `parameters`, and LLM config keys (`model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop`). Constructor-provided values always take precedence over front matter.
|
|
194
|
+
|
|
195
|
+
### Composable Skills
|
|
196
|
+
|
|
197
|
+
Skills let you compose robot behaviors from reusable templates. A skill is just a template whose prompt body is prepended before the main template. Use skills to mix in capabilities like "ask clarifying questions", "respond in JSON", or "follow safety guidelines" without creating a dedicated template for every combination.
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# Compose a support robot from reusable skills
|
|
201
|
+
robot = RobotLab.build(
|
|
202
|
+
name: "support",
|
|
203
|
+
template: :support_agent,
|
|
204
|
+
skills: [:clarifier, :sentiment_aware, :json_responder],
|
|
205
|
+
context: { company: "Acme Corp" }
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Skills can also be declared in template front matter:
|
|
210
|
+
|
|
211
|
+
```markdown
|
|
212
|
+
---
|
|
213
|
+
description: Support agent with built-in skills
|
|
214
|
+
skills:
|
|
215
|
+
- clarifier
|
|
216
|
+
- sentiment_aware
|
|
217
|
+
---
|
|
218
|
+
You are a support agent for <%= company %>.
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Skills are expanded depth-first and can reference other skills (with automatic cycle detection). Config cascades through skills in order — later values override earlier ones, and constructor kwargs always win.
|
|
176
222
|
|
|
177
223
|
### Combining Templates with System Prompts
|
|
178
224
|
|
|
@@ -252,6 +298,28 @@ result = robot
|
|
|
252
298
|
.run("Write a haiku about Ruby programming")
|
|
253
299
|
```
|
|
254
300
|
|
|
301
|
+
## Graceful Tool Error Handling
|
|
302
|
+
|
|
303
|
+
`RobotLab::Tool` automatically catches exceptions in `execute` and returns a plain-text error to the LLM instead of crashing the run. The LLM can then reason about the error and try an alternative approach.
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
tool = RobotLab::Tool.create(name: "fetch_data") do |args|
|
|
307
|
+
raise IOError, "connection refused"
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
result = tool.call({})
|
|
311
|
+
# => "Error (fetch_data): connection refused"
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
This applies to all tools — subclasses, factory tools, and MCP tools. For critical tools where you want exceptions to propagate, opt out per class:
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
class CriticalTool < RobotLab::Tool
|
|
318
|
+
self.raise_on_error = true
|
|
319
|
+
# ...
|
|
320
|
+
end
|
|
321
|
+
```
|
|
322
|
+
|
|
255
323
|
## Creating a Robot with Tools
|
|
256
324
|
|
|
257
325
|
```ruby
|
|
@@ -389,14 +457,15 @@ puts result.value.last_text_content
|
|
|
389
457
|
Connect to external tool servers via Model Context Protocol:
|
|
390
458
|
|
|
391
459
|
```ruby
|
|
392
|
-
# Configure MCP server
|
|
460
|
+
# Configure MCP server (with optional timeout)
|
|
393
461
|
filesystem_server = {
|
|
394
462
|
name: "filesystem",
|
|
395
463
|
transport: {
|
|
396
464
|
type: "stdio",
|
|
397
465
|
command: "mcp-server-filesystem",
|
|
398
466
|
args: ["/path/to/allowed/directory"]
|
|
399
|
-
}
|
|
467
|
+
},
|
|
468
|
+
timeout: 30 # seconds (default: 15)
|
|
400
469
|
}
|
|
401
470
|
|
|
402
471
|
# Create robot with MCP server - tools are auto-discovered
|
|
@@ -406,10 +475,18 @@ robot = RobotLab.build(
|
|
|
406
475
|
mcp: [filesystem_server]
|
|
407
476
|
)
|
|
408
477
|
|
|
478
|
+
# Optionally connect eagerly (default is lazy on first run)
|
|
479
|
+
robot.connect_mcp!
|
|
480
|
+
|
|
481
|
+
# Check connection status
|
|
482
|
+
puts "Failed: #{robot.failed_mcp_server_names}" if robot.failed_mcp_server_names.any?
|
|
483
|
+
|
|
409
484
|
# Robot can now use filesystem tools
|
|
410
485
|
result = robot.run("List the files in the current directory")
|
|
411
486
|
```
|
|
412
487
|
|
|
488
|
+
MCP connections are resilient: failed servers are automatically retried on subsequent `run()` calls, and one failing server does not prevent others from connecting.
|
|
489
|
+
|
|
413
490
|
## Message Bus
|
|
414
491
|
|
|
415
492
|
Robots can communicate bidirectionally via an optional message bus, independent of the Network pipeline. This enables negotiation loops, convergence patterns, and cyclic workflows.
|
|
@@ -502,26 +579,48 @@ Key features:
|
|
|
502
579
|
|
|
503
580
|
## Streaming
|
|
504
581
|
|
|
505
|
-
|
|
582
|
+
Stream LLM content in real-time using a stored callback, a per-call block, or both. Each receives a [`RubyLLM::Chunk`](https://rubyllm.com/streaming/#basic-streaming) — use `chunk.content` for the text delta. Chunks also carry `model_id`, `tool_calls`, `thinking`, and token usage on the final chunk.
|
|
583
|
+
|
|
584
|
+
### Stored Callback (`on_content:`)
|
|
585
|
+
|
|
586
|
+
Wire streaming at build time. The callback fires on every `run()` call automatically:
|
|
506
587
|
|
|
507
588
|
```ruby
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
589
|
+
robot = RobotLab.build(
|
|
590
|
+
name: "assistant",
|
|
591
|
+
system_prompt: "You are helpful.",
|
|
592
|
+
on_content: ->(chunk) { print chunk.content }
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
robot.run("Tell me a story") # streams automatically
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Per-Call Block
|
|
599
|
+
|
|
600
|
+
Pass a block to `run()` for one-off streaming:
|
|
601
|
+
|
|
602
|
+
```ruby
|
|
603
|
+
robot = RobotLab.build(name: "assistant", system_prompt: "You are helpful.")
|
|
604
|
+
|
|
605
|
+
robot.run("Tell me a story") { |chunk| print chunk.content }
|
|
606
|
+
```
|
|
516
607
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
608
|
+
### Both Together
|
|
609
|
+
|
|
610
|
+
When both a stored callback and a runtime block are provided, both fire (stored first):
|
|
611
|
+
|
|
612
|
+
```ruby
|
|
613
|
+
robot = RobotLab.build(
|
|
614
|
+
name: "assistant",
|
|
615
|
+
system_prompt: "You are helpful.",
|
|
616
|
+
on_content: ->(chunk) { log_chunk(chunk.content) }
|
|
522
617
|
)
|
|
618
|
+
|
|
619
|
+
robot.run("Tell me a story") { |chunk| stream_to_client(chunk.content) }
|
|
523
620
|
```
|
|
524
621
|
|
|
622
|
+
The `on_content:` callback participates in the RunConfig cascade, so it can be set at the network or config level and inherited by robots.
|
|
623
|
+
|
|
525
624
|
## Rails Integration
|
|
526
625
|
|
|
527
626
|
```bash
|
data/Rakefile
CHANGED
|
@@ -52,7 +52,12 @@ namespace :examples do
|
|
|
52
52
|
"16_writers_room" => "writers_room.rb"
|
|
53
53
|
}.freeze
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
# Subdirectory demos that are standalone apps (not run via `ruby`)
|
|
56
|
+
STANDALONE_APPS = {
|
|
57
|
+
"18_rails" => { setup: "bin/setup", run: "bin/dev" }
|
|
58
|
+
}.freeze
|
|
59
|
+
|
|
60
|
+
desc "Run all examples (excludes standalone apps like 18_rails)"
|
|
56
61
|
task :all do
|
|
57
62
|
# Single-file examples
|
|
58
63
|
Dir.glob("examples/*.rb").sort.each do |example|
|
|
@@ -72,6 +77,15 @@ namespace :examples do
|
|
|
72
77
|
puts '=' * 60
|
|
73
78
|
ruby path
|
|
74
79
|
end
|
|
80
|
+
|
|
81
|
+
# Remind about standalone apps
|
|
82
|
+
STANDALONE_APPS.each do |dir, commands|
|
|
83
|
+
puts "\n#{'=' * 60}"
|
|
84
|
+
puts "Skipped: examples/#{dir} (standalone app)"
|
|
85
|
+
puts " Setup: cd examples/#{dir} && #{commands[:setup]}"
|
|
86
|
+
puts " Run: cd examples/#{dir} && #{commands[:run]}"
|
|
87
|
+
puts '=' * 60
|
|
88
|
+
end
|
|
75
89
|
end
|
|
76
90
|
|
|
77
91
|
desc "Run a specific example by number (e.g., rake examples:run[1])"
|
|
@@ -89,6 +103,16 @@ namespace :examples do
|
|
|
89
103
|
dir = Dir.glob("examples/#{padded}_*/").first
|
|
90
104
|
if dir
|
|
91
105
|
dir_name = File.basename(dir)
|
|
106
|
+
|
|
107
|
+
# Check if it's a standalone app
|
|
108
|
+
if STANDALONE_APPS.key?(dir_name)
|
|
109
|
+
commands = STANDALONE_APPS[dir_name]
|
|
110
|
+
puts "Example #{args[:num]} is a standalone app (#{dir_name})."
|
|
111
|
+
puts " Setup: cd examples/#{dir_name} && #{commands[:setup]}"
|
|
112
|
+
puts " Run: cd examples/#{dir_name} && #{commands[:run]}"
|
|
113
|
+
next
|
|
114
|
+
end
|
|
115
|
+
|
|
92
116
|
entry = SUBDIR_ENTRY_POINTS[dir_name]
|
|
93
117
|
if entry && File.exist?(File.join(dir, entry))
|
|
94
118
|
ruby File.join(dir, entry)
|
|
@@ -99,6 +123,20 @@ namespace :examples do
|
|
|
99
123
|
puts "Example #{args[:num]} not found"
|
|
100
124
|
end
|
|
101
125
|
end
|
|
126
|
+
|
|
127
|
+
desc "Setup the Rails demo app (example 18)"
|
|
128
|
+
task :rails_setup do
|
|
129
|
+
Dir.chdir("examples/18_rails") do
|
|
130
|
+
sh "bin/setup"
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
desc "Start the Rails demo app (example 18)"
|
|
135
|
+
task :rails do
|
|
136
|
+
Dir.chdir("examples/18_rails") do
|
|
137
|
+
sh "bin/dev"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
102
140
|
end
|
|
103
141
|
|
|
104
142
|
namespace :docs do
|